From owner-svn-src-stable-12@freebsd.org Sun Aug 11 22:31:38 2019 Return-Path: Delivered-To: svn-src-stable-12@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id C839AC475C; Sun, 11 Aug 2019 22:31:38 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 466DGy5b7xz4Tdj; Sun, 11 Aug 2019 22:31:38 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id A2A991D1BC; Sun, 11 Aug 2019 22:31:38 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id x7BMVca9031539; Sun, 11 Aug 2019 22:31:38 GMT (envelope-from ian@FreeBSD.org) Received: (from ian@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id x7BMVcGT031538; Sun, 11 Aug 2019 22:31:38 GMT (envelope-from ian@FreeBSD.org) Message-Id: <201908112231.x7BMVcGT031538@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: ian set sender to ian@FreeBSD.org using -f From: Ian Lepore Date: Sun, 11 Aug 2019 22:31:38 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-12@freebsd.org Subject: svn commit: r350875 - stable/12/sys/dev/iicbus X-SVN-Group: stable-12 X-SVN-Commit-Author: ian X-SVN-Commit-Paths: stable/12/sys/dev/iicbus X-SVN-Commit-Revision: 350875 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-stable-12@freebsd.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: SVN commit messages for only the 12-stable src tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 11 Aug 2019 22:31:38 -0000 Author: ian Date: Sun Aug 11 22:31:38 2019 New Revision: 350875 URL: https://svnweb.freebsd.org/changeset/base/350875 Log: MFC r350104, r350106, r350185, r350203 r350104: Handle the PCF2127 RTC chip the same as PCF2129 when init'ing the chip. This affects the detection of 24-hour vs AM/PM mode... the ampm bit is in a different location on 2127 and 2129 chips compared to other nxp rtc chips. I noticed the 2127 case wasn't being handled correctly when I accidentally misconfiged my system by claiming my PCF2129 was a 2127. r350106: Fix a paste-o, set is212x = false for other chip types. Doh! r350185: Rewrite the nxprtc chip init to extend battery life by using power-saving features offered by the chips. For 2127 and 2129 chips, fix the detection of when chip-init is needed. The chip config needs to be reset whenever power was lost, but the logic was wrong for 212x chips (it only worked for 8523). Now the "oscillator stopped" bit rather than the power manager mode is used to detect startup after powerfail. For all chips, disable the clock output pin. For chips that have a timestamp/tamper-monitor feature, turn off monitoring of the timestamp trigger pin. The 8523, 2127, and 2129 chips have a "power manager" feature that offers several options. We've been using the default mode which enables everything. Now the code sets the power manager options to - direct-switch (when Vdd < Vbat, without extra threshold check) - no battery monitor - no external powerfail monitor This reduces the current draw while running on battery from 1930nA to 880nA, which should roughly double the lifespan of the battery under load. Because battery checking is a nice thing to have, the code now does a check at startup, and then once a day after that, instead of checking continuously (but only actually reporting at startup). The battery check is now done by setting the power manager back to default mode, sleeping briefly while it makes a voltage measurement, then switching back to power-saving mode. r350203: Add support for setting the aging/frequency-offset register via sysctl. The 2127 and 2129 chips support a frequency tuning value in the range of -7 through +8 PPM; add a sysctl handler to read and set the value. Modified: stable/12/sys/dev/iicbus/nxprtc.c Directory Properties: stable/12/ (props changed) Modified: stable/12/sys/dev/iicbus/nxprtc.c ============================================================================== --- stable/12/sys/dev/iicbus/nxprtc.c Sun Aug 11 22:19:54 2019 (r350874) +++ stable/12/sys/dev/iicbus/nxprtc.c Sun Aug 11 22:31:38 2019 (r350875) @@ -60,6 +60,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include @@ -106,6 +107,11 @@ __FBSDID("$FreeBSD$"); #define PCF2127_B_TMR_CD 0x40 /* Run in countdown mode */ #define PCF2127_B_TMR_64HZ 0x01 /* Timer frequency 64Hz */ +#define PCF2127_R_TS_CTL 0x12 /* Timestamp control */ +#define PCF2127_B_TSOFF 0x40 /* Turn off timestamp function */ + +#define PCF2127_R_AGING_OFFSET 0x19 /* Frequency aging offset in PPM */ + /* * PCA/PCF2129-specific registers, bits, and masks. */ @@ -134,12 +140,16 @@ __FBSDID("$FreeBSD$"); #define PCF8523_M_CS3_PM 0xE0 /* Power mode mask */ #define PCF8523_B_CS3_PM_NOBAT 0xE0 /* PM bits: no battery usage */ #define PCF8523_B_CS3_PM_STD 0x00 /* PM bits: standard */ +#define PCF8523_B_CS3_PM_DSNBM 0xa0 /* PM bits: direct switch, no bat mon */ #define PCF8523_B_CS3_BLF 0x04 /* Battery Low Flag bit */ /* * PCF8563-specific registers, bits, and masks. */ #define PCF8563_R_SECOND 0x02 /* Seconds */ + +#define PCF8563_R_CLKOUT 0x0d /* Clock output control */ + #define PCF8563_R_TMR_CTRL 0x0e /* Timer control */ #define PCF8563_R_TMR_COUNT 0x0f /* Timer count */ @@ -196,10 +206,13 @@ struct nxprtc_softc { config_hook; u_int flags; /* SC_F_* flags */ u_int chiptype; /* Type of PCF85xx chip */ + time_t bat_time; /* Next time to check battery */ + int freqadj; /* Current freq adj in PPM */ uint8_t secaddr; /* Address of seconds register */ uint8_t tmcaddr; /* Address of timer count register */ bool use_timer; /* Use timer for fractional sec */ bool use_ampm; /* Chip is set to use am/pm mode */ + bool is212x; /* Chip type is 2127 or 2129 */ }; #define SC_F_CPOL (1 << 0) /* Century bit means 19xx */ @@ -347,99 +360,197 @@ write_timeregs(struct nxprtc_softc *sc, struct time_re } static int -pcf8523_start(struct nxprtc_softc *sc) +freqadj_sysctl(SYSCTL_HANDLER_ARGS) { + struct nxprtc_softc *sc; + int err, freqppm, newppm; + + sc = arg1; + + /* PPM range [-7,8] maps to reg value range [0,15] */ + freqppm = newppm = 8 - sc->freqadj; + + err = sysctl_handle_int(oidp, &newppm, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (freqppm != newppm) { + if (newppm < -7 || newppm > 8) + return (EINVAL); + sc->freqadj = 8 - newppm; + err = write_reg(sc, PCF2127_R_AGING_OFFSET, sc->freqadj); + } + + return (err); +} + +static int +pcf8523_battery_check(struct nxprtc_softc *sc) +{ + struct timespec ts; int err; - uint8_t cs1, cs3, clkout; - bool is2129; + uint8_t cs3; - is2129 = (sc->chiptype == TYPE_PCA2129 || sc->chiptype == TYPE_PCF2129); + /* We check the battery when starting up, and then only once a day. */ + getnanouptime(&ts); + if (ts.tv_sec < sc->bat_time) + return (0); + sc->bat_time = ts.tv_sec + (60 * 60 * 24); - /* Read and sanity-check the control registers. */ - if ((err = read_reg(sc, PCF85xx_R_CS1, &cs1)) != 0) { - device_printf(sc->dev, "cannot read RTC CS1 control\n"); + /* + * The 8523, 2127, and 2129 chips have a "power manager" which includes + * an optional battery voltage monitor and several choices for power + * switching modes. The battery monitor uses a lot of current and it + * remains active when running from battery, making it the "drain my + * battery twice as fast" mode. So, we run the chip in direct-switching + * mode with the battery monitor disabled, reducing the current draw + * when running on battery from 1930nA to 880nA. While it's not clear + * from the datasheets, empirical testing shows that both disabling the + * battery monitor and using direct-switch mode are required to get the + * full power savings. + * + * There isn't any need to continuously monitor the battery voltage, so + * this function is used to periodically enable the monitor, check the + * voltage, then return to the low-power monitor-disabled mode. + */ + err = write_reg(sc, PCF8523_R_CS3, PCF8523_B_CS3_PM_STD); + if (err != 0) { + device_printf(sc->dev, "cannot write CS3 reg\n"); return (err); } + pause_sbt("nxpbat", mstosbt(10), 0, 0); if ((err = read_reg(sc, PCF8523_R_CS3, &cs3)) != 0) { - device_printf(sc->dev, "cannot read RTC CS3 control\n"); + device_printf(sc->dev, "cannot read CS3 reg\n"); return (err); } + err = write_reg(sc, PCF8523_R_CS3, PCF8523_B_CS3_PM_DSNBM); + if (err != 0) { + device_printf(sc->dev, "cannot write CS3 reg\n"); + return (err); + } + if (cs3 & PCF8523_B_CS3_BLF) + device_printf(sc->dev, "WARNING: RTC battery is low\n"); + + return (0); +} + +static int +pcf8523_start(struct nxprtc_softc *sc) +{ + struct sysctl_ctx_list *ctx; + struct sysctl_oid_list *tree; + struct csr { + uint8_t cs1; + uint8_t cs2; + uint8_t cs3; + uint8_t sec; + } csr; + int err; + uint8_t clkout, freqadj; + + /* Read the control and status registers. */ + if ((err = nxprtc_readfrom(sc->dev, PCF85xx_R_CS1, &csr, + sizeof(csr), WAITFLAGS)) != 0){ + device_printf(sc->dev, "cannot read RTC control regs\n"); + return (err); + } + /* - * Do a full init (soft-reset) if... - * - The chip is in battery-disable mode (fresh from the factory). + * Do a full init if... + * - The chip power manager is in battery-disable mode. + * - The OS (oscillator stopped) bit is set (all power was lost). * - The clock-increment STOP flag is set (this is just insane). - * After reset, battery disable mode has to be overridden to "standard" - * mode. Also, turn off clock output to save battery power. */ - if ((cs3 & PCF8523_M_CS3_PM) == PCF8523_B_CS3_PM_NOBAT || - (cs1 & PCF85xx_B_CS1_STOP)) { - cs1 = PCF8523_B_CS1_SOFTRESET; - if ((err = write_reg(sc, PCF85xx_R_CS1, cs1)) != 0) { - device_printf(sc->dev, "cannot write CS1 control\n"); - return (err); - } - cs3 = PCF8523_B_CS3_PM_STD; - if ((err = write_reg(sc, PCF8523_R_CS3, cs3)) != 0) { - device_printf(sc->dev, "cannot write CS3 control\n"); - return (err); - } + if ((csr.cs3 & PCF8523_M_CS3_PM) == PCF8523_B_CS3_PM_NOBAT || + (csr.cs1 & PCF85xx_B_CS1_STOP) || (csr.sec & PCF85xx_B_SECOND_OS)) { + device_printf(sc->dev, + "WARNING: RTC battery failed; time is invalid\n"); + /* - * For 2129 series, trigger OTP refresh by forcing the OTPR bit - * to zero then back to 1, then wait 100ms for the refresh, and - * finally set the bit back to zero with the COF_HIGHZ write. + * For 212x series... + * - Turn off the POR-Override bit (used for mfg test only), + * by writing zero to cs 1 (all other bits power on as zero). + * - Turn off the timestamp option to save the power used to + * monitor that input pin. + * - Trigger OTP refresh by forcing the OTPR bit to zero then + * back to 1, then wait 100ms for the refresh. */ - if (is2129) { - clkout = PCF2129_B_CLKOUT_HIGHZ; - if ((err = write_reg(sc, PCF8523_R_TMR_CLKOUT, - clkout)) != 0) { + if (sc->is212x) { + err = write_reg(sc, PCF85xx_R_CS1, 0); + if (err != 0) { device_printf(sc->dev, - "cannot write CLKOUT control\n"); + "cannot write CS1 reg\n"); return (err); } - if ((err = write_reg(sc, PCF8523_R_TMR_CLKOUT, - clkout | PCF2129_B_CLKOUT_OTPR)) != 0) { + + err = write_reg(sc, PCF2127_R_TS_CTL, PCF2127_B_TSOFF); + if (err != 0) { device_printf(sc->dev, + "cannot write timestamp control\n"); + return (err); + } + + clkout = PCF2129_B_CLKOUT_HIGHZ; + err = write_reg(sc, PCF8523_R_TMR_CLKOUT, clkout); + if (err == 0) + err = write_reg(sc, PCF8523_R_TMR_CLKOUT, + clkout | PCF2129_B_CLKOUT_OTPR); + if (err != 0) { + device_printf(sc->dev, "cannot write CLKOUT control\n"); return (err); } pause_sbt("nxpotp", mstosbt(100), mstosbt(10), 0); } else clkout = PCF8523_B_CLKOUT_HIGHZ; + + /* All chips: set clock output pin to high-z to save power */ if ((err = write_reg(sc, PCF8523_R_TMR_CLKOUT, clkout)) != 0) { device_printf(sc->dev, "cannot write CLKOUT control\n"); return (err); } - device_printf(sc->dev, - "first time startup, enabled RTC battery operation\n"); + } - /* - * Sleep briefly so the battery monitor can make a measurement, - * then re-read CS3 so battery-low status can be reported below. - */ - pause_sbt("nxpbat", mstosbt(100), 0, 0); - if ((err = read_reg(sc, PCF8523_R_CS3, &cs3)) != 0) { - device_printf(sc->dev, "cannot read RTC CS3 control\n"); + /* + * Check the battery voltage and report if it's low. This also has the + * side effect of (re-)initializing the power manager to low-power mode + * when we come up after a power fail. + */ + pcf8523_battery_check(sc); + + /* + * Remember whether we're running in AM/PM mode. The chip default is + * 24-hour mode, but if we're co-existing with some other OS that + * prefers AM/PM we can run that way too. + * + * Also, for 212x chips, retrieve the current frequency aging offset, + * and set up the sysctl handler for reading/setting it. + */ + if (sc->is212x) { + if (csr.cs1 & PCF2129_B_CS1_12HR) + sc->use_ampm = true; + + err = read_reg(sc, PCF2127_R_AGING_OFFSET, &freqadj); + if (err != 0) { + device_printf(sc->dev, + "cannot read AGINGOFFSET register\n"); return (err); } - } + sc->freqadj = (int8_t)freqadj; - /* Let someone know if the battery is weak. */ - if (cs3 & PCF8523_B_CS3_BLF) - device_printf(sc->dev, "WARNING: RTC battery is low\n"); + ctx = device_get_sysctl_ctx(sc->dev); + tree = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)); - /* Remember whether we're running in AM/PM mode. */ - if (is2129) { - if (cs1 & PCF2129_B_CS1_12HR) - sc->use_ampm = true; + SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "freqadj", + CTLFLAG_RWTUN | CTLTYPE_INT | CTLFLAG_MPSAFE, sc, 0, + freqadj_sysctl, "I", "Frequency adjust in PPM, range [-7,+8]"); } else { - if (cs1 & PCF8523_B_CS1_12HR) + if (csr.cs1 & PCF8523_B_CS1_12HR) sc->use_ampm = true; } return (0); } - static int pcf8523_start_timer(struct nxprtc_softc *sc) { @@ -500,6 +611,52 @@ pcf2127_start_timer(struct nxprtc_softc *sc) } static int +pcf8563_start(struct nxprtc_softc *sc) +{ + struct csr { + uint8_t cs1; + uint8_t cs2; + uint8_t sec; + } csr; + int err; + + /* Read the control and status registers. */ + if ((err = nxprtc_readfrom(sc->dev, PCF85xx_R_CS1, &csr, + sizeof(csr), WAITFLAGS)) != 0){ + device_printf(sc->dev, "cannot read RTC control regs\n"); + return (err); + } + + /* + * Do a full init if... + * - The OS (oscillator stopped) bit is set (all power was lost). This + * bit is called VL (Voltage Low) in the 8563 datasheet. + * - The clock-increment STOP flag is set (this is just insane). + */ + if ((csr.cs1 & PCF85xx_B_CS1_STOP) || (csr.sec & PCF85xx_B_SECOND_OS)) { + device_printf(sc->dev, + "WARNING: RTC battery failed; time is invalid\n"); + /* + * - Turn off the POR-Override bit (used for mfg test only), by + * writing zero to cs 1 (all other bits power on as zero). + * - Turn off the clock output pin (to save battery power), by + * writing zero to the clkout control reg. + */ + if ((err = write_reg(sc, PCF85xx_R_CS1, 0)) != 0) { + device_printf(sc->dev, "cannot write CS1 reg\n"); + return (err); + } + + if ((err = write_reg(sc, PCF8563_R_CLKOUT, 0)) != 0) { + device_printf(sc->dev, "cannot write CS1 reg\n"); + return (err); + } + } + + return (0); +} + +static int pcf8563_start_timer(struct nxprtc_softc *sc) { int err; @@ -525,7 +682,6 @@ nxprtc_start(void *dev) { struct nxprtc_softc *sc; int clockflags, resolution; - uint8_t sec; sc = device_get_softc((device_t)dev); config_intrhook_disestablish(&sc->config_hook); @@ -535,6 +691,7 @@ nxprtc_start(void *dev) case TYPE_PCA2129: case TYPE_PCF2129: case TYPE_PCF2127: + sc->is212x = true; if (pcf8523_start(sc) != 0) return; if (pcf2127_start_timer(sc) != 0) { @@ -552,6 +709,8 @@ nxprtc_start(void *dev) break; case TYPE_PCA8565: case TYPE_PCF8563: + if (pcf8563_start(sc) != 0) + return; if (pcf8563_start_timer(sc) != 0) { device_printf(sc->dev, "cannot set up timer\n"); return; @@ -563,19 +722,6 @@ nxprtc_start(void *dev) } /* - * Common init. Read the seconds register so we can check the - * oscillator-stopped status bit in it. - */ - if (read_reg(sc, sc->secaddr, &sec) != 0) { - device_printf(sc->dev, "cannot read RTC seconds\n"); - return; - } - if ((sec & PCF85xx_B_SECOND_OS) != 0) { - device_printf(sc->dev, - "WARNING: RTC battery failed; time is invalid\n"); - } - - /* * Everything looks good if we make it to here; register as an RTC. If * we're using the timer to count fractional seconds, our resolution is * 1e6/64, about 15.6ms. Without the timer we still align the RTC clock @@ -722,6 +868,12 @@ nxprtc_settime(device_t dev, struct timespec *ts) cs1 &= ~PCF85xx_B_CS1_STOP; err = write_reg(sc, PCF85xx_R_CS1, cs1); + + /* + * Check for battery-low. The actual check is throttled to only occur + * once a day, mostly to avoid spamming the user with frequent warnings. + */ + pcf8523_battery_check(sc); errout: