Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 11 Aug 2019 22:31:38 +0000 (UTC)
From:      Ian Lepore <ian@FreeBSD.org>
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
Message-ID:  <201908112231.x7BMVcGT031538@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
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 <sys/kernel.h>
 #include <sys/libkern.h>
 #include <sys/module.h>
+#include <sys/sysctl.h>
 
 #include <dev/iicbus/iicbus.h>
 #include <dev/iicbus/iiconf.h>
@@ -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:
 



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201908112231.x7BMVcGT031538>