1 // SPDX-License-Identifier: GPL-2.0+
3 * APM X-Gene SoC Real Time Clock Driver
5 * Copyright (c) 2014, Applied Micro Circuits Corporation
6 * Author: Rameshwar Prasad Sahu <rsahu@apm.com>
10 #include <linux/clk.h>
11 #include <linux/delay.h>
12 #include <linux/init.h>
14 #include <linux/module.h>
16 #include <linux/platform_device.h>
17 #include <linux/rtc.h>
18 #include <linux/slab.h>
20 /* RTC CSR Registers */
25 #define RTC_CCR_IE BIT(0)
26 #define RTC_CCR_MASK BIT(1)
27 #define RTC_CCR_EN BIT(2)
28 #define RTC_CCR_WEN BIT(3)
30 #define RTC_STAT_BIT BIT(0)
31 #define RTC_RSTAT 0x14
35 struct xgene_rtc_dev
{
36 struct rtc_device
*rtc
;
37 void __iomem
*csr_base
;
39 unsigned int irq_wake
;
40 unsigned int irq_enabled
;
43 static int xgene_rtc_read_time(struct device
*dev
, struct rtc_time
*tm
)
45 struct xgene_rtc_dev
*pdata
= dev_get_drvdata(dev
);
47 rtc_time64_to_tm(readl(pdata
->csr_base
+ RTC_CCVR
), tm
);
51 static int xgene_rtc_set_time(struct device
*dev
, struct rtc_time
*tm
)
53 struct xgene_rtc_dev
*pdata
= dev_get_drvdata(dev
);
56 * NOTE: After the following write, the RTC_CCVR is only reflected
57 * after the update cycle of 1 seconds.
59 writel((u32
)rtc_tm_to_time64(tm
), pdata
->csr_base
+ RTC_CLR
);
60 readl(pdata
->csr_base
+ RTC_CLR
); /* Force a barrier */
65 static int xgene_rtc_read_alarm(struct device
*dev
, struct rtc_wkalrm
*alrm
)
67 struct xgene_rtc_dev
*pdata
= dev_get_drvdata(dev
);
69 /* If possible, CMR should be read here */
70 rtc_time64_to_tm(0, &alrm
->time
);
71 alrm
->enabled
= readl(pdata
->csr_base
+ RTC_CCR
) & RTC_CCR_IE
;
76 static int xgene_rtc_alarm_irq_enable(struct device
*dev
, u32 enabled
)
78 struct xgene_rtc_dev
*pdata
= dev_get_drvdata(dev
);
81 ccr
= readl(pdata
->csr_base
+ RTC_CCR
);
89 writel(ccr
, pdata
->csr_base
+ RTC_CCR
);
94 static int xgene_rtc_alarm_irq_enabled(struct device
*dev
)
96 struct xgene_rtc_dev
*pdata
= dev_get_drvdata(dev
);
98 return readl(pdata
->csr_base
+ RTC_CCR
) & RTC_CCR_IE
? 1 : 0;
101 static int xgene_rtc_set_alarm(struct device
*dev
, struct rtc_wkalrm
*alrm
)
103 struct xgene_rtc_dev
*pdata
= dev_get_drvdata(dev
);
105 writel((u32
)rtc_tm_to_time64(&alrm
->time
), pdata
->csr_base
+ RTC_CMR
);
107 xgene_rtc_alarm_irq_enable(dev
, alrm
->enabled
);
112 static const struct rtc_class_ops xgene_rtc_ops
= {
113 .read_time
= xgene_rtc_read_time
,
114 .set_time
= xgene_rtc_set_time
,
115 .read_alarm
= xgene_rtc_read_alarm
,
116 .set_alarm
= xgene_rtc_set_alarm
,
117 .alarm_irq_enable
= xgene_rtc_alarm_irq_enable
,
120 static irqreturn_t
xgene_rtc_interrupt(int irq
, void *id
)
122 struct xgene_rtc_dev
*pdata
= id
;
124 /* Check if interrupt asserted */
125 if (!(readl(pdata
->csr_base
+ RTC_STAT
) & RTC_STAT_BIT
))
128 /* Clear interrupt */
129 readl(pdata
->csr_base
+ RTC_EOI
);
131 rtc_update_irq(pdata
->rtc
, 1, RTC_IRQF
| RTC_AF
);
136 static int xgene_rtc_probe(struct platform_device
*pdev
)
138 struct xgene_rtc_dev
*pdata
;
142 pdata
= devm_kzalloc(&pdev
->dev
, sizeof(*pdata
), GFP_KERNEL
);
145 platform_set_drvdata(pdev
, pdata
);
147 pdata
->csr_base
= devm_platform_ioremap_resource(pdev
, 0);
148 if (IS_ERR(pdata
->csr_base
))
149 return PTR_ERR(pdata
->csr_base
);
151 pdata
->rtc
= devm_rtc_allocate_device(&pdev
->dev
);
152 if (IS_ERR(pdata
->rtc
))
153 return PTR_ERR(pdata
->rtc
);
155 irq
= platform_get_irq(pdev
, 0);
158 ret
= devm_request_irq(&pdev
->dev
, irq
, xgene_rtc_interrupt
, 0,
159 dev_name(&pdev
->dev
), pdata
);
161 dev_err(&pdev
->dev
, "Could not request IRQ\n");
165 pdata
->clk
= devm_clk_get(&pdev
->dev
, NULL
);
166 if (IS_ERR(pdata
->clk
)) {
167 dev_err(&pdev
->dev
, "Couldn't get the clock for RTC\n");
170 ret
= clk_prepare_enable(pdata
->clk
);
174 /* Turn on the clock and the crystal */
175 writel(RTC_CCR_EN
, pdata
->csr_base
+ RTC_CCR
);
177 ret
= device_init_wakeup(&pdev
->dev
, 1);
179 clk_disable_unprepare(pdata
->clk
);
183 /* HW does not support update faster than 1 seconds */
184 pdata
->rtc
->uie_unsupported
= 1;
185 pdata
->rtc
->ops
= &xgene_rtc_ops
;
186 pdata
->rtc
->range_max
= U32_MAX
;
188 ret
= rtc_register_device(pdata
->rtc
);
190 clk_disable_unprepare(pdata
->clk
);
197 static int xgene_rtc_remove(struct platform_device
*pdev
)
199 struct xgene_rtc_dev
*pdata
= platform_get_drvdata(pdev
);
201 xgene_rtc_alarm_irq_enable(&pdev
->dev
, 0);
202 device_init_wakeup(&pdev
->dev
, 0);
203 clk_disable_unprepare(pdata
->clk
);
207 static int __maybe_unused
xgene_rtc_suspend(struct device
*dev
)
209 struct platform_device
*pdev
= to_platform_device(dev
);
210 struct xgene_rtc_dev
*pdata
= platform_get_drvdata(pdev
);
213 irq
= platform_get_irq(pdev
, 0);
216 * If this RTC alarm will be used for waking the system up,
217 * don't disable it of course. Else we just disable the alarm
218 * and await suspension.
220 if (device_may_wakeup(&pdev
->dev
)) {
221 if (!enable_irq_wake(irq
))
224 pdata
->irq_enabled
= xgene_rtc_alarm_irq_enabled(dev
);
225 xgene_rtc_alarm_irq_enable(dev
, 0);
226 clk_disable_unprepare(pdata
->clk
);
231 static int __maybe_unused
xgene_rtc_resume(struct device
*dev
)
233 struct platform_device
*pdev
= to_platform_device(dev
);
234 struct xgene_rtc_dev
*pdata
= platform_get_drvdata(pdev
);
238 irq
= platform_get_irq(pdev
, 0);
240 if (device_may_wakeup(&pdev
->dev
)) {
241 if (pdata
->irq_wake
) {
242 disable_irq_wake(irq
);
246 rc
= clk_prepare_enable(pdata
->clk
);
248 dev_err(dev
, "Unable to enable clock error %d\n", rc
);
251 xgene_rtc_alarm_irq_enable(dev
, pdata
->irq_enabled
);
257 static SIMPLE_DEV_PM_OPS(xgene_rtc_pm_ops
, xgene_rtc_suspend
, xgene_rtc_resume
);
260 static const struct of_device_id xgene_rtc_of_match
[] = {
261 {.compatible
= "apm,xgene-rtc" },
264 MODULE_DEVICE_TABLE(of
, xgene_rtc_of_match
);
267 static struct platform_driver xgene_rtc_driver
= {
268 .probe
= xgene_rtc_probe
,
269 .remove
= xgene_rtc_remove
,
272 .pm
= &xgene_rtc_pm_ops
,
273 .of_match_table
= of_match_ptr(xgene_rtc_of_match
),
277 module_platform_driver(xgene_rtc_driver
);
279 MODULE_DESCRIPTION("APM X-Gene SoC RTC driver");
280 MODULE_AUTHOR("Rameshwar Sahu <rsahu@apm.com>");
281 MODULE_LICENSE("GPL");