1*4c90b400Skettenis /* $OpenBSD: pcf8523.c,v 1.4 2020/04/27 12:36:03 kettenis Exp $ */ 2875dbdfaSkettenis 3875dbdfaSkettenis /* 4875dbdfaSkettenis * Copyright (c) 2005 Kimihiro Nonaka 5875dbdfaSkettenis * Copyright (c) 2016 Mark Kettenis 6875dbdfaSkettenis * All rights reserved. 7875dbdfaSkettenis * 8875dbdfaSkettenis * Redistribution and use in source and binary forms, with or without 9875dbdfaSkettenis * modification, are permitted provided that the following conditions 10875dbdfaSkettenis * are met: 11875dbdfaSkettenis * 1. Redistributions of source code must retain the above copyright 12875dbdfaSkettenis * notice, this list of conditions and the following disclaimer. 13875dbdfaSkettenis * 2. Redistributions in binary form must reproduce the above copyright 14875dbdfaSkettenis * notice, this list of conditions and the following disclaimer in the 15875dbdfaSkettenis * documentation and/or other materials provided with the distribution. 16875dbdfaSkettenis * 17875dbdfaSkettenis * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND 18875dbdfaSkettenis * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19875dbdfaSkettenis * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20875dbdfaSkettenis * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC 21875dbdfaSkettenis * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22875dbdfaSkettenis * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23875dbdfaSkettenis * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24875dbdfaSkettenis * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25875dbdfaSkettenis * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26875dbdfaSkettenis * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27875dbdfaSkettenis * POSSIBILITY OF SUCH DAMAGE. 28875dbdfaSkettenis */ 29875dbdfaSkettenis 30875dbdfaSkettenis #include <sys/param.h> 31875dbdfaSkettenis #include <sys/systm.h> 32875dbdfaSkettenis #include <sys/device.h> 33875dbdfaSkettenis #include <sys/kernel.h> 34875dbdfaSkettenis #include <sys/fcntl.h> 35875dbdfaSkettenis #include <sys/uio.h> 36875dbdfaSkettenis #include <sys/conf.h> 37875dbdfaSkettenis #include <sys/event.h> 38875dbdfaSkettenis 39875dbdfaSkettenis #include <dev/clock_subr.h> 40875dbdfaSkettenis 41875dbdfaSkettenis #include <dev/i2c/i2cvar.h> 42875dbdfaSkettenis 43875dbdfaSkettenis /* 44875dbdfaSkettenis * PCF8523 Real-Time Clock 45875dbdfaSkettenis */ 46875dbdfaSkettenis 47875dbdfaSkettenis #define PCF8523_ADDR 0x68 /* Fixed I2C Slave Address */ 48875dbdfaSkettenis 49875dbdfaSkettenis #define PCF8523_CONTROL1 0x00 50875dbdfaSkettenis #define PCF8523_CONTROL2 0x01 51875dbdfaSkettenis #define PCF8523_CONTROL3 0x02 52875dbdfaSkettenis #define PCF8523_SECONDS 0x03 53875dbdfaSkettenis #define PCF8523_MINUTES 0x04 54875dbdfaSkettenis #define PCF8523_HOURS 0x05 55875dbdfaSkettenis #define PCF8523_DAY 0x06 56875dbdfaSkettenis #define PCF8523_WDAY 0x07 57875dbdfaSkettenis #define PCF8523_MONTH 0x08 58875dbdfaSkettenis #define PCF8523_YEAR 0x09 59875dbdfaSkettenis #define PCF8523_ALARM_MIN 0x0a 60875dbdfaSkettenis #define PCF8523_ALARM_HOUR 0x0b 61875dbdfaSkettenis #define PCF8523_ALARM_DAY 0x0c 62875dbdfaSkettenis #define PCF8523_ALARM_WDAY 0x0d 63875dbdfaSkettenis #define PCF8523_OFFSET 0x0e 64875dbdfaSkettenis 65875dbdfaSkettenis #define PCF8523_NREGS 20 66875dbdfaSkettenis #define PCF8523_NRTC_REGS 7 67875dbdfaSkettenis 68875dbdfaSkettenis /* 69875dbdfaSkettenis * Bit definitions. 70875dbdfaSkettenis */ 71875dbdfaSkettenis #define PCF8523_CONTROL1_12_24 (1 << 3) 72875dbdfaSkettenis #define PCF8523_CONTROL1_STOP (1 << 5) 73875dbdfaSkettenis #define PCF8523_CONTROL3_PM_MASK 0xe0 74875dbdfaSkettenis #define PCF8523_CONTROL3_PM_BLD (1 << 7) 75875dbdfaSkettenis #define PCF8523_CONTROL3_PM_VDD (1 << 6) 76875dbdfaSkettenis #define PCF8523_CONTROL3_PM_DSM (1 << 5) 77875dbdfaSkettenis #define PCF8523_CONTROL3_BLF (1 << 2) 78875dbdfaSkettenis #define PCF8523_SECONDS_MASK 0x7f 79875dbdfaSkettenis #define PCF8523_SECONDS_OS (1 << 7) 80875dbdfaSkettenis #define PCF8523_MINUTES_MASK 0x7f 81875dbdfaSkettenis #define PCF8523_HOURS_12HRS_PM (1 << 5) /* If 12 hr mode, set = PM */ 82875dbdfaSkettenis #define PCF8523_HOURS_12MASK 0x1f 83875dbdfaSkettenis #define PCF8523_HOURS_24MASK 0x3f 84875dbdfaSkettenis #define PCF8523_DAY_MASK 0x3f 85875dbdfaSkettenis #define PCF8523_WDAY_MASK 0x07 86875dbdfaSkettenis #define PCF8523_MONTH_MASK 0x1f 87875dbdfaSkettenis 88875dbdfaSkettenis struct pcfrtc_softc { 89875dbdfaSkettenis struct device sc_dev; 90875dbdfaSkettenis i2c_tag_t sc_tag; 91875dbdfaSkettenis int sc_address; 92875dbdfaSkettenis struct todr_chip_handle sc_todr; 93875dbdfaSkettenis }; 94875dbdfaSkettenis 95875dbdfaSkettenis int pcfrtc_match(struct device *, void *, void *); 96875dbdfaSkettenis void pcfrtc_attach(struct device *, struct device *, void *); 97875dbdfaSkettenis 98875dbdfaSkettenis struct cfattach pcfrtc_ca = { 99875dbdfaSkettenis sizeof(struct pcfrtc_softc), pcfrtc_match, pcfrtc_attach 100875dbdfaSkettenis }; 101875dbdfaSkettenis 102875dbdfaSkettenis struct cfdriver pcfrtc_cd = { 103875dbdfaSkettenis NULL, "pcfrtc", DV_DULL 104875dbdfaSkettenis }; 105875dbdfaSkettenis 106875dbdfaSkettenis uint8_t pcfrtc_reg_read(struct pcfrtc_softc *, int); 107875dbdfaSkettenis void pcfrtc_reg_write(struct pcfrtc_softc *, int, uint8_t); 108875dbdfaSkettenis int pcfrtc_clock_read(struct pcfrtc_softc *, struct clock_ymdhms *); 109875dbdfaSkettenis int pcfrtc_clock_write(struct pcfrtc_softc *, struct clock_ymdhms *); 110875dbdfaSkettenis int pcfrtc_gettime(struct todr_chip_handle *, struct timeval *); 111875dbdfaSkettenis int pcfrtc_settime(struct todr_chip_handle *, struct timeval *); 112875dbdfaSkettenis int pcfrtc_getcal(struct todr_chip_handle *, int *); 113875dbdfaSkettenis int pcfrtc_setcal(struct todr_chip_handle *, int); 114875dbdfaSkettenis 115875dbdfaSkettenis int 116875dbdfaSkettenis pcfrtc_match(struct device *parent, void *v, void *arg) 117875dbdfaSkettenis { 118875dbdfaSkettenis struct i2c_attach_args *ia = arg; 119875dbdfaSkettenis 1203364f722Skettenis if (strcmp(ia->ia_name, "nxp,pcf8523") == 0 && 121875dbdfaSkettenis ia->ia_addr == PCF8523_ADDR) 122875dbdfaSkettenis return (1); 123875dbdfaSkettenis 124875dbdfaSkettenis return (0); 125875dbdfaSkettenis } 126875dbdfaSkettenis 127875dbdfaSkettenis void 128875dbdfaSkettenis pcfrtc_attach(struct device *parent, struct device *self, void *arg) 129875dbdfaSkettenis { 130875dbdfaSkettenis struct pcfrtc_softc *sc = (struct pcfrtc_softc *)self; 131875dbdfaSkettenis struct i2c_attach_args *ia = arg; 132875dbdfaSkettenis uint8_t reg; 133875dbdfaSkettenis 134875dbdfaSkettenis sc->sc_tag = ia->ia_tag; 135875dbdfaSkettenis sc->sc_address = ia->ia_addr; 136875dbdfaSkettenis sc->sc_todr.cookie = sc; 137875dbdfaSkettenis sc->sc_todr.todr_gettime = pcfrtc_gettime; 138875dbdfaSkettenis sc->sc_todr.todr_settime = pcfrtc_settime; 139875dbdfaSkettenis sc->sc_todr.todr_getcal = pcfrtc_getcal; 140875dbdfaSkettenis sc->sc_todr.todr_setcal = pcfrtc_setcal; 141875dbdfaSkettenis sc->sc_todr.todr_setwen = NULL; 142875dbdfaSkettenis 143875dbdfaSkettenis #if 0 144875dbdfaSkettenis todr_attach(&sc->sc_todr); 145875dbdfaSkettenis #else 146875dbdfaSkettenis /* XXX */ 147875dbdfaSkettenis { 148875dbdfaSkettenis extern todr_chip_handle_t todr_handle; 149875dbdfaSkettenis todr_handle = &sc->sc_todr; 150875dbdfaSkettenis } 151875dbdfaSkettenis #endif 152875dbdfaSkettenis 153875dbdfaSkettenis /* 154875dbdfaSkettenis * Enable battery switch-over and battery low detection in 155875dbdfaSkettenis * standard mode, and switch to 24 hour mode. 156875dbdfaSkettenis */ 157875dbdfaSkettenis reg = pcfrtc_reg_read(sc, PCF8523_CONTROL3); 158875dbdfaSkettenis reg &= ~PCF8523_CONTROL3_PM_MASK; 159875dbdfaSkettenis pcfrtc_reg_write(sc, PCF8523_CONTROL3, reg); 160875dbdfaSkettenis reg = pcfrtc_reg_read(sc, PCF8523_CONTROL1); 161875dbdfaSkettenis reg &= ~PCF8523_CONTROL1_12_24; 162875dbdfaSkettenis reg &= ~PCF8523_CONTROL1_STOP; 163875dbdfaSkettenis pcfrtc_reg_write(sc, PCF8523_CONTROL1, reg); 164875dbdfaSkettenis 165875dbdfaSkettenis /* Report battery status. */ 166875dbdfaSkettenis reg = pcfrtc_reg_read(sc, PCF8523_CONTROL3); 167875dbdfaSkettenis printf(": battery %s\n", (reg & PCF8523_CONTROL3_BLF) ? "low" : "ok"); 168875dbdfaSkettenis } 169875dbdfaSkettenis 170875dbdfaSkettenis int 171875dbdfaSkettenis pcfrtc_gettime(struct todr_chip_handle *ch, struct timeval *tv) 172875dbdfaSkettenis { 173875dbdfaSkettenis struct pcfrtc_softc *sc = ch->cookie; 174875dbdfaSkettenis struct clock_ymdhms dt; 175875dbdfaSkettenis 176875dbdfaSkettenis memset(&dt, 0, sizeof(dt)); 177875dbdfaSkettenis if (pcfrtc_clock_read(sc, &dt) == 0) 178875dbdfaSkettenis return (-1); 179875dbdfaSkettenis 180875dbdfaSkettenis tv->tv_sec = clock_ymdhms_to_secs(&dt); 181875dbdfaSkettenis tv->tv_usec = 0; 182875dbdfaSkettenis return (0); 183875dbdfaSkettenis } 184875dbdfaSkettenis 185875dbdfaSkettenis int 186875dbdfaSkettenis pcfrtc_settime(struct todr_chip_handle *ch, struct timeval *tv) 187875dbdfaSkettenis { 188875dbdfaSkettenis struct pcfrtc_softc *sc = ch->cookie; 189875dbdfaSkettenis struct clock_ymdhms dt; 190*4c90b400Skettenis uint8_t reg; 191875dbdfaSkettenis 192875dbdfaSkettenis clock_secs_to_ymdhms(tv->tv_sec, &dt); 193875dbdfaSkettenis if (pcfrtc_clock_write(sc, &dt) == 0) 194875dbdfaSkettenis return (-1); 195*4c90b400Skettenis 196*4c90b400Skettenis /* Clear OS flag. */ 197*4c90b400Skettenis reg = pcfrtc_reg_read(sc, PCF8523_SECONDS); 198*4c90b400Skettenis if (reg & PCF8523_SECONDS_OS) { 199*4c90b400Skettenis reg &= ~PCF8523_SECONDS_OS; 200*4c90b400Skettenis pcfrtc_reg_write(sc, PCF8523_SECONDS, reg); 201*4c90b400Skettenis } 202*4c90b400Skettenis 203875dbdfaSkettenis return (0); 204875dbdfaSkettenis } 205875dbdfaSkettenis 206875dbdfaSkettenis int 207875dbdfaSkettenis pcfrtc_setcal(struct todr_chip_handle *ch, int cal) 208875dbdfaSkettenis { 209875dbdfaSkettenis return (EOPNOTSUPP); 210875dbdfaSkettenis } 211875dbdfaSkettenis 212875dbdfaSkettenis int 213875dbdfaSkettenis pcfrtc_getcal(struct todr_chip_handle *ch, int *cal) 214875dbdfaSkettenis { 215875dbdfaSkettenis return (EOPNOTSUPP); 216875dbdfaSkettenis } 217875dbdfaSkettenis 218875dbdfaSkettenis uint8_t 219875dbdfaSkettenis pcfrtc_reg_read(struct pcfrtc_softc *sc, int reg) 220875dbdfaSkettenis { 221875dbdfaSkettenis uint8_t cmd = reg; 222875dbdfaSkettenis uint8_t val; 223875dbdfaSkettenis 224875dbdfaSkettenis iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 225875dbdfaSkettenis if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address, 226875dbdfaSkettenis NULL, 0, &cmd, sizeof cmd, I2C_F_POLL) || 227875dbdfaSkettenis iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address, 228875dbdfaSkettenis NULL, 0, &val, sizeof val, I2C_F_POLL)) { 229875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL); 230875dbdfaSkettenis printf("%s: pcfrtc_reg_read: failed to read reg%d\n", 231875dbdfaSkettenis sc->sc_dev.dv_xname, reg); 232875dbdfaSkettenis return 0; 233875dbdfaSkettenis } 234875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL); 235875dbdfaSkettenis return val; 236875dbdfaSkettenis } 237875dbdfaSkettenis 238875dbdfaSkettenis void 239875dbdfaSkettenis pcfrtc_reg_write(struct pcfrtc_softc *sc, int reg, uint8_t val) 240875dbdfaSkettenis { 241875dbdfaSkettenis uint8_t cmd = reg; 242875dbdfaSkettenis 243875dbdfaSkettenis iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 244875dbdfaSkettenis if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address, 245875dbdfaSkettenis &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL)) { 246875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL); 247875dbdfaSkettenis printf("%s: pcfrtc_reg_write: failed to write reg%d\n", 248875dbdfaSkettenis sc->sc_dev.dv_xname, reg); 249875dbdfaSkettenis return; 250875dbdfaSkettenis } 251875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL); 252875dbdfaSkettenis } 253875dbdfaSkettenis 254875dbdfaSkettenis int 255875dbdfaSkettenis pcfrtc_clock_read(struct pcfrtc_softc *sc, struct clock_ymdhms *dt) 256875dbdfaSkettenis { 257875dbdfaSkettenis uint8_t regs[PCF8523_NRTC_REGS]; 258875dbdfaSkettenis uint8_t cmd = PCF8523_SECONDS; 259875dbdfaSkettenis 260875dbdfaSkettenis iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 261875dbdfaSkettenis if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address, 262875dbdfaSkettenis NULL, 0, &cmd, sizeof cmd, I2C_F_POLL) || 263875dbdfaSkettenis iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address, 264875dbdfaSkettenis NULL, 0, regs, PCF8523_NRTC_REGS, I2C_F_POLL)) { 265875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL); 266875dbdfaSkettenis printf("%s: pcfrtc_clock_read: failed to read rtc\n", 267875dbdfaSkettenis sc->sc_dev.dv_xname); 268875dbdfaSkettenis return (0); 269875dbdfaSkettenis } 270875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL); 271875dbdfaSkettenis 272875dbdfaSkettenis /* 273875dbdfaSkettenis * Convert the PCF8523's register values into something useable 274875dbdfaSkettenis */ 275875dbdfaSkettenis dt->dt_sec = FROMBCD(regs[0] & PCF8523_SECONDS_MASK); 276875dbdfaSkettenis dt->dt_min = FROMBCD(regs[1] & PCF8523_MINUTES_MASK); 277875dbdfaSkettenis dt->dt_hour = FROMBCD(regs[2] & PCF8523_HOURS_24MASK); 278875dbdfaSkettenis dt->dt_day = FROMBCD(regs[3] & PCF8523_DAY_MASK); 279875dbdfaSkettenis dt->dt_mon = FROMBCD(regs[5] & PCF8523_MONTH_MASK); 280875dbdfaSkettenis dt->dt_year = FROMBCD(regs[6]) + 2000; 281875dbdfaSkettenis 282875dbdfaSkettenis if ((regs[0] & PCF8523_SECONDS_OS)) 283875dbdfaSkettenis return (0); 284875dbdfaSkettenis 285875dbdfaSkettenis return (1); 286875dbdfaSkettenis } 287875dbdfaSkettenis 288875dbdfaSkettenis int 289875dbdfaSkettenis pcfrtc_clock_write(struct pcfrtc_softc *sc, struct clock_ymdhms *dt) 290875dbdfaSkettenis { 291875dbdfaSkettenis uint8_t regs[PCF8523_NRTC_REGS]; 292875dbdfaSkettenis uint8_t cmd = PCF8523_SECONDS; 293875dbdfaSkettenis 294875dbdfaSkettenis /* 295875dbdfaSkettenis * Convert our time representation into something the PCF8523 296875dbdfaSkettenis * can understand. 297875dbdfaSkettenis */ 298875dbdfaSkettenis regs[0] = TOBCD(dt->dt_sec); 299875dbdfaSkettenis regs[1] = TOBCD(dt->dt_min); 300875dbdfaSkettenis regs[2] = TOBCD(dt->dt_hour); 301875dbdfaSkettenis regs[3] = TOBCD(dt->dt_day); 302875dbdfaSkettenis regs[4] = TOBCD(dt->dt_wday); 303875dbdfaSkettenis regs[5] = TOBCD(dt->dt_mon); 304875dbdfaSkettenis regs[6] = TOBCD(dt->dt_year - 2000); 305875dbdfaSkettenis 306875dbdfaSkettenis iic_acquire_bus(sc->sc_tag, I2C_F_POLL); 307875dbdfaSkettenis if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address, 308875dbdfaSkettenis &cmd, sizeof cmd, regs, PCF8523_NRTC_REGS, I2C_F_POLL)) { 309875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL); 310875dbdfaSkettenis printf("%s: pcfrtc_clock_write: failed to write rtc\n", 311875dbdfaSkettenis sc->sc_dev.dv_xname); 312875dbdfaSkettenis return (0); 313875dbdfaSkettenis } 314875dbdfaSkettenis iic_release_bus(sc->sc_tag, I2C_F_POLL); 315875dbdfaSkettenis return (1); 316875dbdfaSkettenis } 317