Commit | Line | Data |
---|---|---|
77c13d99 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
ba172208 BS |
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> | |
ba172208 BS |
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, 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, 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, &now); | |
98 | if (ret < 0) | |
99 | return ret; | |
100 | rtc_time64_to_tm(now, tm); | |
101 | ||
102 | return 0; | |
103 | } | |
104 | ||
72ef256e | 105 | static int dc_rtc_set_time(struct device *dev, struct rtc_time *tm) |
ba172208 BS |
106 | { |
107 | struct dc_rtc *rtc = dev_get_drvdata(dev); | |
108 | ||
72ef256e | 109 | return dc_rtc_write(rtc, rtc_tm_to_time64(tm)); |
ba172208 BS |
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(reference + alarm_reg, &alarm->time); | |
122 | ||
123 | ret = dc_rtc_read(rtc, &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(&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 | ||
34c7b3ac | 158 | static const struct rtc_class_ops dc_rtc_ops = { |
ba172208 | 159 | .read_time = dc_rtc_read_time, |
72ef256e | 160 | .set_time = dc_rtc_set_time, |
ba172208 BS |
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_dev, 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 resource *res; | |
179 | struct dc_rtc *rtc; | |
180 | int irq, ret; | |
181 | ||
182 | rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); | |
183 | if (!rtc) | |
184 | return -ENOMEM; | |
185 | ||
186 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
187 | rtc->regs = devm_ioremap_resource(&pdev->dev, res); | |
188 | if (IS_ERR(rtc->regs)) | |
189 | return PTR_ERR(rtc->regs); | |
190 | ||
060711f5 AB |
191 | rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev); |
192 | if (IS_ERR(rtc->rtc_dev)) | |
193 | return PTR_ERR(rtc->rtc_dev); | |
194 | ||
ba172208 BS |
195 | irq = platform_get_irq(pdev, 0); |
196 | if (irq < 0) | |
197 | return irq; | |
198 | ret = devm_request_irq(&pdev->dev, irq, dc_rtc_irq, 0, pdev->name, rtc); | |
199 | if (ret < 0) | |
200 | return ret; | |
201 | ||
202 | platform_set_drvdata(pdev, rtc); | |
ba172208 | 203 | |
060711f5 | 204 | rtc->rtc_dev->ops = &dc_rtc_ops; |
e5fe3c3e | 205 | rtc->rtc_dev->range_max = U32_MAX; |
060711f5 AB |
206 | |
207 | return rtc_register_device(rtc->rtc_dev); | |
ba172208 BS |
208 | } |
209 | ||
210 | static const struct of_device_id dc_dt_ids[] = { | |
211 | { .compatible = "cnxt,cx92755-rtc" }, | |
212 | { /* sentinel */ } | |
213 | }; | |
214 | MODULE_DEVICE_TABLE(of, dc_dt_ids); | |
215 | ||
216 | static struct platform_driver dc_rtc_driver = { | |
217 | .driver = { | |
218 | .name = "digicolor_rtc", | |
219 | .of_match_table = of_match_ptr(dc_dt_ids), | |
220 | }, | |
221 | }; | |
222 | module_platform_driver_probe(dc_rtc_driver, dc_rtc_probe); | |
223 | ||
224 | MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>"); | |
225 | MODULE_DESCRIPTION("Conexant Digicolor Realtime Clock Driver (RTC)"); | |
226 | MODULE_LICENSE("GPL"); |