diff mbox series

[5/6] drivers/thermal/exynos: add initial Exynos 850 support

Message ID 20240719120853.1924771-6-m.majewski2@samsung.com
State New
Headers show
Series Add initial Exynos 850 support to the thermal driver | expand

Commit Message

Mateusz Majewski July 19, 2024, 12:08 p.m. UTC
This is loosely adapted from an implementation available at
https://gitlab.com/Linaro/96boards/e850-96/kernel/-/blob/android-exynos-4.14-linaro/drivers/thermal/samsung/exynos_tmu.c
Some differences from that implementation:
- unlike that implementation, we do not use the ACPM mechanism, instead
  we just access the registers, like we do for other SoCs,
- the SoC is supposed to support multiple sensors inside one unit. The
  vendor implementation uses one kernel device per sensor, we would
  probably prefer to have one device for all sensors, have
  #thermal-sensor-cells = <1> and so on. We implemented this, but we
  could not get the extra sensors to work on our hardware so far. This
  might be due to a misconfiguration and we will probably come back to
  this, however our implementation only supports a single sensor for
  now,
- the vendor implementation supports disabling CPU cores as a cooling
  device. We did not attempt to port this, and this would not really fit
  this driver anyway.

Additionally, some differences from the other SoCs supported by this
driver:
- this SoC does not require a clock to work correctly, so we need an
  exception for data->clk,
- we do not really constrain the e-fuse information like the other SoCs
  do (data->{min,max}_efuse_value). In our tests, those values (as well
  as the raw sensor values) were much higher than in the other SoCs, to
  the degree that reusing the data->{min,max}_efuse_value from the other
  SoCs would cause instant critical temperature reset on boot,
- this SoC provides more information in the e-fuse data than other SoCs,
  so we read some values inside exynos850_tmu_initialize instead of
  hardcoding them in exynos_map_dt_data.

Signed-off-by: Mateusz Majewski <m.majewski2@samsung.com>
---
 drivers/thermal/samsung/exynos_tmu.c | 214 +++++++++++++++++++++++++--
 1 file changed, 202 insertions(+), 12 deletions(-)

Comments

Sam Protsenko July 23, 2024, 12:02 a.m. UTC | #1
On Fri, Jul 19, 2024 at 7:10 AM Mateusz Majewski
<m.majewski2@samsung.com> wrote:
>
> This is loosely adapted from an implementation available at
> https://gitlab.com/Linaro/96boards/e850-96/kernel/-/blob/android-exynos-4.14-linaro/drivers/thermal/samsung/exynos_tmu.c

Not sure if it's going to be helpful to you, but we also uploaded the
downstream k5.10 a while back, and it features a bit different TMU
driver implementation [1].

[1] https://gitlab.com/Linaro/96boards/e850-96/kernel/-/tree/android-exynos-5.10-linaro/drivers/thermal/samsung?ref_type=heads

> Some differences from that implementation:
> - unlike that implementation, we do not use the ACPM mechanism, instead
>   we just access the registers, like we do for other SoCs,

Do you know what are the possible implications of not using ACPM? As I
understand, ACPM is a Samsung's downstream framework which uses APM
(Active Power Management) IP block internally to act as an IPC
mechanism, which makes it possible to offload any PM related
operations (which might get quite heavy, if we are to belive the TRM
description of APM) from CPU to APM. I'm not against the direct
registers access based implementation (in fact, I'm not sure how that
APM/ACPM thing can be implemented in upstreamable way and if it's
worth it at all). Just curious if we understand what we are
potentially missing out, and if at some point we'll be forced to
implement that ACPM thing anyway (for something else)?

> - the SoC is supposed to support multiple sensors inside one unit. The
>   vendor implementation uses one kernel device per sensor, we would
>   probably prefer to have one device for all sensors, have
>   #thermal-sensor-cells = <1> and so on. We implemented this, but we
>   could not get the extra sensors to work on our hardware so far. This
>   might be due to a misconfiguration and we will probably come back to
>   this, however our implementation only supports a single sensor for
>   now,
> - the vendor implementation supports disabling CPU cores as a cooling
>   device. We did not attempt to port this, and this would not really fit
>   this driver anyway.
>
> Additionally, some differences from the other SoCs supported by this
> driver:
> - this SoC does not require a clock to work correctly, so we need an
>   exception for data->clk,

Not sure if that's true, as already discussed in my comments for the
previous patches. Looks like one clock is still needed, which is the
PCLK bus clock (to interface registers) which might simultaneously act
as an operating (functional) clock.

> - we do not really constrain the e-fuse information like the other SoCs
>   do (data->{min,max}_efuse_value). In our tests, those values (as well
>   as the raw sensor values) were much higher than in the other SoCs, to
>   the degree that reusing the data->{min,max}_efuse_value from the other
>   SoCs would cause instant critical temperature reset on boot,
> - this SoC provides more information in the e-fuse data than other SoCs,
>   so we read some values inside exynos850_tmu_initialize instead of
>   hardcoding them in exynos_map_dt_data.
>
> Signed-off-by: Mateusz Majewski <m.majewski2@samsung.com>
> ---
>  drivers/thermal/samsung/exynos_tmu.c | 214 +++++++++++++++++++++++++--
>  1 file changed, 202 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c
> index f0de72a62fd7..bd52663f1a5a 100644
> --- a/drivers/thermal/samsung/exynos_tmu.c
> +++ b/drivers/thermal/samsung/exynos_tmu.c
> @@ -116,6 +116,43 @@
>  #define EXYNOS7_EMUL_DATA_SHIFT                        7
>  #define EXYNOS7_EMUL_DATA_MASK                 0x1ff
>
> +/* Exynos850 specific registers */
> +#define EXYNOS850_TMU_REG_AVG_CON              0x58

Exynos850 TRM says AVG_CONTROL offset is 0x38, and 0x58 is actually
for THRESHOLD0_TEMP_RISE3_2 register.

> +#define EXYNOS850_TMU_REG_CONTROL1             0x24
> +#define EXYNOS850_TMU_REG_COUNTER_VALUE0       0x30
> +#define EXYNOS850_TMU_REG_COUNTER_VALUE1       0x34
> +#define EXYNOS850_TMU_REG_CURRENT_TEMP1_0      0x40

In TRM, this register is called CURRENT_TEMP0_1. Maybe change 1_0 -> 0_1?

> +#define EXYNOS850_TMU_REG_THD_TEMP0_RISE       0x50
> +#define EXYNOS850_TMU_REG_THD_TEMP0_FALL       0x60
> +#define EXYNOS850_TMU_REG_TRIM0                        0x3C
> +
> +#define EXYNOS850_TMU_AVG_CON_SHIFT            18

Maybe rename it to something like EXYNOS850_TMU_T_AVG_MODE_SHIFT, to
avoid confusion with AVG_CONTROL register? That belongs to TRIMINFO2
register, if I understand it correctly, not to AVG_CONTROL.

> +#define EXYNOS850_TMU_AVG_MODE_MASK            0x7

I'd suggest to group all the definitions here as such:

#define REG1_OFFSET
#define REG1_FIELD1_OFFSET
#define REG1_FIELD2_OFFSET
...empty line...
#define REG2_OFFSET
#define REG2_FIELD1_OFFSET
#define REG2_FIELD2_OFFSET
...etc...

Or otherwise each shift/mask constant should contain its register name
as a prefix, to avoid confusion. But right now it's kinda hard to
understand what belongs to what :) But that's just a nitpick.

> +#define EXYNOS850_TMU_BGRI_TRIM_MASK           0xF

Suggest using GENMASK() macro whenever possible.

> +#define EXYNOS850_TMU_BGRI_TRIM_SHIFT          20
> +#define EXYNOS850_TMU_CLK_SENSE_ON_MASK                0xffff
> +#define EXYNOS850_TMU_CLK_SENSE_ON_SHIFT       16
> +#define EXYNOS850_TMU_DEM_ENABLE               1
> +#define EXYNOS850_TMU_DEM_SHIFT                        4

