1f5fae395SAlexandre Belloni // SPDX-License-Identifier: GPL-2.0
2d3c7a3f7SDaniel Ribeiro /*
3d3c7a3f7SDaniel Ribeiro * pcap rtc code for Motorola EZX phones
4d3c7a3f7SDaniel Ribeiro *
5d3c7a3f7SDaniel Ribeiro * Copyright (c) 2008 guiming zhuo <gmzhuo@gmail.com>
6d3c7a3f7SDaniel Ribeiro * Copyright (c) 2009 Daniel Ribeiro <drwyrm@gmail.com>
7d3c7a3f7SDaniel Ribeiro *
8d3c7a3f7SDaniel Ribeiro * Based on Motorola's rtc.c Copyright (c) 2003-2005 Motorola
9d3c7a3f7SDaniel Ribeiro */
10d3c7a3f7SDaniel Ribeiro
11d3c7a3f7SDaniel Ribeiro #include <linux/kernel.h>
12d3c7a3f7SDaniel Ribeiro #include <linux/module.h>
13d3c7a3f7SDaniel Ribeiro #include <linux/init.h>
14d3c7a3f7SDaniel Ribeiro #include <linux/mfd/ezx-pcap.h>
15d3c7a3f7SDaniel Ribeiro #include <linux/rtc.h>
165a0e3ad6STejun Heo #include <linux/slab.h>
17d3c7a3f7SDaniel Ribeiro #include <linux/platform_device.h>
18d3c7a3f7SDaniel Ribeiro
19d3c7a3f7SDaniel Ribeiro struct pcap_rtc {
20d3c7a3f7SDaniel Ribeiro struct pcap_chip *pcap;
21d3c7a3f7SDaniel Ribeiro struct rtc_device *rtc;
22d3c7a3f7SDaniel Ribeiro };
23d3c7a3f7SDaniel Ribeiro
pcap_rtc_irq(int irq,void * _pcap_rtc)24d3c7a3f7SDaniel Ribeiro static irqreturn_t pcap_rtc_irq(int irq, void *_pcap_rtc)
25d3c7a3f7SDaniel Ribeiro {
26d3c7a3f7SDaniel Ribeiro struct pcap_rtc *pcap_rtc = _pcap_rtc;
27d3c7a3f7SDaniel Ribeiro unsigned long rtc_events;
28d3c7a3f7SDaniel Ribeiro
29d3c7a3f7SDaniel Ribeiro if (irq == pcap_to_irq(pcap_rtc->pcap, PCAP_IRQ_1HZ))
30d3c7a3f7SDaniel Ribeiro rtc_events = RTC_IRQF | RTC_UF;
31d3c7a3f7SDaniel Ribeiro else if (irq == pcap_to_irq(pcap_rtc->pcap, PCAP_IRQ_TODA))
32d3c7a3f7SDaniel Ribeiro rtc_events = RTC_IRQF | RTC_AF;
33d3c7a3f7SDaniel Ribeiro else
34d3c7a3f7SDaniel Ribeiro rtc_events = 0;
35d3c7a3f7SDaniel Ribeiro
36d3c7a3f7SDaniel Ribeiro rtc_update_irq(pcap_rtc->rtc, 1, rtc_events);
37d3c7a3f7SDaniel Ribeiro return IRQ_HANDLED;
38d3c7a3f7SDaniel Ribeiro }
39d3c7a3f7SDaniel Ribeiro
pcap_rtc_read_alarm(struct device * dev,struct rtc_wkalrm * alrm)40d3c7a3f7SDaniel Ribeiro static int pcap_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
41d3c7a3f7SDaniel Ribeiro {
4285368bb9SWolfram Sang struct pcap_rtc *pcap_rtc = dev_get_drvdata(dev);
43d3c7a3f7SDaniel Ribeiro struct rtc_time *tm = &alrm->time;
44d3c7a3f7SDaniel Ribeiro unsigned long secs;
45d3c7a3f7SDaniel Ribeiro u32 tod; /* time of day, seconds since midnight */
46d3c7a3f7SDaniel Ribeiro u32 days; /* days since 1/1/1970 */
47d3c7a3f7SDaniel Ribeiro
48d3c7a3f7SDaniel Ribeiro ezx_pcap_read(pcap_rtc->pcap, PCAP_REG_RTC_TODA, &tod);
49d3c7a3f7SDaniel Ribeiro secs = tod & PCAP_RTC_TOD_MASK;
50d3c7a3f7SDaniel Ribeiro
51d3c7a3f7SDaniel Ribeiro ezx_pcap_read(pcap_rtc->pcap, PCAP_REG_RTC_DAYA, &days);
52d3c7a3f7SDaniel Ribeiro secs += (days & PCAP_RTC_DAY_MASK) * SEC_PER_DAY;
53d3c7a3f7SDaniel Ribeiro
5403745d1fSAlexandre Belloni rtc_time64_to_tm(secs, tm);
55d3c7a3f7SDaniel Ribeiro
56d3c7a3f7SDaniel Ribeiro return 0;
57d3c7a3f7SDaniel Ribeiro }
58d3c7a3f7SDaniel Ribeiro
pcap_rtc_set_alarm(struct device * dev,struct rtc_wkalrm * alrm)59d3c7a3f7SDaniel Ribeiro static int pcap_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
60d3c7a3f7SDaniel Ribeiro {
6185368bb9SWolfram Sang struct pcap_rtc *pcap_rtc = dev_get_drvdata(dev);
6203745d1fSAlexandre Belloni unsigned long secs = rtc_tm_to_time64(&alrm->time);
63d3c7a3f7SDaniel Ribeiro u32 tod, days;
64d3c7a3f7SDaniel Ribeiro
65d3c7a3f7SDaniel Ribeiro tod = secs % SEC_PER_DAY;
66d3c7a3f7SDaniel Ribeiro ezx_pcap_write(pcap_rtc->pcap, PCAP_REG_RTC_TODA, tod);
67d3c7a3f7SDaniel Ribeiro
68d3c7a3f7SDaniel Ribeiro days = secs / SEC_PER_DAY;
69d3c7a3f7SDaniel Ribeiro ezx_pcap_write(pcap_rtc->pcap, PCAP_REG_RTC_DAYA, days);
70d3c7a3f7SDaniel Ribeiro
71d3c7a3f7SDaniel Ribeiro return 0;
72d3c7a3f7SDaniel Ribeiro }
73d3c7a3f7SDaniel Ribeiro
pcap_rtc_read_time(struct device * dev,struct rtc_time * tm)74d3c7a3f7SDaniel Ribeiro static int pcap_rtc_read_time(struct device *dev, struct rtc_time *tm)
75d3c7a3f7SDaniel Ribeiro {
7685368bb9SWolfram Sang struct pcap_rtc *pcap_rtc = dev_get_drvdata(dev);
77d3c7a3f7SDaniel Ribeiro unsigned long secs;
78d3c7a3f7SDaniel Ribeiro u32 tod, days;
79d3c7a3f7SDaniel Ribeiro
80d3c7a3f7SDaniel Ribeiro ezx_pcap_read(pcap_rtc->pcap, PCAP_REG_RTC_TOD, &tod);
81d3c7a3f7SDaniel Ribeiro secs = tod & PCAP_RTC_TOD_MASK;
82d3c7a3f7SDaniel Ribeiro
83d3c7a3f7SDaniel Ribeiro ezx_pcap_read(pcap_rtc->pcap, PCAP_REG_RTC_DAY, &days);
84d3c7a3f7SDaniel Ribeiro secs += (days & PCAP_RTC_DAY_MASK) * SEC_PER_DAY;
85d3c7a3f7SDaniel Ribeiro
8603745d1fSAlexandre Belloni rtc_time64_to_tm(secs, tm);
87d3c7a3f7SDaniel Ribeiro
88ab62670eSAlexandre Belloni return 0;
89d3c7a3f7SDaniel Ribeiro }
90d3c7a3f7SDaniel Ribeiro
pcap_rtc_set_time(struct device * dev,struct rtc_time * tm)91c8b599edSAlexandre Belloni static int pcap_rtc_set_time(struct device *dev, struct rtc_time *tm)
92d3c7a3f7SDaniel Ribeiro {
9385368bb9SWolfram Sang struct pcap_rtc *pcap_rtc = dev_get_drvdata(dev);
94c8b599edSAlexandre Belloni unsigned long secs = rtc_tm_to_time64(tm);
95d3c7a3f7SDaniel Ribeiro u32 tod, days;
96d3c7a3f7SDaniel Ribeiro
97d3c7a3f7SDaniel Ribeiro tod = secs % SEC_PER_DAY;
98d3c7a3f7SDaniel Ribeiro ezx_pcap_write(pcap_rtc->pcap, PCAP_REG_RTC_TOD, tod);
99d3c7a3f7SDaniel Ribeiro
100d3c7a3f7SDaniel Ribeiro days = secs / SEC_PER_DAY;
101d3c7a3f7SDaniel Ribeiro ezx_pcap_write(pcap_rtc->pcap, PCAP_REG_RTC_DAY, days);
102d3c7a3f7SDaniel Ribeiro
103d3c7a3f7SDaniel Ribeiro return 0;
104d3c7a3f7SDaniel Ribeiro }
105d3c7a3f7SDaniel Ribeiro
pcap_rtc_irq_enable(struct device * dev,int pirq,unsigned int en)106d3c7a3f7SDaniel Ribeiro static int pcap_rtc_irq_enable(struct device *dev, int pirq, unsigned int en)
107d3c7a3f7SDaniel Ribeiro {
10885368bb9SWolfram Sang struct pcap_rtc *pcap_rtc = dev_get_drvdata(dev);
109d3c7a3f7SDaniel Ribeiro
110d3c7a3f7SDaniel Ribeiro if (en)
111d3c7a3f7SDaniel Ribeiro enable_irq(pcap_to_irq(pcap_rtc->pcap, pirq));
112d3c7a3f7SDaniel Ribeiro else
113d3c7a3f7SDaniel Ribeiro disable_irq(pcap_to_irq(pcap_rtc->pcap, pirq));
114d3c7a3f7SDaniel Ribeiro
115d3c7a3f7SDaniel Ribeiro return 0;
116d3c7a3f7SDaniel Ribeiro }
117d3c7a3f7SDaniel Ribeiro
pcap_rtc_alarm_irq_enable(struct device * dev,unsigned int en)118d3c7a3f7SDaniel Ribeiro static int pcap_rtc_alarm_irq_enable(struct device *dev, unsigned int en)
119d3c7a3f7SDaniel Ribeiro {
120d3c7a3f7SDaniel Ribeiro return pcap_rtc_irq_enable(dev, PCAP_IRQ_TODA, en);
121d3c7a3f7SDaniel Ribeiro }
122d3c7a3f7SDaniel Ribeiro
123d3c7a3f7SDaniel Ribeiro static const struct rtc_class_ops pcap_rtc_ops = {
124d3c7a3f7SDaniel Ribeiro .read_time = pcap_rtc_read_time,
125c8b599edSAlexandre Belloni .set_time = pcap_rtc_set_time,
126d3c7a3f7SDaniel Ribeiro .read_alarm = pcap_rtc_read_alarm,
127d3c7a3f7SDaniel Ribeiro .set_alarm = pcap_rtc_set_alarm,
128d3c7a3f7SDaniel Ribeiro .alarm_irq_enable = pcap_rtc_alarm_irq_enable,
129d3c7a3f7SDaniel Ribeiro };
130d3c7a3f7SDaniel Ribeiro
pcap_rtc_probe(struct platform_device * pdev)1315cc2b9c6SJingoo Han static int __init pcap_rtc_probe(struct platform_device *pdev)
132d3c7a3f7SDaniel Ribeiro {
133d3c7a3f7SDaniel Ribeiro struct pcap_rtc *pcap_rtc;
134d3c7a3f7SDaniel Ribeiro int timer_irq, alarm_irq;
135d3c7a3f7SDaniel Ribeiro int err = -ENOMEM;
136d3c7a3f7SDaniel Ribeiro
1376b5f4862SJingoo Han pcap_rtc = devm_kzalloc(&pdev->dev, sizeof(struct pcap_rtc),
1386b5f4862SJingoo Han GFP_KERNEL);
139d3c7a3f7SDaniel Ribeiro if (!pcap_rtc)
140d3c7a3f7SDaniel Ribeiro return err;
141d3c7a3f7SDaniel Ribeiro
142d3c7a3f7SDaniel Ribeiro pcap_rtc->pcap = dev_get_drvdata(pdev->dev.parent);
143d3c7a3f7SDaniel Ribeiro
1444b3687f9SJohn Stultz platform_set_drvdata(pdev, pcap_rtc);
1454b3687f9SJohn Stultz
146d1403c48SAlexandre Belloni pcap_rtc->rtc = devm_rtc_allocate_device(&pdev->dev);
147a1c805a9SJingoo Han if (IS_ERR(pcap_rtc->rtc))
148a1c805a9SJingoo Han return PTR_ERR(pcap_rtc->rtc);
149d3c7a3f7SDaniel Ribeiro
150d1403c48SAlexandre Belloni pcap_rtc->rtc->ops = &pcap_rtc_ops;
151d1403c48SAlexandre Belloni pcap_rtc->rtc->range_max = (1 << 14) * 86400ULL - 1;
152d1403c48SAlexandre Belloni
153d3c7a3f7SDaniel Ribeiro timer_irq = pcap_to_irq(pcap_rtc->pcap, PCAP_IRQ_1HZ);
154d3c7a3f7SDaniel Ribeiro alarm_irq = pcap_to_irq(pcap_rtc->pcap, PCAP_IRQ_TODA);
155d3c7a3f7SDaniel Ribeiro
1566b5f4862SJingoo Han err = devm_request_irq(&pdev->dev, timer_irq, pcap_rtc_irq, 0,
1576b5f4862SJingoo Han "RTC Timer", pcap_rtc);
158d3c7a3f7SDaniel Ribeiro if (err)
159a1c805a9SJingoo Han return err;
160d3c7a3f7SDaniel Ribeiro
1616b5f4862SJingoo Han err = devm_request_irq(&pdev->dev, alarm_irq, pcap_rtc_irq, 0,
1626b5f4862SJingoo Han "RTC Alarm", pcap_rtc);
163d3c7a3f7SDaniel Ribeiro if (err)
164a1c805a9SJingoo Han return err;
165d3c7a3f7SDaniel Ribeiro
166*fdcfd854SBartosz Golaszewski return devm_rtc_register_device(pcap_rtc->rtc);
167d3c7a3f7SDaniel Ribeiro }
168d3c7a3f7SDaniel Ribeiro
pcap_rtc_remove(struct platform_device * pdev)1695cc2b9c6SJingoo Han static int __exit pcap_rtc_remove(struct platform_device *pdev)
170d3c7a3f7SDaniel Ribeiro {
171d3c7a3f7SDaniel Ribeiro return 0;
172d3c7a3f7SDaniel Ribeiro }
173d3c7a3f7SDaniel Ribeiro
174d3c7a3f7SDaniel Ribeiro static struct platform_driver pcap_rtc_driver = {
1755cc2b9c6SJingoo Han .remove = __exit_p(pcap_rtc_remove),
176d3c7a3f7SDaniel Ribeiro .driver = {
177d3c7a3f7SDaniel Ribeiro .name = "pcap-rtc",
178d3c7a3f7SDaniel Ribeiro },
179d3c7a3f7SDaniel Ribeiro };
180d3c7a3f7SDaniel Ribeiro
1810de4c7c1SJingoo Han module_platform_driver_probe(pcap_rtc_driver, pcap_rtc_probe);
182d3c7a3f7SDaniel Ribeiro
183d3c7a3f7SDaniel Ribeiro MODULE_DESCRIPTION("Motorola pcap rtc driver");
184d3c7a3f7SDaniel Ribeiro MODULE_AUTHOR("guiming zhuo <gmzhuo@gmail.com>");
185d3c7a3f7SDaniel Ribeiro MODULE_LICENSE("GPL");
186