1 /* $OpenBSD: mcp794xx.c,v 1.1 2019/09/06 09:38:19 patrick 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 extern todr_chip_handle_t todr_handle; 28 29 #define MCP794XX_SC 0x00 30 #define MCP794XX_SC_ST (1 << 7) 31 #define MCP794XX_MN 0x01 32 #define MCP794XX_HR 0x02 33 #define MCP794XX_HR_PM (1 << 5) 34 #define MCP794XX_HR_12H (1 << 6) 35 #define MCP794XX_DW 0x03 36 #define MCP794XX_DW_VBATEN (1 << 3) 37 #define MCP794XX_DW_PWRFAIL (1 << 4) 38 #define MCP794XX_DW_OSCRUN (1 << 5) 39 #define MCP794XX_DT 0x04 40 #define MCP794XX_MO 0x05 41 #define MCP794XX_YR 0x06 42 #define MCP794XX_CR 0x07 43 #define MCP794XX_CR_EXTOSC (1 << 3) 44 45 #define MCP794XX_NRTC_REGS 7 46 47 struct mcprtc_softc { 48 struct device sc_dev; 49 i2c_tag_t sc_tag; 50 i2c_addr_t sc_addr; 51 52 int sc_extosc; 53 struct todr_chip_handle sc_todr; 54 }; 55 56 int mcprtc_match(struct device *, void *, void *); 57 void mcprtc_attach(struct device *, struct device *, void *); 58 59 struct cfattach mcprtc_ca = { 60 sizeof(struct mcprtc_softc), mcprtc_match, mcprtc_attach 61 }; 62 63 struct cfdriver mcprtc_cd = { 64 NULL, "mcprtc", DV_DULL 65 }; 66 67 uint8_t mcprtc_reg_read(struct mcprtc_softc *, int); 68 void mcprtc_reg_write(struct mcprtc_softc *, int, uint8_t); 69 int mcprtc_clock_read(struct mcprtc_softc *, struct clock_ymdhms *); 70 int mcprtc_clock_write(struct mcprtc_softc *, struct clock_ymdhms *); 71 int mcprtc_gettime(struct todr_chip_handle *, struct timeval *); 72 int mcprtc_settime(struct todr_chip_handle *, struct timeval *); 73 74 int 75 mcprtc_match(struct device *parent, void *match, void *aux) 76 { 77 struct i2c_attach_args *ia = aux; 78 79 if (strcmp(ia->ia_name, "microchip,mcp7940x") == 0 || 80 strcmp(ia->ia_name, "microchip,mcp7941x") == 0) 81 return 1; 82 83 return 0; 84 } 85 86 void 87 mcprtc_attach(struct device *parent, struct device *self, void *aux) 88 { 89 struct mcprtc_softc *sc = (struct mcprtc_softc *)self; 90 struct i2c_attach_args *ia = aux; 91 92 sc->sc_tag = ia->ia_tag; 93 sc->sc_addr = ia->ia_addr; 94 95 sc->sc_todr.cookie = sc; 96 sc->sc_todr.todr_gettime = mcprtc_gettime; 97 sc->sc_todr.todr_settime = mcprtc_settime; 98 99 printf("\n"); 100 todr_handle = &sc->sc_todr; 101 } 102 103 int 104 mcprtc_gettime(struct todr_chip_handle *handle, struct timeval *tv) 105 { 106 struct mcprtc_softc *sc = handle->cookie; 107 struct clock_ymdhms dt; 108 int error; 109 110 error = mcprtc_clock_read(sc, &dt); 111 if (error) 112 return error; 113 114 if (dt.dt_sec > 59 || dt.dt_min > 59 || dt.dt_hour > 23 || 115 dt.dt_day > 31 || dt.dt_day == 0 || 116 dt.dt_mon > 12 || dt.dt_mon == 0 || 117 dt.dt_year < POSIX_BASE_YEAR) 118 return EINVAL; 119 120 tv->tv_sec = clock_ymdhms_to_secs(&dt); 121 tv->tv_usec = 0; 122 return 0; 123 } 124 125 int 126 mcprtc_settime(struct todr_chip_handle *handle, struct timeval *tv) 127 { 128 struct mcprtc_softc *sc = handle->cookie; 129 struct clock_ymdhms dt; 130 131 clock_secs_to_ymdhms(tv->tv_sec, &dt); 132 133 return mcprtc_clock_write(sc, &dt); 134 } 135 136 uint8_t 137 mcprtc_reg_read(struct mcprtc_softc *sc, int reg) 138 { 139 uint8_t cmd = reg; 140 uint8_t val; 141 int error; 142 143 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 144 error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, 145 &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL); 146 iic_release_bus(sc->sc_tag, I2C_F_POLL); 147 148 if (error) { 149 printf("%s: can't read register 0x%02x\n", 150 sc->sc_dev.dv_xname, reg); 151 val = 0xff; 152 } 153 154 return val; 155 } 156 157 void 158 mcprtc_reg_write(struct mcprtc_softc *sc, int reg, uint8_t val) 159 { 160 uint8_t cmd = reg; 161 int error; 162 163 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 164 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 165 &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL); 166 iic_release_bus(sc->sc_tag, I2C_F_POLL); 167 168 if (error) { 169 printf("%s: can't write register 0x%02x\n", 170 sc->sc_dev.dv_xname, reg); 171 } 172 } 173 174 int 175 mcprtc_clock_read(struct mcprtc_softc *sc, struct clock_ymdhms *dt) 176 { 177 uint8_t regs[MCP794XX_NRTC_REGS]; 178 uint8_t cmd = MCP794XX_SC; 179 int error; 180 181 /* Don't trust the RTC if the oscillator is not running. */ 182 if (!(mcprtc_reg_read(sc, MCP794XX_DW) & MCP794XX_DW_OSCRUN)) 183 return EIO; 184 185 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 186 error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, 187 &cmd, sizeof(cmd), regs, MCP794XX_NRTC_REGS, I2C_F_POLL); 188 iic_release_bus(sc->sc_tag, I2C_F_POLL); 189 190 if (error) { 191 printf("%s: can't read RTC\n", sc->sc_dev.dv_xname); 192 return error; 193 } 194 195 /* 196 * Convert the MCP794XX's register values into something useable. 197 */ 198 dt->dt_sec = FROMBCD(regs[0] & 0x7f); 199 dt->dt_min = FROMBCD(regs[1] & 0x7f); 200 dt->dt_hour = FROMBCD(regs[2] & 0x1f); 201 if ((regs[2] & MCP794XX_HR_12H) && (regs[2] & MCP794XX_HR_PM)) 202 dt->dt_hour += 12; 203 dt->dt_wday = FROMBCD(regs[3] & 0x7); 204 dt->dt_day = FROMBCD(regs[4] & 0x3f); 205 dt->dt_mon = FROMBCD(regs[5] & 0x1f); 206 dt->dt_year = FROMBCD(regs[6]) + 2000; 207 208 return 0; 209 } 210 211 int 212 mcprtc_clock_write(struct mcprtc_softc *sc, struct clock_ymdhms *dt) 213 { 214 uint8_t regs[MCP794XX_NRTC_REGS]; 215 uint8_t cmd = MCP794XX_SC; 216 uint8_t oscoff, oscbit; 217 uint8_t reg; 218 int error, i; 219 220 /* 221 * Convert our time representation into something the MCP794XX 222 * can understand. 223 */ 224 regs[0] = TOBCD(dt->dt_sec); 225 regs[1] = TOBCD(dt->dt_min); 226 regs[2] = TOBCD(dt->dt_hour); 227 regs[3] = TOBCD(dt->dt_wday) | MCP794XX_DW_VBATEN; 228 regs[4] = TOBCD(dt->dt_day); 229 regs[5] = TOBCD(dt->dt_mon); 230 regs[6] = TOBCD(dt->dt_year - 2000); 231 232 /* Stop RTC. */ 233 if (sc->sc_extosc) { 234 oscoff = MCP794XX_CR; 235 oscbit = MCP794XX_CR_EXTOSC; 236 } else { 237 oscoff = MCP794XX_SC; 238 oscbit = MCP794XX_SC_ST; 239 } 240 241 /* Stop RTC such that we can write to it. */ 242 reg = mcprtc_reg_read(sc, oscoff); 243 reg &= ~oscbit; 244 mcprtc_reg_write(sc, oscoff, reg); 245 246 for (i = 0; i < 10; i++) { 247 reg = mcprtc_reg_read(sc, MCP794XX_DW); 248 if ((reg & MCP794XX_DW_OSCRUN) == 0) 249 break; 250 delay(10); 251 } 252 if (i == 10) { 253 error = EIO; 254 goto fail; 255 } 256 257 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 258 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 259 &cmd, sizeof(cmd), regs, MCP794XX_NRTC_REGS, I2C_F_POLL); 260 iic_release_bus(sc->sc_tag, I2C_F_POLL); 261 262 /* Restart RTC. */ 263 reg = mcprtc_reg_read(sc, oscoff); 264 reg |= oscbit; 265 mcprtc_reg_write(sc, oscoff, reg); 266 267 fail: 268 if (error) { 269 printf("%s: can't write RTC\n", sc->sc_dev.dv_xname); 270 return error; 271 } 272 273 return 0; 274 } 275