Instead of above two values, it could be just BIT(4) for
EXYNOS850_TMU_DEM_ENABLE?

> +#define EXYNOS850_TMU_EN_TEMP_SEN_OFF_MASK     0xffff
> +#define EXYNOS850_TMU_EN_TEMP_SEN_OFF_SHIFT    0
> +#define EXYNOS850_TMU_LPI_MODE_MASK            1
> +#define EXYNOS850_TMU_LPI_MODE_SHIFT           10
> +#define EXYNOS850_TMU_T_BUF_SLOPE_SEL_MASK     0xF
> +#define EXYNOS850_TMU_T_BUF_SLOPE_SEL_SHIFT    18
> +#define EXYNOS850_TMU_T_BUF_VREF_SEL_MASK      0x1F
> +#define EXYNOS850_TMU_T_BUF_VREF_SEL_SHIFT     18
> +#define EXYNOS850_TMU_TEM1051X_SENSE_VALUE     0x028A
> +#define EXYNOS850_TMU_TEM1456X_SENSE_VALUE     0x0A28

I'd pull those two values under shift/mask definitions. Also, please
use lowercase characters for hex values, here and in all other places.

> +#define EXYNOS850_TMU_TEMP_SHIFT               9
> +#define EXYNOS850_TMU_TRIMINFO_SHIFT           4
> +#define EXYNOS850_TMU_T_TRIM0_MASK             0xF
> +#define EXYNOS850_TMU_T_TRIM0_SHIFT            18
> +#define EXYNOS850_TMU_VBEI_TRIM_MASK           0xF
> +#define EXYNOS850_TMU_VBEI_TRIM_SHIFT          8
> +#define EXYNOS850_TMU_VREF_TRIM_MASK           0xF
> +#define EXYNOS850_TMU_VREF_TRIM_SHIFT          12
> +
>  #define EXYNOS_FIRST_POINT_TRIM                        25
>  #define EXYNOS_SECOND_POINT_TRIM               85
>
> @@ -133,6 +170,7 @@ enum soc_type {
>         SOC_ARCH_EXYNOS5420_TRIMINFO,
>         SOC_ARCH_EXYNOS5433,
>         SOC_ARCH_EXYNOS7,
> +       SOC_ARCH_EXYNOS850,
>  };
>
>  /**
> @@ -231,13 +269,16 @@ static int code_to_temp(struct exynos_tmu_data *data, u16 temp_code)
>
>  static void sanitize_temp_error(struct exynos_tmu_data *data, u32 trim_info)
>  {
> -       u16 tmu_temp_mask =
> -               (data->soc == SOC_ARCH_EXYNOS7) ? EXYNOS7_TMU_TEMP_MASK
> -                                               : EXYNOS_TMU_TEMP_MASK;
> +       u16 tmu_temp_mask = (data->soc == SOC_ARCH_EXYNOS7 ||
> +                            data->soc == SOC_ARCH_EXYNOS850) ?
> +                                   EXYNOS7_TMU_TEMP_MASK :
> +                                   EXYNOS_TMU_TEMP_MASK;
> +       int tmu_85_shift = (data->soc == SOC_ARCH_EXYNOS850) ?
> +                                  EXYNOS850_TMU_TEMP_SHIFT :
> +                                  EXYNOS_TRIMINFO_85_SHIFT;

Something seems off to me here. How come the shift value for EXYNOS7
case is 8, but the mask is actually 9 bits long? Does it mean the
first error field is 8 bits long, and the second error field is 9 bits
long for EXYNOS7? I don't have the Exynos7 manual, so it's just a
hunch. But if it's true, maybe this shift value has to be added in
your [PATCH 2/6] to fix Exynos7 case?

Also, just an idea: those values (and other similar values) could be
pre-calculated somewhere during the probe, stored in some struct (e.g.
_variant or _chip) and then just used here. Stylistically, instead of
the ternary operator, maybe switch one would easier to read? Again,
those are very minor nitpicks.

>
>         data->temp_error1 = trim_info & tmu_temp_mask;
> -       data->temp_error2 = ((trim_info >> EXYNOS_TRIMINFO_85_SHIFT) &
> -                               tmu_temp_mask);
> +       data->temp_error2 = ((trim_info >> tmu_85_shift) & tmu_temp_mask);
>

No need for the left-most and right-most brackets.

>         if (!data->temp_error1 ||
>             (data->min_efuse_value > data->temp_error1) ||
> @@ -245,9 +286,8 @@ static void sanitize_temp_error(struct exynos_tmu_data *data, u32 trim_info)
>                 data->temp_error1 = data->efuse_value & tmu_temp_mask;
>
>         if (!data->temp_error2)
> -               data->temp_error2 =
> -                       (data->efuse_value >> EXYNOS_TRIMINFO_85_SHIFT) &
> -                       tmu_temp_mask;
> +               data->temp_error2 = (data->efuse_value >> tmu_85_shift) &
> +                                   tmu_temp_mask;
>  }
>
>  static int exynos_tmu_initialize(struct platform_device *pdev)
> @@ -588,6 +628,129 @@ static void exynos7_tmu_initialize(struct platform_device *pdev)
>         sanitize_temp_error(data, trim_info);
>  }
>
> +static void exynos850_tmu_set_low_temp(struct exynos_tmu_data *data, u8 temp)
> +{
> +       exynos_tmu_update_temp(data, EXYNOS850_TMU_REG_THD_TEMP0_FALL + 12, 0,
> +                              temp);
> +       exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
> +                             EXYNOS_TMU_INTEN_FALL0_SHIFT + 0, true);
> +}
> +
> +static void exynos850_tmu_set_high_temp(struct exynos_tmu_data *data, u8 temp)
> +{
> +       exynos_tmu_update_temp(data, EXYNOS850_TMU_REG_THD_TEMP0_RISE + 12, 16,
> +                              temp);
> +       exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
> +                             EXYNOS7_TMU_INTEN_RISE0_SHIFT + 1, true);
> +}
> +
> +static void exynos850_tmu_disable_low(struct exynos_tmu_data *data)
> +{
> +       exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
> +                             EXYNOS_TMU_INTEN_FALL0_SHIFT + 0, false);
> +}
> +
> +static void exynos850_tmu_disable_high(struct exynos_tmu_data *data)
> +{
> +       exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
> +                             EXYNOS7_TMU_INTEN_RISE0_SHIFT + 1, false);
> +}
> +
> +static void exynos850_tmu_set_crit_temp(struct exynos_tmu_data *data, u8 temp)
> +{
> +       exynos_tmu_update_temp(data, EXYNOS850_TMU_REG_THD_TEMP0_RISE + 0, 16,
> +                              temp);
> +       exynos_tmu_update_bit(data, EXYNOS_TMU_REG_CONTROL,
> +                             EXYNOS_TMU_THERM_TRIP_EN_SHIFT, true);
> +       exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
> +                             EXYNOS7_TMU_INTEN_RISE0_SHIFT + 7, true);
> +}
> +
> +static void exynos850_tmu_initialize(struct platform_device *pdev)
> +{
> +       struct exynos_tmu_data *data = platform_get_drvdata(pdev);
> +       int cal_type;

Please make it u32.

> +       unsigned int avg_mode, buf, bgri, vref, vbei;

Suggest renaming buf -> reg, and maybe make it u32.

> +
> +       buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
> +       cal_type = (buf & EXYNOS5433_TRIMINFO_CALIB_SEL_MASK) >>
> +                  EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT;
> +       data->reference_voltage = (buf >> EXYNOS850_TMU_T_BUF_VREF_SEL_SHIFT) &
> +                                 EXYNOS850_TMU_T_BUF_VREF_SEL_MASK;
> +       buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO +
> +                   EXYNOS850_TMU_TRIMINFO_SHIFT);
> +       data->gain = (buf >> EXYNOS850_TMU_T_BUF_SLOPE_SEL_SHIFT) &
> +                    EXYNOS850_TMU_T_BUF_SLOPE_SEL_MASK;
> +       buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO +
> +                   2 * EXYNOS850_TMU_TRIMINFO_SHIFT);
> +       avg_mode = (buf >> EXYNOS850_TMU_AVG_CON_SHIFT) &
> +                  EXYNOS850_TMU_AVG_MODE_MASK;
> +       buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO +
> +                   3 * EXYNOS850_TMU_TRIMINFO_SHIFT);
> +       bgri = (buf >> EXYNOS850_TMU_T_TRIM0_SHIFT) &
> +              EXYNOS850_TMU_T_TRIM0_MASK;
> +       buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO +
> +                   4 * EXYNOS850_TMU_TRIMINFO_SHIFT);
> +       vref = (buf >> EXYNOS850_TMU_T_TRIM0_SHIFT) &
> +              EXYNOS850_TMU_T_TRIM0_MASK;
> +       buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO +
> +                   5 * EXYNOS850_TMU_TRIMINFO_SHIFT);

For cases like that, maybe introduce some macro like:

    #define EXYNOS850_TRIMINFO_OFFSET(n)    (EXYNOS_TMU_REG_TRIMINFO +
(n) * EXYNOS850_TMU_TRIMINFO_SHIFT)

and use it everywhere?

> +       vbei = (buf >> EXYNOS850_TMU_T_TRIM0_SHIFT) &
> +              EXYNOS850_TMU_T_TRIM0_MASK;
> +
> +       buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
> +       sanitize_temp_error(data, buf);
> +
> +       switch (cal_type) {
> +       case EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING:
> +               data->cal_type = TYPE_TWO_POINT_TRIMMING;
> +               break;
> +       case EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING:

Add "fallthrough;" here? Or maybe just remove above line at all?

> +       default:
> +               data->cal_type = TYPE_ONE_POINT_TRIMMING;
> +               break;
> +       }
> +
> +       dev_info(&pdev->dev, "Calibration type is %d-point calibration\n",
> +                cal_type ? 2 : 1);
> +
> +       buf = readl(data->base + EXYNOS850_TMU_REG_AVG_CON);
> +       buf &= ~(EXYNOS850_TMU_AVG_MODE_MASK);

No need for brackets.

> +       buf &= ~(EXYNOS850_TMU_DEM_ENABLE << EXYNOS850_TMU_DEM_SHIFT);
> +       if (avg_mode) {
> +               buf |= avg_mode;
> +               buf |= (EXYNOS850_TMU_DEM_ENABLE << EXYNOS850_TMU_DEM_SHIFT);
> +       }
> +       writel(buf, data->base + EXYNOS850_TMU_REG_AVG_CON);
> +
> +       buf = readl(data->base + EXYNOS850_TMU_REG_COUNTER_VALUE0);
> +       buf &= ~(EXYNOS850_TMU_EN_TEMP_SEN_OFF_MASK
> +                << EXYNOS850_TMU_EN_TEMP_SEN_OFF_SHIFT);
> +       buf |= EXYNOS850_TMU_TEM1051X_SENSE_VALUE
> +              << EXYNOS850_TMU_EN_TEMP_SEN_OFF_SHIFT;
> +       writel(buf, data->base + EXYNOS850_TMU_REG_COUNTER_VALUE0);
> +
> +       buf = readl(data->base + EXYNOS850_TMU_REG_COUNTER_VALUE1);
> +       buf &= ~(EXYNOS850_TMU_CLK_SENSE_ON_MASK
> +                << EXYNOS850_TMU_CLK_SENSE_ON_SHIFT);
> +       buf |= EXYNOS850_TMU_TEM1051X_SENSE_VALUE
> +              << EXYNOS850_TMU_CLK_SENSE_ON_SHIFT;
> +       writel(buf, data->base + EXYNOS850_TMU_REG_COUNTER_VALUE1);
> +
> +       buf = readl(data->base + EXYNOS850_TMU_REG_TRIM0);
> +       buf &= ~(EXYNOS850_TMU_BGRI_TRIM_MASK << EXYNOS850_TMU_BGRI_TRIM_SHIFT);
> +       buf &= ~(EXYNOS850_TMU_VREF_TRIM_MASK << EXYNOS850_TMU_VREF_TRIM_SHIFT);
> +       buf &= ~(EXYNOS850_TMU_VBEI_TRIM_MASK << EXYNOS850_TMU_VBEI_TRIM_SHIFT);

Why not define this mask value like this instead:

    #define EXYNOS850_TMU_VBEI_TRIM_MASK        GENMASK(11,8)

And then you'll be able to do just:

    buf &= ~EXYNOS850_TMU_VBEI_TRIM_MASK;

The same goes for all similar cases.

> +       buf |= (bgri << EXYNOS850_TMU_BGRI_TRIM_SHIFT);
> +       buf |= (vref << EXYNOS850_TMU_VREF_TRIM_SHIFT);
> +       buf |= (vbei << EXYNOS850_TMU_VBEI_TRIM_SHIFT);

Brackets are not needed.

> +       writel(buf, data->base + EXYNOS850_TMU_REG_TRIM0);
> +
> +       buf = readl(data->base + EXYNOS850_TMU_REG_CONTROL1);
> +       buf &= ~(EXYNOS850_TMU_LPI_MODE_MASK << EXYNOS850_TMU_LPI_MODE_SHIFT);
> +       writel(buf, data->base + EXYNOS850_TMU_REG_CONTROL1);
> +}
> +
>  static void exynos4210_tmu_control(struct platform_device *pdev, bool on)
>  {
>         struct exynos_tmu_data *data = platform_get_drvdata(pdev);
> @@ -679,7 +842,8 @@ static u32 get_emul_con_reg(struct exynos_tmu_data *data, unsigned int val,
>
>                 val &= ~(EXYNOS_EMUL_TIME_MASK << EXYNOS_EMUL_TIME_SHIFT);
>                 val |= (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT);
> -               if (data->soc == SOC_ARCH_EXYNOS7) {
> +               if (data->soc == SOC_ARCH_EXYNOS7 ||
> +                   data->soc == SOC_ARCH_EXYNOS850) {
>                         val &= ~(EXYNOS7_EMUL_DATA_MASK <<
>                                 EXYNOS7_EMUL_DATA_SHIFT);
>                         val |= (temp_to_code(data, temp) <<
> @@ -709,7 +873,8 @@ static void exynos4412_tmu_set_emulation(struct exynos_tmu_data *data,
>                 emul_con = EXYNOS5260_EMUL_CON;
>         else if (data->soc == SOC_ARCH_EXYNOS5433)
>                 emul_con = EXYNOS5433_TMU_EMUL_CON;
> -       else if (data->soc == SOC_ARCH_EXYNOS7)
> +       else if (data->soc == SOC_ARCH_EXYNOS7 ||
> +                data->soc == SOC_ARCH_EXYNOS850)
>                 emul_con = EXYNOS7_TMU_REG_EMUL_CON;
>         else
>                 emul_con = EXYNOS_EMUL_CON;
> @@ -766,6 +931,12 @@ static int exynos7_tmu_read(struct exynos_tmu_data *data)
>                 EXYNOS7_TMU_TEMP_MASK;
>  }
>
> +static int exynos850_tmu_read(struct exynos_tmu_data *data)
> +{
> +       return readw(data->base + EXYNOS850_TMU_REG_CURRENT_TEMP1_0) &
> +              EXYNOS7_TMU_TEMP_MASK;
> +}
> +
>  static irqreturn_t exynos_tmu_threaded_irq(int irq, void *id)
>  {
>         struct exynos_tmu_data *data = id;
> @@ -794,7 +965,8 @@ static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data)
>         if (data->soc == SOC_ARCH_EXYNOS5260) {
>                 tmu_intstat = EXYNOS5260_TMU_REG_INTSTAT;
>                 tmu_intclear = EXYNOS5260_TMU_REG_INTCLEAR;
> -       } else if (data->soc == SOC_ARCH_EXYNOS7) {
> +       } else if (data->soc == SOC_ARCH_EXYNOS7 ||
> +                  data->soc == SOC_ARCH_EXYNOS850) {
>                 tmu_intstat = EXYNOS7_TMU_REG_INTPEND;
>                 tmu_intclear = EXYNOS7_TMU_REG_INTPEND;
>         } else if (data->soc == SOC_ARCH_EXYNOS5433) {
> @@ -845,6 +1017,9 @@ static const struct of_device_id exynos_tmu_match[] = {
>         }, {
>                 .compatible = "samsung,exynos7-tmu",
>                 .data = (const void *)SOC_ARCH_EXYNOS7,
> +       }, {
> +               .compatible = "samsung,exynos850-tmu",
> +               .data = (const void *)SOC_ARCH_EXYNOS850,
>         },
>         { },
>  };
> @@ -957,6 +1132,21 @@ static int exynos_map_dt_data(struct platform_device *pdev)
>                 data->min_efuse_value = 15;
>                 data->max_efuse_value = 100;
>                 break;
> +       case SOC_ARCH_EXYNOS850:
> +               data->tmu_set_low_temp = exynos850_tmu_set_low_temp;
> +               data->tmu_set_high_temp = exynos850_tmu_set_high_temp;
> +               data->tmu_disable_low = exynos850_tmu_disable_low;
> +               data->tmu_disable_high = exynos850_tmu_disable_high;
> +               data->tmu_set_crit_temp = exynos850_tmu_set_crit_temp;
> +               data->tmu_initialize = exynos850_tmu_initialize;
> +               data->tmu_control = exynos4210_tmu_control;
> +               data->tmu_read = exynos850_tmu_read;
> +               data->tmu_set_emulation = exynos4412_tmu_set_emulation;
> +               data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
> +               data->efuse_value = 55;
> +               data->min_efuse_value = 0;
> +               data->max_efuse_value = 511;
> +               break;
>         default:
>                 dev_err(&pdev->dev, "Platform not supported\n");
>                 return -EINVAL;
> @@ -1051,7 +1241,7 @@ static int exynos_tmu_probe(struct platform_device *pdev)
>                 return ret;
>
>         data->clk = devm_clk_get(dev, "tmu_apbif");
> -       if (IS_ERR(data->clk))
> +       if (IS_ERR(data->clk) && data->soc != SOC_ARCH_EXYNOS850)
>                 return dev_err_probe(dev, PTR_ERR(data->clk), "Failed to get clock\n");
>
>         data->clk_sec = devm_clk_get(dev, "tmu_triminfo_apbif");
> --
> 2.45.1
>
>
Mateusz Majewski July 23, 2024, 2:16 p.m. UTC | #2
> Do you know what are the possible implications of not using ACPM? As I
> understand, ACPM is a Samsung's downstream framework which uses APM
> (Active Power Management) IP block internally to act as an IPC
> mechanism, which makes it possible to offload any PM related
> operations (which might get quite heavy, if we are to belive the TRM
> description of APM) from CPU to APM. I'm not against the direct
> registers access based implementation (in fact, I'm not sure how that
> APM/ACPM thing can be implemented in upstreamable way and if it's
> worth it at all). Just curious if we understand what we are
> potentially missing out, and if at some point we'll be forced to
> implement that ACPM thing anyway (for something else)?

Not sure honestly. The downstream v4.10 driver does many operations on
registers anyway...?

> Not sure if that's true, as already discussed in my comments for the
> previous patches. Looks like one clock is still needed, which is the
> PCLK bus clock (to interface registers) which might simultaneously act
> as an operating (functional) clock.

The code seems to be working correctly without this clock, both register
reads and writes. Maybe the support for extra sensors, which I couldn't
get to work, would require this clock?

> Exynos850 TRM says AVG_CONTROL offset is 0x38, and 0x58 is actually
> for THRESHOLD0_TEMP_RISE3_2 register.

Thank you so much! Will fix in v2. Though writing to the right place
doesn't seem to change much in practice, probably just means that the
correct mode is being used.

> Something seems off to me here. How come the shift value for EXYNOS7
> case is 8, but the mask is actually 9 bits long? Does it mean the
> first error field is 8 bits long, and the second error field is 9 bits
> long for EXYNOS7? I don't have the Exynos7 manual, so it's just a
> hunch. But if it's true, maybe this shift value has to be added in
> your [PATCH 2/6] to fix Exynos7 case?

I did not really want to mess with Exynos7 code, as we don't have an
Exynos7 board sadly. Honestly I feel like I should drop the 2/6 patch
completely and only modify the code to run on 850 correctly.

> Also, just an idea: those values (and other similar values) could be
> pre-calculated somewhere during the probe, stored in some struct (e.g.
> _variant or _chip) and then just used here.

sanitize_temp_error is only called one per probe and once per resume, so
probably little to gain?

Will also do all other.
Sam Protsenko July 23, 2024, 3:23 p.m. UTC | #3
On Tue, Jul 23, 2024 at 9:17 AM Mateusz Majewski
<m.majewski2@samsung.com> wrote:
>
> > Do you know what are the possible implications of not using ACPM? As I
> > understand, ACPM is a Samsung's downstream framework which uses APM
> > (Active Power Management) IP block internally to act as an IPC
> > mechanism, which makes it possible to offload any PM related
> > operations (which might get quite heavy, if we are to belive the TRM
> > description of APM) from CPU to APM. I'm not against the direct
> > registers access based implementation (in fact, I'm not sure how that
> > APM/ACPM thing can be implemented in upstreamable way and if it's
> > worth it at all). Just curious if we understand what we are
> > potentially missing out, and if at some point we'll be forced to
> > implement that ACPM thing anyway (for something else)?
>
> Not sure honestly. The downstream v4.10 driver does many operations on
> registers anyway...?
>
> > Not sure if that's true, as already discussed in my comments for the
> > previous patches. Looks like one clock is still needed, which is the
> > PCLK bus clock (to interface registers) which might simultaneously act
> > as an operating (functional) clock.
>
> The code seems to be working correctly without this clock, both register
> reads and writes. Maybe the support for extra sensors, which I couldn't
> get to work, would require this clock?
>

Chances are that clock was enabled by the bootloader for us (or it's
just enabled by default) and it just keeps running. If that's so, I'd
say it must be described in dts and controlled by the driver. Because
otherwise it might get disabled at any point in future, e.g. kernel
may disable it during startup as an unused clock (when it's added to
the clock driver), etc. Let me enable that clock for you, and then you
can use /sys/kernel/debug/clk/ files to disable it manually and see if
it actually affects TMU driver.

> > Exynos850 TRM says AVG_CONTROL offset is 0x38, and 0x58 is actually
> > for THRESHOLD0_TEMP_RISE3_2 register.
>
> Thank you so much! Will fix in v2. Though writing to the right place
> doesn't seem to change much in practice, probably just means that the
> correct mode is being used.
>
> > Something seems off to me here. How come the shift value for EXYNOS7
> > case is 8, but the mask is actually 9 bits long? Does it mean the
> > first error field is 8 bits long, and the second error field is 9 bits
> > long for EXYNOS7? I don't have the Exynos7 manual, so it's just a
> > hunch. But if it's true, maybe this shift value has to be added in
> > your [PATCH 2/6] to fix Exynos7 case?
>
> I did not really want to mess with Exynos7 code, as we don't have an
> Exynos7 board sadly. Honestly I feel like I should drop the 2/6 patch
> completely and only modify the code to run on 850 correctly.
>

It feels like there is an error for Exynos7 case there. Take a look at
this commit:

    aef27b658b43 ("thermal: exynos: use sanitize_temp_error() in
exynos7_tmu_initialize()")

I think that commit just forgets to update the shift value for Exynos7
properly. This code:

    data->temp_error1 = trim_info & tmu_temp_mask;
    data->temp_error2 = ((trim_info >> EXYNOS_TRIMINFO_85_SHIFT) &
                EXYNOS_TMU_TEMP_MASK);

in case of Exynos7 becomes:

    data->temp_error1 = trim_info & 0x1ff;    // mask = 9 bits
    data->temp_error2 = (trim_info >> 8) & 0xff;

it contradicts itself, because it takes 9 rightmost bits for error1,
and then uses 1 of those bits for error2 too. It's obvious that if 9
bits are already used for error1, then for error2 it has to be shifted
by 9 bits, not 8.

That's why I think your patch 2/6 is legit and useful on its own, and
it's actually a good catch on your part! But the shift value has to be
fixed as well (for Exynos7). It's not ideal you don't have the
hardware to test it, but it just screams *bug* to me :) Also, maybe we
can ask someone who has Exynos7 hardware to test it for us?

> > Also, just an idea: those values (and other similar values) could be
> > pre-calculated somewhere during the probe, stored in some struct (e.g.
> > _variant or _chip) and then just used here.
>
> sanitize_temp_error is only called one per probe and once per resume, so
> probably little to gain?
>

Sure, it was just a minor suggestion to make the code look more linear
so to speak. It can be totally skipped.

> Will also do all other.
Sam Protsenko July 23, 2024, 4:47 p.m. UTC | #4
On Tue, Jul 23, 2024 at 10:23 AM Sam Protsenko
<semen.protsenko@linaro.org> wrote:
>
> On Tue, Jul 23, 2024 at 9:17 AM Mateusz Majewski
> <m.majewski2@samsung.com> wrote:
> >
> > > Do you know what are the possible implications of not using ACPM? As I
> > > understand, ACPM is a Samsung's downstream framework which uses APM
> > > (Active Power Management) IP block internally to act as an IPC
> > > mechanism, which makes it possible to offload any PM related
> > > operations (which might get quite heavy, if we are to belive the TRM
> > > description of APM) from CPU to APM. I'm not against the direct
> > > registers access based implementation (in fact, I'm not sure how that
> > > APM/ACPM thing can be implemented in upstreamable way and if it's
> > > worth it at all). Just curious if we understand what we are
> > > potentially missing out, and if at some point we'll be forced to
> > > implement that ACPM thing anyway (for something else)?
> >
> > Not sure honestly. The downstream v4.10 driver does many operations on
> > registers anyway...?
> >
> > > Not sure if that's true, as already discussed in my comments for the
> > > previous patches. Looks like one clock is still needed, which is the
> > > PCLK bus clock (to interface registers) which might simultaneously act
> > > as an operating (functional) clock.
> >
> > The code seems to be working correctly without this clock, both register
> > reads and writes. Maybe the support for extra sensors, which I couldn't
> > get to work, would require this clock?
> >
>
> Chances are that clock was enabled by the bootloader for us (or it's
> just enabled by default) and it just keeps running. If that's so, I'd
> say it must be described in dts and controlled by the driver. Because
> otherwise it might get disabled at any point in future, e.g. kernel
> may disable it during startup as an unused clock (when it's added to
> the clock driver), etc. Let me enable that clock for you, and then you
> can use /sys/kernel/debug/clk/ files to disable it manually and see if
> it actually affects TMU driver.
>

Yeah, that clock is definitely needed. Just submitted the series [1]
adding it, which makes the kernel stuck on startup when your series is
applied. To fix that I added next lines to the TMU node in dts:

8<--------------------------------------------------------------------------->8
     tmuctrl_0: tmu@10070000 {
         compatible = "samsung,exynos850-tmu";
         reg = <0x10070000 0x800>;
         interrupts = <GIC_SPI 226 IRQ_TYPE_LEVEL_HIGH>;
+        clock-names = "tmu_apbif";
+        clocks = <&cmu_peri CLK_GOUT_BUSIF_TMU_PCLK>;
         #thermal-sensor-cells = <0>;
    };
8<--------------------------------------------------------------------------->8

Please rework your patches to account for that required clock. Alas
the TMU dts changes can't be submitted until my series [1] is applied.
But you can still apply my series locally, and I think the driver and
bindings changes don't depend on that clock, so it should be ok to
send those

Thanks!

[1] https://lore.kernel.org/linux-samsung-soc/20240723163311.28654-2-semen.protsenko@linaro.org/T/#mf28e4aab0111b95479ef632bc1979dff93d28cc7

> > > Exynos850 TRM says AVG_CONTROL offset is 0x38, and 0x58 is actually
> > > for THRESHOLD0_TEMP_RISE3_2 register.
> >
> > Thank you so much! Will fix in v2. Though writing to the right place
> > doesn't seem to change much in practice, probably just means that the
> > correct mode is being used.
> >
> > > Something seems off to me here. How come the shift value for EXYNOS7
> > > case is 8, but the mask is actually 9 bits long? Does it mean the
> > > first error field is 8 bits long, and the second error field is 9 bits
> > > long for EXYNOS7? I don't have the Exynos7 manual, so it's just a
> > > hunch. But if it's true, maybe this shift value has to be added in
> > > your [PATCH 2/6] to fix Exynos7 case?
> >
> > I did not really want to mess with Exynos7 code, as we don't have an
> > Exynos7 board sadly. Honestly I feel like I should drop the 2/6 patch
> > completely and only modify the code to run on 850 correctly.
> >
>
> It feels like there is an error for Exynos7 case there. Take a look at
> this commit:
>
>     aef27b658b43 ("thermal: exynos: use sanitize_temp_error() in
> exynos7_tmu_initialize()")
>
> I think that commit just forgets to update the shift value for Exynos7
> properly. This code:
>
>     data->temp_error1 = trim_info & tmu_temp_mask;
>     data->temp_error2 = ((trim_info >> EXYNOS_TRIMINFO_85_SHIFT) &
>                 EXYNOS_TMU_TEMP_MASK);
>
> in case of Exynos7 becomes:
>
>     data->temp_error1 = trim_info & 0x1ff;    // mask = 9 bits
>     data->temp_error2 = (trim_info >> 8) & 0xff;
>
> it contradicts itself, because it takes 9 rightmost bits for error1,
> and then uses 1 of those bits for error2 too. It's obvious that if 9
> bits are already used for error1, then for error2 it has to be shifted
> by 9 bits, not 8.
>
> That's why I think your patch 2/6 is legit and useful on its own, and
> it's actually a good catch on your part! But the shift value has to be
> fixed as well (for Exynos7). It's not ideal you don't have the
> hardware to test it, but it just screams *bug* to me :) Also, maybe we
> can ask someone who has Exynos7 hardware to test it for us?
>
> > > Also, just an idea: those values (and other similar values) could be
> > > pre-calculated somewhere during the probe, stored in some struct (e.g.
> > > _variant or _chip) and then just used here.
> >
> > sanitize_temp_error is only called one per probe and once per resume, so
> > probably little to gain?
> >
>
> Sure, it was just a minor suggestion to make the code look more linear
> so to speak. It can be totally skipped.
>
> > Will also do all other.
Mateusz Majewski July 24, 2024, 3:30 p.m. UTC | #5
> I'd suggest to group all the definitions here as such:
> 
> #define REG1_OFFSET
> #define REG1_FIELD1_OFFSET
> #define REG1_FIELD2_OFFSET
> ...empty line...
> #define REG2_OFFSET
> #define REG2_FIELD1_OFFSET
> #define REG2_FIELD2_OFFSET
> ...etc...
> 
> Or otherwise each shift/mask constant should contain its register name
> as a prefix, to avoid confusion. But right now it's kinda hard to
> understand what belongs to what :) But that's just a nitpick.

I came up with this:

/* Exynos850 specific registers */
#define EXYNOS850_TMU_REG_CURRENT_TEMP0_1	0x40
#define EXYNOS850_TMU_REG_THD_TEMP0_RISE	0x50
#define EXYNOS850_TMU_REG_THD_TEMP0_FALL	0x60
#define EXYNOS850_TMU_TEMP_SHIFT		9

#define EXYNOS850_TMU_TRIMINFO_SHIFT		4
#define EXYNOS850_TMU_TRIMINFO_OFFSET(n) \
	(EXYNOS_TMU_REG_TRIMINFO + (n) * EXYNOS850_TMU_TRIMINFO_SHIFT)
#define EXYNOS850_TMU_T_TRIM0_SHIFT		18

#define EXYNOS850_TMU_REG_CONTROL1		0x24
#define EXYNOS850_TMU_LPI_MODE_MASK		1
#define EXYNOS850_TMU_LPI_MODE_SHIFT		10

#define EXYNOS850_TMU_REG_COUNTER_VALUE0	0x30
#define EXYNOS850_TMU_EN_TEMP_SEN_OFF_MASK	0xffff
#define EXYNOS850_TMU_EN_TEMP_SEN_OFF_SHIFT	0

#define EXYNOS850_TMU_REG_COUNTER_VALUE1	0x34
#define EXYNOS850_TMU_CLK_SENSE_ON_MASK		0xffff
#define EXYNOS850_TMU_CLK_SENSE_ON_SHIFT	16

#define EXYNOS850_TMU_REG_AVG_CON		0x38
#define EXYNOS850_TMU_AVG_MODE_MASK		0x7
#define EXYNOS850_TMU_DEM_ENABLE		BIT(4)

