From: Vitalij Alshevsky Date: Thu, 29 Mar 2018 05:36:12 +0000 (+0300) Subject: sunxi: Add OPP table, cpu voltage control, cpu thermal sensor for Xunlong Orange... X-Git-Url: http://git.openwrt.org/?p=openwrt%2Fstaging%2Fhauke.git;a=commitdiff_plain;h=d867a91e4e86741c722d2d2f8e5f6cf5d1287dad sunxi: Add OPP table, cpu voltage control, cpu thermal sensor for Xunlong Orange Pi PC Temperature info: cat /sys/class/thermal/thermal_zone0/temp Scaling governors info (performance or powersave): cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor echo "powersave" > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor Warning! For hard working system active cooling must be Tests resulte with stress [menuconfig>tools>stress]: Temp after test with passive cooling (aluminum heat sink) about 78-82C Idle temp about 30-36C for i in 480000 816000 1008000 1296000 1440000 ; do echo "cur temp: `cat /sys/class/thermal/thermal_zone0/temp`" echo "set freq to: " $i echo $i >/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq stress -t 300 --cpu 4 --vm 2 --vm-bytes 512MB --io 4 echo "temp after test: `cat /sys/class/thermal/thermal_zone0/temp`" sleep 60 done Signed-off-by: Vitalij Alshevsky --- diff --git a/target/linux/sunxi/cortexa7/config-default b/target/linux/sunxi/cortexa7/config-default index cf41ddb4e6..e49607ec6e 100644 --- a/target/linux/sunxi/cortexa7/config-default +++ b/target/linux/sunxi/cortexa7/config-default @@ -8,3 +8,5 @@ CONFIG_DWMAC_SUN8I=y CONFIG_MDIO_BUS_MUX=y # CONFIG_PINCTRL_SUN5I is not set # CONFIG_SUN4I_A10_CCU is not set +CONFIG_REGULATOR_SY8106A=y +CONFIG_SUN8I_THS=y diff --git a/target/linux/sunxi/patches-4.14/050-sun8i-H3-Orange-Pi-PC-cpufreq-thermal.patch b/target/linux/sunxi/patches-4.14/050-sun8i-H3-Orange-Pi-PC-cpufreq-thermal.patch new file mode 100644 index 0000000000..d5dcf223b0 --- /dev/null +++ b/target/linux/sunxi/patches-4.14/050-sun8i-H3-Orange-Pi-PC-cpufreq-thermal.patch @@ -0,0 +1,876 @@ +--- a/arch/arm/boot/dts/sunxi-h3-h5.dtsi ++++ b/arch/arm/boot/dts/sunxi-h3-h5.dtsi +@@ -383,6 +383,16 @@ + }; + }; + ++ ths: thermal-sensor@1c25000 { ++ reg = <0x01c25000 0x400>, <0x01c14234 0x4>; ++ reg-names = "ths", "calibration"; ++ interrupts = ; ++ clocks = <&ccu CLK_BUS_THS>, <&ccu CLK_THS>; ++ clock-names = "ahb", "ths"; ++ resets = <&ccu RST_BUS_THS>; ++ reset-names = "ahb"; ++ }; ++ + timer@01c20c00 { + compatible = "allwinner,sun4i-a10-timer"; + reg = <0x01c20c00 0xa0>; +@@ -632,6 +642,20 @@ + #reset-cells = <1>; + }; + ++ r_i2c: i2c@1f02400 { ++ compatible = "allwinner,sun6i-a31-i2c"; ++ reg = <0x01f02400 0x400>; ++ interrupts = ; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&r_i2c_pins>; ++ clocks = <&r_ccu CLK_APB0_I2C>; ++ clock-frequency = <100000>; ++ resets = <&r_ccu RST_APB0_I2C>; ++ status = "disabled"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; ++ + codec_analog: codec-analog@01f015c0 { + compatible = "allwinner,sun8i-h3-codec-analog"; + reg = <0x01f015c0 0x4>; +@@ -662,6 +686,11 @@ + pins = "PL11"; + function = "s_cir_rx"; + }; ++ ++ r_i2c_pins: r-i2c { ++ pins = "PL0", "PL1"; ++ function = "s_i2c"; ++ }; + }; + }; + }; + +--- a/arch/arm/boot/dts/sun8i-h3.dtsi ++++ b/arch/arm/boot/dts/sun8i-h3.dtsi +@@ -43,34 +43,95 @@ + #include "sunxi-h3-h5.dtsi" + + / { ++ cpu0_opp_table: opp_table0 { ++ compatible = "operating-points-v2"; ++ opp-shared; ++ ++ opp@480000000 { ++ opp-hz = /bits/ 64 <480000000>; ++ opp-microvolt = <1040000 1040000 1100000>; ++ clock-latency-ns = <244144>; /* 8 32k periods */ ++ opp-suspend; ++ }; ++ ++ opp@648000000 { ++ opp-hz = /bits/ 64 <648000000>; ++ opp-microvolt = <1040000 1040000 1300000>; ++ clock-latency-ns = <244144>; /* 8 32k periods */ ++ }; ++ ++ opp@816000000 { ++ opp-hz = /bits/ 64 <816000000>; ++ opp-microvolt = <1100000 1100000 1300000>; ++ clock-latency-ns = <244144>; /* 8 32k periods */ ++ }; ++ ++ opp@1008000000 { ++ opp-hz = /bits/ 64 <1008000000>; ++ opp-microvolt = <1140000 1140000 1300000>; ++ clock-latency-ns = <244144>; /* 8 32k periods */ ++ }; ++ ++ opp@1200000000 { ++ opp-hz = /bits/ 64 <1200000000>; ++ opp-microvolt = <1200000 1200000 1300000>; ++ clock-latency-ns = <244144>; /* 8 32k periods */ ++ }; ++ }; ++ + cpus { + #address-cells = <1>; + #size-cells = <0>; + +- cpu@0 { ++ cpu0: cpu@0 { + compatible = "arm,cortex-a7"; + device_type = "cpu"; + reg = <0>; ++ clocks = <&ccu CLK_CPUX>; ++ clock-names = "cpu"; ++ operating-points-v2 = <&cpu0_opp_table>; ++ clock-frequency = <1200000000>; ++ #cooling-cells = <0x2>; + }; + + cpu@1 { + compatible = "arm,cortex-a7"; + device_type = "cpu"; + reg = <1>; ++ operating-points-v2 = <&cpu0_opp_table>; ++ clock-frequency = <1200000000>; + }; + + cpu@2 { + compatible = "arm,cortex-a7"; + device_type = "cpu"; + reg = <2>; ++ operating-points-v2 = <&cpu0_opp_table>; ++ clock-frequency = <1200000000>; + }; + + cpu@3 { + compatible = "arm,cortex-a7"; + device_type = "cpu"; + reg = <3>; ++ operating-points-v2 = <&cpu0_opp_table>; ++ clock-frequency = <1200000000>; + }; + }; ++ ++ iio-hwmon { ++ compatible = "iio-hwmon"; ++ io-channels = <&ths>; ++ }; ++ ++ thermal-zones { ++ cpu_thermal: cpu-thermal { ++ /* milliseconds */ ++ polling-delay-passive = <250>; ++ polling-delay = <1000>; ++ thermal-sensors = <&ths 0>; ++ }; ++ }; + + timer { + compatible = "arm,armv7-timer"; +@@ -121,6 +177,12 @@ + "sample"; + }; + ++&ths { ++ compatible = "allwinner,sun8i-h3-ths"; ++ #thermal-sensor-cells = <0>; ++ #io-channel-cells = <0>; ++}; ++ + &pio { + compatible = "allwinner,sun8i-h3-pinctrl"; + }; + +--- a/arch/arm/boot/dts/sun8i-h3-orangepi-pc.dts ++++ b/arch/arm/boot/dts/sun8i-h3-orangepi-pc.dts +@@ -42,10 +42,10 @@ + + /dts-v1/; + #include "sun8i-h3.dtsi" +-#include "sunxi-common-regulators.dtsi" + + #include + #include ++#include + + / { + model = "Xunlong Orange Pi PC"; +@@ -88,6 +88,109 @@ + gpios = <&r_pio 0 3 GPIO_ACTIVE_LOW>; + }; + }; ++ ++ reg_vcc3v3: vcc3v3 { ++ compatible = "regulator-fixed"; ++ regulator-name = "vcc3v3"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ }; ++ ++ reg_usb0_vbus: usb0-vbus { ++ compatible = "regulator-fixed"; ++ regulator-name = "usb0-vbus"; ++ regulator-min-microvolt = <5000000>; ++ regulator-max-microvolt = <5000000>; ++ enable-active-high; ++ gpio = <&r_pio 0 2 GPIO_ACTIVE_HIGH>; /* PL2 */ ++ status = "okay"; ++ }; ++}; ++ ++&cpu0_opp_table { ++ 1368000000 { ++ opp-hz = /bits/ 64 <1368000000>; ++ opp-microvolt = <1340000 1340000 1400000>; ++ clock-latency-ns = <244144>; /* 8 32k periods */ ++ }; ++ ++ 1440000000 { ++ opp-hz = /bits/ 64 <1440000000>; ++ opp-microvolt = <1400000 1400000 1400000>; ++ clock-latency-ns = <244144>; /* 8 32k periods */ ++ }; ++ ++ 1512000000 { ++ opp-hz = /bits/ 64 <1512000000>; ++ opp-microvolt = <1400000 1400000 1400000>; ++ clock-latency-ns = <244144>; /* 8 32k periods */ ++ }; ++}; ++ ++&cpu0 { ++ cooling-min-level = <0>; ++ cooling-max-level = <15>; ++ cpu-supply = <®_sy8106a>; ++}; ++ ++&cpu_thermal { ++ trips { ++ cpu_warm: cpu_warm { ++ temperature = <65000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ ++ cpu_hot: cpu_hot { ++ temperature = <75000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ ++ cpu_very_hot: cpu_very_hot { ++ temperature = <90000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ ++ cpu_crit: cpu_crit { ++ temperature = <105000>; ++ hysteresis = <2000>; ++ type = "critical"; ++ }; ++ }; ++ ++ cooling-maps { ++ cpu_warm_limit_cpu { ++ trip = <&cpu_warm>; ++ cooling-device = <&cpu0 THERMAL_NO_LIMIT 10>; ++ }; ++ ++ cpu_hot_limit_cpu { ++ trip = <&cpu_hot>; ++ cooling-device = <&cpu0 12 12>; ++ }; ++ ++ cpu_very_hot_limit_cpu { ++ trip = <&cpu_very_hot>; ++ cooling-device = <&cpu0 14 THERMAL_NO_LIMIT>; ++ }; ++ }; ++}; ++ ++&r_i2c { ++ status = "okay"; ++ ++ reg_sy8106a: regulator@65 { ++ compatible = "silergy,sy8106a"; ++ reg = <0x65>; ++ regulator-name = "vdd-cpux"; ++ regulator-min-microvolt = <1000000>; ++ regulator-max-microvolt = <1400000>; ++ regulator-ramp-delay = <200>; ++ regulator-boot-on; ++ regulator-always-on; ++ }; + }; + + &codec { + +--- a/arch/arm/configs/sunxi_defconfig ++++ b/arch/arm/configs/sunxi_defconfig +@@ -40,6 +40,7 @@ + CONFIG_AHCI_SUNXI=y + CONFIG_NETDEVICES=y + CONFIG_SUN4I_EMAC=y ++CONFIG_SUN8I_THS=y + # CONFIG_NET_VENDOR_ARC is not set + # CONFIG_NET_CADENCE is not set + # CONFIG_NET_VENDOR_BROADCOM is not set +@@ -93,6 +94,7 @@ + CONFIG_REGULATOR_FIXED_VOLTAGE=y + CONFIG_REGULATOR_AXP20X=y + CONFIG_REGULATOR_GPIO=y ++CONFIG_REGULATOR_SY8106A=y + CONFIG_MEDIA_SUPPORT=y + CONFIG_RC_CORE=y + CONFIG_RC_DEVICES=y + +--- a/drivers/regulator/Kconfig ++++ b/drivers/regulator/Kconfig +@@ -785,6 +785,13 @@ + This driver supports the internal VMMC regulator in the STw481x + PMIC chips. + ++config REGULATOR_SY8106A ++ tristate "Silergy SY8106A regulator" ++ depends on I2C && (OF || COMPILE_TEST) ++ select REGMAP_I2C ++ help ++ This driver supports SY8106A single output regulator. ++ + config REGULATOR_TPS51632 + tristate "TI TPS51632 Power Regulator" + depends on I2C + +--- a/drivers/regulator/Makefile ++++ b/drivers/regulator/Makefile +@@ -98,6 +98,7 @@ + obj-$(CONFIG_REGULATOR_SKY81452) += sky81452-regulator.o + obj-$(CONFIG_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o + obj-$(CONFIG_REGULATOR_STW481X_VMMC) += stw481x-vmmc.o ++obj-$(CONFIG_REGULATOR_SY8106A) += sy8106a-regulator.o + obj-$(CONFIG_REGULATOR_TI_ABB) += ti-abb-regulator.o + obj-$(CONFIG_REGULATOR_TPS6105X) += tps6105x-regulator.o + obj-$(CONFIG_REGULATOR_TPS62360) += tps62360-regulator.o + +--- a/drivers/thermal/Kconfig ++++ b/drivers/thermal/Kconfig +@@ -407,6 +407,16 @@ + Enable this option if you want to have support for thermal management + controller present in Mediatek SoCs + ++config SUN8I_THS ++ tristate "Thermal sensor driver for Allwinner H3" ++ depends on MACH_SUN8I || COMPILE_TEST ++ depends on HAS_IOMEM ++ depends on NVMEM_SUNXI_SID ++ depends on OF ++ depends on RESET_CONTROLLER ++ help ++ Enable this options for support thermal reporting on some Allwinner SoCs. ++ + menu "Broadcom thermal drivers" + depends on ARCH_BCM || COMPILE_TEST + source "drivers/thermal/broadcom/Kconfig" + +--- a/drivers/thermal/Makefile ++++ b/drivers/thermal/Makefile +@@ -58,6 +58,7 @@ + obj-$(CONFIG_TEGRA_SOCTHERM) += tegra/ + obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o + obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o ++obj-$(CONFIG_SUN8I_THS) += sun8i_ths.o + obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o + obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o + obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o + +--- /dev/null ++++ b/drivers/regulator/sy8106a-regulator.c +@@ -0,0 +1,168 @@ ++/* ++ * sy8106a-regulator.c - Regulator device driver for SY8106A ++ * ++ * Copyright (C) 2016 Ondřej Jirman ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Library General Public ++ * License as published by the Free Software Foundation; either ++ * version 2 of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Library General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SY8106A_REG_VOUT1_SEL 0x01 ++#define SY8106A_REG_VOUT_COM 0x02 ++#define SY8106A_REG_VOUT1_SEL_MASK 0x7f ++#define SY8106A_DISABLE_REG BIT(0) ++/* ++ * The I2C controlled voltage will only work when this bit is set; otherwise ++ * it will behave like a fixed regulator. ++ */ ++#define SY8106A_GO_BIT BIT(7) ++ ++struct sy8106a { ++ struct regulator_dev *rdev; ++ struct regmap *regmap; ++}; ++ ++static const struct regmap_config sy8106a_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++}; ++ ++static int sy8106a_set_voltage_sel(struct regulator_dev *rdev, unsigned int sel) ++{ ++ /* We use our set_voltage_sel in order to avoid unnecessary I2C ++ * chatter, because the regulator_get_voltage_sel_regmap using ++ * apply_bit would perform 4 unnecessary transfers instead of one, ++ * increasing the chance of error. ++ */ ++ return regmap_write(rdev->regmap, rdev->desc->vsel_reg, ++ sel | SY8106A_GO_BIT); ++} ++ ++static const struct regulator_ops sy8106a_ops = { ++ .set_voltage_sel = sy8106a_set_voltage_sel, ++ .set_voltage_time_sel = regulator_set_voltage_time_sel, ++ .get_voltage_sel = regulator_get_voltage_sel_regmap, ++ .list_voltage = regulator_list_voltage_linear, ++ /* Enabling/disabling the regulator is not yet implemented */ ++}; ++ ++/* Default limits measured in millivolts and milliamps */ ++#define SY8106A_MIN_MV 680 ++#define SY8106A_MAX_MV 1950 ++#define SY8106A_STEP_MV 10 ++ ++static const struct regulator_desc sy8106a_reg = { ++ .name = "SY8106A", ++ .id = 0, ++ .ops = &sy8106a_ops, ++ .type = REGULATOR_VOLTAGE, ++ .n_voltages = ((SY8106A_MAX_MV - SY8106A_MIN_MV) / SY8106A_STEP_MV) + 1, ++ .min_uV = (SY8106A_MIN_MV * 1000), ++ .uV_step = (SY8106A_STEP_MV * 1000), ++ .vsel_reg = SY8106A_REG_VOUT1_SEL, ++ .vsel_mask = SY8106A_REG_VOUT1_SEL_MASK, ++ /* ++ * This ramp_delay is a conservative default value which works on ++ * H3/H5 boards VDD-CPUX situations. ++ */ ++ .ramp_delay = 200, ++ .owner = THIS_MODULE, ++}; ++ ++/* ++ * I2C driver interface functions ++ */ ++static int sy8106a_i2c_probe(struct i2c_client *i2c, ++ const struct i2c_device_id *id) ++{ ++ struct sy8106a *chip; ++ struct device *dev = &i2c->dev; ++ struct regulator_dev *rdev = NULL; ++ struct regulator_config config = { }; ++ unsigned int selector; ++ int error; ++ ++ chip = devm_kzalloc(&i2c->dev, sizeof(struct sy8106a), GFP_KERNEL); ++ if (!chip) ++ return -ENOMEM; ++ ++ chip->regmap = devm_regmap_init_i2c(i2c, &sy8106a_regmap_config); ++ if (IS_ERR(chip->regmap)) { ++ error = PTR_ERR(chip->regmap); ++ dev_err(&i2c->dev, "Failed to allocate register map: %d\n", ++ error); ++ return error; ++ } ++ ++ config.dev = &i2c->dev; ++ config.regmap = chip->regmap; ++ config.driver_data = chip; ++ ++ config.of_node = dev->of_node; ++ config.init_data = of_get_regulator_init_data(dev, dev->of_node, ++ &sy8106a_reg); ++ ++ if (!config.init_data) ++ return -ENOMEM; ++ ++ /* Probe regulator */ ++ error = regmap_read(chip->regmap, SY8106A_REG_VOUT1_SEL, &selector); ++ if (error) { ++ dev_err(&i2c->dev, "Failed to read voltage at probe time: %d\n", error); ++ return error; ++ } ++ ++ rdev = devm_regulator_register(&i2c->dev, &sy8106a_reg, &config); ++ if (IS_ERR(rdev)) { ++ error = PTR_ERR(rdev); ++ dev_err(&i2c->dev, "Failed to register SY8106A regulator: %d\n", error); ++ return error; ++ } ++ ++ chip->rdev = rdev; ++ ++ i2c_set_clientdata(i2c, chip); ++ ++ return 0; ++} ++ ++static const struct of_device_id sy8106a_i2c_of_match[] = { ++ { .compatible = "silergy,sy8106a" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, sy8106a_i2c_of_match); ++ ++static const struct i2c_device_id sy8106a_i2c_id[] = { ++ { "sy8106a", 0 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(i2c, sy8106a_i2c_id); ++ ++static struct i2c_driver sy8106a_regulator_driver = { ++ .driver = { ++ .name = "sy8106a", ++ .of_match_table = of_match_ptr(sy8106a_i2c_of_match), ++ }, ++ .probe = sy8106a_i2c_probe, ++ .id_table = sy8106a_i2c_id, ++}; ++ ++module_i2c_driver(sy8106a_regulator_driver); ++ ++MODULE_AUTHOR("Ondřej Jirman "); ++MODULE_DESCRIPTION("Regulator device driver for Silergy SY8106A"); ++MODULE_LICENSE("GPL"); + +--- /dev/null ++++ b/drivers/thermal/sun8i_ths.c +@@ -0,0 +1,332 @@ ++/* ++ * Thermal sensor driver for Allwinner new SoCs ++ * ++ * Copyright (C) 2016 Ondřej Jirman ++ * Based on the work of Josef Gajdusek ++ * ++ * This software is licensed under the terms of the GNU General Public ++ * License version 2, as published by the Free Software Foundation, and ++ * may be copied, distributed, and modified under those terms. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define THS_H3_MAX_SENSOR_NUM 4 ++ ++#define THS_H3_CTRL0 0x00 ++#define THS_H3_CTRL2 0x40 ++#define THS_H3_INT_CTRL 0x44 ++#define THS_H3_STAT 0x48 ++#define THS_H3_FILTER 0x70 ++#define THS_H3_CDATA0 0x74 ++#define THS_H3_CDATA1 0x74 ++#define THS_H3_DATA(n) (0x80 + 4 * (n)) ++ ++#define THS_H3_CTRL0_SENSOR_ACQ0(x) (x) ++#define THS_H3_CTRL2_SENSE_EN(n) BIT(0 + (n)) ++#define THS_H3_CTRL2_SENSOR_ACQ1(x) ((x) << 16) ++#define THS_H3_INT_CTRL_DATA_IRQ_EN(n) BIT(8 + (n)) ++#define THS_H3_INT_CTRL_THERMAL_PER(x) ((x) << 12) ++#define THS_H3_STAT_DATA_IRQ_STS(n) BIT(8 + (n)) ++#define THS_H3_FILTER_TYPE(x) ((x) << 0) ++#define THS_H3_FILTER_EN BIT(2) ++ ++#define THS_H3_CLK_IN 40000000 /* Hz */ ++#define THS_H3_DATA_PERIOD 330 /* ms */ ++ ++#define THS_H3_FILTER_TYPE_VALUE 2 /* average over 2^(n+1) samples */ ++#define THS_H3_FILTER_DIV (1 << (THS_H3_FILTER_TYPE_VALUE + 1)) ++#define THS_H3_INT_CTRL_THERMAL_PER_VALUE \ ++ (THS_H3_DATA_PERIOD * (THS_H3_CLK_IN / 1000) / THS_H3_FILTER_DIV / 4096 - 1) ++#define THS_H3_CTRL0_SENSOR_ACQ0_VALUE 0x3f /* 16us */ ++#define THS_H3_CTRL2_SENSOR_ACQ1_VALUE 0x3f ++ ++struct sun8i_ths_data; ++ ++struct sun8i_ths_sensor { ++ struct sun8i_ths_data *data; ++ int id; ++ struct thermal_zone_device *tzd; ++ u32 val; ++}; ++ ++struct sun8i_ths_cfg { ++ int sensor_num; ++ int (*calc_temp)(u32 val); ++}; ++ ++struct sun8i_ths_data { ++ struct reset_control *reset; ++ struct clk *clk; ++ struct clk *busclk; ++ void __iomem *regs; ++ struct nvmem_cell *calcell; ++ const struct sun8i_ths_cfg *cfg; ++ struct sun8i_ths_sensor sensors[THS_H3_MAX_SENSOR_NUM]; ++}; ++ ++static int sun8i_ths_calc_temp_h3(u32 val) ++{ ++ return (217000 - (int)((val * 1000000) / 8253)); ++} ++ ++static int sun8i_ths_get_temp(void *_data, int *out) ++{ ++ struct sun8i_ths_sensor *sensor = _data; ++ ++ if (sensor->val == 0) ++ return -EBUSY; ++ ++ /* Formula and parameters from the Allwinner 3.4 kernel */ ++ *out = sensor->data->cfg->calc_temp(sensor->val); ++ return 0; ++} ++ ++static irqreturn_t sun8i_ths_irq_thread(int irq, void *_data) ++{ ++ struct sun8i_ths_data *data = _data; ++ int i; ++ ++ for (i = 0; i < data->cfg->sensor_num; i++) { ++ if (!(readl(data->regs + THS_H3_STAT) & ++ THS_H3_STAT_DATA_IRQ_STS(i))) ++ continue; ++ ++ writel(THS_H3_STAT_DATA_IRQ_STS(i), data->regs + THS_H3_STAT); ++ ++ data->sensors[i].val = readl(data->regs + THS_H3_DATA(i)); ++ if (data->sensors[i].val) ++ thermal_zone_device_update(data->sensors[i].tzd, ++ THERMAL_EVENT_TEMP_SAMPLE); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static void sun8i_ths_init(struct sun8i_ths_data *data) ++{ ++ u32 val; ++ int i; ++ ++ writel(THS_H3_CTRL0_SENSOR_ACQ0(THS_H3_CTRL0_SENSOR_ACQ0_VALUE), ++ data->regs + THS_H3_CTRL0); ++ writel(THS_H3_FILTER_EN | THS_H3_FILTER_TYPE(THS_H3_FILTER_TYPE_VALUE), ++ data->regs + THS_H3_FILTER); ++ ++ val = THS_H3_CTRL2_SENSOR_ACQ1(THS_H3_CTRL2_SENSOR_ACQ1_VALUE); ++ for (i = 0; i < data->cfg->sensor_num; i++) ++ val |= THS_H3_CTRL2_SENSE_EN(i); ++ writel(val, data->regs + THS_H3_CTRL2); ++ ++ val = THS_H3_INT_CTRL_THERMAL_PER(THS_H3_INT_CTRL_THERMAL_PER_VALUE); ++ for (i = 0; i < data->cfg->sensor_num; i++) ++ val |= THS_H3_INT_CTRL_DATA_IRQ_EN(i); ++ writel(val, data->regs + THS_H3_INT_CTRL); ++} ++ ++static int sun8i_ths_calibrate(struct sun8i_ths_data *data) ++{ ++ u32 *caldata; ++ size_t callen; ++ ++ caldata = nvmem_cell_read(data->calcell, &callen); ++ if (IS_ERR(caldata)) ++ return PTR_ERR(caldata); ++ ++ writel(be32_to_cpu(caldata[0]), data->regs + THS_H3_CDATA0); ++ if (callen > 4) ++ writel(be32_to_cpu(caldata[1]), data->regs + THS_H3_CDATA1); ++ ++ kfree(caldata); ++ return 0; ++} ++ ++static const struct thermal_zone_of_device_ops sun8i_ths_thermal_ops = { ++ .get_temp = sun8i_ths_get_temp, ++}; ++ ++static int sun8i_ths_probe(struct platform_device *pdev) ++{ ++ struct sun8i_ths_data *data; ++ struct resource *res; ++ int ret, irq, i; ++ ++ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->cfg = of_device_get_match_data(&pdev->dev); ++ if (!data->cfg) ++ return -EINVAL; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(&pdev->dev, "no memory resources defined\n"); ++ return -EINVAL; ++ } ++ ++ data->regs = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(data->regs)) { ++ ret = PTR_ERR(data->regs); ++ dev_err(&pdev->dev, "failed to ioremap THS registers: %d\n", ret); ++ return ret; ++ } ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) { ++ dev_err(&pdev->dev, "failed to get IRQ: %d\n", irq); ++ return irq; ++ } ++ ++ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, ++ sun8i_ths_irq_thread, IRQF_ONESHOT, ++ dev_name(&pdev->dev), data); ++ if (ret) ++ return ret; ++ ++ data->busclk = devm_clk_get(&pdev->dev, "ahb"); ++ if (IS_ERR(data->busclk)) { ++ ret = PTR_ERR(data->busclk); ++ dev_err(&pdev->dev, "failed to get ahb clk: %d\n", ret); ++ return ret; ++ } ++ ++ data->clk = devm_clk_get(&pdev->dev, "ths"); ++ if (IS_ERR(data->clk)) { ++ ret = PTR_ERR(data->clk); ++ dev_err(&pdev->dev, "failed to get ths clk: %d\n", ret); ++ return ret; ++ } ++ ++ data->reset = devm_reset_control_get(&pdev->dev, "ahb"); ++ if (IS_ERR(data->reset)) { ++ ret = PTR_ERR(data->reset); ++ dev_err(&pdev->dev, "failed to get reset: %d\n", ret); ++ return ret; ++ } ++ ++ ret = reset_control_deassert(data->reset); ++ if (ret) { ++ dev_err(&pdev->dev, "reset deassert failed: %d\n", ret); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(data->busclk); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to enable bus clk: %d\n", ret); ++ goto err_assert_reset; ++ } ++ ++ ret = clk_prepare_enable(data->clk); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to enable ths clk: %d\n", ret); ++ goto err_disable_bus; ++ } ++ ++ ret = clk_set_rate(data->clk, THS_H3_CLK_IN); ++ if (ret) ++ goto err_disable_ths; ++ ++ data->calcell = devm_nvmem_cell_get(&pdev->dev, "cal"); ++ if (IS_ERR(data->calcell)) { ++ if (PTR_ERR(data->calcell) == -EPROBE_DEFER) { ++ ret = PTR_ERR(data->calcell); ++ goto err_disable_ths; ++ } ++ /* ++ * Even if the external calibration data stored in eFUSE is ++ * not accessible, the THS hardware can still work, although ++ * the data won't be so accurate. ++ * The default value of calibration register is 0x800 for ++ * every sensor, and the calibration value is usually 0x7xx ++ * or 0x8xx, so they won't be away from the default value ++ * for a lot. ++ * So here we do not return if the calibartion data is not ++ * available, except the probe needs deferring. ++ */ ++ } else { ++ ret = sun8i_ths_calibrate(data); ++ if (ret) ++ goto err_disable_ths; ++ } ++ ++ for (i = 0; i < data->cfg->sensor_num; i++) { ++ data->sensors[i].data = data; ++ data->sensors[i].id = i; ++ data->sensors[i].tzd = ++ devm_thermal_zone_of_sensor_register(&pdev->dev, ++ i, &data->sensors[i], &sun8i_ths_thermal_ops); ++ if (IS_ERR(data->sensors[i].tzd)) { ++ ret = PTR_ERR(data->sensors[i].tzd); ++ dev_err(&pdev->dev, ++ "failed to register thermal zone %d: %d\n", ++ i, ret); ++ goto err_disable_ths; ++ } ++ } ++ ++ sun8i_ths_init(data); ++ ++ platform_set_drvdata(pdev, data); ++ return 0; ++ ++err_disable_ths: ++ clk_disable_unprepare(data->clk); ++err_disable_bus: ++ clk_disable_unprepare(data->busclk); ++err_assert_reset: ++ reset_control_assert(data->reset); ++ return ret; ++} ++ ++static int sun8i_ths_remove(struct platform_device *pdev) ++{ ++ struct sun8i_ths_data *data = platform_get_drvdata(pdev); ++ ++ reset_control_assert(data->reset); ++ clk_disable_unprepare(data->clk); ++ clk_disable_unprepare(data->busclk); ++ return 0; ++} ++ ++static const struct sun8i_ths_cfg sun8i_h3_ths_cfg = { ++ .sensor_num = 1, ++ .calc_temp = sun8i_ths_calc_temp_h3, ++}; ++ ++static const struct of_device_id sun8i_ths_id_table[] = { ++ { .compatible = "allwinner,sun8i-h3-ths", .data = &sun8i_h3_ths_cfg }, ++ { /* sentinel */ }, ++}; ++MODULE_DEVICE_TABLE(of, sun8i_ths_id_table); ++ ++static struct platform_driver sun8i_ths_driver = { ++ .probe = sun8i_ths_probe, ++ .remove = sun8i_ths_remove, ++ .driver = { ++ .name = "sun8i_ths", ++ .of_match_table = sun8i_ths_id_table, ++ }, ++}; ++ ++module_platform_driver(sun8i_ths_driver); ++ ++MODULE_AUTHOR("Ondřej Jirman "); ++MODULE_DESCRIPTION("Thermal sensor driver for new Allwinner SoCs"); ++MODULE_LICENSE("GPL v2");