diff options
author | Lee Jones <lee.jones@linaro.org> | 2015-04-09 10:47:33 -0400 |
---|---|---|
committer | Lee Jones <lee.jones@linaro.org> | 2015-04-30 08:21:31 -0400 |
commit | b5b2bdfc2893910fdc02d21ec5c535635c896ff7 (patch) | |
tree | 97f216688c9ae61c8e9175bd8c38f53571451dbc /drivers/rtc | |
parent | 5cb69745da35c372b3db001efbd3967b633ba0d1 (diff) |
rtc: st: Add new driver for ST's LPC RTC
ST's Low Power Controller (LPC) controls two devices; watchdog and RTC.
Only one of the devices can be used at any one time. This is enforced
by the correlating MFD driver. This portion of the driver-set controls
the Real Time Clock.
Cc: Alessandro Zummo <a.zummo@towertech.it>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 11 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-st-lpc.c | 354 |
3 files changed, 366 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 6149ae01e11f..8b8b332efaed 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -1510,6 +1510,17 @@ config RTC_DRV_SIRFSOC | |||
1510 | Say "yes" here to support the real time clock on SiRF SOC chips. | 1510 | Say "yes" here to support the real time clock on SiRF SOC chips. |
1511 | This driver can also be built as a module called rtc-sirfsoc. | 1511 | This driver can also be built as a module called rtc-sirfsoc. |
1512 | 1512 | ||
1513 | config RTC_DRV_ST_LPC | ||
1514 | tristate "STMicroelectronics LPC RTC" | ||
1515 | depends on ARCH_STI | ||
1516 | depends on OF | ||
1517 | help | ||
1518 | Say Y here to include STMicroelectronics Low Power Controller | ||
1519 | (LPC) based RTC support. | ||
1520 | |||
1521 | To compile this driver as a module, choose M here: the | ||
1522 | module will be called rtc-st-lpc. | ||
1523 | |||
1513 | config RTC_DRV_MOXART | 1524 | config RTC_DRV_MOXART |
1514 | tristate "MOXA ART RTC" | 1525 | tristate "MOXA ART RTC" |
1515 | depends on ARCH_MOXART || COMPILE_TEST | 1526 | depends on ARCH_MOXART || COMPILE_TEST |
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index c31731c29762..411e630f36b9 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile | |||
@@ -153,4 +153,5 @@ obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o | |||
153 | obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o | 153 | obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o |
154 | obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o | 154 | obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o |
155 | obj-$(CONFIG_RTC_DRV_SIRFSOC) += rtc-sirfsoc.o | 155 | obj-$(CONFIG_RTC_DRV_SIRFSOC) += rtc-sirfsoc.o |
156 | obj-$(CONFIG_RTC_DRV_ST_LPC) += rtc-st-lpc.o | ||
156 | obj-$(CONFIG_RTC_DRV_MOXART) += rtc-moxart.o | 157 | obj-$(CONFIG_RTC_DRV_MOXART) += rtc-moxart.o |
diff --git a/drivers/rtc/rtc-st-lpc.c b/drivers/rtc/rtc-st-lpc.c new file mode 100644 index 000000000000..3f9d0acb81c7 --- /dev/null +++ b/drivers/rtc/rtc-st-lpc.c | |||
@@ -0,0 +1,354 @@ | |||
1 | /* | ||
2 | * rtc-st-lpc.c - ST's LPC RTC, powered by the Low Power Timer | ||
3 | * | ||
4 | * Copyright (C) 2014 STMicroelectronics Limited | ||
5 | * | ||
6 | * Author: David Paris <david.paris@st.com> for STMicroelectronics | ||
7 | * Lee Jones <lee.jones@linaro.org> for STMicroelectronics | ||
8 | * | ||
9 | * Based on the original driver written by Stuart Menefy. | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public Licence | ||
13 | * as published by the Free Software Foundation; either version | ||
14 | * 2 of the Licence, or (at your option) any later version. | ||
15 | */ | ||
16 | |||
17 | #include <linux/clk.h> | ||
18 | #include <linux/delay.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/io.h> | ||
21 | #include <linux/irq.h> | ||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/of.h> | ||
25 | #include <linux/of_irq.h> | ||
26 | #include <linux/platform_device.h> | ||
27 | #include <linux/rtc.h> | ||
28 | |||
29 | #include <dt-bindings/mfd/st-lpc.h> | ||
30 | |||
31 | /* Low Power Timer */ | ||
32 | #define LPC_LPT_LSB_OFF 0x400 | ||
33 | #define LPC_LPT_MSB_OFF 0x404 | ||
34 | #define LPC_LPT_START_OFF 0x408 | ||
35 | |||
36 | /* Low Power Alarm */ | ||
37 | #define LPC_LPA_LSB_OFF 0x410 | ||
38 | #define LPC_LPA_MSB_OFF 0x414 | ||
39 | #define LPC_LPA_START_OFF 0x418 | ||
40 | |||
41 | /* LPC as WDT */ | ||
42 | #define LPC_WDT_OFF 0x510 | ||
43 | #define LPC_WDT_FLAG_OFF 0x514 | ||
44 | |||
45 | struct st_rtc { | ||
46 | struct rtc_device *rtc_dev; | ||
47 | struct rtc_wkalrm alarm; | ||
48 | struct resource *res; | ||
49 | struct clk *clk; | ||
50 | unsigned long clkrate; | ||
51 | void __iomem *ioaddr; | ||
52 | bool irq_enabled:1; | ||
53 | spinlock_t lock; | ||
54 | short irq; | ||
55 | }; | ||
56 | |||
57 | static void st_rtc_set_hw_alarm(struct st_rtc *rtc, | ||
58 | unsigned long msb, unsigned long lsb) | ||
59 | { | ||
60 | unsigned long flags; | ||
61 | |||
62 | spin_lock_irqsave(&rtc->lock, flags); | ||
63 | |||
64 | writel_relaxed(1, rtc->ioaddr + LPC_WDT_OFF); | ||
65 | |||
66 | writel_relaxed(msb, rtc->ioaddr + LPC_LPA_MSB_OFF); | ||
67 | writel_relaxed(lsb, rtc->ioaddr + LPC_LPA_LSB_OFF); | ||
68 | writel_relaxed(1, rtc->ioaddr + LPC_LPA_START_OFF); | ||
69 | |||
70 | writel_relaxed(0, rtc->ioaddr + LPC_WDT_OFF); | ||
71 | |||
72 | spin_unlock_irqrestore(&rtc->lock, flags); | ||
73 | } | ||
74 | |||
75 | static irqreturn_t st_rtc_handler(int this_irq, void *data) | ||
76 | { | ||
77 | struct st_rtc *rtc = (struct st_rtc *)data; | ||
78 | |||
79 | rtc_update_irq(rtc->rtc_dev, 1, RTC_AF); | ||
80 | |||
81 | return IRQ_HANDLED; | ||
82 | } | ||
83 | |||
84 | static int st_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
85 | { | ||
86 | struct st_rtc *rtc = dev_get_drvdata(dev); | ||
87 | unsigned long lpt_lsb, lpt_msb; | ||
88 | unsigned long long lpt; | ||
89 | unsigned long flags; | ||
90 | |||
91 | spin_lock_irqsave(&rtc->lock, flags); | ||
92 | |||
93 | do { | ||
94 | lpt_msb = readl_relaxed(rtc->ioaddr + LPC_LPT_MSB_OFF); | ||
95 | lpt_lsb = readl_relaxed(rtc->ioaddr + LPC_LPT_LSB_OFF); | ||
96 | } while (readl_relaxed(rtc->ioaddr + LPC_LPT_MSB_OFF) != lpt_msb); | ||
97 | |||
98 | spin_unlock_irqrestore(&rtc->lock, flags); | ||
99 | |||
100 | lpt = ((unsigned long long)lpt_msb << 32) | lpt_lsb; | ||
101 | do_div(lpt, rtc->clkrate); | ||
102 | rtc_time_to_tm(lpt, tm); | ||
103 | |||
104 | return 0; | ||
105 | } | ||
106 | |||
107 | static int st_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
108 | { | ||
109 | struct st_rtc *rtc = dev_get_drvdata(dev); | ||
110 | unsigned long long lpt; | ||
111 | unsigned long secs, flags; | ||
112 | int ret; | ||
113 | |||
114 | ret = rtc_tm_to_time(tm, &secs); | ||
115 | if (ret) | ||
116 | return ret; | ||
117 | |||
118 | lpt = (unsigned long long)secs * rtc->clkrate; | ||
119 | |||
120 | spin_lock_irqsave(&rtc->lock, flags); | ||
121 | |||
122 | writel_relaxed(lpt >> 32, rtc->ioaddr + LPC_LPT_MSB_OFF); | ||
123 | writel_relaxed(lpt, rtc->ioaddr + LPC_LPT_LSB_OFF); | ||
124 | writel_relaxed(1, rtc->ioaddr + LPC_LPT_START_OFF); | ||
125 | |||
126 | spin_unlock_irqrestore(&rtc->lock, flags); | ||
127 | |||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int st_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) | ||
132 | { | ||
133 | struct st_rtc *rtc = dev_get_drvdata(dev); | ||
134 | unsigned long flags; | ||
135 | |||
136 | spin_lock_irqsave(&rtc->lock, flags); | ||
137 | |||
138 | memcpy(wkalrm, &rtc->alarm, sizeof(struct rtc_wkalrm)); | ||
139 | |||
140 | spin_unlock_irqrestore(&rtc->lock, flags); | ||
141 | |||
142 | return 0; | ||
143 | } | ||
144 | |||
145 | static int st_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) | ||
146 | { | ||
147 | struct st_rtc *rtc = dev_get_drvdata(dev); | ||
148 | |||
149 | if (enabled && !rtc->irq_enabled) { | ||
150 | enable_irq(rtc->irq); | ||
151 | rtc->irq_enabled = true; | ||
152 | } else if (!enabled && rtc->irq_enabled) { | ||
153 | disable_irq(rtc->irq); | ||
154 | rtc->irq_enabled = false; | ||
155 | } | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static int st_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *t) | ||
161 | { | ||
162 | struct st_rtc *rtc = dev_get_drvdata(dev); | ||
163 | struct rtc_time now; | ||
164 | unsigned long now_secs; | ||
165 | unsigned long alarm_secs; | ||
166 | unsigned long long lpa; | ||
167 | |||
168 | st_rtc_read_time(dev, &now); | ||
169 | rtc_tm_to_time(&now, &now_secs); | ||
170 | rtc_tm_to_time(&t->time, &alarm_secs); | ||
171 | |||
172 | /* Invalid alarm time */ | ||
173 | if (now_secs > alarm_secs) | ||
174 | return -EINVAL; | ||
175 | |||
176 | memcpy(&rtc->alarm, t, sizeof(struct rtc_wkalrm)); | ||
177 | |||
178 | /* Now many secs to fire */ | ||
179 | alarm_secs -= now_secs; | ||
180 | lpa = (unsigned long long)alarm_secs * rtc->clkrate; | ||
181 | |||
182 | st_rtc_set_hw_alarm(rtc, lpa >> 32, lpa); | ||
183 | st_rtc_alarm_irq_enable(dev, t->enabled); | ||
184 | |||
185 | return 0; | ||
186 | } | ||
187 | |||
188 | static struct rtc_class_ops st_rtc_ops = { | ||
189 | .read_time = st_rtc_read_time, | ||
190 | .set_time = st_rtc_set_time, | ||
191 | .read_alarm = st_rtc_read_alarm, | ||
192 | .set_alarm = st_rtc_set_alarm, | ||
193 | .alarm_irq_enable = st_rtc_alarm_irq_enable, | ||
194 | }; | ||
195 | |||
196 | static int st_rtc_probe(struct platform_device *pdev) | ||
197 | { | ||
198 | struct device_node *np = pdev->dev.of_node; | ||
199 | struct st_rtc *rtc; | ||
200 | struct resource *res; | ||
201 | struct rtc_time tm_check; | ||
202 | uint32_t mode; | ||
203 | int ret = 0; | ||
204 | |||
205 | ret = of_property_read_u32(np, "st,lpc-mode", &mode); | ||
206 | if (ret) { | ||
207 | dev_err(&pdev->dev, "An LPC mode must be provided\n"); | ||
208 | return -EINVAL; | ||
209 | } | ||
210 | |||
211 | /* LPC can either run in RTC or WDT mode */ | ||
212 | if (mode != ST_LPC_MODE_RTC) | ||
213 | return -ENODEV; | ||
214 | |||
215 | rtc = devm_kzalloc(&pdev->dev, sizeof(struct st_rtc), GFP_KERNEL); | ||
216 | if (!rtc) | ||
217 | return -ENOMEM; | ||
218 | |||
219 | spin_lock_init(&rtc->lock); | ||
220 | |||
221 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
222 | rtc->ioaddr = devm_ioremap_resource(&pdev->dev, res); | ||
223 | if (IS_ERR(rtc->ioaddr)) | ||
224 | return PTR_ERR(rtc->ioaddr); | ||
225 | |||
226 | rtc->irq = irq_of_parse_and_map(np, 0); | ||
227 | if (!rtc->irq) { | ||
228 | dev_err(&pdev->dev, "IRQ missing or invalid\n"); | ||
229 | return -EINVAL; | ||
230 | } | ||
231 | |||
232 | ret = devm_request_irq(&pdev->dev, rtc->irq, st_rtc_handler, 0, | ||
233 | pdev->name, rtc); | ||
234 | if (ret) { | ||
235 | dev_err(&pdev->dev, "Failed to request irq %i\n", rtc->irq); | ||
236 | return ret; | ||
237 | } | ||
238 | |||
239 | enable_irq_wake(rtc->irq); | ||
240 | disable_irq(rtc->irq); | ||
241 | |||
242 | rtc->clk = clk_get(&pdev->dev, NULL); | ||
243 | if (IS_ERR(rtc->clk)) { | ||
244 | dev_err(&pdev->dev, "Unable to request clock\n"); | ||
245 | return PTR_ERR(rtc->clk); | ||
246 | } | ||
247 | |||
248 | clk_prepare_enable(rtc->clk); | ||
249 | |||
250 | rtc->clkrate = clk_get_rate(rtc->clk); | ||
251 | if (!rtc->clkrate) { | ||
252 | dev_err(&pdev->dev, "Unable to fetch clock rate\n"); | ||
253 | return -EINVAL; | ||
254 | } | ||
255 | |||
256 | device_set_wakeup_capable(&pdev->dev, 1); | ||
257 | |||
258 | platform_set_drvdata(pdev, rtc); | ||
259 | |||
260 | /* | ||
261 | * The RTC-LPC is able to manage date.year > 2038 | ||
262 | * but currently the kernel can not manage this date! | ||
263 | * If the RTC-LPC has a date.year > 2038 then | ||
264 | * it's set to the epoch "Jan 1st 2000" | ||
265 | */ | ||
266 | st_rtc_read_time(&pdev->dev, &tm_check); | ||
267 | |||
268 | if (tm_check.tm_year >= (2038 - 1900)) { | ||
269 | memset(&tm_check, 0, sizeof(tm_check)); | ||
270 | tm_check.tm_year = 100; | ||
271 | tm_check.tm_mday = 1; | ||
272 | st_rtc_set_time(&pdev->dev, &tm_check); | ||
273 | } | ||
274 | |||
275 | rtc->rtc_dev = rtc_device_register("st-lpc-rtc", &pdev->dev, | ||
276 | &st_rtc_ops, THIS_MODULE); | ||
277 | if (IS_ERR(rtc->rtc_dev)) { | ||
278 | clk_disable_unprepare(rtc->clk); | ||
279 | return PTR_ERR(rtc->rtc_dev); | ||
280 | } | ||
281 | |||
282 | return 0; | ||
283 | } | ||
284 | |||
285 | static int st_rtc_remove(struct platform_device *pdev) | ||
286 | { | ||
287 | struct st_rtc *rtc = platform_get_drvdata(pdev); | ||
288 | |||
289 | if (likely(rtc->rtc_dev)) | ||
290 | rtc_device_unregister(rtc->rtc_dev); | ||
291 | |||
292 | return 0; | ||
293 | } | ||
294 | |||
295 | #ifdef CONFIG_PM_SLEEP | ||
296 | static int st_rtc_suspend(struct device *dev) | ||
297 | { | ||
298 | struct st_rtc *rtc = dev_get_drvdata(dev); | ||
299 | |||
300 | if (device_may_wakeup(dev)) | ||
301 | return 0; | ||
302 | |||
303 | writel_relaxed(1, rtc->ioaddr + LPC_WDT_OFF); | ||
304 | writel_relaxed(0, rtc->ioaddr + LPC_LPA_START_OFF); | ||
305 | writel_relaxed(0, rtc->ioaddr + LPC_WDT_OFF); | ||
306 | |||
307 | return 0; | ||
308 | } | ||
309 | |||
310 | static int st_rtc_resume(struct device *dev) | ||
311 | { | ||
312 | struct st_rtc *rtc = dev_get_drvdata(dev); | ||
313 | |||
314 | rtc_alarm_irq_enable(rtc->rtc_dev, 0); | ||
315 | |||
316 | /* | ||
317 | * clean 'rtc->alarm' to allow a new | ||
318 | * .set_alarm to the upper RTC layer | ||
319 | */ | ||
320 | memset(&rtc->alarm, 0, sizeof(struct rtc_wkalrm)); | ||
321 | |||
322 | writel_relaxed(0, rtc->ioaddr + LPC_LPA_MSB_OFF); | ||
323 | writel_relaxed(0, rtc->ioaddr + LPC_LPA_LSB_OFF); | ||
324 | writel_relaxed(1, rtc->ioaddr + LPC_WDT_OFF); | ||
325 | writel_relaxed(1, rtc->ioaddr + LPC_LPA_START_OFF); | ||
326 | writel_relaxed(0, rtc->ioaddr + LPC_WDT_OFF); | ||
327 | |||
328 | return 0; | ||
329 | } | ||
330 | #endif | ||
331 | |||
332 | static SIMPLE_DEV_PM_OPS(st_rtc_pm_ops, st_rtc_suspend, st_rtc_resume); | ||
333 | |||
334 | static const struct of_device_id st_rtc_match[] = { | ||
335 | { .compatible = "st,stih407-lpc" }, | ||
336 | {} | ||
337 | }; | ||
338 | MODULE_DEVICE_TABLE(of, st_rtc_match); | ||
339 | |||
340 | static struct platform_driver st_rtc_platform_driver = { | ||
341 | .driver = { | ||
342 | .name = "st-lpc-rtc", | ||
343 | .pm = &st_rtc_pm_ops, | ||
344 | .of_match_table = st_rtc_match, | ||
345 | }, | ||
346 | .probe = st_rtc_probe, | ||
347 | .remove = st_rtc_remove, | ||
348 | }; | ||
349 | |||
350 | module_platform_driver(st_rtc_platform_driver); | ||
351 | |||
352 | MODULE_DESCRIPTION("STMicroelectronics LPC RTC driver"); | ||
353 | MODULE_AUTHOR("David Paris <david.paris@st.com>"); | ||
354 | MODULE_LICENSE("GPL"); | ||