#define EXYNOS850_TMU_REG_TRIM0			0x3c
#define EXYNOS850_TMU_TRIM0_MASK		0xf
#define EXYNOS850_TMU_VBEI_TRIM_SHIFT		8
#define EXYNOS850_TMU_VREF_TRIM_SHIFT		12
#define EXYNOS850_TMU_BGRI_TRIM_SHIFT		20

#define EXYNOS850_TMU_TEM1051X_SENSE_VALUE	0x028a
#define EXYNOS850_TMU_TEM1456X_SENSE_VALUE	0x0a28

This also omits some definitions that were in v1, as they had the same
value and they were the same thing anyway. For instance, I dropped
EXYNOS850_TMU_T_BUF_VREF_SEL_MASK in favor of
EXYNOS_TMU_REF_VOLTAGE_MASK, and have a single EXYNOS850_TMU_TRIM0_MASK
instead of EXYNOS850_TMU_BGRI_TRIM_MASK, EXYNOS850_TMU_VREF_TRIM_MASK,
EXYNOS850_TMU_VBEI_TRIM_MASK and EXYNOS850_TMU_T_TRIM0_MASK. Also,

> Suggest using GENMASK() macro whenever possible.

This would make me have a separate mask for each of these again. Maybe
if this driver gets refactored in the future to use u32_get_bits() and
so on this would make more sense?
Sam Protsenko July 25, 2024, 12:56 a.m. UTC | #6
On Wed, Jul 24, 2024 at 10:31 AM Mateusz Majewski
<m.majewski2@samsung.com> wrote:
>
> > I'd suggest to group all the definitions here as such:
> >
> > #define REG1_OFFSET
> > #define REG1_FIELD1_OFFSET
> > #define REG1_FIELD2_OFFSET
> > ...empty line...
> > #define REG2_OFFSET
> > #define REG2_FIELD1_OFFSET
> > #define REG2_FIELD2_OFFSET
> > ...etc...
> >
> > Or otherwise each shift/mask constant should contain its register name
> > as a prefix, to avoid confusion. But right now it's kinda hard to
> > understand what belongs to what :) But that's just a nitpick.
>
> I came up with this:
>
> /* Exynos850 specific registers */
> #define EXYNOS850_TMU_REG_CURRENT_TEMP0_1       0x40
> #define EXYNOS850_TMU_REG_THD_TEMP0_RISE        0x50
> #define EXYNOS850_TMU_REG_THD_TEMP0_FALL        0x60
> #define EXYNOS850_TMU_TEMP_SHIFT                9
>
> #define EXYNOS850_TMU_TRIMINFO_SHIFT            4
> #define EXYNOS850_TMU_TRIMINFO_OFFSET(n) \
>         (EXYNOS_TMU_REG_TRIMINFO + (n) * EXYNOS850_TMU_TRIMINFO_SHIFT)
> #define EXYNOS850_TMU_T_TRIM0_SHIFT             18
>
> #define EXYNOS850_TMU_REG_CONTROL1              0x24
> #define EXYNOS850_TMU_LPI_MODE_MASK             1
> #define EXYNOS850_TMU_LPI_MODE_SHIFT            10
>
> #define EXYNOS850_TMU_REG_COUNTER_VALUE0        0x30
> #define EXYNOS850_TMU_EN_TEMP_SEN_OFF_MASK      0xffff
> #define EXYNOS850_TMU_EN_TEMP_SEN_OFF_SHIFT     0
>
> #define EXYNOS850_TMU_REG_COUNTER_VALUE1        0x34
> #define EXYNOS850_TMU_CLK_SENSE_ON_MASK         0xffff
> #define EXYNOS850_TMU_CLK_SENSE_ON_SHIFT        16
>
> #define EXYNOS850_TMU_REG_AVG_CON               0x38
> #define EXYNOS850_TMU_AVG_MODE_MASK             0x7
> #define EXYNOS850_TMU_DEM_ENABLE                BIT(4)
>
> #define EXYNOS850_TMU_REG_TRIM0                 0x3c
> #define EXYNOS850_TMU_TRIM0_MASK                0xf
> #define EXYNOS850_TMU_VBEI_TRIM_SHIFT           8
> #define EXYNOS850_TMU_VREF_TRIM_SHIFT           12
> #define EXYNOS850_TMU_BGRI_TRIM_SHIFT           20
>
> #define EXYNOS850_TMU_TEM1051X_SENSE_VALUE      0x028a
> #define EXYNOS850_TMU_TEM1456X_SENSE_VALUE      0x0a28
>

