xref: /openbsd/sys/dev/i2c/pcf8523.c (revision 76d0caae)
1 /*	$OpenBSD: pcf8523.c,v 1.6 2021/11/22 20:19: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 
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 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
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
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 	sc->sc_todr.cookie = sc;
130 	sc->sc_todr.todr_gettime = pcfrtc_gettime;
131 	sc->sc_todr.todr_settime = pcfrtc_settime;
132 	sc->sc_todr.todr_setwen = NULL;
133 
134 #if 0
135 	todr_attach(&sc->sc_todr);
136 #else
137 	/* XXX */
138 	{
139 	extern todr_chip_handle_t todr_handle;
140 	todr_handle = &sc->sc_todr;
141 	}
142 #endif
143 
144 	/*
145 	 * Enable battery switch-over and battery low detection in
146 	 * standard mode, and switch to 24 hour mode.
147 	 */
148 	reg = pcfrtc_reg_read(sc, PCF8523_CONTROL3);
149 	reg &= ~PCF8523_CONTROL3_PM_MASK;
150 	pcfrtc_reg_write(sc, PCF8523_CONTROL3, reg);
151 	reg = pcfrtc_reg_read(sc, PCF8523_CONTROL1);
152 	reg &= ~PCF8523_CONTROL1_12_24;
153 	reg &= ~PCF8523_CONTROL1_STOP;
154 	pcfrtc_reg_write(sc, PCF8523_CONTROL1, reg);
155 
156 	/* Report battery status. */
157 	reg = pcfrtc_reg_read(sc, PCF8523_CONTROL3);
158 	printf(": battery %s\n", (reg & PCF8523_CONTROL3_BLF) ? "low" : "ok");
159 }
160 
161 int
162 pcfrtc_gettime(struct todr_chip_handle *ch, struct timeval *tv)
163 {
164 	struct pcfrtc_softc *sc = ch->cookie;
165 	struct clock_ymdhms dt;
166 
167 	memset(&dt, 0, sizeof(dt));
168 	if (pcfrtc_clock_read(sc, &dt) == 0)
169 		return (-1);
170 
171 	tv->tv_sec = clock_ymdhms_to_secs(&dt);
172 	tv->tv_usec = 0;
173 	return (0);
174 }
175 
176 int
177 pcfrtc_settime(struct todr_chip_handle *ch, struct timeval *tv)
178 {
179 	struct pcfrtc_softc *sc = ch->cookie;
180 	struct clock_ymdhms dt;
181 	uint8_t reg;
182 
183 	clock_secs_to_ymdhms(tv->tv_sec, &dt);
184 	if (pcfrtc_clock_write(sc, &dt) == 0)
185 		return (-1);
186 
187 	/* Clear OS flag. */
188 	reg = pcfrtc_reg_read(sc, PCF8523_SECONDS);
189 	if (reg & PCF8523_SECONDS_OS) {
190 		reg &= ~PCF8523_SECONDS_OS;
191 		pcfrtc_reg_write(sc, PCF8523_SECONDS, reg);
192 	}
193 
194 	return (0);
195 }
196 
197 uint8_t
198 pcfrtc_reg_read(struct pcfrtc_softc *sc, int reg)
199 {
200 	uint8_t cmd = reg;
201 	uint8_t val;
202 
203 	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
204 	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
205 	    NULL, 0, &cmd, sizeof cmd, I2C_F_POLL) ||
206 	    iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address,
207 	    NULL, 0, &val, sizeof val, I2C_F_POLL)) {
208 		iic_release_bus(sc->sc_tag, I2C_F_POLL);
209 		printf("%s: pcfrtc_reg_read: failed to read reg%d\n",
210 		    sc->sc_dev.dv_xname, reg);
211 		return 0;
212 	}
213 	iic_release_bus(sc->sc_tag, I2C_F_POLL);
214 	return val;
215 }
216 
217 void
218 pcfrtc_reg_write(struct pcfrtc_softc *sc, int reg, uint8_t val)
219 {
220 	uint8_t cmd = reg;
221 
222 	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
223 	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
224 	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL)) {
225 		iic_release_bus(sc->sc_tag, I2C_F_POLL);
226 		printf("%s: pcfrtc_reg_write: failed to write reg%d\n",
227 		    sc->sc_dev.dv_xname, reg);
228 		return;
229 	}
230 	iic_release_bus(sc->sc_tag, I2C_F_POLL);
231 }
232 
233 int
234 pcfrtc_clock_read(struct pcfrtc_softc *sc, struct clock_ymdhms *dt)
235 {
236 	uint8_t regs[PCF8523_NRTC_REGS];
237 	uint8_t cmd = PCF8523_SECONDS;
238 
239 	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
240 	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
241 	    NULL, 0, &cmd, sizeof cmd, I2C_F_POLL) ||
242 	    iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address,
243 	    NULL, 0, regs, PCF8523_NRTC_REGS, I2C_F_POLL)) {
244 		iic_release_bus(sc->sc_tag, I2C_F_POLL);
245 		printf("%s: pcfrtc_clock_read: failed to read rtc\n",
246 		    sc->sc_dev.dv_xname);
247 		return (0);
248 	}
249 	iic_release_bus(sc->sc_tag, I2C_F_POLL);
250 
251 	/*
252 	 * Convert the PCF8523's register values into something useable
253 	 */
254 	dt->dt_sec = FROMBCD(regs[0] & PCF8523_SECONDS_MASK);
255 	dt->dt_min = FROMBCD(regs[1] & PCF8523_MINUTES_MASK);
256 	dt->dt_hour = FROMBCD(regs[2] & PCF8523_HOURS_24MASK);
257 	dt->dt_day = FROMBCD(regs[3] & PCF8523_DAY_MASK);
258 	dt->dt_mon = FROMBCD(regs[5] & PCF8523_MONTH_MASK);
259 	dt->dt_year = FROMBCD(regs[6]) + 2000;
260 
261 	if (regs[0] & PCF8523_SECONDS_OS)
262 		return (0);
263 
264 	return (1);
265 }
266 
267 int
268 pcfrtc_clock_write(struct pcfrtc_softc *sc, struct clock_ymdhms *dt)
269 {
270 	uint8_t regs[PCF8523_NRTC_REGS];
271 	uint8_t cmd = PCF8523_SECONDS;
272 
273 	/*
274 	 * Convert our time representation into something the PCF8523
275 	 * can understand.
276 	 */
277 	regs[0] = TOBCD(dt->dt_sec);
278 	regs[1] = TOBCD(dt->dt_min);
279 	regs[2] = TOBCD(dt->dt_hour);
280 	regs[3] = TOBCD(dt->dt_day);
281 	regs[4] = TOBCD(dt->dt_wday);
282 	regs[5] = TOBCD(dt->dt_mon);
283 	regs[6] = TOBCD(dt->dt_year - 2000);
284 
285 	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
286 	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address,
287 	    &cmd, sizeof cmd, regs, PCF8523_NRTC_REGS, I2C_F_POLL)) {
288 		iic_release_bus(sc->sc_tag, I2C_F_POLL);
289 		printf("%s: pcfrtc_clock_write: failed to write rtc\n",
290 		    sc->sc_dev.dv_xname);
291 		return (0);
292 	}
293 	iic_release_bus(sc->sc_tag, I2C_F_POLL);
294 	return (1);
295 }
296