1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * RTC driver for NXP LPC178x/18xx/43xx Real-Time Clock (RTC)
5 * Copyright (C) 2011 NXP Semiconductors
6 * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com>
11 #include <linux/kernel.h>
12 #include <linux/mod_devicetable.h>
13 #include <linux/module.h>
14 #include <linux/platform_device.h>
15 #include <linux/rtc.h>
17 /* LPC24xx RTC register offsets and bits */
18 #define LPC24XX_ILR 0x00
19 #define LPC24XX_RTCCIF BIT(0)
20 #define LPC24XX_RTCALF BIT(1)
21 #define LPC24XX_CTC 0x04
22 #define LPC24XX_CCR 0x08
23 #define LPC24XX_CLKEN BIT(0)
24 #define LPC178X_CCALEN BIT(4)
25 #define LPC24XX_CIIR 0x0c
26 #define LPC24XX_AMR 0x10
27 #define LPC24XX_ALARM_DISABLE 0xff
28 #define LPC24XX_CTIME0 0x14
29 #define LPC24XX_CTIME1 0x18
30 #define LPC24XX_CTIME2 0x1c
31 #define LPC24XX_SEC 0x20
32 #define LPC24XX_MIN 0x24
33 #define LPC24XX_HOUR 0x28
34 #define LPC24XX_DOM 0x2c
35 #define LPC24XX_DOW 0x30
36 #define LPC24XX_DOY 0x34
37 #define LPC24XX_MONTH 0x38
38 #define LPC24XX_YEAR 0x3c
39 #define LPC24XX_ALSEC 0x60
40 #define LPC24XX_ALMIN 0x64
41 #define LPC24XX_ALHOUR 0x68
42 #define LPC24XX_ALDOM 0x6c
43 #define LPC24XX_ALDOW 0x70
44 #define LPC24XX_ALDOY 0x74
45 #define LPC24XX_ALMON 0x78
46 #define LPC24XX_ALYEAR 0x7c
48 /* Macros to read fields in consolidated time (CT) registers */
49 #define CT0_SECS(x) (((x) >> 0) & 0x3f)
50 #define CT0_MINS(x) (((x) >> 8) & 0x3f)
51 #define CT0_HOURS(x) (((x) >> 16) & 0x1f)
52 #define CT0_DOW(x) (((x) >> 24) & 0x07)
53 #define CT1_DOM(x) (((x) >> 0) & 0x1f)
54 #define CT1_MONTH(x) (((x) >> 8) & 0x0f)
55 #define CT1_YEAR(x) (((x) >> 16) & 0xfff)
56 #define CT2_DOY(x) (((x) >> 0) & 0xfff)
58 #define rtc_readl(dev, reg) readl((dev)->rtc_base + (reg))
59 #define rtc_writel(dev, reg, val) writel((val), (dev)->rtc_base + (reg))
62 void __iomem
*rtc_base
;
63 struct rtc_device
*rtc
;
68 static int lpc24xx_rtc_set_time(struct device
*dev
, struct rtc_time
*tm
)
70 struct lpc24xx_rtc
*rtc
= dev_get_drvdata(dev
);
72 /* Disable RTC during update */
73 rtc_writel(rtc
, LPC24XX_CCR
, LPC178X_CCALEN
);
75 rtc_writel(rtc
, LPC24XX_SEC
, tm
->tm_sec
);
76 rtc_writel(rtc
, LPC24XX_MIN
, tm
->tm_min
);
77 rtc_writel(rtc
, LPC24XX_HOUR
, tm
->tm_hour
);
78 rtc_writel(rtc
, LPC24XX_DOW
, tm
->tm_wday
);
79 rtc_writel(rtc
, LPC24XX_DOM
, tm
->tm_mday
);
80 rtc_writel(rtc
, LPC24XX_DOY
, tm
->tm_yday
);
81 rtc_writel(rtc
, LPC24XX_MONTH
, tm
->tm_mon
);
82 rtc_writel(rtc
, LPC24XX_YEAR
, tm
->tm_year
);
84 rtc_writel(rtc
, LPC24XX_CCR
, LPC24XX_CLKEN
| LPC178X_CCALEN
);
89 static int lpc24xx_rtc_read_time(struct device
*dev
, struct rtc_time
*tm
)
91 struct lpc24xx_rtc
*rtc
= dev_get_drvdata(dev
);
94 ct0
= rtc_readl(rtc
, LPC24XX_CTIME0
);
95 ct1
= rtc_readl(rtc
, LPC24XX_CTIME1
);
96 ct2
= rtc_readl(rtc
, LPC24XX_CTIME2
);
98 tm
->tm_sec
= CT0_SECS(ct0
);
99 tm
->tm_min
= CT0_MINS(ct0
);
100 tm
->tm_hour
= CT0_HOURS(ct0
);
101 tm
->tm_wday
= CT0_DOW(ct0
);
102 tm
->tm_mon
= CT1_MONTH(ct1
);
103 tm
->tm_mday
= CT1_DOM(ct1
);
104 tm
->tm_year
= CT1_YEAR(ct1
);
105 tm
->tm_yday
= CT2_DOY(ct2
);
110 static int lpc24xx_rtc_read_alarm(struct device
*dev
, struct rtc_wkalrm
*wkalrm
)
112 struct lpc24xx_rtc
*rtc
= dev_get_drvdata(dev
);
113 struct rtc_time
*tm
= &wkalrm
->time
;
115 tm
->tm_sec
= rtc_readl(rtc
, LPC24XX_ALSEC
);
116 tm
->tm_min
= rtc_readl(rtc
, LPC24XX_ALMIN
);
117 tm
->tm_hour
= rtc_readl(rtc
, LPC24XX_ALHOUR
);
118 tm
->tm_mday
= rtc_readl(rtc
, LPC24XX_ALDOM
);
119 tm
->tm_wday
= rtc_readl(rtc
, LPC24XX_ALDOW
);
120 tm
->tm_yday
= rtc_readl(rtc
, LPC24XX_ALDOY
);
121 tm
->tm_mon
= rtc_readl(rtc
, LPC24XX_ALMON
);
122 tm
->tm_year
= rtc_readl(rtc
, LPC24XX_ALYEAR
);
124 wkalrm
->enabled
= rtc_readl(rtc
, LPC24XX_AMR
) == 0;
125 wkalrm
->pending
= !!(rtc_readl(rtc
, LPC24XX_ILR
) & LPC24XX_RTCCIF
);
127 return rtc_valid_tm(&wkalrm
->time
);
130 static int lpc24xx_rtc_set_alarm(struct device
*dev
, struct rtc_wkalrm
*wkalrm
)
132 struct lpc24xx_rtc
*rtc
= dev_get_drvdata(dev
);
133 struct rtc_time
*tm
= &wkalrm
->time
;
135 /* Disable alarm irq during update */
136 rtc_writel(rtc
, LPC24XX_AMR
, LPC24XX_ALARM_DISABLE
);
138 rtc_writel(rtc
, LPC24XX_ALSEC
, tm
->tm_sec
);
139 rtc_writel(rtc
, LPC24XX_ALMIN
, tm
->tm_min
);
140 rtc_writel(rtc
, LPC24XX_ALHOUR
, tm
->tm_hour
);
141 rtc_writel(rtc
, LPC24XX_ALDOM
, tm
->tm_mday
);
142 rtc_writel(rtc
, LPC24XX_ALDOW
, tm
->tm_wday
);
143 rtc_writel(rtc
, LPC24XX_ALDOY
, tm
->tm_yday
);
144 rtc_writel(rtc
, LPC24XX_ALMON
, tm
->tm_mon
);
145 rtc_writel(rtc
, LPC24XX_ALYEAR
, tm
->tm_year
);
148 rtc_writel(rtc
, LPC24XX_AMR
, 0);
153 static int lpc24xx_rtc_alarm_irq_enable(struct device
*dev
, unsigned int enable
)
155 struct lpc24xx_rtc
*rtc
= dev_get_drvdata(dev
);
158 rtc_writel(rtc
, LPC24XX_AMR
, 0);
160 rtc_writel(rtc
, LPC24XX_AMR
, LPC24XX_ALARM_DISABLE
);
165 static irqreturn_t
lpc24xx_rtc_interrupt(int irq
, void *data
)
167 unsigned long events
= RTC_IRQF
;
168 struct lpc24xx_rtc
*rtc
= data
;
171 /* Check interrupt cause */
172 rtc_iir
= rtc_readl(rtc
, LPC24XX_ILR
);
173 if (rtc_iir
& LPC24XX_RTCALF
) {
175 rtc_writel(rtc
, LPC24XX_AMR
, LPC24XX_ALARM_DISABLE
);
178 /* Clear interrupt status and report event */
179 rtc_writel(rtc
, LPC24XX_ILR
, rtc_iir
);
180 rtc_update_irq(rtc
->rtc
, 1, events
);
185 static const struct rtc_class_ops lpc24xx_rtc_ops
= {
186 .read_time
= lpc24xx_rtc_read_time
,
187 .set_time
= lpc24xx_rtc_set_time
,
188 .read_alarm
= lpc24xx_rtc_read_alarm
,
189 .set_alarm
= lpc24xx_rtc_set_alarm
,
190 .alarm_irq_enable
= lpc24xx_rtc_alarm_irq_enable
,
193 static int lpc24xx_rtc_probe(struct platform_device
*pdev
)
195 struct lpc24xx_rtc
*rtc
;
198 rtc
= devm_kzalloc(&pdev
->dev
, sizeof(*rtc
), GFP_KERNEL
);
202 rtc
->rtc_base
= devm_platform_ioremap_resource(pdev
, 0);
203 if (IS_ERR(rtc
->rtc_base
))
204 return PTR_ERR(rtc
->rtc_base
);
206 irq
= platform_get_irq(pdev
, 0);
210 rtc
->clk_rtc
= devm_clk_get(&pdev
->dev
, "rtc");
211 if (IS_ERR(rtc
->clk_rtc
)) {
212 dev_err(&pdev
->dev
, "error getting rtc clock\n");
213 return PTR_ERR(rtc
->clk_rtc
);
216 rtc
->clk_reg
= devm_clk_get(&pdev
->dev
, "reg");
217 if (IS_ERR(rtc
->clk_reg
)) {
218 dev_err(&pdev
->dev
, "error getting reg clock\n");
219 return PTR_ERR(rtc
->clk_reg
);
222 ret
= clk_prepare_enable(rtc
->clk_rtc
);
224 dev_err(&pdev
->dev
, "unable to enable rtc clock\n");
228 ret
= clk_prepare_enable(rtc
->clk_reg
);
230 dev_err(&pdev
->dev
, "unable to enable reg clock\n");
231 goto disable_rtc_clk
;
234 platform_set_drvdata(pdev
, rtc
);
236 /* Clear any pending interrupts */
237 rtc_writel(rtc
, LPC24XX_ILR
, LPC24XX_RTCCIF
| LPC24XX_RTCALF
);
239 /* Enable RTC count */
240 rtc_writel(rtc
, LPC24XX_CCR
, LPC24XX_CLKEN
| LPC178X_CCALEN
);
242 ret
= devm_request_irq(&pdev
->dev
, irq
, lpc24xx_rtc_interrupt
, 0,
245 dev_warn(&pdev
->dev
, "can't request interrupt\n");
249 rtc
->rtc
= devm_rtc_device_register(&pdev
->dev
, "lpc24xx-rtc",
250 &lpc24xx_rtc_ops
, THIS_MODULE
);
251 if (IS_ERR(rtc
->rtc
)) {
252 dev_err(&pdev
->dev
, "can't register rtc device\n");
253 ret
= PTR_ERR(rtc
->rtc
);
260 clk_disable_unprepare(rtc
->clk_reg
);
262 clk_disable_unprepare(rtc
->clk_rtc
);
266 static void lpc24xx_rtc_remove(struct platform_device
*pdev
)
268 struct lpc24xx_rtc
*rtc
= platform_get_drvdata(pdev
);
270 /* Ensure all interrupt sources are masked */
271 rtc_writel(rtc
, LPC24XX_AMR
, LPC24XX_ALARM_DISABLE
);
272 rtc_writel(rtc
, LPC24XX_CIIR
, 0);
274 rtc_writel(rtc
, LPC24XX_CCR
, LPC178X_CCALEN
);
276 clk_disable_unprepare(rtc
->clk_rtc
);
277 clk_disable_unprepare(rtc
->clk_reg
);
280 static const struct of_device_id lpc24xx_rtc_match
[] = {
281 { .compatible
= "nxp,lpc1788-rtc" },
284 MODULE_DEVICE_TABLE(of
, lpc24xx_rtc_match
);
286 static struct platform_driver lpc24xx_rtc_driver
= {
287 .probe
= lpc24xx_rtc_probe
,
288 .remove
= lpc24xx_rtc_remove
,
290 .name
= "lpc24xx-rtc",
291 .of_match_table
= lpc24xx_rtc_match
,
294 module_platform_driver(lpc24xx_rtc_driver
);
296 MODULE_AUTHOR("Kevin Wells <wellsk40@gmail.com>");
297 MODULE_DESCRIPTION("RTC driver for the LPC178x/18xx/408x/43xx SoCs");
298 MODULE_LICENSE("GPL");