Commit | Line | Data |
---|---|---|
3bf0eea8 LPC |
1 | /* |
2 | * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> | |
d0f744c8 | 3 | * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> |
3bf0eea8 LPC |
4 | * JZ4740 SoC RTC driver |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2 of the License, or (at your | |
9 | * option) any later version. | |
10 | * | |
11 | * You should have received a copy of the GNU General Public License along | |
12 | * with this program; if not, write to the Free Software Foundation, Inc., | |
13 | * 675 Mass Ave, Cambridge, MA 02139, USA. | |
14 | * | |
15 | */ | |
16 | ||
c08ac489 | 17 | #include <linux/io.h> |
3bf0eea8 LPC |
18 | #include <linux/kernel.h> |
19 | #include <linux/module.h> | |
20 | #include <linux/platform_device.h> | |
21 | #include <linux/rtc.h> | |
22 | #include <linux/slab.h> | |
23 | #include <linux/spinlock.h> | |
24 | ||
25 | #define JZ_REG_RTC_CTRL 0x00 | |
26 | #define JZ_REG_RTC_SEC 0x04 | |
27 | #define JZ_REG_RTC_SEC_ALARM 0x08 | |
28 | #define JZ_REG_RTC_REGULATOR 0x0C | |
29 | #define JZ_REG_RTC_HIBERNATE 0x20 | |
30 | #define JZ_REG_RTC_SCRATCHPAD 0x34 | |
31 | ||
32 | #define JZ_RTC_CTRL_WRDY BIT(7) | |
33 | #define JZ_RTC_CTRL_1HZ BIT(6) | |
34 | #define JZ_RTC_CTRL_1HZ_IRQ BIT(5) | |
35 | #define JZ_RTC_CTRL_AF BIT(4) | |
36 | #define JZ_RTC_CTRL_AF_IRQ BIT(3) | |
37 | #define JZ_RTC_CTRL_AE BIT(2) | |
38 | #define JZ_RTC_CTRL_ENABLE BIT(0) | |
39 | ||
40 | struct jz4740_rtc { | |
41 | struct resource *mem; | |
42 | void __iomem *base; | |
43 | ||
44 | struct rtc_device *rtc; | |
45 | ||
7c6a52a0 | 46 | int irq; |
3bf0eea8 LPC |
47 | |
48 | spinlock_t lock; | |
49 | }; | |
50 | ||
51 | static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc, size_t reg) | |
52 | { | |
53 | return readl(rtc->base + reg); | |
54 | } | |
55 | ||
56 | static int jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc) | |
57 | { | |
58 | uint32_t ctrl; | |
59 | int timeout = 1000; | |
60 | ||
61 | do { | |
62 | ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL); | |
63 | } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout); | |
64 | ||
65 | return timeout ? 0 : -EIO; | |
66 | } | |
67 | ||
68 | static inline int jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t reg, | |
69 | uint32_t val) | |
70 | { | |
71 | int ret; | |
72 | ret = jz4740_rtc_wait_write_ready(rtc); | |
73 | if (ret == 0) | |
74 | writel(val, rtc->base + reg); | |
75 | ||
76 | return ret; | |
77 | } | |
78 | ||
79 | static int jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc, uint32_t mask, | |
80 | bool set) | |
81 | { | |
82 | int ret; | |
83 | unsigned long flags; | |
84 | uint32_t ctrl; | |
85 | ||
86 | spin_lock_irqsave(&rtc->lock, flags); | |
87 | ||
88 | ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL); | |
89 | ||
90 | /* Don't clear interrupt flags by accident */ | |
91 | ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF; | |
92 | ||
93 | if (set) | |
94 | ctrl |= mask; | |
95 | else | |
96 | ctrl &= ~mask; | |
97 | ||
98 | ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl); | |
99 | ||
100 | spin_unlock_irqrestore(&rtc->lock, flags); | |
101 | ||
102 | return ret; | |
103 | } | |
104 | ||
105 | static int jz4740_rtc_read_time(struct device *dev, struct rtc_time *time) | |
106 | { | |
107 | struct jz4740_rtc *rtc = dev_get_drvdata(dev); | |
108 | uint32_t secs, secs2; | |
109 | int timeout = 5; | |
110 | ||
111 | /* If the seconds register is read while it is updated, it can contain a | |
112 | * bogus value. This can be avoided by making sure that two consecutive | |
113 | * reads have the same value. | |
114 | */ | |
115 | secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC); | |
116 | secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC); | |
117 | ||
118 | while (secs != secs2 && --timeout) { | |
119 | secs = secs2; | |
120 | secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC); | |
121 | } | |
122 | ||
123 | if (timeout == 0) | |
124 | return -EIO; | |
125 | ||
126 | rtc_time_to_tm(secs, time); | |
127 | ||
128 | return rtc_valid_tm(time); | |
129 | } | |
130 | ||
131 | static int jz4740_rtc_set_mmss(struct device *dev, unsigned long secs) | |
132 | { | |
133 | struct jz4740_rtc *rtc = dev_get_drvdata(dev); | |
134 | ||
135 | return jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs); | |
136 | } | |
137 | ||
138 | static int jz4740_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) | |
139 | { | |
140 | struct jz4740_rtc *rtc = dev_get_drvdata(dev); | |
141 | uint32_t secs; | |
142 | uint32_t ctrl; | |
143 | ||
144 | secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM); | |
145 | ||
146 | ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL); | |
147 | ||
148 | alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE); | |
149 | alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF); | |
150 | ||
151 | rtc_time_to_tm(secs, &alrm->time); | |
152 | ||
153 | return rtc_valid_tm(&alrm->time); | |
154 | } | |
155 | ||
156 | static int jz4740_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) | |
157 | { | |
158 | int ret; | |
159 | struct jz4740_rtc *rtc = dev_get_drvdata(dev); | |
160 | unsigned long secs; | |
161 | ||
162 | rtc_tm_to_time(&alrm->time, &secs); | |
163 | ||
164 | ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM, secs); | |
165 | if (!ret) | |
d0f744c8 PC |
166 | ret = jz4740_rtc_ctrl_set_bits(rtc, |
167 | JZ_RTC_CTRL_AE | JZ_RTC_CTRL_AF_IRQ, alrm->enabled); | |
3bf0eea8 LPC |
168 | |
169 | return ret; | |
170 | } | |
171 | ||
3bf0eea8 LPC |
172 | static int jz4740_rtc_alarm_irq_enable(struct device *dev, unsigned int enable) |
173 | { | |
174 | struct jz4740_rtc *rtc = dev_get_drvdata(dev); | |
175 | return jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AF_IRQ, enable); | |
176 | } | |
177 | ||
178 | static struct rtc_class_ops jz4740_rtc_ops = { | |
179 | .read_time = jz4740_rtc_read_time, | |
180 | .set_mmss = jz4740_rtc_set_mmss, | |
181 | .read_alarm = jz4740_rtc_read_alarm, | |
182 | .set_alarm = jz4740_rtc_set_alarm, | |
3bf0eea8 LPC |
183 | .alarm_irq_enable = jz4740_rtc_alarm_irq_enable, |
184 | }; | |
185 | ||
186 | static irqreturn_t jz4740_rtc_irq(int irq, void *data) | |
187 | { | |
188 | struct jz4740_rtc *rtc = data; | |
189 | uint32_t ctrl; | |
190 | unsigned long events = 0; | |
191 | ||
192 | ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL); | |
193 | ||
194 | if (ctrl & JZ_RTC_CTRL_1HZ) | |
195 | events |= (RTC_UF | RTC_IRQF); | |
196 | ||
197 | if (ctrl & JZ_RTC_CTRL_AF) | |
198 | events |= (RTC_AF | RTC_IRQF); | |
199 | ||
200 | rtc_update_irq(rtc->rtc, 1, events); | |
201 | ||
202 | jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF, false); | |
203 | ||
204 | return IRQ_HANDLED; | |
205 | } | |
206 | ||
207 | void jz4740_rtc_poweroff(struct device *dev) | |
208 | { | |
209 | struct jz4740_rtc *rtc = dev_get_drvdata(dev); | |
210 | jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1); | |
211 | } | |
212 | EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff); | |
213 | ||
5a167f45 | 214 | static int jz4740_rtc_probe(struct platform_device *pdev) |
3bf0eea8 LPC |
215 | { |
216 | int ret; | |
217 | struct jz4740_rtc *rtc; | |
218 | uint32_t scratchpad; | |
219 | ||
c08ac489 | 220 | rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); |
3bf0eea8 LPC |
221 | if (!rtc) |
222 | return -ENOMEM; | |
223 | ||
224 | rtc->irq = platform_get_irq(pdev, 0); | |
225 | if (rtc->irq < 0) { | |
3bf0eea8 | 226 | dev_err(&pdev->dev, "Failed to get platform irq\n"); |
c08ac489 | 227 | return -ENOENT; |
3bf0eea8 LPC |
228 | } |
229 | ||
230 | rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
231 | if (!rtc->mem) { | |
3bf0eea8 | 232 | dev_err(&pdev->dev, "Failed to get platform mmio memory\n"); |
c08ac489 | 233 | return -ENOENT; |
3bf0eea8 LPC |
234 | } |
235 | ||
c08ac489 JH |
236 | rtc->mem = devm_request_mem_region(&pdev->dev, rtc->mem->start, |
237 | resource_size(rtc->mem), pdev->name); | |
3bf0eea8 | 238 | if (!rtc->mem) { |
3bf0eea8 | 239 | dev_err(&pdev->dev, "Failed to request mmio memory region\n"); |
c08ac489 | 240 | return -EBUSY; |
3bf0eea8 LPC |
241 | } |
242 | ||
c08ac489 JH |
243 | rtc->base = devm_ioremap_nocache(&pdev->dev, rtc->mem->start, |
244 | resource_size(rtc->mem)); | |
3bf0eea8 | 245 | if (!rtc->base) { |
3bf0eea8 | 246 | dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); |
c08ac489 | 247 | return -EBUSY; |
3bf0eea8 LPC |
248 | } |
249 | ||
250 | spin_lock_init(&rtc->lock); | |
251 | ||
252 | platform_set_drvdata(pdev, rtc); | |
253 | ||
d0f744c8 PC |
254 | device_init_wakeup(&pdev->dev, 1); |
255 | ||
c08ac489 JH |
256 | rtc->rtc = devm_rtc_device_register(&pdev->dev, pdev->name, |
257 | &jz4740_rtc_ops, THIS_MODULE); | |
3bf0eea8 LPC |
258 | if (IS_ERR(rtc->rtc)) { |
259 | ret = PTR_ERR(rtc->rtc); | |
260 | dev_err(&pdev->dev, "Failed to register rtc device: %d\n", ret); | |
c08ac489 | 261 | return ret; |
3bf0eea8 LPC |
262 | } |
263 | ||
c08ac489 | 264 | ret = devm_request_irq(&pdev->dev, rtc->irq, jz4740_rtc_irq, 0, |
3bf0eea8 LPC |
265 | pdev->name, rtc); |
266 | if (ret) { | |
267 | dev_err(&pdev->dev, "Failed to request rtc irq: %d\n", ret); | |
c08ac489 | 268 | return ret; |
3bf0eea8 LPC |
269 | } |
270 | ||
271 | scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD); | |
272 | if (scratchpad != 0x12345678) { | |
273 | ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD, 0x12345678); | |
274 | ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0); | |
275 | if (ret) { | |
276 | dev_err(&pdev->dev, "Could not write write to RTC registers\n"); | |
c08ac489 | 277 | return ret; |
3bf0eea8 LPC |
278 | } |
279 | } | |
280 | ||
281 | return 0; | |
3bf0eea8 LPC |
282 | } |
283 | ||
d0f744c8 PC |
284 | #ifdef CONFIG_PM |
285 | static int jz4740_rtc_suspend(struct device *dev) | |
286 | { | |
287 | struct jz4740_rtc *rtc = dev_get_drvdata(dev); | |
288 | ||
289 | if (device_may_wakeup(dev)) | |
290 | enable_irq_wake(rtc->irq); | |
291 | return 0; | |
292 | } | |
293 | ||
294 | static int jz4740_rtc_resume(struct device *dev) | |
295 | { | |
296 | struct jz4740_rtc *rtc = dev_get_drvdata(dev); | |
297 | ||
298 | if (device_may_wakeup(dev)) | |
299 | disable_irq_wake(rtc->irq); | |
300 | return 0; | |
301 | } | |
302 | ||
303 | static const struct dev_pm_ops jz4740_pm_ops = { | |
304 | .suspend = jz4740_rtc_suspend, | |
305 | .resume = jz4740_rtc_resume, | |
306 | }; | |
307 | #define JZ4740_RTC_PM_OPS (&jz4740_pm_ops) | |
308 | ||
309 | #else | |
310 | #define JZ4740_RTC_PM_OPS NULL | |
311 | #endif /* CONFIG_PM */ | |
312 | ||
681d0378 | 313 | static struct platform_driver jz4740_rtc_driver = { |
d0f744c8 | 314 | .probe = jz4740_rtc_probe, |
d0f744c8 PC |
315 | .driver = { |
316 | .name = "jz4740-rtc", | |
3bf0eea8 | 317 | .owner = THIS_MODULE, |
d0f744c8 | 318 | .pm = JZ4740_RTC_PM_OPS, |
3bf0eea8 LPC |
319 | }, |
320 | }; | |
321 | ||
0c4eae66 | 322 | module_platform_driver(jz4740_rtc_driver); |
3bf0eea8 LPC |
323 | |
324 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); | |
325 | MODULE_LICENSE("GPL"); | |
326 | MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n"); | |
327 | MODULE_ALIAS("platform:jz4740-rtc"); |