]>
Commit | Line | Data |
---|---|---|
fcb624be SA |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Holtek HT1380/HT1381 Serial Timekeeper Chip | |
4 | * | |
5 | * Communication with the chip is vendor-specific. | |
6 | * It is done via 3 GPIO pins: reset, clock, and data. | |
7 | * Describe in .dts this way: | |
8 | * | |
9 | * rtc { | |
10 | * compatible = "holtek,ht1380"; | |
11 | * rst-gpios = <&gpio 19 GPIO_ACTIVE_LOW>; | |
12 | * clk-gpios = <&gpio 20 GPIO_ACTIVE_HIGH>; | |
13 | * dat-gpios = <&gpio 21 GPIO_ACTIVE_HIGH>; | |
14 | * }; | |
15 | * | |
16 | */ | |
17 | ||
d678a59d | 18 | #include <common.h> |
fcb624be SA |
19 | #include <dm.h> |
20 | #include <rtc.h> | |
21 | #include <bcd.h> | |
22 | #include <asm/gpio.h> | |
23 | #include <linux/delay.h> | |
24 | ||
25 | struct ht1380_priv { | |
26 | struct gpio_desc rst_desc; | |
27 | struct gpio_desc clk_desc; | |
28 | struct gpio_desc dat_desc; | |
29 | }; | |
30 | ||
31 | enum registers { | |
32 | SEC, | |
33 | MIN, | |
34 | HOUR, | |
35 | MDAY, | |
36 | MONTH, | |
37 | WDAY, | |
38 | YEAR, | |
39 | WP, | |
40 | N_REGS | |
41 | }; | |
42 | ||
43 | enum hour_mode { | |
44 | AMPM_MODE = 0x80, /* RTC is in AM/PM mode */ | |
45 | PM_NOW = 0x20, /* set if PM, clear if AM */ | |
46 | }; | |
47 | ||
48 | static const int BURST = 0xbe; | |
49 | static const int READ = 1; | |
50 | ||
51 | static void ht1380_half_period_delay(void) | |
52 | { | |
53 | /* | |
54 | * Delay for half a period. 1 us complies with the 500 KHz maximum | |
55 | * input serial clock limit given by the datasheet. | |
56 | */ | |
57 | udelay(1); | |
58 | } | |
59 | ||
60 | static int ht1380_send_byte(struct ht1380_priv *priv, int byte) | |
61 | { | |
62 | int ret; | |
63 | ||
64 | for (int bit = 0; bit < 8; bit++) { | |
65 | ret = dm_gpio_set_value(&priv->dat_desc, byte >> bit & 1); | |
66 | if (ret) | |
67 | break; | |
68 | ht1380_half_period_delay(); | |
69 | ||
70 | ret = dm_gpio_set_value(&priv->clk_desc, 1); | |
71 | if (ret) | |
72 | break; | |
73 | ht1380_half_period_delay(); | |
74 | ||
75 | ret = dm_gpio_set_value(&priv->clk_desc, 0); | |
76 | if (ret) | |
77 | break; | |
78 | } | |
79 | ||
80 | return ret; | |
81 | } | |
82 | ||
83 | /* | |
84 | * Leave reset state. The transfer operation can then be started. | |
85 | */ | |
86 | static int ht1380_reset_off(struct ht1380_priv *priv) | |
87 | { | |
88 | const unsigned int T_CC = 4; /* us, Reset to Clock Setup */ | |
89 | int ret; | |
90 | ||
91 | /* | |
92 | * Leave RESET state. | |
93 | * Make sure we make the minimal delay required by the datasheet. | |
94 | */ | |
95 | ret = dm_gpio_set_value(&priv->rst_desc, 0); | |
96 | udelay(T_CC); | |
97 | ||
98 | return ret; | |
99 | } | |
100 | ||
101 | /* | |
102 | * Enter reset state. Completes the transfer operation. | |
103 | */ | |
104 | static int ht1380_reset_on(struct ht1380_priv *priv) | |
105 | { | |
106 | const unsigned int T_CWH = 4; /* us, Reset Inactive Time */ | |
107 | int ret; | |
108 | ||
109 | /* | |
110 | * Enter RESET state. | |
111 | * Make sure we make the minimal delay required by the datasheet. | |
112 | */ | |
113 | ret = dm_gpio_set_value(&priv->rst_desc, 1); | |
114 | udelay(T_CWH); | |
115 | ||
116 | return ret; | |
117 | } | |
118 | ||
119 | static int ht1380_rtc_get(struct udevice *dev, struct rtc_time *tm) | |
120 | { | |
121 | struct ht1380_priv *priv = dev_get_priv(dev); | |
122 | int ret, i, bit, reg[N_REGS]; | |
123 | ||
124 | ret = dm_gpio_set_value(&priv->clk_desc, 0); | |
125 | if (ret) | |
126 | return ret; | |
127 | ||
128 | ret = dm_gpio_set_dir_flags(&priv->dat_desc, GPIOD_IS_OUT); | |
129 | if (ret) | |
130 | return ret; | |
131 | ||
132 | ret = ht1380_reset_off(priv); | |
133 | if (ret) | |
134 | goto exit; | |
135 | ||
136 | ret = ht1380_send_byte(priv, BURST + READ); | |
137 | if (ret) | |
138 | goto exit; | |
139 | ||
140 | ret = dm_gpio_set_dir_flags(&priv->dat_desc, GPIOD_IS_IN); | |
141 | if (ret) | |
142 | goto exit; | |
143 | ||
144 | for (i = 0; i < N_REGS; i++) { | |
145 | reg[i] = 0; | |
146 | ||
147 | for (bit = 0; bit < 8; bit++) { | |
148 | ht1380_half_period_delay(); | |
149 | ||
150 | ret = dm_gpio_set_value(&priv->clk_desc, 1); | |
151 | if (ret) | |
152 | goto exit; | |
153 | ht1380_half_period_delay(); | |
154 | ||
155 | reg[i] |= dm_gpio_get_value(&priv->dat_desc) << bit; | |
156 | ret = dm_gpio_set_value(&priv->clk_desc, 0); | |
157 | if (ret) | |
158 | goto exit; | |
159 | } | |
160 | } | |
161 | ||
162 | ret = -EINVAL; | |
163 | ||
164 | /* Correctness check: some bits are always zero */ | |
165 | if (reg[MIN] & 0x80 || reg[HOUR] & 0x40 || reg[MDAY] & 0xc0 || | |
166 | reg[MONTH] & 0xe0 || reg[WDAY] & 0xf8 || reg[WP] & 0x7f) | |
167 | goto exit; | |
168 | ||
169 | /* Correctness check: some registers are always non-zero */ | |
170 | if (!reg[MDAY] || !reg[MONTH] || !reg[WDAY]) | |
171 | goto exit; | |
172 | ||
173 | tm->tm_sec = bcd2bin(reg[SEC]); | |
174 | tm->tm_min = bcd2bin(reg[MIN]); | |
175 | if (reg[HOUR] & AMPM_MODE) { | |
176 | /* AM-PM Mode, range is 01-12 */ | |
177 | tm->tm_hour = bcd2bin(reg[HOUR] & 0x1f) % 12; | |
178 | if (reg[HOUR] & PM_NOW) { | |
179 | /* it is PM (otherwise AM) */ | |
180 | tm->tm_hour += 12; | |
181 | } | |
182 | } else { | |
183 | /* 24-hour Mode, range is 0-23 */ | |
184 | tm->tm_hour = bcd2bin(reg[HOUR]); | |
185 | } | |
186 | tm->tm_mday = bcd2bin(reg[MDAY]); | |
187 | tm->tm_mon = bcd2bin(reg[MONTH]); | |
188 | tm->tm_year = 2000 + bcd2bin(reg[YEAR]); | |
189 | tm->tm_wday = bcd2bin(reg[WDAY]) - 1; | |
190 | tm->tm_yday = 0; | |
191 | tm->tm_isdst = 0; | |
192 | ||
193 | ret = 0; | |
194 | ||
195 | exit: | |
196 | ht1380_reset_on(priv); | |
197 | ||
198 | return ret; | |
199 | } | |
200 | ||
201 | static int ht1380_write_protection_off(struct ht1380_priv *priv) | |
202 | { | |
203 | int ret; | |
204 | const int PROTECT = 0x8e; | |
205 | ||
206 | ret = ht1380_reset_off(priv); | |
207 | if (ret) | |
208 | return ret; | |
209 | ||
210 | ret = ht1380_send_byte(priv, PROTECT); | |
211 | if (ret) | |
212 | return ret; | |
213 | ret = ht1380_send_byte(priv, 0); /* WP bit is 0 */ | |
214 | if (ret) | |
215 | return ret; | |
216 | ||
217 | return ht1380_reset_on(priv); | |
218 | } | |
219 | ||
220 | static int ht1380_rtc_set(struct udevice *dev, const struct rtc_time *tm) | |
221 | { | |
222 | struct ht1380_priv *priv = dev_get_priv(dev); | |
223 | int ret, i, reg[N_REGS]; | |
224 | ||
225 | ret = dm_gpio_set_value(&priv->clk_desc, 0); | |
226 | if (ret) | |
227 | return ret; | |
228 | ||
229 | ret = dm_gpio_set_dir_flags(&priv->dat_desc, GPIOD_IS_OUT); | |
230 | if (ret) | |
231 | goto exit; | |
232 | ||
233 | ret = ht1380_write_protection_off(priv); | |
234 | if (ret) | |
235 | goto exit; | |
236 | ||
237 | reg[SEC] = bin2bcd(tm->tm_sec); | |
238 | reg[MIN] = bin2bcd(tm->tm_min); | |
239 | reg[HOUR] = bin2bcd(tm->tm_hour); | |
240 | reg[MDAY] = bin2bcd(tm->tm_mday); | |
241 | reg[MONTH] = bin2bcd(tm->tm_mon); | |
242 | reg[WDAY] = bin2bcd(tm->tm_wday) + 1; | |
243 | reg[YEAR] = bin2bcd(tm->tm_year - 2000); | |
244 | reg[WP] = 0x80; /* WP bit is 1 */ | |
245 | ||
246 | ret = ht1380_reset_off(priv); | |
247 | if (ret) | |
248 | goto exit; | |
249 | ||
250 | ret = ht1380_send_byte(priv, BURST); | |
251 | for (i = 0; i < N_REGS && ret; i++) | |
252 | ret = ht1380_send_byte(priv, reg[i]); | |
253 | ||
254 | exit: | |
255 | ht1380_reset_on(priv); | |
256 | ||
257 | return ret; | |
258 | } | |
259 | ||
260 | static int ht1380_probe(struct udevice *dev) | |
261 | { | |
262 | int ret; | |
263 | struct ht1380_priv *priv; | |
264 | ||
265 | priv = dev_get_priv(dev); | |
266 | if (!priv) | |
267 | return -EINVAL; | |
268 | ||
269 | ret = gpio_request_by_name(dev, "rst-gpios", 0, | |
270 | &priv->rst_desc, GPIOD_IS_OUT); | |
271 | if (ret) | |
272 | goto fail_rst; | |
273 | ||
274 | ret = gpio_request_by_name(dev, "clk-gpios", 0, | |
275 | &priv->clk_desc, GPIOD_IS_OUT); | |
276 | if (ret) | |
277 | goto fail_clk; | |
278 | ||
279 | ret = gpio_request_by_name(dev, "dat-gpios", 0, | |
280 | &priv->dat_desc, 0); | |
281 | if (ret) | |
282 | goto fail_dat; | |
283 | ||
284 | ret = ht1380_reset_on(priv); | |
285 | if (ret) | |
286 | goto fail; | |
287 | ||
288 | return 0; | |
289 | ||
290 | fail: | |
291 | dm_gpio_free(dev, &priv->dat_desc); | |
292 | fail_dat: | |
293 | dm_gpio_free(dev, &priv->clk_desc); | |
294 | fail_clk: | |
295 | dm_gpio_free(dev, &priv->rst_desc); | |
296 | fail_rst: | |
297 | return ret; | |
298 | } | |
299 | ||
300 | static int ht1380_remove(struct udevice *dev) | |
301 | { | |
302 | struct ht1380_priv *priv = dev_get_priv(dev); | |
303 | ||
304 | dm_gpio_free(dev, &priv->rst_desc); | |
305 | dm_gpio_free(dev, &priv->clk_desc); | |
306 | dm_gpio_free(dev, &priv->dat_desc); | |
307 | ||
308 | return 0; | |
309 | } | |
310 | ||
311 | static const struct rtc_ops ht1380_rtc_ops = { | |
312 | .get = ht1380_rtc_get, | |
313 | .set = ht1380_rtc_set, | |
314 | }; | |
315 | ||
316 | static const struct udevice_id ht1380_rtc_ids[] = { | |
317 | { .compatible = "holtek,ht1380" }, | |
318 | { } | |
319 | }; | |
320 | ||
321 | U_BOOT_DRIVER(rtc_ht1380) = { | |
322 | .name = "rtc-ht1380", | |
323 | .id = UCLASS_RTC, | |
324 | .probe = ht1380_probe, | |
325 | .remove = ht1380_remove, | |
326 | .of_match = ht1380_rtc_ids, | |
327 | .ops = &ht1380_rtc_ops, | |
328 | .priv_auto = sizeof(struct ht1380_priv), | |
329 | }; |