#include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/module.h>
+#include <linux/nvmem-consumer.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
  * struct rockchip_tsadc_chip - hold the private data of tsadc chip
  * @chn_offset: the channel offset of the first channel
  * @chn_num: the channel number of tsadc chip
- * @tshut_temp: the hardware-controlled shutdown temperature value
+ * @trim_slope: used to convert the trim code to a temperature in millicelsius
+ * @tshut_temp: the hardware-controlled shutdown temperature value, with no trim
  * @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO)
  * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
  * @initialize: SoC special initialize tsadc controller method
  * @irq_ack: clear the interrupt
  * @control: enable/disable method for the tsadc controller
- * @get_temp: get the temperature
+ * @get_temp: get the raw temperature, unadjusted by trim
  * @set_alarm_temp: set the high temperature interrupt
  * @set_tshut_temp: set the hardware-controlled shutdown temperature
  * @set_tshut_mode: set the hardware-controlled shutdown mode
+ * @get_trim_code: convert a hardware temperature code to one adjusted for by trim
  * @table: the chip-specific conversion table
  */
 struct rockchip_tsadc_chip {
        int chn_offset;
        int chn_num;
 
+       /* Used to convert trim code to trim temp */
+       int trim_slope;
+
        /* The hardware-controlled tshut property */
        int tshut_temp;
        enum tshut_mode tshut_mode;
        int (*set_tshut_temp)(const struct chip_tsadc_table *table,
                              int chn, void __iomem *reg, int temp);
        void (*set_tshut_mode)(int chn, void __iomem *reg, enum tshut_mode m);
+       int (*get_trim_code)(const struct chip_tsadc_table *table,
+                            int code, int trim_base, int trim_base_frac);
 
        /* Per-table methods */
        struct chip_tsadc_table table;
  * struct rockchip_thermal_sensor - hold the information of thermal sensor
  * @thermal:  pointer to the platform/configuration data
  * @tzd: pointer to a thermal zone
+ * @of_node: pointer to the device_node representing this sensor, if any
  * @id: identifier of the thermal sensor
+ * @trim_temp: per-sensor trim temperature value
  */
 struct rockchip_thermal_sensor {
        struct rockchip_thermal_data *thermal;
        struct thermal_zone_device *tzd;
+       struct device_node *of_node;
        int id;
+       int trim_temp;
 };
 
 /**
  * @pclk: the advanced peripherals bus clock
  * @grf: the general register file will be used to do static set by software
  * @regs: the base address of tsadc controller
- * @tshut_temp: the hardware-controlled shutdown temperature value
+ * @trim_base: major component of sensor trim value, in Celsius
+ * @trim_base_frac: minor component of sensor trim value, in Decicelsius
+ * @trim: fallback thermal trim value for each channel
+ * @tshut_temp: the hardware-controlled shutdown temperature value, with no trim
+ * @trim_temp: the fallback trim temperature for the whole sensor
  * @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO)
  * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
  */
        struct regmap *grf;
        void __iomem *regs;
 
+       int trim_base;
+       int trim_base_frac;
+       int trim;
+
        int tshut_temp;
+       int trim_temp;
        enum tshut_mode tshut_mode;
        enum tshut_polarity tshut_polarity;
 };
 
 #define GRF_CON_TSADC_CH_INV                   (0x10001 << 1)
 
+
+#define RK_MAX_TEMP                            (180000)
+
 /**
  * struct tsadc_table - code to temperature conversion table
  * @code: the value of adc channel
        writel_relaxed(val_cru, regs + TSADCV3_HSHUT_CRU_INT_EN);
 }
 
+static int rk_tsadcv2_get_trim_code(const struct chip_tsadc_table *table,
+                                   int code, int trim_base, int trim_base_frac)
+{
+       int temp = trim_base * 1000 + trim_base_frac * 100;
+       u32 base_code = rk_tsadcv2_temp_to_code(table, temp);
+
+       return code - base_code;
+}
+
 static const struct rockchip_tsadc_chip px30_tsadc_data = {
        /* cpu, gpu */
        .chn_offset = 0,
        .set_alarm_temp = rk_tsadcv3_alarm_temp,
        .set_tshut_temp = rk_tsadcv3_tshut_temp,
        .set_tshut_mode = rk_tsadcv4_tshut_mode,
+       .get_trim_code = rk_tsadcv2_get_trim_code,
+       .trim_slope = 923,
        .table = {
                .id = rk3588_code_table,
                .length = ARRAY_SIZE(rk3588_code_table),
                __func__, sensor->id, low, high);
 
        return tsadc->set_alarm_temp(&tsadc->table,
-                                    sensor->id, thermal->regs, high);
+                                    sensor->id, thermal->regs, high + sensor->trim_temp);
 }
 
 static int rockchip_thermal_get_temp(struct thermal_zone_device *tz, int *out_temp)
 
        retval = tsadc->get_temp(&tsadc->table,
                                 sensor->id, thermal->regs, out_temp);
