xref: /netbsd-src/sys/dev/i2c/pcf8583.c (revision ce2c90c7c172d95d2402a5b3d96d8f8e6d138a21)
1 /*	$NetBSD: pcf8583.c,v 1.6 2006/09/04 23:45:30 gdamore 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/param.h>
46 #include <sys/systm.h>
47 #include <sys/device.h>
48 #include <sys/kernel.h>
49 #include <sys/fcntl.h>
50 #include <sys/uio.h>
51 #include <sys/conf.h>
52 #include <sys/event.h>
53 
54 #include <dev/clock_subr.h>
55 
56 #include <dev/i2c/i2cvar.h>
57 #include <dev/i2c/pcf8583reg.h>
58 #include <dev/i2c/pcf8583var.h>
59 
60 struct pcfrtc_softc {
61 	struct device sc_dev;
62 	i2c_tag_t sc_tag;
63 	int sc_address;
64 	int sc_open;
65 	struct todr_chip_handle sc_todr;
66 };
67 
68 static int  pcfrtc_match(struct device *, struct cfdata *, void *);
69 static void pcfrtc_attach(struct device *, struct device *, void *);
70 
71 CFATTACH_DECL(pcfrtc, sizeof(struct pcfrtc_softc),
72 	pcfrtc_match, pcfrtc_attach, NULL, NULL);
73 extern struct cfdriver pcfrtc_cd;
74 
75 dev_type_open(pcfrtc_open);
76 dev_type_close(pcfrtc_close);
77 dev_type_read(pcfrtc_read);
78 dev_type_write(pcfrtc_write);
79 
80 const struct cdevsw pcfrtc_cdevsw = {
81 	pcfrtc_open, pcfrtc_close, pcfrtc_read, pcfrtc_write, noioctl,
82 	nostop, notty, nopoll, nommap, nokqfilter
83 };
84 
85 static int pcfrtc_clock_read(struct pcfrtc_softc *, struct clock_ymdhms *,
86 			     uint8_t *);
87 static int pcfrtc_clock_write(struct pcfrtc_softc *, struct clock_ymdhms *,
88 			      uint8_t);
89 static int pcfrtc_gettime(struct todr_chip_handle *, volatile struct timeval *);
90 static int pcfrtc_settime(struct todr_chip_handle *, volatile struct timeval *);
91 
92 int
93 pcfrtc_match(struct device *parent, struct cfdata *cf, void *aux)
94 {
95 	struct i2c_attach_args *ia = aux;
96 
97 	if ((ia->ia_addr & PCF8583_ADDRMASK) == PCF8583_ADDR)
98 		return (1);
99 
100 	return (0);
101 }
102 
103 void
104 pcfrtc_attach(struct device *parent, struct device *self, void *aux)
105 {
106 	struct pcfrtc_softc *sc = device_private(self);
107 	struct i2c_attach_args *ia = aux;
108 	uint8_t cmdbuf[1], csr;
109 
110 	sc->sc_tag = ia->ia_tag;
111 	sc->sc_address = ia->ia_addr;
112 
113 	aprint_naive(": Real-time Clock/NVRAM\n");
114 	aprint_normal(": PCF8583 Real-time Clock/NVRAM\n");
115 
116 	cmdbuf[0] = PCF8583_REG_CSR;
117 	if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address,
118 	    cmdbuf, 1, &csr, 1, 0) != 0) {
119 		aprint_error("%s: unable to read CSR\n", sc->sc_dev.dv_xname);
120 		return;
121 	}
122 	aprint_normal("%s: ", sc->sc_dev.dv_xname);
123 	switch (csr & PCF8583_CSR_FN_MASK) {
124 	case PCF8583_CSR_FN_32768HZ:
125 		aprint_normal(" 32.768 kHz clock");
126 		break;
127 
128 	case PCF8583_CSR_FN_50HZ:
129 		aprint_normal(" 50 Hz clock");
130 		break;
131 
132 	case PCF8583_CSR_FN_EVENT:
133 		aprint_normal(" event counter");
134 		break;
135 
136 	case PCF8583_CSR_FN_TEST:
137 		aprint_normal(" test mode");
138 		break;
139 	}
140 	if (csr & PCF8583_CSR_STOP)
141 		aprint_normal(", stopped");
142 	if (csr & PCF8583_CSR_ALARMENABLE)
143 		aprint_normal(", alarm enabled");
144 	aprint_normal("\n");
145 
146 	sc->sc_open = 0;
147 
148 	sc->sc_todr.cookie = sc;
149 	sc->sc_todr.todr_gettime = pcfrtc_gettime;
150 	sc->sc_todr.todr_settime = pcfrtc_settime;
151 	sc->sc_todr.todr_setwen = NULL;
152 
153 	todr_attach(&sc->sc_todr);
154 }
155 
156 /*ARGSUSED*/
157 int
158 pcfrtc_open(dev_t dev, int flag, int fmt, struct lwp *l)
159 {
160 	struct pcfrtc_softc *sc;
161 
162 	if ((sc = device_lookup(&pcfrtc_cd, minor(dev))) == NULL)
163 		return (ENXIO);
164 
165 	/* XXX: Locking */
166 
167 	if (sc->sc_open)
168 		return (EBUSY);
169 
170 	sc->sc_open = 1;
171 	return (0);
172 }
173 
174 /*ARGSUSED*/
175 int
176 pcfrtc_close(dev_t dev, int flag, int fmt, struct lwp *l)
177 {
178 	struct pcfrtc_softc *sc;
179 
180 	if ((sc = device_lookup(&pcfrtc_cd, minor(dev))) == NULL)
181 		return (ENXIO);
182 
183 	sc->sc_open = 0;
184 	return (0);
185 }
186 
187 /*ARGSUSED*/
188 int
189 pcfrtc_read(dev_t dev, struct uio *uio, int flags)
190 {
191 	struct pcfrtc_softc *sc;
192 	u_int8_t ch, cmdbuf[1];
193 	int a, error;
194 
195 	if ((sc = device_lookup(&pcfrtc_cd, minor(dev))) == NULL)
196 		return (ENXIO);
197 
198 	if (uio->uio_offset >= PCF8583_NVRAM_SIZE)
199 		return (EINVAL);
200 
201 	if ((error = iic_acquire_bus(sc->sc_tag, 0)) != 0)
202 		return (error);
203 
204 	while (uio->uio_resid && uio->uio_offset < PCF8583_NVRAM_SIZE) {
205 		a = (int)uio->uio_offset;
206 		cmdbuf[0] = a + PCF8583_NVRAM_START;
207 		if ((error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
208 				      sc->sc_address, cmdbuf, 1,
209 				      &ch, 1, 0)) != 0) {
210 			iic_release_bus(sc->sc_tag, 0);
211 			printf("%s: pcfrtc_read: read failed at 0x%x\n",
212 			    sc->sc_dev.dv_xname, a);
213 			return (error);
214 		}
215 		if ((error = uiomove(&ch, 1, uio)) != 0) {
216 			iic_release_bus(sc->sc_tag, 0);
217 			return (error);
218 		}
219 	}
220 
221 	iic_release_bus(sc->sc_tag, 0);
222 
223 	return (0);
224 }
225 
226 /*ARGSUSED*/
227 int
228 pcfrtc_write(dev_t dev, struct uio *uio, int flags)
229 {
230 	struct pcfrtc_softc *sc;
231 	u_int8_t cmdbuf[2];
232 	int a, error;
233 
234 	if ((sc = device_lookup(&pcfrtc_cd, minor(dev))) == NULL)
235 		return (ENXIO);
236 
237 	if (uio->uio_offset >= PCF8583_NVRAM_SIZE)
238 		return (EINVAL);
239 
240 	if ((error = iic_acquire_bus(sc->sc_tag, 0)) != 0)
241 		return (error);
242 
243 	while (uio->uio_resid && uio->uio_offset < PCF8583_NVRAM_SIZE) {
244 		a = (int)uio->uio_offset;
245 		cmdbuf[0] = a + PCF8583_NVRAM_START;
246 		if ((error = uiomove(&cmdbuf[1], 1, uio)) != 0)
247 			break;
248 
249 		if ((error = iic_exec(sc->sc_tag,
250 		    uio->uio_resid ? I2C_OP_WRITE : I2C_OP_WRITE_WITH_STOP,
251 		    sc->sc_address, cmdbuf, 1, &cmdbuf[1], 1, 0)) != 0) {
252 			printf("%s: pcfrtc_write: write failed at 0x%x\n",
253 			    sc->sc_dev.dv_xname, a);
254 			return (error);
255 		}
256 	}
257 
258 	iic_release_bus(sc->sc_tag, 0);
259 
260 	return (error);
261 }
262 
263 static int
264 pcfrtc_gettime(struct todr_chip_handle *ch, volatile struct timeval *tv)
265 {
266 	struct pcfrtc_softc *sc = ch->cookie;
267 	struct clock_ymdhms dt;
268 	int err;
269 	uint8_t centi;
270 
271 	if ((err = pcfrtc_clock_read(sc, &dt, &centi)))
272 		return err;
273 
274 	tv->tv_sec = clock_ymdhms_to_secs(&dt);
275 	tv->tv_usec = centi * 10000;
276 
277 	return (0);
278 }
279 
280 static int
281 pcfrtc_settime(struct todr_chip_handle *ch, volatile struct timeval *tv)
282 {
283 	struct pcfrtc_softc *sc = ch->cookie;
284 	struct clock_ymdhms dt;
285 	int err;
286 
287 	clock_secs_to_ymdhms(tv->tv_sec, &dt);
288 
289 	if ((err = pcfrtc_clock_write(sc, &dt, tv->tv_usec / 10000) == 0))
290 		return err;
291 
292 	return (0);
293 }
294 
295 static const int pcf8583_rtc_offset[] = {
296 	PCF8583_REG_CSR,
297 	PCF8583_REG_CENTI,
298 	PCF8583_REG_SEC,
299 	PCF8583_REG_MIN,
300 	PCF8583_REG_HOUR,
301 	PCF8583_REG_YEARDATE,
302 	PCF8583_REG_WKDYMON,
303 	PCF8583_REG_TIMER,
304 	0xc0,			/* NVRAM -- year stored here */
305 	0xc1,			/* NVRAM -- century stored here */
306 };
307 
308 static int
309 pcfrtc_clock_read(struct pcfrtc_softc *sc, struct clock_ymdhms *dt,
310     uint8_t *centi)
311 {
312 	u_int8_t bcd[10], cmdbuf[1];
313 	int i, err;
314 
315 	if ((err = iic_acquire_bus(sc->sc_tag, I2C_F_POLL))) {
316 		printf("%s: pcfrtc_clock_read: failed to acquire I2C bus\n",
317 		    sc->sc_dev.dv_xname);
318 		return err;
319 	}
320 
321 	/* Read each timekeeping register in order. */
322 	for (i = 0; i < 10; i++) {
323 		cmdbuf[0] = pcf8583_rtc_offset[i];
324 
325 		if ((err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
326 			     sc->sc_address, cmdbuf, 1,
327 			     &bcd[i], 1, I2C_F_POLL))) {
328 			iic_release_bus(sc->sc_tag, I2C_F_POLL);
329 			printf("%s: pcfrtc_clock_read: failed to read rtc "
330 			    "at 0x%x\n", sc->sc_dev.dv_xname,
331 			    pcf8583_rtc_offset[i]);
332 			return err;
333 		}
334 	}
335 
336 	/* Done with I2C */
337 	iic_release_bus(sc->sc_tag, I2C_F_POLL);
338 
339 	/*
340 	 * Convert the PCF8583's register values into something useable
341 	 */
342 	*centi      = FROMBCD(bcd[PCF8583_REG_CENTI]);
343 	dt->dt_sec  = FROMBCD(bcd[PCF8583_REG_SEC]);
344 	dt->dt_min  = FROMBCD(bcd[PCF8583_REG_MIN]);
345 	dt->dt_hour = FROMBCD(bcd[PCF8583_REG_HOUR] & PCF8583_HOUR_MASK);
346 	if (bcd[PCF8583_REG_HOUR] & PCF8583_HOUR_12H) {
347 		dt->dt_hour %= 12;	/* 12AM -> 0, 12PM -> 12 */
348 		if (bcd[PCF8583_REG_HOUR] & PCF8583_HOUR_PM)
349 			dt->dt_hour += 12;
350 	}
351 
352 	dt->dt_day = FROMBCD(bcd[PCF8583_REG_YEARDATE] & PCF8583_DATE_MASK);
353 	dt->dt_mon = FROMBCD(bcd[PCF8583_REG_WKDYMON] & PCF8583_MON_MASK);
354 
355 	dt->dt_year = bcd[8] + (bcd[9] * 100);
356 	/* Try to notice if the year's rolled over. */
357 	if (bcd[PCF8583_REG_CSR] & PCF8583_CSR_MASK)
358 		printf("%s: cannot check year in mask mode\n",
359 		    sc->sc_dev.dv_xname);
360 	else {
361 		while (dt->dt_year % 4 !=
362 		       (bcd[PCF8583_REG_YEARDATE] &
363 			PCF8583_YEAR_MASK) >> PCF8583_YEAR_SHIFT)
364 			dt->dt_year++;
365 	}
366 
367 	return 0;
368 }
369 
370 static int
371 pcfrtc_clock_write(struct pcfrtc_softc *sc, struct clock_ymdhms *dt,
372     uint8_t centi)
373 {
374 	uint8_t bcd[10], cmdbuf[2];
375 	int i, err;
376 
377 	/*
378 	 * Convert our time representation into something the PCF8583
379 	 * can understand.
380 	 */
381 	bcd[PCF8583_REG_CENTI]    = centi;
382 	bcd[PCF8583_REG_SEC]      = TOBCD(dt->dt_sec);
383 	bcd[PCF8583_REG_MIN]      = TOBCD(dt->dt_min);
384 	bcd[PCF8583_REG_HOUR]     = TOBCD(dt->dt_hour) & PCF8583_HOUR_MASK;
385 	bcd[PCF8583_REG_YEARDATE] = TOBCD(dt->dt_day) |
386 	    ((dt->dt_year % 4) << PCF8583_YEAR_SHIFT);
387 	bcd[PCF8583_REG_WKDYMON]  = TOBCD(dt->dt_mon) |
388 	    ((dt->dt_wday % 4) << PCF8583_WKDY_SHIFT);
389 	bcd[8]                    = dt->dt_year % 100;
390 	bcd[9]                    = dt->dt_year / 100;
391 
392 	if ((err = iic_acquire_bus(sc->sc_tag, I2C_F_POLL))) {
393 		printf("%s: pcfrtc_clock_write: failed to acquire I2C bus\n",
394 		    sc->sc_dev.dv_xname);
395 		return err;
396 	}
397 
398 	for (i = 1; i < 10; i++) {
399 		cmdbuf[0] = pcf8583_rtc_offset[i];
400 		if ((err = iic_exec(sc->sc_tag,
401 			     i != 9 ? I2C_OP_WRITE : I2C_OP_WRITE_WITH_STOP,
402 			     sc->sc_address, cmdbuf, 1,
403 			     &bcd[i], 1, I2C_F_POLL))) {
404 			iic_release_bus(sc->sc_tag, I2C_F_POLL);
405 			printf("%s: pcfrtc_clock_write: failed to write rtc "
406 			    " at 0x%x\n", sc->sc_dev.dv_xname,
407 			    pcf8583_rtc_offset[i]);
408 			return err;
409 		}
410 	}
411 
412 	iic_release_bus(sc->sc_tag, I2C_F_POLL);
413 
414 	return 0;
415 }
416 
417 int
418 pcfrtc_bootstrap_read(i2c_tag_t tag, int i2caddr, int offset,
419     u_int8_t *rvp, size_t len)
420 {
421 	u_int8_t cmdbuf[1];
422 
423 	/*
424 	 * NOTE: "offset" is an absolute offset into the PCF8583
425 	 * address space, not relative to the NVRAM.
426 	 */
427 
428 	if (len == 0)
429 		return (0);
430 
431 	if (iic_acquire_bus(tag, I2C_F_POLL) != 0)
432 		return (-1);
433 
434 	while (len) {
435 		/* Read a single byte. */
436 		cmdbuf[0] = offset;
437 		if (iic_exec(tag, I2C_OP_READ_WITH_STOP, i2caddr,
438 			     cmdbuf, 1, rvp, 1, I2C_F_POLL)) {
439 			iic_release_bus(tag, I2C_F_POLL);
440 			return (-1);
441 		}
442 
443 		len--;
444 		rvp++;
445 		offset++;
446 	}
447 
448 	iic_release_bus(tag, I2C_F_POLL);
449 	return (0);
450 }
451 
452 int
453 pcfrtc_bootstrap_write(i2c_tag_t tag, int i2caddr, int offset,
454     u_int8_t *rvp, size_t len)
455 {
456 	u_int8_t cmdbuf[1];
457 
458 	/*
459 	 * NOTE: "offset" is an absolute offset into the PCF8583
460 	 * address space, not relative to the NVRAM.
461 	 */
462 
463 	if (len == 0)
464 		return (0);
465 
466 	if (iic_acquire_bus(tag, I2C_F_POLL) != 0)
467 		return (-1);
468 
469 	while (len) {
470 		/* Write a single byte. */
471 		cmdbuf[0] = offset;
472 		if (iic_exec(tag, I2C_OP_WRITE_WITH_STOP, i2caddr,
473 			     cmdbuf, 1, rvp, 1, I2C_F_POLL)) {
474 			iic_release_bus(tag, I2C_F_POLL);
475 			return (-1);
476 		}
477 
478 		len--;
479 		rvp++;
480 		offset++;
481 	}
482 
483 	iic_release_bus(tag, I2C_F_POLL);
484 	return (0);
485 }
486