Commit | Line | Data |
---|---|---|
fef931ff AJ |
1 | /* |
2 | * Real time clock driver for DA9052 | |
3 | * | |
4 | * Copyright(c) 2012 Dialog Semiconductor Ltd. | |
5 | * | |
6 | * Author: Dajun Dajun Chen <dajun.chen@diasemi.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include <linux/module.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/rtc.h> | |
11f54d05 | 18 | #include <linux/err.h> |
fef931ff AJ |
19 | |
20 | #include <linux/mfd/da9052/da9052.h> | |
21 | #include <linux/mfd/da9052/reg.h> | |
22 | ||
7c994c08 AO |
23 | #define rtc_err(rtc, fmt, ...) \ |
24 | dev_err(rtc->da9052->dev, "%s: " fmt, __func__, ##__VA_ARGS__) | |
fef931ff AJ |
25 | |
26 | struct da9052_rtc { | |
27 | struct rtc_device *rtc; | |
28 | struct da9052 *da9052; | |
fef931ff AJ |
29 | }; |
30 | ||
7c994c08 | 31 | static int da9052_rtc_enable_alarm(struct da9052_rtc *rtc, bool enable) |
fef931ff AJ |
32 | { |
33 | int ret; | |
34 | if (enable) { | |
7c994c08 AO |
35 | ret = da9052_reg_update(rtc->da9052, DA9052_ALARM_Y_REG, |
36 | DA9052_ALARM_Y_ALARM_ON|DA9052_ALARM_Y_TICK_ON, | |
37 | DA9052_ALARM_Y_ALARM_ON); | |
fef931ff | 38 | if (ret != 0) |
7c994c08 | 39 | rtc_err(rtc, "Failed to enable ALM: %d\n", ret); |
fef931ff | 40 | } else { |
7c994c08 AO |
41 | ret = da9052_reg_update(rtc->da9052, DA9052_ALARM_Y_REG, |
42 | DA9052_ALARM_Y_ALARM_ON|DA9052_ALARM_Y_TICK_ON, 0); | |
fef931ff | 43 | if (ret != 0) |
7c994c08 | 44 | rtc_err(rtc, "Write error: %d\n", ret); |
fef931ff AJ |
45 | } |
46 | return ret; | |
47 | } | |
48 | ||
49 | static irqreturn_t da9052_rtc_irq(int irq, void *data) | |
50 | { | |
51 | struct da9052_rtc *rtc = data; | |
fef931ff | 52 | |
7c994c08 | 53 | rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); |
fef931ff AJ |
54 | |
55 | return IRQ_HANDLED; | |
56 | } | |
57 | ||
7c994c08 | 58 | static int da9052_read_alarm(struct da9052_rtc *rtc, struct rtc_time *rtc_tm) |
fef931ff AJ |
59 | { |
60 | int ret; | |
61 | uint8_t v[5]; | |
62 | ||
7c994c08 | 63 | ret = da9052_group_read(rtc->da9052, DA9052_ALARM_MI_REG, 5, v); |
fef931ff | 64 | if (ret != 0) { |
7c994c08 | 65 | rtc_err(rtc, "Failed to group read ALM: %d\n", ret); |
fef931ff AJ |
66 | return ret; |
67 | } | |
68 | ||
69 | rtc_tm->tm_year = (v[4] & DA9052_RTC_YEAR) + 100; | |
70 | rtc_tm->tm_mon = (v[3] & DA9052_RTC_MONTH) - 1; | |
71 | rtc_tm->tm_mday = v[2] & DA9052_RTC_DAY; | |
72 | rtc_tm->tm_hour = v[1] & DA9052_RTC_HOUR; | |
73 | rtc_tm->tm_min = v[0] & DA9052_RTC_MIN; | |
74 | ||
75 | ret = rtc_valid_tm(rtc_tm); | |
fef931ff AJ |
76 | return ret; |
77 | } | |
78 | ||
7c994c08 | 79 | static int da9052_set_alarm(struct da9052_rtc *rtc, struct rtc_time *rtc_tm) |
fef931ff | 80 | { |
7c994c08 AO |
81 | struct da9052 *da9052 = rtc->da9052; |
82 | unsigned long alm_time; | |
fef931ff AJ |
83 | int ret; |
84 | uint8_t v[3]; | |
85 | ||
7c994c08 AO |
86 | ret = rtc_tm_to_time(rtc_tm, &alm_time); |
87 | if (ret != 0) | |
88 | return ret; | |
89 | ||
90 | if (rtc_tm->tm_sec > 0) { | |
91 | alm_time += 60 - rtc_tm->tm_sec; | |
92 | rtc_time_to_tm(alm_time, rtc_tm); | |
93 | } | |
94 | BUG_ON(rtc_tm->tm_sec); /* it will cause repeated irqs if not zero */ | |
95 | ||
fef931ff AJ |
96 | rtc_tm->tm_year -= 100; |
97 | rtc_tm->tm_mon += 1; | |
98 | ||
99 | ret = da9052_reg_update(da9052, DA9052_ALARM_MI_REG, | |
100 | DA9052_RTC_MIN, rtc_tm->tm_min); | |
101 | if (ret != 0) { | |
7c994c08 | 102 | rtc_err(rtc, "Failed to write ALRM MIN: %d\n", ret); |
fef931ff AJ |
103 | return ret; |
104 | } | |
105 | ||
106 | v[0] = rtc_tm->tm_hour; | |
107 | v[1] = rtc_tm->tm_mday; | |
108 | v[2] = rtc_tm->tm_mon; | |
109 | ||
110 | ret = da9052_group_write(da9052, DA9052_ALARM_H_REG, 3, v); | |
111 | if (ret < 0) | |
112 | return ret; | |
113 | ||
114 | ret = da9052_reg_update(da9052, DA9052_ALARM_Y_REG, | |
115 | DA9052_RTC_YEAR, rtc_tm->tm_year); | |
116 | if (ret != 0) | |
7c994c08 | 117 | rtc_err(rtc, "Failed to write ALRM YEAR: %d\n", ret); |
fef931ff AJ |
118 | |
119 | return ret; | |
120 | } | |
121 | ||
7c994c08 | 122 | static int da9052_rtc_get_alarm_status(struct da9052_rtc *rtc) |
fef931ff AJ |
123 | { |
124 | int ret; | |
125 | ||
7c994c08 | 126 | ret = da9052_reg_read(rtc->da9052, DA9052_ALARM_Y_REG); |
fef931ff | 127 | if (ret < 0) { |
7c994c08 | 128 | rtc_err(rtc, "Failed to read ALM: %d\n", ret); |
fef931ff AJ |
129 | return ret; |
130 | } | |
7c994c08 AO |
131 | |
132 | return !!(ret&DA9052_ALARM_Y_ALARM_ON); | |
fef931ff AJ |
133 | } |
134 | ||
135 | static int da9052_rtc_read_time(struct device *dev, struct rtc_time *rtc_tm) | |
136 | { | |
137 | struct da9052_rtc *rtc = dev_get_drvdata(dev); | |
138 | uint8_t v[6]; | |
139 | int ret; | |
140 | ||
141 | ret = da9052_group_read(rtc->da9052, DA9052_COUNT_S_REG, 6, v); | |
142 | if (ret < 0) { | |
7c994c08 | 143 | rtc_err(rtc, "Failed to read RTC time : %d\n", ret); |
fef931ff AJ |
144 | return ret; |
145 | } | |
146 | ||
147 | rtc_tm->tm_year = (v[5] & DA9052_RTC_YEAR) + 100; | |
148 | rtc_tm->tm_mon = (v[4] & DA9052_RTC_MONTH) - 1; | |
149 | rtc_tm->tm_mday = v[3] & DA9052_RTC_DAY; | |
150 | rtc_tm->tm_hour = v[2] & DA9052_RTC_HOUR; | |
151 | rtc_tm->tm_min = v[1] & DA9052_RTC_MIN; | |
152 | rtc_tm->tm_sec = v[0] & DA9052_RTC_SEC; | |
153 | ||
154 | ret = rtc_valid_tm(rtc_tm); | |
7c994c08 | 155 | return ret; |
fef931ff AJ |
156 | } |
157 | ||
158 | static int da9052_rtc_set_time(struct device *dev, struct rtc_time *tm) | |
159 | { | |
160 | struct da9052_rtc *rtc; | |
161 | uint8_t v[6]; | |
7c994c08 | 162 | int ret; |
fef931ff AJ |
163 | |
164 | rtc = dev_get_drvdata(dev); | |
165 | ||
166 | v[0] = tm->tm_sec; | |
167 | v[1] = tm->tm_min; | |
168 | v[2] = tm->tm_hour; | |
169 | v[3] = tm->tm_mday; | |
170 | v[4] = tm->tm_mon + 1; | |
171 | v[5] = tm->tm_year - 100; | |
172 | ||
7c994c08 AO |
173 | ret = da9052_group_write(rtc->da9052, DA9052_COUNT_S_REG, 6, v); |
174 | if (ret < 0) | |
175 | rtc_err(rtc, "failed to set RTC time: %d\n", ret); | |
176 | return ret; | |
fef931ff AJ |
177 | } |
178 | ||
179 | static int da9052_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) | |
180 | { | |
181 | int ret; | |
182 | struct rtc_time *tm = &alrm->time; | |
183 | struct da9052_rtc *rtc = dev_get_drvdata(dev); | |
184 | ||
7c994c08 AO |
185 | ret = da9052_read_alarm(rtc, tm); |
186 | if (ret < 0) { | |
187 | rtc_err(rtc, "failed to read RTC alarm: %d\n", ret); | |
fef931ff | 188 | return ret; |
7c994c08 | 189 | } |
fef931ff | 190 | |
7c994c08 | 191 | alrm->enabled = da9052_rtc_get_alarm_status(rtc); |
fef931ff AJ |
192 | return 0; |
193 | } | |
194 | ||
195 | static int da9052_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) | |
196 | { | |
197 | int ret; | |
198 | struct rtc_time *tm = &alrm->time; | |
199 | struct da9052_rtc *rtc = dev_get_drvdata(dev); | |
200 | ||
7c994c08 | 201 | ret = da9052_rtc_enable_alarm(rtc, 0); |
fef931ff AJ |
202 | if (ret < 0) |
203 | return ret; | |
204 | ||
7c994c08 AO |
205 | ret = da9052_set_alarm(rtc, tm); |
206 | if (ret < 0) | |
fef931ff AJ |
207 | return ret; |
208 | ||
7c994c08 | 209 | ret = da9052_rtc_enable_alarm(rtc, 1); |
fef931ff AJ |
210 | return ret; |
211 | } | |
212 | ||
213 | static int da9052_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) | |
214 | { | |
215 | struct da9052_rtc *rtc = dev_get_drvdata(dev); | |
216 | ||
7c994c08 | 217 | return da9052_rtc_enable_alarm(rtc, enabled); |
fef931ff AJ |
218 | } |
219 | ||
220 | static const struct rtc_class_ops da9052_rtc_ops = { | |
221 | .read_time = da9052_rtc_read_time, | |
222 | .set_time = da9052_rtc_set_time, | |
223 | .read_alarm = da9052_rtc_read_alarm, | |
224 | .set_alarm = da9052_rtc_set_alarm, | |
225 | .alarm_irq_enable = da9052_rtc_alarm_irq_enable, | |
226 | }; | |
227 | ||
5a167f45 | 228 | static int da9052_rtc_probe(struct platform_device *pdev) |
fef931ff AJ |
229 | { |
230 | struct da9052_rtc *rtc; | |
231 | int ret; | |
232 | ||
233 | rtc = devm_kzalloc(&pdev->dev, sizeof(struct da9052_rtc), GFP_KERNEL); | |
234 | if (!rtc) | |
235 | return -ENOMEM; | |
236 | ||
237 | rtc->da9052 = dev_get_drvdata(pdev->dev.parent); | |
238 | platform_set_drvdata(pdev, rtc); | |
7c994c08 AO |
239 | |
240 | ret = da9052_reg_write(rtc->da9052, DA9052_BBAT_CONT_REG, 0xFE); | |
241 | if (ret < 0) { | |
242 | rtc_err(rtc, | |
243 | "Failed to setup RTC battery charging: %d\n", ret); | |
244 | return ret; | |
245 | } | |
246 | ||
247 | ret = da9052_reg_update(rtc->da9052, DA9052_ALARM_Y_REG, | |
248 | DA9052_ALARM_Y_TICK_ON, 0); | |
249 | if (ret != 0) | |
250 | rtc_err(rtc, "Failed to disable TICKS: %d\n", ret); | |
251 | ||
c2c0eed7 | 252 | ret = da9052_request_irq(rtc->da9052, DA9052_IRQ_ALARM, "ALM", |
925e8ea6 | 253 | da9052_rtc_irq, rtc); |
fef931ff | 254 | if (ret != 0) { |
7c994c08 | 255 | rtc_err(rtc, "irq registration failed: %d\n", ret); |
007def04 | 256 | return ret; |
fef931ff AJ |
257 | } |
258 | ||
3689cd74 | 259 | rtc->rtc = devm_rtc_device_register(&pdev->dev, pdev->name, |
fef931ff | 260 | &da9052_rtc_ops, THIS_MODULE); |
dac30a98 | 261 | return PTR_ERR_OR_ZERO(rtc->rtc); |
fef931ff AJ |
262 | } |
263 | ||
fef931ff AJ |
264 | static struct platform_driver da9052_rtc_driver = { |
265 | .probe = da9052_rtc_probe, | |
fef931ff AJ |
266 | .driver = { |
267 | .name = "da9052-rtc", | |
268 | .owner = THIS_MODULE, | |
269 | }, | |
270 | }; | |
271 | ||
272 | module_platform_driver(da9052_rtc_driver); | |
273 | ||
7c994c08 | 274 | MODULE_AUTHOR("Anthony Olech <Anthony.Olech@diasemi.com>"); |
fef931ff AJ |
275 | MODULE_DESCRIPTION("RTC driver for Dialog DA9052 PMIC"); |
276 | MODULE_LICENSE("GPL"); | |
277 | MODULE_ALIAS("platform:da9052-rtc"); |