/* $NetBSD$ */ /* * 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$"); /* * Driver for the TSADC temperature sensor monitor in RK3328 and RK3399. * * TODO: * - make work(!) * - handle setting various temp values * - export warn/crit values */ #include #include #include #include #include #include #include #include #include #include #define DPRINTF(fmt, ...) \ printf("%s:%d: " fmt "\n", __func__, __LINE__, ## __VA_ARGS__) #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 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 NUM_SENSORS 2 static const struct { uint32_t data; /* register value */ int temp; /* micro-degC */ } rk3399_data_table[] = { #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), #undef ENTRY }; struct rk_tsadc_sensor { envsys_data_t s_data; unsigned s_data_reg; unsigned s_comp_int; unsigned s_low_int; bool s_attached; }; /* Ugh. we get this pointer back from envsys. */ CTASSERT(offsetof(struct rk_tsadc_sensor, s_data) == 0); 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; unsigned sc_data_mask; void *sc_ih; struct sysmon_envsys *sc_sme; struct rk_tsadc_sensor sc_sensors[NUM_SENSORS]; struct clk *sc_clock; struct clk *sc_clockapb; struct fdtbus_reset *sc_reset; struct syscon *sc_syscon; }; 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_init_tshut(struct rk_tsadc_softc *, int, int, int); static void rk_tsadc_init_grf(struct rk_tsadc_softc *); static void rk_tsadc_init_enable(struct rk_tsadc_softc *); 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); static void rk_tsadc_refresh(struct sysmon_envsys *, envsys_data_t *); static const struct rk_tsadc_sensor rk_tsadc_sensors[] = { { .s_data = { .desc = "CPU" }, .s_data_reg = TSADC_DATA0, .s_comp_int = TSADC_COMP0_INT, .s_low_int = TSADC_COMP0_LOW_INT, }, { .s_data = { .desc = "GPU" }, .s_data_reg = TSADC_DATA1, .s_comp_int = TSADC_COMP1_INT, .s_low_int = TSADC_COMP1_LOW_INT, }, }; static const char * const compatible[] = { "rockchip,rk3328-tsadc", "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); 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); } 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 tshut_mode, tshut_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_data_mask = 0xfff; pmf_device_register(self, NULL, NULL); memcpy(sc->sc_sensors, rk_tsadc_sensors, sizeof(sc->sc_sensors)); for (unsigned n = 0; n < NUM_SENSORS; n++) { struct rk_tsadc_sensor *s = &sc->sc_sensors[n]; s->s_data.flags = ENVSYS_FNEED_REFRESH; s->s_data.units = ENVSYS_STEMP; s->s_data.state = ENVSYS_SINVALID; if (sysmon_envsys_sensor_attach(sc->sc_sme, &s->s_data)) goto fail; s->s_attached = true; } 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); /* Default to tshut via gpio and tshut low is active */ if (of_getprop_uint32(phandle, "rockchip,hw-tshut-mode", &tshut_mode) != 0) { aprint_error(": could not get TSHUT mode, default to GPIO"); tshut_mode = TSHUT_MODE_GPIO; } if (tshut_mode != TSHUT_MODE_CPU && tshut_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", &tshut_polarity) != 0) { aprint_error(": could not get TSHUT polarity, default to low"); tshut_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 */ 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_counts(sc); rk_tsadc_init_tshut(sc, tshut_mode, tshut_polarity, tshut_temp); rk_tsadc_init_grf(sc); rk_tsadc_init_enable(sc); 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 < NUM_SENSORS; n++) { struct rk_tsadc_sensor *s = &sc->sc_sensors[n]; if (s->s_attached) { sysmon_envsys_sensor_detach(sc->sc_sme, &s->s_data); s->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; 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, TSADC_AUTO_PERIOD_TIME); TSADC_WRITE(sc, TSADC_AUTO_PERIOD_HT, TSADC_AUTO_PERIOD_TIME); TSADC_WRITE(sc, TSADC_HIGH_INT_DEBOUNCE, TSADC_HT_DEBOUNCE_COUNT); TSADC_WRITE(sc, TSADC_HIGH_TSHUT_DEBOUNCE, TSADC_HT_DEBOUNCE_COUNT); } static void rk_tsadc_init_tshut(struct rk_tsadc_softc *sc, int mode, int polarity, int temp) { uint32_t val; uint32_t data = rk_tsadc_temp_to_data(sc, temp); DPRINTF("(%s): data %u temp %d", sc->sc_sme->sme_name, data, temp); /* Handle TSHUT temp setting. */ if (data == sc->sc_data_mask) { aprint_error_dev(sc->sc_dev, "Unable to convert temp %u.%06u into TSASC code", temp / 1000000, temp % 1000000); } else { TSADC_WRITE(sc, TSADC_COMP0_SHUT, data); TSADC_WRITE(sc, TSADC_COMP1_SHUT, data); val = TSADC_READ(sc, TSADC_AUTO_CON); val |= TSADC_AUTO_CON_SRC1_EN | TSADC_AUTO_CON_SRC0_EN; TSADC_WRITE(sc, TSADC_AUTO_CON, val); } /* 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_grf(struct rk_tsadc_softc *sc) { 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); } 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_AUTO_CON_SRC1_EN | TSADC_AUTO_CON_SRC0_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 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; n < __arraycount(rk_tsadc_sensors); n++) { struct rk_tsadc_sensor *s = &sc->sc_sensors[n]; rk_tsadc_refresh(sc->sc_sme, (envsys_data_t *)s); } /* ack interrupt */ val = TSADC_READ(sc, TSADC_INT_PD); TSADC_WRITE(sc, TSADC_INT_PD, val & ~TSADC_INT_PD_EOC_INT_PD); return 1; } static int rk_tsadc_data_to_temp(struct rk_tsadc_softc *sc, uint32_t data) { unsigned i; for (i = 1; i < __arraycount(rk3399_data_table); i++) { if (rk3399_data_table[i].data >= data) { if (rk3399_data_table[i].data == data) return rk3399_data_table[i].temp; /* Need to interpolate, manual says it is linear */ /* XXX */ return rk3399_data_table[i-1].temp; } } return rk3399_data_table[i-1].temp; } static uint32_t rk_tsadc_temp_to_data(struct rk_tsadc_softc *sc, int temp) { unsigned i; for (i = 1; i < __arraycount(rk3399_data_table); i++) { if (rk3399_data_table[i].temp >= temp) { if (rk3399_data_table[i].temp == temp) return rk3399_data_table[i].data; /* Need to interpolate, manual says it is linear */ /* XXX */ return rk3399_data_table[i-1].data; } } return rk3399_data_table[i-1].data; } 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 *s = (struct rk_tsadc_sensor *)edata; uint32_t data = TSADC_READ(sc, s->s_data_reg) & sc->sc_data_mask; int temp = rk_tsadc_data_to_temp(sc, data); DPRINTF("(%s:%s): data %u temp %d", sc->sc_sme->sme_name, edata->desc, data, temp); if (temp == sc->sc_data_mask) { edata->state = ENVSYS_SINVALID; } else { edata->value_cur = temp + TEMP_uC_TO_uK; edata->state = ENVSYS_SVALID; } }