--- pcf8583.c.orig 2025-09-07 22:45:15.000000000 +0100 +++ pcf8583.c 2026-09-28 23:16:04.423791873 +0100 @@ -40,6 +40,15 @@ * * This driver is partially derived from Ben Harris's PCF8583 driver * for NetBSD/acorn26. + * + * Additional code written by Dave Tyson to support dual addressing 0x50/0x51 + * and to select chip operating mode using optional flag in kernel config. + * Default with no flag set or flag 0 is existing tod clock mode, flag 1 sets + * 50hz input (tod clock), flag 2 sets counter mode, flag 3 sets test mode. + * + * Setting counter mode provides an envsys variable mapped to the counter and + * exposes a hw syctl variable which can set to 1 to zero the counter registers + * after the next envstat call. */ #include @@ -49,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +69,7 @@ #include #include #include +#include #include "ioconf.h" @@ -68,10 +79,15 @@ int sc_address; int sc_open; struct todr_chip_handle sc_todr; + struct sysmon_envsys *sc_sme; + envsys_data_t sc_counter; + struct sysctllog *sc_pcfrtclog; + int reset_counter; }; static int pcfrtc_match(device_t, cfdata_t, void *); static void pcfrtc_attach(device_t, device_t, void *); +static int pcfrtc_sysctl_setup(struct pcfrtc_softc *sc); CFATTACH_DECL_NEW(pcfrtc, sizeof(struct pcfrtc_softc), pcfrtc_match, pcfrtc_attach, NULL, NULL); @@ -102,13 +118,16 @@ uint8_t); static int pcfrtc_gettime(struct todr_chip_handle *, struct timeval *); static int pcfrtc_settime(struct todr_chip_handle *, struct timeval *); +static void pcfrtc_readcount(struct sysmon_envsys *, envsys_data_t *) ; +static int pcfrtc_sysctl_check(SYSCTLFN_ARGS); int pcfrtc_match(device_t parent, cfdata_t cf, void *aux) { struct i2c_attach_args *ia = aux; - if ((ia->ia_addr & PCF8583_ADDRMASK) == PCF8583_ADDR) + if ((ia->ia_addr & PCF8583_ADDRMASK) == PCF8583_ADDR || + (ia->ia_addr & PCF8583_ADDRMASK) == PCF8583_ADDR2) return (I2C_MATCH_ADDRESS_ONLY); return (0); @@ -118,53 +137,104 @@ pcfrtc_attach(device_t parent, device_t self, void *aux) { struct pcfrtc_softc *sc = device_private(self); + struct cfdata *cf = device_cfdata(self); struct i2c_attach_args *ia = aux; - uint8_t cmdbuf[1], csr; + uint8_t cmdbuf[1], csr, mode; + int error; sc->sc_tag = ia->ia_tag; sc->sc_address = ia->ia_addr; sc->sc_dev = self; - aprint_naive(": Real-time Clock/NVRAM\n"); - aprint_normal(": PCF8583 Real-time Clock/NVRAM\n"); + /* if flags set & !zero then need to change device mode */ + if (cf->cf_flags != 0) { + mode = cf->cf_flags << 4 & PCF8583_CSR_FN_MASK; + cmdbuf[0] = PCF8583_REG_CSR; + if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_address, + cmdbuf, 1, &mode, 1, 0) != 0) { + aprint_error_dev(self, "unable to write CSR\n"); + return; + } + } + cmdbuf[0] = PCF8583_REG_CSR; if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_address, cmdbuf, 1, &csr, 1, 0) != 0) { aprint_error_dev(self, "unable to read CSR\n"); return; } - aprint_normal_dev(sc->sc_dev, ""); + switch (csr & PCF8583_CSR_FN_MASK) { case PCF8583_CSR_FN_32768HZ: - aprint_normal(" 32.768 kHz clock"); + aprint_naive(": Mode 0: 32768 Hz tod Clock"); + aprint_normal(": PCF8583 Mode 0: 32768 Hz tod Clock"); break; case PCF8583_CSR_FN_50HZ: - aprint_normal(" 50 Hz clock"); + aprint_naive(": Mode 1: 50Hz tod Clock"); + aprint_normal(": PCF8583 Mode 1: 50Hz tod Clock"); break; case PCF8583_CSR_FN_EVENT: - aprint_normal(" event counter"); + aprint_naive(": Mode 2: Counter"); + aprint_normal(": PCF8583 Mode 2: Counter"); break; case PCF8583_CSR_FN_TEST: - aprint_normal(" test mode"); + aprint_naive(": Mode 3: Test mode"); + aprint_normal(": PCF8583 Mode 3: Test mode"); break; } - if (csr & PCF8583_CSR_STOP) - aprint_normal(", stopped"); - if (csr & PCF8583_CSR_ALARMENABLE) - aprint_normal(", alarm enabled"); aprint_normal("\n"); sc->sc_open = 0; - sc->sc_todr.todr_dev = self; - sc->sc_todr.todr_gettime = pcfrtc_gettime; - sc->sc_todr.todr_settime = pcfrtc_settime; + switch (csr & PCF8583_CSR_FN_MASK) { + + case PCF8583_CSR_FN_32768HZ: + case PCF8583_CSR_FN_50HZ: + sc->sc_todr.todr_dev = self; + sc->sc_todr.todr_gettime = pcfrtc_gettime; + sc->sc_todr.todr_settime = pcfrtc_settime; + + todr_attach(&sc->sc_todr); + break; - todr_attach(&sc->sc_todr); + case PCF8583_CSR_FN_EVENT: + if ((sc->sc_sme = sysmon_envsys_create()) == NULL) { + aprint_error_dev(sc->sc_dev, + "unable to create sysmon structure"); + sc->sc_sme = NULL; + return ; + } + sc->sc_counter.units=ENVSYS_INTEGER; + sc->sc_counter.state=ENVSYS_SINVALID; + strlcpy(sc->sc_counter.desc, "counter", + sizeof(sc->sc_counter.desc)); + if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_counter)) { + sysmon_envsys_destroy(sc->sc_sme); + return; + } + + sc->sc_sme->sme_name = device_xname(self) ; + sc->sc_sme->sme_refresh = pcfrtc_readcount; + sc->sc_sme->sme_cookie = sc; + + if (sysmon_envsys_register(sc->sc_sme)) { + sysmon_envsys_destroy(sc->sc_sme); + return; + } + + /* setup sysctl for resetting counter */ + + if ((error = pcfrtc_sysctl_setup(sc)) !=0) { + aprint_error_dev(sc->sc_dev, "cannot setup sysctl tree (%d)\n", error); + sysmon_envsys_destroy(sc->sc_sme); + } + sc->reset_counter = 0; + break ; + } } /*ARGSUSED*/ @@ -499,3 +569,96 @@ iic_release_bus(tag, 0); return (0); } + +int pcfrtc_sysctl_check(SYSCTLFN_ARGS) +{ + struct sysctlnode node; + int t, error; + + t = *(int *)rnode->sysctl_data; + + node = *rnode; + node.sysctl_data = &t; + error = sysctl_lookup(SYSCTLFN_CALL(&node)); + if (error || newp == NULL) + return (error); + + if (t < 0 || t > 1) + return (EINVAL); + + *(int *)rnode->sysctl_data = t; + return 0; +} + +static int pcfrtc_sysctl_setup(struct pcfrtc_softc *sc) +{ + int error; + const struct sysctlnode *cnode; + int sysctlroot_num; + + if ((error = sysctl_createv(&sc->sc_pcfrtclog, 0, NULL, &cnode, + 0, CTLTYPE_NODE, device_xname(sc->sc_dev), + SYSCTL_DESCR("pcfrtc counter reset"), NULL, 0, NULL, 0, + CTL_HW, CTL_CREATE, CTL_EOL)) != 0) + return error; + sysctlroot_num = cnode->sysctl_num; + + if ((error = sysctl_createv(&sc->sc_pcfrtclog, 0, NULL, &cnode, + CTLFLAG_READWRITE, CTLTYPE_INT, "counter_reset", + SYSCTL_DESCR("counter reset"), pcfrtc_sysctl_check, 0, + &sc->reset_counter, 0, CTL_HW, + sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0) + return error; + return 0; +} + +static void pcfrtc_readcount(struct sysmon_envsys *sme, envsys_data_t *edata) +{ + struct pcfrtc_softc *sc = sme->sme_cookie; + int err, i; + uint8_t bcd[3],cmdbuf[1]; + uint8_t zero=0; + + if ((err = iic_acquire_bus(sc->sc_tag, 0))) { + aprint_error_dev(sc->sc_dev, + "pcfrtc_readcount: failed to acquire I2C bus\n"); + return; + } + + for (i = PCF8583_COUNTER0; i <= PCF8583_COUNTER2; i++) { + cmdbuf[0] = i; + + if ((err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, + sc->sc_address, cmdbuf, 1, + &bcd[i-1], 1, 0))) { + iic_release_bus(sc->sc_tag, 0); + aprint_error_dev(sc->sc_dev, + "pcfrtc_readcount: failed to read register at %x\n", i); + return; + } + } + + + edata->value_cur = bcdtobin(bcd[0]) + bcdtobin(bcd[1])*100 + bcdtobin(bcd[2])*10000 ; + edata->state = ENVSYS_SVALID ; + + /* if reset_counter = 1 then clear totals */ + + if (sc->reset_counter != 0) { + for (i = PCF8583_COUNTER0; i <= PCF8583_COUNTER2; i++) { + cmdbuf[0] = i; + + if ((err = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, + sc->sc_address, cmdbuf, 1, &zero, 1, 0))) { + iic_release_bus(sc->sc_tag, 0); + aprint_error_dev(sc->sc_dev, + "pcfrtc_readcount: failed to write register at %x\n", i); + return; + } + } + + sc->reset_counter = 0 ; + } + + iic_release_bus(sc->sc_tag, 0); +} --- pcf8583reg.h.orig 2013-08-07 20:38:45.000000000 +0100 +++ pcf8583reg.h 2026-09-28 14:43:58.730694422 +0100 @@ -22,6 +22,7 @@ */ #define PCF8583_ADDRMASK 0x3ff #define PCF8583_ADDR 0x50 +#define PCF8583_ADDR2 0x51 #define PCF8583_REG_CSR 0x00 #define PCF8583_REG_CENTI 0x01 @@ -86,4 +87,7 @@ #define PCF8583_ALMCTL_TIMERALM 0x40 #define PCF8583_ALMCTL_ALMINT 0x80 +#define PCF8583_COUNTER0 0x01 +#define PCF8583_COUNTER1 0x02 +#define PCF8583_COUNTER2 0x03 #endif /* _PCF8583REG_H */ --- /dev/null 2026-09-29 18:43:11.562089412 +0100 +++ pcf8583.4 2026-09-28 23:11:49.983542646 +0100 @@ -0,0 +1,79 @@ +.\" +.\" Copyright (c) 2026 Dave Tyson +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd October 1, 2026 +.Dt PCF8583 +.Os +.Sh NAME +.Nm pcf8583 +.Nd Driver for NPX PCF8583 Clock and Calendar/Counter/240 x 8bit RAM +via I2C bus +.Sh SYNOPSIS +.Cd "pcfrtc* at iic? addr 0x50" +.Cd "pcfrtc* at iic? addr 0x50 flags x" +.Cd "pcfrtc* at iic? addr 0x51" +.Cd "pcfrtc* at iic? addr 0x51 flags x" +.Sh DESCRIPTION +The +.Nm +driver supports the PCF8583 multifunction chip which can operate in one of four modes +depending on the value of the flag [if specified]: +.Bl -tag +.It mode 0 - Clock & Calendar using a 32768 hz crystal. +This is the default mode in the absence of a flags field and is the state when a +power-on reset occurs. +.It mode 1 - Clock & Calendar using external 50 Hz clock. +Similar to mode 0, but relies on an external clock source rather than a crystal. +.It mode 2 - Counter for external inputs. +The clock & calendar are disabled and the device counts external events using a 3 byte +bcd counter [0-999999]. +.It mode 3 - Test mode. +This is undocumented and unlikely to be useful. +.El +.Pp +The +.Nm +driver is commonly used to support a Real Time Clock using a 32768 hz crystal and a backup battery. It also provides a small amount of battery backed RAM which is used, for example, for bootstrapping acorn32 system. It is possible to use an external 50hz clock by switching to mode 1 - this might be useful with a European mains derived signal as the long term drift is likely to be less than an unstabilised crystal. Access methods to retrieve and set date and time are provided through the +.Em TODR +interface defined in +.Xr todr 9 . +.Pp +Switching to mode 2 provides access to a 6 digit BCD counter which can be used to count arbitary external events. The driver maps this counter to an integer envstat variable which can be read via the normal envstat subsystem and also exposes a sysctl variable to allow the counter to be reset. +.Sh SYSCTL VARIABLES +In mode 2 the following +.Xr sysctl 3 +variables are provided: +.Bl -tag -width indent +.It Li hw.pcfrtc1.counter_reset +If this is set to 1 then the next read of the envstat counter causes the BCD counter to be zeroed and resets the sysctl variable back to zero. +.Sh SEE ALSO +.Xr todr 9 , +.Xr envstat 8 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +driver first appeared in +.Nx 1.0 . +.Sh BUGS +The driver does not support the chip's alarm and timer features. +.Sh AUTHORS +The +.Nm +driver was written by Steve C. Woodford and Jason R. Thorpe for Wasabi Systems, Inc. based on Ben Harris's PCF8583 driver for NetBSD/acorn26. +.Pp +Subsequent modifications to support other modes by +.An Dave Tyson Aq Mt dtyson@anduin.org.uk +