Looks better, thanks!

> This also omits some definitions that were in v1, as they had the same
> value and they were the same thing anyway. For instance, I dropped
> EXYNOS850_TMU_T_BUF_VREF_SEL_MASK in favor of
> EXYNOS_TMU_REF_VOLTAGE_MASK, and have a single EXYNOS850_TMU_TRIM0_MASK
> instead of EXYNOS850_TMU_BGRI_TRIM_MASK, EXYNOS850_TMU_VREF_TRIM_MASK,
> EXYNOS850_TMU_VBEI_TRIM_MASK and EXYNOS850_TMU_T_TRIM0_MASK. Also,
>
> > Suggest using GENMASK() macro whenever possible.
>
> This would make me have a separate mask for each of these again. Maybe
> if this driver gets refactored in the future to use u32_get_bits() and
> so on this would make more sense?

Sure, that was just a suggestion, don't have a strong opinion on that
one. If you don't like it, feel free to skip it for now.
diff mbox series

Patch

diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c
index f0de72a62fd7..bd52663f1a5a 100644
--- a/drivers/thermal/samsung/exynos_tmu.c
+++ b/drivers/thermal/samsung/exynos_tmu.c
@@ -116,6 +116,43 @@ 
 #define EXYNOS7_EMUL_DATA_SHIFT			7
 #define EXYNOS7_EMUL_DATA_MASK			0x1ff
 
