1*0701a158Skettenis /* $OpenBSD: pcf8523.c,v 1.8 2022/10/12 13:39:50 kettenis Exp $ */
2875dbdfaSkettenis
3875dbdfaSkettenis /*
4875dbdfaSkettenis * Copyright (c) 2005 Kimihiro Nonaka
5875dbdfaSkettenis * Copyright (c) 2016 Mark Kettenis
6875dbdfaSkettenis * All rights reserved.
7875dbdfaSkettenis *
8875dbdfaSkettenis * Redistribution and use in source and binary forms, with or without
9875dbdfaSkettenis * modification, are permitted provided that the following conditions
10875dbdfaSkettenis * are met:
11875dbdfaSkettenis * 1. Redistributions of source code must retain the above copyright
12875dbdfaSkettenis * notice, this list of conditions and the following disclaimer.
13875dbdfaSkettenis * 2. Redistributions in binary form must reproduce the above copyright
14875dbdfaSkettenis * notice, this list of conditions and the following disclaimer in the
15875dbdfaSkettenis * documentation and/or other materials provided with the distribution.
16875dbdfaSkettenis *
17875dbdfaSkettenis * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
18875dbdfaSkettenis * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19875dbdfaSkettenis * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20875dbdfaSkettenis * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
21875dbdfaSkettenis * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22875dbdfaSkettenis * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23875dbdfaSkettenis * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24875dbdfaSkettenis * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25875dbdfaSkettenis * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26875dbdfaSkettenis * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27875dbdfaSkettenis * POSSIBILITY OF SUCH DAMAGE.
28875dbdfaSkettenis */
29875dbdfaSkettenis
30875dbdfaSkettenis #include <sys/param.h>
31875dbdfaSkettenis #include <sys/systm.h>
32875dbdfaSkettenis #include <sys/device.h>
33875dbdfaSkettenis
34875dbdfaSkettenis #include <dev/clock_subr.h>
35875dbdfaSkettenis
36875dbdfaSkettenis #include <dev/i2c/i2cvar.h>
37875dbdfaSkettenis
38875dbdfaSkettenis /*
39875dbdfaSkettenis * PCF8523 Real-Time Clock
40875dbdfaSkettenis */
41875dbdfaSkettenis
42875dbdfaSkettenis #define PCF8523_ADDR 0x68 /* Fixed I2C Slave Address */
43875dbdfaSkettenis
44875dbdfaSkettenis #define PCF8523_CONTROL1 0x00
45875dbdfaSkettenis #define PCF8523_CONTROL2 0x01
46875dbdfaSkettenis #define PCF8523_CONTROL3 0x02
47875dbdfaSkettenis #define PCF8523_SECONDS 0x03
48875dbdfaSkettenis #define PCF8523_MINUTES 0x04
49875dbdfaSkettenis #define PCF8523_HOURS 0x05
50875dbdfaSkettenis #define PCF8523_DAY 0x06
51875dbdfaSkettenis #define PCF8523_WDAY 0x07
52875dbdfaSkettenis #define PCF8523_MONTH 0x08
53875dbdfaSkettenis #define PCF8523_YEAR 0x09
54875dbdfaSkettenis #define PCF8523_ALARM_MIN 0x0a
55875dbdfaSkettenis #define PCF8523_ALARM_HOUR 0x0b
56875dbdfaSkettenis #define PCF8523_ALARM_DAY 0x0c
57875dbdfaSkettenis #define PCF8523_ALARM_WDAY 0x0d
58875dbdfaSkettenis #define PCF8523_OFFSET 0x0e
59875dbdfaSkettenis
60875dbdfaSkettenis #define PCF8523_NREGS 20
61875dbdfaSkettenis #define PCF8523_NRTC_REGS 7
62875dbdfaSkettenis
63875dbdfaSkettenis /*
64875dbdfaSkettenis * Bit definitions.
65875dbdfaSkettenis */
66875dbdfaSkettenis #define PCF8523_CONTROL1_12_24 (1 << 3)
67875dbdfaSkettenis #define PCF8523_CONTROL1_STOP (1 << 5)
68875dbdfaSkettenis #define PCF8523_CONTROL3_PM_MASK 0xe0
69875dbdfaSkettenis #define PCF8523_CONTROL3_PM_BLD (1 << 7)
70875dbdfaSkettenis #define PCF8523_CONTROL3_PM_VDD (1 << 6)
71875dbdfaSkettenis #define PCF8523_CONTROL3_PM_DSM (1 << 5)
72875dbdfaSkettenis #define PCF8523_CONTROL3_BLF (1 << 2)
73875dbdfaSkettenis #define PCF8523_SECONDS_MASK 0x7f
74875dbdfaSkettenis #define PCF8523_SECONDS_OS (1 << 7)
75875dbdfaSkettenis #define PCF8523_MINUTES_MASK 0x7f
76875dbdfaSkettenis #define PCF8523_HOURS_12HRS_PM (1 << 5) /* If 12 hr mode, set = PM */
77875dbdfaSkettenis #define PCF8523_HOURS_12MASK 0x1f
78875dbdfaSkettenis #define PCF8523_HOURS_24MASK 0x3f
79875dbdfaSkettenis #define PCF8523_DAY_MASK 0x3f
80875dbdfaSkettenis #define PCF8523_WDAY_MASK 0x07
81875dbdfaSkettenis #define PCF8523_MONTH_MASK 0x1f
82875dbdfaSkettenis
83875dbdfaSkettenis struct pcfrtc_softc {
84875dbdfaSkettenis struct device sc_dev;
85875dbdfaSkettenis i2c_tag_t sc_tag;
86875dbdfaSkettenis int sc_address;
87875dbdfaSkettenis struct todr_chip_handle sc_todr;
88875dbdfaSkettenis };
89875dbdfaSkettenis
90875dbdfaSkettenis int pcfrtc_match(struct device *, void *, void *);
91875dbdfaSkettenis void pcfrtc_attach(struct device *, struct device *, void *);
92875dbdfaSkettenis
93471aeecfSnaddy const struct cfattach pcfrtc_ca = {
94875dbdfaSkettenis sizeof(struct pcfrtc_softc), pcfrtc_match, pcfrtc_attach
95875dbdfaSkettenis };
96875dbdfaSkettenis
97875dbdfaSkettenis struct cfdriver pcfrtc_cd = {
98875dbdfaSkettenis NULL, "pcfrtc", DV_DULL
99875dbdfaSkettenis };
100875dbdfaSkettenis
101875dbdfaSkettenis uint8_t pcfrtc_reg_read(struct pcfrtc_softc *, int);
102875dbdfaSkettenis void pcfrtc_reg_write(struct pcfrtc_softc *, int, uint8_t);
103875dbdfaSkettenis int pcfrtc_clock_read(struct pcfrtc_softc *, struct clock_ymdhms *);
104875dbdfaSkettenis int pcfrtc_clock_write(struct pcfrtc_softc *, struct clock_ymdhms *);
105875dbdfaSkettenis int pcfrtc_gettime(struct todr_chip_handle *, struct timeval *);
106875dbdfaSkettenis int pcfrtc_settime(struct todr_chip_handle *, struct timeval *);
107875dbdfaSkettenis
108875dbdfaSkettenis int
pcfrtc_match(struct device * parent,void * v,void * arg)109875dbdfaSkettenis pcfrtc_match(struct device *parent, void *v, void *arg)
110875dbdfaSkettenis {
111875dbdfaSkettenis struct i2c_attach_args *ia = arg;
112875dbdfaSkettenis
1133364f722Skettenis if (strcmp(ia->ia_name, "nxp,pcf8523") == 0 &&
114875dbdfaSkettenis ia->ia_addr == PCF8523_ADDR)
115875dbdfaSkettenis return (1);
116875dbdfaSkettenis
117875dbdfaSkettenis return (0);
118875dbdfaSkettenis }
119875dbdfaSkettenis
120875dbdfaSkettenis void
pcfrtc_attach(struct device * parent,struct device * self,void * arg)121875dbdfaSkettenis pcfrtc_attach(struct device *parent, struct device *self, void *arg)
122875dbdfaSkettenis {
123875dbdfaSkettenis struct pcfrtc_softc *sc = (struct pcfrtc_softc *)self;
124875dbdfaSkettenis struct i2c_attach_args *ia = arg;
125875dbdfaSkettenis uint8_t reg;
126875dbdfaSkettenis
127875dbdfaSkettenis sc->sc_tag = ia->ia_tag;
128875dbdfaSkettenis sc->sc_address = ia->ia_addr;
129*0701a158Skettenis
130875dbdfaSkettenis sc->sc_todr.cookie = sc;
131875dbdfaSkettenis sc->sc_todr.todr_gettime = pcfrtc_gettime;
132875dbdfaSkettenis sc->sc_todr.todr_settime = pcfrtc_settime;
133875dbdfaSkettenis sc->sc_todr.todr_setwen = NULL;
134*0701a158Skettenis sc->sc_todr.todr_quality = 1000;
135875dbdfaSkettenis todr_attach(&sc->sc_todr);
136875dbdfaSkettenis
137875dbdfaSkettenis /*
138875dbdfaSkettenis * Enable battery switch-over and battery low detection in
139875dbdfaSkettenis * standard mode, and switch to 24 hour mode.
140875dbdfaSkettenis */
141875dbdfaSkettenis reg = pcfrtc_reg_read(sc, PCF8523_CONTROL3);
142875dbdfaSkettenis reg &= ~PCF8523_CONTROL3_PM_MASK;
143875dbdfaSkettenis pcfrtc_reg_write(sc, PCF8523_CONTROL3, reg);
144875dbdfaSkettenis reg = pcfrtc_reg_read(sc, PCF8523_CONTROL1);
145875dbdfaSkettenis reg &= ~PCF8523_CONTROL1_12_24;
146875dbdfaSkettenis reg &= ~PCF8523_CONTROL1_STOP;
147875dbdfaSkettenis pcfrtc_reg_write(sc, PCF8523_CONTROL1, reg);
148875dbdfaSkettenis
149875dbdfaSkettenis /* Report battery status. */
150875dbdfaSkettenis reg = pcfrtc_reg_read(sc, PCF8523_CONTROL3);
151875dbdfaSkettenis printf(": battery %s\n", (reg & PCF8523_CONTROL3_BLF) ? "low" : "ok");
152875dbdfaSkettenis }
153875dbdfaSkettenis
154875dbdfaSkettenis int
pcfrtc_gettime(struct todr_chip_handle * ch,struct timeval * tv)155875dbdfaSkettenis pcfrtc_gettime(struct todr_chip_handle *ch, struct timeval *tv)
156875dbdfaSkettenis {
157875dbdfaSkettenis struct pcfrtc_softc *sc = ch->cookie;
158875dbdfaSkettenis struct clock_ymdhms dt;
159875dbdfaSkettenis
160875dbdfaSkettenis memset(&dt, 0, sizeof(dt));
161875dbdfaSkettenis if (pcfrtc_clock_read(sc, &dt) == 0)
162875dbdfaSkettenis return (-1);
163875dbdfaSkettenis
164875dbdfaSkettenis tv->tv_sec = clock_ymdhms_to_secs(&dt);
165875dbdfaSkettenis tv->tv_usec = 0;
166875dbdfaSkettenis return (0);
167875dbdfaSkettenis }
168875dbdfaSkettenis
169875dbdfaSkettenis int
pcfrtc_settime(struct todr_chip_handle * ch,struct timeval * tv)170875dbdfaSkettenis pcfrtc_settime(struct todr_chip_handle *ch, struct timeval *tv)
171875dbdfaSkettenis {
172875dbdfaSkettenis struct pcfrtc_softc *sc = ch->cookie;
173875dbdfaSkettenis struct clock_ymdhms dt;
1744c90b400Skettenis uint8_t reg;
175875dbdfaSkettenis
176875dbdfaSkettenis clock_secs_to_ymdhms(tv->tv_sec, &dt);
177875dbdfaSkettenis if (pcfrtc_clock_write(sc, &dt) == 0)
178875dbdfaSkettenis return (-1);
1794c90b400Skettenis
1804c90b400Skettenis /* Clear OS flag. */
1814c90b400Skettenis reg = pcfrtc_reg_read(sc, PCF8523_SECONDS);
1824c90b400Skettenis if (reg & PCF8523_SECONDS_OS) {
1834c90b400Skettenis reg &= ~PCF8523_SECONDS_OS;
1844c90b400Skettenis pcfrtc_reg_write(sc, PCF8523_SECONDS, reg);
1854c90b400Skettenis }
1864c90b400Skettenis
187875dbdfaSkettenis return (0);
188875dbdfaSkettenis }
189875dbdfaSkettenis
190875dbdfaSkettenis uint8_t
pcfrtc_reg_read(struct pcfrtc_softc * sc,int reg)191875dbdfaSkettenis pcfrtc_reg_read(struct pcfrtc_softc *sc, int reg)
192875dbdfaSkettenis {
193875dbdfaSkettenis uint8_t cmd = reg;
194875dbdfaSkettenis uint8_t val;
195875dbdfaSkettenis
196875dbdfaSkettenis iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
197875dbdfaSkettenis if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
198875dbdfaSkettenis NULL, 0, &cmd, sizeof cmd, I2C_F_POLL) ||
199875dbdfaSkettenis iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address,
200875dbdfaSkettenis NULL, 0, &val, sizeof val, I2C_F_POLL)) {
201875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL);
202875dbdfaSkettenis printf("%s: pcfrtc_reg_read: failed to read reg%d\n",
203875dbdfaSkettenis sc->sc_dev.dv_xname, reg);
204875dbdfaSkettenis return 0;
205875dbdfaSkettenis }
206875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL);
207875dbdfaSkettenis return val;
208875dbdfaSkettenis }
209875dbdfaSkettenis
210875dbdfaSkettenis void
pcfrtc_reg_write(struct pcfrtc_softc * sc,int reg,uint8_t val)211875dbdfaSkettenis pcfrtc_reg_write(struct pcfrtc_softc *sc, int reg, uint8_t val)
212875dbdfaSkettenis {
213875dbdfaSkettenis uint8_t cmd = reg;
214875dbdfaSkettenis
215875dbdfaSkettenis iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
216875dbdfaSkettenis if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
217875dbdfaSkettenis &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL)) {
218875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL);
219875dbdfaSkettenis printf("%s: pcfrtc_reg_write: failed to write reg%d\n",
220875dbdfaSkettenis sc->sc_dev.dv_xname, reg);
221875dbdfaSkettenis return;
222875dbdfaSkettenis }
223875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL);
224875dbdfaSkettenis }
225875dbdfaSkettenis
226875dbdfaSkettenis int
pcfrtc_clock_read(struct pcfrtc_softc * sc,struct clock_ymdhms * dt)227875dbdfaSkettenis pcfrtc_clock_read(struct pcfrtc_softc *sc, struct clock_ymdhms *dt)
228875dbdfaSkettenis {
229875dbdfaSkettenis uint8_t regs[PCF8523_NRTC_REGS];
230875dbdfaSkettenis uint8_t cmd = PCF8523_SECONDS;
231875dbdfaSkettenis
232875dbdfaSkettenis iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
233875dbdfaSkettenis if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
234875dbdfaSkettenis NULL, 0, &cmd, sizeof cmd, I2C_F_POLL) ||
235875dbdfaSkettenis iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address,
236875dbdfaSkettenis NULL, 0, regs, PCF8523_NRTC_REGS, I2C_F_POLL)) {
237875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL);
238875dbdfaSkettenis printf("%s: pcfrtc_clock_read: failed to read rtc\n",
239875dbdfaSkettenis sc->sc_dev.dv_xname);
240875dbdfaSkettenis return (0);
241875dbdfaSkettenis }
242875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL);
243875dbdfaSkettenis
244875dbdfaSkettenis /*
245875dbdfaSkettenis * Convert the PCF8523's register values into something useable
246875dbdfaSkettenis */
247875dbdfaSkettenis dt->dt_sec = FROMBCD(regs[0] & PCF8523_SECONDS_MASK);
248875dbdfaSkettenis dt->dt_min = FROMBCD(regs[1] & PCF8523_MINUTES_MASK);
249875dbdfaSkettenis dt->dt_hour = FROMBCD(regs[2] & PCF8523_HOURS_24MASK);
250875dbdfaSkettenis dt->dt_day = FROMBCD(regs[3] & PCF8523_DAY_MASK);
251875dbdfaSkettenis dt->dt_mon = FROMBCD(regs[5] & PCF8523_MONTH_MASK);
252875dbdfaSkettenis dt->dt_year = FROMBCD(regs[6]) + 2000;
253875dbdfaSkettenis
2543ff7e2daSkettenis if (regs[0] & PCF8523_SECONDS_OS)
255875dbdfaSkettenis return (0);
256875dbdfaSkettenis
257875dbdfaSkettenis return (1);
258875dbdfaSkettenis }
259875dbdfaSkettenis
260875dbdfaSkettenis int
pcfrtc_clock_write(struct pcfrtc_softc * sc,struct clock_ymdhms * dt)261875dbdfaSkettenis pcfrtc_clock_write(struct pcfrtc_softc *sc, struct clock_ymdhms *dt)
262875dbdfaSkettenis {
263875dbdfaSkettenis uint8_t regs[PCF8523_NRTC_REGS];
264875dbdfaSkettenis uint8_t cmd = PCF8523_SECONDS;
265875dbdfaSkettenis
266875dbdfaSkettenis /*
267875dbdfaSkettenis * Convert our time representation into something the PCF8523
268875dbdfaSkettenis * can understand.
269875dbdfaSkettenis */
270875dbdfaSkettenis regs[0] = TOBCD(dt->dt_sec);
271875dbdfaSkettenis regs[1] = TOBCD(dt->dt_min);
272875dbdfaSkettenis regs[2] = TOBCD(dt->dt_hour);
273875dbdfaSkettenis regs[3] = TOBCD(dt->dt_day);
274875dbdfaSkettenis regs[4] = TOBCD(dt->dt_wday);
275875dbdfaSkettenis regs[5] = TOBCD(dt->dt_mon);
276875dbdfaSkettenis regs[6] = TOBCD(dt->dt_year - 2000);
277875dbdfaSkettenis
278875dbdfaSkettenis iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
279875dbdfaSkettenis if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
280875dbdfaSkettenis &cmd, sizeof cmd, regs, PCF8523_NRTC_REGS, I2C_F_POLL)) {
281875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL);
282875dbdfaSkettenis printf("%s: pcfrtc_clock_write: failed to write rtc\n",
283875dbdfaSkettenis sc->sc_dev.dv_xname);
284875dbdfaSkettenis return (0);
285875dbdfaSkettenis }
286875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL);
287875dbdfaSkettenis return (1);
288875dbdfaSkettenis }
289