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