+/* Exynos850 specific registers */
+#define EXYNOS850_TMU_REG_AVG_CON		0x58
+#define EXYNOS850_TMU_REG_CONTROL1		0x24
+#define EXYNOS850_TMU_REG_COUNTER_VALUE0	0x30
+#define EXYNOS850_TMU_REG_COUNTER_VALUE1	0x34
+#define EXYNOS850_TMU_REG_CURRENT_TEMP1_0	0x40
+#define EXYNOS850_TMU_REG_THD_TEMP0_RISE	0x50
+#define EXYNOS850_TMU_REG_THD_TEMP0_FALL	0x60
+#define EXYNOS850_TMU_REG_TRIM0			0x3C
+
+#define EXYNOS850_TMU_AVG_CON_SHIFT		18
+#define EXYNOS850_TMU_AVG_MODE_MASK		0x7
+#define EXYNOS850_TMU_BGRI_TRIM_MASK		0xF
+#define EXYNOS850_TMU_BGRI_TRIM_SHIFT		20
+#define EXYNOS850_TMU_CLK_SENSE_ON_MASK		0xffff
+#define EXYNOS850_TMU_CLK_SENSE_ON_SHIFT	16
+#define EXYNOS850_TMU_DEM_ENABLE		1
+#define EXYNOS850_TMU_DEM_SHIFT			4
+#define EXYNOS850_TMU_EN_TEMP_SEN_OFF_MASK	0xffff
+#define EXYNOS850_TMU_EN_TEMP_SEN_OFF_SHIFT	0
+#define EXYNOS850_TMU_LPI_MODE_MASK		1
+#define EXYNOS850_TMU_LPI_MODE_SHIFT		10
+#define EXYNOS850_TMU_T_BUF_SLOPE_SEL_MASK	0xF
+#define EXYNOS850_TMU_T_BUF_SLOPE_SEL_SHIFT	18
+#define EXYNOS850_TMU_T_BUF_VREF_SEL_MASK	0x1F
+#define EXYNOS850_TMU_T_BUF_VREF_SEL_SHIFT	18
+#define EXYNOS850_TMU_TEM1051X_SENSE_VALUE	0x028A
+#define EXYNOS850_TMU_TEM1456X_SENSE_VALUE	0x0A28
+#define EXYNOS850_TMU_TEMP_SHIFT		9
+#define EXYNOS850_TMU_TRIMINFO_SHIFT		4
+#define EXYNOS850_TMU_T_TRIM0_MASK		0xF
+#define EXYNOS850_TMU_T_TRIM0_SHIFT		18
+#define EXYNOS850_TMU_VBEI_TRIM_MASK		0xF
+#define EXYNOS850_TMU_VBEI_TRIM_SHIFT		8
+#define EXYNOS850_TMU_VREF_TRIM_MASK		0xF
+#define EXYNOS850_TMU_VREF_TRIM_SHIFT		12
+
 #define EXYNOS_FIRST_POINT_TRIM			25
 #define EXYNOS_SECOND_POINT_TRIM		85
 
