DS3231 on BeagleBone Black with FreeBSD 13-CURRENT exactly 20 h off backwards

Dr. Rolf Jansen freebsd-rj at obsigna.com
Thu Jul 16 15:36:06 UTC 2020


> Am 15.07.2020 um 23:32 schrieb Ian Lepore <ian at freebsd.org>:
> 
> On Wed, 2020-07-15 at 12:15 -0300, Dr. Rolf Jansen wrote:
>>> Am 15.07.2020 um 09:52 schrieb Dr. Rolf Jansen <
>>> freebsd-rj at obsigna.com <mailto:freebsd-rj at obsigna.com>>:
>>> 
>>> I added a DS3231 module to the i2c2 bus of the BBB running 13-
>>> CURRENT. Everything  work fine, except that when I set a time in
>>> the range of 20:00 to 24:00 UTC, then on starting up the RTC
>>> reports a date/time of exactly 20 hours off backwards. While, when
>>> I set a time in the range from 0:00 to 19:59 UTC, it would be
>>> correctly stored by the RTC.
>>> 
>>> Looking at the Maxim DS3231 datasheet (
>>> https://datasheets.maximintegrated.com/en/ds/DS3231.pdf#page=11 <https://datasheets.maximintegrated.com/en/ds/DS3231.pdf#page=11> <
>>> https://datasheets.maximintegrated.com/en/ds/DS3231.pdf#page=11 <https://datasheets.maximintegrated.com/en/ds/DS3231.pdf#page=11>>),
>>> it might be that something gets mixed-up when setting bits 5 and 6
>>> of the hours register. In the history of ds3231.c, I saw that 24
>>> hour mode is not more forced anymore. Perhaps an unresolved
>>> ambiguity was introduced by this change.
>>> 
>>> BTW: the following looks strange:
>>> 
>>> 
> https://github.com/freebsd/freebsd/blob/b2d136be8c26e5efaf82b7bb25432207a682e250/sys/dev/iicbus/ds3231.c#L526 <https://github.com/freebsd/freebsd/blob/b2d136be8c26e5efaf82b7bb25432207a682e250/sys/dev/iicbus/ds3231.c#L526>
>>> <
>>> https://github.com/freebsd/freebsd/blob/b2d136be8c26e5efaf82b7bb25432207a682e250/sys/dev/iicbus/ds3231.c#L526 <https://github.com/freebsd/freebsd/blob/b2d136be8c26e5efaf82b7bb25432207a682e250/sys/dev/iicbus/ds3231.c#L526>
>>>> 
>>> 
>>> This would add 256 instead of 100 when rolling over the century,
>>> really? On real world systems this will let to a Year-2100 problem,
>>> better we solve this quickly, since the time is running, and 80
>>> years compares to nothing on the geologic time scale :-D
>> 
>> For the time being I resolved the issue for me, by completely
>> dropping AM/PM support - I don’t need it, and I anyway always need to
>> remember that AM means (Am Morgen :-).
>> 
>> DS3231_HOUR_MASK_24HR is definitely wrong, since this prevents the
>> setting of times above 20 h. Reading said data sheet, I am almost
>> sure, that DS3231_HOUR_MASK_24HR must be the same as
>> DS3231_HOUR_MASK_12HR = 0x3f.
>> 
> 
> The driver originally forced the chip into 24-hour mode.  I'm the one
> who added support for 12 or 24 hour mode, so this is probably my fault.
> It seems nicer to me to try to deal with whatever mode the chip is
> already in (in case you're dual-booting into some other OS that wants
> it to be a certain way).  I'm pretty sure I've got one of those chips
> around here somewhere, I'll find some time this weekend to get the
> driver fixed.
> 
> -- Ian

I appreciate your efforts for AM/PM support, only I am not sufficiently familiar with the very details of this format, for example I always got wrong the special meanings of 0:00 AM vs. 0:00 PM and 12:00 AM vs. 12:00 PM. So, I am the wrong person to bugfix AM/PM issues. I applied the temporary dirty fix of dropping out AM/PM only for getting the driver quickly working on my side, and I could continue with my current project. Once the driver is fixed upstream, I will use that one, of course.

That said, I wrote a sysctl function for directly getting/setting the time in the RTC with unix time values. Perhaps, something like this would facilitate your debugging efforts, and here it comes:

static int
ds3231_unixtime_sysctl(SYSCTL_HANDLER_ARGS)
{
	int c, error;
	struct timespec ts = {};
	struct bcd_clocktime bct;
	struct ds3231_softc *sc = (struct ds3231_softc *)arg1;
	uint8_t data[7];

	if (req->newptr == NULL) { // get the unixtime
		/* If the clock halted, we don't have good data. */
		if ((error = ds3231_status_read(sc)) != 0) {
			device_printf(sc->sc_dev, "cannot read from RTC.\n");
			return (error);
		}
		if (sc->sc_status & DS3231_STATUS_OSF)
			return (EINVAL);

		error = iicdev_readfrom(sc->sc_dev, DS3231_SECS, data, sizeof(data),
			IIC_INTRWAIT);
		if (error != 0) {
			device_printf(sc->sc_dev, "cannot read from RTC.\n");
			return (error);
		}

		bct.nsec = 0;
		bct.sec  = data[DS3231_SECS]  & DS3231_SECS_MASK;
		bct.min  = data[DS3231_MINS]  & DS3231_MINS_MASK;
		bct.hour = data[DS3231_HOUR]  & DS3231_HOUR_MASK_12HR;
		bct.day  = data[DS3231_DATE]  & DS3231_DATE_MASK;
		bct.mon  = data[DS3231_MONTH] & DS3231_MONTH_MASK;
		bct.year = data[DS3231_YEAR]  & DS3231_YEAR_MASK;
		bct.ispm = data[DS3231_HOUR]  & DS3231_HOUR_IS_PM;

		/*
		 * If the century flag has toggled since we last saw it, there has been
		 * a century rollover.  If this is the first time we're seeing it,
		 * remember the state so we can preserve its polarity on writes.
		 */
		c = (data[DS3231_MONTH] & DS3231_C_MASK) ? 1 : 0;
		if (sc->sc_last_c == -1)
			sc->sc_last_c = c;
		else if (c != sc->sc_last_c) {
			sc->sc_year0 += 100;
			sc->sc_last_c = c;
		}
		bct.year |= sc->sc_year0;

		error = clock_bcd_to_ts(&bct, &ts, false);
		if (error == 0) {
			error = sysctl_handle_long(oidp, &ts.tv_sec, 0, req);
		}
	}

	else if ((error = sysctl_handle_long(oidp, &ts.tv_sec, 0, req) == 0)) { // set the unixtime
		/*
		 * We request a timespec with no resolution-adjustment.  That also
		 * disables utc adjustment, so apply that ourselves.
		 */
		ts.tv_sec -= utc_offset();
		clock_ts_to_bcd(&ts, &bct, false);

		data[DS3231_SECS]    = bct.sec;
		data[DS3231_MINS]    = bct.min;
		data[DS3231_HOUR]    = bct.hour | 0b01000000;
		data[DS3231_DATE]    = bct.day;
		data[DS3231_WEEKDAY] = bct.dow + 1;
		data[DS3231_MONTH]   = bct.mon;
		data[DS3231_YEAR]    = bct.year & 0xff;
		if (sc->sc_last_c)
			data[DS3231_MONTH] |= DS3231_C_MASK;

		/* Write the time back to RTC. */
		error = iicdev_writeto(sc->sc_dev, DS3231_SECS, data, sizeof(data),
			IIC_INTRWAIT);
		if (error != 0) {
			device_printf(sc->sc_dev, "cannot write to RTC.\n");
			return (error);
		}

		/*
		 * Unlike most hardware, the osc-was-stopped bit does not clear itself
		 * after setting the time, it has to be manually written to zero.
		 */
		if (sc->sc_status & DS3231_STATUS_OSF) {
			if ((error = ds3231_status_read(sc)) != 0) {
				device_printf(sc->sc_dev, "cannot read from RTC.\n");
				return (error);
			}
			sc->sc_status &= ~DS3231_STATUS_OSF;
			if ((error = ds3231_status_write(sc, 0, 0)) != 0) {
				device_printf(sc->sc_dev, "cannot write to RTC.\n");
				return (error);
			}
		}
	}

	return (error);
}


...
	/* Date/Time. */
	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "unixtime",
	    CTLFLAG_RW | CTLTYPE_ULONG | CTLFLAG_MPSAFE, sc, 0,
	    ds3231_unixtime_sysctl, "LU", "get/set the Date/Time formatted as a UNIX time stamp");

	/* Temperature. */
