[RFC PATCH 4/5] PM / AVS: thermal: MT8173: Introduce support for SVS engine

Daniel Kurtz djkurtz at chromium.org
Fri Jan 22 15:38:06 PST 2016


Hi Pi-Cheng,

Thanks for the patch!

Some review comments inline...

On Fri, Jan 22, 2016 at 12:40 AM, Pi-Cheng Chen
<pi-cheng.chen at linaro.org> wrote:
> The SVS (Smart Voltage Scaling) engine is a piece of hardware which is
> used to caculate optimized voltage values of several power domains, e.g.

calculate

> CPU clusters, according to chip process corner, temperatures, and other
> factors. Then DVFS driver could apply those optimized voltage values to
> reduce power consumption. This engine takes calibration data stored in
> on-chip E-Fuse device as configuration input during initialization. Once
> the initialization is done, SVS engine issues interrupts according to
> temerature changes of power domains to notify DVFS driver to get

temperature

> calculated voltage values.
>
> The configuration registers of SVS engine are shared with Mediatek
> thermal controller, and those registers are banked for different power
> domains. In addition, the SVS engine also needs some information from
> Mediatek thermal controller, e.g. the temperature of a specific power
> domain, part of the thermal calibration data. Therefore the support for
> SVS engine is integrated with Mediatek thermal controller driver.
>
> Also, for platform specific requirement, to make SVS engine work
> correctly, the initialization of SVS engine should be later then
> Mediatek thermal controller, and prior to mt8173-cpufreq driver. Hence,
> the platform device registration code of mt8173-cpufreq is removed here
> after SVS initialization is done or skipped to ensure the platform
> specific initialization flow.
>
> The functionality of SVS engine is optional for Mediatek thermal
> controller. If the required resources of SVS engine is absent or SVS
> failed during initialization stage, the SVS control flow will be
> skipped and the thermal controller will function normally.
>
> CC: Stephen Boyd <sboyd at codeaurora.org>
>
> Signed-off-by: Pi-Cheng Chen <pi-cheng.chen at linaro.org>
> ---
> This patch relies on the runtime voltage adjustment of OPP introduced in CPR
> patchset done by Stephen Boyd[1].
>
> [1] https://lkml.org/lkml/2015/9/18/833
> ---
>  drivers/thermal/mtk_thermal.c | 704 ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 704 insertions(+)
>
> diff --git a/drivers/thermal/mtk_thermal.c b/drivers/thermal/mtk_thermal.c
> index f20e784..d8ba9dd 100644
> --- a/drivers/thermal/mtk_thermal.c
> +++ b/drivers/thermal/mtk_thermal.c
> @@ -13,6 +13,9 @@
>   */
>
>  #include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/cpu.h>
> +#include <linux/cpufreq.h>
>  #include <linux/delay.h>
>  #include <linux/interrupt.h>
>  #include <linux/kernel.h>
> @@ -21,12 +24,15 @@
>  #include <linux/of.h>
>  #include <linux/of_address.h>
>  #include <linux/platform_device.h>
> +#include <linux/pm_opp.h>
> +#include <linux/regulator/consumer.h>
>  #include <linux/slab.h>
>  #include <linux/io.h>
>  #include <linux/thermal.h>
>  #include <linux/reset.h>
>  #include <linux/types.h>
>  #include <linux/nvmem-consumer.h>
> +#include <linux/workqueue.h>
>
>  /* AUXADC Registers */
>  #define AUXADC_CON0_V          0x000
> @@ -73,7 +79,32 @@
>
>  #define TEMP_SPARE0            0x0f0
>
> +#define SVS_BANK_CONFIG0       0x200
> +#define SVS_BANK_CONFIG1       0x204
> +#define SVS_BANK_CONFIG2       0x208
> +#define SVS_BANK_CONFIG3       0x20c
> +#define SVS_BANK_CONFIG4       0x210
> +#define SVS_BANK_CONFIG5       0x214
> +#define SVS_BANK_FREQPCT30     0x218
> +#define SVS_BANK_FREQPCT74     0x21c
> +#define SVS_BANK_LIMITVALS     0x220
> +#define SVS_BANK_CONFIG6       0x224
> +#define SVS_BANK_CONFIG7       0x228
> +#define SVS_BANK_CONFIG8       0x22c
> +#define SVS_BANK_CONFIG9       0x230
> +#define SVS_BANK_CONFIG10      0x234
> +#define SVS_BANK_EN            0x238
> +#define SVS_BANK_CONTROL0      0x23c
> +#define SVS_BANK_CONTROL1      0x240
> +#define SVS_BANK_CONTROL2      0x244
> +#define SVS_BANK_VOP30         0x248
> +#define SVS_BANK_VOP74         0x24c
> +#define SVS_BANK_INTST         0x254
> +#define SVS_BANK_CONTROL3      0x25c
> +#define SVS_BANK_CONTROL4      0x264
> +
>  #define PTPCORESEL             0x400
> +#define SVS_SVSINTST           0x408
>
>  #define TEMP_MONCTL1_PERIOD_UNIT(x)    ((x) & 0x3ff)
>
> @@ -106,6 +137,24 @@
>  /* The number of sensing points per bank */
>  #define MT8173_NUM_SENSORS_PER_ZONE    4
>
> +/* The number of OPPs supported by SVS */
> +#define MT8173_NUM_SVS_OPP             8
> +
> +/* Bit masks of SVS enable and IRQ configrations */
> +#define PHASE_EN_MASK          0x7
> +#define PHASE_0_EN             0x1
> +#define PHASE_CON_EN           0x2
> +#define PHASE_1_EN             0x4
> +#define PHASE_01_EN            (PHASE_0_EN | PHASE_1_EN)
> +#define PHASE_01_IRQ           0x1
> +#define PHASE_CON_IRQ          (0xff << 16)
> +
> +/* The number of SVS banks implemented */
> +#define MT8173_NUM_SVS_BANKS   2
> +
> +#define MT8173_SVS_BANK_CA53   0
> +#define MT8173_SVS_BANK_CA72   1
> +
>  /* Layout of the fuses providing the calibration data */
>  #define MT8173_CALIB_BUF0_VALID                (1 << 0)
>  #define MT8173_CALIB_BUF1_ADC_GE(x)    (((x) >> 22 ) & 0x3ff)
> @@ -116,6 +165,22 @@
>  #define MT8173_CALIB_BUF2_VTS_TSABB(x) (((x) >> 14 ) & 0x1ff)
>  #define MT8173_CALIB_BUF0_DEGC_CALI(x) (((x) >> 1 ) & 0x3f)
>  #define MT8173_CALIB_BUF0_O_SLOPE(x)   (((x) >> 26 ) & 0x3f)
> +#define MT8173_CALIB_BUF0_O_SLOPE_S(x) (((x) >> 7) & 0x1)
> +
> +/* Helpers to calculate configuration values from SVS calibration data */
> +#define SVS_CALIB_VALID        (1)
> +#define BANK_SHIFT(bank) ((bank == 0) ? 8 : 0)
> +#define SVS_CALIB_BANK_CONFIG0(buf, s)                         \
> +       (((((buf[33] >> (s)) & 0xff)) << 8) |                   \
> +       ((buf[32] >> (s)) & 0xff))

