diff options
author | Atsushi Nemoto <anemo@mba.ocn.ne.jp> | 2009-01-06 17:42:22 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-01-06 18:59:25 -0500 |
commit | 0e1492330cd2c95df2553335d7a77351021a938f (patch) | |
tree | 48f74bcaeb1a5f5fe5ea687de57b6435ca15e05e /drivers/rtc/rtc-tx4939.c | |
parent | bbccf83f6c4e1a0de5bdf51ec9ec708d3a1ce933 (diff) |
rtc: add rtc-tx4939 driver
Add support for RTC in TX4939 SoC.
Signed-off-by: Atsushi Nemoto <anemo@mba.ocn.ne.jp>
Acked-by: Alessandro Zummo <a.zummo@towertech.it>
Cc: David Brownell <david-b@pacbell.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/rtc/rtc-tx4939.c')
-rw-r--r-- | drivers/rtc/rtc-tx4939.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-tx4939.c b/drivers/rtc/rtc-tx4939.c new file mode 100644 index 000000000000..4ee4857ff207 --- /dev/null +++ b/drivers/rtc/rtc-tx4939.c | |||
@@ -0,0 +1,317 @@ | |||
1 | /* | ||
2 | * TX4939 internal RTC driver | ||
3 | * Based on RBTX49xx patch from CELF patch archive. | ||
4 | * | ||
5 | * This file is subject to the terms and conditions of the GNU General Public | ||
6 | * License. See the file "COPYING" in the main directory of this archive | ||
7 | * for more details. | ||
8 | * | ||
9 | * (C) Copyright TOSHIBA CORPORATION 2005-2007 | ||
10 | */ | ||
11 | #include <linux/rtc.h> | ||
12 | #include <linux/platform_device.h> | ||
13 | #include <linux/interrupt.h> | ||
14 | #include <linux/io.h> | ||
15 | #include <asm/txx9/tx4939.h> | ||
16 | |||
17 | struct tx4939rtc_plat_data { | ||
18 | struct rtc_device *rtc; | ||
19 | struct tx4939_rtc_reg __iomem *rtcreg; | ||
20 | }; | ||
21 | |||
22 | static struct tx4939rtc_plat_data *get_tx4939rtc_plat_data(struct device *dev) | ||
23 | { | ||
24 | return platform_get_drvdata(to_platform_device(dev)); | ||
25 | } | ||
26 | |||
27 | static int tx4939_rtc_cmd(struct tx4939_rtc_reg __iomem *rtcreg, int cmd) | ||
28 | { | ||
29 | int i = 0; | ||
30 | |||
31 | __raw_writel(cmd, &rtcreg->ctl); | ||
32 | /* This might take 30us (next 32.768KHz clock) */ | ||
33 | while (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_BUSY) { | ||
34 | /* timeout on approx. 100us (@ GBUS200MHz) */ | ||
35 | if (i++ > 200 * 100) | ||
36 | return -EBUSY; | ||
37 | cpu_relax(); | ||
38 | } | ||
39 | return 0; | ||
40 | } | ||
41 | |||
42 | static int tx4939_rtc_set_mmss(struct device *dev, unsigned long secs) | ||
43 | { | ||
44 | struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); | ||
45 | struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; | ||
46 | int i, ret; | ||
47 | unsigned char buf[6]; | ||
48 | |||
49 | buf[0] = 0; | ||
50 | buf[1] = 0; | ||
51 | buf[2] = secs; | ||
52 | buf[3] = secs >> 8; | ||
53 | buf[4] = secs >> 16; | ||
54 | buf[5] = secs >> 24; | ||
55 | spin_lock_irq(&pdata->rtc->irq_lock); | ||
56 | __raw_writel(0, &rtcreg->adr); | ||
57 | for (i = 0; i < 6; i++) | ||
58 | __raw_writel(buf[i], &rtcreg->dat); | ||
59 | ret = tx4939_rtc_cmd(rtcreg, | ||
60 | TX4939_RTCCTL_COMMAND_SETTIME | | ||
61 | (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALME)); | ||
62 | spin_unlock_irq(&pdata->rtc->irq_lock); | ||
63 | return ret; | ||
64 | } | ||
65 | |||
66 | static int tx4939_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
67 | { | ||
68 | struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); | ||
69 | struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; | ||
70 | int i, ret; | ||
71 | unsigned long sec; | ||
72 | unsigned char buf[6]; | ||
73 | |||
74 | spin_lock_irq(&pdata->rtc->irq_lock); | ||
75 | ret = tx4939_rtc_cmd(rtcreg, | ||
76 | TX4939_RTCCTL_COMMAND_GETTIME | | ||
77 | (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALME)); | ||
78 | if (ret) { | ||
79 | spin_unlock_irq(&pdata->rtc->irq_lock); | ||
80 | return ret; | ||
81 | } | ||
82 | __raw_writel(2, &rtcreg->adr); | ||
83 | for (i = 2; i < 6; i++) | ||
84 | buf[i] = __raw_readl(&rtcreg->dat); | ||
85 | spin_unlock_irq(&pdata->rtc->irq_lock); | ||
86 | sec = (buf[5] << 24) | (buf[4] << 16) | (buf[3] << 8) | buf[2]; | ||
87 | rtc_time_to_tm(sec, tm); | ||
88 | return rtc_valid_tm(tm); | ||
89 | } | ||
90 | |||
91 | static int tx4939_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) | ||
92 | { | ||
93 | struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); | ||
94 | struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; | ||
95 | int i, ret; | ||
96 | unsigned long sec; | ||
97 | unsigned char buf[6]; | ||
98 | |||
99 | if (alrm->time.tm_sec < 0 || | ||
100 | alrm->time.tm_min < 0 || | ||
101 | alrm->time.tm_hour < 0 || | ||
102 | alrm->time.tm_mday < 0 || | ||
103 | alrm->time.tm_mon < 0 || | ||
104 | alrm->time.tm_year < 0) | ||
105 | return -EINVAL; | ||
106 | rtc_tm_to_time(&alrm->time, &sec); | ||
107 | buf[0] = 0; | ||
108 | buf[1] = 0; | ||
109 | buf[2] = sec; | ||
110 | buf[3] = sec >> 8; | ||
111 | buf[4] = sec >> 16; | ||
112 | buf[5] = sec >> 24; | ||
113 | spin_lock_irq(&pdata->rtc->irq_lock); | ||
114 | __raw_writel(0, &rtcreg->adr); | ||
115 | for (i = 0; i < 6; i++) | ||
116 | __raw_writel(buf[i], &rtcreg->dat); | ||
117 | ret = tx4939_rtc_cmd(rtcreg, TX4939_RTCCTL_COMMAND_SETALARM | | ||
118 | (alrm->enabled ? TX4939_RTCCTL_ALME : 0)); | ||
119 | spin_unlock_irq(&pdata->rtc->irq_lock); | ||
120 | return ret; | ||
121 | } | ||
122 | |||
123 | static int tx4939_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) | ||
124 | { | ||
125 | struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); | ||
126 | struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; | ||
127 | int i, ret; | ||
128 | unsigned long sec; | ||
129 | unsigned char buf[6]; | ||
130 | u32 ctl; | ||
131 | |||
132 | spin_lock_irq(&pdata->rtc->irq_lock); | ||
133 | ret = tx4939_rtc_cmd(rtcreg, | ||
134 | TX4939_RTCCTL_COMMAND_GETALARM | | ||
135 | (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALME)); | ||
136 | if (ret) { | ||
137 | spin_unlock_irq(&pdata->rtc->irq_lock); | ||
138 | return ret; | ||
139 | } | ||
140 | __raw_writel(2, &rtcreg->adr); | ||
141 | for (i = 2; i < 6; i++) | ||
142 | buf[i] = __raw_readl(&rtcreg->dat); | ||
143 | ctl = __raw_readl(&rtcreg->ctl); | ||
144 | alrm->enabled = (ctl & TX4939_RTCCTL_ALME) ? 1 : 0; | ||
145 | alrm->pending = (ctl & TX4939_RTCCTL_ALMD) ? 1 : 0; | ||
146 | spin_unlock_irq(&pdata->rtc->irq_lock); | ||
147 | sec = (buf[5] << 24) | (buf[4] << 16) | (buf[3] << 8) | buf[2]; | ||
148 | rtc_time_to_tm(sec, &alrm->time); | ||
149 | return rtc_valid_tm(&alrm->time); | ||
150 | } | ||
151 | |||
152 | static int tx4939_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) | ||
153 | { | ||
154 | struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); | ||
155 | |||
156 | spin_lock_irq(&pdata->rtc->irq_lock); | ||
157 | tx4939_rtc_cmd(pdata->rtcreg, | ||
158 | TX4939_RTCCTL_COMMAND_NOP | | ||
159 | (enabled ? TX4939_RTCCTL_ALME : 0)); | ||
160 | spin_unlock_irq(&pdata->rtc->irq_lock); | ||
161 | return 0; | ||
162 | } | ||
163 | |||
164 | static irqreturn_t tx4939_rtc_interrupt(int irq, void *dev_id) | ||
165 | { | ||
166 | struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev_id); | ||
167 | struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; | ||
168 | unsigned long events = RTC_IRQF; | ||
169 | |||
170 | spin_lock(&pdata->rtc->irq_lock); | ||
171 | if (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALMD) { | ||
172 | events |= RTC_AF; | ||
173 | tx4939_rtc_cmd(rtcreg, TX4939_RTCCTL_COMMAND_NOP); | ||
174 | } | ||
175 | spin_unlock(&pdata->rtc->irq_lock); | ||
176 | rtc_update_irq(pdata->rtc, 1, events); | ||
177 | return IRQ_HANDLED; | ||
178 | } | ||
179 | |||
180 | static const struct rtc_class_ops tx4939_rtc_ops = { | ||
181 | .read_time = tx4939_rtc_read_time, | ||
182 | .read_alarm = tx4939_rtc_read_alarm, | ||
183 | .set_alarm = tx4939_rtc_set_alarm, | ||
184 | .set_mmss = tx4939_rtc_set_mmss, | ||
185 | .alarm_irq_enable = tx4939_rtc_alarm_irq_enable, | ||
186 | }; | ||
187 | |||
188 | static ssize_t tx4939_rtc_nvram_read(struct kobject *kobj, | ||
189 | struct bin_attribute *bin_attr, | ||
190 | char *buf, loff_t pos, size_t size) | ||
191 | { | ||
192 | struct device *dev = container_of(kobj, struct device, kobj); | ||
193 | struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); | ||
194 | struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; | ||
195 | ssize_t count; | ||
196 | |||
197 | spin_lock_irq(&pdata->rtc->irq_lock); | ||
198 | for (count = 0; size > 0 && pos < TX4939_RTC_REG_RAMSIZE; | ||
199 | count++, size--) { | ||
200 | __raw_writel(pos++, &rtcreg->adr); | ||
201 | *buf++ = __raw_readl(&rtcreg->dat); | ||
202 | } | ||
203 | spin_unlock_irq(&pdata->rtc->irq_lock); | ||
204 | return count; | ||
205 | } | ||
206 | |||
207 | static ssize_t tx4939_rtc_nvram_write(struct kobject *kobj, | ||
208 | struct bin_attribute *bin_attr, | ||
209 | char *buf, loff_t pos, size_t size) | ||
210 | { | ||
211 | struct device *dev = container_of(kobj, struct device, kobj); | ||
212 | struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); | ||
213 | struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; | ||
214 | ssize_t count; | ||
215 | |||
216 | spin_lock_irq(&pdata->rtc->irq_lock); | ||
217 | for (count = 0; size > 0 && pos < TX4939_RTC_REG_RAMSIZE; | ||
218 | count++, size--) { | ||
219 | __raw_writel(pos++, &rtcreg->adr); | ||
220 | __raw_writel(*buf++, &rtcreg->dat); | ||
221 | } | ||
222 | spin_unlock_irq(&pdata->rtc->irq_lock); | ||
223 | return count; | ||
224 | } | ||
225 | |||
226 | static struct bin_attribute tx4939_rtc_nvram_attr = { | ||
227 | .attr = { | ||
228 | .name = "nvram", | ||
229 | .mode = S_IRUGO | S_IWUSR, | ||
230 | }, | ||
231 | .size = TX4939_RTC_REG_RAMSIZE, | ||
232 | .read = tx4939_rtc_nvram_read, | ||
233 | .write = tx4939_rtc_nvram_write, | ||
234 | }; | ||
235 | |||
236 | static int __init tx4939_rtc_probe(struct platform_device *pdev) | ||
237 | { | ||
238 | struct rtc_device *rtc; | ||
239 | struct tx4939rtc_plat_data *pdata; | ||
240 | struct resource *res; | ||
241 | int irq, ret; | ||
242 | |||
243 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
244 | if (!res) | ||
245 | return -ENODEV; | ||
246 | irq = platform_get_irq(pdev, 0); | ||
247 | if (irq < 0) | ||
248 | return -ENODEV; | ||
249 | pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); | ||
250 | if (!pdata) | ||
251 | return -ENOMEM; | ||
252 | platform_set_drvdata(pdev, pdata); | ||
253 | |||
254 | if (!devm_request_mem_region(&pdev->dev, res->start, | ||
255 | resource_size(res), pdev->name)) | ||
256 | return -EBUSY; | ||
257 | pdata->rtcreg = devm_ioremap(&pdev->dev, res->start, | ||
258 | resource_size(res)); | ||
259 | if (!pdata->rtcreg) | ||
260 | return -EBUSY; | ||
261 | |||
262 | tx4939_rtc_cmd(pdata->rtcreg, TX4939_RTCCTL_COMMAND_NOP); | ||
263 | if (devm_request_irq(&pdev->dev, irq, tx4939_rtc_interrupt, | ||
264 | IRQF_DISABLED | IRQF_SHARED, | ||
265 | pdev->name, &pdev->dev) < 0) { | ||
266 | return -EBUSY; | ||
267 | } | ||
268 | rtc = rtc_device_register(pdev->name, &pdev->dev, | ||
269 | &tx4939_rtc_ops, THIS_MODULE); | ||
270 | if (IS_ERR(rtc)) | ||
271 | return PTR_ERR(rtc); | ||
272 | pdata->rtc = rtc; | ||
273 | ret = sysfs_create_bin_file(&pdev->dev.kobj, &tx4939_rtc_nvram_attr); | ||
274 | if (ret) | ||
275 | rtc_device_unregister(rtc); | ||
276 | return ret; | ||
277 | } | ||
278 | |||
279 | static int __exit tx4939_rtc_remove(struct platform_device *pdev) | ||
280 | { | ||
281 | struct tx4939rtc_plat_data *pdata = platform_get_drvdata(pdev); | ||
282 | struct rtc_device *rtc = pdata->rtc; | ||
283 | |||
284 | spin_lock_irq(&rtc->irq_lock); | ||
285 | tx4939_rtc_cmd(pdata->rtcreg, TX4939_RTCCTL_COMMAND_NOP); | ||
286 | spin_unlock_irq(&rtc->irq_lock); | ||
287 | sysfs_remove_bin_file(&pdev->dev.kobj, &tx4939_rtc_nvram_attr); | ||
288 | rtc_device_unregister(rtc); | ||
289 | platform_set_drvdata(pdev, NULL); | ||
290 | return 0; | ||
291 | } | ||
292 | |||
293 | static struct platform_driver tx4939_rtc_driver = { | ||
294 | .remove = __exit_p(tx4939_rtc_remove), | ||
295 | .driver = { | ||
296 | .name = "tx4939rtc", | ||
297 | .owner = THIS_MODULE, | ||
298 | }, | ||
299 | }; | ||
300 | |||
301 | static int __init tx4939rtc_init(void) | ||
302 | { | ||
303 | return platform_driver_probe(&tx4939_rtc_driver, tx4939_rtc_probe); | ||
304 | } | ||
305 | |||
306 | static void __exit tx4939rtc_exit(void) | ||
307 | { | ||
308 | platform_driver_unregister(&tx4939_rtc_driver); | ||
309 | } | ||
310 | |||
311 | module_init(tx4939rtc_init); | ||
312 | module_exit(tx4939rtc_exit); | ||
313 | |||
314 | MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>"); | ||
315 | MODULE_DESCRIPTION("TX4939 internal RTC driver"); | ||
316 | MODULE_LICENSE("GPL"); | ||
317 | MODULE_ALIAS("platform:tx4939rtc"); | ||