diff options
author | Laxman Dewangan <ldewangan@nvidia.com> | 2013-11-12 18:11:05 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-11-12 22:09:31 -0500 |
commit | b45062619840a56c090bab57df7b0fb36a30c2af (patch) | |
tree | 38af14fb0190a5386132741b55e6861ebf550c9f /drivers/rtc | |
parent | 5bccae6ec4587044779f0b8e6fcb8f87db4181f0 (diff) |
drivers/rtc/rtc-as3722: add RTC driver
The ams AS3722 is a compact system PMU suitable for mobile phones, tablets
etc.
Add a driver to support accessing the RTC found on the ams AS3722 PMIC
using RTC framework.
[akpm@linux-foundation.org: coding-style fixes]
Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Signed-off-by: Florian Lobmaier <florian.lobmaier@ams.com>
Cc: Mark Brown <broonie@kernel.org>
Cc: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-as3722.c | 275 |
3 files changed, 286 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 4f48b9a26aa0..15f166a470a7 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -153,6 +153,16 @@ config RTC_DRV_88PM80X | |||
153 | This driver can also be built as a module. If so, the module | 153 | This driver can also be built as a module. If so, the module |
154 | will be called rtc-88pm80x. | 154 | will be called rtc-88pm80x. |
155 | 155 | ||
156 | config RTC_DRV_AS3722 | ||
157 | tristate "ams AS3722 RTC driver" | ||
158 | depends on MFD_AS3722 | ||
159 | help | ||
160 | If you say yes here you get support for the RTC of ams AS3722 PMIC | ||
161 | chips. | ||
162 | |||
163 | This driver can also be built as a module. If so, the module | ||
164 | will be called rtc-as3722. | ||
165 | |||
156 | config RTC_DRV_DS1307 | 166 | config RTC_DRV_DS1307 |
157 | tristate "Dallas/Maxim DS1307/37/38/39/40, ST M41T00, EPSON RX-8025" | 167 | tristate "Dallas/Maxim DS1307/37/38/39/40, ST M41T00, EPSON RX-8025" |
158 | help | 168 | help |
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 9312e7965365..27b4bd884066 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile | |||
@@ -20,6 +20,7 @@ obj-$(CONFIG_RTC_DRV_88PM860X) += rtc-88pm860x.o | |||
20 | obj-$(CONFIG_RTC_DRV_88PM80X) += rtc-88pm80x.o | 20 | obj-$(CONFIG_RTC_DRV_88PM80X) += rtc-88pm80x.o |
21 | obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o | 21 | obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o |
22 | obj-$(CONFIG_RTC_DRV_AB8500) += rtc-ab8500.o | 22 | obj-$(CONFIG_RTC_DRV_AB8500) += rtc-ab8500.o |
23 | obj-$(CONFIG_RTC_DRV_AS3722) += rtc-as3722.o | ||
23 | obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o | 24 | obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o |
24 | obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o | 25 | obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o |
25 | obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o | 26 | obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o |
diff --git a/drivers/rtc/rtc-as3722.c b/drivers/rtc/rtc-as3722.c new file mode 100644 index 000000000000..9cfa8170a2d6 --- /dev/null +++ b/drivers/rtc/rtc-as3722.c | |||
@@ -0,0 +1,275 @@ | |||
1 | /* | ||
2 | * rtc-as3722.c - Real Time Clock driver for ams AS3722 PMICs | ||
3 | * | ||
4 | * Copyright (C) 2013 ams AG | ||
5 | * Copyright (c) 2013, NVIDIA Corporation. All rights reserved. | ||
6 | * | ||
7 | * Author: Florian Lobmaier <florian.lobmaier@ams.com> | ||
8 | * Author: Laxman Dewangan <ldewangan@nvidia.com> | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License as published by | ||
12 | * the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU General Public License for more details. | ||
19 | */ | ||
20 | |||
21 | #include <linux/bcd.h> | ||
22 | #include <linux/completion.h> | ||
23 | #include <linux/delay.h> | ||
24 | #include <linux/interrupt.h> | ||
25 | #include <linux/ioctl.h> | ||
26 | #include <linux/kernel.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/mfd/as3722.h> | ||
29 | #include <linux/platform_device.h> | ||
30 | #include <linux/rtc.h> | ||
31 | #include <linux/time.h> | ||
32 | |||
33 | #define AS3722_RTC_START_YEAR 2000 | ||
34 | struct as3722_rtc { | ||
35 | struct rtc_device *rtc; | ||
36 | struct device *dev; | ||
37 | struct as3722 *as3722; | ||
38 | int alarm_irq; | ||
39 | bool irq_enable; | ||
40 | }; | ||
41 | |||
42 | static void as3722_time_to_reg(u8 *rbuff, struct rtc_time *tm) | ||
43 | { | ||
44 | rbuff[0] = bin2bcd(tm->tm_sec); | ||
45 | rbuff[1] = bin2bcd(tm->tm_min); | ||
46 | rbuff[2] = bin2bcd(tm->tm_hour); | ||
47 | rbuff[3] = bin2bcd(tm->tm_mday); | ||
48 | rbuff[4] = bin2bcd(tm->tm_mon); | ||
49 | rbuff[5] = bin2bcd(tm->tm_year - (AS3722_RTC_START_YEAR - 1900)); | ||
50 | } | ||
51 | |||
52 | static void as3722_reg_to_time(u8 *rbuff, struct rtc_time *tm) | ||
53 | { | ||
54 | tm->tm_sec = bcd2bin(rbuff[0] & 0x7F); | ||
55 | tm->tm_min = bcd2bin(rbuff[1] & 0x7F); | ||
56 | tm->tm_hour = bcd2bin(rbuff[2] & 0x3F); | ||
57 | tm->tm_mday = bcd2bin(rbuff[3] & 0x3F); | ||
58 | tm->tm_mon = bcd2bin(rbuff[4] & 0x1F); | ||
59 | tm->tm_year = (AS3722_RTC_START_YEAR - 1900) + bcd2bin(rbuff[5] & 0x7F); | ||
60 | return; | ||
61 | } | ||
62 | |||
63 | static int as3722_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
64 | { | ||
65 | struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); | ||
66 | struct as3722 *as3722 = as3722_rtc->as3722; | ||
67 | u8 as_time_array[6]; | ||
68 | int ret; | ||
69 | |||
70 | ret = as3722_block_read(as3722, AS3722_RTC_SECOND_REG, | ||
71 | 6, as_time_array); | ||
72 | if (ret < 0) { | ||
73 | dev_err(dev, "RTC_SECOND reg block read failed %d\n", ret); | ||
74 | return ret; | ||
75 | } | ||
76 | as3722_reg_to_time(as_time_array, tm); | ||
77 | return 0; | ||
78 | } | ||
79 | |||
80 | static int as3722_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
81 | { | ||
82 | struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); | ||
83 | struct as3722 *as3722 = as3722_rtc->as3722; | ||
84 | u8 as_time_array[6]; | ||
85 | int ret; | ||
86 | |||
87 | if (tm->tm_year < (AS3722_RTC_START_YEAR - 1900)) | ||
88 | return -EINVAL; | ||
89 | |||
90 | as3722_time_to_reg(as_time_array, tm); | ||
91 | ret = as3722_block_write(as3722, AS3722_RTC_SECOND_REG, 6, | ||
92 | as_time_array); | ||
93 | if (ret < 0) | ||
94 | dev_err(dev, "RTC_SECOND reg block write failed %d\n", ret); | ||
95 | return ret; | ||
96 | } | ||
97 | |||
98 | static int as3722_rtc_alarm_irq_enable(struct device *dev, | ||
99 | unsigned int enabled) | ||
100 | { | ||
101 | struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); | ||
102 | |||
103 | if (enabled && !as3722_rtc->irq_enable) { | ||
104 | enable_irq(as3722_rtc->alarm_irq); | ||
105 | as3722_rtc->irq_enable = true; | ||
106 | } else if (!enabled && as3722_rtc->irq_enable) { | ||
107 | disable_irq(as3722_rtc->alarm_irq); | ||
108 | as3722_rtc->irq_enable = false; | ||
109 | } | ||
110 | return 0; | ||
111 | } | ||
112 | |||
113 | static int as3722_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) | ||
114 | { | ||
115 | struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); | ||
116 | struct as3722 *as3722 = as3722_rtc->as3722; | ||
117 | u8 as_time_array[6]; | ||
118 | int ret; | ||
119 | |||
120 | ret = as3722_block_read(as3722, AS3722_RTC_ALARM_SECOND_REG, 6, | ||
121 | as_time_array); | ||
122 | if (ret < 0) { | ||
123 | dev_err(dev, "RTC_ALARM_SECOND block read failed %d\n", ret); | ||
124 | return ret; | ||
125 | } | ||
126 | |||
127 | as3722_reg_to_time(as_time_array, &alrm->time); | ||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int as3722_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) | ||
132 | { | ||
133 | struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); | ||
134 | struct as3722 *as3722 = as3722_rtc->as3722; | ||
135 | u8 as_time_array[6]; | ||
136 | int ret; | ||
137 | |||
138 | if (alrm->time.tm_year < (AS3722_RTC_START_YEAR - 1900)) | ||
139 | return -EINVAL; | ||
140 | |||
141 | ret = as3722_rtc_alarm_irq_enable(dev, 0); | ||
142 | if (ret < 0) { | ||
143 | dev_err(dev, "Disable RTC alarm failed\n"); | ||
144 | return ret; | ||
145 | } | ||
146 | |||
147 | as3722_time_to_reg(as_time_array, &alrm->time); | ||
148 | ret = as3722_block_write(as3722, AS3722_RTC_ALARM_SECOND_REG, 6, | ||
149 | as_time_array); | ||
150 | if (ret < 0) { | ||
151 | dev_err(dev, "RTC_ALARM_SECOND block write failed %d\n", ret); | ||
152 | return ret; | ||
153 | } | ||
154 | |||
155 | if (alrm->enabled) | ||
156 | ret = as3722_rtc_alarm_irq_enable(dev, alrm->enabled); | ||
157 | return ret; | ||
158 | } | ||
159 | |||
160 | static irqreturn_t as3722_alarm_irq(int irq, void *data) | ||
161 | { | ||
162 | struct as3722_rtc *as3722_rtc = data; | ||
163 | |||
164 | rtc_update_irq(as3722_rtc->rtc, 1, RTC_IRQF | RTC_AF); | ||
165 | return IRQ_HANDLED; | ||
166 | } | ||
167 | |||
168 | static const struct rtc_class_ops as3722_rtc_ops = { | ||
169 | .read_time = as3722_rtc_read_time, | ||
170 | .set_time = as3722_rtc_set_time, | ||
171 | .read_alarm = as3722_rtc_read_alarm, | ||
172 | .set_alarm = as3722_rtc_set_alarm, | ||
173 | .alarm_irq_enable = as3722_rtc_alarm_irq_enable, | ||
174 | }; | ||
175 | |||
176 | static int as3722_rtc_probe(struct platform_device *pdev) | ||
177 | { | ||
178 | struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent); | ||
179 | struct as3722_rtc *as3722_rtc; | ||
180 | int ret; | ||
181 | |||
182 | as3722_rtc = devm_kzalloc(&pdev->dev, sizeof(*as3722_rtc), GFP_KERNEL); | ||
183 | if (!as3722_rtc) | ||
184 | return -ENOMEM; | ||
185 | |||
186 | as3722_rtc->as3722 = as3722; | ||
187 | as3722_rtc->dev = &pdev->dev; | ||
188 | platform_set_drvdata(pdev, as3722_rtc); | ||
189 | |||
190 | /* Enable the RTC to make sure it is running. */ | ||
191 | ret = as3722_update_bits(as3722, AS3722_RTC_CONTROL_REG, | ||
192 | AS3722_RTC_ON | AS3722_RTC_ALARM_WAKEUP_EN, | ||
193 | AS3722_RTC_ON | AS3722_RTC_ALARM_WAKEUP_EN); | ||
194 | if (ret < 0) { | ||
195 | dev_err(&pdev->dev, "RTC_CONTROL reg write failed: %d\n", ret); | ||
196 | return ret; | ||
197 | } | ||
198 | |||
199 | device_init_wakeup(&pdev->dev, 1); | ||
200 | |||
201 | as3722_rtc->rtc = rtc_device_register("as3722", &pdev->dev, | ||
202 | &as3722_rtc_ops, THIS_MODULE); | ||
203 | if (IS_ERR(as3722_rtc->rtc)) { | ||
204 | ret = PTR_ERR(as3722_rtc->rtc); | ||
205 | dev_err(&pdev->dev, "RTC register failed: %d\n", ret); | ||
206 | return ret; | ||
207 | } | ||
208 | |||
209 | as3722_rtc->alarm_irq = platform_get_irq(pdev, 0); | ||
210 | dev_info(&pdev->dev, "RTC interrupt %d\n", as3722_rtc->alarm_irq); | ||
211 | |||
212 | ret = request_threaded_irq(as3722_rtc->alarm_irq, NULL, | ||
213 | as3722_alarm_irq, IRQF_ONESHOT | IRQF_EARLY_RESUME, | ||
214 | "rtc-alarm", as3722_rtc); | ||
215 | if (ret < 0) { | ||
216 | dev_err(&pdev->dev, "Failed to request alarm IRQ %d: %d\n", | ||
217 | as3722_rtc->alarm_irq, ret); | ||
218 | goto scrub; | ||
219 | } | ||
220 | disable_irq(as3722_rtc->alarm_irq); | ||
221 | return 0; | ||
222 | scrub: | ||
223 | rtc_device_unregister(as3722_rtc->rtc); | ||
224 | return ret; | ||
225 | } | ||
226 | |||
227 | static int as3722_rtc_remove(struct platform_device *pdev) | ||
228 | { | ||
229 | struct as3722_rtc *as3722_rtc = platform_get_drvdata(pdev); | ||
230 | |||
231 | free_irq(as3722_rtc->alarm_irq, as3722_rtc); | ||
232 | rtc_device_unregister(as3722_rtc->rtc); | ||
233 | return 0; | ||
234 | } | ||
235 | |||
236 | #ifdef CONFIG_PM_SLEEP | ||
237 | static int as3722_rtc_suspend(struct device *dev) | ||
238 | { | ||
239 | struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); | ||
240 | |||
241 | if (device_may_wakeup(dev)) | ||
242 | enable_irq_wake(as3722_rtc->alarm_irq); | ||
243 | |||
244 | return 0; | ||
245 | } | ||
246 | |||
247 | static int as3722_rtc_resume(struct device *dev) | ||
248 | { | ||
249 | struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); | ||
250 | |||
251 | if (device_may_wakeup(dev)) | ||
252 | disable_irq_wake(as3722_rtc->alarm_irq); | ||
253 | return 0; | ||
254 | } | ||
255 | #endif | ||
256 | |||
257 | static const struct dev_pm_ops as3722_rtc_pm_ops = { | ||
258 | SET_SYSTEM_SLEEP_PM_OPS(as3722_rtc_suspend, as3722_rtc_resume) | ||
259 | }; | ||
260 | |||
261 | static struct platform_driver as3722_rtc_driver = { | ||
262 | .probe = as3722_rtc_probe, | ||
263 | .remove = as3722_rtc_remove, | ||
264 | .driver = { | ||
265 | .name = "as3722-rtc", | ||
266 | .pm = &as3722_rtc_pm_ops, | ||
267 | }, | ||
268 | }; | ||
269 | module_platform_driver(as3722_rtc_driver); | ||
270 | |||
271 | MODULE_DESCRIPTION("RTC driver for AS3722 PMICs"); | ||
272 | MODULE_ALIAS("platform:as3722-rtc"); | ||
273 | MODULE_AUTHOR("Florian Lobmaier <florian.lobmaier@ams.com>"); | ||
274 | MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); | ||
275 | MODULE_LICENSE("GPL"); | ||