12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2fef931ffSAshish Jangam /*
3fef931ffSAshish Jangam * Real time clock driver for DA9052
4fef931ffSAshish Jangam *
5fef931ffSAshish Jangam * Copyright(c) 2012 Dialog Semiconductor Ltd.
6fef931ffSAshish Jangam *
7fef931ffSAshish Jangam * Author: Dajun Dajun Chen <dajun.chen@diasemi.com>
8fef931ffSAshish Jangam */
9fef931ffSAshish Jangam
10fef931ffSAshish Jangam #include <linux/module.h>
11fef931ffSAshish Jangam #include <linux/platform_device.h>
12fef931ffSAshish Jangam #include <linux/rtc.h>
1311f54d05SSachin Kamat #include <linux/err.h>
1403cc1625SAdam Ward #include <linux/delay.h>
15fef931ffSAshish Jangam
16fef931ffSAshish Jangam #include <linux/mfd/da9052/da9052.h>
17fef931ffSAshish Jangam #include <linux/mfd/da9052/reg.h>
18fef931ffSAshish Jangam
197c994c08SAnthony Olech #define rtc_err(rtc, fmt, ...) \
207c994c08SAnthony Olech dev_err(rtc->da9052->dev, "%s: " fmt, __func__, ##__VA_ARGS__)
21fef931ffSAshish Jangam
2203cc1625SAdam Ward #define DA9052_GET_TIME_RETRIES 5
2303cc1625SAdam Ward
24fef931ffSAshish Jangam struct da9052_rtc {
25fef931ffSAshish Jangam struct rtc_device *rtc;
26fef931ffSAshish Jangam struct da9052 *da9052;
27fef931ffSAshish Jangam };
28fef931ffSAshish Jangam
da9052_rtc_enable_alarm(struct da9052_rtc * rtc,bool enable)297c994c08SAnthony Olech static int da9052_rtc_enable_alarm(struct da9052_rtc *rtc, bool enable)
30fef931ffSAshish Jangam {
31fef931ffSAshish Jangam int ret;
32fef931ffSAshish Jangam if (enable) {
337c994c08SAnthony Olech ret = da9052_reg_update(rtc->da9052, DA9052_ALARM_Y_REG,
347c994c08SAnthony Olech DA9052_ALARM_Y_ALARM_ON|DA9052_ALARM_Y_TICK_ON,
35fef931ffSAshish Jangam DA9052_ALARM_Y_ALARM_ON);
36fef931ffSAshish Jangam if (ret != 0)
377c994c08SAnthony Olech rtc_err(rtc, "Failed to enable ALM: %d\n", ret);
38fef931ffSAshish Jangam } else {
397c994c08SAnthony Olech ret = da9052_reg_update(rtc->da9052, DA9052_ALARM_Y_REG,
407c994c08SAnthony Olech DA9052_ALARM_Y_ALARM_ON|DA9052_ALARM_Y_TICK_ON, 0);
41fef931ffSAshish Jangam if (ret != 0)
427c994c08SAnthony Olech rtc_err(rtc, "Write error: %d\n", ret);
43fef931ffSAshish Jangam }
44fef931ffSAshish Jangam return ret;
45fef931ffSAshish Jangam }
46fef931ffSAshish Jangam
da9052_rtc_irq(int irq,void * data)47fef931ffSAshish Jangam static irqreturn_t da9052_rtc_irq(int irq, void *data)
48fef931ffSAshish Jangam {
49fef931ffSAshish Jangam struct da9052_rtc *rtc = data;
50fef931ffSAshish Jangam
51fef931ffSAshish Jangam rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF);
52fef931ffSAshish Jangam
53fef931ffSAshish Jangam return IRQ_HANDLED;
54fef931ffSAshish Jangam }
55fef931ffSAshish Jangam
da9052_read_alarm(struct da9052_rtc * rtc,struct rtc_time * rtc_tm)567c994c08SAnthony Olech static int da9052_read_alarm(struct da9052_rtc *rtc, struct rtc_time *rtc_tm)
57fef931ffSAshish Jangam {
58fef931ffSAshish Jangam int ret;
5903cc1625SAdam Ward uint8_t v[2][5];
6003cc1625SAdam Ward int idx = 1;
6103cc1625SAdam Ward int timeout = DA9052_GET_TIME_RETRIES;
62fef931ffSAshish Jangam
6303cc1625SAdam Ward ret = da9052_group_read(rtc->da9052, DA9052_ALARM_MI_REG, 5, &v[0][0]);
6403cc1625SAdam Ward if (ret) {
657c994c08SAnthony Olech rtc_err(rtc, "Failed to group read ALM: %d\n", ret);
66fef931ffSAshish Jangam return ret;
67fef931ffSAshish Jangam }
68fef931ffSAshish Jangam
6903cc1625SAdam Ward do {
7003cc1625SAdam Ward ret = da9052_group_read(rtc->da9052,
7103cc1625SAdam Ward DA9052_ALARM_MI_REG, 5, &v[idx][0]);
7203cc1625SAdam Ward if (ret) {
7303cc1625SAdam Ward rtc_err(rtc, "Failed to group read ALM: %d\n", ret);
7403cc1625SAdam Ward return ret;
7503cc1625SAdam Ward }
7603cc1625SAdam Ward
7703cc1625SAdam Ward if (memcmp(&v[0][0], &v[1][0], 5) == 0) {
7803cc1625SAdam Ward rtc_tm->tm_year = (v[0][4] & DA9052_RTC_YEAR) + 100;
7903cc1625SAdam Ward rtc_tm->tm_mon = (v[0][3] & DA9052_RTC_MONTH) - 1;
8003cc1625SAdam Ward rtc_tm->tm_mday = v[0][2] & DA9052_RTC_DAY;
8103cc1625SAdam Ward rtc_tm->tm_hour = v[0][1] & DA9052_RTC_HOUR;
8203cc1625SAdam Ward rtc_tm->tm_min = v[0][0] & DA9052_RTC_MIN;
83d2c92705SUwe Kleine-König rtc_tm->tm_sec = 0;
84fef931ffSAshish Jangam
85fef931ffSAshish Jangam ret = rtc_valid_tm(rtc_tm);
86fef931ffSAshish Jangam return ret;
87fef931ffSAshish Jangam }
88fef931ffSAshish Jangam
8903cc1625SAdam Ward idx = (1-idx);
9003cc1625SAdam Ward msleep(20);
9103cc1625SAdam Ward
9203cc1625SAdam Ward } while (timeout--);
9303cc1625SAdam Ward
9403cc1625SAdam Ward rtc_err(rtc, "Timed out reading alarm time\n");
9503cc1625SAdam Ward
9603cc1625SAdam Ward return -EIO;
9703cc1625SAdam Ward }
9803cc1625SAdam Ward
da9052_set_alarm(struct da9052_rtc * rtc,struct rtc_time * rtc_tm)997c994c08SAnthony Olech static int da9052_set_alarm(struct da9052_rtc *rtc, struct rtc_time *rtc_tm)
100fef931ffSAshish Jangam {
1017c994c08SAnthony Olech struct da9052 *da9052 = rtc->da9052;
1027c994c08SAnthony Olech unsigned long alm_time;
103fef931ffSAshish Jangam int ret;
104fef931ffSAshish Jangam uint8_t v[3];
105fef931ffSAshish Jangam
10606c4e103SAlexandre Belloni alm_time = rtc_tm_to_time64(rtc_tm);
1077c994c08SAnthony Olech
1087c994c08SAnthony Olech if (rtc_tm->tm_sec > 0) {
1097c994c08SAnthony Olech alm_time += 60 - rtc_tm->tm_sec;
11006c4e103SAlexandre Belloni rtc_time64_to_tm(alm_time, rtc_tm);
1117c994c08SAnthony Olech }
1127c994c08SAnthony Olech BUG_ON(rtc_tm->tm_sec); /* it will cause repeated irqs if not zero */
1137c994c08SAnthony Olech
114fef931ffSAshish Jangam rtc_tm->tm_year -= 100;
115fef931ffSAshish Jangam rtc_tm->tm_mon += 1;
116fef931ffSAshish Jangam
117fef931ffSAshish Jangam ret = da9052_reg_update(da9052, DA9052_ALARM_MI_REG,
118fef931ffSAshish Jangam DA9052_RTC_MIN, rtc_tm->tm_min);
119fef931ffSAshish Jangam if (ret != 0) {
1207c994c08SAnthony Olech rtc_err(rtc, "Failed to write ALRM MIN: %d\n", ret);
121fef931ffSAshish Jangam return ret;
122fef931ffSAshish Jangam }
123fef931ffSAshish Jangam
124fef931ffSAshish Jangam v[0] = rtc_tm->tm_hour;
125fef931ffSAshish Jangam v[1] = rtc_tm->tm_mday;
126fef931ffSAshish Jangam v[2] = rtc_tm->tm_mon;
127fef931ffSAshish Jangam
128fef931ffSAshish Jangam ret = da9052_group_write(da9052, DA9052_ALARM_H_REG, 3, v);
129fef931ffSAshish Jangam if (ret < 0)
130fef931ffSAshish Jangam return ret;
131fef931ffSAshish Jangam
132fef931ffSAshish Jangam ret = da9052_reg_update(da9052, DA9052_ALARM_Y_REG,
133fef931ffSAshish Jangam DA9052_RTC_YEAR, rtc_tm->tm_year);
134fef931ffSAshish Jangam if (ret != 0)
1357c994c08SAnthony Olech rtc_err(rtc, "Failed to write ALRM YEAR: %d\n", ret);
136fef931ffSAshish Jangam
137fef931ffSAshish Jangam return ret;
138fef931ffSAshish Jangam }
139fef931ffSAshish Jangam
da9052_rtc_get_alarm_status(struct da9052_rtc * rtc)1407c994c08SAnthony Olech static int da9052_rtc_get_alarm_status(struct da9052_rtc *rtc)
141fef931ffSAshish Jangam {
142fef931ffSAshish Jangam int ret;
143fef931ffSAshish Jangam
1447c994c08SAnthony Olech ret = da9052_reg_read(rtc->da9052, DA9052_ALARM_Y_REG);
145fef931ffSAshish Jangam if (ret < 0) {
1467c994c08SAnthony Olech rtc_err(rtc, "Failed to read ALM: %d\n", ret);
147fef931ffSAshish Jangam return ret;
148fef931ffSAshish Jangam }
1497c994c08SAnthony Olech
1507c994c08SAnthony Olech return !!(ret&DA9052_ALARM_Y_ALARM_ON);
151fef931ffSAshish Jangam }
152fef931ffSAshish Jangam
da9052_rtc_read_time(struct device * dev,struct rtc_time * rtc_tm)153fef931ffSAshish Jangam static int da9052_rtc_read_time(struct device *dev, struct rtc_time *rtc_tm)
154fef931ffSAshish Jangam {
155fef931ffSAshish Jangam struct da9052_rtc *rtc = dev_get_drvdata(dev);
156fef931ffSAshish Jangam int ret;
15703cc1625SAdam Ward uint8_t v[2][6];
15803cc1625SAdam Ward int idx = 1;
15903cc1625SAdam Ward int timeout = DA9052_GET_TIME_RETRIES;
160fef931ffSAshish Jangam
16103cc1625SAdam Ward ret = da9052_group_read(rtc->da9052, DA9052_COUNT_S_REG, 6, &v[0][0]);
16203cc1625SAdam Ward if (ret) {
1637c994c08SAnthony Olech rtc_err(rtc, "Failed to read RTC time : %d\n", ret);
164fef931ffSAshish Jangam return ret;
165fef931ffSAshish Jangam }
166fef931ffSAshish Jangam
16703cc1625SAdam Ward do {
16803cc1625SAdam Ward ret = da9052_group_read(rtc->da9052,
16903cc1625SAdam Ward DA9052_COUNT_S_REG, 6, &v[idx][0]);
17003cc1625SAdam Ward if (ret) {
17103cc1625SAdam Ward rtc_err(rtc, "Failed to read RTC time : %d\n", ret);
17203cc1625SAdam Ward return ret;
17303cc1625SAdam Ward }
17403cc1625SAdam Ward
17503cc1625SAdam Ward if (memcmp(&v[0][0], &v[1][0], 6) == 0) {
17603cc1625SAdam Ward rtc_tm->tm_year = (v[0][5] & DA9052_RTC_YEAR) + 100;
17703cc1625SAdam Ward rtc_tm->tm_mon = (v[0][4] & DA9052_RTC_MONTH) - 1;
17803cc1625SAdam Ward rtc_tm->tm_mday = v[0][3] & DA9052_RTC_DAY;
17903cc1625SAdam Ward rtc_tm->tm_hour = v[0][2] & DA9052_RTC_HOUR;
18003cc1625SAdam Ward rtc_tm->tm_min = v[0][1] & DA9052_RTC_MIN;
18103cc1625SAdam Ward rtc_tm->tm_sec = v[0][0] & DA9052_RTC_SEC;
182fef931ffSAshish Jangam
183bb54be13SAlexandre Belloni return 0;
184fef931ffSAshish Jangam }
185fef931ffSAshish Jangam
18603cc1625SAdam Ward idx = (1-idx);
18703cc1625SAdam Ward msleep(20);
18803cc1625SAdam Ward
18903cc1625SAdam Ward } while (timeout--);
19003cc1625SAdam Ward
19103cc1625SAdam Ward rtc_err(rtc, "Timed out reading time\n");
19203cc1625SAdam Ward
19303cc1625SAdam Ward return -EIO;
19403cc1625SAdam Ward }
19503cc1625SAdam Ward
da9052_rtc_set_time(struct device * dev,struct rtc_time * tm)196fef931ffSAshish Jangam static int da9052_rtc_set_time(struct device *dev, struct rtc_time *tm)
197fef931ffSAshish Jangam {
198fef931ffSAshish Jangam struct da9052_rtc *rtc;
199fef931ffSAshish Jangam uint8_t v[6];
2007c994c08SAnthony Olech int ret;
201fef931ffSAshish Jangam
202ae824c48SAdam Ward /* DA9052 only has 6 bits for year - to represent 2000-2063 */
203ae824c48SAdam Ward if ((tm->tm_year < 100) || (tm->tm_year > 163))
204ae824c48SAdam Ward return -EINVAL;
205ae824c48SAdam Ward
206fef931ffSAshish Jangam rtc = dev_get_drvdata(dev);
207fef931ffSAshish Jangam
208fef931ffSAshish Jangam v[0] = tm->tm_sec;
209fef931ffSAshish Jangam v[1] = tm->tm_min;
210fef931ffSAshish Jangam v[2] = tm->tm_hour;
211fef931ffSAshish Jangam v[3] = tm->tm_mday;
212fef931ffSAshish Jangam v[4] = tm->tm_mon + 1;
213fef931ffSAshish Jangam v[5] = tm->tm_year - 100;
214fef931ffSAshish Jangam
2157c994c08SAnthony Olech ret = da9052_group_write(rtc->da9052, DA9052_COUNT_S_REG, 6, v);
2167c994c08SAnthony Olech if (ret < 0)
2177c994c08SAnthony Olech rtc_err(rtc, "failed to set RTC time: %d\n", ret);
2187c994c08SAnthony Olech return ret;
219fef931ffSAshish Jangam }
220fef931ffSAshish Jangam
da9052_rtc_read_alarm(struct device * dev,struct rtc_wkalrm * alrm)221fef931ffSAshish Jangam static int da9052_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
222fef931ffSAshish Jangam {
223fef931ffSAshish Jangam int ret;
224fef931ffSAshish Jangam struct rtc_time *tm = &alrm->time;
225fef931ffSAshish Jangam struct da9052_rtc *rtc = dev_get_drvdata(dev);
226fef931ffSAshish Jangam
2277c994c08SAnthony Olech ret = da9052_read_alarm(rtc, tm);
2287c994c08SAnthony Olech if (ret < 0) {
2297c994c08SAnthony Olech rtc_err(rtc, "failed to read RTC alarm: %d\n", ret);
230fef931ffSAshish Jangam return ret;
2317c994c08SAnthony Olech }
232fef931ffSAshish Jangam
2337c994c08SAnthony Olech alrm->enabled = da9052_rtc_get_alarm_status(rtc);
234fef931ffSAshish Jangam return 0;
235fef931ffSAshish Jangam }
236fef931ffSAshish Jangam
da9052_rtc_set_alarm(struct device * dev,struct rtc_wkalrm * alrm)237fef931ffSAshish Jangam static int da9052_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
238fef931ffSAshish Jangam {
239fef931ffSAshish Jangam int ret;
240fef931ffSAshish Jangam struct rtc_time *tm = &alrm->time;
241fef931ffSAshish Jangam struct da9052_rtc *rtc = dev_get_drvdata(dev);
242fef931ffSAshish Jangam
243ae824c48SAdam Ward /* DA9052 only has 6 bits for year - to represent 2000-2063 */
244ae824c48SAdam Ward if ((tm->tm_year < 100) || (tm->tm_year > 163))
245ae824c48SAdam Ward return -EINVAL;
246ae824c48SAdam Ward
2477c994c08SAnthony Olech ret = da9052_rtc_enable_alarm(rtc, 0);
248fef931ffSAshish Jangam if (ret < 0)
249fef931ffSAshish Jangam return ret;
250fef931ffSAshish Jangam
2517c994c08SAnthony Olech ret = da9052_set_alarm(rtc, tm);
2527c994c08SAnthony Olech if (ret < 0)
253fef931ffSAshish Jangam return ret;
254fef931ffSAshish Jangam
2557c994c08SAnthony Olech ret = da9052_rtc_enable_alarm(rtc, 1);
256fef931ffSAshish Jangam return ret;
257fef931ffSAshish Jangam }
258fef931ffSAshish Jangam
da9052_rtc_alarm_irq_enable(struct device * dev,unsigned int enabled)259fef931ffSAshish Jangam static int da9052_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
260fef931ffSAshish Jangam {
261fef931ffSAshish Jangam struct da9052_rtc *rtc = dev_get_drvdata(dev);
262fef931ffSAshish Jangam
2637c994c08SAnthony Olech return da9052_rtc_enable_alarm(rtc, enabled);
264fef931ffSAshish Jangam }
265fef931ffSAshish Jangam
266fef931ffSAshish Jangam static const struct rtc_class_ops da9052_rtc_ops = {
267fef931ffSAshish Jangam .read_time = da9052_rtc_read_time,
268fef931ffSAshish Jangam .set_time = da9052_rtc_set_time,
269fef931ffSAshish Jangam .read_alarm = da9052_rtc_read_alarm,
270fef931ffSAshish Jangam .set_alarm = da9052_rtc_set_alarm,
271fef931ffSAshish Jangam .alarm_irq_enable = da9052_rtc_alarm_irq_enable,
272fef931ffSAshish Jangam };
273fef931ffSAshish Jangam
da9052_rtc_probe(struct platform_device * pdev)2745a167f45SGreg Kroah-Hartman static int da9052_rtc_probe(struct platform_device *pdev)
275fef931ffSAshish Jangam {
276fef931ffSAshish Jangam struct da9052_rtc *rtc;
277fef931ffSAshish Jangam int ret;
278fef931ffSAshish Jangam
279fef931ffSAshish Jangam rtc = devm_kzalloc(&pdev->dev, sizeof(struct da9052_rtc), GFP_KERNEL);
280fef931ffSAshish Jangam if (!rtc)
281fef931ffSAshish Jangam return -ENOMEM;
282fef931ffSAshish Jangam
283fef931ffSAshish Jangam rtc->da9052 = dev_get_drvdata(pdev->dev.parent);
284fef931ffSAshish Jangam platform_set_drvdata(pdev, rtc);
2857c994c08SAnthony Olech
2867c994c08SAnthony Olech ret = da9052_reg_write(rtc->da9052, DA9052_BBAT_CONT_REG, 0xFE);
2877c994c08SAnthony Olech if (ret < 0) {
2887c994c08SAnthony Olech rtc_err(rtc,
2897c994c08SAnthony Olech "Failed to setup RTC battery charging: %d\n", ret);
2907c994c08SAnthony Olech return ret;
2917c994c08SAnthony Olech }
2927c994c08SAnthony Olech
2937c994c08SAnthony Olech ret = da9052_reg_update(rtc->da9052, DA9052_ALARM_Y_REG,
2947c994c08SAnthony Olech DA9052_ALARM_Y_TICK_ON, 0);
2957c994c08SAnthony Olech if (ret != 0)
2967c994c08SAnthony Olech rtc_err(rtc, "Failed to disable TICKS: %d\n", ret);
2977c994c08SAnthony Olech
2986406d96eSSteve Twiss device_init_wakeup(&pdev->dev, true);
299d17077e5SAlexandre Belloni rtc->rtc = devm_rtc_allocate_device(&pdev->dev);
3006406d96eSSteve Twiss if (IS_ERR(rtc->rtc))
3016406d96eSSteve Twiss return PTR_ERR(rtc->rtc);
3026406d96eSSteve Twiss
303d17077e5SAlexandre Belloni rtc->rtc->ops = &da9052_rtc_ops;
30423af616cSAlexandre Belloni rtc->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
30523af616cSAlexandre Belloni rtc->rtc->range_max = RTC_TIMESTAMP_END_2063;
306d17077e5SAlexandre Belloni
307*fdcfd854SBartosz Golaszewski ret = devm_rtc_register_device(rtc->rtc);
308d17077e5SAlexandre Belloni if (ret)
309d17077e5SAlexandre Belloni return ret;
310d17077e5SAlexandre Belloni
311c2c0eed7SAnthony Olech ret = da9052_request_irq(rtc->da9052, DA9052_IRQ_ALARM, "ALM",
312925e8ea6SAshish Jangam da9052_rtc_irq, rtc);
313fef931ffSAshish Jangam if (ret != 0) {
3147c994c08SAnthony Olech rtc_err(rtc, "irq registration failed: %d\n", ret);
315007def04SDevendra Naga return ret;
316fef931ffSAshish Jangam }
317fef931ffSAshish Jangam
3186406d96eSSteve Twiss return 0;
319fef931ffSAshish Jangam }
320fef931ffSAshish Jangam
321fef931ffSAshish Jangam static struct platform_driver da9052_rtc_driver = {
322fef931ffSAshish Jangam .probe = da9052_rtc_probe,
323fef931ffSAshish Jangam .driver = {
324fef931ffSAshish Jangam .name = "da9052-rtc",
325fef931ffSAshish Jangam },
326fef931ffSAshish Jangam };
327fef931ffSAshish Jangam
328fef931ffSAshish Jangam module_platform_driver(da9052_rtc_driver);
329fef931ffSAshish Jangam
3307c994c08SAnthony Olech MODULE_AUTHOR("Anthony Olech <Anthony.Olech@diasemi.com>");
331fef931ffSAshish Jangam MODULE_DESCRIPTION("RTC driver for Dialog DA9052 PMIC");
332fef931ffSAshish Jangam MODULE_LICENSE("GPL");
333fef931ffSAshish Jangam MODULE_ALIAS("platform:da9052-rtc");
334