xref: /openbsd/sys/dev/i2c/pcf8523.c (revision 0701a158)
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 
83 struct 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 
90 int pcfrtc_match(struct device *, void *, void *);
91 void pcfrtc_attach(struct device *, struct device *, void *);
92 
93 const struct cfattach pcfrtc_ca = {
94 	sizeof(struct pcfrtc_softc), pcfrtc_match, pcfrtc_attach
95 };
96 
97 struct cfdriver pcfrtc_cd = {
98 	NULL, "pcfrtc", DV_DULL
99 };
100 
101 uint8_t pcfrtc_reg_read(struct pcfrtc_softc *, int);
102 void pcfrtc_reg_write(struct pcfrtc_softc *, int, uint8_t);
103 int pcfrtc_clock_read(struct pcfrtc_softc *, struct clock_ymdhms *);
104 int pcfrtc_clock_write(struct pcfrtc_softc *, struct clock_ymdhms *);
105 int pcfrtc_gettime(struct todr_chip_handle *, struct timeval *);
106 int pcfrtc_settime(struct todr_chip_handle *, struct timeval *);
107 
108 int
pcfrtc_match(struct device * parent,void * v,void * arg)109 pcfrtc_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 
120 void
pcfrtc_attach(struct device * parent,struct device * self,void * arg)121 pcfrtc_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 
154 int
pcfrtc_gettime(struct todr_chip_handle * ch,struct timeval * tv)155 pcfrtc_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 
169 int
pcfrtc_settime(struct todr_chip_handle * ch,struct timeval * tv)170 pcfrtc_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 
190 uint8_t
pcfrtc_reg_read(struct pcfrtc_softc * sc,int reg)191 pcfrtc_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 
210 void
pcfrtc_reg_write(struct pcfrtc_softc * sc,int reg,uint8_t val)211 pcfrtc_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 
226 int
pcfrtc_clock_read(struct pcfrtc_softc * sc,struct clock_ymdhms * dt)227 pcfrtc_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 
260 int
pcfrtc_clock_write(struct pcfrtc_softc * sc,struct clock_ymdhms * dt)261 pcfrtc_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