1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Real Time Clock driver for Conexant Digicolor |
4 | * |
5 | * Copyright (C) 2015 Paradox Innovation Ltd. |
6 | * |
7 | * Author: Baruch Siach <baruch@tkos.co.il> |
8 | */ |
9 | |
10 | #include <linux/io.h> |
11 | #include <linux/iopoll.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/module.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/rtc.h> |
16 | #include <linux/of.h> |
17 | |
18 | #define DC_RTC_CONTROL 0x0 |
19 | #define DC_RTC_TIME 0x8 |
20 | #define DC_RTC_REFERENCE 0xc |
21 | #define DC_RTC_ALARM 0x10 |
22 | #define DC_RTC_INTFLAG_CLEAR 0x14 |
23 | #define DC_RTC_INTENABLE 0x16 |
24 | |
25 | #define DC_RTC_CMD_MASK 0xf |
26 | #define DC_RTC_GO_BUSY BIT(7) |
27 | |
28 | #define CMD_NOP 0 |
29 | #define CMD_RESET 1 |
30 | #define CMD_WRITE 3 |
31 | #define CMD_READ 4 |
32 | |
33 | #define CMD_DELAY_US (10*1000) |
34 | #define CMD_TIMEOUT_US (500*CMD_DELAY_US) |
35 | |
36 | struct dc_rtc { |
37 | struct rtc_device *rtc_dev; |
38 | void __iomem *regs; |
39 | }; |
40 | |
41 | static int dc_rtc_cmds(struct dc_rtc *rtc, const u8 *cmds, int len) |
42 | { |
43 | u8 val; |
44 | int i, ret; |
45 | |
46 | for (i = 0; i < len; i++) { |
47 | writeb_relaxed((cmds[i] & DC_RTC_CMD_MASK) | DC_RTC_GO_BUSY, |
48 | rtc->regs + DC_RTC_CONTROL); |
49 | ret = readb_relaxed_poll_timeout( |
50 | rtc->regs + DC_RTC_CONTROL, val, |
51 | !(val & DC_RTC_GO_BUSY), CMD_DELAY_US, CMD_TIMEOUT_US); |
52 | if (ret < 0) |
53 | return ret; |
54 | } |
55 | |
56 | return 0; |
57 | } |
58 | |
59 | static int dc_rtc_read(struct dc_rtc *rtc, unsigned long *val) |
60 | { |
61 | static const u8 read_cmds[] = {CMD_READ, CMD_NOP}; |
62 | u32 reference, time1, time2; |
63 | int ret; |
64 | |
65 | ret = dc_rtc_cmds(rtc, cmds: read_cmds, ARRAY_SIZE(read_cmds)); |
66 | if (ret < 0) |
67 | return ret; |
68 | |
69 | reference = readl_relaxed(rtc->regs + DC_RTC_REFERENCE); |
70 | time1 = readl_relaxed(rtc->regs + DC_RTC_TIME); |
71 | /* Read twice to ensure consistency */ |
72 | while (1) { |
73 | time2 = readl_relaxed(rtc->regs + DC_RTC_TIME); |
74 | if (time1 == time2) |
75 | break; |
76 | time1 = time2; |
77 | } |
78 | |
79 | *val = reference + time1; |
80 | return 0; |
81 | } |
82 | |
83 | static int dc_rtc_write(struct dc_rtc *rtc, u32 val) |
84 | { |
85 | static const u8 write_cmds[] = {CMD_WRITE, CMD_NOP, CMD_RESET, CMD_NOP}; |
86 | |
87 | writel_relaxed(val, rtc->regs + DC_RTC_REFERENCE); |
88 | return dc_rtc_cmds(rtc, cmds: write_cmds, ARRAY_SIZE(write_cmds)); |
89 | } |
90 | |
91 | static int dc_rtc_read_time(struct device *dev, struct rtc_time *tm) |
92 | { |
93 | struct dc_rtc *rtc = dev_get_drvdata(dev); |
94 | unsigned long now; |
95 | int ret; |
96 | |
97 | ret = dc_rtc_read(rtc, val: &now); |
98 | if (ret < 0) |
99 | return ret; |
100 | rtc_time64_to_tm(time: now, tm); |
101 | |
102 | return 0; |
103 | } |
104 | |
105 | static int dc_rtc_set_time(struct device *dev, struct rtc_time *tm) |
106 | { |
107 | struct dc_rtc *rtc = dev_get_drvdata(dev); |
108 | |
109 | return dc_rtc_write(rtc, val: rtc_tm_to_time64(tm)); |
110 | } |
111 | |
112 | static int dc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) |
113 | { |
114 | struct dc_rtc *rtc = dev_get_drvdata(dev); |
115 | u32 alarm_reg, reference; |
116 | unsigned long now; |
117 | int ret; |
118 | |
119 | alarm_reg = readl_relaxed(rtc->regs + DC_RTC_ALARM); |
120 | reference = readl_relaxed(rtc->regs + DC_RTC_REFERENCE); |
121 | rtc_time64_to_tm(time: reference + alarm_reg, tm: &alarm->time); |
122 | |
123 | ret = dc_rtc_read(rtc, val: &now); |
124 | if (ret < 0) |
125 | return ret; |
126 | |
127 | alarm->pending = alarm_reg + reference > now; |
128 | alarm->enabled = readl_relaxed(rtc->regs + DC_RTC_INTENABLE); |
129 | |
130 | return 0; |
131 | } |
132 | |
133 | static int dc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) |
134 | { |
135 | struct dc_rtc *rtc = dev_get_drvdata(dev); |
136 | time64_t alarm_time; |
137 | u32 reference; |
138 | |
139 | alarm_time = rtc_tm_to_time64(tm: &alarm->time); |
140 | |
141 | reference = readl_relaxed(rtc->regs + DC_RTC_REFERENCE); |
142 | writel_relaxed(alarm_time - reference, rtc->regs + DC_RTC_ALARM); |
143 | |
144 | writeb_relaxed(!!alarm->enabled, rtc->regs + DC_RTC_INTENABLE); |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | static int dc_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) |
150 | { |
151 | struct dc_rtc *rtc = dev_get_drvdata(dev); |
152 | |
153 | writeb_relaxed(!!enabled, rtc->regs + DC_RTC_INTENABLE); |
154 | |
155 | return 0; |
156 | } |
157 | |
158 | static const struct rtc_class_ops dc_rtc_ops = { |
159 | .read_time = dc_rtc_read_time, |
160 | .set_time = dc_rtc_set_time, |
161 | .read_alarm = dc_rtc_read_alarm, |
162 | .set_alarm = dc_rtc_set_alarm, |
163 | .alarm_irq_enable = dc_rtc_alarm_irq_enable, |
164 | }; |
165 | |
166 | static irqreturn_t dc_rtc_irq(int irq, void *dev_id) |
167 | { |
168 | struct dc_rtc *rtc = dev_id; |
169 | |
170 | writeb_relaxed(1, rtc->regs + DC_RTC_INTFLAG_CLEAR); |
171 | rtc_update_irq(rtc: rtc->rtc_dev, num: 1, RTC_AF | RTC_IRQF); |
172 | |
173 | return IRQ_HANDLED; |
174 | } |
175 | |
176 | static int __init dc_rtc_probe(struct platform_device *pdev) |
177 | { |
178 | struct dc_rtc *rtc; |
179 | int irq, ret; |
180 | |
181 | rtc = devm_kzalloc(dev: &pdev->dev, size: sizeof(*rtc), GFP_KERNEL); |
182 | if (!rtc) |
183 | return -ENOMEM; |
184 | |
185 | rtc->regs = devm_platform_ioremap_resource(pdev, index: 0); |
186 | if (IS_ERR(ptr: rtc->regs)) |
187 | return PTR_ERR(ptr: rtc->regs); |
188 | |
189 | rtc->rtc_dev = devm_rtc_allocate_device(dev: &pdev->dev); |
190 | if (IS_ERR(ptr: rtc->rtc_dev)) |
191 | return PTR_ERR(ptr: rtc->rtc_dev); |
192 | |
193 | irq = platform_get_irq(pdev, 0); |
194 | if (irq < 0) |
195 | return irq; |
196 | ret = devm_request_irq(dev: &pdev->dev, irq, handler: dc_rtc_irq, irqflags: 0, devname: pdev->name, dev_id: rtc); |
197 | if (ret < 0) |
198 | return ret; |
199 | |
200 | platform_set_drvdata(pdev, data: rtc); |
201 | |
202 | rtc->rtc_dev->ops = &dc_rtc_ops; |
203 | rtc->rtc_dev->range_max = U32_MAX; |
204 | |
205 | return devm_rtc_register_device(rtc->rtc_dev); |
206 | } |
207 | |
208 | static const __maybe_unused struct of_device_id dc_dt_ids[] = { |
209 | { .compatible = "cnxt,cx92755-rtc" }, |
210 | { /* sentinel */ } |
211 | }; |
212 | MODULE_DEVICE_TABLE(of, dc_dt_ids); |
213 | |
214 | static struct platform_driver dc_rtc_driver = { |
215 | .driver = { |
216 | .name = "digicolor_rtc" , |
217 | .of_match_table = of_match_ptr(dc_dt_ids), |
218 | }, |
219 | }; |
220 | module_platform_driver_probe(dc_rtc_driver, dc_rtc_probe); |
221 | |
222 | MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>" ); |
223 | MODULE_DESCRIPTION("Conexant Digicolor Realtime Clock Driver (RTC)" ); |
224 | MODULE_LICENSE("GPL" ); |
225 | |