Index: stable/11/sys/dev/iicbus/ds1307.c =================================================================== --- stable/11/sys/dev/iicbus/ds1307.c (revision 331495) +++ stable/11/sys/dev/iicbus/ds1307.c (revision 331496) @@ -1,427 +1,435 @@ /*- * 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. */ #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" struct ds1307_softc { device_t sc_dev; struct intr_config_hook enum_hook; uint8_t sc_ctrl; bool sc_mcp7941x; 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 } }; #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"); 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"); 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) 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 == NULL) + if (compat->ocd_str == NULL) return (ENXIO); device_set_desc(dev, (const char *)compat->ocd_data); return (BUS_PROBE_DEFAULT); #else device_set_desc(dev, "Maxim DS1307 RTC"); return (BUS_PROBE_NOWILDCARD); #endif } static int ds1307_attach(device_t dev) { 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; #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 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 ((secs & DS1307_SECS_CH) != 0) { + if (sc->sc_mcp7941x) + osc_en = 0x80; + else + osc_en = 0x00; + + if (((secs & DS1307_SECS_CH) ^ osc_en) != 0) { device_printf(sc->sc_dev, "WARNING: RTC clock stopped, check the battery.\n"); } /* Configuration parameters. */ 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"); /* * 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 clocktime ct; + struct bcd_clocktime bct; struct ds1307_softc *sc; - uint8_t data[7], hourmask; + uint8_t data[7], hourmask, st_mask; 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"); return (error); } /* If the clock halted, we don't have good data. */ - if (data[DS1307_SECS] & DS1307_SECS_CH) + if (sc->sc_mcp7941x) + st_mask = 0x80; + else + st_mask = 0x00; + + if (((data[DS1307_SECS] & DS1307_SECS_CH) ^ st_mask) != 0) return (EINVAL); /* If chip is in AM/PM mode remember that. */ if (data[DS1307_HOUR] & DS1307_HOUR_USE_AMPM) { sc->sc_use_ampm = true; hourmask = DS1307_HOUR_MASK_12HR; } else hourmask = DS1307_HOUR_MASK_24HR; - ct.nsec = 0; - ct.sec = FROMBCD(data[DS1307_SECS] & DS1307_SECS_MASK); - ct.min = FROMBCD(data[DS1307_MINS] & DS1307_MINS_MASK); - ct.hour = FROMBCD(data[DS1307_HOUR] & hourmask); - ct.day = FROMBCD(data[DS1307_DATE] & DS1307_DATE_MASK); - ct.mon = FROMBCD(data[DS1307_MONTH] & DS1307_MONTH_MASK); - ct.year = FROMBCD(data[DS1307_YEAR] & DS1307_YEAR_MASK); + 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; - if (sc->sc_use_ampm) { - if (ct.hour == 12) - ct.hour = 0; - if (data[DS1307_HOUR] & DS1307_HOUR_IS_PM) - ct.hour += 12; - } - - return (clock_ct_to_ts(&ct, ts)); + return (clock_bcd_to_ts(&bct, ts, sc->sc_use_ampm)); } static int ds1307_settime(device_t dev, struct timespec *ts) { - struct clocktime ct; + struct bcd_clocktime bct; struct ds1307_softc *sc; - int error; + 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_ct(ts, &ct); + clock_ts_to_bcd(ts, &bct, sc->sc_use_ampm); /* If the chip is in AM/PM mode, adjust hour and set flags as needed. */ if (sc->sc_use_ampm) { pmflags = DS1307_HOUR_USE_AMPM; - if (ct.hour >= 12) { - ct.hour -= 12; + if (bct.ispm) pmflags |= DS1307_HOUR_IS_PM; - } - if (ct.hour == 0) - ct.hour = 12; } else pmflags = 0; - data[DS1307_SECS] = TOBCD(ct.sec); - data[DS1307_MINS] = TOBCD(ct.min); - data[DS1307_HOUR] = TOBCD(ct.hour) | pmflags; - data[DS1307_DATE] = TOBCD(ct.day); - data[DS1307_WEEKDAY] = ct.dow; - data[DS1307_MONTH] = TOBCD(ct.mon); - data[DS1307_YEAR] = TOBCD(ct.year % 100); + 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) { + 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"); 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), }; static devclass_t ds1307_devclass; DRIVER_MODULE(ds1307, iicbus, ds1307_driver, ds1307_devclass, NULL, NULL); MODULE_VERSION(ds1307, 1); MODULE_DEPEND(ds1307, iicbus, 1, 1, 1); Index: stable/11/sys/dev/iicbus/ds1307reg.h =================================================================== --- stable/11/sys/dev/iicbus/ds1307reg.h (revision 331495) +++ stable/11/sys/dev/iicbus/ds1307reg.h (revision 331496) @@ -1,63 +1,66 @@ /*- * 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 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 #endif /* _DS1307REG_H_ */ Index: stable/11/sys/dev/iicbus/ds13rtc.c =================================================================== --- stable/11/sys/dev/iicbus/ds13rtc.c (revision 331495) +++ stable/11/sys/dev/iicbus/ds13rtc.c (revision 331496) @@ -1,627 +1,617 @@ /*- * Copyright (c) 2017 Ian Lepore * 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 Dallas/Maxim DS13xx real-time clock/calendar chips: * * - DS1307 = Original/basic rtc + 56 bytes ram; 5v only. * - DS1308 = Updated 1307, available in 1.8v-5v variations. * - DS1337 = Like 1308, integrated xtal, 32khz output on at powerup. * - DS1338 = Like 1308, integrated xtal. * - DS1339 = Like 1337, integrated xtal, integrated trickle charger. * - DS1340 = Like 1338, ST M41T00 compatible. * - DS1341 = Like 1338, can slave-sync osc to external clock signal. * - DS1342 = Like 1341 but requires different xtal. * - DS1371 = 32-bit binary counter, watchdog timer. * - DS1372 = 32-bit binary counter, 64-bit unique id in rom. * - DS1374 = 32-bit binary counter, watchdog timer, trickle charger. * - DS1375 = Like 1308 but only 16 bytes ram. * - DS1388 = Rtc, watchdog timer, 512 bytes eeprom (not sram). * * This driver supports only basic timekeeping functions. It provides no access * to or control over any other functionality provided by the chips. */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #endif #include "clock_if.h" #include "iicbus_if.h" /* * I2C address 1101 000x */ #define DS13xx_ADDR 0xd0 /* * Registers, bits within them, and masks for the various chip types. */ #define DS13xx_R_NONE 0xff /* Placeholder */ #define DS130x_R_CONTROL 0x07 #define DS133x_R_CONTROL 0x0e #define DS1340_R_CONTROL 0x07 #define DS1341_R_CONTROL 0x0e #define DS1371_R_CONTROL 0x07 #define DS1372_R_CONTROL 0x07 #define DS1374_R_CONTROL 0x07 #define DS1375_R_CONTROL 0x0e #define DS1388_R_CONTROL 0x0c #define DS13xx_R_SECOND 0x00 #define DS1388_R_SECOND 0x01 #define DS130x_R_STATUS DS13xx_R_NONE #define DS133x_R_STATUS 0x0f #define DS1340_R_STATUS 0x09 #define DS137x_R_STATUS 0x08 #define DS1388_R_STATUS 0x0b #define DS13xx_B_STATUS_OSF 0x80 /* OSF is 1<<7 in status and sec regs */ #define DS13xx_B_HOUR_AMPM 0x40 /* AMPM mode is bit 1<<6 */ #define DS13xx_B_HOUR_PM 0x20 /* PM hours indicated by 1<<5 */ #define DS13xx_B_MONTH_CENTURY 0x80 /* 21st century indicated by 1<<7 */ #define DS13xx_M_SECOND 0x7f /* Masks for all BCD time regs... */ #define DS13xx_M_MINUTE 0x7f #define DS13xx_M_12HOUR 0x1f #define DS13xx_M_24HOUR 0x3f #define DS13xx_M_DAY 0x3f #define DS13xx_M_MONTH 0x1f #define DS13xx_M_YEAR 0xff /* * The chip types we support. */ enum { TYPE_NONE, TYPE_DS1307, TYPE_DS1308, TYPE_DS1337, TYPE_DS1338, TYPE_DS1339, TYPE_DS1340, TYPE_DS1341, TYPE_DS1342, TYPE_DS1371, TYPE_DS1372, TYPE_DS1374, TYPE_DS1375, TYPE_DS1388, TYPE_COUNT }; static const char *desc_strings[] = { "", "Dallas/Maxim DS1307 RTC", "Dallas/Maxim DS1308 RTC", "Dallas/Maxim DS1337 RTC", "Dallas/Maxim DS1338 RTC", "Dallas/Maxim DS1339 RTC", "Dallas/Maxim DS1340 RTC", "Dallas/Maxim DS1341 RTC", "Dallas/Maxim DS1342 RTC", "Dallas/Maxim DS1371 RTC", "Dallas/Maxim DS1372 RTC", "Dallas/Maxim DS1374 RTC", "Dallas/Maxim DS1375 RTC", "Dallas/Maxim DS1388 RTC", }; CTASSERT(nitems(desc_strings) == TYPE_COUNT); /* * The time registers in the order they are laid out in hardware. */ struct time_regs { uint8_t sec, min, hour, wday, day, month, year; }; struct ds13rtc_softc { device_t dev; device_t busdev; u_int chiptype; /* Type of DS13xx chip */ uint8_t secaddr; /* Address of seconds register */ uint8_t osfaddr; /* Address of register with OSF */ bool use_ampm; /* Use AM/PM mode. */ bool use_century; /* Use the Century bit. */ bool is_binary_counter; /* Chip has 32-bit binary counter. */ }; /* * We use the compat_data table to look up hint strings in the non-FDT case, so * define the struct locally when we don't get it from ofw_bus_subr.h. */ #ifdef FDT typedef struct ofw_compat_data ds13_compat_data; #else typedef struct { const char *ocd_str; uintptr_t ocd_data; } ds13_compat_data; #endif static ds13_compat_data compat_data[] = { {"dallas,ds1307", TYPE_DS1307}, {"dallas,ds1308", TYPE_DS1308}, {"dallas,ds1337", TYPE_DS1337}, {"dallas,ds1338", TYPE_DS1338}, {"dallas,ds1339", TYPE_DS1339}, {"dallas,ds1340", TYPE_DS1340}, {"dallas,ds1341", TYPE_DS1341}, {"dallas,ds1342", TYPE_DS1342}, {"dallas,ds1371", TYPE_DS1371}, {"dallas,ds1372", TYPE_DS1372}, {"dallas,ds1374", TYPE_DS1374}, {"dallas,ds1375", TYPE_DS1375}, {"dallas,ds1388", TYPE_DS1388}, {NULL, TYPE_NONE}, }; static int read_reg(struct ds13rtc_softc *sc, uint8_t reg, uint8_t *val) { return (iicdev_readfrom(sc->dev, reg, val, sizeof(*val), IIC_WAIT)); } static int write_reg(struct ds13rtc_softc *sc, uint8_t reg, uint8_t val) { return (iicdev_writeto(sc->dev, reg, &val, sizeof(val), IIC_WAIT)); } static int read_timeregs(struct ds13rtc_softc *sc, struct time_regs *tregs) { int err; if ((err = iicdev_readfrom(sc->dev, sc->secaddr, tregs, sizeof(*tregs), IIC_WAIT)) != 0) return (err); return (err); } static int write_timeregs(struct ds13rtc_softc *sc, struct time_regs *tregs) { return (iicdev_writeto(sc->dev, sc->secaddr, tregs, sizeof(*tregs), IIC_WAIT)); } static int read_timeword(struct ds13rtc_softc *sc, time_t *secs) { int err; uint8_t buf[4]; if ((err = iicdev_readfrom(sc->dev, sc->secaddr, buf, sizeof(buf), IIC_WAIT)) == 0) *secs = le32dec(buf); return (err); } static int write_timeword(struct ds13rtc_softc *sc, time_t secs) { uint8_t buf[4]; le32enc(buf, (uint32_t)secs); return (iicdev_writeto(sc->dev, sc->secaddr, buf, sizeof(buf), IIC_WAIT)); } static void ds13rtc_start(void *arg) { struct ds13rtc_softc *sc; uint8_t ctlreg, statreg; sc = arg; /* * Every chip in this family can be usefully initialized by writing 0 to * the control register, except DS1375 which has an external oscillator * controlled by values in the ctlreg that we know nothing about, so * we'd best leave them alone. For all other chips, writing 0 enables * the oscillator, disables signals/outputs in battery-backed mode * (saves power) and disables features like watchdog timers and alarms. */ switch (sc->chiptype) { case TYPE_DS1307: case TYPE_DS1308: case TYPE_DS1338: case TYPE_DS1340: case TYPE_DS1371: case TYPE_DS1372: case TYPE_DS1374: ctlreg = DS130x_R_CONTROL; break; case TYPE_DS1337: case TYPE_DS1339: ctlreg = DS133x_R_CONTROL; break; case TYPE_DS1341: case TYPE_DS1342: ctlreg = DS1341_R_CONTROL; break; case TYPE_DS1375: ctlreg = DS13xx_R_NONE; break; case TYPE_DS1388: ctlreg = DS1388_R_CONTROL; break; default: device_printf(sc->dev, "missing init code for this chiptype\n"); return; } if (ctlreg != DS13xx_R_NONE) write_reg(sc, ctlreg, 0); /* * Common init. Read the OSF/CH status bit and report stopped clocks to * the user. The status bit will be cleared the first time we write * valid time to the chip (and must not be cleared before that). */ if (read_reg(sc, sc->osfaddr, &statreg) != 0) { device_printf(sc->dev, "cannot read RTC clock status bit\n"); return; } if (statreg & DS13xx_B_STATUS_OSF) { device_printf(sc->dev, "WARNING: RTC battery failed; time is invalid\n"); } /* * Figure out whether the chip is configured for AM/PM mode. On all * chips that do AM/PM mode, the flag bit is in the hours register, * which is secaddr+2. */ if ((sc->chiptype != TYPE_DS1340) && !sc->is_binary_counter) { if (read_reg(sc, sc->secaddr + 2, &statreg) != 0) { device_printf(sc->dev, "cannot read RTC clock AM/PM bit\n"); return; } if (statreg & DS13xx_B_HOUR_AMPM) sc->use_ampm = true; } /* * Everything looks good if we make it to here; register as an RTC. * Schedule RTC updates to happen just after top-of-second. */ clock_register_flags(sc->dev, 1000000, CLOCKF_SETTIME_NO_ADJ); clock_schedule(sc->dev, 1); } static int ds13rtc_gettime(device_t dev, struct timespec *ts) { - struct clocktime ct; + struct bcd_clocktime bct; struct time_regs tregs; struct ds13rtc_softc *sc; int err; uint8_t statreg, hourmask; sc = device_get_softc(dev); /* Read the OSF/CH bit; if the clock stopped we can't provide time. */ if ((err = read_reg(sc, sc->osfaddr, &statreg)) != 0) { return (err); } if (statreg & DS13xx_B_STATUS_OSF) return (EINVAL); /* hardware is good, time is not. */ /* If the chip counts time in binary, we just read and return it. */ if (sc->is_binary_counter) { ts->tv_nsec = 0; return (read_timeword(sc, &ts->tv_sec)); } /* * Chip counts in BCD, read and decode it... */ if ((err = read_timeregs(sc, &tregs)) != 0) { device_printf(dev, "cannot read RTC time\n"); return (err); } if (sc->use_ampm) hourmask = DS13xx_M_12HOUR; else hourmask = DS13xx_M_24HOUR; - ct.sec = FROMBCD(tregs.sec & DS13xx_M_SECOND); - ct.min = FROMBCD(tregs.min & DS13xx_M_MINUTE); - ct.hour = FROMBCD(tregs.hour & hourmask); - ct.day = FROMBCD(tregs.day & DS13xx_M_DAY); - ct.mon = FROMBCD(tregs.month & DS13xx_M_MONTH); - ct.year = FROMBCD(tregs.year & DS13xx_M_YEAR); - ct.nsec = 0; + bct.nsec = 0; + bct.ispm = tregs.hour & DS13xx_B_HOUR_PM; + bct.sec = tregs.sec & DS13xx_M_SECOND; + bct.min = tregs.min & DS13xx_M_MINUTE; + bct.hour = tregs.hour & hourmask; + bct.day = tregs.day & DS13xx_M_DAY; + bct.mon = tregs.month & DS13xx_M_MONTH; + bct.year = tregs.year & DS13xx_M_YEAR; - if (sc->use_ampm) { - if (ct.hour == 12) - ct.hour = 0; - if (tregs.hour & DS13xx_B_HOUR_PM) - ct.hour += 12; - } - /* * If this chip has a century bit, honor it. Otherwise let * clock_ct_to_ts() infer the century from the 2-digit year. */ if (sc->use_century) - ct.year += (tregs.month & DS13xx_B_MONTH_CENTURY) ? 2000 : 1900; + bct.year += (tregs.month & DS13xx_B_MONTH_CENTURY) ? 0x100 : 0; - err = clock_ct_to_ts(&ct, ts); + err = clock_bcd_to_ts(&bct, ts, sc->use_ampm); return (err); } static int ds13rtc_settime(device_t dev, struct timespec *ts) { - struct clocktime ct; + struct bcd_clocktime bct; struct time_regs tregs; struct ds13rtc_softc *sc; int err; uint8_t cflag, statreg, 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(); /* If the chip counts time in binary, store tv_sec and we're done. */ if (sc->is_binary_counter) return (write_timeword(sc, ts->tv_sec)); - clock_ts_to_ct(ts, &ct); + clock_ts_to_bcd(ts, &bct, sc->use_ampm); /* If the chip is in AMPM mode deal with the PM flag. */ pmflags = 0; if (sc->use_ampm) { pmflags = DS13xx_B_HOUR_AMPM; - if (ct.hour >= 12) { - ct.hour -= 12; + if (bct.ispm) pmflags |= DS13xx_B_HOUR_PM; - } - if (ct.hour == 0) - ct.hour = 12; } /* If the chip has a century bit, set it as needed. */ cflag = 0; if (sc->use_century) { - if (ct.year >= 2000) + if (bct.year >= 2000) cflag |= DS13xx_B_MONTH_CENTURY; } - tregs.sec = TOBCD(ct.sec); - tregs.min = TOBCD(ct.min); - tregs.hour = TOBCD(ct.hour) | pmflags; - tregs.day = TOBCD(ct.day); - tregs.month = TOBCD(ct.mon) | cflag; - tregs.year = TOBCD(ct.year % 100); - tregs.wday = ct.dow; + tregs.sec = bct.sec; + tregs.min = bct.min; + tregs.hour = bct.hour | pmflags; + tregs.day = bct.day; + tregs.month = bct.mon | cflag; + tregs.year = bct.year & 0xff; + tregs.wday = bct.dow; /* * Set the time. Reset the OSF bit if it is on and it is not part of * the time registers (in which case writing time resets it). */ if ((err = write_timeregs(sc, &tregs)) != 0) goto errout; if (sc->osfaddr != sc->secaddr) { if ((err = read_reg(sc, sc->osfaddr, &statreg)) != 0) goto errout; if (statreg & DS13xx_B_STATUS_OSF) { statreg &= ~DS13xx_B_STATUS_OSF; err = write_reg(sc, sc->osfaddr, statreg); } } errout: if (err != 0) device_printf(dev, "cannot update RTC time\n"); return (err); } static int ds13rtc_get_chiptype(device_t dev) { #ifdef FDT return (ofw_bus_search_compatible(dev, compat_data)->ocd_data); #else ds13_compat_data *cdata; const char *htype; /* * We can only attach if provided a chiptype hint string. */ if (resource_string_value(device_get_name(dev), device_get_unit(dev), "compatible", &htype) != 0) return (TYPE_NONE); /* * Loop through the ofw compat data comparing the hinted chip type to * the compat strings. */ for (cdata = compat_data; cdata->ocd_str != NULL; ++cdata) { if (strcmp(htype, cdata->ocd_str) == 0) break; } return (cdata->ocd_data); #endif } static int ds13rtc_probe(device_t dev) { int chiptype, goodrv; #ifdef FDT if (!ofw_bus_status_okay(dev)) return (ENXIO); goodrv = BUS_PROBE_GENERIC; #else goodrv = BUS_PROBE_NOWILDCARD; #endif chiptype = ds13rtc_get_chiptype(dev); if (chiptype == TYPE_NONE) return (ENXIO); device_set_desc(dev, desc_strings[chiptype]); return (goodrv); } static int ds13rtc_attach(device_t dev) { struct ds13rtc_softc *sc; sc = device_get_softc(dev); sc->dev = dev; sc->busdev = device_get_parent(dev); /* * We need to know what kind of chip we're driving. */ if ((sc->chiptype = ds13rtc_get_chiptype(dev)) == TYPE_NONE) { device_printf(dev, "impossible: cannot determine chip type\n"); return (ENXIO); } /* The seconds register is in the same place on all except DS1388. */ if (sc->chiptype == TYPE_DS1388) sc->secaddr = DS1388_R_SECOND; else sc->secaddr = DS13xx_R_SECOND; /* * The OSF/CH (osc failed/clock-halted) bit appears in different * registers for different chip types. The DS1375 has no OSF indicator * because it has no internal oscillator; we just point to an always- * zero bit in the status register for that chip. */ switch (sc->chiptype) { case TYPE_DS1307: case TYPE_DS1308: case TYPE_DS1338: sc->osfaddr = DS13xx_R_SECOND; break; case TYPE_DS1337: case TYPE_DS1339: case TYPE_DS1341: case TYPE_DS1342: case TYPE_DS1375: sc->osfaddr = DS133x_R_STATUS; sc->use_century = true; break; case TYPE_DS1340: sc->osfaddr = DS1340_R_STATUS; break; case TYPE_DS1371: case TYPE_DS1372: case TYPE_DS1374: sc->osfaddr = DS137x_R_STATUS; sc->is_binary_counter = true; break; case TYPE_DS1388: sc->osfaddr = DS1388_R_STATUS; break; } /* * We have to wait until interrupts are enabled. Sometimes I2C read * and write only works when the interrupts are available. */ config_intrhook_oneshot(ds13rtc_start, sc); return (0); } static int ds13rtc_detach(device_t dev) { clock_unregister(dev); return (0); } static device_method_t ds13rtc_methods[] = { DEVMETHOD(device_probe, ds13rtc_probe), DEVMETHOD(device_attach, ds13rtc_attach), DEVMETHOD(device_detach, ds13rtc_detach), DEVMETHOD(clock_gettime, ds13rtc_gettime), DEVMETHOD(clock_settime, ds13rtc_settime), DEVMETHOD_END }; static driver_t ds13rtc_driver = { "ds13rtc", ds13rtc_methods, sizeof(struct ds13rtc_softc), }; static devclass_t ds13rtc_devclass; DRIVER_MODULE(ds13rtc, iicbus, ds13rtc_driver, ds13rtc_devclass, NULL, NULL); MODULE_VERSION(ds13rtc, 1); MODULE_DEPEND(ds13rtc, iicbus, IICBB_MINVER, IICBB_PREFVER, IICBB_MAXVER); Index: stable/11/sys/dev/iicbus/nxprtc.c =================================================================== --- stable/11/sys/dev/iicbus/nxprtc.c (revision 331495) +++ stable/11/sys/dev/iicbus/nxprtc.c (revision 331496) @@ -1,837 +1,816 @@ /*- * Copyright (c) 2017 Ian Lepore * 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 NXP real-time clock/calendar chips: * - PCF8563 = low power, countdown timer * - PCA8565 = like PCF8563, automotive temperature range * - PCF8523 = low power, countdown timer, oscillator freq tuning, 2 timers * - PCF2127 = like PCF8523, industrial, tcxo, tamper/ts, i2c & spi, 512B ram * - PCA2129 = like PCF8523, automotive, tcxo, tamper/ts, i2c & spi, no timer * - PCF2129 = like PCF8523, industrial, tcxo, tamper/ts, i2c & spi, no timer * * Most chips have a countdown timer, ostensibly intended to generate periodic * interrupt signals on an output pin. The timer is driven from the same * divider chain that clocks the time of day registers, and they start counting * in sync when the STOP bit is cleared after the time and timer registers are * set. The timer register can also be read on the fly, so we use it to count * fractional seconds and get a resolution of ~15ms. */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #endif #include "clock_if.h" #include "iicbus_if.h" /* * I2C address 1010 001x : PCA2129 PCF2127 PCF2129 PCF8563 PCF8565 * I2C address 1101 000x : PCF8523 */ #define PCF8563_ADDR 0xa2 #define PCF8523_ADDR 0xd0 /* * Registers, bits within them, and masks that are common to all chip types. */ #define PCF85xx_R_CS1 0x00 /* CS1 and CS2 control regs are in */ #define PCF85xx_R_CS2 0x01 /* the same location on all chips. */ #define PCF85xx_B_CS1_STOP 0x20 /* Stop time incrementing bit */ #define PCF85xx_B_SECOND_OS 0x80 /* Oscillator Stopped bit */ #define PCF85xx_M_SECOND 0x7f /* Masks for all BCD time regs... */ #define PCF85xx_M_MINUTE 0x7f #define PCF85xx_M_12HOUR 0x1f #define PCF85xx_M_24HOUR 0x3f #define PCF85xx_M_DAY 0x3f #define PCF85xx_M_MONTH 0x1f #define PCF85xx_M_YEAR 0xff /* * PCF2127-specific registers, bits, and masks. */ #define PCF2127_R_TMR_CTL 0x10 /* Timer/watchdog control */ #define PCF2127_M_TMR_CTRL 0xe3 /* Mask off undef bits */ #define PCF2127_B_TMR_CD 0x40 /* Run in countdown mode */ #define PCF2127_B_TMR_64HZ 0x01 /* Timer frequency 64Hz */ /* * PCA/PCF2129-specific registers, bits, and masks. */ #define PCF2129_B_CS1_12HR 0x04 /* Use 12-hour (AM/PM) mode bit */ #define PCF2129_B_CLKOUT_OTPR 0x20 /* OTP refresh command */ #define PCF2129_B_CLKOUT_HIGHZ 0x07 /* Clock Out Freq = disable */ /* * PCF8523-specific registers, bits, and masks. */ #define PCF8523_R_CS3 0x02 /* Control and status reg 3 */ #define PCF8523_R_SECOND 0x03 /* Seconds */ #define PCF8523_R_TMR_CLKOUT 0x0F /* Timer and clockout control */ #define PCF8523_R_TMR_A_FREQ 0x10 /* Timer A frequency control */ #define PCF8523_R_TMR_A_COUNT 0x11 /* Timer A count */ #define PCF8523_M_TMR_A_FREQ 0x07 /* Mask off undef bits */ #define PCF8523_B_HOUR_PM 0x20 /* PM bit */ #define PCF8523_B_CS1_SOFTRESET 0x58 /* Initiate Soft Reset bits */ #define PCF8523_B_CS1_12HR 0x08 /* Use 12-hour (AM/PM) mode bit */ #define PCF8523_B_CLKOUT_TACD 0x02 /* TimerA runs in CountDown mode */ #define PCF8523_B_CLKOUT_HIGHZ 0x38 /* Clock Out Freq = disable */ #define PCF8523_B_TMR_A_64HZ 0x01 /* Timer A freq 64Hz */ #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_BLF 0x04 /* Battery Low Flag bit */ /* * PCF8563-specific registers, bits, and masks. */ #define PCF8563_R_SECOND 0x02 /* Seconds */ #define PCF8563_R_TMR_CTRL 0x0e /* Timer control */ #define PCF8563_R_TMR_COUNT 0x0f /* Timer count */ #define PCF8563_M_TMR_CTRL 0x93 /* Mask off undef bits */ #define PCF8563_B_TMR_ENABLE 0x80 /* Enable countdown timer */ #define PCF8563_B_TMR_64HZ 0x01 /* Timer frequency 64Hz */ #define PCF8563_B_MONTH_C 0x80 /* Century bit */ /* * We use the countdown timer for fractional seconds. We program it for 64 Hz, * the fastest available rate that doesn't roll over in less than a second. */ #define TMR_TICKS_SEC 64 #define TMR_TICKS_HALFSEC 32 /* * The chip types we support. */ enum { TYPE_NONE, TYPE_PCA2129, TYPE_PCA8565, TYPE_PCF2127, TYPE_PCF2129, TYPE_PCF8523, TYPE_PCF8563, TYPE_COUNT }; static const char *desc_strings[] = { "", "NXP PCA2129 RTC", "NXP PCA8565 RTC", "NXP PCF2127 RTC", "NXP PCF2129 RTC", "NXP PCF8523 RTC", "NXP PCF8563 RTC", }; CTASSERT(nitems(desc_strings) == TYPE_COUNT); /* * The time registers in the order they are laid out in hardware. */ struct time_regs { uint8_t sec, min, hour, day, wday, month, year; }; struct nxprtc_softc { device_t dev; device_t busdev; struct intr_config_hook config_hook; u_int flags; /* SC_F_* flags */ u_int chiptype; /* Type of PCF85xx chip */ 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 */ }; #define SC_F_CPOL (1 << 0) /* Century bit means 19xx */ -#define SC_F_AMPM (1 << 1) /* Use PM flag in hours reg */ /* * We use the compat_data table to look up hint strings in the non-FDT case, so * define the struct locally when we don't get it from ofw_bus_subr.h. */ #ifdef FDT typedef struct ofw_compat_data nxprtc_compat_data; #else typedef struct { const char *ocd_str; uintptr_t ocd_data; } nxprtc_compat_data; #endif static nxprtc_compat_data compat_data[] = { {"nxp,pca2129", TYPE_PCA2129}, {"nxp,pca8565", TYPE_PCA8565}, {"nxp,pcf2127", TYPE_PCF2127}, {"nxp,pcf2129", TYPE_PCF2129}, {"nxp,pcf8523", TYPE_PCF8523}, {"nxp,pcf8563", TYPE_PCF8563}, /* Undocumented compat strings known to exist in the wild... */ {"pcf8563", TYPE_PCF8563}, {"phg,pcf8563", TYPE_PCF8563}, {"philips,pcf8563", TYPE_PCF8563}, {NULL, TYPE_NONE}, }; static int read_reg(struct nxprtc_softc *sc, uint8_t reg, uint8_t *val) { return (iicdev_readfrom(sc->dev, reg, val, sizeof(*val), IIC_WAIT)); } static int write_reg(struct nxprtc_softc *sc, uint8_t reg, uint8_t val) { return (iicdev_writeto(sc->dev, reg, &val, sizeof(val), IIC_WAIT)); } static int read_timeregs(struct nxprtc_softc *sc, struct time_regs *tregs, uint8_t *tmr) { int err; uint8_t sec, tmr1, tmr2; /* * The datasheet says loop to read the same timer value twice because it * does not freeze while reading. To that we add our own logic that * the seconds register must be the same before and after reading the * timer, ensuring the fractional part is from the same second as tregs. */ do { if (sc->use_timer) { if ((err = read_reg(sc, sc->secaddr, &sec)) != 0) break; if ((err = read_reg(sc, sc->tmcaddr, &tmr1)) != 0) break; if ((err = read_reg(sc, sc->tmcaddr, &tmr2)) != 0) break; if (tmr1 != tmr2) continue; } if ((err = iicdev_readfrom(sc->dev, sc->secaddr, tregs, sizeof(*tregs), IIC_WAIT)) != 0) break; } while (sc->use_timer && tregs->sec != sec); /* * If the timer value is greater than our hz rate (or is zero), * something is wrong. Maybe some other OS used the timer differently? * Just set it to zero. Likewise if we're not using the timer. After * the offset calc below, the zero turns into 32, the mid-second point, * which in effect performs 4/5 rounding, which is just the right thing * to do if we don't have fine-grained time. */ if (!sc->use_timer || tmr1 > TMR_TICKS_SEC) tmr1 = 0; /* * Turn the downcounter into an upcounter. The timer starts counting at * and rolls over at mid-second, so add half a second worth of ticks to * get its zero point back in sync with the tregs.sec rollover. */ *tmr = (TMR_TICKS_SEC - tmr1 + TMR_TICKS_HALFSEC) % TMR_TICKS_SEC; return (err); } static int write_timeregs(struct nxprtc_softc *sc, struct time_regs *tregs) { return (iicdev_writeto(sc->dev, sc->secaddr, tregs, sizeof(*tregs), IIC_WAIT)); } static int pcf8523_start(struct nxprtc_softc *sc) { int err; uint8_t cs1, cs3, clkout; bool is2129; is2129 = (sc->chiptype == TYPE_PCA2129 || sc->chiptype == TYPE_PCF2129); /* 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"); return (err); } if ((err = read_reg(sc, PCF8523_R_CS3, &cs3)) != 0) { device_printf(sc->dev, "cannot read RTC CS3 control\n"); return (err); } /* * Do a full init (soft-reset) if... * - The chip is in battery-disable mode (fresh from the factory). * - 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); } /* * 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. */ if (is2129) { clkout = PCF2129_B_CLKOUT_HIGHZ; if ((err = write_reg(sc, PCF8523_R_TMR_CLKOUT, clkout)) != 0) { device_printf(sc->dev, "cannot write CLKOUT control\n"); return (err); } if ((err = write_reg(sc, PCF8523_R_TMR_CLKOUT, clkout | PCF2129_B_CLKOUT_OTPR)) != 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; 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"); return (err); } } /* Let someone know if the battery is weak. */ if (cs3 & PCF8523_B_CS3_BLF) device_printf(sc->dev, "WARNING: RTC battery is low\n"); /* Remember whether we're running in AM/PM mode. */ if (is2129) { if (cs1 & PCF2129_B_CS1_12HR) - sc->flags |= SC_F_AMPM; + sc->use_ampm = true; } else { if (cs1 & PCF8523_B_CS1_12HR) - sc->flags |= SC_F_AMPM; + sc->use_ampm = true; } return (0); } static int pcf8523_start_timer(struct nxprtc_softc *sc) { int err; uint8_t clkout, stdclk, stdfreq, tmrfreq; /* * Read the timer control and frequency regs. If they don't have the * values we normally program into them then the timer count doesn't * contain a valid fractional second, so zero it to prevent using a bad * value. Then program the normal timer values so that on the first * settime call we'll begin to use fractional time. */ if ((err = read_reg(sc, PCF8523_R_TMR_A_FREQ, &tmrfreq)) != 0) return (err); if ((err = read_reg(sc, PCF8523_R_TMR_CLKOUT, &clkout)) != 0) return (err); stdfreq = PCF8523_B_TMR_A_64HZ; stdclk = PCF8523_B_CLKOUT_TACD | PCF8523_B_CLKOUT_HIGHZ; if (clkout != stdclk || (tmrfreq & PCF8523_M_TMR_A_FREQ) != stdfreq) { if ((err = write_reg(sc, sc->tmcaddr, 0)) != 0) return (err); if ((err = write_reg(sc, PCF8523_R_TMR_A_FREQ, stdfreq)) != 0) return (err); if ((err = write_reg(sc, PCF8523_R_TMR_CLKOUT, stdclk)) != 0) return (err); } return (0); } static int pcf2127_start_timer(struct nxprtc_softc *sc) { int err; uint8_t stdctl, tmrctl; /* See comment in pcf8523_start_timer(). */ if ((err = read_reg(sc, PCF2127_R_TMR_CTL, &tmrctl)) != 0) return (err); stdctl = PCF2127_B_TMR_CD | PCF8523_B_TMR_A_64HZ; if ((tmrctl & PCF2127_M_TMR_CTRL) != stdctl) { if ((err = write_reg(sc, sc->tmcaddr, 0)) != 0) return (err); if ((err = write_reg(sc, PCF2127_R_TMR_CTL, stdctl)) != 0) return (err); } return (0); } static int pcf8563_start_timer(struct nxprtc_softc *sc) { int err; uint8_t stdctl, tmrctl; /* See comment in pcf8523_start_timer(). */ if ((err = read_reg(sc, PCF8563_R_TMR_CTRL, &tmrctl)) != 0) return (err); stdctl = PCF8563_B_TMR_ENABLE | PCF8563_B_TMR_64HZ; if ((tmrctl & PCF8563_M_TMR_CTRL) != stdctl) { if ((err = write_reg(sc, sc->tmcaddr, 0)) != 0) return (err); if ((err = write_reg(sc, PCF8563_R_TMR_CTRL, stdctl)) != 0) return (err); } return (0); } static void 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); /* First do chip-specific inits. */ switch (sc->chiptype) { case TYPE_PCA2129: case TYPE_PCF2129: if (pcf8523_start(sc) != 0) return; /* No timer to start */ break; case TYPE_PCF2127: if (pcf8523_start(sc) != 0) return; if (pcf2127_start_timer(sc) != 0) { device_printf(sc->dev, "cannot set up timer\n"); return; } break; case TYPE_PCF8523: if (pcf8523_start(sc) != 0) return; if (pcf8523_start_timer(sc) != 0) { device_printf(sc->dev, "cannot set up timer\n"); return; } break; case TYPE_PCA8565: case TYPE_PCF8563: if (pcf8563_start_timer(sc) != 0) { device_printf(sc->dev, "cannot set up timer\n"); return; } break; default: device_printf(sc->dev, "missing init code for this chiptype\n"); return; } /* * 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 * when setting it so our error is an average .5s when reading it. * Schedule our clock_settime() method to be called at a .495ms offset * into the second, because the clock hardware resets the divider chain * to the mid-second point when you set the time and it takes about 5ms * of i2c bus activity to set the clock. */ resolution = sc->use_timer ? 1000000 / TMR_TICKS_SEC : 1000000 / 2; clockflags = CLOCKF_GETTIME_NO_ADJ | CLOCKF_SETTIME_NO_TS; clock_register_flags(sc->dev, resolution, clockflags); clock_schedule(sc->dev, 495000000); } static int nxprtc_gettime(device_t dev, struct timespec *ts) { - struct clocktime ct; + struct bcd_clocktime bct; struct time_regs tregs; struct nxprtc_softc *sc; int err; uint8_t cs1, hourmask, tmrcount; sc = device_get_softc(dev); /* * Read the time, but before using it, validate that the oscillator- * stopped/power-fail bit is not set, and that the time-increment STOP * bit is not set in the control reg. The latter can happen if there * was an error when setting the time. */ if ((err = read_timeregs(sc, &tregs, &tmrcount)) != 0) { device_printf(dev, "cannot read RTC time\n"); return (err); } if ((err = read_reg(sc, PCF85xx_R_CS1, &cs1)) != 0) { device_printf(dev, "cannot read RTC time\n"); return (err); } if ((tregs.sec & PCF85xx_B_SECOND_OS) || (cs1 & PCF85xx_B_CS1_STOP)) { device_printf(dev, "RTC clock not running\n"); return (EINVAL); /* hardware is good, time is not. */ } - if (sc->flags & SC_F_AMPM) + if (sc->use_ampm) hourmask = PCF85xx_M_12HOUR; else hourmask = PCF85xx_M_24HOUR; - ct.nsec = ((uint64_t)tmrcount * 1000000000) / TMR_TICKS_SEC; - ct.sec = FROMBCD(tregs.sec & PCF85xx_M_SECOND); - ct.min = FROMBCD(tregs.min & PCF85xx_M_MINUTE); - ct.hour = FROMBCD(tregs.hour & hourmask); - ct.day = FROMBCD(tregs.day & PCF85xx_M_DAY); - ct.mon = FROMBCD(tregs.month & PCF85xx_M_MONTH); - ct.year = FROMBCD(tregs.year & PCF85xx_M_YEAR); - ct.year += 1900; - if (ct.year < POSIX_BASE_YEAR) - ct.year += 100; /* assume [1970, 2069] */ + bct.nsec = ((uint64_t)tmrcount * 1000000000) / TMR_TICKS_SEC; + bct.ispm = (tregs.hour & PCF8523_B_HOUR_PM) != 0; + bct.sec = tregs.sec & PCF85xx_M_SECOND; + bct.min = tregs.min & PCF85xx_M_MINUTE; + bct.hour = tregs.hour & hourmask; + bct.day = tregs.day & PCF85xx_M_DAY; + bct.mon = tregs.month & PCF85xx_M_MONTH; + bct.year = tregs.year & PCF85xx_M_YEAR; /* * Old PCF8563 datasheets recommended that the C bit be 1 for 19xx and 0 * for 20xx; newer datasheets don't recommend that. We don't care, * but we may co-exist with other OSes sharing the hardware. Determine * existing polarity on a read so that we can preserve it on a write. */ if (sc->chiptype == TYPE_PCF8563) { if (tregs.month & PCF8563_B_MONTH_C) { - if (ct.year >= 2000) + if (bct.year < 0x70) sc->flags |= SC_F_CPOL; - } else if (ct.year < 2000) + } else if (bct.year >= 0x70) sc->flags |= SC_F_CPOL; } - /* If this chip is running in 12-hour/AMPM mode, deal with it. */ - if (sc->flags & SC_F_AMPM) { - if (ct.hour == 12) - ct.hour = 0; - if (tregs.hour & PCF8523_B_HOUR_PM) - ct.hour += 12; - } - - err = clock_ct_to_ts(&ct, ts); + err = clock_bcd_to_ts(&bct, ts, sc->use_ampm); ts->tv_sec += utc_offset(); return (err); } static int nxprtc_settime(device_t dev, struct timespec *ts) { - struct clocktime ct; + struct bcd_clocktime bct; struct time_regs tregs; struct nxprtc_softc *sc; int err; - uint8_t cflag, cs1, pmflag; + uint8_t cflag, cs1; sc = device_get_softc(dev); /* * We stop the clock, set the time, then restart the clock. Half a * second after restarting the clock it ticks over to the next second. * So to align the RTC, we schedule this function to be called when * system time is roughly halfway (.495) through the current second. * * Reserve use of the i2c bus and stop the RTC clock. Note that if * anything goes wrong from this point on, we leave the clock stopped, * because we don't really know what state it's in. */ if ((err = iicbus_request_bus(sc->busdev, sc->dev, IIC_WAIT)) != 0) return (err); if ((err = read_reg(sc, PCF85xx_R_CS1, &cs1)) != 0) goto errout; cs1 |= PCF85xx_B_CS1_STOP; if ((err = write_reg(sc, PCF85xx_R_CS1, cs1)) != 0) goto errout; /* Grab a fresh post-sleep idea of what time it is. */ getnanotime(ts); ts->tv_sec -= utc_offset(); ts->tv_nsec = 0; - clock_ts_to_ct(ts, &ct); + clock_ts_to_bcd(ts, &bct, sc->use_ampm); - /* If the chip is in AMPM mode deal with the PM flag. */ - pmflag = 0; - if (sc->flags & SC_F_AMPM) { - if (ct.hour >= 12) { - ct.hour -= 12; - pmflag = PCF8523_B_HOUR_PM; - } - if (ct.hour == 0) - ct.hour = 12; - } - /* On 8563 set the century based on the polarity seen when reading. */ cflag = 0; if (sc->chiptype == TYPE_PCF8563) { if ((sc->flags & SC_F_CPOL) != 0) { - if (ct.year >= 2000) + if (bct.year >= 0x2000) cflag = PCF8563_B_MONTH_C; - } else if (ct.year < 2000) + } else if (bct.year < 0x2000) cflag = PCF8563_B_MONTH_C; } - tregs.sec = TOBCD(ct.sec); - tregs.min = TOBCD(ct.min); - tregs.hour = TOBCD(ct.hour) | pmflag; - tregs.day = TOBCD(ct.day); - tregs.month = TOBCD(ct.mon); - tregs.year = TOBCD(ct.year % 100) | cflag; - tregs.wday = ct.dow; + tregs.sec = bct.sec; + tregs.min = bct.min; + tregs.hour = bct.hour | (bct.ispm ? PCF8523_B_HOUR_PM : 0); + tregs.day = bct.day; + tregs.month = bct.mon; + tregs.year = (bct.year & 0xff) | cflag; + tregs.wday = bct.dow; /* * Set the time, reset the timer count register, then start the clocks. */ if ((err = write_timeregs(sc, &tregs)) != 0) goto errout; if ((err = write_reg(sc, sc->tmcaddr, TMR_TICKS_SEC)) != 0) return (err); cs1 &= ~PCF85xx_B_CS1_STOP; err = write_reg(sc, PCF85xx_R_CS1, cs1); errout: iicbus_release_bus(sc->busdev, sc->dev); if (err != 0) device_printf(dev, "cannot write RTC time\n"); return (err); } static int nxprtc_get_chiptype(device_t dev) { #ifdef FDT return (ofw_bus_search_compatible(dev, compat_data)->ocd_data); #else nxprtc_compat_data *cdata; const char *htype; int chiptype; /* * If given a chiptype hint string, loop through the ofw compat data * comparing the hinted chip type to the compat strings. The table end * marker ocd_data is TYPE_NONE. */ if (resource_string_value(device_get_name(dev), device_get_unit(dev), "compatible", &htype) == 0) { for (cdata = compat_data; cdata->ocd_str != NULL; ++cdata) { if (strcmp(htype, cdata->ocd_str) == 0) break; } chiptype = cdata->ocd_data; } else chiptype = TYPE_NONE; /* * On non-FDT systems the historical behavior of this driver was to * assume a PCF8563; keep doing that for compatibility. */ if (chiptype == TYPE_NONE) return (TYPE_PCF8563); else return (chiptype); #endif } static int nxprtc_probe(device_t dev) { int chiptype, rv; #ifdef FDT if (!ofw_bus_status_okay(dev)) return (ENXIO); rv = BUS_PROBE_GENERIC; #else rv = BUS_PROBE_NOWILDCARD; #endif if ((chiptype = nxprtc_get_chiptype(dev)) == TYPE_NONE) return (ENXIO); device_set_desc(dev, desc_strings[chiptype]); return (rv); } static int nxprtc_attach(device_t dev) { struct nxprtc_softc *sc; sc = device_get_softc(dev); sc->dev = dev; sc->busdev = device_get_parent(dev); /* We need to know what kind of chip we're driving. */ sc->chiptype = nxprtc_get_chiptype(dev); /* The features and some register addresses vary by chip type. */ switch (sc->chiptype) { case TYPE_PCA2129: case TYPE_PCF2129: sc->secaddr = PCF8523_R_SECOND; sc->tmcaddr = 0; sc->use_timer = false; break; case TYPE_PCF2127: case TYPE_PCF8523: sc->secaddr = PCF8523_R_SECOND; sc->tmcaddr = PCF8523_R_TMR_A_COUNT; sc->use_timer = true; break; case TYPE_PCA8565: case TYPE_PCF8563: sc->secaddr = PCF8563_R_SECOND; sc->tmcaddr = PCF8563_R_TMR_COUNT; sc->use_timer = true; break; default: device_printf(dev, "impossible: cannot determine chip type\n"); return (ENXIO); } /* * We have to wait until interrupts are enabled. Sometimes I2C read * and write only works when the interrupts are available. */ sc->config_hook.ich_func = nxprtc_start; sc->config_hook.ich_arg = dev; if (config_intrhook_establish(&sc->config_hook) != 0) return (ENOMEM); return (0); } static int nxprtc_detach(device_t dev) { clock_unregister(dev); return (0); } static device_method_t nxprtc_methods[] = { DEVMETHOD(device_probe, nxprtc_probe), DEVMETHOD(device_attach, nxprtc_attach), DEVMETHOD(device_detach, nxprtc_detach), DEVMETHOD(clock_gettime, nxprtc_gettime), DEVMETHOD(clock_settime, nxprtc_settime), DEVMETHOD_END }; static driver_t nxprtc_driver = { "nxprtc", nxprtc_methods, sizeof(struct nxprtc_softc), }; static devclass_t nxprtc_devclass; DRIVER_MODULE(nxprtc, iicbus, nxprtc_driver, nxprtc_devclass, NULL, NULL); MODULE_VERSION(nxprtc, 1); MODULE_DEPEND(nxprtc, iicbus, IICBB_MINVER, IICBB_PREFVER, IICBB_MAXVER); Index: stable/11/sys/kern/subr_clock.c =================================================================== --- stable/11/sys/kern/subr_clock.c (revision 331495) +++ stable/11/sys/kern/subr_clock.c (revision 331496) @@ -1,254 +1,331 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1988 University of Utah. * Copyright (c) 1982, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * the Systems Programming Group of the University of Utah Computer * Science Department. * * 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. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * from: Utah $Hdr: clock.c 1.18 91/01/21$ * from: @(#)clock.c 8.2 (Berkeley) 1/12/94 * from: NetBSD: clock_subr.c,v 1.6 2001/07/07 17:04:02 thorpej Exp * and * from: src/sys/i386/isa/clock.c,v 1.176 2001/09/04 */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include int tz_minuteswest; int tz_dsttime; /* * The adjkerntz and wall_cmos_clock sysctls are in the "machdep" sysctl * namespace because they were misplaced there originally. */ static int adjkerntz; static int sysctl_machdep_adjkerntz(SYSCTL_HANDLER_ARGS) { int error; error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req); if (!error && req->newptr) resettodr(); return (error); } SYSCTL_PROC(_machdep, OID_AUTO, adjkerntz, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, &adjkerntz, 0, sysctl_machdep_adjkerntz, "I", "Local offset from UTC in seconds"); static int ct_debug; SYSCTL_INT(_debug, OID_AUTO, clocktime, CTLFLAG_RWTUN, &ct_debug, 0, "Enable printing of clocktime debugging"); static int wall_cmos_clock; SYSCTL_INT(_machdep, OID_AUTO, wall_cmos_clock, CTLFLAG_RW, &wall_cmos_clock, 0, "Enables application of machdep.adjkerntz"); /*--------------------------------------------------------------------* * Generic routines to convert between a POSIX date * (seconds since 1/1/1970) and yr/mo/day/hr/min/sec * Derived from NetBSD arch/hp300/hp300/clock.c */ #define FEBRUARY 2 #define days_in_year(y) (leapyear(y) ? 366 : 365) #define days_in_month(y, m) \ (month_days[(m) - 1] + (m == FEBRUARY ? leapyear(y) : 0)) /* Day of week. Days are counted from 1/1/1970, which was a Thursday */ #define day_of_week(days) (((days) + 4) % 7) static const int month_days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; /* * Optimization: using a precomputed count of days between POSIX_BASE_YEAR and * some recent year avoids lots of unnecessary loop iterations in conversion. * recent_base_days is the number of days before the start of recent_base_year. */ static const int recent_base_year = 2017; static const int recent_base_days = 17167; /* * This inline avoids some unnecessary modulo operations * as compared with the usual macro: * ( ((year % 4) == 0 && * (year % 100) != 0) || * ((year % 400) == 0) ) * It is otherwise equivalent. */ static int leapyear(int year) { int rv = 0; if ((year & 3) == 0) { rv = 1; if ((year % 100) == 0) { rv = 0; if ((year % 400) == 0) rv = 1; } } return (rv); } static void print_ct(struct clocktime *ct) { printf("[%04d-%02d-%02d %02d:%02d:%02d]", ct->year, ct->mon, ct->day, ct->hour, ct->min, ct->sec); } int clock_ct_to_ts(struct clocktime *ct, struct timespec *ts) { int i, year, days; if (ct_debug) { printf("ct_to_ts("); print_ct(ct); printf(")"); } /* * Many realtime clocks store the year as 2-digit BCD; pivot on 70 to * determine century. Some clocks have a "century bit" and drivers do * year += 100, so interpret values between 70-199 as relative to 1900. */ year = ct->year; if (year < 70) year += 2000; else if (year < 200) year += 1900; /* Sanity checks. */ if (ct->mon < 1 || ct->mon > 12 || ct->day < 1 || ct->day > days_in_month(year, ct->mon) || ct->hour > 23 || ct->min > 59 || ct->sec > 59 || year < 1970 || (sizeof(time_t) == 4 && year > 2037)) { /* time_t overflow */ if (ct_debug) printf(" = EINVAL\n"); return (EINVAL); } /* * Compute days since start of time * First from years, then from months. */ if (year >= recent_base_year) { i = recent_base_year; days = recent_base_days; } else { i = POSIX_BASE_YEAR; days = 0; } for (; i < year; i++) days += days_in_year(i); /* Months */ for (i = 1; i < ct->mon; i++) days += days_in_month(year, i); days += (ct->day - 1); ts->tv_sec = (((time_t)days * 24 + ct->hour) * 60 + ct->min) * 60 + ct->sec; ts->tv_nsec = ct->nsec; if (ct_debug) printf(" = %jd.%09ld\n", (intmax_t)ts->tv_sec, ts->tv_nsec); return (0); } +int +clock_bcd_to_ts(struct bcd_clocktime *bct, struct timespec *ts, bool ampm) +{ + struct clocktime ct; + int bcent, byear; + + /* + * Year may come in as 2-digit or 4-digit BCD. Split the value into + * separate BCD century and year values for validation and conversion. + */ + bcent = bct->year >> 8; + byear = bct->year & 0xff; + + /* + * Ensure that all values are valid BCD numbers, to avoid assertions in + * the BCD-to-binary conversion routines. clock_ct_to_ts() will further + * validate the field ranges (such as 0 <= min <= 59) during conversion. + */ + if (!validbcd(bcent) || !validbcd(byear) || !validbcd(bct->mon) || + !validbcd(bct->day) || !validbcd(bct->hour) || + !validbcd(bct->min) || !validbcd(bct->sec)) { + if (ct_debug) + printf("clock_bcd_to_ts: bad BCD: " + "[%04x-%02x-%02x %02x:%02x:%02x]\n", + bct->year, bct->mon, bct->day, + bct->hour, bct->min, bct->sec); + return (EINVAL); + } + + ct.year = FROMBCD(byear) + FROMBCD(bcent) * 100; + ct.mon = FROMBCD(bct->mon); + ct.day = FROMBCD(bct->day); + ct.hour = FROMBCD(bct->hour); + ct.min = FROMBCD(bct->min); + ct.sec = FROMBCD(bct->sec); + ct.dow = bct->dow; + ct.nsec = bct->nsec; + + /* If asked to handle am/pm, convert from 12hr+pmflag to 24hr. */ + if (ampm) { + if (ct.hour == 12) + ct.hour = 0; + if (bct->ispm) + ct.hour += 12; + } + + return (clock_ct_to_ts(&ct, ts)); +} + void clock_ts_to_ct(struct timespec *ts, struct clocktime *ct) { int i, year, days; time_t rsec; /* remainder seconds */ time_t secs; secs = ts->tv_sec; days = secs / SECDAY; rsec = secs % SECDAY; ct->dow = day_of_week(days); /* Subtract out whole years. */ if (days >= recent_base_days) { year = recent_base_year; days -= recent_base_days; } else { year = POSIX_BASE_YEAR; } for (; days >= days_in_year(year); year++) days -= days_in_year(year); ct->year = year; /* Subtract out whole months, counting them in i. */ for (i = 1; days >= days_in_month(year, i); i++) days -= days_in_month(year, i); ct->mon = i; /* Days are what is left over (+1) from all that. */ ct->day = days + 1; /* Hours, minutes, seconds are easy */ ct->hour = rsec / 3600; rsec = rsec % 3600; ct->min = rsec / 60; rsec = rsec % 60; ct->sec = rsec; ct->nsec = ts->tv_nsec; if (ct_debug) { printf("ts_to_ct(%jd.%09ld) = ", (intmax_t)ts->tv_sec, ts->tv_nsec); print_ct(ct); printf("\n"); } +} + +void +clock_ts_to_bcd(struct timespec *ts, struct bcd_clocktime *bct, bool ampm) +{ + struct clocktime ct; + + clock_ts_to_ct(ts, &ct); + + /* If asked to handle am/pm, convert from 24hr to 12hr+pmflag. */ + bct->ispm = false; + if (ampm) { + if (ct.hour >= 12) { + ct.hour -= 12; + bct->ispm = true; + } + if (ct.hour == 0) + ct.hour = 12; + } + + bct->year = TOBCD(ct.year % 100) | (TOBCD(ct.year / 100) << 8); + bct->mon = TOBCD(ct.mon); + bct->day = TOBCD(ct.day); + bct->hour = TOBCD(ct.hour); + bct->min = TOBCD(ct.min); + bct->sec = TOBCD(ct.sec); + bct->dow = ct.dow; + bct->nsec = ct.nsec; } int utc_offset(void) { return (tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0)); } Index: stable/11/sys/sys/clock.h =================================================================== --- stable/11/sys/sys/clock.h (revision 331495) +++ stable/11/sys/sys/clock.h (revision 331496) @@ -1,135 +1,185 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1996 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Gordon W. Ross * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. * * $NetBSD: clock_subr.h,v 1.7 2000/10/03 13:41:07 tsutsui Exp $ * * * This file is the central clearing-house for calendrical issues. * * In general the kernel does not know about minutes, hours, days, timezones, * daylight savings time, leap-years and such. All that is theoretically a * matter for userland only. * * Parts of kernel code does however care: badly designed filesystems store * timestamps in local time and RTC chips sometimes track time in a local * timezone instead of UTC and so on. * * All that code should go here for service. * * $FreeBSD$ */ #ifndef _SYS_CLOCK_H_ #define _SYS_CLOCK_H_ #ifdef _KERNEL /* No user serviceable parts */ /* * Timezone info from settimeofday(2), usually not used */ extern int tz_minuteswest; extern int tz_dsttime; int utc_offset(void); /* - * Structure to hold the values typically reported by time-of-day clocks. - * This can be passed to the generic conversion functions to be converted - * to a struct timespec. + * Structure to hold the values typically reported by time-of-day clocks, + * expressed as binary integers (see below for a BCD version). This can be + * passed to the conversion functions to be converted to/from a struct timespec. + * + * On input, the year is interpreted as follows: + * 0 - 69 = 2000 - 2069 + * 70 - 99 = 1970 - 1999 + * 100 - 199 = 2000 - 2099 (Supports hardware "century bit".) + * 200 - 1969 = Invalid. + * 1970 - 9999 = Full 4-digit century+year. + * + * The dow field is ignored (not even validated) on input, but is always + * populated with day-of-week on output. + * + * clock_ct_to_ts() returns EINVAL if any values are out of range. The year + * field will always be 4-digit on output. */ struct clocktime { int year; /* year (4 digit year) */ int mon; /* month (1 - 12) */ int day; /* day (1 - 31) */ int hour; /* hour (0 - 23) */ int min; /* minute (0 - 59) */ int sec; /* second (0 - 59) */ int dow; /* day of week (0 - 6; 0 = Sunday) */ long nsec; /* nano seconds */ }; int clock_ct_to_ts(struct clocktime *, struct timespec *); void clock_ts_to_ct(struct timespec *, struct clocktime *); + +/* + * Structure to hold the values typically reported by time-of-day clocks, + * expressed as BCD. This can be passed to the conversion functions to be + * converted to/from a struct timespec. + * + * The clock_bcd_to_ts() function interprets the values in the year through sec + * fields as BCD numbers, and returns EINVAL if any BCD values are out of range. + * After conversion to binary, the values are passed to clock_ct_to_ts() and + * undergo further validation as described above. Year may be 2 or 4-digit BCD, + * interpreted as described above. The nsec field is binary. If the ampm arg + * is true, the incoming hour and ispm values are interpreted as 12-hour am/pm + * representation of the hour, otherwise hour is interpreted as 24-hour and ispm + * is ignored. + * + * The clock_ts_to_bcd() function converts the timespec to BCD values stored + * into year through sec. The value in year will be 4-digit BCD (e.g., + * 0x2017). The mon through sec values will be 2-digit BCD. The nsec field will + * be binary, and the range of dow makes its binary and BCD values identical. + * If the ampm arg is true, the hour and ispm fields are set to the 12-hour + * time plus a pm flag, otherwise the hour is set to 24-hour time and ispm is + * set to false. + */ +struct bcd_clocktime { + uint16_t year; /* year (2 or 4 digit year) */ + uint8_t mon; /* month (1 - 12) */ + uint8_t day; /* day (1 - 31) */ + uint8_t hour; /* hour (0 - 23 or 1 - 12) */ + uint8_t min; /* minute (0 - 59) */ + uint8_t sec; /* second (0 - 59) */ + uint8_t dow; /* day of week (0 - 6; 0 = Sunday) */ + long nsec; /* nanoseconds */ + bool ispm; /* true if hour represents pm time */ +}; + +int clock_bcd_to_ts(struct bcd_clocktime *, struct timespec *, bool ampm); +void clock_ts_to_bcd(struct timespec *, struct bcd_clocktime *, bool ampm); /* * Time-of-day clock functions and flags. These functions might sleep. * * clock_register and clock_unregister() do what they say. Upon return from * unregister, the clock's methods are not running and will not be called again. * * clock_schedule() requests that a registered clock's clock_settime() calls * happen at the given offset into the second. The default is 0, meaning no * specific scheduling. To schedule the call as soon after top-of-second as * possible, specify 1. Each clock has its own schedule, but taskqueue_thread * is shared by many tasks; the timing of the call is not guaranteed. * * Flags: * * CLOCKF_SETTIME_NO_TS * Do not pass a timespec to clock_settime(), the driver obtains its own time * and applies its own adjustments (this flag implies CLOCKF_SETTIME_NO_ADJ). * * CLOCKF_SETTIME_NO_ADJ * Do not apply utc offset and resolution/accuracy adjustments to the value * passed to clock_settime(), the driver applies them itself. * * CLOCKF_GETTIME_NO_ADJ * Do not apply utc offset and resolution/accuracy adjustments to the value * returned from clock_gettime(), the driver has already applied them. */ #define CLOCKF_SETTIME_NO_TS 0x00000001 #define CLOCKF_SETTIME_NO_ADJ 0x00000002 #define CLOCKF_GETTIME_NO_ADJ 0x00000004 void clock_register(device_t _clockdev, long _resolution_us); void clock_register_flags(device_t _clockdev, long _resolution_us, int _flags); void clock_schedule(device_t clockdev, u_int _offsetns); void clock_unregister(device_t _clockdev); /* * BCD to decimal and decimal to BCD. */ #define FROMBCD(x) bcd2bin(x) #define TOBCD(x) bin2bcd(x) /* Some handy constants. */ #define SECDAY (24 * 60 * 60) #define SECYR (SECDAY * 365) /* Traditional POSIX base year */ #define POSIX_BASE_YEAR 1970 void timespec2fattime(struct timespec *tsp, int utc, u_int16_t *ddp, u_int16_t *dtp, u_int8_t *dhp); void fattime2timespec(unsigned dd, unsigned dt, unsigned dh, int utc, struct timespec *tsp); #endif /* _KERNEL */ #endif /* !_SYS_CLOCK_H_ */ Index: stable/11/sys/x86/isa/atrtc.c =================================================================== --- stable/11/sys/x86/isa/atrtc.c (revision 331495) +++ stable/11/sys/x86/isa/atrtc.c (revision 331496) @@ -1,425 +1,428 @@ /*- * Copyright (c) 2008 Poul-Henning Kamp * Copyright (c) 2010 Alexander Motin * 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$ */ #include __FBSDID("$FreeBSD$"); #include "opt_isa.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEV_ISA #include #include #endif #include #include "clock_if.h" /* * clock_lock protects low-level access to individual hardware registers. * atrtc_time_lock protects the entire sequence of accessing multiple registers * to read or write the date and time. */ #define RTC_LOCK do { if (!kdb_active) mtx_lock_spin(&clock_lock); } while (0) #define RTC_UNLOCK do { if (!kdb_active) mtx_unlock_spin(&clock_lock); } while (0) struct mtx atrtc_time_lock; MTX_SYSINIT(atrtc_lock_init, &atrtc_time_lock, "atrtc", MTX_DEF); int atrtcclock_disable = 0; static int rtc_reg = -1; static u_char rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF; static u_char rtc_statusb = RTCSB_24HR; /* * RTC support routines */ -int -rtcin(int reg) +static inline u_char +rtcin_locked(int reg) { - u_char val; - RTC_LOCK; if (rtc_reg != reg) { inb(0x84); outb(IO_RTC, reg); rtc_reg = reg; inb(0x84); } - val = inb(IO_RTC + 1); - RTC_UNLOCK; - return (val); + return (inb(IO_RTC + 1)); } -void -writertc(int reg, u_char val) +static inline void +rtcout_locked(int reg, u_char val) { - RTC_LOCK; if (rtc_reg != reg) { inb(0x84); outb(IO_RTC, reg); rtc_reg = reg; inb(0x84); } outb(IO_RTC + 1, val); inb(0x84); +} + +int +rtcin(int reg) +{ + u_char val; + + RTC_LOCK; + val = rtcin_locked(reg); RTC_UNLOCK; + return (val); } -static __inline int -readrtc(int port) +void +writertc(int reg, u_char val) { - return(bcd2bin(rtcin(port))); + + RTC_LOCK; + rtcout_locked(reg, val); + RTC_UNLOCK; } static void atrtc_start(void) { writertc(RTC_STATUSA, rtc_statusa); writertc(RTC_STATUSB, RTCSB_24HR); } static void atrtc_rate(unsigned rate) { rtc_statusa = RTCSA_DIVIDER | rate; writertc(RTC_STATUSA, rtc_statusa); } static void atrtc_enable_intr(void) { rtc_statusb |= RTCSB_PINTR; writertc(RTC_STATUSB, rtc_statusb); rtcin(RTC_INTR); } static void atrtc_disable_intr(void) { rtc_statusb &= ~RTCSB_PINTR; writertc(RTC_STATUSB, rtc_statusb); rtcin(RTC_INTR); } void atrtc_restore(void) { /* Restore all of the RTC's "status" (actually, control) registers. */ rtcin(RTC_STATUSA); /* dummy to get rtc_reg set */ writertc(RTC_STATUSB, RTCSB_24HR); writertc(RTC_STATUSA, rtc_statusa); writertc(RTC_STATUSB, rtc_statusb); rtcin(RTC_INTR); } -static void -atrtc_set(struct timespec *ts) -{ - struct clocktime ct; - - clock_ts_to_ct(ts, &ct); - - mtx_lock(&atrtc_time_lock); - - /* Disable RTC updates and interrupts. */ - writertc(RTC_STATUSB, RTCSB_HALT | RTCSB_24HR); - - writertc(RTC_SEC, bin2bcd(ct.sec)); /* Write back Seconds */ - writertc(RTC_MIN, bin2bcd(ct.min)); /* Write back Minutes */ - writertc(RTC_HRS, bin2bcd(ct.hour)); /* Write back Hours */ - - writertc(RTC_WDAY, ct.dow + 1); /* Write back Weekday */ - writertc(RTC_DAY, bin2bcd(ct.day)); /* Write back Day */ - writertc(RTC_MONTH, bin2bcd(ct.mon)); /* Write back Month */ - writertc(RTC_YEAR, bin2bcd(ct.year % 100)); /* Write back Year */ -#ifdef USE_RTC_CENTURY - writertc(RTC_CENTURY, bin2bcd(ct.year / 100)); /* ... and Century */ -#endif - - /* Re-enable RTC updates and interrupts. */ - writertc(RTC_STATUSB, rtc_statusb); - rtcin(RTC_INTR); - - mtx_unlock(&atrtc_time_lock); -} - /********************************************************************** * RTC driver for subr_rtc */ struct atrtc_softc { int port_rid, intr_rid; struct resource *port_res; struct resource *intr_res; void *intr_handler; struct eventtimer et; }; static int rtc_start(struct eventtimer *et, sbintime_t first, sbintime_t period) { atrtc_rate(max(fls(period + (period >> 1)) - 17, 1)); atrtc_enable_intr(); return (0); } static int rtc_stop(struct eventtimer *et) { atrtc_disable_intr(); return (0); } /* * This routine receives statistical clock interrupts from the RTC. * As explained above, these occur at 128 interrupts per second. * When profiling, we receive interrupts at a rate of 1024 Hz. * * This does not actually add as much overhead as it sounds, because * when the statistical clock is active, the hardclock driver no longer * needs to keep (inaccurate) statistics on its own. This decouples * statistics gathering from scheduling interrupts. * * The RTC chip requires that we read status register C (RTC_INTR) * to acknowledge an interrupt, before it will generate the next one. * Under high interrupt load, rtcintr() can be indefinitely delayed and * the clock can tick immediately after the read from RTC_INTR. In this * case, the mc146818A interrupt signal will not drop for long enough * to register with the 8259 PIC. If an interrupt is missed, the stat * clock will halt, considerably degrading system performance. This is * why we use 'while' rather than a more straightforward 'if' below. * Stat clock ticks can still be lost, causing minor loss of accuracy * in the statistics, but the stat clock will no longer stop. */ static int rtc_intr(void *arg) { struct atrtc_softc *sc = (struct atrtc_softc *)arg; int flag = 0; while (rtcin(RTC_INTR) & RTCIR_PERIOD) { flag = 1; if (sc->et.et_active) sc->et.et_event_cb(&sc->et, sc->et.et_arg); } return(flag ? FILTER_HANDLED : FILTER_STRAY); } /* * Attach to the ISA PnP descriptors for the timer and realtime clock. */ static struct isa_pnp_id atrtc_ids[] = { { 0x000bd041 /* PNP0B00 */, "AT realtime clock" }, { 0 } }; static int atrtc_probe(device_t dev) { int result; result = ISA_PNP_PROBE(device_get_parent(dev), dev, atrtc_ids); /* ENOENT means no PnP-ID, device is hinted. */ if (result == ENOENT) { device_set_desc(dev, "AT realtime clock"); return (BUS_PROBE_LOW_PRIORITY); } return (result); } static int atrtc_attach(device_t dev) { struct atrtc_softc *sc; rman_res_t s; int i; sc = device_get_softc(dev); sc->port_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->port_rid, IO_RTC, IO_RTC + 1, 2, RF_ACTIVE); if (sc->port_res == NULL) device_printf(dev, "Warning: Couldn't map I/O.\n"); atrtc_start(); clock_register(dev, 1000000); bzero(&sc->et, sizeof(struct eventtimer)); if (!atrtcclock_disable && (resource_int_value(device_get_name(dev), device_get_unit(dev), "clock", &i) != 0 || i != 0)) { sc->intr_rid = 0; while (bus_get_resource(dev, SYS_RES_IRQ, sc->intr_rid, &s, NULL) == 0 && s != 8) sc->intr_rid++; sc->intr_res = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->intr_rid, 8, 8, 1, RF_ACTIVE); if (sc->intr_res == NULL) { device_printf(dev, "Can't map interrupt.\n"); return (0); } else if ((bus_setup_intr(dev, sc->intr_res, INTR_TYPE_CLK, rtc_intr, NULL, sc, &sc->intr_handler))) { device_printf(dev, "Can't setup interrupt.\n"); return (0); } else { /* Bind IRQ to BSP to avoid live migration. */ bus_bind_intr(dev, sc->intr_res, 0); } sc->et.et_name = "RTC"; sc->et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_POW2DIV; sc->et.et_quality = 0; sc->et.et_frequency = 32768; sc->et.et_min_period = 0x00080000; sc->et.et_max_period = 0x80000000; sc->et.et_start = rtc_start; sc->et.et_stop = rtc_stop; sc->et.et_priv = dev; et_register(&sc->et); } return(0); } static int atrtc_resume(device_t dev) { atrtc_restore(); return(0); } static int atrtc_settime(device_t dev __unused, struct timespec *ts) { + struct bcd_clocktime bct; - atrtc_set(ts); + clock_ts_to_bcd(ts, &bct, false); + + mtx_lock(&atrtc_time_lock); + RTC_LOCK; + + /* Disable RTC updates and interrupts. */ + rtcout_locked(RTC_STATUSB, RTCSB_HALT | RTCSB_24HR); + + /* Write all the time registers. */ + rtcout_locked(RTC_SEC, bct.sec); + rtcout_locked(RTC_MIN, bct.min); + rtcout_locked(RTC_HRS, bct.hour); + rtcout_locked(RTC_WDAY, bct.dow + 1); + rtcout_locked(RTC_DAY, bct.day); + rtcout_locked(RTC_MONTH, bct.mon); + rtcout_locked(RTC_YEAR, bct.year & 0xff); +#ifdef USE_RTC_CENTURY + rtcout_locked(RTC_CENTURY, bct.year >> 8); +#endif + + /* + * Re-enable RTC updates and interrupts. + */ + rtcout_locked(RTC_STATUSB, rtc_statusb); + rtcin_locked(RTC_INTR); + + RTC_UNLOCK; + mtx_unlock(&atrtc_time_lock); + return (0); } static int atrtc_gettime(device_t dev, struct timespec *ts) { - struct clocktime ct; + struct bcd_clocktime bct; /* Look if we have a RTC present and the time is valid */ if (!(rtcin(RTC_STATUSD) & RTCSD_PWR)) { device_printf(dev, "WARNING: Battery failure indication\n"); return (EINVAL); } /* * wait for time update to complete * If RTCSA_TUP is zero, we have at least 244us before next update. * This is fast enough on most hardware, but a refinement would be * to make sure that no more than 240us pass after we start reading, * and try again if so. */ mtx_lock(&atrtc_time_lock); while (rtcin(RTC_STATUSA) & RTCSA_TUP) continue; - critical_enter(); - ct.nsec = 0; - ct.sec = readrtc(RTC_SEC); - ct.min = readrtc(RTC_MIN); - ct.hour = readrtc(RTC_HRS); - ct.day = readrtc(RTC_DAY); - ct.dow = readrtc(RTC_WDAY) - 1; - ct.mon = readrtc(RTC_MONTH); - ct.year = readrtc(RTC_YEAR); + RTC_LOCK; + bct.sec = rtcin_locked(RTC_SEC); + bct.min = rtcin_locked(RTC_MIN); + bct.hour = rtcin_locked(RTC_HRS); + bct.day = rtcin_locked(RTC_DAY); + bct.mon = rtcin_locked(RTC_MONTH); + bct.year = rtcin_locked(RTC_YEAR); #ifdef USE_RTC_CENTURY - ct.year += readrtc(RTC_CENTURY) * 100; -#else - ct.year += (ct.year < 80 ? 2000 : 1900); + bct.year |= rtcin_locked(RTC_CENTURY) << 8; #endif - critical_exit(); + RTC_UNLOCK; mtx_unlock(&atrtc_time_lock); - /* Set dow = -1 because some clocks don't set it correctly. */ - ct.dow = -1; - return (clock_ct_to_ts(&ct, ts)); + /* dow is unused in timespec conversion and we have no nsec info. */ + bct.dow = 0; + bct.nsec = 0; + return (clock_bcd_to_ts(&bct, ts, false)); } static device_method_t atrtc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, atrtc_probe), DEVMETHOD(device_attach, atrtc_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), /* XXX stop statclock? */ DEVMETHOD(device_resume, atrtc_resume), /* clock interface */ DEVMETHOD(clock_gettime, atrtc_gettime), DEVMETHOD(clock_settime, atrtc_settime), { 0, 0 } }; static driver_t atrtc_driver = { "atrtc", atrtc_methods, sizeof(struct atrtc_softc), }; static devclass_t atrtc_devclass; DRIVER_MODULE(atrtc, isa, atrtc_driver, atrtc_devclass, 0, 0); DRIVER_MODULE(atrtc, acpi, atrtc_driver, atrtc_devclass, 0, 0); #include "opt_ddb.h" #ifdef DDB #include DB_SHOW_COMMAND(rtc, rtc) { printf("%02x/%02x/%02x %02x:%02x:%02x, A = %02x, B = %02x, C = %02x\n", rtcin(RTC_YEAR), rtcin(RTC_MONTH), rtcin(RTC_DAY), rtcin(RTC_HRS), rtcin(RTC_MIN), rtcin(RTC_SEC), rtcin(RTC_STATUSA), rtcin(RTC_STATUSB), rtcin(RTC_INTR)); } #endif /* DDB */ Index: stable/11 =================================================================== --- stable/11 (revision 331495) +++ stable/11 (revision 331496) Property changes on: stable/11 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r306288,314936,325527,327971,328005,328039,328068-328069,328301-328303