pcf8583.c revision 1.21 1 /* $NetBSD: pcf8583.c,v 1.21 2025/09/07 21:45:15 thorpej Exp $ */
2
3 /*
4 * Copyright (c) 2003 Wasabi Systems, Inc.
5 * All rights reserved.
6 *
7 * Written by Steve C. Woodford and Jason R. Thorpe for Wasabi Systems, Inc.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed for the NetBSD Project by
20 * Wasabi Systems, Inc.
21 * 4. The name of Wasabi Systems, Inc. may not be used to endorse
22 * or promote products derived from this software without specific prior
23 * written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 */
37
38 /*
39 * Driver for the Philips PCF8583 Real Time Clock.
40 *
41 * This driver is partially derived from Ben Harris's PCF8583 driver
42 * for NetBSD/acorn26.
43 */
44
45 #include <sys/cdefs.h>
46 __KERNEL_RCSID(0, "$NetBSD: pcf8583.c,v 1.21 2025/09/07 21:45:15 thorpej Exp $");
47
48 #include <sys/param.h>
49 #include <sys/systm.h>
50 #include <sys/device.h>
51 #include <sys/kernel.h>
52 #include <sys/fcntl.h>
53 #include <sys/uio.h>
54 #include <sys/conf.h>
55 #include <sys/event.h>
56
57 #include <dev/clock_subr.h>
58
59 #include <dev/i2c/i2cvar.h>
60 #include <dev/i2c/pcf8583reg.h>
61 #include <dev/i2c/pcf8583var.h>
62
63 #include "ioconf.h"
64
65 struct pcfrtc_softc {
66 device_t sc_dev;
67 i2c_tag_t sc_tag;
68 int sc_address;
69 int sc_open;
70 struct todr_chip_handle sc_todr;
71 };
72
73 static int pcfrtc_match(device_t, cfdata_t, void *);
74 static void pcfrtc_attach(device_t, device_t, void *);
75
76 CFATTACH_DECL_NEW(pcfrtc, sizeof(struct pcfrtc_softc),
77 pcfrtc_match, pcfrtc_attach, NULL, NULL);
78
79 dev_type_open(pcfrtc_open);
80 dev_type_close(pcfrtc_close);
81 dev_type_read(pcfrtc_read);
82 dev_type_write(pcfrtc_write);
83
84 const struct cdevsw pcfrtc_cdevsw = {
85 .d_open = pcfrtc_open,
86 .d_close = pcfrtc_close,
87 .d_read = pcfrtc_read,
88 .d_write = pcfrtc_write,
89 .d_ioctl = noioctl,
90 .d_stop = nostop,
91 .d_tty = notty,
92 .d_poll = nopoll,
93 .d_mmap = nommap,
94 .d_kqfilter = nokqfilter,
95 .d_discard = nodiscard,
96 .d_flag = D_OTHER
97 };
98
99 static int pcfrtc_clock_read(struct pcfrtc_softc *, struct clock_ymdhms *,
100 uint8_t *);
101 static int pcfrtc_clock_write(struct pcfrtc_softc *, struct clock_ymdhms *,
102 uint8_t);
103 static int pcfrtc_gettime(struct todr_chip_handle *, struct timeval *);
104 static int pcfrtc_settime(struct todr_chip_handle *, struct timeval *);
105
106 int
107 pcfrtc_match(device_t parent, cfdata_t cf, void *aux)
108 {
109 struct i2c_attach_args *ia = aux;
110
111 if ((ia->ia_addr & PCF8583_ADDRMASK) == PCF8583_ADDR)
112 return (I2C_MATCH_ADDRESS_ONLY);
113
114 return (0);
115 }
116
117 void
118 pcfrtc_attach(device_t parent, device_t self, void *aux)
119 {
120 struct pcfrtc_softc *sc = device_private(self);
121 struct i2c_attach_args *ia = aux;
122 uint8_t cmdbuf[1], csr;
123
124 sc->sc_tag = ia->ia_tag;
125 sc->sc_address = ia->ia_addr;
126 sc->sc_dev = self;
127
128 aprint_naive(": Real-time Clock/NVRAM\n");
129 aprint_normal(": PCF8583 Real-time Clock/NVRAM\n");
130
131 cmdbuf[0] = PCF8583_REG_CSR;
132 if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address,
133 cmdbuf, 1, &csr, 1, 0) != 0) {
134 aprint_error_dev(self, "unable to read CSR\n");
135 return;
136 }
137 aprint_normal_dev(sc->sc_dev, "");
138 switch (csr & PCF8583_CSR_FN_MASK) {
139 case PCF8583_CSR_FN_32768HZ:
140 aprint_normal(" 32.768 kHz clock");
141 break;
142
143 case PCF8583_CSR_FN_50HZ:
144 aprint_normal(" 50 Hz clock");
145 break;
146
147 case PCF8583_CSR_FN_EVENT:
148 aprint_normal(" event counter");
149 break;
150
151 case PCF8583_CSR_FN_TEST:
152 aprint_normal(" test mode");
153 break;
154 }
155 if (csr & PCF8583_CSR_STOP)
156 aprint_normal(", stopped");
157 if (csr & PCF8583_CSR_ALARMENABLE)
158 aprint_normal(", alarm enabled");
159 aprint_normal("\n");
160
161 sc->sc_open = 0;
162
163 sc->sc_todr.todr_dev = self;
164 sc->sc_todr.todr_gettime = pcfrtc_gettime;
165 sc->sc_todr.todr_settime = pcfrtc_settime;
166
167 todr_attach(&sc->sc_todr);
168 }
169
170 /*ARGSUSED*/
171 int
172 pcfrtc_open(dev_t dev, int flag, int fmt, struct lwp *l)
173 {
174 struct pcfrtc_softc *sc;
175
176 if ((sc = device_lookup_private(&pcfrtc_cd, minor(dev))) == NULL)
177 return (ENXIO);
178
179 /* XXX: Locking */
180
181 if (sc->sc_open)
182 return (EBUSY);
183
184 sc->sc_open = 1;
185 return (0);
186 }
187
188 /*ARGSUSED*/
189 int
190 pcfrtc_close(dev_t dev, int flag, int fmt, struct lwp *l)
191 {
192 struct pcfrtc_softc *sc;
193
194 if ((sc = device_lookup_private(&pcfrtc_cd, minor(dev))) == NULL)
195 return (ENXIO);
196
197 sc->sc_open = 0;
198 return (0);
199 }
200
201 /*ARGSUSED*/
202 int
203 pcfrtc_read(dev_t dev, struct uio *uio, int flags)
204 {
205 struct pcfrtc_softc *sc;
206 u_int8_t ch, cmdbuf[1];
207 int a, error;
208
209 if ((sc = device_lookup_private(&pcfrtc_cd, minor(dev))) == NULL)
210 return (ENXIO);
211
212 if (uio->uio_offset >= PCF8583_NVRAM_SIZE)
213 return (EINVAL);
214
215 if ((error = iic_acquire_bus(sc->sc_tag, 0)) != 0)
216 return (error);
217
218 while (uio->uio_resid && uio->uio_offset < PCF8583_NVRAM_SIZE) {
219 a = (int)uio->uio_offset;
220 cmdbuf[0] = a + PCF8583_NVRAM_START;
221 if ((error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
222 sc->sc_address, cmdbuf, 1,
223 &ch, 1, 0)) != 0) {
224 iic_release_bus(sc->sc_tag, 0);
225 aprint_error_dev(sc->sc_dev,
226 "pcfrtc_read: read failed at 0x%x\n", a);
227 return (error);
228 }
229 if ((error = uiomove(&ch, 1, uio)) != 0) {
230 iic_release_bus(sc->sc_tag, 0);
231 return (error);
232 }
233 }
234
235 iic_release_bus(sc->sc_tag, 0);
236
237 return (0);
238 }
239
240 /*ARGSUSED*/
241 int
242 pcfrtc_write(dev_t dev, struct uio *uio, int flags)
243 {
244 struct pcfrtc_softc *sc;
245 u_int8_t cmdbuf[2];
246 int a, error;
247
248 if ((sc = device_lookup_private(&pcfrtc_cd, minor(dev))) == NULL)
249 return (ENXIO);
250
251 if (uio->uio_offset >= PCF8583_NVRAM_SIZE)
252 return (EINVAL);
253
254 if ((error = iic_acquire_bus(sc->sc_tag, 0)) != 0)
255 return (error);
256
257 while (uio->uio_resid && uio->uio_offset < PCF8583_NVRAM_SIZE) {
258 a = (int)uio->uio_offset;
259 cmdbuf[0] = a + PCF8583_NVRAM_START;
260 if ((error = uiomove(&cmdbuf[1], 1, uio)) != 0)
261 break;
262
263 if ((error = iic_exec(sc->sc_tag,
264 uio->uio_resid ? I2C_OP_WRITE : I2C_OP_WRITE_WITH_STOP,
265 sc->sc_address, cmdbuf, 1, &cmdbuf[1], 1, 0)) != 0) {
266 aprint_error_dev(sc->sc_dev,
267 "pcfrtc_write: write failed at 0x%x\n", a);
268 return (error);
269 }
270 }
271
272 iic_release_bus(sc->sc_tag, 0);
273
274 return (error);
275 }
276
277 static int
278 pcfrtc_gettime(struct todr_chip_handle *ch, struct timeval *tv)
279 {
280 struct pcfrtc_softc *sc = device_private(ch->todr_dev);
281 struct clock_ymdhms dt;
282 int err;
283 uint8_t centi;
284
285 if ((err = pcfrtc_clock_read(sc, &dt, ¢i)))
286 return err;
287
288 tv->tv_sec = clock_ymdhms_to_secs(&dt);
289 tv->tv_usec = centi * 10000;
290
291 return (0);
292 }
293
294 static int
295 pcfrtc_settime(struct todr_chip_handle *ch, struct timeval *tv)
296 {
297 struct pcfrtc_softc *sc = device_private(ch->todr_dev);
298 struct clock_ymdhms dt;
299 int err;
300
301 clock_secs_to_ymdhms(tv->tv_sec, &dt);
302
303 if ((err = pcfrtc_clock_write(sc, &dt, tv->tv_usec / 10000)) != 0)
304 return err;
305
306 return (0);
307 }
308
309 static const int pcf8583_rtc_offset[] = {
310 PCF8583_REG_CSR,
311 PCF8583_REG_CENTI,
312 PCF8583_REG_SEC,
313 PCF8583_REG_MIN,
314 PCF8583_REG_HOUR,
315 PCF8583_REG_YEARDATE,
316 PCF8583_REG_WKDYMON,
317 PCF8583_REG_TIMER,
318 0xc0, /* NVRAM -- year stored here */
319 0xc1, /* NVRAM -- century stored here */
320 };
321
322 static int
323 pcfrtc_clock_read(struct pcfrtc_softc *sc, struct clock_ymdhms *dt,
324 uint8_t *centi)
325 {
326 u_int8_t bcd[10], cmdbuf[1];
327 int i, err;
328
329 if ((err = iic_acquire_bus(sc->sc_tag, 0))) {
330 aprint_error_dev(sc->sc_dev,
331 "pcfrtc_clock_read: failed to acquire I2C bus\n");
332 return err;
333 }
334
335 /* Read each timekeeping register in order. */
336 for (i = 0; i < 10; i++) {
337 cmdbuf[0] = pcf8583_rtc_offset[i];
338
339 if ((err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
340 sc->sc_address, cmdbuf, 1,
341 &bcd[i], 1, 0))) {
342 iic_release_bus(sc->sc_tag, 0);
343 aprint_error_dev(sc->sc_dev,
344 "pcfrtc_clock_read: failed to read rtc "
345 "at 0x%x\n",
346 pcf8583_rtc_offset[i]);
347 return err;
348 }
349 }
350
351 /* Done with I2C */
352 iic_release_bus(sc->sc_tag, 0);
353
354 /*
355 * Convert the PCF8583's register values into something useable
356 */
357 *centi = bcdtobin(bcd[PCF8583_REG_CENTI]);
358 dt->dt_sec = bcdtobin(bcd[PCF8583_REG_SEC]);
359 dt->dt_min = bcdtobin(bcd[PCF8583_REG_MIN]);
360 dt->dt_hour = bcdtobin(bcd[PCF8583_REG_HOUR] & PCF8583_HOUR_MASK);
361 if (bcd[PCF8583_REG_HOUR] & PCF8583_HOUR_12H) {
362 dt->dt_hour %= 12; /* 12AM -> 0, 12PM -> 12 */
363 if (bcd[PCF8583_REG_HOUR] & PCF8583_HOUR_PM)
364 dt->dt_hour += 12;
365 }
366
367 dt->dt_day = bcdtobin(bcd[PCF8583_REG_YEARDATE] & PCF8583_DATE_MASK);
368 dt->dt_mon = bcdtobin(bcd[PCF8583_REG_WKDYMON] & PCF8583_MON_MASK);
369
370 dt->dt_year = bcd[8] + (bcd[9] * 100);
371 /* Try to notice if the year's rolled over. */
372 if (bcd[PCF8583_REG_CSR] & PCF8583_CSR_MASK)
373 aprint_error_dev(sc->sc_dev,
374 "cannot check year in mask mode\n");
375 else {
376 while (dt->dt_year % 4 !=
377 (bcd[PCF8583_REG_YEARDATE] &
378 PCF8583_YEAR_MASK) >> PCF8583_YEAR_SHIFT)
379 dt->dt_year++;
380 }
381
382 return 0;
383 }
384
385 static int
386 pcfrtc_clock_write(struct pcfrtc_softc *sc, struct clock_ymdhms *dt,
387 uint8_t centi)
388 {
389 uint8_t bcd[10], cmdbuf[2];
390 int i, err;
391
392 /*
393 * Convert our time representation into something the PCF8583
394 * can understand.
395 */
396 bcd[PCF8583_REG_CENTI] = centi;
397 bcd[PCF8583_REG_SEC] = bintobcd(dt->dt_sec);
398 bcd[PCF8583_REG_MIN] = bintobcd(dt->dt_min);
399 bcd[PCF8583_REG_HOUR] = bintobcd(dt->dt_hour) & PCF8583_HOUR_MASK;
400 bcd[PCF8583_REG_YEARDATE] = bintobcd(dt->dt_day) |
401 ((dt->dt_year % 4) << PCF8583_YEAR_SHIFT);
402 bcd[PCF8583_REG_WKDYMON] = bintobcd(dt->dt_mon) |
403 ((dt->dt_wday % 4) << PCF8583_WKDY_SHIFT);
404 bcd[8] = dt->dt_year % 100;
405 bcd[9] = dt->dt_year / 100;
406
407 if ((err = iic_acquire_bus(sc->sc_tag, 0))) {
408 aprint_error_dev(sc->sc_dev,
409 "pcfrtc_clock_write: failed to acquire I2C bus\n");
410 return err;
411 }
412
413 for (i = 1; i < 10; i++) {
414 cmdbuf[0] = pcf8583_rtc_offset[i];
415 if ((err = iic_exec(sc->sc_tag,
416 i != 9 ? I2C_OP_WRITE : I2C_OP_WRITE_WITH_STOP,
417 sc->sc_address, cmdbuf, 1,
418 &bcd[i], 1, 0))) {
419 iic_release_bus(sc->sc_tag, 0);
420 aprint_error_dev(sc->sc_dev,
421 "pcfrtc_clock_write: failed to write rtc "
422 " at 0x%x\n",
423 pcf8583_rtc_offset[i]);
424 return err;
425 }
426 }
427
428 iic_release_bus(sc->sc_tag, 0);
429
430 return 0;
431 }
432
433 int
434 pcfrtc_bootstrap_read(i2c_tag_t tag, int i2caddr, int offset,
435 u_int8_t *rvp, size_t len)
436 {
437 u_int8_t cmdbuf[1];
438
439 /*
440 * NOTE: "offset" is an absolute offset into the PCF8583
441 * address space, not relative to the NVRAM.
442 */
443
444 if (len == 0)
445 return (0);
446
447 if (iic_acquire_bus(tag, 0) != 0)
448 return (-1);
449
450 while (len) {
451 /* Read a single byte. */
452 cmdbuf[0] = offset;
453 if (iic_exec(tag, I2C_OP_READ_WITH_STOP, i2caddr,
454 cmdbuf, 1, rvp, 1, 0)) {
455 iic_release_bus(tag, 0);
456 return (-1);
457 }
458
459 len--;
460 rvp++;
461 offset++;
462 }
463
464 iic_release_bus(tag, 0);
465 return (0);
466 }
467
468 int
469 pcfrtc_bootstrap_write(i2c_tag_t tag, int i2caddr, int offset,
470 u_int8_t *rvp, size_t len)
471 {
472 u_int8_t cmdbuf[1];
473
474 /*
475 * NOTE: "offset" is an absolute offset into the PCF8583
476 * address space, not relative to the NVRAM.
477 */
478
479 if (len == 0)
480 return (0);
481
482 if (iic_acquire_bus(tag, 0) != 0)
483 return (-1);
484
485 while (len) {
486 /* Write a single byte. */
487 cmdbuf[0] = offset;
488 if (iic_exec(tag, I2C_OP_WRITE_WITH_STOP, i2caddr,
489 cmdbuf, 1, rvp, 1, 0)) {
490 iic_release_bus(tag, 0);
491 return (-1);
492 }
493
494 len--;
495 rvp++;
496 offset++;
497 }
498
499 iic_release_bus(tag, 0);
500 return (0);
501 }
502