...


Then, I wrote a shell script for testing, whether setting/getting the time works as expected (checkds3231.sh):

#!/bin/sh

DATE=`date "+%Y-%m-%d"`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 00:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 01:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 02:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 03:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 04:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 05:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 06:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 07:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 08:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 09:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 10:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 11:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 12:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 13:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 14:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 15:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 16:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 17:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 18:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 19:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 20:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 21:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 22:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date -jf "%Y-%m-%d %H:%M:%S" "+%s" $DATE" 23:26:14"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

sysctl dev.ds3231.0.unixtime=`date "+%s"` > /dev/null 2>&1
date -jf "%s" "+%Y-%m-%d %H:%M:%S" `sysctl -n dev.ds3231.0.unixtime`

The sample output here looks good:

2020-07-16 00:26:14
2020-07-16 01:26:14
2020-07-16 02:26:14
2020-07-16 03:26:14
2020-07-16 04:26:14
2020-07-16 05:26:14
2020-07-16 06:26:14
2020-07-16 07:26:14
2020-07-16 08:26:14
2020-07-16 09:26:14
2020-07-16 10:26:14
2020-07-16 11:26:14
2020-07-16 12:26:14
2020-07-16 13:26:14
2020-07-16 14:26:14
2020-07-16 15:26:14
2020-07-16 16:26:14
2020-07-16 17:26:14
2020-07-16 18:26:14
2020-07-16 19:26:14
2020-07-16 20:26:14
2020-07-16 21:26:14
2020-07-16 22:26:14
2020-07-16 23:26:14
2020-07-16 12:32:12

Best regards

Rolf


More information about the freebsd-arm mailing list