+       *out_temp -= sensor->trim_temp;
+
        return retval;
 }
 
        .set_trips = rockchip_thermal_set_trips,
 };
 
+/**
+ * rockchip_get_efuse_value - read an OTP cell from a device node
+ * @np: pointer to the device node with the nvmem-cells property
+ * @cell_name: name of cell that should be read
+ * @value: pointer to where the read value will be placed
+ *
+ * Return: Negative errno on failure, during which *value will not be touched,
+ * or 0 on success.
+ */
+static int rockchip_get_efuse_value(struct device_node *np, const char *cell_name,
+                                   int *value)
+{
+       struct nvmem_cell *cell;
+       int ret = 0;
+       size_t len;
+       u8 *buf;
+       int i;
+
+       cell = of_nvmem_cell_get(np, cell_name);
+       if (IS_ERR(cell))
+               return PTR_ERR(cell);
+
+       buf = nvmem_cell_read(cell, &len);
+
+       nvmem_cell_put(cell);
+
+       if (IS_ERR(buf))
+               return PTR_ERR(buf);
+
+       if (len > sizeof(*value)) {
+               ret = -ERANGE;
+               goto exit;
+       }
+
+       /* Copy with implicit endian conversion */
+       *value = 0;
+       for (i = 0; i < len; i++)
+               *value |= (int) buf[i] << (8 * i);
+
+exit:
+       kfree(buf);
+       return ret;
+}
+
+static int rockchip_get_trim_configuration(struct device *dev, struct device_node *np,
+                                          struct rockchip_thermal_data *thermal)
+{
+       const struct rockchip_tsadc_chip *tsadc = thermal->chip;
+       int trim_base = 0, trim_base_frac = 0, trim = 0;
+       int trim_code;
+       int ret;
+
+       thermal->trim_base = 0;
+       thermal->trim_base_frac = 0;
+       thermal->trim = 0;
+
+       if (!tsadc->get_trim_code)
+               return 0;
+
+       ret = rockchip_get_efuse_value(np, "trim_base", &trim_base);
+       if (ret < 0) {
+               if (ret == -ENOENT) {
+                       trim_base = 30;
+                       dev_dbg(dev, "trim_base is absent, defaulting to 30\n");
+               } else {
+                       dev_err(dev, "failed reading nvmem value of trim_base: %pe\n",
+                               ERR_PTR(ret));
+                       return ret;
+               }
+       }
+       ret = rockchip_get_efuse_value(np, "trim_base_frac", &trim_base_frac);
+       if (ret < 0) {
+               if (ret == -ENOENT) {
+                       dev_dbg(dev, "trim_base_frac is absent, defaulting to 0\n");
+               } else {
+                       dev_err(dev, "failed reading nvmem value of trim_base_frac: %pe\n",
+                               ERR_PTR(ret));
+                       return ret;
+               }
+       }
+       thermal->trim_base = trim_base;
+       thermal->trim_base_frac = trim_base_frac;
+
+       /*
+        * If the tsadc node contains the trim property, then it is used in the
+        * absence of per-channel trim values
+        */
+       if (!rockchip_get_efuse_value(np, "trim", &trim))
+               thermal->trim = trim;
+       if (trim) {
+               trim_code = tsadc->get_trim_code(&tsadc->table, trim,
+                                                trim_base, trim_base_frac);
+               thermal->trim_temp = thermal->chip->trim_slope * trim_code;
+       }
+
+       return 0;
+}
+
 static int rockchip_configure_from_dt(struct device *dev,
                                      struct device_node *np,
                                      struct rockchip_thermal_data *thermal)
        if (IS_ERR(thermal->grf))
                dev_warn(dev, "Missing rockchip,grf property\n");
 
