Add a driver for the Maxim DS3231 a low-cost, extremely accurate (+-2PPM)

I2C real-time clock (RTC).

The DS3231 has an integrated temperature-compensated crystal oscillator
(TXCO) and crystal.

DS3231 has a temperature sensor, an independent 32kHz output (which can be
turned on and off by the driver) and another output that can be used as
interrupt for alarms or as a second square-wave output, which frequency and
operation mode can be set by driver sysctl(8) knobs.

Differential Revision:	https://reviews.freebsd.org/D1016
Reviewed by:	ian, rpaulo
Tested on:	Raspberry pi model B
This commit is contained in:
Luiz Otavio O Souza 2015-02-28 19:02:44 +00:00
parent 18daa0ee9a
commit befcc3e26b
4 changed files with 808 additions and 0 deletions

145
share/man/man4/ds3231.4 Normal file
View File

@ -0,0 +1,145 @@
.\"
.\" Copyright (c) 2014 Luiz Otavio O Souza <loos@freebsd.org>
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd October 26, 2014
.Dt DS3231 4
.Os
.Sh NAME
.Nm ds3231
.Nd Extremely Accurate i2c-integrated RTC/TCXO/Crystal
.Sh SYNOPSIS
.Cd "device iic"
.Cd "device iicbus"
.Cd "device ds3231"
.Sh DESCRIPTION
The
.Nm
is a low-cost, extremely accurate I2C realtime clock (RTC) with an
integrated temperature-compensated crystal oscillator (TCXO) and crystal.
.Pp
The device incorporates a battery input and maintains accurate timekeeping
when main power to the device is interrupted.
.Pp
Access to
.Nm
data is made with the
.Xr sysctl 8
interface:
.Bd -literal
dev.ds3231.0.%desc: Maxim DS3231 RTC
dev.ds3231.0.%driver: ds3231
dev.ds3231.0.%location: addr=0xd0
dev.ds3231.0.%pnpinfo: name=rtc compat=maxim,ds3231
dev.ds3231.0.%parent: iicbus1
dev.ds3231.0.temperature: 23.2C
dev.ds3231.0.temp_conv: 0
dev.ds3231.0.bbsqw: 0
dev.ds3231.0.sqw_freq: 8192
dev.ds3231.0.sqw_mode: interrupt
dev.ds3231.0.32khz_enable: 1
.Ed
.Bl -tag -width ".Va dev.ds3231.%d.temperature"
.It Va dev.ds3231.%d.temperature
The read-only value of the current temperature read by the RTC.
.It Va dev.ds3231.%d.temp_conv
Start a new temperature convertion.
When read as 1, a temperature conversion is in progress.
When read as 0 and then set to 1, a temperature convertion is started.
The temperature conversion runs automatically on power up and once every 64
seconds afterward.
.It Va dev.ds3231.%d.bbsqw
If set to 1 and
.Va dev.ds3231.%d.sqw_mode
is set to square-wave, battery-backed square-wave output is enabled.
If set to 0, the SQW pin will be set to high impendance when the RTC is
being powered by battery.
.It Va dev.ds3231.%d.sqw_freq
Select the frequency of the SQW pin when the square-wave output is enabled on
.Va dev.ds3231.%d.sqw_mode .
It can be set to 1, 1024, 4096, and 8192.
.It Va dev.ds3231.%d.sqw_mode
Set the operation mode for the SQW pin.
It can be set to 'interrupt' (default) or 'square-wave'.
In interrupt mode, the SQW pin is used to generate interrupts for the RTC
alarms.
In square-wave mode, the SQW pin drives a square-wave of
.Va dev.ds3231.%d.sqw_freq
frequency.
.It Va dev.ds3231.%d.32khz_enable
Enable the 32kHz output.
.El
.Pp
Please check the
.Nm
datasheet for more details.
.Pp
On a
.Xr device.hints 5
based system, such as
.Li MIPS ,
these values are configurable for
.Nm :
.Bl -tag -width ".Va hint.ds3231.%d.addr"
.It Va hint.ds3231.%d.at
The
.Xr iicbus 4
that the
.Nm
is connected to.
.It Va hint.ds3231.%d.addr
The i2c address of
.Nm .
.El
.Pp
On a
.Xr FDT 4
based system the following properties must be set:
.Bl -tag -width ".Va compatible"
.It Va compatible
Must always be set to "maxim,ds3231".
.It Va reg
The i2c address of
.Nm .
The default address for
.Nm
is 0xd0.
.El
.Sh SEE ALSO
.Xr fdt 4 ,
.Xr iic 4 ,
.Xr iicbus 4 ,
.Xr sysctl 8
.Sh HISTORY
The
.Nm
driver first appeared in
.Fx 11.0 .
.Sh AUTHORS
.An -nosplit
The
.Nm
driver and this manual page were written by
.An Luiz Otavio O Souza Aq Mt loos@FreeBSD.org .

