diff --git a/sys/dev/iicbus/ds1307.c b/sys/dev/iicbus/ds1307.c index 2ba712400fe4..e14f10631fd5 100644 --- a/sys/dev/iicbus/ds1307.c +++ b/sys/dev/iicbus/ds1307.c @@ -1,431 +1,585 @@ /*- * Copyright (c) 2015 Luiz Otavio O Souza + * Copyright (c) 2022 Mathew McBride * 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 __FBSDID("$FreeBSD$"); /* * Driver for Maxim DS1307 I2C real-time clock/calendar. */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #endif #include #include "clock_if.h" #include "iicbus_if.h" +enum { + TYPE_DS1307, + TYPE_MAXIM1307, + TYPE_MICROCHIP_MCP7491X, + TYPE_EPSON_RX8035, + TYPE_COUNT +}; + struct ds1307_softc { - device_t sc_dev; - struct intr_config_hook - enum_hook; - uint8_t sc_ctrl; - bool sc_mcp7941x; - bool sc_use_ampm; + device_t sc_dev; + struct intr_config_hook enum_hook; + uint32_t chiptype; + uint8_t sc_ctrl; + bool sc_use_ampm; }; static void ds1307_start(void *); #ifdef FDT static const struct ofw_compat_data ds1307_compat_data[] = { - {"dallas,ds1307", (uintptr_t)"Dallas DS1307 RTC"}, - {"maxim,ds1307", (uintptr_t)"Maxim DS1307 RTC"}, - {"microchip,mcp7941x", (uintptr_t)"Microchip MCP7941x RTC"}, - { NULL, 0 } + {"dallas,ds1307", TYPE_DS1307}, + {"maxim,ds1307", TYPE_MAXIM1307}, + {"microchip,mcp7941x", TYPE_MICROCHIP_MCP7491X}, + {"epson,rx8035", TYPE_EPSON_RX8035}, + { NULL, 0 } }; #endif static int ds1307_read1(device_t dev, uint8_t reg, uint8_t *data) { return (iicdev_readfrom(dev, reg, data, 1, IIC_INTRWAIT)); } static int ds1307_write1(device_t dev, uint8_t reg, uint8_t data) { return (iicdev_writeto(dev, reg, &data, 1, IIC_INTRWAIT)); } static int ds1307_ctrl_read(struct ds1307_softc *sc) { int error; sc->sc_ctrl = 0; error = ds1307_read1(sc->sc_dev, DS1307_CONTROL, &sc->sc_ctrl); if (error) { - device_printf(sc->sc_dev, "cannot read from RTC.\n"); + device_printf(sc->sc_dev, "%s: cannot read from RTC: %d\n", + __func__, error); return (error); } return (0); } static int ds1307_ctrl_write(struct ds1307_softc *sc) { int error; uint8_t ctrl; ctrl = sc->sc_ctrl & DS1307_CTRL_MASK; error = ds1307_write1(sc->sc_dev, DS1307_CONTROL, ctrl); if (error != 0) - device_printf(sc->sc_dev, "cannot write to RTC.\n"); + device_printf(sc->sc_dev, "%s: cannot write to RTC: %d\n", + __func__, error); return (error); } static int ds1307_sqwe_sysctl(SYSCTL_HANDLER_ARGS) { int sqwe, error, newv, sqwe_bit; struct ds1307_softc *sc; sc = (struct ds1307_softc *)arg1; error = ds1307_ctrl_read(sc); if (error != 0) return (error); - if (sc->sc_mcp7941x) + if (sc->chiptype == TYPE_MICROCHIP_MCP7491X) sqwe_bit = MCP7941X_CTRL_SQWE; else sqwe_bit = DS1307_CTRL_SQWE; sqwe = newv = (sc->sc_ctrl & sqwe_bit) ? 1 : 0; error = sysctl_handle_int(oidp, &newv, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (sqwe != newv) { sc->sc_ctrl &= ~sqwe_bit; if (newv) sc->sc_ctrl |= sqwe_bit; error = ds1307_ctrl_write(sc); if (error != 0) return (error); } return (error); } static int ds1307_sqw_freq_sysctl(SYSCTL_HANDLER_ARGS) { int ds1307_sqw_freq[] = { 1, 4096, 8192, 32768 }; int error, freq, i, newf, tmp; struct ds1307_softc *sc; sc = (struct ds1307_softc *)arg1; error = ds1307_ctrl_read(sc); if (error != 0) return (error); tmp = (sc->sc_ctrl & DS1307_CTRL_RS_MASK); if (tmp >= nitems(ds1307_sqw_freq)) tmp = nitems(ds1307_sqw_freq) - 1; freq = ds1307_sqw_freq[tmp]; error = sysctl_handle_int(oidp, &freq, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (freq != ds1307_sqw_freq[tmp]) { newf = 0; for (i = 0; i < nitems(ds1307_sqw_freq); i++) if (freq >= ds1307_sqw_freq[i]) newf = i; sc->sc_ctrl &= ~DS1307_CTRL_RS_MASK; sc->sc_ctrl |= newf; error = ds1307_ctrl_write(sc); if (error != 0) return (error); } return (error); } static int ds1307_sqw_out_sysctl(SYSCTL_HANDLER_ARGS) { int sqwe, error, newv; struct ds1307_softc *sc; sc = (struct ds1307_softc *)arg1; error = ds1307_ctrl_read(sc); if (error != 0) return (error); sqwe = newv = (sc->sc_ctrl & DS1307_CTRL_OUT) ? 1 : 0; error = sysctl_handle_int(oidp, &newv, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (sqwe != newv) { sc->sc_ctrl &= ~DS1307_CTRL_OUT; if (newv) sc->sc_ctrl |= DS1307_CTRL_OUT; error = ds1307_ctrl_write(sc); if (error != 0) return (error); } return (error); } static int ds1307_probe(device_t dev) { #ifdef FDT const struct ofw_compat_data *compat; if (!ofw_bus_status_okay(dev)) return (ENXIO); compat = ofw_bus_search_compatible(dev, ds1307_compat_data); - if (compat->ocd_str != NULL) { - device_set_desc(dev, (const char *)compat->ocd_data); - return (BUS_PROBE_DEFAULT); + if (compat->ocd_str == NULL) + return (ENXIO); + + switch(compat->ocd_data) { + case TYPE_DS1307: + device_set_desc(dev, "Dallas DS1307"); + break; + case TYPE_MAXIM1307: + device_set_desc(dev, "Maxim DS1307"); + break; + case TYPE_MICROCHIP_MCP7491X: + device_set_desc(dev, "Microchip MCP7491X"); + break; + case TYPE_EPSON_RX8035: + device_set_desc(dev, "Epson RX-8035"); + break; + default: + device_set_desc(dev, "Unknown DS1307-like device"); + break; } + return (BUS_PROBE_DEFAULT); #endif + device_set_desc(dev, "Maxim DS1307 RTC"); return (BUS_PROBE_NOWILDCARD); } static int ds1307_attach(device_t dev) { +#ifdef FDT + const struct ofw_compat_data *compat; +#endif struct ds1307_softc *sc; sc = device_get_softc(dev); sc->sc_dev = dev; sc->enum_hook.ich_func = ds1307_start; sc->enum_hook.ich_arg = dev; - #ifdef FDT - if (ofw_bus_is_compatible(dev, "microchip,mcp7941x")) - sc->sc_mcp7941x = 1; + compat = ofw_bus_search_compatible(dev, ds1307_compat_data); + sc->chiptype = compat->ocd_data; + /* Unify the chiptypes to DS1307 where possible. */ + if (sc->chiptype == TYPE_MAXIM1307) + sc->chiptype = TYPE_DS1307; +#else + sc->chiptype = TYPE_DS1307; #endif /* * 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 int ds1307_detach(device_t dev) { clock_unregister(dev); return (0); } +static bool +is_epson_time_valid(struct ds1307_softc *sc) +{ + device_t dev; + int error; + uint8_t ctrl2; + + dev = sc->sc_dev; + + /* + * The RX-8035 single register read is non-standard + * Refer to section 8.9.5 of the RX-8035 application manual: + * "I2C bus basic transfer format", under "Standard Read Method". + * Basically, register to read goes into the top 4 bits. + */ + error = ds1307_read1(dev, (RX8035_CTRL_2 << 4), &ctrl2); + if (error) { + device_printf(dev, "%s cannot read Control 2 register: %d\n", + __func__, error); + return (false); + } + + if (ctrl2 & RX8035_CTRL_2_XSTP) { + device_printf(dev, "Oscillation stop detected (ctrl2=%#02x)\n", + ctrl2); + return (false); + } + + /* + * Power on reset (PON) generally implies oscillation stop, + * but catch it as well to be sure. + */ + if (ctrl2 & RX8035_CTRL_2_PON) { + device_printf(dev, "Power-on reset detected (ctrl2=%#02x)\n", + ctrl2); + return (false); + } + + return (true); +} + +static int +mark_epson_time_valid(struct ds1307_softc *sc) +{ + device_t dev; + int error; + uint8_t ctrl2; + uint8_t control_mask; + + dev = sc->sc_dev; + + error = ds1307_read1(dev, (RX8035_CTRL_2 << 4), &ctrl2); + if (error) { + device_printf(dev, "%s cannot read Control 2 register: %d\n", + __func__, error); + return (false); + } + + control_mask = (RX8035_CTRL_2_PON | RX8035_CTRL_2_XSTP | RX8035_CTRL_2_VDET); + ctrl2 = ctrl2 & ~(control_mask); + + error = ds1307_write1(dev, (RX8035_CTRL_2 << 4), ctrl2); + if (error) { + device_printf(dev, "%s cannot write to Control 2 register: %d\n", + __func__, error); + return (false); + } + return (true); +} + +static bool is_dev_time_valid(struct ds1307_softc *sc) +{ + device_t dev; + int error; + uint8_t osc_en; + uint8_t secs; + + /* Epson RTCs have different control/status registers. */ + if (sc->chiptype == TYPE_EPSON_RX8035) + return (is_epson_time_valid(sc)); + + dev = sc->sc_dev; + /* Check if the oscillator is disabled. */ + error = ds1307_read1(dev, DS1307_SECS, &secs); + if (error) { + device_printf(dev, "%s: cannot read from RTC: %d\n", + __func__, error); + return (false); + } + + switch (sc->chiptype) { + case TYPE_MICROCHIP_MCP7491X: + osc_en = 0x80; + break; + default: + osc_en = 0x00; + break; + } + if (((secs & DS1307_SECS_CH) ^ osc_en) != 0) + return (false); + + return (true); +} + static void ds1307_start(void *xdev) { device_t dev; struct ds1307_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree_node; struct sysctl_oid_list *tree; - uint8_t secs; - uint8_t osc_en; 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); - /* Check if the oscillator is disabled. */ - if (ds1307_read1(sc->sc_dev, DS1307_SECS, &secs) != 0) { - device_printf(sc->sc_dev, "cannot read from RTC.\n"); - return; - } - if (sc->sc_mcp7941x) - osc_en = 0x80; - else - osc_en = 0x00; - - if (((secs & DS1307_SECS_CH) ^ osc_en) != 0) { - device_printf(sc->sc_dev, + if (!is_dev_time_valid(sc)) + device_printf(dev, "WARNING: RTC clock stopped, check the battery.\n"); - } - /* Configuration parameters. */ + /* + * Configuration parameters: + * square wave output cannot be changed or inhibited on the RX-8035, + * so don't present the sysctls there. + */ + if (sc->chiptype == TYPE_EPSON_RX8035) + goto skip_sysctl; + + ctx = device_get_sysctl_ctx(dev); + tree_node = device_get_sysctl_tree(dev); + tree = SYSCTL_CHILDREN(tree_node); + SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "sqwe", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0, ds1307_sqwe_sysctl, "IU", "DS1307 square-wave enable"); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "sqw_freq", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0, ds1307_sqw_freq_sysctl, "IU", "DS1307 square-wave output frequency"); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "sqw_out", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0, ds1307_sqw_out_sysctl, "IU", "DS1307 square-wave output state"); +skip_sysctl: /* * Register as a clock with 1 second resolution. Schedule the * clock_settime() method to be called just after top-of-second; * resetting the time resets top-of-second in the hardware. */ clock_register_flags(dev, 1000000, CLOCKF_SETTIME_NO_ADJ); clock_schedule(dev, 1); } static int ds1307_gettime(device_t dev, struct timespec *ts) { - int error; struct bcd_clocktime bct; struct ds1307_softc *sc; - uint8_t data[7], hourmask, st_mask; + int error; + uint8_t data[7], hourmask, ampm_mode; sc = device_get_softc(dev); error = iicdev_readfrom(sc->sc_dev, DS1307_SECS, data, sizeof(data), IIC_INTRWAIT); if (error != 0) { - device_printf(dev, "cannot read from RTC.\n"); + device_printf(dev, "%s: cannot read from RTC: %d\n", + __func__, error); return (error); } - /* If the clock halted, we don't have good data. */ - if (sc->sc_mcp7941x) - st_mask = 0x80; - else - st_mask = 0x00; - - if (((data[DS1307_SECS] & DS1307_SECS_CH) ^ st_mask) != 0) + if (!is_dev_time_valid(sc)) { + device_printf(dev, "Device time not valid.\n"); return (EINVAL); + } - /* If chip is in AM/PM mode remember that. */ - if (data[DS1307_HOUR] & DS1307_HOUR_USE_AMPM) { + /* + * If the chip is in AM/PM mode remember that. + * The EPSON uses a 1 to signify 24 hour mode, while the DS uses a 0, + * in slighly different positions. + */ + if (sc->chiptype == TYPE_EPSON_RX8035) + ampm_mode = !(data[DS1307_HOUR] & RX8035_HOUR_USE_24); + else + ampm_mode = data[DS1307_HOUR] & DS1307_HOUR_USE_AMPM; + + if (ampm_mode) { sc->sc_use_ampm = true; hourmask = DS1307_HOUR_MASK_12HR; } else hourmask = DS1307_HOUR_MASK_24HR; bct.nsec = 0; bct.ispm = (data[DS1307_HOUR] & DS1307_HOUR_IS_PM) != 0; bct.sec = data[DS1307_SECS] & DS1307_SECS_MASK; bct.min = data[DS1307_MINS] & DS1307_MINS_MASK; bct.hour = data[DS1307_HOUR] & hourmask; bct.day = data[DS1307_DATE] & DS1307_DATE_MASK; bct.mon = data[DS1307_MONTH] & DS1307_MONTH_MASK; bct.year = data[DS1307_YEAR] & DS1307_YEAR_MASK; clock_dbgprint_bcd(sc->sc_dev, CLOCK_DBG_READ, &bct); return (clock_bcd_to_ts(&bct, ts, sc->sc_use_ampm)); } static int ds1307_settime(device_t dev, struct timespec *ts) { struct bcd_clocktime bct; struct ds1307_softc *sc; int error, year; uint8_t data[7]; uint8_t pmflags; sc = device_get_softc(dev); /* * 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, sc->sc_use_ampm); clock_dbgprint_bcd(sc->sc_dev, CLOCK_DBG_WRITE, &bct); - /* If the chip is in AM/PM mode, adjust hour and set flags as needed. */ + /* + * If the chip is in AM/PM mode, adjust hour and set flags as needed. + * The AM/PM bit polarity and position is different on the EPSON. + */ if (sc->sc_use_ampm) { - pmflags = DS1307_HOUR_USE_AMPM; + pmflags = (sc->chiptype != TYPE_EPSON_RX8035) ? + DS1307_HOUR_USE_AMPM : 0; if (bct.ispm) pmflags |= DS1307_HOUR_IS_PM; - } else + + } else if (sc->chiptype == TYPE_EPSON_RX8035) + pmflags = RX8035_HOUR_USE_24; + else pmflags = 0; data[DS1307_SECS] = bct.sec; data[DS1307_MINS] = bct.min; data[DS1307_HOUR] = bct.hour | pmflags; data[DS1307_DATE] = bct.day; data[DS1307_WEEKDAY] = bct.dow; data[DS1307_MONTH] = bct.mon; data[DS1307_YEAR] = bct.year & 0xff; - if (sc->sc_mcp7941x) { + if (sc->chiptype == TYPE_MICROCHIP_MCP7491X) { data[DS1307_SECS] |= MCP7941X_SECS_ST; data[DS1307_WEEKDAY] |= MCP7941X_WEEKDAY_VBATEN; year = bcd2bin(bct.year >> 8) * 100 + bcd2bin(bct.year & 0xff); if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) data[DS1307_MONTH] |= MCP7941X_MONTH_LPYR; } + /* Write the time back to RTC. */ error = iicdev_writeto(sc->sc_dev, DS1307_SECS, data, sizeof(data), IIC_INTRWAIT); if (error != 0) - device_printf(dev, "cannot write to RTC.\n"); + device_printf(dev, "%s: cannot write to RTC: %d\n", + __func__, error); + + if (sc->chiptype == TYPE_EPSON_RX8035) + error = mark_epson_time_valid(sc); return (error); } static device_method_t ds1307_methods[] = { DEVMETHOD(device_probe, ds1307_probe), DEVMETHOD(device_attach, ds1307_attach), DEVMETHOD(device_detach, ds1307_detach), DEVMETHOD(clock_gettime, ds1307_gettime), DEVMETHOD(clock_settime, ds1307_settime), DEVMETHOD_END }; static driver_t ds1307_driver = { "ds1307", ds1307_methods, sizeof(struct ds1307_softc), }; DRIVER_MODULE(ds1307, iicbus, ds1307_driver, NULL, NULL); MODULE_VERSION(ds1307, 1); MODULE_DEPEND(ds1307, iicbus, 1, 1, 1); IICBUS_FDT_PNP_INFO(ds1307_compat_data); diff --git a/sys/dev/iicbus/ds1307reg.h b/sys/dev/iicbus/ds1307reg.h index f2763a5e0cd3..e4f7979c97da 100644 --- a/sys/dev/iicbus/ds1307reg.h +++ b/sys/dev/iicbus/ds1307reg.h @@ -1,66 +1,72 @@ /*- * Copyright (c) 2015 Luiz Otavio O Souza * 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 DS1307 RTC registers. */ #ifndef _DS1307REG_H_ #define _DS1307REG_H_ #define DS1307_SECS 0x00 #define DS1307_SECS_MASK 0x7f #define DS1307_SECS_CH 0x80 #define MCP7941X_SECS_ST 0x80 #define DS1307_MINS 0x01 #define DS1307_MINS_MASK 0x7f #define DS1307_HOUR 0x02 #define DS1307_HOUR_MASK_12HR 0x1f #define DS1307_HOUR_MASK_24HR 0x3f #define DS1307_HOUR_IS_PM 0x20 #define DS1307_HOUR_USE_AMPM 0x40 +#define RX8035_HOUR_USE_24 0x80 #define DS1307_WEEKDAY 0x03 #define MCP7941X_WEEKDAY_VBATEN 0x08 #define DS1307_WEEKDAY_MASK 0x07 #define DS1307_DATE 0x04 #define DS1307_DATE_MASK 0x3f #define DS1307_MONTH 0x05 #define MCP7941X_MONTH_LPYR 0x20 #define DS1307_MONTH_MASK 0x1f #define DS1307_YEAR 0x06 #define DS1307_YEAR_MASK 0xff #define DS1307_CONTROL 0x07 #define DS1307_CTRL_OUT (1 << 7) #define MCP7941X_CTRL_SQWE (1 << 6) #define DS1307_CTRL_SQWE (1 << 4) #define DS1307_CTRL_RS1 (1 << 1) #define DS1307_CTRL_RS0 (1 << 0) #define DS1307_CTRL_RS_MASK (DS1307_CTRL_RS1 | DS1307_CTRL_RS0) #define DS1307_CTRL_MASK 0x93 +#define RX8035_CTRL_2 0x0F +#define RX8035_CTRL_2_PON (1 << 4) +#define RX8035_CTRL_2_XSTP (1 << 5) +#define RX8035_CTRL_2_VDET (1 << 6) + #endif /* _DS1307REG_H_ */