+       rockchip_get_trim_configuration(dev, np, thermal);
+
        return 0;
 }
 
                                 int id)
 {
        const struct rockchip_tsadc_chip *tsadc = thermal->chip;
+       struct device *dev = &pdev->dev;
+       int trim = thermal->trim;
+       int trim_code, tshut_temp;
+       int trim_temp = 0;
        int error;
 
+       if (thermal->trim_temp)
+               trim_temp = thermal->trim_temp;
+
+       if (tsadc->get_trim_code && sensor->of_node) {
+               error = rockchip_get_efuse_value(sensor->of_node, "trim", &trim);
+               if (error < 0 && error != -ENOENT) {
+                       dev_err(dev, "failed reading trim of sensor %d: %pe\n",
+                               id, ERR_PTR(error));
+                       return error;
+               }
+               if (trim) {
+                       trim_code = tsadc->get_trim_code(&tsadc->table, trim,
+                                                        thermal->trim_base,
+                                                        thermal->trim_base_frac);
+                       trim_temp = thermal->chip->trim_slope * trim_code;
+               }
+       }
+
+       sensor->trim_temp = trim_temp;
+
+       dev_dbg(dev, "trim of sensor %d is %d\n", id, sensor->trim_temp);
+
+       tshut_temp = min(thermal->tshut_temp + sensor->trim_temp, RK_MAX_TEMP);
+
        tsadc->set_tshut_mode(id, thermal->regs, thermal->tshut_mode);
 
-       error = tsadc->set_tshut_temp(&tsadc->table, id, thermal->regs,
-                             thermal->tshut_temp);
+       error = tsadc->set_tshut_temp(&tsadc->table, id, thermal->regs, tshut_temp);
        if (error)
-               dev_err(&pdev->dev, "%s: invalid tshut=%d, error=%d\n",
-                       __func__, thermal->tshut_temp, error);
+               dev_err(dev, "%s: invalid tshut=%d, error=%d\n",
+                       __func__, tshut_temp, error);
 
        sensor->thermal = thermal;
        sensor->id = id;
-       sensor->tzd = devm_thermal_of_zone_register(&pdev->dev, id, sensor,
+       sensor->tzd = devm_thermal_of_zone_register(dev, id, sensor,
                                                    &rockchip_of_thermal_ops);
        if (IS_ERR(sensor->tzd)) {
                error = PTR_ERR(sensor->tzd);
-               dev_err(&pdev->dev, "failed to register sensor %d: %d\n",
+               dev_err(dev, "failed to register sensor %d: %d\n",
                        id, error);
                return error;
        }
 {
        struct device_node *np = pdev->dev.of_node;
        struct rockchip_thermal_data *thermal;
+       struct device_node *child;
        int irq;
        int i;
        int error;
+       u32 chn;
 
        irq = platform_get_irq(pdev, 0);
        if (irq < 0)
        thermal->chip->initialize(thermal->grf, thermal->regs,
                                  thermal->tshut_polarity);
 
+       for_each_available_child_of_node(np, child) {
+               if (!of_property_read_u32(child, "reg", &chn)) {
+                       if (chn < thermal->chip->chn_num)
+                               thermal->sensors[chn].of_node = child;
+                       else
+                               dev_warn(&pdev->dev,
+                                        "sensor address (%d) too large, ignoring its trim\n",
+                                        chn);
+               }
+
+       }
+
        for (i = 0; i < thermal->chip->chn_num; i++) {
                error = rockchip_thermal_register_sensor(pdev, thermal,
                                                &thermal->sensors[i],
 static int __maybe_unused rockchip_thermal_resume(struct device *dev)
 {
        struct rockchip_thermal_data *thermal = dev_get_drvdata(dev);
-       int i;
+       const struct rockchip_tsadc_chip *tsadc = thermal->chip;
+       struct rockchip_thermal_sensor *sensor;
+       int tshut_temp;
        int error;
+       int i;
 
        error = clk_enable(thermal->clk);
        if (error)
 
        rockchip_thermal_reset_controller(thermal->reset);
 
-       thermal->chip->initialize(thermal->grf, thermal->regs,
-                                 thermal->tshut_polarity);
+       tsadc->initialize(thermal->grf, thermal->regs, thermal->tshut_polarity);
 
        for (i = 0; i < thermal->chip->chn_num; i++) {
-               int id = thermal->sensors[i].id;
+               sensor = &thermal->sensors[i];
+
+               tshut_temp = min(thermal->tshut_temp + sensor->trim_temp,
+                                RK_MAX_TEMP);
 
-               thermal->chip->set_tshut_mode(id, thermal->regs,
+               tsadc->set_tshut_mode(sensor->id, thermal->regs,
                                              thermal->tshut_mode);
 
-               error = thermal->chip->set_tshut_temp(&thermal->chip->table,
-                                             id, thermal->regs,
-                                             thermal->tshut_temp);
+               error = tsadc->set_tshut_temp(&thermal->chip->table,
+                                             sensor->id, thermal->regs,
+                                             tshut_temp);
                if (error)
                        dev_err(dev, "%s: invalid tshut=%d, error=%d\n",
-                               __func__, thermal->tshut_temp, error);
+                               __func__, tshut_temp, error);
        }
 
        thermal->chip->control(thermal->regs, true);