--- /dev/null
+--- 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 = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
++ 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 = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>;
++ 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 <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/input/input.h>
++#include <dt-bindings/thermal/thermal.h>
+
+ / {
+ 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 <megous@megous.com>
++ *
++ * 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 <linux/err.h>
++#include <linux/i2c.h>
++#include <linux/module.h>
++#include <linux/regmap.h>
++#include <linux/regulator/driver.h>
++#include <linux/regulator/of_regulator.h>
++
++#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 <megous@megous.com>");
++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 <atx@atx.name>
++ *
++ * 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 <linux/clk.h>
++#include <linux/interrupt.h>
++#include <linux/io.h>
++#include <linux/module.h>
++#include <linux/nvmem-consumer.h>
++#include <linux/of_device.h>
++#include <linux/platform_device.h>
++#include <linux/reset.h>
++#include <linux/slab.h>
++#include <linux/thermal.h>
++#include <linux/printk.h>
++
++#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 <megous@megous.com>");
++MODULE_DESCRIPTION("Thermal sensor driver for new Allwinner SoCs");
++MODULE_LICENSE("GPL v2");