summaryrefslogtreecommitdiffstats
path: root/drivers/rtc/rtc-jz4740.c
blob: 95885357d9795e0dada29b9db90b5f24c3c13bb0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/*
 *  JZ4740 SoC RTC driver
 *
 *  This code was ported from linux-3.15 kernel by Antony Pavlov.
 *
 *  Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
 *  Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net>
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under  the terms of  the GNU General Public License as published by the
 *  Free Software Foundation;  either version 2 of the License, or (at your
 *  option) any later version.
 *
 */

#include <common.h>
#include <init.h>
#include <driver.h>
#include <xfuncs.h>
#include <errno.h>
#include <io.h>
#include <rtc.h>
#include <linux/rtc.h>

#define JZ_REG_RTC_CTRL		0x00
#define JZ_REG_RTC_SEC		0x04
#define JZ_REG_RTC_SEC_ALARM	0x08
#define JZ_REG_RTC_REGULATOR	0x0C
#define JZ_REG_RTC_HIBERNATE	0x20
#define JZ_REG_RTC_SCRATCHPAD	0x34

#define JZ_RTC_CTRL_WRDY	BIT(7)

struct jz4740_rtc {
	struct rtc_device rtc;

	void __iomem *base;
};

static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc, size_t reg)
{
	return readl(rtc->base + reg);
}

static int jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc)
{
	uint32_t ctrl;
	int timeout = 1000;

	do {
		ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
	} while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);

	return timeout ? 0 : -EIO;
}

static inline int jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t reg,
	uint32_t val)
{
	int ret;
	ret = jz4740_rtc_wait_write_ready(rtc);
	if (ret == 0)
		writel(val, rtc->base + reg);

	return ret;
}

static inline struct jz4740_rtc *to_jz4740_rtc_priv(struct rtc_device *rtcdev)
{
	return container_of(rtcdev, struct jz4740_rtc, rtc);
}

static int jz4740_rtc_read_time(struct rtc_device *rtcdev, struct rtc_time *time)
{
	struct jz4740_rtc *rtc = to_jz4740_rtc_priv(rtcdev);
	uint32_t secs, secs2;
	int timeout = 5;

	/* If the seconds register is read while it is updated, it can contain a
	 * bogus value. This can be avoided by making sure that two consecutive
	 * reads have the same value.
	 */
	secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
	secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);

	while (secs != secs2 && --timeout) {
		secs = secs2;
		secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
	}

	if (timeout == 0)
		return -EIO;

	rtc_time_to_tm(secs, time);

	return rtc_valid_tm(time);
}

static int jz4740_rtc_set_time(struct rtc_device *rtcdev, struct rtc_time *t)
{
	struct jz4740_rtc *rtc = to_jz4740_rtc_priv(rtcdev);
	unsigned long secs;

	rtc_tm_to_time(t, &secs);

	return jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
}

static struct rtc_class_ops jz4740_rtc_ops = {
	.read_time	= jz4740_rtc_read_time,
	.set_time	= jz4740_rtc_set_time,
};

static int jz4740_rtc_probe(struct device_d *dev)
{
	struct resource *iores;
	int ret;
	struct jz4740_rtc *rtc;
	uint32_t scratchpad;
	void __iomem *base;

	iores = dev_request_mem_resource(dev, 0);
	if (IS_ERR(iores)) {
		dev_err(dev, "could not get memory region\n");
		return PTR_ERR(iores);
	}
	base = IOMEM(iores->start);

	rtc = xzalloc(sizeof(*rtc));

	rtc->base = base;
	rtc->rtc.ops = &jz4740_rtc_ops;
	rtc->rtc.dev = dev;

	ret = rtc_register(&rtc->rtc);
	if (ret) {
		dev_err(dev, "Failed to register rtc device: %d\n", ret);
		return ret;
	}

	scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD);
	if (scratchpad != 0x12345678) {
		ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD, 0x12345678);
		ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0);
		if (ret) {
			dev_err(dev, "Could not write write to RTC registers\n");
			return ret;
		}
	}

	return 0;
}

static __maybe_unused struct of_device_id jz4740_rtc_dt_ids[] = {
	{
		.compatible = "ingenic,jz4740-rtc",
	}, {
		/* sentinel */
	}
};

static struct driver_d jz4740_rtc_driver = {
	.name  = "jz4740-rtc",
	.probe	 = jz4740_rtc_probe,
	.of_compatible = DRV_OF_COMPAT(jz4740_rtc_dt_ids),
};
device_platform_driver(jz4740_rtc_driver);