]>
Commit | Line | Data |
---|---|---|
ae930c91 AF |
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); |