View File

@ -1452,6 +1452,7 @@ dev/iicbus/ad7418.c optional ad7418
dev/iicbus/ds133x.c optional ds133x
dev/iicbus/ds1374.c optional ds1374
dev/iicbus/ds1672.c optional ds1672
dev/iicbus/ds3231.c optional ds3231
dev/iicbus/icee.c optional icee
dev/iicbus/if_ic.c optional ic
dev/iicbus/iic.c optional iic

584
sys/dev/iicbus/ds3231.c Normal file
View File

@ -0,0 +1,584 @@
/*-
* Copyright (c) 2014-2015 Luiz Otavio O Souza <loos@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* Driver for Maxim DS3231[N] real-time clock/calendar.
*/
#include "opt_platform.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/clock.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <dev/iicbus/iicbus.h>
#include <dev/iicbus/iiconf.h>
#ifdef FDT
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#endif
#include <dev/iicbus/ds3231reg.h>
#include "clock_if.h"
#include "iicbus_if.h"
struct ds3231_softc {
device_t sc_dev;
int sc_last_c;
int sc_year0;
struct intr_config_hook enum_hook;
uint16_t sc_addr; /* DS3231 slave address. */
uint8_t sc_ctrl;
uint8_t sc_status;
};
static int ds3231_sqw_freq[] = { 1, 1024, 4096, 8192 };
static void ds3231_start(void *);
static int
ds3231_read(device_t dev, uint16_t addr, uint8_t reg, uint8_t *data, size_t len)
{
struct iic_msg msg[2] = {
{ addr, IIC_M_WR | IIC_M_NOSTOP, 1, &reg },
{ addr, IIC_M_RD, len, data },
};
return (iicbus_transfer(dev, msg, nitems(msg)));
}
static int
ds3231_write(device_t dev, uint16_t addr, uint8_t *data, size_t len)
{
struct iic_msg msg[1] = {
{ addr, IIC_M_WR, len, data },
};
return (iicbus_transfer(dev, msg, nitems(msg)));
}
static int
ds3231_ctrl_read(struct ds3231_softc *sc)
{
int error;
sc->sc_ctrl = 0;
error = ds3231_read(sc->sc_dev, sc->sc_addr, DS3231_CONTROL,
&sc->sc_ctrl, sizeof(sc->sc_ctrl));
if (error) {
device_printf(sc->sc_dev, "cannot read from RTC.\n");
return (error);
}
return (0);
}
static int
ds3231_ctrl_write(struct ds3231_softc *sc)
{
int error;
uint8_t data[2];
data[0] = DS3231_CONTROL;
/* Always enable the oscillator. Always disable both alarms. */
data[1] = sc->sc_ctrl & ~DS3231_CTRL_MASK;
error = ds3231_write(sc->sc_dev, sc->sc_addr, data, sizeof(data));
if (error != 0)
device_printf(sc->sc_dev, "cannot write to RTC.\n");
return (error);
}
static int
ds3231_status_read(struct ds3231_softc *sc)
{
int error;
sc->sc_status = 0;
error = ds3231_read(sc->sc_dev, sc->sc_addr, DS3231_STATUS,
&sc->sc_status, sizeof(sc->sc_status));
if (error) {
device_printf(sc->sc_dev, "cannot read from RTC.\n");
return (error);
}
return (0);
}
static int
ds3231_status_write(struct ds3231_softc *sc, int clear_a1, int clear_a2)
{
int error;
uint8_t data[2];
data[0] = DS3231_STATUS;
data[1] = sc->sc_status;
if (clear_a1 == 0)
data[1] |= DS3231_STATUS_A1F;
if (clear_a2 == 0)
data[1] |= DS3231_STATUS_A2F;
error = ds3231_write(sc->sc_dev, sc->sc_addr, data, sizeof(data));
if (error != 0)
device_printf(sc->sc_dev, "cannot write to RTC.\n");
return (error);
}
static int
ds3231_set_24hrs_mode(struct ds3231_softc *sc)
{
int error;
uint8_t data[2], hour;
hour = 0;
error = ds3231_read(sc->sc_dev, sc->sc_addr, DS3231_HOUR,
&hour, sizeof(hour));
if (error) {
device_printf(sc->sc_dev, "cannot read from RTC.\n");
return (error);
}
data[0] = DS3231_HOUR;
data[1] = hour & ~DS3231_C_MASK;
error = ds3231_write(sc->sc_dev, sc->sc_addr, data, sizeof(data));
if (error != 0)
device_printf(sc->sc_dev, "cannot write to RTC.\n");
return (error);
}
static int
ds3231_temp_read(struct ds3231_softc *sc, int *temp)
{
int error, neg, t;
uint8_t buf8[2];
uint16_t buf;
error = ds3231_read(sc->sc_dev, sc->sc_addr, DS3231_TEMP,
buf8, sizeof(buf8));
if (error != 0)
return (error);
buf = (buf8[0] << 8) | (buf8[1] & 0xff);
neg = 0;
if (buf & DS3231_NEG_BIT) {
buf = ~(buf & DS3231_TEMP_MASK) + 1;
neg = 1;
}
*temp = ((int16_t)buf >> 8) * 10;
t = 0;
if (buf & DS3231_0250C)
t += 250;
if (buf & DS3231_0500C)
t += 500;
t /= 100;
*temp += t;
if (neg)
*temp = -(*temp);
*temp += TZ_ZEROC;
return (0);
}
static int
ds3231_temp_sysctl(SYSCTL_HANDLER_ARGS)
{
int error, temp;
struct ds3231_softc *sc;
sc = (struct ds3231_softc *)arg1;
if (ds3231_temp_read(sc, &temp) != 0)
return (EIO);
error = sysctl_handle_int(oidp, &temp, 0, req);
return (error);
}
static int
ds3231_conv_sysctl(SYSCTL_HANDLER_ARGS)
{
int error, conv, newc;
struct ds3231_softc *sc;
sc = (struct ds3231_softc *)arg1;
error = ds3231_ctrl_read(sc);
if (error != 0)
return (error);
newc = conv = (sc->sc_ctrl & DS3231_CTRL_CONV) ? 1 : 0;
error = sysctl_handle_int(oidp, &newc, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (conv == 0 && newc != 0) {
error = ds3231_status_read(sc);
if (error != 0)
return (error);
if (sc->sc_status & DS3231_STATUS_BUSY)
return (0);
sc->sc_ctrl |= DS3231_CTRL_CONV;
error = ds3231_ctrl_write(sc);
if (error != 0)
return (error);
}
return (error);
}
static int
ds3231_bbsqw_sysctl(SYSCTL_HANDLER_ARGS)
{
int bbsqw, error, newb;
struct ds3231_softc *sc;
sc = (struct ds3231_softc *)arg1;
error = ds3231_ctrl_read(sc);
if (error != 0)
return (error);
bbsqw = newb = (sc->sc_ctrl & DS3231_CTRL_BBSQW) ? 1 : 0;
error = sysctl_handle_int(oidp, &newb, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (bbsqw != newb) {
sc->sc_ctrl &= ~DS3231_CTRL_BBSQW;
if (newb)
sc->sc_ctrl |= DS3231_CTRL_BBSQW;
error = ds3231_ctrl_write(sc);
if (error != 0)
return (error);
}
return (error);
}
static int
ds3231_sqw_freq_sysctl(SYSCTL_HANDLER_ARGS)
{
int error, freq, i, newf, tmp;
struct ds3231_softc *sc;
sc = (struct ds3231_softc *)arg1;
error = ds3231_ctrl_read(sc);
if (error != 0)
return (error);
tmp = (sc->sc_ctrl & DS3231_CTRL_RS_MASK) >> DS3231_CTRL_RS_SHIFT;
if (tmp > nitems(ds3231_sqw_freq))
tmp = nitems(ds3231_sqw_freq);
freq = ds3231_sqw_freq[tmp];
error = sysctl_handle_int(oidp, &freq, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (freq != ds3231_sqw_freq[tmp]) {
newf = 0;
for (i = 0; i < nitems(ds3231_sqw_freq); i++)
if (freq >= ds3231_sqw_freq[i])
newf = i;
sc->sc_ctrl &= ~DS3231_CTRL_RS_MASK;
sc->sc_ctrl |= newf << DS3231_CTRL_RS_SHIFT;
error = ds3231_ctrl_write(sc);
if (error != 0)
return (error);
}
return (error);
}
static int
ds3231_str_sqw_mode(char *buf)
{
int len, rtrn;
rtrn = -1;
len = strlen(buf);
if ((len > 2 && strncasecmp("interrupt", buf, len) == 0) ||
(len > 2 && strncasecmp("int", buf, len) == 0)) {
rtrn = 1;
} else if ((len > 2 && strncasecmp("square-wave", buf, len) == 0) ||
(len > 2 && strncasecmp("sqw", buf, len) == 0)) {
rtrn = 0;
}
return (rtrn);
}
static int
ds3231_sqw_mode_sysctl(SYSCTL_HANDLER_ARGS)
{
char buf[16];
int error, mode, newm;
struct ds3231_softc *sc;
sc = (struct ds3231_softc *)arg1;
error = ds3231_ctrl_read(sc);
if (error != 0)
return (error);
if (sc->sc_ctrl & DS3231_CTRL_INTCN) {
mode = 1;
strlcpy(buf, "interrupt", sizeof(buf));
} else {
mode = 0;
strlcpy(buf, "square-wave", sizeof(buf));
}
error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
if (error != 0 || req->newptr == NULL)
return (error);
newm = ds3231_str_sqw_mode(buf);
if (newm != -1 && mode != newm) {
sc->sc_ctrl &= ~DS3231_CTRL_INTCN;
if (newm == 1)
sc->sc_ctrl |= DS3231_CTRL_INTCN;
error = ds3231_ctrl_write(sc);
if (error != 0)
return (error);
}
return (error);
}
static int
ds3231_en32khz_sysctl(SYSCTL_HANDLER_ARGS)
{
int error, en32khz, tmp;
struct ds3231_softc *sc;
sc = (struct ds3231_softc *)arg1;
error = ds3231_status_read(sc);
if (error != 0)
return (error);
tmp = en32khz = (sc->sc_status & DS3231_STATUS_EN32KHZ) ? 1 : 0;
error = sysctl_handle_int(oidp, &en32khz, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (en32khz != tmp) {
sc->sc_status &= ~DS3231_STATUS_EN32KHZ;
if (en32khz)
sc->sc_status |= DS3231_STATUS_EN32KHZ;
error = ds3231_status_write(sc, 0, 0);
if (error != 0)
return (error);
}
return (error);
}
static int
ds3231_probe(device_t dev)
{
#ifdef FDT
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_is_compatible(dev, "maxim,ds3231"))
return (ENXIO);
#endif
device_set_desc(dev, "Maxim DS3231 RTC");
return (BUS_PROBE_DEFAULT);
}
static int
ds3231_attach(device_t dev)
{
struct ds3231_softc *sc;
sc = device_get_softc(dev);
sc->sc_dev = dev;
sc->sc_addr = iicbus_get_addr(dev);
sc->sc_last_c = -1;
sc->sc_year0 = 1900;
sc->enum_hook.ich_func = ds3231_start;
sc->enum_hook.ich_arg = dev;
/*
* We have to wait until interrupts are enabled. Usually I2C read
* and write only works when the interrupts are available.
*/
if (config_intrhook_establish(&sc->enum_hook) != 0)
return (ENOMEM);
return (0);
}
static void
ds3231_start(void *xdev)
{
device_t dev;
struct ds3231_softc *sc;
struct sysctl_ctx_list *ctx;
struct sysctl_oid *tree_node;
struct sysctl_oid_list *tree;
dev = (device_t)xdev;
sc = device_get_softc(dev);
ctx = device_get_sysctl_ctx(dev);
tree_node = device_get_sysctl_tree(dev);
tree = SYSCTL_CHILDREN(tree_node);
config_intrhook_disestablish(&sc->enum_hook);
if (ds3231_ctrl_read(sc) != 0)
return;
if (ds3231_status_read(sc) != 0)
return;
/* Clear the OSF bit and ack any pending alarm interrupt. */
if (sc->sc_status & DS3231_STATUS_OSF) {
device_printf(sc->sc_dev,
"oscillator has stopped, check the battery.\n");
sc->sc_status &= ~DS3231_STATUS_OSF;
}
if (ds3231_status_write(sc, 1, 1) != 0)
return;
/* Always enable the oscillator. */
if (ds3231_ctrl_write(sc) != 0)
return;
/* Set the 24 hours mode. */
if (ds3231_set_24hrs_mode(sc) != 0)
return;
/* Temperature. */
SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "temperature",
CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0,
ds3231_temp_sysctl, "IK", "Current temperature");
/* Configuration parameters. */
SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "temp_conv",
CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0,
ds3231_conv_sysctl, "IU",
"DS3231 start a new temperature converstion");
SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "bbsqw",
CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0,
ds3231_bbsqw_sysctl, "IU",
"DS3231 battery-backed square-wave output enable");
SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "sqw_freq",
CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0,
ds3231_sqw_freq_sysctl, "IU",
"DS3231 square-wave output frequency");
SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "sqw_mode",
CTLFLAG_RW | CTLTYPE_STRING | CTLFLAG_MPSAFE, sc, 0,
ds3231_sqw_mode_sysctl, "A", "DS3231 SQW output mode control");
SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "32khz_enable",
CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0,
ds3231_en32khz_sysctl, "IU", "DS3231 enable the 32kHz output");
/* 1 second resolution. */
clock_register(dev, 1000000);
}
static int
ds3231_gettime(device_t dev, struct timespec *ts)
{
int c, error;
struct clocktime ct;
struct ds3231_softc *sc;
uint8_t data[7];
sc = device_get_softc(dev);
memset(data, 0, sizeof(data));
error = ds3231_read(sc->sc_dev, sc->sc_addr, DS3231_SECS,
data, sizeof(data));
if (error != 0) {
device_printf(dev, "cannot read from RTC.\n");
return (error);
}
ct.nsec = 0;
ct.sec = FROMBCD(data[DS3231_SECS] & DS3231_SECS_MASK);
ct.min = FROMBCD(data[DS3231_MINS] & DS3231_MINS_MASK);
ct.hour = FROMBCD(data[DS3231_HOUR] & DS3231_HOUR_MASK);
ct.day = FROMBCD(data[DS3231_DATE] & DS3231_DATE_MASK);
ct.dow = data[DS3231_WEEKDAY] & DS3231_WEEKDAY_MASK;
ct.mon = FROMBCD(data[DS3231_MONTH] & DS3231_MONTH_MASK);
ct.year = FROMBCD(data[DS3231_YEAR] & DS3231_YEAR_MASK);
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;
}
ct.year += sc->sc_year0;
if (ct.year < POSIX_BASE_YEAR)
ct.year += 100; /* assume [1970, 2069] */
return (clock_ct_to_ts(&ct, ts));
}
static int
ds3231_settime(device_t dev, struct timespec *ts)
{
int error;
struct clocktime ct;
struct ds3231_softc *sc;
uint8_t data[8];
sc = device_get_softc(dev);
/* Accuracy is only one second. */
if (ts->tv_nsec >= 500000000)
ts->tv_sec++;
ts->tv_nsec = 0;
clock_ts_to_ct(ts, &ct);
memset(data, 0, sizeof(data));
data[0] = DS3231_SECS;
data[DS3231_SECS + 1] = TOBCD(ct.sec);
data[DS3231_MINS + 1] = TOBCD(ct.min);
data[DS3231_HOUR + 1] = TOBCD(ct.hour);
data[DS3231_DATE + 1] = TOBCD(ct.day);
data[DS3231_WEEKDAY + 1] = ct.dow;
data[DS3231_MONTH + 1] = TOBCD(ct.mon);
data[DS3231_YEAR + 1] = TOBCD(ct.year % 100);
if (sc->sc_last_c)
data[DS3231_MONTH] |= DS3231_C_MASK;
/* Write the time back to RTC. */
error = ds3231_write(dev, sc->sc_addr, data, sizeof(data));
if (error != 0)
device_printf(dev, "cannot write to RTC.\n");
return (error);
}
static device_method_t ds3231_methods[] = {
DEVMETHOD(device_probe, ds3231_probe),
DEVMETHOD(device_attach, ds3231_attach),
DEVMETHOD(clock_gettime, ds3231_gettime),
DEVMETHOD(clock_settime, ds3231_settime),
DEVMETHOD_END
};
static driver_t ds3231_driver = {
"ds3231",
ds3231_methods,
sizeof(struct ds3231_softc),
};
static devclass_t ds3231_devclass;
DRIVER_MODULE(ds3231, iicbus, ds3231_driver, ds3231_devclass, NULL, NULL);
MODULE_VERSION(ds3231, 1);
MODULE_DEPEND(ds3231, iicbus, 1, 1, 1);

