diff options
author | Andreas Färber <afaerber@suse.de> | 2017-09-04 18:53:23 -0400 |
---|---|---|
committer | Alexandre Belloni <alexandre.belloni@free-electrons.com> | 2017-09-05 03:55:02 -0400 |
commit | ae930c912b3274d79319179c1f5ddd9423dd0a7d (patch) | |
tree | cff04e305d00946f0312b1d459fffd5325e2c248 | |
parent | 69c9d96ce60b3a78ea4f311faed96f0e24ff655a (diff) |
rtc: Add Realtek RTD1295
Based on QNAP's arch/arm/mach-rtk119x/driver/rtk_rtc_drv.c code and
mach-rtk119x/driver/dc2vo/fpga/include/mis_reg.h register definitions.
The base year 2014 was observed on all of Zidoo X9S, ProBox2 Ava and
Beelink Lake I.
Signed-off-by: Andreas Färber <afaerber@suse.de>
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
-rw-r--r-- | drivers/rtc/Kconfig | 8 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-rtd119x.c | 242 |
3 files changed, 251 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index a76a26e2292a..e0e58f3b1420 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -1765,6 +1765,14 @@ config RTC_DRV_CPCAP | |||
1765 | Say y here for CPCAP rtc found on some Motorola phones | 1765 | Say y here for CPCAP rtc found on some Motorola phones |
1766 | and tablets such as Droid 4. | 1766 | and tablets such as Droid 4. |
1767 | 1767 | ||
1768 | config RTC_DRV_RTD119X | ||
1769 | bool "Realtek RTD129x RTC" | ||
1770 | depends on ARCH_REALTEK || COMPILE_TEST | ||
1771 | default ARCH_REALTEK | ||
1772 | help | ||
1773 | If you say yes here, you get support for the RTD1295 SoC | ||
1774 | Real Time Clock. | ||
1775 | |||
1768 | comment "HID Sensor RTC drivers" | 1776 | comment "HID Sensor RTC drivers" |
1769 | 1777 | ||
1770 | config RTC_DRV_HID_SENSOR_TIME | 1778 | config RTC_DRV_HID_SENSOR_TIME |
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index d995d49d8218..7230014c92af 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile | |||
@@ -131,6 +131,7 @@ obj-$(CONFIG_RTC_DRV_RP5C01) += rtc-rp5c01.o | |||
131 | obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o | 131 | obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o |
132 | obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o | 132 | obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o |
133 | obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o | 133 | obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o |
134 | obj-$(CONFIG_RTC_DRV_RTD119X) += rtc-rtd119x.o | ||
134 | obj-$(CONFIG_RTC_DRV_RV3029C2) += rtc-rv3029c2.o | 135 | obj-$(CONFIG_RTC_DRV_RV3029C2) += rtc-rv3029c2.o |
135 | obj-$(CONFIG_RTC_DRV_RV8803) += rtc-rv8803.o | 136 | obj-$(CONFIG_RTC_DRV_RV8803) += rtc-rv8803.o |
136 | obj-$(CONFIG_RTC_DRV_RX4581) += rtc-rx4581.o | 137 | obj-$(CONFIG_RTC_DRV_RX4581) += rtc-rx4581.o |
diff --git a/drivers/rtc/rtc-rtd119x.c b/drivers/rtc/rtc-rtd119x.c new file mode 100644 index 000000000000..b233559d950b --- /dev/null +++ b/drivers/rtc/rtc-rtd119x.c | |||
@@ -0,0 +1,242 @@ | |||
1 | /* | ||
2 | * Realtek RTD129x RTC | ||
3 | * | ||
4 | * Copyright (c) 2017 Andreas Färber | ||
5 | * | ||
6 | * SPDX-License-Identifier: GPL-2.0+ | ||
7 | */ | ||
8 | |||
9 | #include <linux/clk.h> | ||
10 | #include <linux/io.h> | ||
11 | #include <linux/module.h> | ||
12 | #include <linux/of.h> | ||
13 | #include <linux/of_address.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/rtc.h> | ||
16 | #include <linux/spinlock.h> | ||
17 | |||
18 | #define RTD_RTCSEC 0x00 | ||
19 | #define RTD_RTCMIN 0x04 | ||
20 | #define RTD_RTCHR 0x08 | ||
21 | #define RTD_RTCDATE1 0x0c | ||
22 | #define RTD_RTCDATE2 0x10 | ||
23 | #define RTD_RTCACR 0x28 | ||
24 | #define RTD_RTCEN 0x2c | ||
25 | #define RTD_RTCCR 0x30 | ||
26 | |||
27 | #define RTD_RTCSEC_RTCSEC_MASK 0x7f | ||
28 | |||
29 | #define RTD_RTCMIN_RTCMIN_MASK 0x3f | ||
30 | |||
31 | #define RTD_RTCHR_RTCHR_MASK 0x1f | ||
32 | |||
33 | #define RTD_RTCDATE1_RTCDATE1_MASK 0xff | ||
34 | |||
35 | #define RTD_RTCDATE2_RTCDATE2_MASK 0x7f | ||
36 | |||
37 | #define RTD_RTCACR_RTCPWR BIT(7) | ||
38 | |||
39 | #define RTD_RTCEN_RTCEN_MASK 0xff | ||
40 | |||
41 | #define RTD_RTCCR_RTCRST BIT(6) | ||
42 | |||
43 | struct rtd119x_rtc { | ||
44 | void __iomem *base; | ||
45 | struct clk *clk; | ||
46 | struct rtc_device *rtcdev; | ||
47 | unsigned int base_year; | ||
48 | }; | ||
49 | |||
50 | static inline int rtd119x_rtc_days_in_year(int year) | ||
51 | { | ||
52 | return 365 + (is_leap_year(year) ? 1 : 0); | ||
53 | } | ||
54 | |||
55 | static void rtd119x_rtc_reset(struct device *dev) | ||
56 | { | ||
57 | struct rtd119x_rtc *data = dev_get_drvdata(dev); | ||
58 | u32 val; | ||
59 | |||
60 | val = readl_relaxed(data->base + RTD_RTCCR); | ||
61 | val |= RTD_RTCCR_RTCRST; | ||
62 | writel_relaxed(val, data->base + RTD_RTCCR); | ||
63 | |||
64 | val &= ~RTD_RTCCR_RTCRST; | ||
65 | writel(val, data->base + RTD_RTCCR); | ||
66 | } | ||
67 | |||
68 | static void rtd119x_rtc_set_enabled(struct device *dev, bool enable) | ||
69 | { | ||
70 | struct rtd119x_rtc *data = dev_get_drvdata(dev); | ||
71 | u32 val; | ||
72 | |||
73 | val = readl_relaxed(data->base + RTD_RTCEN); | ||
74 | if (enable) { | ||
75 | if ((val & RTD_RTCEN_RTCEN_MASK) == 0x5a) | ||
76 | return; | ||
77 | writel_relaxed(0x5a, data->base + RTD_RTCEN); | ||
78 | } else { | ||
79 | writel_relaxed(0, data->base + RTD_RTCEN); | ||
80 | } | ||
81 | } | ||
82 | |||
83 | static int rtd119x_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
84 | { | ||
85 | struct rtd119x_rtc *data = dev_get_drvdata(dev); | ||
86 | s32 day; | ||
87 | u32 sec; | ||
88 | unsigned int year; | ||
89 | int tries = 0; | ||
90 | |||
91 | while (true) { | ||
92 | tm->tm_sec = (readl_relaxed(data->base + RTD_RTCSEC) & RTD_RTCSEC_RTCSEC_MASK) >> 1; | ||
93 | tm->tm_min = readl_relaxed(data->base + RTD_RTCMIN) & RTD_RTCMIN_RTCMIN_MASK; | ||
94 | tm->tm_hour = readl_relaxed(data->base + RTD_RTCHR) & RTD_RTCHR_RTCHR_MASK; | ||
95 | day = readl_relaxed(data->base + RTD_RTCDATE1) & RTD_RTCDATE1_RTCDATE1_MASK; | ||
96 | day |= (readl_relaxed(data->base + RTD_RTCDATE2) & RTD_RTCDATE2_RTCDATE2_MASK) << 8; | ||
97 | sec = (readl_relaxed(data->base + RTD_RTCSEC) & RTD_RTCSEC_RTCSEC_MASK) >> 1; | ||
98 | tries++; | ||
99 | |||
100 | if (sec == tm->tm_sec) | ||
101 | break; | ||
102 | |||
103 | if (tries >= 3) | ||
104 | return -EINVAL; | ||
105 | } | ||
106 | if (tries > 1) | ||
107 | dev_dbg(dev, "%s: needed %i tries\n", __func__, tries); | ||
108 | |||
109 | year = data->base_year; | ||
110 | while (day >= rtd119x_rtc_days_in_year(year)) { | ||
111 | day -= rtd119x_rtc_days_in_year(year); | ||
112 | year++; | ||
113 | } | ||
114 | tm->tm_year = year - 1900; | ||
115 | tm->tm_yday = day; | ||
116 | |||
117 | tm->tm_mon = 0; | ||
118 | while (day >= rtc_month_days(tm->tm_mon, year)) { | ||
119 | day -= rtc_month_days(tm->tm_mon, year); | ||
120 | tm->tm_mon++; | ||
121 | } | ||
122 | tm->tm_mday = day + 1; | ||
123 | |||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | static int rtd119x_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
128 | { | ||
129 | struct rtd119x_rtc *data = dev_get_drvdata(dev); | ||
130 | unsigned int day; | ||
131 | int i; | ||
132 | |||
133 | if (1900 + tm->tm_year < data->base_year) | ||
134 | return -EINVAL; | ||
135 | |||
136 | day = 0; | ||
137 | for (i = data->base_year; i < 1900 + tm->tm_year; i++) | ||
138 | day += rtd119x_rtc_days_in_year(i); | ||
139 | |||
140 | day += tm->tm_yday; | ||
141 | if (day > 0x7fff) | ||
142 | return -EINVAL; | ||
143 | |||
144 | rtd119x_rtc_set_enabled(dev, false); | ||
145 | |||
146 | writel_relaxed((tm->tm_sec << 1) & RTD_RTCSEC_RTCSEC_MASK, data->base + RTD_RTCSEC); | ||
147 | writel_relaxed(tm->tm_min & RTD_RTCMIN_RTCMIN_MASK, data->base + RTD_RTCMIN); | ||
148 | writel_relaxed(tm->tm_hour & RTD_RTCHR_RTCHR_MASK, data->base + RTD_RTCHR); | ||
149 | writel_relaxed(day & RTD_RTCDATE1_RTCDATE1_MASK, data->base + RTD_RTCDATE1); | ||
150 | writel_relaxed((day >> 8) & RTD_RTCDATE2_RTCDATE2_MASK, data->base + RTD_RTCDATE2); | ||
151 | |||
152 | rtd119x_rtc_set_enabled(dev, true); | ||
153 | |||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | static const struct rtc_class_ops rtd119x_rtc_ops = { | ||
158 | .read_time = rtd119x_rtc_read_time, | ||
159 | .set_time = rtd119x_rtc_set_time, | ||
160 | }; | ||
161 | |||
162 | static const struct of_device_id rtd119x_rtc_dt_ids[] = { | ||
163 | { .compatible = "realtek,rtd1295-rtc" }, | ||
164 | { } | ||
165 | }; | ||
166 | |||
167 | static int rtd119x_rtc_probe(struct platform_device *pdev) | ||
168 | { | ||
169 | struct rtd119x_rtc *data; | ||
170 | struct resource *res; | ||
171 | u32 val; | ||
172 | int ret; | ||
173 | |||
174 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); | ||
175 | if (!data) | ||
176 | return -ENOMEM; | ||
177 | |||
178 | platform_set_drvdata(pdev, data); | ||
179 | data->base_year = 2014; | ||
180 | |||
181 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
182 | data->base = devm_ioremap_resource(&pdev->dev, res); | ||
183 | if (IS_ERR(data->base)) | ||
184 | return PTR_ERR(data->base); | ||
185 | |||
186 | data->clk = of_clk_get(pdev->dev.of_node, 0); | ||
187 | if (IS_ERR(data->clk)) | ||
188 | return PTR_ERR(data->clk); | ||
189 | |||
190 | ret = clk_prepare_enable(data->clk); | ||
191 | if (ret) { | ||
192 | clk_put(data->clk); | ||
193 | return ret; | ||
194 | } | ||
195 | |||
196 | val = readl_relaxed(data->base + RTD_RTCACR); | ||
197 | if (!(val & RTD_RTCACR_RTCPWR)) { | ||
198 | writel_relaxed(RTD_RTCACR_RTCPWR, data->base + RTD_RTCACR); | ||
199 | |||
200 | rtd119x_rtc_reset(&pdev->dev); | ||
201 | |||
202 | writel_relaxed(0, data->base + RTD_RTCMIN); | ||
203 | writel_relaxed(0, data->base + RTD_RTCHR); | ||
204 | writel_relaxed(0, data->base + RTD_RTCDATE1); | ||
205 | writel_relaxed(0, data->base + RTD_RTCDATE2); | ||
206 | } | ||
207 | |||
208 | rtd119x_rtc_set_enabled(&pdev->dev, true); | ||
209 | |||
210 | data->rtcdev = devm_rtc_device_register(&pdev->dev, "rtc", | ||
211 | &rtd119x_rtc_ops, THIS_MODULE); | ||
212 | if (IS_ERR(data->rtcdev)) { | ||
213 | dev_err(&pdev->dev, "failed to register rtc device"); | ||
214 | clk_disable_unprepare(data->clk); | ||
215 | clk_put(data->clk); | ||
216 | return PTR_ERR(data->rtcdev); | ||
217 | } | ||
218 | |||
219 | return 0; | ||
220 | } | ||
221 | |||
222 | static int rtd119x_rtc_remove(struct platform_device *pdev) | ||
223 | { | ||
224 | struct rtd119x_rtc *data = platform_get_drvdata(pdev); | ||
225 | |||
226 | rtd119x_rtc_set_enabled(&pdev->dev, false); | ||
227 | |||
228 | clk_disable_unprepare(data->clk); | ||
229 | clk_put(data->clk); | ||
230 | |||
231 | return 0; | ||
232 | } | ||
233 | |||
234 | static struct platform_driver rtd119x_rtc_driver = { | ||
235 | .probe = rtd119x_rtc_probe, | ||
236 | .remove = rtd119x_rtc_remove, | ||
237 | .driver = { | ||
238 | .name = "rtd1295-rtc", | ||
239 | .of_match_table = rtd119x_rtc_dt_ids, | ||
240 | }, | ||
241 | }; | ||
242 | builtin_platform_driver(rtd119x_rtc_driver); | ||