Commit | Line | Data |
---|---|---|
b4506261 LD |
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); | |
a038c3aa | 48 | rbuff[4] = bin2bcd(tm->tm_mon + 1); |
b4506261 LD |
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); | |
a038c3aa | 58 | tm->tm_mon = bcd2bin(rbuff[4] & 0x1F) - 1; |
b4506261 LD |
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 | ||
75465c49 | 201 | as3722_rtc->rtc = devm_rtc_device_register(&pdev->dev, "as3722-rtc", |
b4506261 LD |
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 | ||
75465c49 | 212 | ret = devm_request_threaded_irq(&pdev->dev, as3722_rtc->alarm_irq, NULL, |
ddf7059c | 213 | as3722_alarm_irq, IRQF_ONESHOT, |
b4506261 LD |
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); | |
75465c49 | 218 | return ret; |
b4506261 LD |
219 | } |
220 | disable_irq(as3722_rtc->alarm_irq); | |
221 | return 0; | |
b4506261 LD |
222 | } |
223 | ||
224 | #ifdef CONFIG_PM_SLEEP | |
225 | static int as3722_rtc_suspend(struct device *dev) | |
226 | { | |
227 | struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); | |
228 | ||
229 | if (device_may_wakeup(dev)) | |
230 | enable_irq_wake(as3722_rtc->alarm_irq); | |
231 | ||
232 | return 0; | |
233 | } | |
234 | ||
235 | static int as3722_rtc_resume(struct device *dev) | |
236 | { | |
237 | struct as3722_rtc *as3722_rtc = dev_get_drvdata(dev); | |
238 | ||
239 | if (device_may_wakeup(dev)) | |
240 | disable_irq_wake(as3722_rtc->alarm_irq); | |
241 | return 0; | |
242 | } | |
243 | #endif | |
244 | ||
d55255cb JH |
245 | static SIMPLE_DEV_PM_OPS(as3722_rtc_pm_ops, as3722_rtc_suspend, |
246 | as3722_rtc_resume); | |
b4506261 LD |
247 | |
248 | static struct platform_driver as3722_rtc_driver = { | |
249 | .probe = as3722_rtc_probe, | |
b4506261 LD |
250 | .driver = { |
251 | .name = "as3722-rtc", | |
252 | .pm = &as3722_rtc_pm_ops, | |
253 | }, | |
254 | }; | |
255 | module_platform_driver(as3722_rtc_driver); | |
256 | ||
257 | MODULE_DESCRIPTION("RTC driver for AS3722 PMICs"); | |
258 | MODULE_ALIAS("platform:as3722-rtc"); | |
259 | MODULE_AUTHOR("Florian Lobmaier <florian.lobmaier@ams.com>"); | |
260 | MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); | |
261 | MODULE_LICENSE("GPL"); |