12be7f1b5SAlexandre Belloni // SPDX-License-Identifier: GPL-2.0
2f22d9cdcSMiodrag Dinic /* drivers/rtc/rtc-goldfish.c
3f22d9cdcSMiodrag Dinic *
4f22d9cdcSMiodrag Dinic * Copyright (C) 2007 Google, Inc.
5f22d9cdcSMiodrag Dinic * Copyright (C) 2017 Imagination Technologies Ltd.
6f22d9cdcSMiodrag Dinic */
7f22d9cdcSMiodrag Dinic
8bd01386eSAlexandre Belloni #include <linux/io.h>
9f22d9cdcSMiodrag Dinic #include <linux/module.h>
106a6ec8c1SAlexandre Belloni #include <linux/of.h>
11f22d9cdcSMiodrag Dinic #include <linux/platform_device.h>
12f22d9cdcSMiodrag Dinic #include <linux/rtc.h>
133378c7f4SLaurent Vivier #include <linux/goldfish.h>
14*c92e7ef1SLaurent Vivier #include <clocksource/timer-goldfish.h>
15f22d9cdcSMiodrag Dinic
16f22d9cdcSMiodrag Dinic struct goldfish_rtc {
17f22d9cdcSMiodrag Dinic void __iomem *base;
18f22d9cdcSMiodrag Dinic int irq;
19f22d9cdcSMiodrag Dinic struct rtc_device *rtc;
20f22d9cdcSMiodrag Dinic };
21f22d9cdcSMiodrag Dinic
goldfish_rtc_read_alarm(struct device * dev,struct rtc_wkalrm * alrm)22f22d9cdcSMiodrag Dinic static int goldfish_rtc_read_alarm(struct device *dev,
23f22d9cdcSMiodrag Dinic struct rtc_wkalrm *alrm)
24f22d9cdcSMiodrag Dinic {
25f22d9cdcSMiodrag Dinic u64 rtc_alarm;
26f22d9cdcSMiodrag Dinic u64 rtc_alarm_low;
27f22d9cdcSMiodrag Dinic u64 rtc_alarm_high;
28f22d9cdcSMiodrag Dinic void __iomem *base;
29f22d9cdcSMiodrag Dinic struct goldfish_rtc *rtcdrv;
30f22d9cdcSMiodrag Dinic
31f22d9cdcSMiodrag Dinic rtcdrv = dev_get_drvdata(dev);
32f22d9cdcSMiodrag Dinic base = rtcdrv->base;
33f22d9cdcSMiodrag Dinic
343378c7f4SLaurent Vivier rtc_alarm_low = gf_ioread32(base + TIMER_ALARM_LOW);
353378c7f4SLaurent Vivier rtc_alarm_high = gf_ioread32(base + TIMER_ALARM_HIGH);
36f22d9cdcSMiodrag Dinic rtc_alarm = (rtc_alarm_high << 32) | rtc_alarm_low;
37f22d9cdcSMiodrag Dinic
38f22d9cdcSMiodrag Dinic do_div(rtc_alarm, NSEC_PER_SEC);
39f22d9cdcSMiodrag Dinic memset(alrm, 0, sizeof(struct rtc_wkalrm));
40f22d9cdcSMiodrag Dinic
41b509306dSAlexandre Belloni rtc_time64_to_tm(rtc_alarm, &alrm->time);
42f22d9cdcSMiodrag Dinic
433378c7f4SLaurent Vivier if (gf_ioread32(base + TIMER_ALARM_STATUS))
44f22d9cdcSMiodrag Dinic alrm->enabled = 1;
45f22d9cdcSMiodrag Dinic else
46f22d9cdcSMiodrag Dinic alrm->enabled = 0;
47f22d9cdcSMiodrag Dinic
48f22d9cdcSMiodrag Dinic return 0;
49f22d9cdcSMiodrag Dinic }
50f22d9cdcSMiodrag Dinic
goldfish_rtc_set_alarm(struct device * dev,struct rtc_wkalrm * alrm)51f22d9cdcSMiodrag Dinic static int goldfish_rtc_set_alarm(struct device *dev,
52f22d9cdcSMiodrag Dinic struct rtc_wkalrm *alrm)
53f22d9cdcSMiodrag Dinic {
54f22d9cdcSMiodrag Dinic struct goldfish_rtc *rtcdrv;
55f22d9cdcSMiodrag Dinic u64 rtc_alarm64;
56f22d9cdcSMiodrag Dinic u64 rtc_status_reg;
57f22d9cdcSMiodrag Dinic void __iomem *base;
58f22d9cdcSMiodrag Dinic
59f22d9cdcSMiodrag Dinic rtcdrv = dev_get_drvdata(dev);
60f22d9cdcSMiodrag Dinic base = rtcdrv->base;
61f22d9cdcSMiodrag Dinic
62f22d9cdcSMiodrag Dinic if (alrm->enabled) {
63b509306dSAlexandre Belloni rtc_alarm64 = rtc_tm_to_time64(&alrm->time) * NSEC_PER_SEC;
643378c7f4SLaurent Vivier gf_iowrite32((rtc_alarm64 >> 32), base + TIMER_ALARM_HIGH);
653378c7f4SLaurent Vivier gf_iowrite32(rtc_alarm64, base + TIMER_ALARM_LOW);
663378c7f4SLaurent Vivier gf_iowrite32(1, base + TIMER_IRQ_ENABLED);
67f22d9cdcSMiodrag Dinic } else {
68f22d9cdcSMiodrag Dinic /*
69f22d9cdcSMiodrag Dinic * if this function was called with enabled=0
70f22d9cdcSMiodrag Dinic * then it could mean that the application is
71f22d9cdcSMiodrag Dinic * trying to cancel an ongoing alarm
72f22d9cdcSMiodrag Dinic */
733378c7f4SLaurent Vivier rtc_status_reg = gf_ioread32(base + TIMER_ALARM_STATUS);
74f22d9cdcSMiodrag Dinic if (rtc_status_reg)
753378c7f4SLaurent Vivier gf_iowrite32(1, base + TIMER_CLEAR_ALARM);
76f22d9cdcSMiodrag Dinic }
77f22d9cdcSMiodrag Dinic
78b509306dSAlexandre Belloni return 0;
79f22d9cdcSMiodrag Dinic }
80f22d9cdcSMiodrag Dinic
goldfish_rtc_alarm_irq_enable(struct device * dev,unsigned int enabled)81f22d9cdcSMiodrag Dinic static int goldfish_rtc_alarm_irq_enable(struct device *dev,
82f22d9cdcSMiodrag Dinic unsigned int enabled)
83f22d9cdcSMiodrag Dinic {
84f22d9cdcSMiodrag Dinic void __iomem *base;
85f22d9cdcSMiodrag Dinic struct goldfish_rtc *rtcdrv;
86f22d9cdcSMiodrag Dinic
87f22d9cdcSMiodrag Dinic rtcdrv = dev_get_drvdata(dev);
88f22d9cdcSMiodrag Dinic base = rtcdrv->base;
89f22d9cdcSMiodrag Dinic
90f22d9cdcSMiodrag Dinic if (enabled)
913378c7f4SLaurent Vivier gf_iowrite32(1, base + TIMER_IRQ_ENABLED);
92f22d9cdcSMiodrag Dinic else
933378c7f4SLaurent Vivier gf_iowrite32(0, base + TIMER_IRQ_ENABLED);
94f22d9cdcSMiodrag Dinic
95f22d9cdcSMiodrag Dinic return 0;
96f22d9cdcSMiodrag Dinic }
97f22d9cdcSMiodrag Dinic
goldfish_rtc_interrupt(int irq,void * dev_id)98f22d9cdcSMiodrag Dinic static irqreturn_t goldfish_rtc_interrupt(int irq, void *dev_id)
99f22d9cdcSMiodrag Dinic {
100f22d9cdcSMiodrag Dinic struct goldfish_rtc *rtcdrv = dev_id;
101f22d9cdcSMiodrag Dinic void __iomem *base = rtcdrv->base;
102f22d9cdcSMiodrag Dinic
1033378c7f4SLaurent Vivier gf_iowrite32(1, base + TIMER_CLEAR_INTERRUPT);
104f22d9cdcSMiodrag Dinic
105f22d9cdcSMiodrag Dinic rtc_update_irq(rtcdrv->rtc, 1, RTC_IRQF | RTC_AF);
106f22d9cdcSMiodrag Dinic
107f22d9cdcSMiodrag Dinic return IRQ_HANDLED;
108f22d9cdcSMiodrag Dinic }
109f22d9cdcSMiodrag Dinic
goldfish_rtc_read_time(struct device * dev,struct rtc_time * tm)110f22d9cdcSMiodrag Dinic static int goldfish_rtc_read_time(struct device *dev, struct rtc_time *tm)
111f22d9cdcSMiodrag Dinic {
112f22d9cdcSMiodrag Dinic struct goldfish_rtc *rtcdrv;
113f22d9cdcSMiodrag Dinic void __iomem *base;
114f22d9cdcSMiodrag Dinic u64 time_high;
115f22d9cdcSMiodrag Dinic u64 time_low;
116f22d9cdcSMiodrag Dinic u64 time;
117f22d9cdcSMiodrag Dinic
118f22d9cdcSMiodrag Dinic rtcdrv = dev_get_drvdata(dev);
119f22d9cdcSMiodrag Dinic base = rtcdrv->base;
120f22d9cdcSMiodrag Dinic
1213378c7f4SLaurent Vivier time_low = gf_ioread32(base + TIMER_TIME_LOW);
1223378c7f4SLaurent Vivier time_high = gf_ioread32(base + TIMER_TIME_HIGH);
123f22d9cdcSMiodrag Dinic time = (time_high << 32) | time_low;
124f22d9cdcSMiodrag Dinic
125f22d9cdcSMiodrag Dinic do_div(time, NSEC_PER_SEC);
126f22d9cdcSMiodrag Dinic
127b509306dSAlexandre Belloni rtc_time64_to_tm(time, tm);
128f22d9cdcSMiodrag Dinic
129f22d9cdcSMiodrag Dinic return 0;
130f22d9cdcSMiodrag Dinic }
131f22d9cdcSMiodrag Dinic
goldfish_rtc_set_time(struct device * dev,struct rtc_time * tm)132f22d9cdcSMiodrag Dinic static int goldfish_rtc_set_time(struct device *dev, struct rtc_time *tm)
133f22d9cdcSMiodrag Dinic {
134f22d9cdcSMiodrag Dinic struct goldfish_rtc *rtcdrv;
135f22d9cdcSMiodrag Dinic void __iomem *base;
136f22d9cdcSMiodrag Dinic u64 now64;
137f22d9cdcSMiodrag Dinic
138f22d9cdcSMiodrag Dinic rtcdrv = dev_get_drvdata(dev);
139f22d9cdcSMiodrag Dinic base = rtcdrv->base;
140f22d9cdcSMiodrag Dinic
141b509306dSAlexandre Belloni now64 = rtc_tm_to_time64(tm) * NSEC_PER_SEC;
1423378c7f4SLaurent Vivier gf_iowrite32((now64 >> 32), base + TIMER_TIME_HIGH);
1433378c7f4SLaurent Vivier gf_iowrite32(now64, base + TIMER_TIME_LOW);
144f22d9cdcSMiodrag Dinic
145b509306dSAlexandre Belloni return 0;
146f22d9cdcSMiodrag Dinic }
147f22d9cdcSMiodrag Dinic
148f22d9cdcSMiodrag Dinic static const struct rtc_class_ops goldfish_rtc_ops = {
149f22d9cdcSMiodrag Dinic .read_time = goldfish_rtc_read_time,
150f22d9cdcSMiodrag Dinic .set_time = goldfish_rtc_set_time,
151f22d9cdcSMiodrag Dinic .read_alarm = goldfish_rtc_read_alarm,
152f22d9cdcSMiodrag Dinic .set_alarm = goldfish_rtc_set_alarm,
153f22d9cdcSMiodrag Dinic .alarm_irq_enable = goldfish_rtc_alarm_irq_enable
154f22d9cdcSMiodrag Dinic };
155f22d9cdcSMiodrag Dinic
goldfish_rtc_probe(struct platform_device * pdev)156f22d9cdcSMiodrag Dinic static int goldfish_rtc_probe(struct platform_device *pdev)
157f22d9cdcSMiodrag Dinic {
158f22d9cdcSMiodrag Dinic struct goldfish_rtc *rtcdrv;
159f22d9cdcSMiodrag Dinic int err;
160f22d9cdcSMiodrag Dinic
161f22d9cdcSMiodrag Dinic rtcdrv = devm_kzalloc(&pdev->dev, sizeof(*rtcdrv), GFP_KERNEL);
162f22d9cdcSMiodrag Dinic if (!rtcdrv)
163f22d9cdcSMiodrag Dinic return -ENOMEM;
164f22d9cdcSMiodrag Dinic
165f22d9cdcSMiodrag Dinic platform_set_drvdata(pdev, rtcdrv);
16689576bebSMarkus Elfring rtcdrv->base = devm_platform_ioremap_resource(pdev, 0);
167f22d9cdcSMiodrag Dinic if (IS_ERR(rtcdrv->base))
168f4c29a09STiezhu Yang return PTR_ERR(rtcdrv->base);
169f22d9cdcSMiodrag Dinic
170f22d9cdcSMiodrag Dinic rtcdrv->irq = platform_get_irq(pdev, 0);
171f22d9cdcSMiodrag Dinic if (rtcdrv->irq < 0)
172f22d9cdcSMiodrag Dinic return -ENODEV;
173f22d9cdcSMiodrag Dinic
174409b84e3SAlexandre Belloni rtcdrv->rtc = devm_rtc_allocate_device(&pdev->dev);
175f22d9cdcSMiodrag Dinic if (IS_ERR(rtcdrv->rtc))
176f22d9cdcSMiodrag Dinic return PTR_ERR(rtcdrv->rtc);
177f22d9cdcSMiodrag Dinic
178409b84e3SAlexandre Belloni rtcdrv->rtc->ops = &goldfish_rtc_ops;
1795e2954fdSAlexandre Belloni rtcdrv->rtc->range_max = U64_MAX / NSEC_PER_SEC;
180409b84e3SAlexandre Belloni
181f22d9cdcSMiodrag Dinic err = devm_request_irq(&pdev->dev, rtcdrv->irq,
182f22d9cdcSMiodrag Dinic goldfish_rtc_interrupt,
183f22d9cdcSMiodrag Dinic 0, pdev->name, rtcdrv);
184f22d9cdcSMiodrag Dinic if (err)
185f22d9cdcSMiodrag Dinic return err;
186f22d9cdcSMiodrag Dinic
187fdcfd854SBartosz Golaszewski return devm_rtc_register_device(rtcdrv->rtc);
188f22d9cdcSMiodrag Dinic }
189f22d9cdcSMiodrag Dinic
190f22d9cdcSMiodrag Dinic static const struct of_device_id goldfish_rtc_of_match[] = {
191f22d9cdcSMiodrag Dinic { .compatible = "google,goldfish-rtc", },
192f22d9cdcSMiodrag Dinic {},
193f22d9cdcSMiodrag Dinic };
194f22d9cdcSMiodrag Dinic MODULE_DEVICE_TABLE(of, goldfish_rtc_of_match);
195f22d9cdcSMiodrag Dinic
196f22d9cdcSMiodrag Dinic static struct platform_driver goldfish_rtc = {
197f22d9cdcSMiodrag Dinic .probe = goldfish_rtc_probe,
198f22d9cdcSMiodrag Dinic .driver = {
199f22d9cdcSMiodrag Dinic .name = "goldfish_rtc",
200f22d9cdcSMiodrag Dinic .of_match_table = goldfish_rtc_of_match,
201f22d9cdcSMiodrag Dinic }
202f22d9cdcSMiodrag Dinic };
203f22d9cdcSMiodrag Dinic
204f22d9cdcSMiodrag Dinic module_platform_driver(goldfish_rtc);
20582d632b8SJames Hogan
20682d632b8SJames Hogan MODULE_LICENSE("GPL v2");
207