View File

@ -0,0 +1,78 @@
/*-
* Copyright (c) 2014-2015 Luiz Otavio O Souza <loos@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
/*
* Maxim DS3231 RTC registers.
*/
#ifndef _DS3231REG_H_
#define _DS3231REG_H_
#define DS3231_SECS 0x00
#define DS3231_SECS_MASK 0x7f
#define DS3231_MINS 0x01
#define DS3231_MINS_MASK 0x7f
#define DS3231_HOUR 0x02
#define DS3231_HOUR_MASK 0x3f
#define DS3231_WEEKDAY 0x03
#define DS3231_WEEKDAY_MASK 0x07
#define DS3231_DATE 0x04
#define DS3231_DATE_MASK 0x3f
#define DS3231_MONTH 0x05
#define DS3231_MONTH_MASK 0x1f
#define DS3231_C_MASK 0x80
#define DS3231_YEAR 0x06
#define DS3231_YEAR_MASK 0xff
#define DS3231_CONTROL 0x0e
#define DS3231_CTRL_EOSC (1 << 7)
#define DS3231_CTRL_BBSQW (1 << 6)
#define DS3231_CTRL_CONV (1 << 5)
#define DS3231_CTRL_RS2 (1 << 4)
#define DS3231_CTRL_RS1 (1 << 3)
#define DS3231_CTRL_RS_MASK (DS3231_CTRL_RS2 | DS3231_CTRL_RS1)
#define DS3231_CTRL_RS_SHIFT 3
#define DS3231_CTRL_INTCN (1 << 2)
#define DS3231_CTRL_A2IE (1 << 1)
#define DS3231_CTRL_A1IE (1 << 0)
#define DS3231_CTRL_MASK \
(DS3231_CTRL_EOSC | DS3231_CTRL_A1IE | DS3231_CTRL_A2IE)
#define DS3231_STATUS 0x0f
#define DS3231_STATUS_OSF (1 << 7)
#define DS3231_STATUS_EN32KHZ (1 << 3)
#define DS3231_STATUS_BUSY (1 << 2)
#define DS3231_STATUS_A2F (1 << 1)
#define DS3231_STATUS_A1F (1 << 0)
#define DS3231_TEMP 0x11
#define DS3231_TEMP_MASK 0xffc0
#define DS3231_0500C 0x80
#define DS3231_0250C 0x40
#define DS3231_MSB 0x8000
#define DS3231_NEG_BIT DS3231_MSB
#define TZ_ZEROC 2732
#endif /* _DS3231REG_H_ */