Module Name: src
Committed By: jmcneill
Date: Thu Oct 5 01:30:26 UTC 2017
Modified Files:
src/sys/arch/arm/sunxi: files.sunxi
Added Files:
src/sys/arch/arm/sunxi: sunxi_thermal.c
Log Message:
Add driver for thermal sensor controller found in Allwinner H3, A64, and
A83T SoCs.
To generate a diff of this commit:
cvs rdiff -u -r1.25 -r1.26 src/sys/arch/arm/sunxi/files.sunxi
cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/sunxi/sunxi_thermal.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/arch/arm/sunxi/files.sunxi
diff -u src/sys/arch/arm/sunxi/files.sunxi:1.25 src/sys/arch/arm/sunxi/files.sunxi:1.26
--- src/sys/arch/arm/sunxi/files.sunxi:1.25 Tue Oct 3 23:42:17 2017
+++ src/sys/arch/arm/sunxi/files.sunxi Thu Oct 5 01:30:26 2017
@@ -1,4 +1,4 @@
-# $NetBSD: files.sunxi,v 1.25 2017/10/03 23:42:17 jmcneill Exp $
+# $NetBSD: files.sunxi,v 1.26 2017/10/05 01:30:26 jmcneill Exp $
#
# Configuration info for Allwinner sunxi family SoCs
#
@@ -174,6 +174,11 @@ device sunxisid
attach sunxisid at fdt with sunxi_sid
file arch/arm/sunxi/sunxi_sid.c sunxi_sid
+# Thermal sensor controller
+device sunxithermal: sysmon_envsys, sysmon_taskq
+attach sunxithermal at fdt with sunxi_thermal
+file arch/arm/sunxi/sunxi_thermal.c sunxi_thermal
+
# SOC parameters
defflag opt_soc.h SOC_SUNXI
defflag opt_soc.h SOC_SUN5I: SOC_SUNXI
Added files:
Index: src/sys/arch/arm/sunxi/sunxi_thermal.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_thermal.c:1.1
--- /dev/null Thu Oct 5 01:30:26 2017
+++ src/sys/arch/arm/sunxi/sunxi_thermal.c Thu Oct 5 01:30:26 2017
@@ -0,0 +1,572 @@
+/* $NetBSD: sunxi_thermal.c,v 1.1 2017/10/05 01:30:26 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2016-2017 Jared McNeill <[email protected]>
+ * 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.
+ */
+
+/*
+ * Allwinner thermal sensor controller
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sunxi_thermal.c,v 1.1 2017/10/05 01:30:26 jmcneill Exp $");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/reboot.h>
+
+#include <dev/sysmon/sysmonvar.h>
+#include <dev/sysmon/sysmon_taskq.h>
+
+#include <dev/fdt/fdtvar.h>
+
+#include <arm/sunxi/sunxi_sid.h>
+
+#define THS_CTRL0 0x00
+#define THS_CTRL1 0x04
+#define ADC_CALI_EN (1 << 17)
+#define THS_CTRL2 0x40
+#define SENSOR_ACQ1_SHIFT 16
+#define SENSOR2_EN (1 << 2)
+#define SENSOR1_EN (1 << 1)
+#define SENSOR0_EN (1 << 0)
+#define THS_INTC 0x44
+#define THS_INTS 0x48
+#define THS2_DATA_IRQ_STS (1 << 10)
+#define THS1_DATA_IRQ_STS (1 << 9)
+#define THS0_DATA_IRQ_STS (1 << 8)
+#define SHUT_INT2_STS (1 << 6)
+#define SHUT_INT1_STS (1 << 5)
+#define SHUT_INT0_STS (1 << 4)
+#define ALARM_INT2_STS (1 << 2)
+#define ALARM_INT1_STS (1 << 1)
+#define ALARM_INT0_STS (1 << 0)
+#define THS_ALARM0_CTRL 0x50
+#define ALARM_T_HOT_MASK 0xfff
+#define ALARM_T_HOT_SHIFT 16
+#define ALARM_T_HYST_MASK 0xfff
+#define ALARM_T_HYST_SHIFT 0
+#define THS_SHUTDOWN0_CTRL 0x60
+#define SHUT_T_HOT_MASK 0xfff
+#define SHUT_T_HOT_SHIFT 16
+#define THS_FILTER 0x70
+#define THS_CALIB0 0x74
+#define THS_CALIB1 0x78
+#define THS_DATA0 0x80
+#define THS_DATA1 0x84
+#define THS_DATA2 0x88
+#define DATA_MASK 0xfff
+
+#define A83T_ADC_ACQUIRE_TIME 0x17
+#define A83T_FILTER 0x4
+#define A83T_INTC 0x1000
+#define A83T_TEMP_BASE 2719000
+#define A83T_TEMP_MUL 1000
+#define A83T_TEMP_DIV 14186
+#define A83T_CLK_RATE 24000000
+
+#define A64_ADC_ACQUIRE_TIME 0x190
+#define A64_FILTER 0x6
+#define A64_INTC 0x18000
+#define A64_TEMP_BASE 2170000
+#define A64_TEMP_MUL 1000
+#define A64_TEMP_DIV 8560
+#define A64_CLK_RATE 4000000
+
+#define H3_ADC_ACQUIRE_TIME 0x3f
+#define H3_FILTER 0x6
+#define H3_INTC 0x191000
+#define H3_TEMP_BASE 217
+#define H3_TEMP_MUL 1000
+#define H3_TEMP_DIV 8253
+#define H3_TEMP_MINUS 1794000
+#define H3_CLK_RATE 4000000
+#define H3_INIT_ALARM 90 /* degC */
+#define H3_INIT_SHUT 105 /* degC */
+
+#define TEMP_C_TO_K 273150000
+#define SENSOR_ENABLE_ALL (SENSOR0_EN|SENSOR1_EN|SENSOR2_EN)
+#define SHUT_INT_ALL (SHUT_INT0_STS|SHUT_INT1_STS|SHUT_INT2_STS)
+#define ALARM_INT_ALL (ALARM_INT0_STS)
+
+#define MAX_SENSORS 3
+
+#if notyet
+#define THROTTLE_ENABLE_DEFAULT 1
+
+/* Enable thermal throttling */
+static int sunxi_thermal_throttle_enable = THROTTLE_ENABLE_DEFAULT;
+#endif
+
+struct sunxi_thermal_sensor {
+ const char *name;
+ const char *desc;
+ int init_alarm;
+ int init_shut;
+};
+
+struct sunxi_thermal_config {
+ struct sunxi_thermal_sensor sensors[MAX_SENSORS];
+ int nsensors;
+ uint64_t clk_rate;
+ uint32_t adc_acquire_time;
+ int adc_cali_en;
+ uint32_t filter;
+ uint32_t intc;
+ int (*to_temp)(uint32_t);
+ uint32_t (*to_reg)(int);
+ int temp_base;
+ int temp_mul;
+ int temp_div;
+ int calib0, calib1;
+ uint32_t calib0_mask, calib1_mask;
+};
+
+static int
+a83t_to_temp(uint32_t val)
+{
+ return ((A83T_TEMP_BASE - (val * A83T_TEMP_MUL)) / A83T_TEMP_DIV);
+}
+
+static const struct sunxi_thermal_config a83t_config = {
+ .nsensors = 3,
+ .sensors = {
+ [0] = {
+ .name = "cluster0",
+ .desc = "CPU cluster 0 temperature",
+ },
+ [1] = {
+ .name = "cluster1",
+ .desc = "CPU cluster 1 temperature",
+ },
+ [2] = {
+ .name = "gpu",
+ .desc = "GPU temperature",
+ },
+ },
+ .clk_rate = A83T_CLK_RATE,
+ .adc_acquire_time = A83T_ADC_ACQUIRE_TIME,
+ .adc_cali_en = 1,
+ .filter = A83T_FILTER,
+ .intc = A83T_INTC,
+ .to_temp = a83t_to_temp,
+ .calib0_mask = 0xffffffff,
+ .calib1_mask = 0xffffffff,
+};
+
+static int
+a64_to_temp(uint32_t val)
+{
+ return ((A64_TEMP_BASE - (val * A64_TEMP_MUL)) / A64_TEMP_DIV);
+}
+
+static const struct sunxi_thermal_config a64_config = {
+ .nsensors = 3,
+ .sensors = {
+ [0] = {
+ .name = "cpu",
+ .desc = "CPU temperature",
+ },
+ [1] = {
+ .name = "gpu1",
+ .desc = "GPU temperature 1",
+ },
+ [2] = {
+ .name = "gpu2",
+ .desc = "GPU temperature 2",
+ },
+ },
+ .clk_rate = A64_CLK_RATE,
+ .adc_acquire_time = A64_ADC_ACQUIRE_TIME,
+ .filter = A64_FILTER,
+ .intc = A64_INTC,
+ .to_temp = a64_to_temp,
+};
+
+static int
+h3_to_temp(uint32_t val)
+{
+ return (H3_TEMP_BASE - ((val * H3_TEMP_MUL) / H3_TEMP_DIV));
+}
+
+static uint32_t
+h3_to_reg(int val)
+{
+ return ((H3_TEMP_MINUS - (val * H3_TEMP_DIV)) / H3_TEMP_MUL);
+}
+
+static const struct sunxi_thermal_config h3_config = {
+ .nsensors = 1,
+ .sensors = {
+ [0] = {
+ .name = "cpu",
+ .desc = "CPU temperature",
+ .init_alarm = H3_INIT_ALARM,
+ .init_shut = H3_INIT_SHUT,
+ },
+ },
+ .clk_rate = H3_CLK_RATE,
+ .adc_acquire_time = H3_ADC_ACQUIRE_TIME,
+ .filter = H3_FILTER,
+ .intc = H3_INTC,
+ .to_temp = h3_to_temp,
+ .to_reg = h3_to_reg,
+ .calib0_mask = 0xfff,
+};
+
+static struct of_compat_data compat_data[] = {
+ { "allwinner,sun8i-a83t-ts", (uintptr_t)&a83t_config },
+ { "allwinner,sun8i-h3-ts", (uintptr_t)&h3_config },
+ { "allwinner,sun50i-a64-ts", (uintptr_t)&a64_config },
+ { NULL, (uintptr_t)NULL }
+};
+
+struct sunxi_thermal_softc {
+ device_t dev;
+ int phandle;
+ bus_space_tag_t bst;
+ bus_space_handle_t bsh;
+ struct sunxi_thermal_config *conf;
+
+ kmutex_t lock;
+ callout_t tick;
+
+ struct sysmon_envsys *sme;
+ envsys_data_t data[MAX_SENSORS];
+};
+
+#define RD4(sc, reg) \
+ bus_space_read_4((sc)->bst, (sc)->bsh, (reg))
+#define WR4(sc, reg, val) \
+ bus_space_write_4((sc)->bst, (sc)->bsh, (reg), (val))
+
+static int
+sunxi_thermal_init(struct sunxi_thermal_softc *sc)
+{
+ uint32_t calib[2];
+ int error;
+
+ if (sc->conf->calib0_mask != 0 || sc->conf->calib1_mask != 0) {
+ /* Read calibration settings from SRAM */
+ error = sunxi_sid_read_tscalib(calib);
+ if (error != 0)
+ return error;
+
+ calib[0] &= sc->conf->calib0_mask;
+ calib[1] &= sc->conf->calib1_mask;
+
+ /* Write calibration settings to thermal controller */
+ if (calib[0] != 0)
+ WR4(sc, THS_CALIB0, calib[0]);
+ if (calib[1] != 0)
+ WR4(sc, THS_CALIB1, calib[1]);
+ }
+
+ /* Configure ADC acquire time (CLK_IN/(N+1)) and enable sensors */
+ WR4(sc, THS_CTRL1, ADC_CALI_EN);
+ WR4(sc, THS_CTRL0, sc->conf->adc_acquire_time);
+ WR4(sc, THS_CTRL2, sc->conf->adc_acquire_time << SENSOR_ACQ1_SHIFT);
+
+ /* Enable average filter */
+ WR4(sc, THS_FILTER, sc->conf->filter);
+
+ /* Enable interrupts */
+ WR4(sc, THS_INTS, RD4(sc, THS_INTS));
+ WR4(sc, THS_INTC, sc->conf->intc | SHUT_INT_ALL | ALARM_INT_ALL);
+
+ /* Enable sensors */
+ WR4(sc, THS_CTRL2, RD4(sc, THS_CTRL2) | SENSOR_ENABLE_ALL);
+
+ return 0;
+}
+
+static int
+sunxi_thermal_gettemp(struct sunxi_thermal_softc *sc, int sensor)
+{
+ uint32_t val;
+
+ val = RD4(sc, THS_DATA0 + (sensor * 4));
+
+ return sc->conf->to_temp(val);
+}
+
+static int
+sunxi_thermal_getshut(struct sunxi_thermal_softc *sc, int sensor)
+{
+ uint32_t val;
+
+ val = RD4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4));
+ val = (val >> SHUT_T_HOT_SHIFT) & SHUT_T_HOT_MASK;
+
+ return sc->conf->to_temp(val);
+}
+
+static void
+sunxi_thermal_setshut(struct sunxi_thermal_softc *sc, int sensor, int temp)
+{
+ uint32_t val;
+
+ val = RD4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4));
+ val &= ~(SHUT_T_HOT_MASK << SHUT_T_HOT_SHIFT);
+ val |= (sc->conf->to_reg(temp) << SHUT_T_HOT_SHIFT);
+ WR4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4), val);
+}
+
+static int
+sunxi_thermal_gethyst(struct sunxi_thermal_softc *sc, int sensor)
+{
+ uint32_t val;
+
+ val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4));
+ val = (val >> ALARM_T_HYST_SHIFT) & ALARM_T_HYST_MASK;
+
+ return sc->conf->to_temp(val);
+}
+
+static int
+sunxi_thermal_getalarm(struct sunxi_thermal_softc *sc, int sensor)
+{
+ uint32_t val;
+
+ val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4));
+ val = (val >> ALARM_T_HOT_SHIFT) & ALARM_T_HOT_MASK;
+
+ return sc->conf->to_temp(val);
+}
+
+static void
+sunxi_thermal_setalarm(struct sunxi_thermal_softc *sc, int sensor, int temp)
+{
+ uint32_t val;
+
+ val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4));
+ val &= ~(ALARM_T_HOT_MASK << ALARM_T_HOT_SHIFT);
+ val |= (sc->conf->to_reg(temp) << ALARM_T_HOT_SHIFT);
+ WR4(sc, THS_ALARM0_CTRL + (sensor * 4), val);
+}
+
+static void
+sunxi_thermal_task_shut(void *arg)
+{
+ struct sunxi_thermal_softc * const sc = arg;
+
+ device_printf(sc->dev,
+ "WARNING - current temperature exceeds safe limits\n");
+
+ cpu_reboot(RB_POWERDOWN, NULL);
+}
+
+static void
+sunxi_thermal_task_alarm(void *arg)
+{
+ struct sunxi_thermal_softc * const sc = arg;
+
+ const int alarm_val = sunxi_thermal_getalarm(sc, 0);
+ const int temp_val = sunxi_thermal_gettemp(sc, 0);
+
+ if (temp_val < alarm_val)
+ pmf_event_inject(NULL, PMFE_THROTTLE_DISABLE);
+ else
+ callout_schedule(&sc->tick, hz);
+}
+
+static void
+sunxi_thermal_tick(void *arg)
+{
+ struct sunxi_thermal_softc * const sc = arg;
+
+ sysmon_task_queue_sched(0, sunxi_thermal_task_alarm, sc);
+}
+
+static int
+sunxi_thermal_intr(void *arg)
+{
+ struct sunxi_thermal_softc * const sc = arg;
+ uint32_t ints;
+
+ mutex_enter(&sc->lock);
+
+ ints = RD4(sc, THS_INTS);
+ WR4(sc, THS_INTS, ints);
+
+ if ((ints & SHUT_INT_ALL) != 0)
+ sysmon_task_queue_sched(0, sunxi_thermal_task_shut, sc);
+
+ if ((ints & ALARM_INT_ALL) != 0) {
+ pmf_event_inject(NULL, PMFE_THROTTLE_ENABLE);
+ sysmon_task_queue_sched(0, sunxi_thermal_task_alarm, sc);
+ }
+
+ mutex_exit(&sc->lock);
+
+ return 1;
+}
+
+static void
+sunxi_thermal_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
+{
+ struct sunxi_thermal_softc * const sc = sme->sme_cookie;
+
+ const int64_t temp = sunxi_thermal_gettemp(sc, edata->private);
+
+ edata->value_cur = temp * 1000000 + TEMP_C_TO_K;
+ edata->state = ENVSYS_SVALID;
+}
+
+static int
+sunxi_thermal_init_clocks(struct sunxi_thermal_softc *sc)
+{
+ struct fdtbus_reset *rst;
+ struct clk *clk;
+ int error;
+
+ clk = fdtbus_clock_get(sc->phandle, "ahb");
+ if (clk == NULL)
+ return ENXIO;
+ error = clk_enable(clk);
+ if (error != 0)
+ return error;
+
+ clk = fdtbus_clock_get(sc->phandle, "ths");
+ if (clk == NULL)
+ return ENXIO;
+ error = clk_set_rate(clk, sc->conf->clk_rate);
+ if (error != 0)
+ return error;
+ error = clk_enable(clk);
+ if (error != 0)
+ return error;
+
+ rst = fdtbus_reset_get_index(sc->phandle, 0);
+ if (rst == NULL)
+ return ENXIO;
+ error = fdtbus_reset_deassert(rst);
+ if (error != 0)
+ return error;
+
+ return 0;
+}
+
+static int
+sunxi_thermal_match(device_t parent, cfdata_t cf, void *aux)
+{
+ struct fdt_attach_args * const faa = aux;
+
+ return of_match_compat_data(faa->faa_phandle, compat_data);
+}
+
+static void
+sunxi_thermal_attach(device_t parent, device_t self, void *aux)
+{
+ struct sunxi_thermal_softc * const sc = device_private(self);
+ struct fdt_attach_args * const faa = aux;
+ const int phandle = faa->faa_phandle;
+ char intrstr[128];
+ bus_addr_t addr;
+ bus_size_t size;
+ void *ih;
+ int i;
+
+ if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+ aprint_error(": couldn't get registers\n");
+ return;
+ }
+
+ sc->dev = self;
+ sc->phandle = phandle;
+ sc->bst = faa->faa_bst;
+ sc->conf = (void *)of_search_compatible(phandle, compat_data)->data;
+ if (bus_space_map(sc->bst, addr, size, 0, &sc->bsh) != 0) {
+ aprint_error(": couldn't map registers\n");
+ return;
+ }
+ mutex_init(&sc->lock, MUTEX_DEFAULT, IPL_VM);
+ callout_init(&sc->tick, CALLOUT_MPSAFE);
+ callout_setfunc(&sc->tick, sunxi_thermal_tick, sc);
+
+ if (sunxi_thermal_init_clocks(sc) != 0) {
+ aprint_error(": couldn't enable clocks\n");
+ return;
+ }
+
+ if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
+ aprint_error(": couldn't decode interrupt\n");
+ return;
+ }
+
+ aprint_naive("\n");
+ aprint_normal(": Thermal sensor controller\n");
+
+ ih = fdtbus_intr_establish(phandle, 0, IPL_VM, FDT_INTR_MPSAFE,
+ sunxi_thermal_intr, sc);
+ if (ih == NULL) {
+ aprint_error_dev(self, "couldn't establish interrupt on %s\n",
+ intrstr);
+ return;
+ }
+ aprint_normal_dev(self, "interrupting on %s\n", intrstr);
+
+ for (i = 0; i < sc->conf->nsensors; i++) {
+ if (sc->conf->sensors[i].init_alarm > 0)
+ sunxi_thermal_setalarm(sc, i,
+ sc->conf->sensors[i].init_alarm);
+ if (sc->conf->sensors[i].init_shut > 0)
+ sunxi_thermal_setshut(sc, i,
+ sc->conf->sensors[i].init_shut);
+ }
+
+ if (sunxi_thermal_init(sc) != 0) {
+ aprint_error_dev(self, "failed to initialize sensors\n");
+ return;
+ }
+
+ sc->sme = sysmon_envsys_create();
+ sc->sme->sme_name = device_xname(self);
+ sc->sme->sme_cookie = sc;
+ sc->sme->sme_refresh = sunxi_thermal_refresh;
+ for (i = 0; i < sc->conf->nsensors; i++) {
+ sc->data[i].private = i;
+ sc->data[i].units = ENVSYS_STEMP;
+ sc->data[i].state = ENVSYS_SINVALID;
+ snprintf(sc->data[i].desc, sizeof(sc->data[i].desc),
+ sc->conf->sensors[i].desc);
+ sysmon_envsys_sensor_attach(sc->sme, &sc->data[i]);
+ }
+ sysmon_envsys_register(sc->sme);
+
+ for (i = 0; i < sc->conf->nsensors; i++) {
+ device_printf(self,
+ "%s: alarm %dC hyst %dC shut %dC\n",
+ sc->conf->sensors[i].name,
+ sunxi_thermal_getalarm(sc, i),
+ sunxi_thermal_gethyst(sc, i),
+ sunxi_thermal_getshut(sc, i));
+ }
+}
+
+CFATTACH_DECL_NEW(sunxi_thermal, sizeof(struct sunxi_thermal_softc),
+ sunxi_thermal_match, sunxi_thermal_attach, NULL, NULL);