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