@@ -133,6 +170,7 @@  enum soc_type {
 	SOC_ARCH_EXYNOS5420_TRIMINFO,
 	SOC_ARCH_EXYNOS5433,
 	SOC_ARCH_EXYNOS7,
+	SOC_ARCH_EXYNOS850,
 };
 
 /**
@@ -231,13 +269,16 @@  static int code_to_temp(struct exynos_tmu_data *data, u16 temp_code)
 
 static void sanitize_temp_error(struct exynos_tmu_data *data, u32 trim_info)
 {
-	u16 tmu_temp_mask =
-		(data->soc == SOC_ARCH_EXYNOS7) ? EXYNOS7_TMU_TEMP_MASK
-						: EXYNOS_TMU_TEMP_MASK;
+	u16 tmu_temp_mask = (data->soc == SOC_ARCH_EXYNOS7 ||
+			     data->soc == SOC_ARCH_EXYNOS850) ?
+				    EXYNOS7_TMU_TEMP_MASK :
+				    EXYNOS_TMU_TEMP_MASK;
+	int tmu_85_shift = (data->soc == SOC_ARCH_EXYNOS850) ?
+				   EXYNOS850_TMU_TEMP_SHIFT :
+				   EXYNOS_TRIMINFO_85_SHIFT;
 
 	data->temp_error1 = trim_info & tmu_temp_mask;
-	data->temp_error2 = ((trim_info >> EXYNOS_TRIMINFO_85_SHIFT) &
-				tmu_temp_mask);
+	data->temp_error2 = ((trim_info >> tmu_85_shift) & tmu_temp_mask);
 
 	if (!data->temp_error1 ||
 	    (data->min_efuse_value > data->temp_error1) ||
@@ -245,9 +286,8 @@  static void sanitize_temp_error(struct exynos_tmu_data *data, u32 trim_info)
 		data->temp_error1 = data->efuse_value & tmu_temp_mask;
 
 	if (!data->temp_error2)
-		data->temp_error2 =
-			(data->efuse_value >> EXYNOS_TRIMINFO_85_SHIFT) &
-			tmu_temp_mask;
+		data->temp_error2 = (data->efuse_value >> tmu_85_shift) &
+				    tmu_temp_mask;
 }
 
 static int exynos_tmu_initialize(struct platform_device *pdev)
@@ -588,6 +628,129 @@  static void exynos7_tmu_initialize(struct platform_device *pdev)
 	sanitize_temp_error(data, trim_info);
 }
 
+static void exynos850_tmu_set_low_temp(struct exynos_tmu_data *data, u8 temp)
+{
+	exynos_tmu_update_temp(data, EXYNOS850_TMU_REG_THD_TEMP0_FALL + 12, 0,
+			       temp);
+	exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+			      EXYNOS_TMU_INTEN_FALL0_SHIFT + 0, true);
+}
+
+static void exynos850_tmu_set_high_temp(struct exynos_tmu_data *data, u8 temp)
+{
+	exynos_tmu_update_temp(data, EXYNOS850_TMU_REG_THD_TEMP0_RISE + 12, 16,
+			       temp);
+	exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+			      EXYNOS7_TMU_INTEN_RISE0_SHIFT + 1, true);
+}
+
+static void exynos850_tmu_disable_low(struct exynos_tmu_data *data)
+{
+	exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+			      EXYNOS_TMU_INTEN_FALL0_SHIFT + 0, false);
+}
+
+static void exynos850_tmu_disable_high(struct exynos_tmu_data *data)
+{
+	exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+			      EXYNOS7_TMU_INTEN_RISE0_SHIFT + 1, false);
+}
+
+static void exynos850_tmu_set_crit_temp(struct exynos_tmu_data *data, u8 temp)
+{
+	exynos_tmu_update_temp(data, EXYNOS850_TMU_REG_THD_TEMP0_RISE + 0, 16,
+			       temp);
+	exynos_tmu_update_bit(data, EXYNOS_TMU_REG_CONTROL,
+			      EXYNOS_TMU_THERM_TRIP_EN_SHIFT, true);
+	exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+			      EXYNOS7_TMU_INTEN_RISE0_SHIFT + 7, true);
+}
+
+static void exynos850_tmu_initialize(struct platform_device *pdev)
+{
+	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+	int cal_type;
+	unsigned int avg_mode, buf, bgri, vref, vbei;
+
+	buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
+	cal_type = (buf & EXYNOS5433_TRIMINFO_CALIB_SEL_MASK) >>
+		   EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT;
+	data->reference_voltage = (buf >> EXYNOS850_TMU_T_BUF_VREF_SEL_SHIFT) &
+				  EXYNOS850_TMU_T_BUF_VREF_SEL_MASK;
+	buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO +
+		    EXYNOS850_TMU_TRIMINFO_SHIFT);
+	data->gain = (buf >> EXYNOS850_TMU_T_BUF_SLOPE_SEL_SHIFT) &
+		     EXYNOS850_TMU_T_BUF_SLOPE_SEL_MASK;
+	buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO +
+		    2 * EXYNOS850_TMU_TRIMINFO_SHIFT);
+	avg_mode = (buf >> EXYNOS850_TMU_AVG_CON_SHIFT) &
+		   EXYNOS850_TMU_AVG_MODE_MASK;
+	buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO +
+		    3 * EXYNOS850_TMU_TRIMINFO_SHIFT);
+	bgri = (buf >> EXYNOS850_TMU_T_TRIM0_SHIFT) &
+	       EXYNOS850_TMU_T_TRIM0_MASK;
+	buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO +
+		    4 * EXYNOS850_TMU_TRIMINFO_SHIFT);
+	vref = (buf >> EXYNOS850_TMU_T_TRIM0_SHIFT) &
+	       EXYNOS850_TMU_T_TRIM0_MASK;
+	buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO +
+		    5 * EXYNOS850_TMU_TRIMINFO_SHIFT);
+	vbei = (buf >> EXYNOS850_TMU_T_TRIM0_SHIFT) &
+	       EXYNOS850_TMU_T_TRIM0_MASK;
+
+	buf = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
+	sanitize_temp_error(data, buf);
+
+	switch (cal_type) {
+	case EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING:
+		data->cal_type = TYPE_TWO_POINT_TRIMMING;
+		break;
+	case EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING:
+	default:
+		data->cal_type = TYPE_ONE_POINT_TRIMMING;
+		break;
+	}
+
+	dev_info(&pdev->dev, "Calibration type is %d-point calibration\n",
+		 cal_type ? 2 : 1);
+
+	buf = readl(data->base + EXYNOS850_TMU_REG_AVG_CON);
+	buf &= ~(EXYNOS850_TMU_AVG_MODE_MASK);
+	buf &= ~(EXYNOS850_TMU_DEM_ENABLE << EXYNOS850_TMU_DEM_SHIFT);
+	if (avg_mode) {
+		buf |= avg_mode;
+		buf |= (EXYNOS850_TMU_DEM_ENABLE << EXYNOS850_TMU_DEM_SHIFT);
+	}
+	writel(buf, data->base + EXYNOS850_TMU_REG_AVG_CON);
+
+	buf = readl(data->base + EXYNOS850_TMU_REG_COUNTER_VALUE0);
+	buf &= ~(EXYNOS850_TMU_EN_TEMP_SEN_OFF_MASK
+		 << EXYNOS850_TMU_EN_TEMP_SEN_OFF_SHIFT);
+	buf |= EXYNOS850_TMU_TEM1051X_SENSE_VALUE
+	       << EXYNOS850_TMU_EN_TEMP_SEN_OFF_SHIFT;
+	writel(buf, data->base + EXYNOS850_TMU_REG_COUNTER_VALUE0);
+
+	buf = readl(data->base + EXYNOS850_TMU_REG_COUNTER_VALUE1);
+	buf &= ~(EXYNOS850_TMU_CLK_SENSE_ON_MASK
+		 << EXYNOS850_TMU_CLK_SENSE_ON_SHIFT);
+	buf |= EXYNOS850_TMU_TEM1051X_SENSE_VALUE
+	       << EXYNOS850_TMU_CLK_SENSE_ON_SHIFT;
+	writel(buf, data->base + EXYNOS850_TMU_REG_COUNTER_VALUE1);
+
+	buf = readl(data->base + EXYNOS850_TMU_REG_TRIM0);
+	buf &= ~(EXYNOS850_TMU_BGRI_TRIM_MASK << EXYNOS850_TMU_BGRI_TRIM_SHIFT);
+	buf &= ~(EXYNOS850_TMU_VREF_TRIM_MASK << EXYNOS850_TMU_VREF_TRIM_SHIFT);
+	buf &= ~(EXYNOS850_TMU_VBEI_TRIM_MASK << EXYNOS850_TMU_VBEI_TRIM_SHIFT);
+	buf |= (bgri << EXYNOS850_TMU_BGRI_TRIM_SHIFT);
+	buf |= (vref << EXYNOS850_TMU_VREF_TRIM_SHIFT);
+	buf |= (vbei << EXYNOS850_TMU_VBEI_TRIM_SHIFT);
+	writel(buf, data->base + EXYNOS850_TMU_REG_TRIM0);
+
+	buf = readl(data->base + EXYNOS850_TMU_REG_CONTROL1);
+	buf &= ~(EXYNOS850_TMU_LPI_MODE_MASK << EXYNOS850_TMU_LPI_MODE_SHIFT);
+	writel(buf, data->base + EXYNOS850_TMU_REG_CONTROL1);
+}
+
 static void exynos4210_tmu_control(struct platform_device *pdev, bool on)
 {
 	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
@@ -679,7 +842,8 @@  static u32 get_emul_con_reg(struct exynos_tmu_data *data, unsigned int val,
 
 		val &= ~(EXYNOS_EMUL_TIME_MASK << EXYNOS_EMUL_TIME_SHIFT);
 		val |= (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT);
-		if (data->soc == SOC_ARCH_EXYNOS7) {
+		if (data->soc == SOC_ARCH_EXYNOS7 ||
+		    data->soc == SOC_ARCH_EXYNOS850) {
 			val &= ~(EXYNOS7_EMUL_DATA_MASK <<
 				EXYNOS7_EMUL_DATA_SHIFT);
 			val |= (temp_to_code(data, temp) <<
@@ -709,7 +873,8 @@  static void exynos4412_tmu_set_emulation(struct exynos_tmu_data *data,
 		emul_con = EXYNOS5260_EMUL_CON;
 	else if (data->soc == SOC_ARCH_EXYNOS5433)
 		emul_con = EXYNOS5433_TMU_EMUL_CON;
-	else if (data->soc == SOC_ARCH_EXYNOS7)
+	else if (data->soc == SOC_ARCH_EXYNOS7 ||
+		 data->soc == SOC_ARCH_EXYNOS850)
 		emul_con = EXYNOS7_TMU_REG_EMUL_CON;
 	else
 		emul_con = EXYNOS_EMUL_CON;
@@ -766,6 +931,12 @@  static int exynos7_tmu_read(struct exynos_tmu_data *data)
 		EXYNOS7_TMU_TEMP_MASK;
 }
 
+static int exynos850_tmu_read(struct exynos_tmu_data *data)
+{
+	return readw(data->base + EXYNOS850_TMU_REG_CURRENT_TEMP1_0) &
+	       EXYNOS7_TMU_TEMP_MASK;
+}
+
 static irqreturn_t exynos_tmu_threaded_irq(int irq, void *id)
 {
 	struct exynos_tmu_data *data = id;
@@ -794,7 +965,8 @@  static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data)
 	if (data->soc == SOC_ARCH_EXYNOS5260) {
 		tmu_intstat = EXYNOS5260_TMU_REG_INTSTAT;
 		tmu_intclear = EXYNOS5260_TMU_REG_INTCLEAR;
-	} else if (data->soc == SOC_ARCH_EXYNOS7) {
+	} else if (data->soc == SOC_ARCH_EXYNOS7 ||
+		   data->soc == SOC_ARCH_EXYNOS850) {
 		tmu_intstat = EXYNOS7_TMU_REG_INTPEND;
 		tmu_intclear = EXYNOS7_TMU_REG_INTPEND;
 	} else if (data->soc == SOC_ARCH_EXYNOS5433) {
@@ -845,6 +1017,9 @@  static const struct of_device_id exynos_tmu_match[] = {
 	}, {
 		.compatible = "samsung,exynos7-tmu",
 		.data = (const void *)SOC_ARCH_EXYNOS7,
+	}, {
+		.compatible = "samsung,exynos850-tmu",
+		.data = (const void *)SOC_ARCH_EXYNOS850,
 	},
 	{ },
 };
@@ -957,6 +1132,21 @@  static int exynos_map_dt_data(struct platform_device *pdev)
 		data->min_efuse_value = 15;
 		data->max_efuse_value = 100;
 		break;
+	case SOC_ARCH_EXYNOS850:
+		data->tmu_set_low_temp = exynos850_tmu_set_low_temp;
+		data->tmu_set_high_temp = exynos850_tmu_set_high_temp;
+		data->tmu_disable_low = exynos850_tmu_disable_low;
+		data->tmu_disable_high = exynos850_tmu_disable_high;
+		data->tmu_set_crit_temp = exynos850_tmu_set_crit_temp;
+		data->tmu_initialize = exynos850_tmu_initialize;
+		data->tmu_control = exynos4210_tmu_control;
+		data->tmu_read = exynos850_tmu_read;
+		data->tmu_set_emulation = exynos4412_tmu_set_emulation;
+		data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
+		data->efuse_value = 55;
+		data->min_efuse_value = 0;
+		data->max_efuse_value = 511;
+		break;
 	default:
 		dev_err(&pdev->dev, "Platform not supported\n");
 		return -EINVAL;
@@ -1051,7 +1241,7 @@  static int exynos_tmu_probe(struct platform_device *pdev)
 		return ret;
 
 	data->clk = devm_clk_get(dev, "tmu_apbif");
-	if (IS_ERR(data->clk))
+	if (IS_ERR(data->clk) && data->soc != SOC_ARCH_EXYNOS850)
 		return dev_err_probe(dev, PTR_ERR(data->clk), "Failed to get clock\n");
 
 	data->clk_sec = devm_clk_get(dev, "tmu_triminfo_apbif");