Thread (12 messages) 12 messages, 5 authors, 2016-02-19

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

From: Daniel Kurtz <hidden>
Date: 2016-01-22 23:38:06
Also in: linux-devicetree, linux-mediatek, linux-pm

Hi Pi-Cheng,

Thanks for the patch!

Some review comments inline...

On Fri, Jan 22, 2016 at 12:40 AM, Pi-Cheng Chen
[off-list ref] 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
quoted hunk ↗ jump to hunk
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 <redacted>

Signed-off-by: Pi-Cheng Chen <redacted>
---
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.
quoted hunk ↗ jump to hunk
+#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.
quoted hunk ↗ jump to hunk
        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.
quoted hunk ↗ jump to hunk
+       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?
quoted hunk ↗ jump to hunk
+
+               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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help