pcf8523.c revision 1.2
1/*	$OpenBSD: pcf8523.c,v 1.2 2016/05/16 22:55:23 kettenis Exp $	*/
2
3/*
4 * Copyright (c) 2005 Kimihiro Nonaka
5 * Copyright (c) 2016 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 * PCF8523 Real-Time Clock
45 */
46
47#define	PCF8523_ADDR		0x68	/* Fixed I2C Slave Address */
48
49#define PCF8523_CONTROL1	0x00
50#define PCF8523_CONTROL2	0x01
51#define PCF8523_CONTROL3	0x02
52#define PCF8523_SECONDS		0x03
53#define PCF8523_MINUTES		0x04
54#define PCF8523_HOURS		0x05
55#define PCF8523_DAY		0x06
56#define PCF8523_WDAY		0x07
57#define PCF8523_MONTH		0x08
58#define PCF8523_YEAR		0x09
59#define PCF8523_ALARM_MIN	0x0a
60#define PCF8523_ALARM_HOUR	0x0b
61#define PCF8523_ALARM_DAY	0x0c
62#define PCF8523_ALARM_WDAY	0x0d
63#define PCF8523_OFFSET		0x0e
64
65#define	PCF8523_NREGS		20
66#define	PCF8523_NRTC_REGS	7
67
68/*
69 * Bit definitions.
70 */
71#define	PCF8523_CONTROL1_12_24	(1 << 3)
72#define	PCF8523_CONTROL1_STOP	(1 << 5)
73#define	PCF8523_CONTROL3_PM_MASK 0xe0
74#define PCF8523_CONTROL3_PM_BLD	(1 << 7)
75#define PCF8523_CONTROL3_PM_VDD	(1 << 6)
76#define PCF8523_CONTROL3_PM_DSM	(1 << 5)
77#define PCF8523_CONTROL3_BLF	(1 << 2)
78#define	PCF8523_SECONDS_MASK	0x7f
79#define	PCF8523_SECONDS_OS	(1 << 7)
80#define	PCF8523_MINUTES_MASK	0x7f
81#define	PCF8523_HOURS_12HRS_PM	(1 << 5)	/* If 12 hr mode, set = PM */
82#define	PCF8523_HOURS_12MASK	0x1f
83#define	PCF8523_HOURS_24MASK	0x3f
84#define	PCF8523_DAY_MASK	0x3f
85#define	PCF8523_WDAY_MASK	0x07
86#define	PCF8523_MONTH_MASK	0x1f
87
88struct pcfrtc_softc {
89	struct device sc_dev;
90	i2c_tag_t sc_tag;
91	int sc_address;
92	struct todr_chip_handle sc_todr;
93};
94
95int pcfrtc_match(struct device *, void *, void *);
96void pcfrtc_attach(struct device *, struct device *, void *);
97
98struct cfattach pcfrtc_ca = {
99	sizeof(struct pcfrtc_softc), pcfrtc_match, pcfrtc_attach
100};
101
102struct cfdriver pcfrtc_cd = {
103	NULL, "pcfrtc", DV_DULL
104};
105
106uint8_t pcfrtc_reg_read(struct pcfrtc_softc *, int);
107void pcfrtc_reg_write(struct pcfrtc_softc *, int, uint8_t);
108int pcfrtc_clock_read(struct pcfrtc_softc *, struct clock_ymdhms *);
109int pcfrtc_clock_write(struct pcfrtc_softc *, struct clock_ymdhms *);
110int pcfrtc_gettime(struct todr_chip_handle *, struct timeval *);
111int pcfrtc_settime(struct todr_chip_handle *, struct timeval *);
112int pcfrtc_getcal(struct todr_chip_handle *, int *);
113int pcfrtc_setcal(struct todr_chip_handle *, int);
114
115int
116pcfrtc_match(struct device *parent, void *v, void *arg)
117{
118	struct i2c_attach_args *ia = arg;
119
120	if (strcmp(ia->ia_name, "pcf8523") == 0 &&
121	    ia->ia_addr == PCF8523_ADDR)
122		return (1);
123
124	return (0);
125}
126
127void
128pcfrtc_attach(struct device *parent, struct device *self, void *arg)
129{
130	struct pcfrtc_softc *sc = (struct pcfrtc_softc *)self;
131	struct i2c_attach_args *ia = arg;
132	uint8_t reg;
133
134	sc->sc_tag = ia->ia_tag;
135	sc->sc_address = ia->ia_addr;
136	sc->sc_todr.cookie = sc;
137	sc->sc_todr.todr_gettime = pcfrtc_gettime;
138	sc->sc_todr.todr_settime = pcfrtc_settime;
139	sc->sc_todr.todr_getcal = pcfrtc_getcal;
140	sc->sc_todr.todr_setcal = pcfrtc_setcal;
141	sc->sc_todr.todr_setwen = NULL;
142
143#if 0
144	todr_attach(&sc->sc_todr);
145#else
146	/* XXX */
147	{
148	extern todr_chip_handle_t todr_handle;
149	todr_handle = &sc->sc_todr;
150	}
151#endif
152
153	/*
154	 * Enable battery switch-over and battery low detection in
155	 * standard mode, and switch to 24 hour mode.
156	 */
157	reg = pcfrtc_reg_read(sc, PCF8523_CONTROL3);
158	reg &= ~PCF8523_CONTROL3_PM_MASK;
159	pcfrtc_reg_write(sc, PCF8523_CONTROL3, reg);
160	reg = pcfrtc_reg_read(sc, PCF8523_CONTROL1);
161	reg &= ~PCF8523_CONTROL1_12_24;
162	reg &= ~PCF8523_CONTROL1_STOP;
163	pcfrtc_reg_write(sc, PCF8523_CONTROL1, reg);
164
165	/* Report battery status. */
166	reg = pcfrtc_reg_read(sc, PCF8523_CONTROL3);
167	printf(": battery %s\n", (reg & PCF8523_CONTROL3_BLF) ? "low" : "ok");
168
169	/* Clear OS flag.  */
170	reg = pcfrtc_reg_read(sc, PCF8523_SECONDS);
171	if (reg & PCF8523_SECONDS_OS) {
172		reg &= ~PCF8523_SECONDS_OS;
173		pcfrtc_reg_write(sc, PCF8523_SECONDS, reg);
174	}
175}
176
177int
178pcfrtc_gettime(struct todr_chip_handle *ch, struct timeval *tv)
179{
180	struct pcfrtc_softc *sc = ch->cookie;
181	struct clock_ymdhms dt;
182
183	memset(&dt, 0, sizeof(dt));
184	if (pcfrtc_clock_read(sc, &dt) == 0)
185		return (-1);
186
187	tv->tv_sec = clock_ymdhms_to_secs(&dt);
188	tv->tv_usec = 0;
189	return (0);
190}
191
192int
193pcfrtc_settime(struct todr_chip_handle *ch, struct timeval *tv)
194{
195	struct pcfrtc_softc *sc = ch->cookie;
196	struct clock_ymdhms dt;
197
198	clock_secs_to_ymdhms(tv->tv_sec, &dt);
199
200	if (pcfrtc_clock_write(sc, &dt) == 0)
201		return (-1);
202	return (0);
203}
204
205int
206pcfrtc_setcal(struct todr_chip_handle *ch, int cal)
207{
208	return (EOPNOTSUPP);
209}
210
211int
212pcfrtc_getcal(struct todr_chip_handle *ch, int *cal)
213{
214	return (EOPNOTSUPP);
215}
216
217uint8_t
218pcfrtc_reg_read(struct pcfrtc_softc *sc, int reg)
219{
220	uint8_t cmd = reg;
221	uint8_t val;
222
223	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
224	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
225	    NULL, 0, &cmd, sizeof cmd, I2C_F_POLL) ||
226	    iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address,
227	    NULL, 0, &val, sizeof val, I2C_F_POLL)) {
228		iic_release_bus(sc->sc_tag, I2C_F_POLL);
229		printf("%s: pcfrtc_reg_read: failed to read reg%d\n",
230		    sc->sc_dev.dv_xname, reg);
231		return 0;
232	}
233	iic_release_bus(sc->sc_tag, I2C_F_POLL);
234	return val;
235}
236
237void
238pcfrtc_reg_write(struct pcfrtc_softc *sc, int reg, uint8_t val)
239{
240	uint8_t cmd = reg;
241
242	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
243	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
244	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL)) {
245		iic_release_bus(sc->sc_tag, I2C_F_POLL);
246		printf("%s: pcfrtc_reg_write: failed to write reg%d\n",
247		    sc->sc_dev.dv_xname, reg);
248		return;
249	}
250	iic_release_bus(sc->sc_tag, I2C_F_POLL);
251}
252
253int
254pcfrtc_clock_read(struct pcfrtc_softc *sc, struct clock_ymdhms *dt)
255{
256	uint8_t regs[PCF8523_NRTC_REGS];
257	uint8_t cmd = PCF8523_SECONDS;
258
259	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
260	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
261	    NULL, 0, &cmd, sizeof cmd, I2C_F_POLL) ||
262	    iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address,
263	    NULL, 0, regs, PCF8523_NRTC_REGS, I2C_F_POLL)) {
264		iic_release_bus(sc->sc_tag, I2C_F_POLL);
265		printf("%s: pcfrtc_clock_read: failed to read rtc\n",
266		    sc->sc_dev.dv_xname);
267		return (0);
268	}
269	iic_release_bus(sc->sc_tag, I2C_F_POLL);
270
271	/*
272	 * Convert the PCF8523's register values into something useable
273	 */
274	dt->dt_sec = FROMBCD(regs[0] & PCF8523_SECONDS_MASK);
275	dt->dt_min = FROMBCD(regs[1] & PCF8523_MINUTES_MASK);
276	dt->dt_hour = FROMBCD(regs[2] & PCF8523_HOURS_24MASK);
277	dt->dt_day = FROMBCD(regs[3] & PCF8523_DAY_MASK);
278	dt->dt_mon = FROMBCD(regs[5] & PCF8523_MONTH_MASK);
279	dt->dt_year = FROMBCD(regs[6]) + 2000;
280
281	if ((regs[0] & PCF8523_SECONDS_OS))
282		return (0);
283
284	return (1);
285}
286
287int
288pcfrtc_clock_write(struct pcfrtc_softc *sc, struct clock_ymdhms *dt)
289{
290	uint8_t regs[PCF8523_NRTC_REGS];
291	uint8_t cmd = PCF8523_SECONDS;
292
293	/*
294	 * Convert our time representation into something the PCF8523
295	 * can understand.
296	 */
297	regs[0] = TOBCD(dt->dt_sec);
298	regs[1] = TOBCD(dt->dt_min);
299	regs[2] = TOBCD(dt->dt_hour);
300	regs[3] = TOBCD(dt->dt_day);
301	regs[4] = TOBCD(dt->dt_wday);
302	regs[5] = TOBCD(dt->dt_mon);
303	regs[6] = TOBCD(dt->dt_year - 2000);
304
305	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
306	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
307	    &cmd, sizeof cmd, regs, PCF8523_NRTC_REGS, I2C_F_POLL)) {
308		iic_release_bus(sc->sc_tag, I2C_F_POLL);
309		printf("%s: pcfrtc_clock_write: failed to write rtc\n",
310		    sc->sc_dev.dv_xname);
311		return (0);
312	}
313	iic_release_bus(sc->sc_tag, I2C_F_POLL);
314	return (1);
315}
316