1/*	$OpenBSD: mcp794xx.c,v 1.3 2022/10/15 18:22:53 kettenis Exp $	*/
2/*
3 * Copyright (c) 2018 Mark Kettenis <kettenis@openbsd.org>
4 * Copyright (c) 2018 Patrick Wildt <patrick@blueri.se>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/param.h>
20#include <sys/systm.h>
21#include <sys/device.h>
22
23#include <dev/i2c/i2cvar.h>
24
25#include <dev/clock_subr.h>
26
27#define MCP794XX_SC		0x00
28#define  MCP794XX_SC_ST			(1 << 7)
29#define MCP794XX_MN		0x01
30#define MCP794XX_HR		0x02
31#define  MCP794XX_HR_PM			(1 << 5)
32#define  MCP794XX_HR_12H		(1 << 6)
33#define MCP794XX_DW		0x03
34#define  MCP794XX_DW_VBATEN		(1 << 3)
35#define  MCP794XX_DW_PWRFAIL		(1 << 4)
36#define  MCP794XX_DW_OSCRUN		(1 << 5)
37#define MCP794XX_DT		0x04
38#define MCP794XX_MO		0x05
39#define MCP794XX_YR		0x06
40#define MCP794XX_CR		0x07
41#define  MCP794XX_CR_EXTOSC		(1 << 3)
42
43#define MCP794XX_NRTC_REGS	7
44
45struct mcprtc_softc {
46	struct device sc_dev;
47	i2c_tag_t sc_tag;
48	i2c_addr_t sc_addr;
49
50	int sc_extosc;
51	struct todr_chip_handle sc_todr;
52};
53
54int	mcprtc_match(struct device *, void *, void *);
55void	mcprtc_attach(struct device *, struct device *, void *);
56
57const struct cfattach mcprtc_ca = {
58	sizeof(struct mcprtc_softc), mcprtc_match, mcprtc_attach
59};
60
61struct cfdriver mcprtc_cd = {
62	NULL, "mcprtc", DV_DULL
63};
64
65uint8_t	mcprtc_reg_read(struct mcprtc_softc *, int);
66void	mcprtc_reg_write(struct mcprtc_softc *, int, uint8_t);
67int	mcprtc_clock_read(struct mcprtc_softc *, struct clock_ymdhms *);
68int	mcprtc_clock_write(struct mcprtc_softc *, struct clock_ymdhms *);
69int	mcprtc_gettime(struct todr_chip_handle *, struct timeval *);
70int	mcprtc_settime(struct todr_chip_handle *, struct timeval *);
71
72int
73mcprtc_match(struct device *parent, void *match, void *aux)
74{
75	struct i2c_attach_args *ia = aux;
76
77	if (strcmp(ia->ia_name, "microchip,mcp7940x") == 0 ||
78	    strcmp(ia->ia_name, "microchip,mcp7941x") == 0)
79		return 1;
80
81	return 0;
82}
83
84void
85mcprtc_attach(struct device *parent, struct device *self, void *aux)
86{
87	struct mcprtc_softc *sc = (struct mcprtc_softc *)self;
88	struct i2c_attach_args *ia = aux;
89
90	sc->sc_tag = ia->ia_tag;
91	sc->sc_addr = ia->ia_addr;
92
93	sc->sc_todr.cookie = sc;
94	sc->sc_todr.todr_gettime = mcprtc_gettime;
95	sc->sc_todr.todr_settime = mcprtc_settime;
96	sc->sc_todr.todr_quality = 1000;
97	todr_attach(&sc->sc_todr);
98
99	printf("\n");
100}
101
102int
103mcprtc_gettime(struct todr_chip_handle *handle, struct timeval *tv)
104{
105	struct mcprtc_softc *sc = handle->cookie;
106	struct clock_ymdhms dt;
107	int error;
108
109	error = mcprtc_clock_read(sc, &dt);
110	if (error)
111		return error;
112
113	if (dt.dt_sec > 59 || dt.dt_min > 59 || dt.dt_hour > 23 ||
114	    dt.dt_day > 31 || dt.dt_day == 0 ||
115	    dt.dt_mon > 12 || dt.dt_mon == 0 ||
116	    dt.dt_year < POSIX_BASE_YEAR)
117		return EINVAL;
118
119	tv->tv_sec = clock_ymdhms_to_secs(&dt);
120	tv->tv_usec = 0;
121	return 0;
122}
123
124int
125mcprtc_settime(struct todr_chip_handle *handle, struct timeval *tv)
126{
127	struct mcprtc_softc *sc = handle->cookie;
128	struct clock_ymdhms dt;
129
130	clock_secs_to_ymdhms(tv->tv_sec, &dt);
131
132	return mcprtc_clock_write(sc, &dt);
133}
134
135uint8_t
136mcprtc_reg_read(struct mcprtc_softc *sc, int reg)
137{
138	uint8_t cmd = reg;
139	uint8_t val;
140	int error;
141
142	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
143	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
144	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
145	iic_release_bus(sc->sc_tag, I2C_F_POLL);
146
147	if (error) {
148		printf("%s: can't read register 0x%02x\n",
149		    sc->sc_dev.dv_xname, reg);
150		val = 0xff;
151	}
152
153	return val;
154}
155
156void
157mcprtc_reg_write(struct mcprtc_softc *sc, int reg, uint8_t val)
158{
159	uint8_t cmd = reg;
160	int error;
161
162	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
163	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
164	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
165	iic_release_bus(sc->sc_tag, I2C_F_POLL);
166
167	if (error) {
168		printf("%s: can't write register 0x%02x\n",
169		    sc->sc_dev.dv_xname, reg);
170	}
171}
172
173int
174mcprtc_clock_read(struct mcprtc_softc *sc, struct clock_ymdhms *dt)
175{
176	uint8_t regs[MCP794XX_NRTC_REGS];
177	uint8_t cmd = MCP794XX_SC;
178	int error;
179
180	/* Don't trust the RTC if the oscillator is not running. */
181	if (!(mcprtc_reg_read(sc, MCP794XX_DW) & MCP794XX_DW_OSCRUN))
182		return EIO;
183
184	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
185	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
186	    &cmd, sizeof(cmd), regs, MCP794XX_NRTC_REGS, I2C_F_POLL);
187	iic_release_bus(sc->sc_tag, I2C_F_POLL);
188
189	if (error) {
190		printf("%s: can't read RTC\n", sc->sc_dev.dv_xname);
191		return error;
192	}
193
194	/*
195	 * Convert the MCP794XX's register values into something useable.
196	 */
197	dt->dt_sec = FROMBCD(regs[0] & 0x7f);
198	dt->dt_min = FROMBCD(regs[1] & 0x7f);
199	dt->dt_hour = FROMBCD(regs[2] & 0x1f);
200	if ((regs[2] & MCP794XX_HR_12H) && (regs[2] & MCP794XX_HR_PM))
201		dt->dt_hour += 12;
202	dt->dt_wday = FROMBCD(regs[3] & 0x7);
203	dt->dt_day = FROMBCD(regs[4] & 0x3f);
204	dt->dt_mon = FROMBCD(regs[5] & 0x1f);
205	dt->dt_year = FROMBCD(regs[6]) + 2000;
206
207	return 0;
208}
209
210int
211mcprtc_clock_write(struct mcprtc_softc *sc, struct clock_ymdhms *dt)
212{
213	uint8_t regs[MCP794XX_NRTC_REGS];
214	uint8_t cmd = MCP794XX_SC;
215	uint8_t oscoff, oscbit;
216	uint8_t reg;
217	int error, i;
218
219	/*
220	 * Convert our time representation into something the MCP794XX
221	 * can understand.
222	 */
223	regs[0] = TOBCD(dt->dt_sec);
224	regs[1] = TOBCD(dt->dt_min);
225	regs[2] = TOBCD(dt->dt_hour);
226	regs[3] = TOBCD(dt->dt_wday) | MCP794XX_DW_VBATEN;
227	regs[4] = TOBCD(dt->dt_day);
228	regs[5] = TOBCD(dt->dt_mon);
229	regs[6] = TOBCD(dt->dt_year - 2000);
230
231	/* Stop RTC. */
232	if (sc->sc_extosc) {
233		oscoff = MCP794XX_CR;
234		oscbit = MCP794XX_CR_EXTOSC;
235	} else {
236		oscoff = MCP794XX_SC;
237		oscbit = MCP794XX_SC_ST;
238	}
239
240	/* Stop RTC such that we can write to it. */
241	reg = mcprtc_reg_read(sc, oscoff);
242	reg &= ~oscbit;
243	mcprtc_reg_write(sc, oscoff, reg);
244
245	for (i = 0; i < 10; i++) {
246		reg = mcprtc_reg_read(sc, MCP794XX_DW);
247		if ((reg & MCP794XX_DW_OSCRUN) == 0)
248			break;
249		delay(10);
250	}
251	if (i == 10) {
252		error = EIO;
253		goto fail;
254	}
255
256	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
257	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
258	    &cmd, sizeof(cmd), regs, MCP794XX_NRTC_REGS, I2C_F_POLL);
259	iic_release_bus(sc->sc_tag, I2C_F_POLL);
260
261	/* Restart RTC. */
262	reg = mcprtc_reg_read(sc, oscoff);
263	reg |= oscbit;
264	mcprtc_reg_write(sc, oscoff, reg);
265
266fail:
267	if (error) {
268		printf("%s: can't write RTC\n", sc->sc_dev.dv_xname);
269		return error;
270	}
271
272	return 0;
273}
274