1 /* $OpenBSD: isl1208.c,v 1.3 2020/04/24 22:42:31 kettenis Exp $ */ 2 /* 3 * Copyright (c) 2018 Mark Kettenis <kettenis@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/param.h> 19 #include <sys/systm.h> 20 #include <sys/device.h> 21 22 #include <dev/i2c/i2cvar.h> 23 24 #include <dev/clock_subr.h> 25 26 extern todr_chip_handle_t todr_handle; 27 28 #define ISL1208_SC 0x00 29 #define ISL1208_MN 0x01 30 #define ISL1208_HR 0x02 31 #define ISL1208_HR_HR21 (1 << 5) 32 #define ISL1208_HR_MIL (1 << 7) 33 #define ISL1208_DT 0x03 34 #define ISL1208_MO 0x04 35 #define ISL1208_YR 0x05 36 #define ISL1208_DW 0x06 37 #define ISL1208_SR 0x07 38 #define ISL1208_SR_RTCF (1 << 0) 39 #define ISL1208_SR_WRTC (1 << 4) 40 41 #define ISL1208_NRTC_REGS 7 42 43 struct islrtc_softc { 44 struct device sc_dev; 45 i2c_tag_t sc_tag; 46 i2c_addr_t sc_addr; 47 48 struct todr_chip_handle sc_todr; 49 }; 50 51 int islrtc_match(struct device *, void *, void *); 52 void islrtc_attach(struct device *, struct device *, void *); 53 54 struct cfattach islrtc_ca = { 55 sizeof(struct islrtc_softc), islrtc_match, islrtc_attach 56 }; 57 58 struct cfdriver islrtc_cd = { 59 NULL, "islrtc", DV_DULL 60 }; 61 62 uint8_t islrtc_reg_read(struct islrtc_softc *, int); 63 void islrtc_reg_write(struct islrtc_softc *, int, uint8_t); 64 int islrtc_clock_read(struct islrtc_softc *, struct clock_ymdhms *); 65 int islrtc_clock_write(struct islrtc_softc *, struct clock_ymdhms *); 66 int islrtc_gettime(struct todr_chip_handle *, struct timeval *); 67 int islrtc_settime(struct todr_chip_handle *, struct timeval *); 68 69 int 70 islrtc_match(struct device *parent, void *match, void *aux) 71 { 72 struct i2c_attach_args *ia = aux; 73 74 if (strcmp(ia->ia_name, "isil,isl1208") == 0 || 75 strcmp(ia->ia_name, "isil,isl1218") == 0) 76 return 1; 77 78 return 0; 79 } 80 81 void 82 islrtc_attach(struct device *parent, struct device *self, void *aux) 83 { 84 struct islrtc_softc *sc = (struct islrtc_softc *)self; 85 struct i2c_attach_args *ia = aux; 86 87 sc->sc_tag = ia->ia_tag; 88 sc->sc_addr = ia->ia_addr; 89 90 sc->sc_todr.cookie = sc; 91 sc->sc_todr.todr_gettime = islrtc_gettime; 92 sc->sc_todr.todr_settime = islrtc_settime; 93 todr_handle = &sc->sc_todr; 94 95 printf("\n"); 96 } 97 98 int 99 islrtc_gettime(struct todr_chip_handle *handle, struct timeval *tv) 100 { 101 struct islrtc_softc *sc = handle->cookie; 102 struct clock_ymdhms dt; 103 int error; 104 105 error = islrtc_clock_read(sc, &dt); 106 if (error) 107 return error; 108 109 if (dt.dt_sec > 59 || dt.dt_min > 59 || dt.dt_hour > 23 || 110 dt.dt_day > 31 || dt.dt_day == 0 || 111 dt.dt_mon > 12 || dt.dt_mon == 0 || 112 dt.dt_year < POSIX_BASE_YEAR) 113 return EINVAL; 114 115 tv->tv_sec = clock_ymdhms_to_secs(&dt); 116 tv->tv_usec = 0; 117 return 0; 118 } 119 120 int 121 islrtc_settime(struct todr_chip_handle *handle, struct timeval *tv) 122 { 123 struct islrtc_softc *sc = handle->cookie; 124 struct clock_ymdhms dt; 125 126 clock_secs_to_ymdhms(tv->tv_sec, &dt); 127 128 return islrtc_clock_write(sc, &dt); 129 } 130 131 uint8_t 132 islrtc_reg_read(struct islrtc_softc *sc, int reg) 133 { 134 uint8_t cmd = reg; 135 uint8_t val; 136 int error; 137 138 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 139 error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, 140 &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL); 141 iic_release_bus(sc->sc_tag, I2C_F_POLL); 142 143 if (error) { 144 printf("%s: can't read register 0x%02x\n", 145 sc->sc_dev.dv_xname, reg); 146 val = 0xff; 147 } 148 149 return val; 150 } 151 152 void 153 islrtc_reg_write(struct islrtc_softc *sc, int reg, uint8_t val) 154 { 155 uint8_t cmd = reg; 156 int error; 157 158 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 159 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 160 &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL); 161 iic_release_bus(sc->sc_tag, I2C_F_POLL); 162 163 if (error) { 164 printf("%s: can't write register 0x%02x\n", 165 sc->sc_dev.dv_xname, reg); 166 } 167 } 168 169 int 170 islrtc_clock_read(struct islrtc_softc *sc, struct clock_ymdhms *dt) 171 { 172 uint8_t regs[ISL1208_NRTC_REGS]; 173 uint8_t cmd = ISL1208_SC; 174 uint8_t status; 175 int error; 176 177 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 178 error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, 179 &cmd, sizeof(cmd), regs, ISL1208_NRTC_REGS, I2C_F_POLL); 180 iic_release_bus(sc->sc_tag, I2C_F_POLL); 181 182 if (error) { 183 printf("%s: can't read RTC\n", sc->sc_dev.dv_xname); 184 return error; 185 } 186 187 /* 188 * Convert the ISL1208's register values into something useable. 189 */ 190 dt->dt_sec = FROMBCD(regs[0]); 191 dt->dt_min = FROMBCD(regs[1]); 192 if (regs[2] & ISL1208_HR_MIL) { 193 dt->dt_hour = FROMBCD(regs[2] & ~ISL1208_HR_MIL); 194 } else { 195 dt->dt_hour = FROMBCD(regs[2] & ~ISL1208_HR_HR21); 196 if (regs[2] & ISL1208_HR_HR21) 197 dt->dt_hour += 12; 198 } 199 dt->dt_day = FROMBCD(regs[3]); 200 dt->dt_mon = FROMBCD(regs[4]); 201 dt->dt_year = FROMBCD(regs[5]) + 2000; 202 203 /* Consider the time to be invalid if we lost power. */ 204 status = islrtc_reg_read(sc, ISL1208_SR); 205 if (status & ISL1208_SR_RTCF) 206 return EINVAL; 207 208 return 0; 209 } 210 211 int 212 islrtc_clock_write(struct islrtc_softc *sc, struct clock_ymdhms *dt) 213 { 214 uint8_t regs[ISL1208_NRTC_REGS]; 215 uint8_t cmd = ISL1208_SC; 216 uint8_t reg; 217 int error; 218 219 /* 220 * Convert our time representation into something the ISL1208 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) | ISL1208_HR_MIL; 226 regs[3] = TOBCD(dt->dt_day); 227 regs[4] = TOBCD(dt->dt_mon); 228 regs[5] = TOBCD(dt->dt_year - 2000); 229 regs[6] = TOBCD(dt->dt_wday); 230 231 /* Stop RTC such that we can write to it. */ 232 reg = islrtc_reg_read(sc, ISL1208_SR); 233 if (reg == 0xff) { 234 error = EIO; 235 goto fail; 236 } 237 islrtc_reg_write(sc, ISL1208_SR, reg | ISL1208_SR_WRTC); 238 239 iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 240 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 241 &cmd, sizeof(cmd), regs, ISL1208_NRTC_REGS, I2C_F_POLL); 242 iic_release_bus(sc->sc_tag, I2C_F_POLL); 243 244 /* Restart RTC. */ 245 islrtc_reg_write(sc, ISL1208_SR, reg & ~ISL1208_SR_WRTC); 246 247 fail: 248 if (error) { 249 printf("%s: can't write RTC\n", sc->sc_dev.dv_xname); 250 return error; 251 } 252 253 return 0; 254 } 255