diff options
author | Tianping Fang <tianping.fang@mediatek.com> | 2015-05-06 03:23:41 -0400 |
---|---|---|
committer | Alexandre Belloni <alexandre.belloni@free-electrons.com> | 2015-06-24 19:13:35 -0400 |
commit | fc2979118f3f5193475cb53d5df7bdaa7e358a42 (patch) | |
tree | b291b976349a19fc9f642bd67821739ef270488d | |
parent | a5d7ea0912cc35efb61bccb1f07432f25fce6281 (diff) |
rtc: mediatek: Add MT6397 RTC driver
Add Mediatek MT6397 RTC driver
Signed-off-by: Tianping Fang <tianping.fang@mediatek.com>
Signed-off-by: Eddie Huang <eddie.huang@mediatek.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
-rw-r--r-- | drivers/rtc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-mt6397.c | 394 |
3 files changed, 405 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index b957b4f67f0f..20a1f35dc0d5 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -1530,6 +1530,16 @@ config RTC_DRV_MOXART | |||
1530 | This driver can also be built as a module. If so, the module | 1530 | This driver can also be built as a module. If so, the module |
1531 | will be called rtc-moxart | 1531 | will be called rtc-moxart |
1532 | 1532 | ||
1533 | config RTC_DRV_MT6397 | ||
1534 | tristate "Mediatek Real Time Clock driver" | ||
1535 | depends on MFD_MT6397 || COMPILE_TEST | ||
1536 | help | ||
1537 | This selects the Mediatek(R) RTC driver. RTC is part of Mediatek | ||
1538 | MT6397 PMIC. You should enable MT6397 PMIC MFD before select | ||
1539 | Mediatek(R) RTC driver. | ||
1540 | |||
1541 | If you want to use Mediatek(R) RTC interface, select Y or M here. | ||
1542 | |||
1533 | config RTC_DRV_XGENE | 1543 | config RTC_DRV_XGENE |
1534 | tristate "APM X-Gene RTC" | 1544 | tristate "APM X-Gene RTC" |
1535 | depends on HAS_IOMEM | 1545 | depends on HAS_IOMEM |
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 2b82e2b0311b..371d97795fb5 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile | |||
@@ -155,3 +155,4 @@ obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o | |||
155 | obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o | 155 | obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o |
156 | obj-$(CONFIG_RTC_DRV_SIRFSOC) += rtc-sirfsoc.o | 156 | obj-$(CONFIG_RTC_DRV_SIRFSOC) += rtc-sirfsoc.o |
157 | obj-$(CONFIG_RTC_DRV_MOXART) += rtc-moxart.o | 157 | obj-$(CONFIG_RTC_DRV_MOXART) += rtc-moxart.o |
158 | obj-$(CONFIG_RTC_DRV_MT6397) += rtc-mt6397.o | ||
diff --git a/drivers/rtc/rtc-mt6397.c b/drivers/rtc/rtc-mt6397.c new file mode 100644 index 000000000000..8bed852e4961 --- /dev/null +++ b/drivers/rtc/rtc-mt6397.c | |||
@@ -0,0 +1,394 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2014-2015 MediaTek Inc. | ||
3 | * Author: Tianping.Fang <tianping.fang@mediatek.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | */ | ||
14 | |||
15 | #include <linux/delay.h> | ||
16 | #include <linux/init.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/regmap.h> | ||
19 | #include <linux/rtc.h> | ||
20 | #include <linux/irqdomain.h> | ||
21 | #include <linux/platform_device.h> | ||
22 | #include <linux/of_address.h> | ||
23 | #include <linux/of_irq.h> | ||
24 | #include <linux/io.h> | ||
25 | #include <linux/mfd/mt6397/core.h> | ||
26 | |||
27 | #define RTC_BBPU 0x0000 | ||
28 | #define RTC_BBPU_CBUSY BIT(6) | ||
29 | |||
30 | #define RTC_WRTGR 0x003c | ||
31 | |||
32 | #define RTC_IRQ_STA 0x0002 | ||
33 | #define RTC_IRQ_STA_AL BIT(0) | ||
34 | #define RTC_IRQ_STA_LP BIT(3) | ||
35 | |||
36 | #define RTC_IRQ_EN 0x0004 | ||
37 | #define RTC_IRQ_EN_AL BIT(0) | ||
38 | #define RTC_IRQ_EN_ONESHOT BIT(2) | ||
39 | #define RTC_IRQ_EN_LP BIT(3) | ||
40 | #define RTC_IRQ_EN_ONESHOT_AL (RTC_IRQ_EN_ONESHOT | RTC_IRQ_EN_AL) | ||
41 | |||
42 | #define RTC_AL_MASK 0x0008 | ||
43 | #define RTC_AL_MASK_DOW BIT(4) | ||
44 | |||
45 | #define RTC_TC_SEC 0x000a | ||
46 | /* Min, Hour, Dom... register offset to RTC_TC_SEC */ | ||
47 | #define RTC_OFFSET_SEC 0 | ||
48 | #define RTC_OFFSET_MIN 1 | ||
49 | #define RTC_OFFSET_HOUR 2 | ||
50 | #define RTC_OFFSET_DOM 3 | ||
51 | #define RTC_OFFSET_DOW 4 | ||
52 | #define RTC_OFFSET_MTH 5 | ||
53 | #define RTC_OFFSET_YEAR 6 | ||
54 | #define RTC_OFFSET_COUNT 7 | ||
55 | |||
56 | #define RTC_AL_SEC 0x0018 | ||
57 | |||
58 | #define RTC_PDN2 0x002e | ||
59 | #define RTC_PDN2_PWRON_ALARM BIT(4) | ||
60 | |||
61 | #define RTC_MIN_YEAR 1968 | ||
62 | #define RTC_BASE_YEAR 1900 | ||
63 | #define RTC_NUM_YEARS 128 | ||
64 | #define RTC_MIN_YEAR_OFFSET (RTC_MIN_YEAR - RTC_BASE_YEAR) | ||
65 | |||
66 | struct mt6397_rtc { | ||
67 | struct device *dev; | ||
68 | struct rtc_device *rtc_dev; | ||
69 | struct mutex lock; | ||
70 | struct regmap *regmap; | ||
71 | int irq; | ||
72 | u32 addr_base; | ||
73 | }; | ||
74 | |||
75 | static int mtk_rtc_write_trigger(struct mt6397_rtc *rtc) | ||
76 | { | ||
77 | unsigned long timeout = jiffies + HZ; | ||
78 | int ret; | ||
79 | u32 data; | ||
80 | |||
81 | ret = regmap_write(rtc->regmap, rtc->addr_base + RTC_WRTGR, 1); | ||
82 | if (ret < 0) | ||
83 | return ret; | ||
84 | |||
85 | while (1) { | ||
86 | ret = regmap_read(rtc->regmap, rtc->addr_base + RTC_BBPU, | ||
87 | &data); | ||
88 | if (ret < 0) | ||
89 | break; | ||
90 | if (!(data & RTC_BBPU_CBUSY)) | ||
91 | break; | ||
92 | if (time_after(jiffies, timeout)) { | ||
93 | ret = -ETIMEDOUT; | ||
94 | break; | ||
95 | } | ||
96 | cpu_relax(); | ||
97 | } | ||
98 | |||
99 | return ret; | ||
100 | } | ||
101 | |||
102 | static irqreturn_t mtk_rtc_irq_handler_thread(int irq, void *data) | ||
103 | { | ||
104 | struct mt6397_rtc *rtc = data; | ||
105 | u32 irqsta, irqen; | ||
106 | int ret; | ||
107 | |||
108 | ret = regmap_read(rtc->regmap, rtc->addr_base + RTC_IRQ_STA, &irqsta); | ||
109 | if ((ret >= 0) && (irqsta & RTC_IRQ_STA_AL)) { | ||
110 | rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF); | ||
111 | irqen = irqsta & ~RTC_IRQ_EN_AL; | ||
112 | mutex_lock(&rtc->lock); | ||
113 | if (regmap_write(rtc->regmap, rtc->addr_base + RTC_IRQ_EN, | ||
114 | irqen) < 0) | ||
115 | mtk_rtc_write_trigger(rtc); | ||
116 | mutex_unlock(&rtc->lock); | ||
117 | |||
118 | return IRQ_HANDLED; | ||
119 | } | ||
120 | |||
121 | return IRQ_NONE; | ||
122 | } | ||
123 | |||
124 | static int __mtk_rtc_read_time(struct mt6397_rtc *rtc, | ||
125 | struct rtc_time *tm, int *sec) | ||
126 | { | ||
127 | int ret; | ||
128 | u16 data[RTC_OFFSET_COUNT]; | ||
129 | |||
130 | mutex_lock(&rtc->lock); | ||
131 | ret = regmap_bulk_read(rtc->regmap, rtc->addr_base + RTC_TC_SEC, | ||
132 | data, RTC_OFFSET_COUNT); | ||
133 | if (ret < 0) | ||
134 | goto exit; | ||
135 | |||
136 | tm->tm_sec = data[RTC_OFFSET_SEC]; | ||
137 | tm->tm_min = data[RTC_OFFSET_MIN]; | ||
138 | tm->tm_hour = data[RTC_OFFSET_HOUR]; | ||
139 | tm->tm_mday = data[RTC_OFFSET_DOM]; | ||
140 | tm->tm_mon = data[RTC_OFFSET_MTH]; | ||
141 | tm->tm_year = data[RTC_OFFSET_YEAR]; | ||
142 | |||
143 | ret = regmap_read(rtc->regmap, rtc->addr_base + RTC_TC_SEC, sec); | ||
144 | exit: | ||
145 | mutex_unlock(&rtc->lock); | ||
146 | return ret; | ||
147 | } | ||
148 | |||
149 | static int mtk_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
150 | { | ||
151 | time64_t time; | ||
152 | struct mt6397_rtc *rtc = dev_get_drvdata(dev); | ||
153 | int sec, ret; | ||
154 | |||
155 | do { | ||
156 | ret = __mtk_rtc_read_time(rtc, tm, &sec); | ||
157 | if (ret < 0) | ||
158 | goto exit; | ||
159 | } while (sec < tm->tm_sec); | ||
160 | |||
161 | /* HW register use 7 bits to store year data, minus | ||
162 | * RTC_MIN_YEAR_OFFSET before write year data to register, and plus | ||
163 | * RTC_MIN_YEAR_OFFSET back after read year from register | ||
164 | */ | ||
165 | tm->tm_year += RTC_MIN_YEAR_OFFSET; | ||
166 | |||
167 | /* HW register start mon from one, but tm_mon start from zero. */ | ||
168 | tm->tm_mon--; | ||
169 | time = rtc_tm_to_time64(tm); | ||
170 | |||
171 | /* rtc_tm_to_time64 covert Gregorian date to seconds since | ||
172 | * 01-01-1970 00:00:00, and this date is Thursday. | ||
173 | */ | ||
174 | tm->tm_wday = (time / 86400 + 4) % 7; | ||
175 | |||
176 | exit: | ||
177 | return ret; | ||
178 | } | ||
179 | |||
180 | static int mtk_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
181 | { | ||
182 | struct mt6397_rtc *rtc = dev_get_drvdata(dev); | ||
183 | int ret; | ||
184 | u16 data[RTC_OFFSET_COUNT]; | ||
185 | |||
186 | tm->tm_year -= RTC_MIN_YEAR_OFFSET; | ||
187 | tm->tm_mon++; | ||
188 | |||
189 | data[RTC_OFFSET_SEC] = tm->tm_sec; | ||
190 | data[RTC_OFFSET_MIN] = tm->tm_min; | ||
191 | data[RTC_OFFSET_HOUR] = tm->tm_hour; | ||
192 | data[RTC_OFFSET_DOM] = tm->tm_mday; | ||
193 | data[RTC_OFFSET_MTH] = tm->tm_mon; | ||
194 | data[RTC_OFFSET_YEAR] = tm->tm_year; | ||
195 | |||
196 | mutex_lock(&rtc->lock); | ||
197 | ret = regmap_bulk_write(rtc->regmap, rtc->addr_base + RTC_TC_SEC, | ||
198 | data, RTC_OFFSET_COUNT); | ||
199 | if (ret < 0) | ||
200 | goto exit; | ||
201 | |||
202 | /* Time register write to hardware after call trigger function */ | ||
203 | ret = mtk_rtc_write_trigger(rtc); | ||
204 | |||
205 | exit: | ||
206 | mutex_unlock(&rtc->lock); | ||
207 | return ret; | ||
208 | } | ||
209 | |||
210 | static int mtk_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) | ||
211 | { | ||
212 | struct rtc_time *tm = &alm->time; | ||
213 | struct mt6397_rtc *rtc = dev_get_drvdata(dev); | ||
214 | u32 irqen, pdn2; | ||
215 | int ret; | ||
216 | u16 data[RTC_OFFSET_COUNT]; | ||
217 | |||
218 | mutex_lock(&rtc->lock); | ||
219 | ret = regmap_read(rtc->regmap, rtc->addr_base + RTC_IRQ_EN, &irqen); | ||
220 | if (ret < 0) | ||
221 | goto err_exit; | ||
222 | ret = regmap_read(rtc->regmap, rtc->addr_base + RTC_PDN2, &pdn2); | ||
223 | if (ret < 0) | ||
224 | goto err_exit; | ||
225 | |||
226 | ret = regmap_bulk_read(rtc->regmap, rtc->addr_base + RTC_AL_SEC, | ||
227 | data, RTC_OFFSET_COUNT); | ||
228 | if (ret < 0) | ||
229 | goto err_exit; | ||
230 | |||
231 | alm->enabled = !!(irqen & RTC_IRQ_EN_AL); | ||
232 | alm->pending = !!(pdn2 & RTC_PDN2_PWRON_ALARM); | ||
233 | mutex_unlock(&rtc->lock); | ||
234 | |||
235 | tm->tm_sec = data[RTC_OFFSET_SEC]; | ||
236 | tm->tm_min = data[RTC_OFFSET_MIN]; | ||
237 | tm->tm_hour = data[RTC_OFFSET_HOUR]; | ||
238 | tm->tm_mday = data[RTC_OFFSET_DOM]; | ||
239 | tm->tm_mon = data[RTC_OFFSET_MTH]; | ||
240 | tm->tm_year = data[RTC_OFFSET_YEAR]; | ||
241 | |||
242 | tm->tm_year += RTC_MIN_YEAR_OFFSET; | ||
243 | tm->tm_mon--; | ||
244 | |||
245 | return 0; | ||
246 | err_exit: | ||
247 | mutex_unlock(&rtc->lock); | ||
248 | return ret; | ||
249 | } | ||
250 | |||
251 | static int mtk_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) | ||
252 | { | ||
253 | struct rtc_time *tm = &alm->time; | ||
254 | struct mt6397_rtc *rtc = dev_get_drvdata(dev); | ||
255 | int ret; | ||
256 | u16 data[RTC_OFFSET_COUNT]; | ||
257 | |||
258 | tm->tm_year -= RTC_MIN_YEAR_OFFSET; | ||
259 | tm->tm_mon++; | ||
260 | |||
261 | data[RTC_OFFSET_SEC] = tm->tm_sec; | ||
262 | data[RTC_OFFSET_MIN] = tm->tm_min; | ||
263 | data[RTC_OFFSET_HOUR] = tm->tm_hour; | ||
264 | data[RTC_OFFSET_DOM] = tm->tm_mday; | ||
265 | data[RTC_OFFSET_MTH] = tm->tm_mon; | ||
266 | data[RTC_OFFSET_YEAR] = tm->tm_year; | ||
267 | |||
268 | mutex_lock(&rtc->lock); | ||
269 | if (alm->enabled) { | ||
270 | ret = regmap_bulk_write(rtc->regmap, | ||
271 | rtc->addr_base + RTC_AL_SEC, | ||
272 | data, RTC_OFFSET_COUNT); | ||
273 | if (ret < 0) | ||
274 | goto exit; | ||
275 | ret = regmap_write(rtc->regmap, rtc->addr_base + RTC_AL_MASK, | ||
276 | RTC_AL_MASK_DOW); | ||
277 | if (ret < 0) | ||
278 | goto exit; | ||
279 | ret = regmap_update_bits(rtc->regmap, | ||
280 | rtc->addr_base + RTC_IRQ_EN, | ||
281 | RTC_IRQ_EN_ONESHOT_AL, | ||
282 | RTC_IRQ_EN_ONESHOT_AL); | ||
283 | if (ret < 0) | ||
284 | goto exit; | ||
285 | } else { | ||
286 | ret = regmap_update_bits(rtc->regmap, | ||
287 | rtc->addr_base + RTC_IRQ_EN, | ||
288 | RTC_IRQ_EN_ONESHOT_AL, 0); | ||
289 | if (ret < 0) | ||
290 | goto exit; | ||
291 | } | ||
292 | |||
293 | /* All alarm time register write to hardware after calling | ||
294 | * mtk_rtc_write_trigger. This can avoid race condition if alarm | ||
295 | * occur happen during writing alarm time register. | ||
296 | */ | ||
297 | ret = mtk_rtc_write_trigger(rtc); | ||
298 | exit: | ||
299 | mutex_unlock(&rtc->lock); | ||
300 | return ret; | ||
301 | } | ||
302 | |||
303 | static struct rtc_class_ops mtk_rtc_ops = { | ||
304 | .read_time = mtk_rtc_read_time, | ||
305 | .set_time = mtk_rtc_set_time, | ||
306 | .read_alarm = mtk_rtc_read_alarm, | ||
307 | .set_alarm = mtk_rtc_set_alarm, | ||
308 | }; | ||
309 | |||
310 | static int mtk_rtc_probe(struct platform_device *pdev) | ||
311 | { | ||
312 | struct resource *res; | ||
313 | struct mt6397_chip *mt6397_chip = dev_get_drvdata(pdev->dev.parent); | ||
314 | struct mt6397_rtc *rtc; | ||
315 | int ret; | ||
316 | |||
317 | rtc = devm_kzalloc(&pdev->dev, sizeof(struct mt6397_rtc), GFP_KERNEL); | ||
318 | if (!rtc) | ||
319 | return -ENOMEM; | ||
320 | |||
321 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
322 | rtc->addr_base = res->start; | ||
323 | |||
324 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
325 | rtc->irq = irq_create_mapping(mt6397_chip->irq_domain, res->start); | ||
326 | if (rtc->irq <= 0) | ||
327 | return -EINVAL; | ||
328 | |||
329 | rtc->regmap = mt6397_chip->regmap; | ||
330 | rtc->dev = &pdev->dev; | ||
331 | mutex_init(&rtc->lock); | ||
332 | |||
333 | platform_set_drvdata(pdev, rtc); | ||
334 | |||
335 | ret = request_threaded_irq(rtc->irq, NULL, | ||
336 | mtk_rtc_irq_handler_thread, | ||
337 | IRQF_ONESHOT | IRQF_TRIGGER_HIGH, | ||
338 | "mt6397-rtc", rtc); | ||
339 | if (ret) { | ||
340 | dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n", | ||
341 | rtc->irq, ret); | ||
342 | goto out_dispose_irq; | ||
343 | } | ||
344 | |||
345 | rtc->rtc_dev = rtc_device_register("mt6397-rtc", &pdev->dev, | ||
346 | &mtk_rtc_ops, THIS_MODULE); | ||
347 | if (IS_ERR(rtc->rtc_dev)) { | ||
348 | dev_err(&pdev->dev, "register rtc device failed\n"); | ||
349 | ret = PTR_ERR(rtc->rtc_dev); | ||
350 | goto out_free_irq; | ||
351 | } | ||
352 | |||
353 | device_init_wakeup(&pdev->dev, 1); | ||
354 | |||
355 | return 0; | ||
356 | |||
357 | out_free_irq: | ||
358 | free_irq(rtc->irq, rtc->rtc_dev); | ||
359 | out_dispose_irq: | ||
360 | irq_dispose_mapping(rtc->irq); | ||
361 | return ret; | ||
362 | } | ||
363 | |||
364 | static int mtk_rtc_remove(struct platform_device *pdev) | ||
365 | { | ||
366 | struct mt6397_rtc *rtc = platform_get_drvdata(pdev); | ||
367 | |||
368 | rtc_device_unregister(rtc->rtc_dev); | ||
369 | free_irq(rtc->irq, rtc->rtc_dev); | ||
370 | irq_dispose_mapping(rtc->irq); | ||
371 | |||
372 | return 0; | ||
373 | } | ||
374 | |||
375 | static const struct of_device_id mt6397_rtc_of_match[] = { | ||
376 | { .compatible = "mediatek,mt6397-rtc", }, | ||
377 | { } | ||
378 | }; | ||
379 | |||
380 | static struct platform_driver mtk_rtc_driver = { | ||
381 | .driver = { | ||
382 | .name = "mt6397-rtc", | ||
383 | .of_match_table = mt6397_rtc_of_match, | ||
384 | }, | ||
385 | .probe = mtk_rtc_probe, | ||
386 | .remove = mtk_rtc_remove, | ||
387 | }; | ||
388 | |||
389 | module_platform_driver(mtk_rtc_driver); | ||
390 | |||
391 | MODULE_LICENSE("GPL v2"); | ||
392 | MODULE_AUTHOR("Tianping Fang <tianping.fang@mediatek.com>"); | ||
393 | MODULE_DESCRIPTION("RTC Driver for MediaTek MT6397 PMIC"); | ||
394 | MODULE_ALIAS("platform:mt6397-rtc"); | ||