/* $NetBSD: rk_tsadc.c,v 1.7 2019/07/03 20:55:21 jmcneill Exp $ */ /* * Copyright (c) 2019 Matthew R. Green * 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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. */ #include __KERNEL_RCSID(0, "$NetBSD: rk_tsadc.c,v 1.7 2019/07/03 20:55:21 jmcneill Exp $"); /* * Driver for the TSADC temperature sensor monitor in RK3328 and RK3399. * * TODO: * - handle setting various temp values * - handle DT trips/temp value defaults * - interrupts aren't triggered (test by lowering warn/crit values), and * once they work, make the interrupt do something */ #include #include #include #include #include #include #include #include #include #include #ifdef RKTSADC_DEBUG #define DPRINTF(fmt, ...) \ printf("%s:%d: " fmt "\n", __func__, __LINE__, ## __VA_ARGS__) #else #define DPRINTF(fmt, ...) #endif /* Register definitions */ #define TSADC_USER_CON 0x00 #define TSADC_USER_CON_ADC_STATUS __BIT(12) #define TSADC_USER_CON_INTER_PD_SOC __BITS(11,6) #define TSADC_USER_CON_START __BIT(5) #define TSADC_USER_CON_START_MODE __BIT(4) #define TSADC_USER_CON_ADC_POWER_CTRL __BIT(3) #define TSADC_USER_CON_ADC_INPUT_SRC_SEL __BITS(2,0) #define TSADC_AUTO_CON 0x04 #define TSADC_AUTO_CON_LAST_TSHUT_2CRU __BIT(25) #define TSADC_AUTO_CON_LAST_TSHUT_2GPIO __BIT(24) #define TSADC_AUTO_CON_SAMPLE_DLY_SEL __BIT(17) #define TSADC_AUTO_CON_AUTO_STATUS __BIT(16) #define TSADC_AUTO_CON_SRC1_LT_EN __BIT(13) #define TSADC_AUTO_CON_SRC0_LT_EN __BIT(12) #define TSADC_AUTO_CON_TSHUT_POLARITY __BIT(8) #define TSADC_AUTO_CON_SRC1_EN __BIT(5) #define TSADC_AUTO_CON_SRC0_EN __BIT(4) #define TSADC_AUTO_CON_Q_SEL __BIT(1) #define TSADC_AUTO_CON_AUTO_EN __BIT(0) #define TSADC_INT_EN 0x08 #define TSADC_INT_EN_EOC_INT_EN __BIT(16) #define TSADC_INT_EN_LT_INTEN_SRC1 __BIT(13) #define TSADC_INT_EN_LT_INTEN_SRC0 __BIT(12) #define TSADC_INT_EN_TSHUT_2CRU_EN_SRC1 __BIT(9) #define TSADC_INT_EN_TSHUT_2CRU_EN_SRC0 __BIT(8) #define TSADC_INT_EN_TSHUT_2GPIO_EN_SRC1 __BIT(5) #define TSADC_INT_EN_TSHUT_2GPIO_EN_SRC0 __BIT(4) #define TSADC_INT_EN_HT_INTEN_SRC1 __BIT(1) #define TSADC_INT_EN_HT_INTEN_SRC0 __BIT(0) #define TSADC_INT_PD 0x0c #define TSADC_INT_PD_EOC_INT_PD __BIT(16) #define TSADC_INT_PD_LT_IRQ_SRC1 __BIT(13) #define TSADC_INT_PD_LT_IRQ_SRC0 __BIT(12) #define TSADC_INT_PD_TSHUT_O_SRC1 __BIT(5) #define TSADC_INT_PD_TSHUT_O_SRC0 __BIT(4) #define TSADC_INT_PD_HT_IRQ_SRC1 __BIT(1) #define TSADC_INT_PD_HT_IRQ_SRC0 __BIT(0) #define TSADC_DATA0 0x20 #define TSADC_DATA0_ADC_DATA __BITS(11,0) #define TSADC_DATA1 0x24 #define TSADC_DATA1_ADC_DATA __BITS(11,0) #define TSADC_COMP0_INT 0x30 #define TSADC_COMP0_INT_COMP_SRC0 __BITS(11,0) #define TSADC_COMP1_INT 0x34 #define TSADC_COMP1_INT_COMP_SRC1 __BITS(11,0) #define TSADC_COMP0_SHUT 0x40 #define TSADC_COMP0_SHUT_COMP_SRC0 __BITS(11,0) #define TSADC_COMP1_SHUT 0x44 #define TSADC_COMP1_SHUT_COMP_SRC1 __BITS(11,0) #define TSADC_HIGH_INT_DEBOUNCE 0x60 #define TSADC_HIGH_INT_DEBOUNCE_TEMP __BITS(7,0) #define TSADC_HIGH_TSHUT_DEBOUNCE 0x64 #define TSADC_HIGH_TSHUT_DEBOUNCE_TEMP __BITS(7,0) #define TSADC_AUTO_PERIOD 0x68 #define TSADC_AUTO_PERIOD_TEMP __BITS(31,0) #define TSADC_AUTO_PERIOD_HT 0x6c #define TSADC_AUTO_PERIOD_HT_TEMP __BITS(31,0) #define TSADC_COMP0_LOW_INT 0x80 #define TSADC_COMP0_LOW_INT_COMP_SRC0 __BITS(11,0) #define TSADC_COMP1_LOW_INT 0x84 #define TSADC_COMP1_LOW_INT_COMP_SRC1 __BITS(11,0) #define RK3328_TSADC_AUTO_PERIOD_TIME 250 /* 250ms */ #define RK3399_TSADC_AUTO_PERIOD_TIME 1875 /* 2.5ms */ #define TSADC_HT_DEBOUNCE_COUNT 4 /* * All this magic is taking from the Linux rockchip_thermal driver. * * VCM means "voltage common mode", but the documentation for RK3399 * does not mention this and I don't know what any of this really * is for. */ #define RK3399_GRF_SARADC_TESTBIT 0xe644 #define RK3399_GRF_SARADC_TESTBIT_ON (0x10001 << 2) #define RK3399_GRF_TSADC_TESTBIT_L 0xe648 #define RK3399_GRF_TSADC_TESTBIT_VCM_EN_L (0x10001 << 7) #define RK3399_GRF_TSADC_TESTBIT_H 0xe64c #define RK3399_GRF_TSADC_TESTBIT_VCM_EN_H (0x10001 << 7) #define RK3399_GRF_TSADC_TESTBIT_H_ON (0x10001 << 2) #define TEMP_uC_TO_uK 273150000 #define TSHUT_MODE_CPU 0 #define TSHUT_MODE_GPIO 1 #define TSHUT_LOW_ACTIVE 0 #define TSHUT_HIGH_ACTIVE 1 #define TSHUT_DEF_TEMP 95000 #define TSADC_DATA_MAX 0xfff #define MAX_SENSORS 2 typedef struct rk_data_array { uint32_t data; /* register value */ int temp; /* micro-degC */ } rk_data_array; struct rk_tsadc_softc; typedef struct rk_data { const rk_data_array *rd_array; size_t rd_size; void (*rd_init)(struct rk_tsadc_softc *, int, int); bool rd_decr; /* lower values -> higher temp */ unsigned rd_min, rd_max; unsigned rd_auto_period; unsigned rd_num_sensors; } rk_data; /* Per-sensor data */ struct rk_tsadc_sensor { envsys_data_t s_data; bool s_attached; /* TSADC register offsets for this sensor */ unsigned s_data_reg; unsigned s_comp_tshut; unsigned s_comp_int; /* enable bit in AUTO_CON register */ unsigned s_comp_int_en; /* warn/crit values in micro Kelvin */ int s_warn; int s_tshut; }; struct rk_tsadc_softc { device_t sc_dev; int sc_phandle; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; size_t sc_size; uint32_t sc_data_mask; void *sc_ih; struct sysmon_envsys *sc_sme; struct rk_tsadc_sensor sc_sensors[MAX_SENSORS]; struct clk *sc_clock; struct clk *sc_clockapb; struct fdtbus_reset *sc_reset; struct syscon *sc_syscon; const rk_data *sc_rd; }; static int rk_tsadc_match(device_t, cfdata_t, void *); static void rk_tsadc_attach(device_t, device_t, void *); static int rk_tsadc_detach(device_t, int); static int rk_tsadc_init_clocks(struct rk_tsadc_softc *); static void rk_tsadc_init_counts(struct rk_tsadc_softc *); static void rk_tsadc_tshut_set(struct rk_tsadc_softc *s); static void rk_tsadc_init_tshut(struct rk_tsadc_softc *, int, int); static void rk_tsadc_init_rk3328(struct rk_tsadc_softc *, int, int); static void rk_tsadc_init_rk3399(struct rk_tsadc_softc *, int, int); static void rk_tsadc_init_enable(struct rk_tsadc_softc *); static void rk_tsadc_init(struct rk_tsadc_softc *, int, int); static void rk_tsadc_refresh(struct sysmon_envsys *, envsys_data_t *); static void rk_tsadc_get_limits(struct sysmon_envsys *, envsys_data_t *, sysmon_envsys_lim_t *, uint32_t *); static int rk_tsadc_intr(void *); static int rk_tsadc_data_to_temp(struct rk_tsadc_softc *, uint32_t); static uint32_t rk_tsadc_temp_to_data(struct rk_tsadc_softc *, int); /* RK3328/RK3399 compatible sensors */ static const struct rk_tsadc_sensor rk_tsadc_sensors[] = { { .s_data = { .desc = "CPU" }, .s_data_reg = TSADC_DATA0, .s_comp_tshut = TSADC_COMP0_SHUT, .s_comp_int = TSADC_COMP0_INT, .s_comp_int_en = TSADC_AUTO_CON_SRC0_EN, /* * XXX DT has: * cpu_alert1: cpu_alert1 { * temperature = <75000>; * hysteresis = <2000>; * cpu_crit: cpu_crit { * temperature = <95000>; * hysteresis = <2000>; * pull out of here? * do something with hysteresis? put in debounce? * * Note that tshut may be overriden by the board specific DT. */ .s_warn = 75000000, .s_tshut = 95000000, }, { .s_data = { .desc = "GPU" }, .s_data_reg = TSADC_DATA1, .s_comp_tshut = TSADC_COMP1_SHUT, .s_comp_int = TSADC_COMP1_INT, .s_comp_int_en = TSADC_AUTO_CON_SRC1_EN, .s_warn = 75000000, .s_tshut = 95000000, }, }; /* * Table from RK3328 manual. Note that the manual lists valid numbers as * 4096 - number. This also means it is increasing not decreasing for * higher temps, and the min and max are also offset from 4096. */ #define RK3328_DATA_OFFSET (4096) static const rk_data_array rk3328_data_array[] = { #define ENTRY(d,C) \ { .data = RK3328_DATA_OFFSET - (d), .temp = (C) * 1000 * 1000, } ENTRY(TSADC_DATA_MAX, -40), ENTRY(3800, -40), ENTRY(3792, -35), ENTRY(3783, -30), ENTRY(3774, -25), ENTRY(3765, -20), ENTRY(3756, -15), ENTRY(3747, -10), ENTRY(3737, -5), ENTRY(3728, 0), ENTRY(3718, 5), ENTRY(3708, 10), ENTRY(3698, 15), ENTRY(3688, 20), ENTRY(3678, 25), ENTRY(3667, 30), ENTRY(3656, 35), ENTRY(3645, 40), ENTRY(3634, 45), ENTRY(3623, 50), ENTRY(3611, 55), ENTRY(3600, 60), ENTRY(3588, 65), ENTRY(3575, 70), ENTRY(3563, 75), ENTRY(3550, 80), ENTRY(3537, 85), ENTRY(3524, 90), ENTRY(3510, 95), ENTRY(3496, 100), ENTRY(3482, 105), ENTRY(3467, 110), ENTRY(3452, 115), ENTRY(3437, 120), ENTRY(3421, 125), ENTRY(0, 125), #undef ENTRY }; /* Table from RK3399 manual */ static const rk_data_array rk3399_data_array[] = { #define ENTRY(d,C) { .data = (d), .temp = (C) * 1000 * 1000, } ENTRY(0, -40), ENTRY(402, -40), ENTRY(410, -35), ENTRY(419, -30), ENTRY(427, -25), ENTRY(436, -20), ENTRY(444, -15), ENTRY(453, -10), ENTRY(461, -5), ENTRY(470, 0), ENTRY(478, 5), ENTRY(487, 10), ENTRY(496, 15), ENTRY(504, 20), ENTRY(513, 25), ENTRY(521, 30), ENTRY(530, 35), ENTRY(538, 40), ENTRY(547, 45), ENTRY(555, 50), ENTRY(564, 55), ENTRY(573, 60), ENTRY(581, 65), ENTRY(590, 70), ENTRY(599, 75), ENTRY(607, 80), ENTRY(616, 85), ENTRY(624, 90), ENTRY(633, 95), ENTRY(642, 100), ENTRY(650, 105), ENTRY(659, 110), ENTRY(668, 115), ENTRY(677, 120), ENTRY(685, 125), ENTRY(TSADC_DATA_MAX, 125), #undef ENTRY }; static const rk_data rk3328_data_table = { .rd_array = rk3328_data_array, .rd_size = __arraycount(rk3328_data_array), .rd_init = rk_tsadc_init_rk3328, .rd_decr = false, .rd_max = RK3328_DATA_OFFSET - 3420, .rd_min = RK3328_DATA_OFFSET - 3801, .rd_auto_period = RK3328_TSADC_AUTO_PERIOD_TIME, .rd_num_sensors = 1, }; static const rk_data rk3399_data_table = { .rd_array = rk3399_data_array, .rd_size = __arraycount(rk3399_data_array), .rd_init = rk_tsadc_init_rk3399, .rd_decr = false, .rd_max = 686, .rd_min = 401, .rd_auto_period = RK3399_TSADC_AUTO_PERIOD_TIME, .rd_num_sensors = 2, }; static const char * const compatible_rk3328[] = { "rockchip,rk3328-tsadc", NULL }; static const char * const compatible_rk3399[] = { "rockchip,rk3399-tsadc", NULL }; #define TSADC_READ(sc, reg) \ bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) #define TSADC_WRITE(sc, reg, val) \ bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) CFATTACH_DECL3_NEW(rk_tsadc, sizeof(struct rk_tsadc_softc), rk_tsadc_match, rk_tsadc_attach, rk_tsadc_detach, NULL, NULL, NULL, DVF_DETACH_SHUTDOWN); /* init/teardown support */ static int rk_tsadc_match(device_t parent, cfdata_t cf, void *aux) { struct fdt_attach_args * const faa = aux; return of_match_compatible(faa->faa_phandle, compatible_rk3328) || of_match_compatible(faa->faa_phandle, compatible_rk3399); } static void rk_tsadc_attach(device_t parent, device_t self, void *aux) { struct rk_tsadc_softc * const sc = device_private(self); struct fdt_attach_args * const faa = aux; char intrstr[128]; const int phandle = faa->faa_phandle; bus_addr_t addr; int mode, polarity, tshut_temp; sc->sc_dev = self; sc->sc_phandle = phandle; sc->sc_bst = faa->faa_bst; aprint_naive("\n"); aprint_normal(": RK3328/3399 Temperature Sensor ADC\n"); sc->sc_sme = sysmon_envsys_create(); sc->sc_sme->sme_name = device_xname(self); sc->sc_sme->sme_cookie = sc; sc->sc_sme->sme_refresh = rk_tsadc_refresh; sc->sc_sme->sme_get_limits = rk_tsadc_get_limits; sc->sc_data_mask = TSADC_DATA_MAX; pmf_device_register(self, NULL, NULL); if (of_match_compatible(faa->faa_phandle, compatible_rk3328)) { sc->sc_rd = &rk3328_data_table; } else { KASSERT(of_match_compatible(faa->faa_phandle, compatible_rk3399)); sc->sc_rd = &rk3399_data_table; } /* Default to tshut via gpio and tshut low is active */ if (of_getprop_uint32(phandle, "rockchip,hw-tshut-mode", &mode) != 0) { aprint_error(": could not get TSHUT mode, default to GPIO"); mode = TSHUT_MODE_GPIO; } if (mode != TSHUT_MODE_CPU && mode != TSHUT_MODE_GPIO) { aprint_error(": TSHUT mode should be 0 or 1\n"); goto fail; } if (of_getprop_uint32(phandle, "rockchip,hw-tshut-polarity", &polarity) != 0) { aprint_error(": could not get TSHUT polarity, default to low"); polarity = TSHUT_LOW_ACTIVE; } if (of_getprop_uint32(phandle, "rockchip,hw-tshut-temp", &tshut_temp) != 0) { aprint_error(": could not get TSHUT temperature, default to %u", TSHUT_DEF_TEMP); tshut_temp = TSHUT_DEF_TEMP; } tshut_temp *= 1000; /* convert fdt ms -> us */ memcpy(sc->sc_sensors, rk_tsadc_sensors, sizeof(sc->sc_sensors)); for (unsigned n = 0; n < sc->sc_rd->rd_num_sensors; n++) { struct rk_tsadc_sensor *rks = &sc->sc_sensors[n]; rks->s_data.flags = ENVSYS_FMONLIMITS; rks->s_data.units = ENVSYS_STEMP; rks->s_data.state = ENVSYS_SINVALID; if (sysmon_envsys_sensor_attach(sc->sc_sme, &rks->s_data)) goto fail; rks->s_attached = true; rks->s_tshut = tshut_temp; #if 0 // testing rks->s_tshut = 68000000; rks->s_warn = 61000000; #endif } sc->sc_syscon = fdtbus_syscon_acquire(phandle, "rockchip,grf"); if (sc->sc_syscon == NULL) { aprint_error(": couldn't get grf syscon\n"); goto fail; } if (fdtbus_get_reg(phandle, 0, &addr, &sc->sc_size) != 0) { aprint_error(": couldn't get registers\n"); sc->sc_size = 0; goto fail; } if (bus_space_map(sc->sc_bst, addr, sc->sc_size, 0, &sc->sc_bsh) != 0) { aprint_error(": couldn't map registers\n"); sc->sc_size = 0; goto fail; } if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { aprint_error(": failed to decode interrupt\n"); goto fail; } sc->sc_ih = fdtbus_intr_establish(phandle, 0, IPL_VM, FDT_INTR_MPSAFE, rk_tsadc_intr, sc); if (sc->sc_ih == NULL) { aprint_error_dev(self, "couldn't establish interrupt on %s\n", intrstr); goto fail; } aprint_normal_dev(self, "interrupting on %s\n", intrstr); if (rk_tsadc_init_clocks(sc)) { aprint_error(": couldn't enable clocks\n"); return; } /* * Manual says to setup auto period (both), high temp (interrupt), * high temp (shutdown), enable high temp resets (TSHUT to GPIO * or reset chip), set the debounce times, and, finally, enable the * controller iself. */ rk_tsadc_init(sc, mode, polarity); return; fail: rk_tsadc_detach(self, 0); } static int rk_tsadc_detach(device_t self, int flags) { struct rk_tsadc_softc *sc = device_private(self); pmf_device_deregister(self); for (unsigned n = 0; n < sc->sc_rd->rd_num_sensors; n++) { struct rk_tsadc_sensor *rks = &sc->sc_sensors[n]; if (rks->s_attached) { sysmon_envsys_sensor_detach(sc->sc_sme, &rks->s_data); rks->s_attached = false; } } sysmon_envsys_unregister(sc->sc_sme); if (sc->sc_clockapb) clk_disable(sc->sc_clockapb); if (sc->sc_clock) clk_disable(sc->sc_clock); if (sc->sc_ih) fdtbus_intr_disestablish(sc->sc_phandle, sc->sc_ih); if (sc->sc_size) bus_space_unmap(sc->sc_bst, sc->sc_bsh, sc->sc_size); sysmon_envsys_destroy(sc->sc_sme); return 0; } static int rk_tsadc_init_clocks(struct rk_tsadc_softc *sc) { int error; fdtbus_clock_assign(sc->sc_phandle); sc->sc_reset = fdtbus_reset_get(sc->sc_phandle, "tsadc-apb"); sc->sc_clock = fdtbus_clock_get(sc->sc_phandle, "tsadc"); sc->sc_clockapb = fdtbus_clock_get(sc->sc_phandle, "apb_pclk"); if (sc->sc_reset == NULL || sc->sc_clock == NULL || sc->sc_clockapb == NULL) return EINVAL; fdtbus_reset_assert(sc->sc_reset); error = clk_enable(sc->sc_clock); if (error) { fdtbus_reset_deassert(sc->sc_reset); return error; } error = clk_enable(sc->sc_clockapb); DELAY(20); fdtbus_reset_deassert(sc->sc_reset); return error; } static void rk_tsadc_init_counts(struct rk_tsadc_softc *sc) { TSADC_WRITE(sc, TSADC_AUTO_PERIOD, sc->sc_rd->rd_auto_period); TSADC_WRITE(sc, TSADC_AUTO_PERIOD_HT, sc->sc_rd->rd_auto_period); TSADC_WRITE(sc, TSADC_HIGH_INT_DEBOUNCE, TSADC_HT_DEBOUNCE_COUNT); TSADC_WRITE(sc, TSADC_HIGH_TSHUT_DEBOUNCE, TSADC_HT_DEBOUNCE_COUNT); } /* Configure the hardware with the tshut setup. */ static void rk_tsadc_tshut_set(struct rk_tsadc_softc *sc) { uint32_t val = TSADC_READ(sc, TSADC_AUTO_CON); for (unsigned n = 0; n < sc->sc_rd->rd_num_sensors; n++) { struct rk_tsadc_sensor *rks = &sc->sc_sensors[n]; uint32_t data, warndata; if (!rks->s_attached) continue; data = rk_tsadc_temp_to_data(sc, rks->s_tshut); warndata = rk_tsadc_temp_to_data(sc, rks->s_warn); DPRINTF("(%s:%s): tshut/data %d/%u warn/data %d/%u", sc->sc_sme->sme_name, rks->s_data.desc, rks->s_tshut, data, rks->s_warn, warndata); if (data == sc->sc_data_mask) { aprint_error_dev(sc->sc_dev, "Failed converting critical temp %u.%06u to code", rks->s_tshut / 1000000, rks->s_tshut % 1000000); continue; } if (warndata == sc->sc_data_mask) { aprint_error_dev(sc->sc_dev, "Failed converting warn temp %u.%06u to code", rks->s_warn / 1000000, rks->s_warn % 1000000); continue; } TSADC_WRITE(sc, rks->s_comp_tshut, data); TSADC_WRITE(sc, rks->s_comp_int, warndata); val |= rks->s_comp_int_en; } TSADC_WRITE(sc, TSADC_AUTO_CON, val); } static void rk_tsadc_init_tshut(struct rk_tsadc_softc *sc, int mode, int polarity) { uint32_t val; /* Handle TSHUT temp setting. */ rk_tsadc_tshut_set(sc); /* Handle TSHUT mode setting. */ val = TSADC_READ(sc, TSADC_INT_EN); if (mode == TSHUT_MODE_CPU) { val |= TSADC_INT_EN_TSHUT_2CRU_EN_SRC1 | TSADC_INT_EN_TSHUT_2CRU_EN_SRC0; val &= ~(TSADC_INT_EN_TSHUT_2GPIO_EN_SRC1 | TSADC_INT_EN_TSHUT_2GPIO_EN_SRC0); } else { KASSERT(mode == TSHUT_MODE_GPIO); val &= ~(TSADC_INT_EN_TSHUT_2CRU_EN_SRC1 | TSADC_INT_EN_TSHUT_2CRU_EN_SRC0); val |= TSADC_INT_EN_TSHUT_2GPIO_EN_SRC1 | TSADC_INT_EN_TSHUT_2GPIO_EN_SRC0; } TSADC_WRITE(sc, TSADC_INT_EN, val); /* Handle TSHUT polarity setting. */ val = TSADC_READ(sc, TSADC_AUTO_CON); if (polarity == TSHUT_HIGH_ACTIVE) val |= TSADC_AUTO_CON_TSHUT_POLARITY; else val &= ~TSADC_AUTO_CON_TSHUT_POLARITY; TSADC_WRITE(sc, TSADC_AUTO_CON, val); } static void rk_tsadc_init_rk3328(struct rk_tsadc_softc *sc, int mode, int polarity) { rk_tsadc_init_tshut(sc, mode, polarity); rk_tsadc_init_counts(sc); } static void rk_tsadc_init_rk3399(struct rk_tsadc_softc *sc, int mode, int polarity) { syscon_lock(sc->sc_syscon); syscon_write_4(sc->sc_syscon, RK3399_GRF_TSADC_TESTBIT_L, RK3399_GRF_TSADC_TESTBIT_VCM_EN_L); syscon_write_4(sc->sc_syscon, RK3399_GRF_TSADC_TESTBIT_H, RK3399_GRF_TSADC_TESTBIT_VCM_EN_H); DELAY(20); syscon_write_4(sc->sc_syscon, RK3399_GRF_SARADC_TESTBIT, RK3399_GRF_SARADC_TESTBIT_ON); syscon_write_4(sc->sc_syscon, RK3399_GRF_TSADC_TESTBIT_H, RK3399_GRF_TSADC_TESTBIT_H_ON); DELAY(100); syscon_unlock(sc->sc_syscon); rk_tsadc_init_counts(sc); rk_tsadc_init_tshut(sc, mode, polarity); } static void rk_tsadc_init_enable(struct rk_tsadc_softc *sc) { uint32_t val; val = TSADC_READ(sc, TSADC_AUTO_CON); val |= TSADC_AUTO_CON_AUTO_STATUS | TSADC_AUTO_CON_SRC1_LT_EN | TSADC_AUTO_CON_SRC0_LT_EN; TSADC_WRITE(sc, TSADC_AUTO_CON, val); /* Finally, register & enable the controller */ sysmon_envsys_register(sc->sc_sme); val = TSADC_READ(sc, TSADC_AUTO_CON); val |= TSADC_AUTO_CON_AUTO_EN | TSADC_AUTO_CON_Q_SEL; TSADC_WRITE(sc, TSADC_AUTO_CON, val); } static void rk_tsadc_init(struct rk_tsadc_softc *sc, int mode, int polarity) { (*sc->sc_rd->rd_init)(sc, mode, polarity); rk_tsadc_init_enable(sc); } /* run time support */ /* given edata, find the matching rk sensor structure */ static struct rk_tsadc_sensor * rk_tsadc_edata_to_sensor(struct rk_tsadc_softc * const sc, envsys_data_t *edata) { for (unsigned n = 0; n < sc->sc_rd->rd_num_sensors; n++) { struct rk_tsadc_sensor *rks = &sc->sc_sensors[n]; if (&rks->s_data == edata) return rks; } return NULL; } static void rk_tsadc_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) { struct rk_tsadc_softc * const sc = sme->sme_cookie; struct rk_tsadc_sensor *rks = rk_tsadc_edata_to_sensor(sc, edata); unsigned data; int temp; if (rks == NULL) return; data = TSADC_READ(sc, rks->s_data_reg) & sc->sc_data_mask; temp = rk_tsadc_data_to_temp(sc, data); DPRINTF("(%s:%s): temp/data %d/%u", sc->sc_sme->sme_name, rks->s_data.desc, temp, data); if (temp == sc->sc_data_mask) { edata->state = ENVSYS_SINVALID; } else { edata->value_cur = temp + TEMP_uC_TO_uK; edata->state = ENVSYS_SVALID; } } static void rk_tsadc_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata, sysmon_envsys_lim_t *lim, uint32_t *props) { struct rk_tsadc_softc *sc = sme->sme_cookie; struct rk_tsadc_sensor *rks = rk_tsadc_edata_to_sensor(sc, edata); if (rks == NULL) return; lim->sel_critmax = rks->s_tshut + TEMP_uC_TO_uK; lim->sel_warnmax = rks->s_warn + TEMP_uC_TO_uK; *props = PROP_CRITMAX | PROP_WARNMAX; } /* XXX do something with interrupts that don't happen yet. */ static int rk_tsadc_intr(void *arg) { struct rk_tsadc_softc * const sc = arg; uint32_t val; /* XXX */ DPRINTF("(%s): interrupted", sc->sc_sme->sme_name); for (unsigned n = 0; n < __arraycount(rk_tsadc_sensors); n++) { struct rk_tsadc_sensor *rks = &sc->sc_sensors[n]; rk_tsadc_refresh(sc->sc_sme, (envsys_data_t *)rks); } /* ack interrupt */ val = TSADC_READ(sc, TSADC_INT_PD); TSADC_WRITE(sc, TSADC_INT_PD, val & ~TSADC_INT_PD_EOC_INT_PD); return 1; } /* * Convert TDASC data codes to temp and reverse. The manual only has codes * and temperature values in 5 degC intervals, but says that interpolation * can be done to achieve better resolution between these values, and that * the spacing is linear. */ static int rk_tsadc_data_to_temp(struct rk_tsadc_softc *sc, uint32_t data) { unsigned i; const rk_data *rd = sc->sc_rd; if (data > rd->rd_max || data < rd->rd_min) { DPRINTF("data out of range (%u > %u || %u < %u)", data, rd->rd_max, data, rd->rd_min); return sc->sc_data_mask; } for (i = 1; i < rd->rd_size; i++) { if (rd->rd_array[i].data >= data) { int temprange, offset; uint32_t datarange, datadiff; unsigned first, secnd; if (rd->rd_array[i].data == data) return rd->rd_array[i].temp; /* must interpolate */ if (rd->rd_decr) { first = i; secnd = i+1; } else { first = i; secnd = i-1; } temprange = rd->rd_array[first].temp - rd->rd_array[secnd].temp; datarange = rd->rd_array[first].data - rd->rd_array[secnd].data; datadiff = data - rd->rd_array[secnd].data; offset = (temprange * datadiff) / datarange; return rd->rd_array[secnd].temp + offset; } } panic("didn't find range"); } static uint32_t rk_tsadc_temp_to_data(struct rk_tsadc_softc *sc, int temp) { unsigned i; const rk_data *rd = sc->sc_rd; for (i = 1; i < rd->rd_size; i++) { if (rd->rd_array[i].temp >= temp) { int temprange, tempdiff; uint32_t datarange, offset; unsigned first, secnd; if (rd->rd_array[i].temp == temp) return rd->rd_array[i].data; /* must interpolate */ if (rd->rd_decr) { first = i; secnd = i+1; } else { first = i; secnd = i-1; } datarange = rd->rd_array[first].data - rd->rd_array[secnd].data; temprange = rd->rd_array[first].temp - rd->rd_array[secnd].temp; tempdiff = temp - rd->rd_array[secnd].temp; offset = (datarange * tempdiff) / temprange; return rd->rd_array[secnd].data + offset; } } return sc->sc_data_mask; }