#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: bq4802_ebus.c,v 1.3 2026/07/07 12:33:39 jdc Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/proc.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/bus.h>
#include <machine/autoconf.h>
#include <dev/clock_subr.h>
#include <dev/ic/bq4802reg.h>
#include <dev/ebus/ebusreg.h>
#include <dev/ebus/ebusvar.h>
struct bq4802rtc_softc {
device_t sc_dev;
struct todr_chip_handle sc_tch;
bus_space_tag_t sc_bst;
bus_space_handle_t sc_bsh;
int sc_osc_stp;
};
static int bq4802rtc_ebus_match(device_t, cfdata_t, void *);
static void bq4802rtc_ebus_attach(device_t, device_t, void *);
static int bq4802_gettime_ymdhms(struct todr_chip_handle *,
struct clock_ymdhms *);
static int bq4802_settime_ymdhms(struct todr_chip_handle *,
struct clock_ymdhms *);
static int sysctl_bq4802_osc(SYSCTLFN_ARGS);
CFATTACH_DECL_NEW(bq4802rtc_ebus, sizeof(struct bq4802rtc_softc),
bq4802rtc_ebus_match, bq4802rtc_ebus_attach, NULL, NULL);
#define bq4802_read(sc, reg) \
bus_space_read_1(sc->sc_bst, sc->sc_bsh, reg)
#define bq4802_write(sc, reg, val) \
bus_space_write_1(sc->sc_bst, sc->sc_bsh, reg, val)
static int
bq4802rtc_ebus_match(device_t parent, cfdata_t cf, void *aux)
{
struct ebus_attach_args *ea = aux;
char *compat;
if (strcmp("rtc", ea->ea_name) != 0)
return 0;
compat = prom_getpropstring(ea->ea_node, "compatible");
if (compat != NULL && !strcmp(compat, "bq4802"))
return 2;
return 0;
}
static void
bq4802rtc_ebus_attach(device_t parent, device_t self, void *aux)
{
struct bq4802rtc_softc *sc = device_private(self);
struct ebus_attach_args *ea = aux;
todr_chip_handle_t tch = &sc->sc_tch;
const struct sysctlnode *me = NULL, *node = NULL;
int sz;
uint8_t ctrl, new;
sc->sc_dev = self;
sc->sc_bst = ea->ea_bustag;
sz = ea->ea_reg[0].size;
if (bus_space_map(sc->sc_bst,
EBUS_ADDR_FROM_REG(&ea->ea_reg[0]),
sz, 0,
&sc->sc_bsh) != 0) {
aprint_error(": can't map register\n");
return;
}
aprint_normal(": real time clock\n");
tch->todr_dev = self;
tch->todr_gettime_ymdhms = bq4802_gettime_ymdhms;
tch->todr_settime_ymdhms = bq4802_settime_ymdhms;
ctrl = bq4802_read(sc, BQ4802_CTRL);
if (ctrl & BQ4802_CTRL_STP)
sc->sc_osc_stp = 0;
else {
aprint_error_dev(self,
"WARNING: oscillator is stopped (0x%02x)\n", ctrl);
sc->sc_osc_stp = 1;
}
new = ctrl & ~(BQ4802_CTRL_DSE | BQ4802_CTRL_UTI);
new = new | BQ4802_CTRL_24;
if (new != ctrl)
bq4802_write(sc, BQ4802_CTRL, ctrl);
todr_attach(tch);
sysctl_createv(NULL, 0, NULL, &me,
CTLFLAG_READWRITE,
CTLTYPE_NODE, device_xname(self), NULL,
NULL, 0, NULL, 0,
CTL_HW, CTL_CREATE, CTL_EOL);
if (me == NULL)
aprint_error_dev(self, "unable to add sysctl root\n");
else {
sysctl_createv(NULL, 0, NULL, &node,
CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
CTLTYPE_INT, "stop_oscillator", "Stop the chip oscillator",
sysctl_bq4802_osc, 1, (void *)sc, 0,
CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL);
if (node == NULL)
aprint_error_dev(self, "unable to add sysctl node\n");
}
}
static int
bq4802_gettime_ymdhms(struct todr_chip_handle *todrch, struct clock_ymdhms *dt)
{
struct bq4802rtc_softc *sc = device_private(todrch->todr_dev);
bq4802_regs regs;
uint64_t val;
int s;
s = splclock();
BQ4802_GETTOD(sc, ®s);
splx(s);
dt->dt_sec = bcdtobin(regs[BQ4802_SEC]);
dt->dt_min = bcdtobin(regs[BQ4802_MIN]);
val = bcdtobin(regs[BQ4802_HOUR]);
if (val == 24)
val = 0;
dt->dt_hour = val;
dt->dt_wday = bcdtobin(regs[BQ4802_WDAY]);
dt->dt_day = bcdtobin(regs[BQ4802_DAY]);
dt->dt_mon = bcdtobin(regs[BQ4802_MONTH]);
val = bcdtobin(regs[BQ4802_YEAR]);
val += bcdtobin(regs[BQ4802_CENT]) * 100;
dt->dt_year = val;
return 0;
}
static int
bq4802_settime_ymdhms(struct todr_chip_handle *todrch, struct clock_ymdhms *dt)
{
struct bq4802rtc_softc *sc = device_private(todrch->todr_dev);
bq4802_regs regs;
uint8_t val;
int s;
regs[BQ4802_SEC] = bintobcd(dt->dt_sec);
regs[BQ4802_MIN] = bintobcd(dt->dt_min);
if (dt->dt_hour == 0)
val = 24;
else
val = dt->dt_hour;
regs[BQ4802_HOUR] = bintobcd(val);
regs[BQ4802_WDAY] = bintobcd(dt->dt_wday);
regs[BQ4802_DAY] = bintobcd(dt->dt_day);
regs[BQ4802_MONTH] = bintobcd(dt->dt_mon);
regs[BQ4802_YEAR] = bintobcd(dt->dt_year % 100);
regs[BQ4802_CENT] = bintobcd(dt->dt_year / 100);
s = splclock();
BQ4802_SETTOD(sc, ®s);
splx(s);
return 0;
}
static int
sysctl_bq4802_osc(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
struct bq4802rtc_softc *sc = node.sysctl_data;
int stp;
uint8_t ctrl;
if (newp) {
stp = sc->sc_osc_stp;
node.sysctl_data = &stp;
if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {
if (stp != 0 && stp != 1)
return EINVAL;
if (stp != sc->sc_osc_stp) {
sc->sc_osc_stp = stp;
ctrl = bq4802_read(sc, BQ4802_CTRL);
if (stp)
ctrl &= ~BQ4802_CTRL_STP;
else
ctrl |= BQ4802_CTRL_STP;
bq4802_write(sc, BQ4802_CTRL, ctrl);
}
return 0;
}
return EINVAL;
} else {
node.sysctl_data = &sc->sc_osc_stp;
node.sysctl_size = 4;
return (sysctl_lookup(SYSCTLFN_CALL(&node)));
}
return 0;
}