1 /* $NetBSD: bq4802_ebus.c,v 1.3 2026/07/07 12:33:39 jdc Exp $ */ 2 3 /*- 4 * Copyright (c) 2026 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Julian Coleman. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __KERNEL_RCSID(0, "$NetBSD: bq4802_ebus.c,v 1.3 2026/07/07 12:33:39 jdc Exp $"); 34 35 /* Clock driver for rtc/bq4802 (a Texas Instruments bq4802Y/bq4802LY). */ 36 37 #include <sys/param.h> 38 #include <sys/device.h> 39 #include <sys/proc.h> 40 #include <sys/types.h> 41 #include <sys/sysctl.h> 42 43 #include <sys/bus.h> 44 #include <machine/autoconf.h> 45 46 #include <dev/clock_subr.h> 47 #include <dev/ic/bq4802reg.h> 48 49 #include <dev/ebus/ebusreg.h> 50 #include <dev/ebus/ebusvar.h> 51 52 53 struct bq4802rtc_softc { 54 device_t sc_dev; 55 56 struct todr_chip_handle sc_tch; 57 58 bus_space_tag_t sc_bst; 59 bus_space_handle_t sc_bsh; 60 61 int sc_osc_stp; /* Oscillator stop (sysctl) */ 62 }; 63 64 static int bq4802rtc_ebus_match(device_t, cfdata_t, void *); 65 static void bq4802rtc_ebus_attach(device_t, device_t, void *); 66 static int bq4802_gettime_ymdhms(struct todr_chip_handle *, 67 struct clock_ymdhms *); 68 static int bq4802_settime_ymdhms(struct todr_chip_handle *, 69 struct clock_ymdhms *); 70 static int sysctl_bq4802_osc(SYSCTLFN_ARGS); 71 72 CFATTACH_DECL_NEW(bq4802rtc_ebus, sizeof(struct bq4802rtc_softc), 73 bq4802rtc_ebus_match, bq4802rtc_ebus_attach, NULL, NULL); 74 75 /* Register read and write. */ 76 #define bq4802_read(sc, reg) \ 77 bus_space_read_1(sc->sc_bst, sc->sc_bsh, reg) 78 #define bq4802_write(sc, reg, val) \ 79 bus_space_write_1(sc->sc_bst, sc->sc_bsh, reg, val) 80 81 static int 82 bq4802rtc_ebus_match(device_t parent, cfdata_t cf, void *aux) 83 { 84 struct ebus_attach_args *ea = aux; 85 char *compat; 86 87 if (strcmp("rtc", ea->ea_name) != 0) 88 return 0; 89 90 compat = prom_getpropstring(ea->ea_node, "compatible"); 91 if (compat != NULL && !strcmp(compat, "bq4802")) 92 return 2; /* We need a better match than 'rtc' */ 93 94 return 0; 95 } 96 97 static void 98 bq4802rtc_ebus_attach(device_t parent, device_t self, void *aux) 99 { 100 struct bq4802rtc_softc *sc = device_private(self); 101 struct ebus_attach_args *ea = aux; 102 todr_chip_handle_t tch = &sc->sc_tch; 103 const struct sysctlnode *me = NULL, *node = NULL; 104 int sz; 105 uint8_t ctrl, new; 106 107 sc->sc_dev = self; 108 sc->sc_bst = ea->ea_bustag; 109 110 sz = ea->ea_reg[0].size; 111 112 if (bus_space_map(sc->sc_bst, 113 EBUS_ADDR_FROM_REG(&ea->ea_reg[0]), 114 sz, 0, 115 &sc->sc_bsh) != 0) { 116 aprint_error(": can't map register\n"); 117 return; 118 } 119 120 aprint_normal(": real time clock\n"); 121 122 tch->todr_dev = self; 123 tch->todr_gettime_ymdhms = bq4802_gettime_ymdhms; 124 tch->todr_settime_ymdhms = bq4802_settime_ymdhms; 125 126 /* Setup: alarms off, disable DST, enable updates, 24-hour mode */ 127 ctrl = bq4802_read(sc, BQ4802_CTRL); 128 if (ctrl & BQ4802_CTRL_STP) 129 sc->sc_osc_stp = 0; 130 else { 131 aprint_error_dev(self, 132 "WARNING: oscillator is stopped (0x%02x)\n", ctrl); 133 sc->sc_osc_stp = 1; 134 } 135 new = ctrl & ~(BQ4802_CTRL_DSE | BQ4802_CTRL_UTI); 136 new = new | BQ4802_CTRL_24; 137 if (new != ctrl) 138 bq4802_write(sc, BQ4802_CTRL, ctrl); 139 140 todr_attach(tch); 141 142 /* Setup sysctl for the oscillator control */ 143 sysctl_createv(NULL, 0, NULL, &me, 144 CTLFLAG_READWRITE, 145 CTLTYPE_NODE, device_xname(self), NULL, 146 NULL, 0, NULL, 0, 147 CTL_HW, CTL_CREATE, CTL_EOL); 148 if (me == NULL) 149 aprint_error_dev(self, "unable to add sysctl root\n"); 150 else { 151 sysctl_createv(NULL, 0, NULL, &node, 152 CTLFLAG_READWRITE | CTLFLAG_OWNDESC, 153 CTLTYPE_INT, "stop_oscillator", "Stop the chip oscillator", 154 sysctl_bq4802_osc, 1, (void *)sc, 0, 155 CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 156 if (node == NULL) 157 aprint_error_dev(self, "unable to add sysctl node\n"); 158 } 159 } 160 161 static int 162 bq4802_gettime_ymdhms(struct todr_chip_handle *todrch, struct clock_ymdhms *dt) 163 { 164 struct bq4802rtc_softc *sc = device_private(todrch->todr_dev); 165 bq4802_regs regs; 166 uint64_t val; 167 int s; 168 169 s = splclock(); 170 BQ4802_GETTOD(sc, ®s); 171 splx(s); 172 173 dt->dt_sec = bcdtobin(regs[BQ4802_SEC]); 174 dt->dt_min = bcdtobin(regs[BQ4802_MIN]); 175 val = bcdtobin(regs[BQ4802_HOUR]); 176 if (val == 24) 177 val = 0; 178 dt->dt_hour = val; 179 dt->dt_wday = bcdtobin(regs[BQ4802_WDAY]); 180 dt->dt_day = bcdtobin(regs[BQ4802_DAY]); 181 dt->dt_mon = bcdtobin(regs[BQ4802_MONTH]); 182 val = bcdtobin(regs[BQ4802_YEAR]); 183 val += bcdtobin(regs[BQ4802_CENT]) * 100; 184 dt->dt_year = val; 185 186 return 0; 187 } 188 189 static int 190 bq4802_settime_ymdhms(struct todr_chip_handle *todrch, struct clock_ymdhms *dt) 191 { 192 struct bq4802rtc_softc *sc = device_private(todrch->todr_dev); 193 bq4802_regs regs; 194 uint8_t val; 195 int s; 196 197 regs[BQ4802_SEC] = bintobcd(dt->dt_sec); 198 regs[BQ4802_MIN] = bintobcd(dt->dt_min); 199 if (dt->dt_hour == 0) 200 val = 24; 201 else 202 val = dt->dt_hour; 203 regs[BQ4802_HOUR] = bintobcd(val); 204 regs[BQ4802_WDAY] = bintobcd(dt->dt_wday); 205 regs[BQ4802_DAY] = bintobcd(dt->dt_day); 206 regs[BQ4802_MONTH] = bintobcd(dt->dt_mon); 207 regs[BQ4802_YEAR] = bintobcd(dt->dt_year % 100); 208 regs[BQ4802_CENT] = bintobcd(dt->dt_year / 100); 209 210 s = splclock(); 211 BQ4802_SETTOD(sc, ®s); 212 splx(s); 213 214 return 0; 215 } 216 217 static int 218 sysctl_bq4802_osc(SYSCTLFN_ARGS) 219 { 220 struct sysctlnode node = *rnode; 221 struct bq4802rtc_softc *sc = node.sysctl_data; 222 int stp; 223 uint8_t ctrl; 224 225 if (newp) { 226 /* write */ 227 stp = sc->sc_osc_stp; 228 node.sysctl_data = &stp; 229 if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { 230 if (stp != 0 && stp != 1) 231 return EINVAL; 232 233 if (stp != sc->sc_osc_stp) { 234 sc->sc_osc_stp = stp; 235 ctrl = bq4802_read(sc, BQ4802_CTRL); 236 if (stp) 237 ctrl &= ~BQ4802_CTRL_STP; 238 else 239 ctrl |= BQ4802_CTRL_STP; 240 bq4802_write(sc, BQ4802_CTRL, ctrl); 241 } 242 243 return 0; 244 } 245 return EINVAL; 246 } else { 247 node.sysctl_data = &sc->sc_osc_stp; 248 node.sysctl_size = 4; 249 return (sysctl_lookup(SYSCTLFN_CALL(&node))); 250 } 251 252 return 0; 253 } 254 255 /* 256 SYSCTL_SETUP(sysctl_bq4802_setup, "sysctl bq4802 subtree setup") 257 { 258 259 sysctl_createv(NULL, 0, NULL, NULL, 260 CTLFLAG_PERMANENT, 261 CTLTYPE_NODE, "hw", NULL, 262 NULL, 0, NULL, 0, 263 CTL_HW, CTL_EOL); 264 } 265 */ 266