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