Module Name: src Committed By: uwe Date: Fri Jan 5 03:07:16 UTC 2018
Modified Files: src/sys/dev/i2c: files.i2c Added Files: src/sys/dev/i2c: em3027.c em3027reg.h Log Message: Driver for EM Microelectronic EM3027 RTC and temperature sensor. To generate a diff of this commit: cvs rdiff -u -r0 -r1.1 src/sys/dev/i2c/em3027.c src/sys/dev/i2c/em3027reg.h cvs rdiff -u -r1.80 -r1.81 src/sys/dev/i2c/files.i2c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/dev/i2c/files.i2c diff -u src/sys/dev/i2c/files.i2c:1.80 src/sys/dev/i2c/files.i2c:1.81 --- src/sys/dev/i2c/files.i2c:1.80 Thu Dec 28 23:23:47 2017 +++ src/sys/dev/i2c/files.i2c Fri Jan 5 03:07:15 2018 @@ -1,4 +1,4 @@ -# $NetBSD: files.i2c,v 1.80 2017/12/28 23:23:47 christos Exp $ +# $NetBSD: files.i2c,v 1.81 2018/01/05 03:07:15 uwe Exp $ obsolete defflag opt_i2cbus.h I2C_SCAN define i2cbus { } @@ -295,6 +295,11 @@ device sy8106a attach sy8106a at iic file dev/i2c/sy8106a.c sy8106a +# EM3027 Real Time Clock and Temperature Sensor +device em3027rtc: sysmon_envsys +attach em3027rtc at iic +file dev/i2c/em3027.c em3027rtc + # HID over i2c # HID "bus" define ihidbus {[ reportid = -1 ]} Added files: Index: src/sys/dev/i2c/em3027.c diff -u /dev/null src/sys/dev/i2c/em3027.c:1.1 --- /dev/null Fri Jan 5 03:07:16 2018 +++ src/sys/dev/i2c/em3027.c Fri Jan 5 03:07:15 2018 @@ -0,0 +1,480 @@ +/* $NetBSD: em3027.c,v 1.1 2018/01/05 03:07:15 uwe Exp $ */ +/* + * Copyright (c) 2018 Valery Ushakov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * EM Microelectronic EM3027 RTC + */ +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: em3027.c,v 1.1 2018/01/05 03:07:15 uwe Exp $"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kernel.h> + +#include <dev/clock_subr.h> + +#include <dev/i2c/i2cvar.h> +#include <dev/i2c/em3027reg.h> +#include <dev/sysmon/sysmonvar.h> + +#if 0 +#define aprint_verbose_dev aprint_normal_dev +#define aprint_debug_dev aprint_normal_dev +#endif + + +struct em3027rtc_softc { + device_t sc_dev; + + i2c_tag_t sc_tag; + i2c_addr_t sc_addr; + + bool sc_vlow; + + struct todr_chip_handle sc_todr; + + struct sysmon_envsys *sc_sme; + envsys_data_t sc_sensor; +}; + + +#define EM3027_CONTROL_BASE EM3027_ONOFF +#define EM3027_WATCH_BASE EM3027_WATCH_SEC + +struct em3027rtc_watch { + uint8_t sec; + uint8_t min; + uint8_t hour; + uint8_t day; + uint8_t wday; + uint8_t mon; + uint8_t year; +}; + +#define EM3027_WATCH_SIZE (EM3027_WATCH_YEAR - EM3027_WATCH_BASE + 1) +__CTASSERT(sizeof(struct em3027rtc_watch) == EM3027_WATCH_SIZE); + +#define EM3027_BASE_YEAR 1980 + + +static int em3027rtc_match(device_t, cfdata_t, void *); +static void em3027rtc_attach(device_t, device_t, void *); + +CFATTACH_DECL_NEW(em3027rtc, sizeof(struct em3027rtc_softc), + em3027rtc_match, em3027rtc_attach, NULL, NULL); + + +static bool em3027rtc_enable_thermometer(struct em3027rtc_softc *); +static void em3027rtc_envsys_attach(struct em3027rtc_softc *); + +static int em3027rtc_gettime(struct todr_chip_handle *, struct clock_ymdhms *); +static int em3027rtc_settime(struct todr_chip_handle *, struct clock_ymdhms *); + +static void em3027rtc_sme_refresh(struct sysmon_envsys *, envsys_data_t *); + +static int em3027rtc_iic_exec(struct em3027rtc_softc *, i2c_op_t, uint8_t, + void *, size_t); + +static int em3027rtc_read(struct em3027rtc_softc *, uint8_t, void *, size_t); +static int em3027rtc_write(struct em3027rtc_softc *, uint8_t, void *, size_t); + +static int em3027rtc_read_byte(struct em3027rtc_softc *, uint8_t, uint8_t *); +static int em3027rtc_write_byte(struct em3027rtc_softc *, uint8_t, uint8_t); + + + +static int +em3027rtc_match(device_t parent, cfdata_t cf, void *aux) +{ + const struct i2c_attach_args *ia = aux; + uint8_t reg; + int error; + + if (ia->ia_addr != EM3027_ADDR) + return 0; + + /* check if the device is there */ + error = iic_acquire_bus(ia->ia_tag, 0); + if (error) + return 0; + + error = iic_smbus_read_byte(ia->ia_tag, ia->ia_addr, + EM3027_ONOFF, ®, 0); + iic_release_bus(ia->ia_tag, 0); + if (error) + return 0; + + return 1; +} + + +static void +em3027rtc_attach(device_t parent, device_t self, void *aux) +{ + struct em3027rtc_softc *sc = device_private(self); + const struct i2c_attach_args *ia = aux; + struct ctl { + uint8_t onoff; + uint8_t irq_ctl; + uint8_t irq_flags; + uint8_t status; + } ctl; + int error; + + aprint_naive(": Real-time Clock and Temperature Sensor\n"); + aprint_normal(": Real-time Clock and Temperature Sensor\n"); + + sc->sc_dev = self; + + sc->sc_tag = ia->ia_tag; + sc->sc_addr = ia->ia_addr; + + + /* + * Control Page registers + */ + error = em3027rtc_read(sc, EM3027_CONTROL_BASE, &ctl, sizeof(ctl)); + if (error) { + aprint_error_dev(sc->sc_dev, + "failed to read control page (error %d)\n", error); + return; + } + + + /* Status */ + aprint_debug_dev(sc->sc_dev, "status=0x%02x\n", ctl.status); + + /* Complain about low voltage but continue anyway */ + if (ctl.status & EM3027_STATUS_VLOW2) { + aprint_error_dev(sc->sc_dev, "voltage low (VLow2)\n"); + sc->sc_vlow = true; + } + else if (ctl.status & EM3027_STATUS_VLOW1) { + aprint_error_dev(sc->sc_dev, "voltage low (VLow1)\n"); + sc->sc_vlow = true; + } + + ctl.status = EM3027_STATUS_POWER_ON; + + + /* On/Off */ + aprint_debug_dev(sc->sc_dev, "on/off=0x%02x\n", ctl.onoff); + + if ((ctl.onoff & EM3027_ONOFF_SR) == 0) { + aprint_verbose_dev(sc->sc_dev, "enabling self-recovery\n"); + ctl.onoff |= EM3027_ONOFF_SR; + } + + if ((ctl.onoff & EM3027_ONOFF_EEREF) == 0) { + aprint_verbose_dev(sc->sc_dev, "enabling EEPROM self-refresh\n"); + ctl.onoff |= EM3027_ONOFF_EEREF; + } + + ctl.onoff &= ~EM3027_ONOFF_TR; + + if (ctl.onoff & EM3027_ONOFF_TI) { + aprint_verbose_dev(sc->sc_dev, "disabling timer\n"); + ctl.onoff &= ~EM3027_ONOFF_TI; + } + + if ((ctl.onoff & EM3027_ONOFF_WA) == 0) { + aprint_verbose_dev(sc->sc_dev, "enabling watch\n"); + ctl.onoff |= EM3027_ONOFF_WA; + } + + + /* IRQ Control/Flags */ + if (ctl.irq_ctl != 0) + aprint_debug_dev(sc->sc_dev, + "irq=0x%02x - disabling all\n", ctl.irq_ctl); + ctl.irq_ctl = 0; + ctl.irq_flags = 0; + + + /* Write them back */ + error = em3027rtc_write(sc, EM3027_CONTROL_BASE, &ctl, sizeof(ctl)); + if (error) { + aprint_error_dev(sc->sc_dev, + "failed to write control page (error %d)\n", error); + return; + } + + + /* + * Attach RTC + */ + sc->sc_todr.cookie = sc; + sc->sc_todr.todr_gettime_ymdhms = em3027rtc_gettime; + sc->sc_todr.todr_settime_ymdhms = em3027rtc_settime; + sc->sc_todr.todr_setwen = NULL; + + todr_attach(&sc->sc_todr); + + + /* + * Attach thermometer + */ + em3027rtc_envsys_attach(sc); +} + + +static bool +em3027rtc_enable_thermometer(struct em3027rtc_softc *sc) +{ + uint8_t eeprom_ctl; + int error; + + error = em3027rtc_read_byte(sc, EM3027_EEPROM_CTL, &eeprom_ctl); + if (error) { + aprint_error_dev(sc->sc_dev, + "failed to read eeprom control (error %d)\n", error); + return false; + } + + aprint_debug_dev(sc->sc_dev, "eeprom ctl=0x%02x\n", eeprom_ctl); + if (eeprom_ctl & EM3027_EEPROM_THERM_ENABLE) + return true; + + eeprom_ctl |= EM3027_EEPROM_THERM_ENABLE; + error = em3027rtc_write_byte(sc, EM3027_EEPROM_CTL, eeprom_ctl); + if (error) { + aprint_error_dev(sc->sc_dev, + "failed to write eeprom control (error %d)\n", error); + return false; + } + + return true; +} + + +static void +em3027rtc_envsys_attach(struct em3027rtc_softc *sc) +{ + int error; + + if (!em3027rtc_enable_thermometer(sc)) { + aprint_error_dev(sc->sc_dev, "thermometer not enabled\n"); + return; + } + + sc->sc_sme = sysmon_envsys_create(); + + sc->sc_sme->sme_name = device_xname(sc->sc_dev); + sc->sc_sme->sme_cookie = sc; + sc->sc_sme->sme_refresh = em3027rtc_sme_refresh; + + sc->sc_sensor.units = ENVSYS_STEMP; + sc->sc_sensor.state = ENVSYS_SINVALID; + sc->sc_sensor.flags = 0; + strlcpy(sc->sc_sensor.desc, "temperature", sizeof(sc->sc_sensor.desc)); + + error = sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor); + if (error) { + aprint_error_dev(sc->sc_dev, + "unable to attach sensor (error %d)\n", error); + goto out; + } + + error = sysmon_envsys_register(sc->sc_sme); + if (error) { + aprint_error_dev(sc->sc_dev, + "unable to register with sysmon (error %d)\n", error); + goto out; + } + + return; + +out: + if (error) { + sysmon_envsys_destroy(sc->sc_sme); + sc->sc_sme = NULL; + } +} + + +static int +em3027rtc_iic_exec(struct em3027rtc_softc *sc, i2c_op_t op, uint8_t reg, + void *buf, size_t len) +{ + const int flags = cold ? 0 : I2C_F_POLL; + int error; + + error = iic_acquire_bus(sc->sc_tag, flags); + if (error) + return error; + + error = iic_exec(sc->sc_tag, op, sc->sc_addr, + ®, 1, + (uint8_t *)buf, len, + flags); + + /* XXX: horrible hack that seems to be needed on utilite */ + if (reg == EM3027_WATCH_BASE) + DELAY(1); + + iic_release_bus(sc->sc_tag, flags); + return error; +} + + +static int +em3027rtc_read(struct em3027rtc_softc *sc, uint8_t reg, void *buf, size_t len) +{ + + return em3027rtc_iic_exec(sc, I2C_OP_READ_WITH_STOP, reg, buf, len); +} + + +static int +em3027rtc_read_byte(struct em3027rtc_softc *sc, uint8_t reg, uint8_t *valp) +{ + + return em3027rtc_read(sc, reg, valp, 1); +} + + +static int +em3027rtc_write(struct em3027rtc_softc *sc, uint8_t reg, void *buf, size_t len) +{ + + return em3027rtc_iic_exec(sc, I2C_OP_WRITE_WITH_STOP, reg, buf, len); +} + + +static int +em3027rtc_write_byte(struct em3027rtc_softc *sc, uint8_t reg, uint8_t val) +{ + + return em3027rtc_write(sc, reg, &val, 1); +} + + +static int +em3027rtc_gettime(struct todr_chip_handle *todr, struct clock_ymdhms *dt) +{ + struct em3027rtc_softc *sc = todr->cookie; + struct em3027rtc_watch w; + int error; + + error = em3027rtc_read(sc, EM3027_WATCH_BASE, &w, sizeof(w)); + if (error) { + aprint_error_dev(sc->sc_dev, + "failed to read watch (error %d)\n", error); + return error; + } + + dt->dt_sec = bcdtobin(w.sec); + dt->dt_min = bcdtobin(w.min); + + if (w.hour & EM3027_WATCH_HOUR_S12) { + const int pm = w.hour & EM3027_WATCH_HOUR_PM; + int hr; + + w.hour &= ~(EM3027_WATCH_HOUR_S12 | EM3027_WATCH_HOUR_PM); + hr = bcdtobin(w.hour); + if (hr == 12) + hr = pm ? 12 : 0; + else if (pm) + hr += 12; + + dt->dt_hour = hr; + } + else { + dt->dt_hour = bcdtobin(w.hour); + } + + dt->dt_day = bcdtobin(w.day); + dt->dt_wday = bcdtobin(w.wday) - 1; + dt->dt_mon = bcdtobin(w.mon); + dt->dt_year = bcdtobin(w.year) + EM3027_BASE_YEAR; + + return 0; +} + + +static int +em3027rtc_settime(struct todr_chip_handle *todr, struct clock_ymdhms *dt) +{ + struct em3027rtc_softc *sc = todr->cookie; + struct em3027rtc_watch w; + int error; + + w.sec = bintobcd(dt->dt_sec); + w.min = bintobcd(dt->dt_min); + w.hour = bintobcd(dt->dt_hour); + w.day = bintobcd(dt->dt_day); + w.wday = bintobcd(dt->dt_wday + 1); + w.mon = bintobcd(dt->dt_mon); + w.year = bintobcd(dt->dt_year - EM3027_BASE_YEAR); + + error = em3027rtc_write(sc, EM3027_WATCH_BASE, &w, sizeof(w)); + return error; +} + + +static void +em3027rtc_sme_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) +{ + struct em3027rtc_softc *sc = sme->sme_cookie; + uint8_t status, t_raw; + uint32_t t_uk; + int error; + + edata->state = ENVSYS_SINVALID; + + error = em3027rtc_read_byte(sc, EM3027_STATUS, &status); + if (error) { + aprint_debug_dev(sc->sc_dev, + "failed to read status (error %d)\n", error); + return; + } + + if (status & (EM3027_STATUS_VLOW2 | EM3027_STATUS_VLOW1)) { + if (!sc->sc_vlow) { + sc->sc_vlow = true; + aprint_error_dev(sc->sc_dev, + "voltage low, thermometer is disabled\n"); + } + return; + } + else + sc->sc_vlow = false; + + error = em3027rtc_read_byte(sc, EM3027_TEMP, &t_raw); + if (error) { + aprint_debug_dev(sc->sc_dev, + "failed to read temperature (error %d)\n", error); + return; + } + + + /* convert to microkelvin */ + t_uk = ((int)t_raw + EM3027_TEMP_BASE) * 1000000 + 273150000; + + edata->value_cur = t_uk; + edata->state = ENVSYS_SVALID; +} Index: src/sys/dev/i2c/em3027reg.h diff -u /dev/null src/sys/dev/i2c/em3027reg.h:1.1 --- /dev/null Fri Jan 5 03:07:16 2018 +++ src/sys/dev/i2c/em3027reg.h Fri Jan 5 03:07:15 2018 @@ -0,0 +1,138 @@ +/* $NetBSD: em3027reg.h,v 1.1 2018/01/05 03:07:15 uwe Exp $ */ +/* + * Copyright (c) 2018 Valery Ushakov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * EM Microelectronic EM3027 RTC + */ +#ifndef _EM3027REG_H_ +#define _EM3027REG_H_ + +#define EM3027_ADDR 0x56 + + +/* + * Control Page: 0 + */ +#define EM3027_ONOFF 0x00 +#define EM3027_ONOFF_CLKOUT 0x80 /* 0: IRQ, 1: CLK */ +#define EM3027_ONOFF_SR 0x10 /* self-recovery */ +#define EM3027_ONOFF_EEREF 0x08 /* EEPROM self-refresh */ +#define EM3027_ONOFF_TR 0x04 /* timer auto-reload */ +#define EM3027_ONOFF_TI 0x02 /* timer */ +#define EM3027_ONOFF_WA 0x01 /* watch */ + +#define EM3027_IRQ_CTL 0x01 +#define EM3027_IRQ_FLAGS 0x02 + +/* For both EM3027_IRQ_CTL and EM3027_IRQ_FLAGS */ +#define EM3027_IRQ_SR 0x10 /* self-recovery */ +#define EM3027_IRQ_V2 0x08 /* VLow2 */ +#define EM3027_IRQ_V1 0x04 /* VLow1 */ +#define EM3027_IRQ_TINT 0x02 /* timer */ +#define EM3027_IRQ_AINT 0x01 /* alarm */ + +#define EM3027_STATUS 0x03 +#define EM3027_STATUS_EEBUSY 0x80 /* r/o: write or self-refresh */ +#define EM3027_STATUS_POWER_ON 0x20 +#define EM3027_STATUS_RESET 0x10 /* after reset or recovery */ +#define EM3027_STATUS_VLOW2 0x08 /* voltage lost */ +#define EM3027_STATUS_VLOW1 0x04 /* voltage low */ + +/* Request system reset */ +#define EM3027_RESET 0x04 +#define EM3027_RESET_SYSRES 0x10 + + +/* + * Watch Page: 1 + */ +#define EM3027_WATCH_SEC 0x08 /* 0..59 */ +#define EM3027_WATCH_MIN 0x09 /* 0..59 */ +#define EM3027_WATCH_HOUR 0x0a /* 0..23 or 1..12 */ +#define EM3027_WATCH_HOUR_S12 0x40 /* select 12/24 hours */ +#define EM3027_WATCH_HOUR_PM 0x20 /* am/pm if 12 hours */ +#define EM3027_WATCH_DAY 0x0b /* 1..31 */ +#define EM3027_WATCH_WDAY 0x0c /* 1..7 */ +#define EM3027_WATCH_MON 0x0d /* 1..12 */ +#define EM3027_WATCH_YEAR 0x0e /* 0..79 */ + + +/* + * Alarm Page: 2 + * + * Same format as watch registers except there's no S12 bit in the + * hours register and the upper bit (EM3027_ALARM_ENABLE) in each + * register selects it for comparison. + */ +#define EM3027_ALARM_SEC 0x10 +#define EM3027_ALARM_MIN 0x11 +#define EM3027_ALARM_HOUR 0x12 +#define EM3027_ALARM_DATE 0x13 +#define EM3027_ALARM_DAYS 0x14 +#define EM3027_ALARM_MON 0x15 +#define EM3027_ALARM_YEAR 0x16 + +/* MSB in each alarm register enables comparison */ +#define EM3027_ALARM_ENABLE 0x80 + + +/* + * Timer Page: 3 + */ +#define EM3027_TIMER_LO 0x18 +#define EM3027_TIMER_HI 0x19 + + +/* + * Temperature Page: 4 + */ +#define EM3027_TEMP 0x20 /* -60..195C */ +#define EM3027_TEMP_BASE (-60) + + +/* + * EEPROM Control Page: 6 + */ +#define EM3027_EEPROM_CTL 0x30 + +/* Trickle charger resistors */ +#define EM3027_EEPROM_CHARGER_MASK 0xf0 +#define EM3027_EEPROM_R80K 0x80 +#define EM3027_EEPROM_R20K 0x40 +#define EM3027_EEPROM_R5K 0x20 +#define EM3027_EEPROM_R1_5K 0x10 + +/* Frequency compensated for temperature (0x00 selects raw + uncompensated 32768KHz */ +#define EM3027_EEPROM_FREQ_MASK 0x0c +#define EM3027_EEPROM_FREQ_1HZ 0x0c +#define EM3027_EEPROM_FREQ_32HZ 0x08 +#define EM3027_EEPROM_FREQ_1024HZ 0x04 + +#define EM3027_EEPROM_THERM_ENABLE 0x02 +#define EM3027_EEPROM_THERM_PERIOD 0x01 /* 0: 1s, 1: 16s */ + +#endif /* _EM3027REG_H_ */