pcf8563.c revision 1.1
1/*	$OpenBSD: pcf8563.c,v 1.1 2017/07/24 20:25:29 kettenis Exp $	*/
2
3/*
4 * Copyright (c) 2005 Kimihiro Nonaka
5 * Copyright (c) 2016, 2017 Mark Kettenis
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL WASABI SYSTEMS, INC
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/device.h>
33#include <sys/kernel.h>
34#include <sys/fcntl.h>
35#include <sys/uio.h>
36#include <sys/conf.h>
37#include <sys/event.h>
38
39#include <dev/clock_subr.h>
40
41#include <dev/i2c/i2cvar.h>
42
43/*
44 * PCF8563 Real-Time Clock
45 */
46
47#define PCF8563_ADDR		0x51	/* Fixed I2C Slave Address */
48
49#define PCF8563_CONTROL1	0x00
50#define PCF8563_CONTROL2	0x01
51#define PCF8563_SECONDS		0x02
52#define PCF8563_MINUTES		0x03
53#define PCF8563_HOURS		0x04
54#define PCF8563_DAY		0x05
55#define PCF8563_WDAY		0x06
56#define PCF8563_MONTH		0x07
57#define PCF8563_YEAR		0x08
58
59#define PCF8563_NREGS		12
60#define PCF8563_NRTC_REGS	7
61
62/*
63 * Bit definitions.
64 */
65#define PCF8563_CONTROL1_TESTC	(1 << 3)
66#define PCF8563_CONTROL1_STOP	(1 << 5)
67#define PCF8563_CONTROL1_TEST1	(1 << 1)
68#define PCF8563_SECONDS_MASK	0x7f
69#define PCF8563_SECONDS_VL	(1 << 7)
70#define PCF8563_MINUTES_MASK	0x7f
71#define PCF8563_HOURS_MASK	0x3f
72#define PCF8563_DAY_MASK	0x3f
73#define PCF8563_WDAY_MASK	0x07
74#define PCF8563_MONTH_MASK	0x0f
75#define PCF8563_MONTH_C		(1 << 7)
76
77struct pcxrtc_softc {
78	struct device sc_dev;
79	i2c_tag_t sc_tag;
80	int sc_address;
81	struct todr_chip_handle sc_todr;
82};
83
84int pcxrtc_match(struct device *, void *, void *);
85void pcxrtc_attach(struct device *, struct device *, void *);
86
87struct cfattach pcxrtc_ca = {
88	sizeof(struct pcxrtc_softc), pcxrtc_match, pcxrtc_attach
89};
90
91struct cfdriver pcxrtc_cd = {
92	NULL, "pcxrtc", DV_DULL
93};
94
95uint8_t pcxrtc_reg_read(struct pcxrtc_softc *, int);
96void pcxrtc_reg_write(struct pcxrtc_softc *, int, uint8_t);
97int pcxrtc_clock_read(struct pcxrtc_softc *, struct clock_ymdhms *);
98int pcxrtc_clock_write(struct pcxrtc_softc *, struct clock_ymdhms *);
99int pcxrtc_gettime(struct todr_chip_handle *, struct timeval *);
100int pcxrtc_settime(struct todr_chip_handle *, struct timeval *);
101int pcxrtc_getcal(struct todr_chip_handle *, int *);
102int pcxrtc_setcal(struct todr_chip_handle *, int);
103
104int
105pcxrtc_match(struct device *parent, void *v, void *arg)
106{
107	struct i2c_attach_args *ia = arg;
108
109	if (strcmp(ia->ia_name, "nxp,pcf8563") == 0 &&
110	    ia->ia_addr == PCF8563_ADDR)
111		return (1);
112
113	return (0);
114}
115
116void
117pcxrtc_attach(struct device *parent, struct device *self, void *arg)
118{
119	struct pcxrtc_softc *sc = (struct pcxrtc_softc *)self;
120	struct i2c_attach_args *ia = arg;
121	uint8_t reg;
122
123	sc->sc_tag = ia->ia_tag;
124	sc->sc_address = ia->ia_addr;
125	sc->sc_todr.cookie = sc;
126	sc->sc_todr.todr_gettime = pcxrtc_gettime;
127	sc->sc_todr.todr_settime = pcxrtc_settime;
128	sc->sc_todr.todr_getcal = pcxrtc_getcal;
129	sc->sc_todr.todr_setcal = pcxrtc_setcal;
130	sc->sc_todr.todr_setwen = NULL;
131
132#if 0
133	todr_attach(&sc->sc_todr);
134#else
135	/* XXX */
136	{
137	extern todr_chip_handle_t todr_handle;
138	todr_handle = &sc->sc_todr;
139	}
140#endif
141
142	/* Enable. */
143	reg = pcxrtc_reg_read(sc, PCF8563_CONTROL1);
144	reg &= ~PCF8563_CONTROL1_STOP;
145	pcxrtc_reg_write(sc, PCF8563_CONTROL1, reg);
146
147	/* Report battery status. */
148	reg = pcxrtc_reg_read(sc, PCF8563_SECONDS);
149	printf(": battery %s\n", (reg & PCF8563_SECONDS_VL) ? "low" : "ok");
150}
151
152int
153pcxrtc_gettime(struct todr_chip_handle *ch, struct timeval *tv)
154{
155	struct pcxrtc_softc *sc = ch->cookie;
156	struct clock_ymdhms dt;
157
158	memset(&dt, 0, sizeof(dt));
159	if (pcxrtc_clock_read(sc, &dt) == 0)
160		return (-1);
161
162	tv->tv_sec = clock_ymdhms_to_secs(&dt);
163	tv->tv_usec = 0;
164	return (0);
165}
166
167int
168pcxrtc_settime(struct todr_chip_handle *ch, struct timeval *tv)
169{
170	struct pcxrtc_softc *sc = ch->cookie;
171	struct clock_ymdhms dt;
172
173	clock_secs_to_ymdhms(tv->tv_sec, &dt);
174
175	if (pcxrtc_clock_write(sc, &dt) == 0)
176		return (-1);
177	return (0);
178}
179
180int
181pcxrtc_setcal(struct todr_chip_handle *ch, int cal)
182{
183	return (EOPNOTSUPP);
184}
185
186int
187pcxrtc_getcal(struct todr_chip_handle *ch, int *cal)
188{
189	return (EOPNOTSUPP);
190}
191
192uint8_t
193pcxrtc_reg_read(struct pcxrtc_softc *sc, int reg)
194{
195	uint8_t cmd = reg;
196	uint8_t val;
197
198	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
199	if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address,
200	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL)) {
201		iic_release_bus(sc->sc_tag, I2C_F_POLL);
202		printf("%s: pcxrtc_reg_read: failed to read reg%d\n",
203		    sc->sc_dev.dv_xname, reg);
204		return 0;
205	}
206	iic_release_bus(sc->sc_tag, I2C_F_POLL);
207	return val;
208}
209
210void
211pcxrtc_reg_write(struct pcxrtc_softc *sc, int reg, uint8_t val)
212{
213	uint8_t cmd = reg;
214
215	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
216	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
217	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL)) {
218		iic_release_bus(sc->sc_tag, I2C_F_POLL);
219		printf("%s: pcxrtc_reg_write: failed to write reg%d\n",
220		    sc->sc_dev.dv_xname, reg);
221		return;
222	}
223	iic_release_bus(sc->sc_tag, I2C_F_POLL);
224}
225
226int
227pcxrtc_clock_read(struct pcxrtc_softc *sc, struct clock_ymdhms *dt)
228{
229	uint8_t regs[PCF8563_NRTC_REGS];
230	uint8_t cmd = PCF8563_SECONDS;
231
232	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
233	if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address,
234	    &cmd, sizeof(cmd), regs, PCF8563_NRTC_REGS, I2C_F_POLL)) {
235		iic_release_bus(sc->sc_tag, I2C_F_POLL);
236		printf("%s: pcxrtc_clock_read: failed to read rtc\n",
237		    sc->sc_dev.dv_xname);
238		return (0);
239	}
240	iic_release_bus(sc->sc_tag, I2C_F_POLL);
241
242	/*
243	 * Convert the PCF8563's register values into something useable
244	 */
245	dt->dt_sec = FROMBCD(regs[0] & PCF8563_SECONDS_MASK);
246	dt->dt_min = FROMBCD(regs[1] & PCF8563_MINUTES_MASK);
247	dt->dt_hour = FROMBCD(regs[2] & PCF8563_HOURS_MASK);
248	dt->dt_day = FROMBCD(regs[3] & PCF8563_DAY_MASK);
249	dt->dt_mon = FROMBCD(regs[5] & PCF8563_MONTH_MASK);
250	dt->dt_year = FROMBCD(regs[6]) + 2000;
251
252	if ((regs[0] & PCF8563_SECONDS_VL))
253		return (0);
254
255	return (1);
256}
257
258int
259pcxrtc_clock_write(struct pcxrtc_softc *sc, struct clock_ymdhms *dt)
260{
261	uint8_t regs[PCF8563_NRTC_REGS];
262	uint8_t cmd = PCF8563_SECONDS;
263
264	/*
265	 * Convert our time representation into something the PCF8563
266	 * can understand.
267	 */
268	regs[0] = TOBCD(dt->dt_sec);
269	regs[1] = TOBCD(dt->dt_min);
270	regs[2] = TOBCD(dt->dt_hour);
271	regs[3] = TOBCD(dt->dt_day);
272	regs[4] = TOBCD(dt->dt_wday);
273	regs[5] = TOBCD(dt->dt_mon);
274	regs[6] = TOBCD(dt->dt_year - 2000);
275
276	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
277	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
278	    &cmd, sizeof(cmd), regs, PCF8563_NRTC_REGS, I2C_F_POLL)) {
279		iic_release_bus(sc->sc_tag, I2C_F_POLL);
280		printf("%s: pcxrtc_clock_write: failed to write rtc\n",
281		    sc->sc_dev.dv_xname);
282		return (0);
283	}
284	iic_release_bus(sc->sc_tag, I2C_F_POLL);
285	return (1);
286}
287