Align opening parentheses.

> +#define SVS_CALIB_BANK_CONFIG1(buf, s)                         \
> +       ((((buf[34] >> (s)) & 0xff) << 8) | 0x100006)
> +#define SVS_CALIB_BANK_CONFIG2L(base, s)                       \
> +       ((buf[0] >> (s)) & 0xff)
> +#define SVS_CALIB_BANK_CONFIG2H(base, s)                       \
> +       ((buf[1] >> (s)) & 0xff)
> +#define SVS_CALIB_BANK_CONFIG3(base, s)                                \
> +       (((buf[2] >> (s)) & 0xff) << 8)
>
>  #define THERMAL_NAME    "mtk-thermal"
>
> @@ -126,14 +191,55 @@ struct mtk_thermal_bank {
>         int id;
>  };
>
> +enum svs_state {
> +       SVS_PHASE_0 = 0,
> +       SVS_PHASE_1,
> +       SVS_PHASE_CONTINUOUS,
> +};
> +
> +struct mtk_svs_bank {
> +       int bank_id;
> +       struct mtk_thermal *mt;
> +       struct completion init_done;
> +       struct work_struct work;
> +
> +       struct device *dev;
> +       struct regulator *reg;
> +
> +       /* SVS per-bank calibration values */
> +       u32 ctrl0;
> +       u32 config0;
> +       u32 config1;
> +       u32 config2;
> +       u32 config3;
> +
> +       unsigned long freq_table[MT8173_NUM_SVS_OPP];
> +       int volt_table[MT8173_NUM_SVS_OPP];
> +       int updated_volt_table[MT8173_NUM_SVS_OPP];
> +};
> +
> +struct mtk_svs_bank_cfg {
> +       const int dev_id;
> +       const int ts;
> +       const int vmin;
> +       const int vmax;
> +       const int vboot;
> +       const unsigned long base_freq;
> +};
> +
>  struct mtk_thermal {
>         struct device *dev;
>         void __iomem *thermal_base;
>
>         struct clk *clk_peri_therm;
>         struct clk *clk_auxadc;
> +       struct clk *svs_mux;
> +       struct clk *svs_pll;
>
>         struct mtk_thermal_bank banks[MT8173_NUM_ZONES];
> +       struct mtk_svs_bank svs_banks[MT8173_NUM_SVS_BANKS];
> +
> +       int svs_irq;
>
>         spinlock_t lock;
>
> @@ -142,6 +248,9 @@ struct mtk_thermal {
>         s32 degc_cali;
>         s32 o_slope;
>         s32 vts[MT8173_NUM_SENSORS];
> +       s32 x_roomt[MT8173_NUM_SENSORS];

AFAICT, this x_roomt isn't used.

> +       s32 bts[MT8173_NUM_ZONES];
> +       s32 mts;

What are "bts" and "mts".
Please use more descriptive names, or at least a comment.

>
>         struct thermal_zone_device *tzd;
>  };
> @@ -198,6 +307,25 @@ static const struct mtk_thermal_sense_point
>         },
>  };
>
> +static const struct mtk_svs_bank_cfg svs_bank_cfgs[MT8173_NUM_SVS_BANKS] = {
> +       [MT8173_SVS_BANK_CA53] = {
> +               .dev_id = 0,
> +               .vmax = 1125,
> +               .vmin = 800,
> +               .vboot = 1000,
> +               .base_freq = 1600000,
> +               .ts = MT8173_TS3
> +       },
> +       [MT8173_SVS_BANK_CA72] = {
> +               .dev_id = 2,
> +               .vmax = 1125,
> +               .vmin = 800,
> +               .vboot = 1000,
> +               .base_freq = 2000000,
> +               .ts = MT8173_TS4
> +       }
> +};
> +
>  /**
>   * raw_to_mcelsius - convert a raw ADC value to mcelsius
>   * @mt:                The thermal controller
> @@ -222,6 +350,34 @@ static int raw_to_mcelsius(struct mtk_thermal *mt, int sensno, s32 raw)
>  }
>
>  /**
> + * mvolt_to_config - convert a voltage value to SVS voltage config value
> + * @mvolt:     voltage value
> + */
> +static inline u32 mvolt_to_config(int mvolt)
> +{
> +       return ((mvolt - 700) * 100 + 625 - 1) / 625;
> +}
> +
> +/**
> + * mvolt_to_config - convert a SVS voltage config value to voltage value
> + * @val:       SVS voltage config value
> + */
> +static inline int config_to_mv(u32 val)
> +{
> +       return (val * 625 / 100) + 700;
> +}
> +
> +/**
> + * khz_to_config - convert a frequency value to SVS frequency config value
> + * @rate:      frequency value
> + * @base_rate: rate to be used to calculate frequency percentage
> + */
> +static inline int khz_to_config(unsigned long rate, unsigned long base_rate)
> +{
> +       return (rate * 100 + base_rate - 1) / base_rate;
> +}
> +
> +/**
>   * mtk_thermal_switch_bank - switch to bank
>   * @bank:      The bank
>   *
> @@ -240,6 +396,46 @@ static void mtk_thermal_switch_bank(struct mtk_thermal_bank *bank)
>  }
>
>  /**
> + * mtk_svs_update_voltage_table - update the calculated voltage table
> + * @svs: The SVS bank
> + * @offset: The voltage offset
> + *
> + * Read the calculated voltage values from registers and update the SVS bank
> + * voltage table which will be write to OPP table entries later. Caller should
> + * select the bank and hold mt->lock before calling it.
> + */
> +static void mtk_svs_update_voltage_table(struct mtk_svs_bank *svs, int offset)
> +{
> +       struct mtk_thermal *mt = svs->mt;
> +       int vmin, vmax, *volt_table;
> +       u32 reg;
> +
> +       vmin = svs_bank_cfgs[svs->bank_id].vmin;
> +       vmax = svs_bank_cfgs[svs->bank_id].vmax;
> +       volt_table = svs->updated_volt_table;
> +
> +       reg = readl(mt->thermal_base + SVS_BANK_VOP30);

Please add a comment explaining what are these two registers.

> +       volt_table[0] = clamp(config_to_mv((reg & 0xff) + offset),
> +                             vmin, vmax);
> +       volt_table[1] = clamp(config_to_mv(((reg >> 8) & 0xff) + offset),
> +                             vmin, vmax);
> +       volt_table[2] = clamp(config_to_mv(((reg >> 16) & 0xff) + offset),
> +                             vmin, vmax);
> +       volt_table[3] = clamp(config_to_mv(((reg >> 24) & 0xff) + offset),
> +                             vmin, vmax);
> +
> +       reg = readl(mt->thermal_base + SVS_BANK_VOP74);
> +       volt_table[4] = clamp(config_to_mv((reg & 0xff) + offset),
> +                             vmin, vmax);
> +       volt_table[5] = clamp(config_to_mv(((reg >> 8) & 0xff) + offset),
> +                             vmin, vmax);
> +       volt_table[6] = clamp(config_to_mv(((reg >> 16) & 0xff) + offset),
> +                             vmin, vmax);
> +       volt_table[7] = clamp(config_to_mv(((reg >> 24) & 0xff) + offset),
> +                             vmin, vmax);
> +}
> +
> +/**
>   * mtk_thermal_bank_temperature - get the temperature of a bank
>   * @bank:      The bank
>   *
> @@ -426,6 +622,7 @@ static int mtk_thermal_get_calibration_data(struct device *dev, struct mtk_therm
>         u32 *buf;
>         size_t len;
>         int i, ret = 0;
> +       s32 o_slope_s, ge, oe, gain, x_roomt, temp1, temp2;
>
>         /* Start with default values */
>         mt->adc_ge = 512;
> @@ -433,6 +630,7 @@ static int mtk_thermal_get_calibration_data(struct device *dev, struct mtk_therm
>                 mt->vts[i] = 260;
>         mt->degc_cali = 40;
>         mt->o_slope = 0;
> +       o_slope_s = 0;
>
>         cell = nvmem_cell_get(dev, "calibration-data");
>         if (IS_ERR(cell)) {
> @@ -463,23 +661,464 @@ static int mtk_thermal_get_calibration_data(struct device *dev, struct mtk_therm
>                 mt->vts[MT8173_TSABB] = MT8173_CALIB_BUF2_VTS_TSABB(buf[2]);
>                 mt->degc_cali = MT8173_CALIB_BUF0_DEGC_CALI(buf[0]);
>                 mt->o_slope = MT8173_CALIB_BUF0_O_SLOPE(buf[0]);
> +               o_slope_s = MT8173_CALIB_BUF0_O_SLOPE_S(buf[0]);

Perhaps just do this once, here:
  o_slope_s = MT8173_CALIB_BUF0_O_SLOPE_S(buf[0]);
  mt->o_slope = (o_slope ? -1 : 1) * MT8173_CALIB_BUF0_O_SLOPE(buf[0]);

Don't we need to consider the slope's sign in raw_to_mcelsius(), too?

>         } else {
>                 dev_info(dev, "Device not calibrated, using default calibration values\n");
>         }
>
> +       oe = mt->adc_ge - 512;
> +       ge = oe * 10000 / 4096;
> +       gain = 10000 + ge;
> +
> +       /* calculating MTS */
> +       mt->mts = (o_slope_s == 0) ?
> +                 10000 * 100000 / gain * 15 / 18 / (165 + mt->o_slope) :
> +                 10000 * 100000 / gain * 15 / 18 / (165 - mt->o_slope);

What are all of these magic numbers?
Please define some named constants.

> +
> +       /* calculating per-bank BTS */
> +       for (i = 0; i < MT8173_NUM_SVS_BANKS; i++) {
> +               int ts = svs_bank_cfgs[i].ts;
> +
> +               x_roomt = mt->vts[ts] + 3350 - oe * 10000 / 4096 * 10000 / gain;
> +               temp1 = (10000 * 100000 / 4096 / gain) *
> +                       ge + x_roomt * 10 * 15 / 18;
> +               temp2 = (o_slope_s == 0) ? temp1 * 10 / (165 + mt->o_slope) :
> +                       temp1 * 10 / (165 - mt->o_slope);
> +               mt->bts[i] = (mt->degc_cali * 10 / 2 + temp2 - 250) * 4 / 10;

This math would be more clear if the temporary variables and constants
had meaningful names.
Also, if you really must do integer division, you probably want to use
DIV_ROUND_UP().

> +       }
> +
>  out:
>         kfree(buf);
>
>         return ret;
>  }
>
> +static int mtk_svs_get_calibration_data(struct device *dev,
> +                                       struct mtk_thermal *mt)
> +{
> +       struct nvmem_cell *cell;
> +       u32 *buf;
> +       size_t len;
> +       int i, ret = 0;
> +
> +       cell = nvmem_cell_get(dev, "svs-calibration-data");
> +       if (IS_ERR(cell))
> +               return PTR_ERR(cell);
> +
> +       buf = (u32 *)nvmem_cell_read(cell, &len);

No need to explicitly cast from (void *) to (u32 *).

> +       nvmem_cell_put(cell);
> +
> +       if (IS_ERR(buf)) {
> +               dev_err(dev, "failed to get svs calibration data: %ld\n",
> +                       PTR_ERR(buf));
> +               return PTR_ERR(buf);
> +       }
> +
> +       if (len < 0x8c || !(buf[29] & SVS_CALIB_VALID)) {
> +               dev_err(dev, "Invalid SVS calibration data\n");
> +               ret = -EINVAL;
> +               goto out;
> +       }
> +
> +       for (i = 0; i < MT8173_NUM_SVS_BANKS; i++) {
> +               u32 temp;

Move the BANK_SHIFT(i) into SVS_CALIB_BANK_*() macros and the
following will be more readable.

> +
> +               mt->svs_banks[i].config0 =
> +                               SVS_CALIB_BANK_CONFIG0(buf, BANK_SHIFT(i));
> +               mt->svs_banks[i].config1 =
> +                               SVS_CALIB_BANK_CONFIG1(buf, BANK_SHIFT(i));
> +               mt->svs_banks[i].config3 =
> +                               SVS_CALIB_BANK_CONFIG3(buf, BANK_SHIFT(i));
> +

Add a comment here to explain what you are doing here for config2.

> +               temp = SVS_CALIB_BANK_CONFIG2H(buf, BANK_SHIFT(i));
> +               if (temp < 128 && i == MT8173_SVS_BANK_CA72)
> +                       temp = (unsigned char)((temp - 256) / 2);
> +               temp = ((temp & 0xff) << 8) |
> +                      SVS_CALIB_BANK_CONFIG2L(buf, BANK_SHIFT(i));
> +               mt->svs_banks[i].config2 = temp;
> +       }
> +
> +out:
> +       kfree(buf);
> +
> +       return ret;
> +}
> +
> +/* Caller must call this function with mt->lock held */
> +static void mtk_svs_set_phase(struct mtk_svs_bank *svs, int phase)
> +{
> +       struct mtk_thermal *mt = svs->mt;
> +       unsigned long *freq_tbl, base_freq;
> +       int id = svs->bank_id;
> +
> +       freq_tbl = svs->freq_table;
> +       base_freq = svs_bank_cfgs[id].base_freq;
> +
> +       writel(svs->config0, mt->thermal_base + SVS_BANK_CONFIG0);
> +       writel(svs->config1, mt->thermal_base + SVS_BANK_CONFIG1);
> +       writel(svs->config2, mt->thermal_base + SVS_BANK_CONFIG2);
> +       writel(svs->config3, mt->thermal_base + SVS_BANK_CONFIG3);
> +       writel(0x555555, mt->thermal_base + SVS_BANK_CONFIG4);
> +       writel(0x555555, mt->thermal_base + SVS_BANK_CONFIG5);
> +       writel(0x80000000, mt->thermal_base + SVS_BANK_CONFIG10);

Define some named constants (macros) for these magic numbers.

> +
> +       writel((khz_to_config(freq_tbl[0], base_freq) & 0xff) |

Just have khz_to_config return a u8 and remove the "& 0xff" here.

> +              ((khz_to_config(freq_tbl[1], base_freq) & 0xff) << 8) |
> +              ((khz_to_config(freq_tbl[2], base_freq) & 0xff) << 16) |
> +              ((khz_to_config(freq_tbl[3], base_freq) & 0xff) << 24),
> +              mt->thermal_base + SVS_BANK_FREQPCT30);

Please add a comment here describing what these two registers are for.

> +
> +       writel((khz_to_config(freq_tbl[4], base_freq) & 0xff) |
> +              ((khz_to_config(freq_tbl[5], base_freq) & 0xff) << 8) |
> +              ((khz_to_config(freq_tbl[6], base_freq) & 0xff) << 16) |
> +              ((khz_to_config(freq_tbl[7], base_freq) & 0xff) << 24),
> +              mt->thermal_base + SVS_BANK_FREQPCT74);
> +

Same thing here for mvolt_to_config().

> +       writel(((mvolt_to_config(svs_bank_cfgs[id].vmax) & 0xff) << 24) |
> +              ((mvolt_to_config(svs_bank_cfgs[id].vmin) & 0xff) << 16) | 0x1fe,

Named constants here for 0x1fe and below...

> +              mt->thermal_base + SVS_BANK_LIMITVALS);
> +
> +       writel(mvolt_to_config(svs_bank_cfgs[id].vboot),
> +              mt->thermal_base + SVS_BANK_CONFIG6);
> +       writel(0xa28, mt->thermal_base + SVS_BANK_CONFIG7);
> +       writel(0xffff, mt->thermal_base + SVS_BANK_CONFIG8);
> +
> +       /* clear all pending interrupt */
> +       writel(0xffffffff, mt->thermal_base + SVS_BANK_INTST);

Why is this necessary?

> +
> +       switch (phase) {
> +       case SVS_PHASE_0:
> +               writel(0x5f01, mt->thermal_base + SVS_BANK_CONTROL3);
> +               writel(PHASE_0_EN, mt->thermal_base + SVS_BANK_EN);
> +               break;
> +       case SVS_PHASE_1:
> +               writel(0x5f01, mt->thermal_base + SVS_BANK_CONTROL3);
> +               writel(svs->ctrl0, mt->thermal_base + SVS_BANK_CONTROL0);
> +               writel(PHASE_0_EN | PHASE_1_EN,
> +                      mt->thermal_base + SVS_BANK_EN);
> +               break;
> +       case SVS_PHASE_CONTINUOUS:
> +               writel(((mt->bts[id] & 0xfff) << 12) | (mt->mts & 0xfff),
> +                      mt->thermal_base + SVS_BANK_CONFIG9);
> +               writel(0xff0000, mt->thermal_base + SVS_BANK_CONTROL3);
> +               writel(PHASE_CON_EN, mt->thermal_base + SVS_BANK_EN);
> +       }
> +}
> +
> +static void mtk_svs_adjust_voltage(struct mtk_svs_bank *svs)
> +{
> +       int i;
> +
> +       for (i = 0; i < MT8173_NUM_SVS_OPP; i++) {
> +               if (!svs->freq_table[i])
> +                       continue;
> +

Don't we need to grab some "whole table" lock before manipulating the
OPP table entries?
Otherwise, someone who is iterating over the entire table might see
something unexpected.

> +               dev_pm_opp_adjust_voltage(svs->dev, svs->freq_table[i] * 1000,
> +                                         svs->updated_volt_table[i] * 1000);
> +       }
> +}
> +
> +static void adjust_voltage_work(struct work_struct *work)
> +{
> +       struct mtk_svs_bank *svs = container_of(work, struct mtk_svs_bank,
> +                                               work);
> +       struct mtk_thermal *mt = svs->mt;
> +       unsigned long flags;
> +       int temp, low_temp_offset = 0;
> +
> +       spin_lock_irqsave(&mt->lock, flags);
> +       mtk_thermal_switch_bank(&mt->banks[svs->bank_id]);
> +
> +       temp = mtk_thermal_bank_temperature(&mt->banks[svs->bank_id]);
> +
> +       spin_unlock_irqrestore(&mt->lock, flags);
> +
> +       if (temp <= 33000)
> +               low_temp_offset = 10;

Please define some named constants for this threshold temperature and
the offset voltage.

> +
> +       mtk_svs_update_voltage_table(svs, low_temp_offset);
> +       mtk_svs_adjust_voltage(svs);
> +}
> +
> +static void mtk_svs_bank_disable(struct mtk_svs_bank *svs)
> +{
> +       struct mtk_thermal *mt = svs->mt;
> +       int i;
> +
> +       writel(0, mt->thermal_base + SVS_BANK_EN);
> +       writel(0xffffff, mt->thermal_base + SVS_BANK_INTST);
> +
> +       for (i = 0; i < MT8173_NUM_SVS_OPP; i++) {
> +               if (!svs->freq_table[i])
> +                       continue;
> +
> +               svs->updated_volt_table[i] = svs->volt_table[i];
> +       }
> +}
> +
> +static irqreturn_t mtk_svs_interrupt(int irqno, void *dev_id)
> +{
> +       struct mtk_thermal *mt = dev_id;
> +       unsigned long flags;
> +       u32 svs_intst, bank_en, bank_intst;
> +       int i;
> +
> +       spin_lock_irqsave(&mt->lock, flags);
> +
> +       svs_intst = readl(mt->thermal_base + SVS_SVSINTST);

Do you need to acknowledge/clear these interrupt status bits?

> +       for (i = 0; i < MT8173_NUM_SVS_BANKS; i++) {
> +               struct mtk_svs_bank *svs = &mt->svs_banks[i];
> +
> +               if ((svs_intst >> i) & 0x1)
> +                       continue;
> +
> +               mtk_thermal_switch_bank(&mt->banks[i]);
> +
> +               bank_intst = readl(mt->thermal_base + SVS_BANK_INTST);
> +               bank_en = readl(mt->thermal_base + SVS_BANK_EN);
> +
> +               if (bank_intst == PHASE_01_IRQ && /* phase 0 */
> +                   (bank_en & PHASE_EN_MASK) == PHASE_0_EN) {
> +                       u32 reg;
> +
> +                       reg = readl(mt->thermal_base + SVS_BANK_CONTROL1);
> +                       svs->ctrl0 |= (~(reg & 0xffff) + 1) & 0xffff;
> +                       reg =  readl(mt->thermal_base + SVS_BANK_CONTROL2);
> +                       svs->ctrl0 |= (reg & 0xffff) << 16;
> +
> +                       writel(0, mt->thermal_base + SVS_BANK_EN);
> +                       writel(PHASE_01_IRQ, mt->thermal_base + SVS_BANK_INTST);
> +
> +                       mtk_svs_set_phase(svs, SVS_PHASE_1);
> +               } else if (bank_intst == PHASE_01_IRQ && /* phase 1 */
> +                          (bank_en & PHASE_EN_MASK) == PHASE_01_EN) {
> +                       /*
> +                        * The cpufreq driver won't work during SVS
> +                        * initialization stage, so it's safe to update OPP
> +                        * table here since it won't trigger regulator
> +                        * operation.
> +                        */
> +                       mtk_svs_update_voltage_table(svs, 0);

Shouldn't this pass in the current temperature to apply the "low
temperature offset"?

> +                       mtk_svs_adjust_voltage(svs);
> +
> +                       writel(0, mt->thermal_base + SVS_BANK_EN);
> +                       writel(PHASE_01_IRQ, mt->thermal_base + SVS_BANK_INTST);
> +
> +                       complete(&svs->init_done);
> +
> +                       mtk_svs_set_phase(svs, SVS_PHASE_CONTINUOUS);
> +               } else if (bank_intst && PHASE_CON_IRQ) { /* phase continuous*/

What triggers PHASE_CON_IRQ interrupts?

> +                       /*
> +                        * Schedule a work to update voltages of OPP table
> +                        * entries.
> +                        */
> +                       schedule_work(&svs->work);
> +
> +                       writel(PHASE_CON_IRQ,
> +                              mt->thermal_base + SVS_BANK_INTST);
> +               } else {
> +                       mtk_svs_bank_disable(svs);
> +                       dev_err(mt->svs_banks[i].dev,
> +                               "SVS engine internal error. disabled.\n");
> +
> +                       /*
> +                        * Schedule a work to reset voltages of OPP table
> +                        * entries.
> +                        */
> +                       schedule_work(&svs->work);

You'll want to return IRQ_NONE in this case to notify the spurious irq detector.

> +               }
> +       }
> +
> +       spin_unlock_irqrestore(&mt->lock, flags);
> +
> +       return IRQ_HANDLED;
> +}

Nothing in this interrupt handler looks very time critical.
Can we use a threaded interrupt instead, and continue to use a mutex
for mt->lock.
We could also then just call adjust_voltage_work() directly rather
than indirectly through the work.

> +
> +static int mtk_svs_bank_init(struct mtk_svs_bank *svs)
> +{
> +       struct dev_pm_opp *opp;
> +       cpumask_t cpus;
> +       int ret = 0, count, i;
> +       unsigned long rate;
> +
> +       init_completion(&svs->init_done);
> +
> +       INIT_WORK(&svs->work, adjust_voltage_work);
> +
> +       svs->dev = get_cpu_device(svs_bank_cfgs[svs->bank_id].dev_id);
> +       if (!svs->dev) {
> +               pr_err("failed to get cpu%d device\n",
> +                      svs_bank_cfgs[svs->bank_id].dev_id);
> +               return -ENODEV;
> +       }
> +
> +       svs->reg = regulator_get_exclusive(svs->dev, "proc");
> +       if (IS_ERR(svs->reg)) {
> +               dev_err(svs->dev, "failed to get proc regulator\n");
> +               return PTR_ERR(svs->reg);
> +       }
> +
> +       ret = dev_pm_opp_of_get_sharing_cpus(svs->dev, &cpus);
> +       if (ret) {
> +               dev_err(svs->dev, "failed to get OPP-sharing information\n");
> +               return ret;
> +       }
> +
> +       ret = dev_pm_opp_of_cpumask_add_table(&cpus);
> +       if (ret) {
> +               dev_err(svs->dev, "no OPP table\n");
> +               return ret;
> +       }
> +
> +       rcu_read_lock();
> +       count = dev_pm_opp_get_opp_count(svs->dev);
> +       if (count > MT8173_NUM_SVS_OPP)
> +               dev_warn(svs->dev, "%d OPP entries found.\n"
> +                        "But only %d OPP entry supported.\n", count,
> +                        MT8173_NUM_SVS_OPP);
> +
> +       for (i = 0, rate = (unsigned long)-1; i < MT8173_NUM_SVS_OPP &&
> +            i < count; i++, rate--) {
> +               opp = dev_pm_opp_find_freq_floor(svs->dev, &rate);
> +               if (IS_ERR(opp)) {
> +                       dev_err(svs->dev, "error opp entry!!\n");
> +                       rcu_read_unlock();
> +                       ret = PTR_ERR(opp);
> +                       goto out;
> +               }
> +
> +               svs->freq_table[i] = rate / 1000;
> +               svs->volt_table[i] = dev_pm_opp_get_voltage(opp) / 1000;

Why / 1000?
Just save the original values.  This will keep us from having to * 1000 later.

> +       }
> +
> +out:
> +       rcu_read_unlock();
> +       if (ret) {
> +               regulator_put(svs->reg);
> +               dev_pm_opp_of_cpumask_remove_table(&cpus);
> +       }
> +
> +       return ret;
> +}
> +
> +static void mtk_svs_release(struct mtk_thermal *mt)
> +{
> +       int i, ret;
> +       cpumask_t cpus;
> +
> +       if (!IS_ERR(mt->svs_pll))
> +               clk_put(mt->svs_pll);

devm_clk_put(), devm_free_irq() & devm_regulator_put in this function.

> +
> +       if (!IS_ERR(mt->svs_mux))
> +               clk_put(mt->svs_mux);
> +
> +       if (mt->svs_irq != -1)
> +               free_irq(mt->svs_irq, mt);
> +
> +       for (i = 0; i < MT8173_NUM_SVS_BANKS; i++) {
> +               struct mtk_svs_bank *svs = &mt->svs_banks[i];
> +
> +               ret = dev_pm_opp_of_get_sharing_cpus(svs->dev, &cpus);
> +               if (!ret)
> +                       dev_pm_opp_of_cpumask_remove_table(&cpus);
> +
> +               if (svs->reg)
> +                       regulator_put(svs->reg);
> +       }
> +}
> +
> +static int mtk_svs_init(struct platform_device *pdev, struct mtk_thermal *mt)
> +{
> +       struct clk *parent;
> +       int i, ret, vboot;
> +       unsigned long flags, timeout;
> +       struct mtk_svs_bank *svs;
> +
> +       parent = clk_get_parent(mt->svs_mux);
> +       ret = clk_set_parent(mt->svs_mux, mt->svs_pll);
> +       if (ret) {
> +               dev_err(&pdev->dev,
> +                       "failed to set svs_mux to svs_pll\n");
> +               return ret;
> +       }
> +
> +       for (i = 0; i < MT8173_NUM_SVS_BANKS; i++) {
> +               svs = &mt->svs_banks[i];
> +

Why do you do this boot voltage check?
Doesn't this depend on the specific OPP used by firmware, which is
outside the control of the kernel?

> +               vboot = regulator_get_voltage(svs->reg) / 1000;
> +               if (mvolt_to_config(vboot) !=
> +                   mvolt_to_config(svs_bank_cfgs[i].vboot)) {
> +                       dev_err(svs->dev, "Vboot value mismatch!\n");
> +                       ret = -EINVAL;
> +                       break;
> +               }
> +
> +               ret = regulator_set_mode(svs->reg, REGULATOR_MODE_FAST);
> +               if (ret) {
> +                       dev_err(svs->dev,
> +                               "Failed to set regulator in PWM mode\n");
> +                       ret = -EINVAL;
> +                       break;
> +               }

Shouldn't this be best effort?
If we can't set the regulator to fast mode, can we continue anyway?

> +
> +               spin_lock_irqsave(&mt->lock, flags);
> +               mtk_thermal_switch_bank(&mt->banks[i]);
> +
> +               mtk_svs_set_phase(svs, SVS_PHASE_0);
> +
> +               spin_unlock_irqrestore(&mt->lock, flags);
> +
> +               timeout = wait_for_completion_timeout(&svs->init_done, HZ);
> +               if (timeout == 0) {
> +                       dev_err(svs->dev, "SVS initialization timeout.\n");
> +                       ret = -EINVAL;
> +                       goto err_bank_init;
> +               }
> +
> +               ret = regulator_set_mode(svs->reg, REGULATOR_MODE_NORMAL);
> +               if (ret)
> +                       dev_err(svs->dev,
> +                               "Failed to set regulator in normal mode\n");
> +
> +               regulator_put(svs->reg);
> +               svs->reg = NULL;
> +       }
> +
> +err_bank_init:
> +
> +       if (ret)
> +               for (i = 0; i < MT8173_NUM_SVS_BANKS; i++) {
> +                       svs = &mt->svs_banks[i];
> +
> +                       spin_lock_irqsave(&mt->lock, flags);
> +                       mtk_thermal_switch_bank(&mt->banks[i]);
> +
> +                       mtk_svs_bank_disable(svs);
> +
> +                       spin_unlock_irqrestore(&mt->lock, flags);
> +
> +                       mtk_svs_adjust_voltage(svs);
> +               }
> +
> +       ret = clk_set_parent(mt->svs_mux, parent);
> +       if (ret) {
> +               dev_err(&pdev->dev,
> +                       "failed to set svs_mux to original parent\n");
> +               return ret;
> +       }
> +
> +       return ret;
> +}
> +
>  static int mtk_thermal_probe(struct platform_device *pdev)
>  {
>         int ret, i;
>         struct device_node *auxadc, *apmixedsys, *np = pdev->dev.of_node;
>         struct mtk_thermal *mt;
>         struct resource *res;
> +       struct platform_device *pdev_ret;
>         u64 auxadc_phys_base, apmixed_phys_base;
> +       bool skip_svs_init = false;
>
>         mt = devm_kzalloc(&pdev->dev, sizeof(*mt), GFP_KERNEL);
>         if (!mt)
> @@ -493,6 +1132,23 @@ static int mtk_thermal_probe(struct platform_device *pdev)
>         if (IS_ERR(mt->clk_auxadc))
>                 return PTR_ERR(mt->clk_auxadc);
>
> +       /*
> +        * These two clks are optional for mtk-thermal. If present, SVS engine
> +        * will be activated.
> +        */
> +       mt->svs_pll = devm_clk_get(&pdev->dev, "svs_pll");
> +       mt->svs_mux = devm_clk_get(&pdev->dev, "svs_mux");
> +       if (IS_ERR(mt->svs_pll) || IS_ERR(mt->svs_mux))
> +               skip_svs_init = true;
> +
> +       mt->svs_irq = platform_get_irq(pdev, 1);
> +       ret = devm_request_irq(&pdev->dev, mt->svs_irq, mtk_svs_interrupt,
> +                              IRQF_TRIGGER_LOW, "mtk-svs", mt);
> +       if (ret) {
> +               mt->svs_irq = -1;
> +               skip_svs_init = true;
> +       }
> +
>         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>         mt->thermal_base = devm_ioremap_resource(&pdev->dev, res);
>         if (IS_ERR(mt->thermal_base))
> @@ -502,6 +1158,27 @@ static int mtk_thermal_probe(struct platform_device *pdev)
>         if (ret)
>                 return ret;
>
> +       ret = mtk_svs_get_calibration_data(&pdev->dev, mt);
> +       if (ret == -EPROBE_DEFER)
> +               return ret;
> +       else if (ret)
> +               skip_svs_init = true;
> +
> +       for (i = 0; i < MT8173_NUM_SVS_BANKS; i++) {
> +               mt->svs_banks[i].bank_id = i;
> +               mt->svs_banks[i].mt = mt;
> +
> +               ret = mtk_svs_bank_init(&mt->svs_banks[i]);
> +               if (ret) {
> +                       if (ret == -EPROBE_DEFER)
> +                               return ret;
> +
> +                       dev_err(&pdev->dev,
> +                               "failed to initialize mtk svs bank%d\n", i);
> +                       skip_svs_init = true;
> +               }
> +       }
> +
>         spin_lock_init(&mt->lock);
>
>         mt->dev = &pdev->dev;
> @@ -562,6 +1239,33 @@ static int mtk_thermal_probe(struct platform_device *pdev)
>         if (IS_ERR(mt->tzd))
>                 goto err_register;
>
> +       /*
> +        * SVS engine needs to be initialized after mtk-thermal initialization
> +        * is done and before the initialization of cpufreq driver.
> +        */
> +       if (!skip_svs_init) {
> +               ret = mtk_svs_init(pdev, mt);
> +               if (ret) {
> +                       /* Keep running system with SVS engine disabled. */
> +                       dev_err(&pdev->dev,
> +                               "Failed to initialze MTK SVS engine\n");
> +                       skip_svs_init = true;
> +               }
> +       }
> +
> +       /*
> +        * Release all resources needed by SVS if not all required resources
> +        * are present or SVS initialization failed.
> +        */
> +       if (skip_svs_init)
> +               mtk_svs_release(mt);
> +
> +       pdev_ret = platform_device_register_simple("mt8173-cpufreq", -1,
> +                                                  NULL, 0);
> +       if (IS_ERR(pdev_ret))
> +               dev_err(&pdev->dev,
> +                       "failed to register mt8173-cpufreq device\n");
> +
>         return 0;
>
>  err_register:
> --
> 1.9.1
>
>
> _______________________________________________
> Linux-mediatek mailing list
> Linux-mediatek at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-mediatek



More information about the linux-arm-kernel mailing list