[PATCH 2/2] thermal: exynos: Add support for ARTPEC-8

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add support thermal management for Axis ARTPEC-8 SoC.
ARTPEC-8 is the SoC platform of Axis Communications.
In the existing thermal management function of exynos, functions that support
remote sensors have been added.
 
Signed-off-by: sangmin kim <hypmean.kim@xxxxxxxxxxx>
---
 drivers/thermal/samsung/exynos_tmu.c | 666 ++++++++++++++++++++++++++++++++---
 1 file changed, 616 insertions(+), 50 deletions(-)
 
diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c
index f4ab4c5..9837f42 100644
--- a/drivers/thermal/samsung/exynos_tmu.c
+++ b/drivers/thermal/samsung/exynos_tmu.c
@@ -14,6 +14,7 @@
 #include <linux/clk.h>
 #include <linux/io.h>
 #include <linux/interrupt.h>
+#include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/of_address.h>
@@ -124,6 +125,77 @@
 
 #define MCELSIUS        1000
 
+/* Artpec8 specific registers */
+#define ARTPEC8_TMU_REG_TRIMINFO        0x0
+#define ARTPEC8_TMU_REG_TRIMINFO1        0x4
+#define ARTPEC8_TMU_REG_TRIMINFO2        0x8
+#define ARTPEC8_TMU_REG_TRIMINFO3        0xC
+#define ARTPEC8_TMU_REG_TRIMINFO4        0x10
+#define ARTPEC8_TMU_REG_TRIMINFO5        0x14
+#define ARTPEC8_TMU_REG_CONTROL                0x20
+#define ARTPEC8_TMU_REG_CONTROL1        0x24
+#define ARTPEC8_TMU_REG_STATUS                0x28
+
+#define ARTPEC8_TMU_REG_AVG_CONTROL        0x38
+#define ARTPEC8_TMU_REG_TMU_TRIM0        0x3C
+
+#define ARTPEC8_TMU_REG_EMUL_CON        0x160
+#define NUM_PROBE_OFFSET                16
+
+#define ARTPEC8_FIRST_POINT_TRIM        25
+#define ARTPEC8_SECOND_POINT_TRIM        105
+
+#define ARTPEC8_EMUL_EN                        1
+#define ARTPEC8_TIME_OFFSET                16
+#define ARTPEC8_EMUL_NEXT_TIME                (0x4e20 << ARTPEC8_TIME_OFFSET)
+
+#define ARTPEC8_TMU_TEMP_MASK                0x1ff
+#define ARTPEC8_CALIB_SEL_SHIFT                23
+
+#define ARTPEC8_EMUL_DATA_SHIFT                7
+
+#define ARTPEC8_T_BUF_VREF_SEL_SHIFT        18
+#define ARTPEC8_T_BUF_SLOPE_SEL_SHIFT        18
+#define ARTPEC8_INTEN_TRIPPING_SHIFT        7
+#define ARTPEC8_INTEN_CLOCKDOWN_SHIFT        8
+#define ARTPEC8_TRIMINFO_105_SHIFT        9
+#define ARTPEC8_INTEN_FALL0_SHIFT        16
+#define ARTPEC8_TMU_REF_VOLTAGE_SHIFT        24
+#define ARTPEC8_TMU_REF_VOLTAGE_MASK        0x1f
+#define ARTPEC8_TMU_BUF_SLOPE_SEL_SHIFT        8
+#define ARTPEC8_TMU_BUF_SLOPE_SEL_MASK        0xf
+
+#define ARTPEC8_TMU_CONTROL_CORE_EN        1
+#define ARTPEC8_TMU_CONTROL_AUTO_MODE        2
+#define ARTPEC8_TMU_CONTROL_TRIP_EN        (1 << 12)
+#define ARTPEC8_LPI_MODE_EN                (1 << 10)
+
+#define ARTPEC8_TRIM0_BGR_I_SHIFT        20
+#define ARTPEC8_TRIM0_VREF_SHIFT        12
+#define ARTPEC8_TRIM0_VBE_I_SHIFT        8
+
+#define INTPEND_RISE_MASK                0xff
+#define INTPEND_FALL_MASK                0xff0000
+#define ARTPEC8_TRIM0_MASK                0xf
+#define ARTPEC8_TRIM2_MASK                0x7
+
+#define ARTPEC8_TRIMINFO_TRIM0_SHIFT        18
+
+#define LOW_TEMP_WEIGHT                        9203
+#define HIGH_TEMP_WEIGHT                9745
+#define TEMP_WEIGHT                        10000
+
+struct sensor_offset {
+        u32 trim_offset;
+        u32 temp_offset;
+        u32 temp_reg_shift;
+        u32 rise_offset;
+        u32 fall_offset;
+        u32 past_offset;
+        u32 inten;
+        u32 intpend;
+};
+
 enum soc_type {
         SOC_ARCH_EXYNOS3250 = 1,
         SOC_ARCH_EXYNOS4210,
@@ -134,6 +206,63 @@ enum soc_type {
         SOC_ARCH_EXYNOS5420_TRIMINFO,
         SOC_ARCH_EXYNOS5433,
         SOC_ARCH_EXYNOS7,
+        SOC_ARCH_ARTPEC8,
+};
+
+#define SENSOR(_tr, _te, _sh, _ri, _fa, _pa, _en, _pend)        \
+        {                                        \
+                .trim_offset        = _tr,                \
+                .temp_offset        = _te,                \
+                .temp_reg_shift        = _sh,                \
+                .rise_offset        = _ri,                \
+                .fall_offset        = _fa,                \
+                .past_offset        = _pa,                \
+                .inten                = _en,                \
+                .intpend        = _pend,                \
+        }
+
+static const struct sensor_offset artpec8_sensors[] = {
+        SENSOR(0x0,        0x40,        0,  0x50,        0x60,        0x70,        0x110,        0x118),
+        SENSOR(0x4,        0x40,        9,  0x170,        0x180,        0x90,        0x120,        0x128),
+        SENSOR(0x8,        0x44,        0,  0x190,        0x1a0,        0xb0,        0x130,        0x138),
+        SENSOR(0xc,        0x44,        9,  0x1b0,        0x1c0,        0xd0,        0x140,        0x148),
+        SENSOR(0x10,        0x44,        18, 0x1d0,        0x1e0,        0xf0,        0x150,        0x158),
+        SENSOR(0x14,        0x48,        0,  0x1f0,        0x200,        0x250,        0x310,        0x318),
+};
+
+/**
+ * struct artpec8_sensor: A structure to hold the private data of the sensor
+ * @tmudev: The tmu device which this sensor is connected.
+ * @tzd: Thermal zonde device pointer to register this sensor.
+ * @id: Identifier of the one instance of the thermal sensor.
+ * @ntrip: Number of threshols for this sensor.
+ * @triminfo_25: OTP information to trim temperature sensor error for 25C
+ * @triminfo_105: OTP information to trim temperature sensor error for 105C
+ * @trim_offset: Offset of triminfo register.
+ * @temp_offset: Offset of current temperature. The temperature values of
+ *                2 to 3 remote sensors are stored in this register.
+ * @temp_reg_shift: start location of each tempt in temp_off
+ * @rise_offset: Offset of rising threshold level 6 and 7.
+ * @fall_offset: Offset of falling thershold level 6 and 7.
+ * @past_offset: Offset of Past temperature 0,1.
+ * @inten: Offset of interrupt enable sfr.
+ * @intpend: Offset of interrupt pending sfr.
+ */
+struct artpec8_sensor {
+        struct exynos_tmu_data *tmudev;
+        struct thermal_zone_device *tzd;
+        int id;
+        unsigned int ntrip;
+        u16 triminfo_25;
+        u16 triminfo_105;
+        u32 trim_offset;
+        u32 temp_offset;
+        u32 temp_reg_shift;
+        u32 rise_offset;
+        u32 fall_offset;
+        u32 past_offset;
+        u32 inten;
+        u32 intpend;
 };
 
 /**
@@ -193,6 +322,7 @@ struct exynos_tmu_data {
         struct thermal_zone_device *tzd;
         unsigned int ntrip;
         bool enabled;
+        u32 nr_remote;
 
         void (*tmu_set_trip_temp)(struct exynos_tmu_data *data, int trip,
                                  u8 temp);
@@ -203,6 +333,8 @@ struct exynos_tmu_data {
         int (*tmu_read)(struct exynos_tmu_data *data);
         void (*tmu_set_emulation)(struct exynos_tmu_data *data, int temp);
         void (*tmu_clear_irqs)(struct exynos_tmu_data *data);
+
+        struct artpec8_sensor sensor[0];
 };
 
 /*
@@ -220,6 +352,28 @@ static int temp_to_code(struct exynos_tmu_data *data, u8 temp)
                 data->temp_error1;
 }
 
+static u16 artpec8_temp_to_code(struct artpec8_sensor *sensor, int temp)
+{
+        int code;
+        int weight;
+
+        if (sensor->tmudev->cal_type == TYPE_ONE_POINT_TRIMMING)
+                return temp + sensor->triminfo_25 - ARTPEC8_FIRST_POINT_TRIM;
+
+        if (temp > ARTPEC8_FIRST_POINT_TRIM)
+                weight = HIGH_TEMP_WEIGHT;
+        else
+                weight = LOW_TEMP_WEIGHT;
+
+        code = DIV_ROUND_CLOSEST((temp - ARTPEC8_FIRST_POINT_TRIM) *
+                (sensor->triminfo_105 - sensor->triminfo_25) * TEMP_WEIGHT,
+                (ARTPEC8_SECOND_POINT_TRIM - ARTPEC8_FIRST_POINT_TRIM) *
+                weight);
+        code += sensor->triminfo_25;
+
+        return (u16)code;
+}
+
 /*
  * Calculate a temperature value from a temperature code.
  * The unit of the temperature is degree Celsius.
@@ -235,6 +389,27 @@ static int code_to_temp(struct exynos_tmu_data *data, u16 temp_code)
                 EXYNOS_FIRST_POINT_TRIM;
 }
 
+static int artpec8_code_to_temp(struct artpec8_sensor *sensor, u16 code)
+{
+        int temp;
+        int weight;
+
+        if (sensor->tmudev->cal_type == TYPE_ONE_POINT_TRIMMING)
+                return code - sensor->triminfo_25 + ARTPEC8_FIRST_POINT_TRIM;
+
+        if (code > sensor->triminfo_25)
+                weight = HIGH_TEMP_WEIGHT;
+        else
+                weight = LOW_TEMP_WEIGHT;
+
+        temp = DIV_ROUND_CLOSEST((code - sensor->triminfo_25) *
+                (ARTPEC8_SECOND_POINT_TRIM - ARTPEC8_FIRST_POINT_TRIM) * weight,
+                (sensor->triminfo_105 - sensor->triminfo_25) * TEMP_WEIGHT);
+        temp += ARTPEC8_FIRST_POINT_TRIM;
+
+        return temp;
+}
+
 static void sanitize_temp_error(struct exynos_tmu_data *data, u32 trim_info)
 {
         u16 tmu_temp_mask =
@@ -338,7 +513,8 @@ static u32 get_con_reg(struct exynos_tmu_data *data, u32 con)
         con &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK << EXYNOS_TMU_REF_VOLTAGE_SHIFT);
         con |= data->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT;
 
-        con &= ~(EXYNOS_TMU_BUF_SLOPE_SEL_MASK << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT);
+        con &= ~(EXYNOS_TMU_BUF_SLOPE_SEL_MASK <<
+                        EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT);
         con |= (data->gain << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT);
 
         con &= ~(EXYNOS_TMU_TRIP_MODE_MASK << EXYNOS_TMU_TRIP_MODE_SHIFT);
@@ -558,6 +734,120 @@ static void exynos7_tmu_initialize(struct platform_device *pdev)
         sanitize_temp_error(data, trim_info);
 }
 
+static void artpec8_tmu_set_trip_temp(struct exynos_tmu_data *data,
+                int trip, int temp, int remote)
+{
+        unsigned int reg_off, bit_off;
+        u32 th;
+        struct artpec8_sensor *sensor;
+        unsigned int temp_rise;
+
+        sensor = &data->sensor[remote];
+        temp_rise = sensor->rise_offset;
+
+        reg_off = ((7 - trip) / 2) * 4;
+        bit_off = ((8 - trip) % 2);
+
+        th = readl(data->base + temp_rise + reg_off);
+        th &= ~(ARTPEC8_TMU_TEMP_MASK << (16 * bit_off));
+        th |= artpec8_temp_to_code(sensor, temp) << (16 * bit_off);
+        writel(th, data->base + temp_rise + reg_off);
+}
+
+static void artpec8_tmu_set_trip_hyst(struct exynos_tmu_data *data,
+                int trip, int temp, int hyst, int remote)
+{
+        unsigned int reg_off, bit_off;
+        u32 th;
+        struct artpec8_sensor *sensor;
+        unsigned int temp_fall;
+
+        sensor = &data->sensor[remote];
+        temp_fall = sensor->fall_offset;
+
+        reg_off = ((7 - trip) / 2) * 4;
+        bit_off = ((8 - trip) % 2);
+
+        th = readl(data->base + temp_fall + reg_off);
+        th &= ~(ARTPEC8_TMU_TEMP_MASK << (16 * bit_off));
+        th |= artpec8_temp_to_code(sensor, temp - hyst) << (16 * bit_off);
+        writel(th, data->base + temp_fall + reg_off);
+}
+
+static void artpec8_tmu_clear_irqs(struct exynos_tmu_data *data, int i)
+{
+        u32 intp = readl(data->base + data->sensor[i].intpend);
+
+        writel(intp, data->base + data->sensor[i].intpend);
+}
+
+static int artpec8_sensor_initialize(struct exynos_tmu_data *data,
+                int sensor_idx)
+{
+        struct thermal_zone_device *tzd;
+        struct artpec8_sensor *sensor;
+        int ret = 0, trip, temp, hyst;
+
+        sensor = &data->sensor[sensor_idx];
+        if (!sensor)
+                return -EINVAL;
+
+        tzd = sensor->tzd;
+        for (trip = 0; trip < sensor->ntrip; trip++) {
+                ret = tzd->ops->get_trip_temp(tzd, trip, &temp);
+                if (ret)
+                        break;
+
+                temp /= MCELSIUS;
+                artpec8_tmu_set_trip_temp(data, trip, temp, sensor_idx);
+
+                ret = tzd->ops->get_trip_hyst(tzd, trip, &hyst);
+                if (ret)
+                        break;
+
+                hyst /= MCELSIUS;
+                artpec8_tmu_set_trip_hyst(data, trip, temp, hyst, sensor_idx);
+        }
+        artpec8_tmu_clear_irqs(data, sensor_idx);
+
+        return ret;
+}
+
+static void artpec8_tmu_initialize(struct platform_device *pdev)
+{
+        struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+        int ret = 0, sensor_idx;
+
+        mutex_lock(&data->lock);
+
+        for (sensor_idx = 0; sensor_idx < data->nr_remote; sensor_idx++) {
+                if (!readb(data->base + ARTPEC8_TMU_REG_STATUS))
+                        break;
+
+                ret = artpec8_sensor_initialize(data, sensor_idx);
+                if (ret)
+                        break;
+        }
+
+        mutex_unlock(&data->lock);
+}
+
+static void artpec8_enable_interrupt(struct exynos_tmu_data *data,
+                int sensor_idx)
+{
+        int i;
+        unsigned int interrupt_en = 0;
+        struct thermal_zone_device *tz = data->sensor[sensor_idx].tzd;
+
+        for (i = 0; i < data->sensor->ntrip; i++) {
+                if (!of_thermal_is_trip_valid(tz, i))
+                        continue;
+                interrupt_en |= (1 << i);
+        }
+        writel(interrupt_en, data->base + data->sensor[sensor_idx].inten);
+}
+
+
 static void exynos4210_tmu_control(struct platform_device *pdev, bool on)
 {
         struct exynos_tmu_data *data = platform_get_drvdata(pdev);
@@ -650,6 +940,62 @@ static void exynos7_tmu_control(struct platform_device *pdev, bool on)
         writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
 }
 
+static void artpec8_tmu_control(struct platform_device *pdev, bool on)
+{
+        struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+        unsigned int con, con1, i;
+        unsigned int vref;
+        unsigned int slope;
+        unsigned int trim0, trim0_bgr, trim0_vref, trim0_vbe, avg_mode;
+
+        vref = (readl(data->base + ARTPEC8_TMU_REG_TRIMINFO) >>
+                ARTPEC8_T_BUF_VREF_SEL_SHIFT) &
+                ARTPEC8_TMU_REF_VOLTAGE_MASK;
+        slope = (readl(data->base + ARTPEC8_TMU_REG_TRIMINFO1) >>
+                ARTPEC8_T_BUF_SLOPE_SEL_SHIFT) &
+                ARTPEC8_TMU_BUF_SLOPE_SEL_MASK;
+        con = (vref << ARTPEC8_TMU_REF_VOLTAGE_SHIFT) |
+                (slope << ARTPEC8_TMU_BUF_SLOPE_SEL_SHIFT);
+
+        if (on) {
+                for (i = 0; i < data->nr_remote; i++)
+                        artpec8_enable_interrupt(data, i);
+                con |= (ARTPEC8_TMU_CONTROL_CORE_EN |
+                        ARTPEC8_TMU_CONTROL_AUTO_MODE);
+        } else {
+                con &= ~(ARTPEC8_TMU_CONTROL_CORE_EN);
+        }
+
+        trim0_bgr = readl(data->base + ARTPEC8_TMU_REG_TRIMINFO3) >>
+                ARTPEC8_TRIMINFO_TRIM0_SHIFT;
+        trim0_bgr &= ARTPEC8_TRIM0_MASK;
+        trim0_vref = readl(data->base + ARTPEC8_TMU_REG_TRIMINFO4) >>
+                ARTPEC8_TRIMINFO_TRIM0_SHIFT;
+        trim0_vref &= ARTPEC8_TRIM0_MASK;
+        trim0_vbe = readl(data->base + ARTPEC8_TMU_REG_TRIMINFO5) >>
+                ARTPEC8_TRIMINFO_TRIM0_SHIFT;
+        trim0_vbe &= ARTPEC8_TRIM0_MASK;
+        trim0 = trim0_bgr << ARTPEC8_TRIM0_BGR_I_SHIFT |
+                trim0_vref << ARTPEC8_TRIM0_VREF_SHIFT |
+                trim0_vbe << ARTPEC8_TRIM0_VBE_I_SHIFT;
+
+        con1 = (data->nr_remote << NUM_PROBE_OFFSET) | ARTPEC8_LPI_MODE_EN;
+
+        while (!readb(data->base + ARTPEC8_TMU_REG_STATUS))
+                pr_debug("TMU busy waiting\n");
+
+
+        avg_mode = readl(data->base + ARTPEC8_TMU_REG_AVG_CONTROL);
+        avg_mode &= ~ARTPEC8_TRIM2_MASK;
+        avg_mode |= (readl(data->base + ARTPEC8_TMU_REG_TRIMINFO2) >>
+                ARTPEC8_TRIMINFO_TRIM0_SHIFT) & ARTPEC8_TRIM2_MASK;
+
+        writel(avg_mode, data->base + ARTPEC8_TMU_REG_AVG_CONTROL);
+        writel(trim0, data->base + ARTPEC8_TMU_REG_TMU_TRIM0);
+        writel(con1, data->base + ARTPEC8_TMU_REG_CONTROL1);
+        writel(con, data->base + ARTPEC8_TMU_REG_CONTROL);
+}
+
 static int exynos_get_temp(void *p, int *temp)
 {
         struct exynos_tmu_data *data = p;
@@ -679,6 +1025,37 @@ static int exynos_get_temp(void *p, int *temp)
         return ret;
 }
 
+static int artpec8_get_temp(void *p, int *temp)
+{
+        struct artpec8_sensor *sensor = p;
+        struct exynos_tmu_data *data;
+        bool enabled, valid;
+        u16 value;
+        int ret = 0;
+
+        if (!sensor)
+                return -EINVAL;
+
+        data = sensor->tmudev;
+        if (!data)
+                return -EINVAL;
+
+        enabled = readl(data->base + ARTPEC8_TMU_REG_CONTROL) & 0x1;
+        valid = readl(data->base + ARTPEC8_TMU_REG_STATUS) & 0xf0;
+        if (!enabled || !valid)
+                return -EAGAIN;
+
+        mutex_lock(&data->lock);
+
+        value = (readl(data->base + sensor->temp_offset) >>
+                sensor->temp_reg_shift) & ARTPEC8_TMU_TEMP_MASK;
+        *temp = artpec8_code_to_temp(sensor, value) * MCELSIUS;
+
+        mutex_unlock(&data->lock);
+
+        return ret;
+}
+
 #ifdef CONFIG_THERMAL_EMULATION
 static u32 get_emul_con_reg(struct exynos_tmu_data *data, unsigned int val,
                             int temp)
@@ -748,10 +1125,32 @@ static int exynos_tmu_set_emulation(void *drv_data, int temp)
 out:
         return ret;
 }
+
+static int artpec8_tmu_set_emulation(void *sensor_data, int temp)
+{
+        struct artpec8_sensor *sensor = sensor_data;
+        struct exynos_tmu_data *data = sensor->tmudev;
+        u32 temp_code, econ;
+
+        temp /= MCELSIUS;
+
+        mutex_lock(&data->lock);
+
+        temp_code = artpec8_temp_to_code(sensor, temp);
+        econ = ARTPEC8_EMUL_NEXT_TIME | temp_code << ARTPEC8_EMUL_DATA_SHIFT |
+                ARTPEC8_EMUL_EN;
+        writel(econ, data->base + ARTPEC8_TMU_REG_EMUL_CON);
+
+        mutex_unlock(&data->lock);
+
+        return 0;
+}
 #else
 #define exynos4412_tmu_set_emulation NULL
 static int exynos_tmu_set_emulation(void *drv_data, int temp)
         { return -EINVAL; }
+static int artpec8_tmu_set_emulation(void *sensor_data, int temp)
+        { return -EINVAL; }
 #endif /* CONFIG_THERMAL_EMULATION */
 
 static int exynos4210_tmu_read(struct exynos_tmu_data *data)
@@ -791,6 +1190,45 @@ static void exynos_tmu_work(struct work_struct *work)
         enable_irq(data->irq);
 }
 
+static void artpec8_tmu_work(struct work_struct *work)
+{
+        int i;
+        u32 inten, intpend, rise, fall;
+        struct artpec8_sensor *sensor;
+        struct exynos_tmu_data *data = container_of(work,
+                        struct exynos_tmu_data, irq_work);
+
+        for (i = 0; i < data->nr_remote; i++)
+                thermal_zone_device_update(data->sensor[i].tzd,
+                                THERMAL_EVENT_UNSPECIFIED);
+
+        mutex_lock(&data->lock);
+        for (i = 0; i < data->nr_remote; i++) {
+                sensor = &data->sensor[i];
+                intpend = readl(data->base + sensor->intpend);
+
+                if (intpend) {
+                        fall = intpend & INTPEND_FALL_MASK;
+                        rise = intpend & INTPEND_RISE_MASK;
+
+                        if (fall) {
+                                inten = readl(data->base + sensor->inten) & (~fall);
+                                inten |= fall >> ARTPEC8_INTEN_FALL0_SHIFT;
+                                writel(inten, data->base + sensor->inten);
+                        }
+
+                        if (rise) {
+                                inten = readl(data->base + sensor->inten) & (~rise);
+                                inten |= (rise << ARTPEC8_INTEN_FALL0_SHIFT) |
+                                        min_t(u32, rise << 1, BIT(sensor->ntrip - 1)) | (rise >> 1);
+                                writel(inten, data->base + sensor->inten);
+                        }
+                        writel(intpend, data->base + sensor->intpend);
+                }
+        }
+        mutex_unlock(&data->lock);
+}
+
 static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data)
 {
         unsigned int val_irq;
@@ -860,6 +1298,9 @@ static const struct of_device_id exynos_tmu_match[] = {
         }, {
                 .compatible = "samsung,exynos7-tmu",
                 .data = (const void *)SOC_ARCH_EXYNOS7,
+        }, {
+                .compatible = "axis,artpec8-tmu",
+                .data = (const void *)SOC_ARCH_ARTPEC8,
         },
         { },
 };
@@ -895,6 +1336,7 @@ static int exynos_map_dt_data(struct platform_device *pdev)
         }
 
         data->soc = (enum soc_type)of_device_get_match_data(&pdev->dev);
+        data->cal_type = TYPE_ONE_POINT_TRIMMING;
 
         switch (data->soc) {
         case SOC_ARCH_EXYNOS4210:
@@ -968,13 +1410,19 @@ static int exynos_map_dt_data(struct platform_device *pdev)
                 data->min_efuse_value = 15;
                 data->max_efuse_value = 100;
                 break;
+        case SOC_ARCH_ARTPEC8:
+                data->tmu_initialize = artpec8_tmu_initialize;
+                data->tmu_control = artpec8_tmu_control;
+                data->cal_type = readl(data->base + ARTPEC8_TMU_REG_TRIMINFO) >>
+                        ARTPEC8_CALIB_SEL_SHIFT;
+                of_property_read_u32(pdev->dev.of_node, "remote_sensors",
+                                &data->nr_remote);
+                break;
         default:
                 dev_err(&pdev->dev, "Platform not supported\n");
                 return -EINVAL;
         }
 
-        data->cal_type = TYPE_ONE_POINT_TRIMMING;
-
         /*
          * Check if the TMU shares some registers and then try to map the
          * memory of common registers.
@@ -1002,42 +1450,138 @@ static const struct thermal_zone_of_device_ops exynos_sensor_ops = {
         .set_emul_temp = exynos_tmu_set_emulation,
 };
 
+static const struct thermal_zone_of_device_ops artpec8_ops = {
+        .get_temp = artpec8_get_temp,
+        .set_emul_temp = artpec8_tmu_set_emulation,
+};
+
+static int artpec8_map_sensor_data(struct exynos_tmu_data *data,
+                int sensor_idx, struct artpec8_sensor *sensor)
+{
+        int id = sensor_idx;
+
+        sensor->id = id;
+        sensor->tmudev = data;
+        sensor->trim_offset = artpec8_sensors[id].trim_offset;
+        sensor->temp_offset = artpec8_sensors[id].temp_offset;
+        sensor->temp_reg_shift = artpec8_sensors[id].temp_reg_shift;
+        sensor->rise_offset = artpec8_sensors[id].rise_offset;
+        sensor->fall_offset = artpec8_sensors[id].fall_offset;
+        sensor->past_offset = artpec8_sensors[id].past_offset;
+        sensor->inten = artpec8_sensors[id].inten;
+        sensor->intpend = artpec8_sensors[id].intpend;
+        sensor->triminfo_25 = readl(data->base + sensor->trim_offset) &
+                ARTPEC8_TMU_TEMP_MASK;
+        sensor->triminfo_105 = (readl(data->base + sensor->trim_offset) >>
+                ARTPEC8_TRIMINFO_105_SHIFT) & ARTPEC8_TMU_TEMP_MASK;
+
+        return 0;
+}
+
+static int artpec8_register_tzd(struct platform_device *pdev)
+{
+        struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+        struct artpec8_sensor *sensor;
+        struct device *dev = &pdev->dev;
+        int sensor_idx, ret = 0;
+        struct thermal_zone_device *tzd;
+        const struct thermal_trip *trips;
+
+        for (sensor_idx = 0; sensor_idx < data->nr_remote; sensor_idx++) {
+                sensor = &data->sensor[sensor_idx];
+
+                ret = artpec8_map_sensor_data(data, sensor_idx, sensor);
+                if (ret)
+                        break;
+
+                tzd = devm_thermal_zone_of_sensor_register(dev,
+                                sensor_idx, sensor, &artpec8_ops);
+                if (IS_ERR(tzd))
+                        continue;
+
+                sensor->tzd = tzd;
+                trips = of_thermal_get_trip_points(tzd);
+                if (!trips) {
+                        dev_warn(dev,
+                                "Cannot get trip points from device tree!\n");
+                        ret = -ENODEV;
+                        break;
+                }
+                sensor->ntrip = of_thermal_get_ntrips(tzd);
+        }
+
+        return ret;
+}
+
 static int exynos_tmu_probe(struct platform_device *pdev)
 {
         struct exynos_tmu_data *data;
         int ret;
+        int sensor_idx;
+        int nr_remote = 0;
+        struct device *dev;
+        const struct of_device_id *dev_id;
 
-        data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data),
-                                        GFP_KERNEL);
+        if (pdev->dev.of_node)
+                dev = &pdev->dev;
+        else
+                dev = pdev->dev.parent;
+
+        dev_id = of_match_node(exynos_tmu_match, dev->of_node);
+        if (dev_id) {
+                data = (struct exynos_tmu_data *)dev_id->data;
+        } else {
+                dev_warn(dev, "dev id error\n");
+                return -EINVAL;
+        }
+
+        ret = of_property_read_u32(dev->of_node, "remote_sensors", &nr_remote);
+        if (ret < 0)
+                data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data),
+                                    GFP_KERNEL);
+        else
+                data = devm_kzalloc(dev, sizeof(struct exynos_tmu_data) +
+                        (sizeof(struct artpec8_sensor) * nr_remote),
+                        GFP_KERNEL);
         if (!data)
                 return -ENOMEM;
 
         platform_set_drvdata(pdev, data);
         mutex_init(&data->lock);
 
+        if (data->soc != SOC_ARCH_ARTPEC8) {
         /*
          * Try enabling the regulator if found
          * TODO: Add regulator as an SOC feature, so that regulator enable
          * is a compulsory call.
          */
-        data->regulator = devm_regulator_get_optional(&pdev->dev, "vtmu");
-        if (!IS_ERR(data->regulator)) {
-                ret = regulator_enable(data->regulator);
-                if (ret) {
-                        dev_err(&pdev->dev, "failed to enable vtmu\n");
-                        return ret;
+                data->regulator = devm_regulator_get_optional(&pdev->dev, "vtmu");
+                if (!IS_ERR(data->regulator)) {
+                        ret = regulator_enable(data->regulator);
+                        if (ret) {
+                                dev_err(&pdev->dev, "failed to enable vtmu\n");
+                                return ret;
+                        }
+                } else {
+                        if (PTR_ERR(data->regulator) == -EPROBE_DEFER)
+                                return -EPROBE_DEFER;
+                        dev_info(&pdev->dev, "Regulator node (vtmu) not found\n");
                 }
-        } else {
-                if (PTR_ERR(data->regulator) == -EPROBE_DEFER)
-                        return -EPROBE_DEFER;
-                dev_info(&pdev->dev, "Regulator node (vtmu) not found\n");
         }
 
         ret = exynos_map_dt_data(pdev);
         if (ret)
                 goto err_sensor;
 
-        INIT_WORK(&data->irq_work, exynos_tmu_work);
+        if (data->soc == SOC_ARCH_ARTPEC8) {
+                ret = artpec8_register_tzd(pdev);
+                if (ret)
+                        return -EINVAL;
+
+                INIT_WORK(&data->irq_work, artpec8_tmu_work);
+        } else {
+                INIT_WORK(&data->irq_work, exynos_tmu_work);
+        }
 
         data->clk = devm_clk_get(&pdev->dev, "tmu_apbif");
         if (IS_ERR(data->clk)) {
@@ -1046,18 +1590,21 @@ static int exynos_tmu_probe(struct platform_device *pdev)
                 goto err_sensor;
         }
 
-        data->clk_sec = devm_clk_get(&pdev->dev, "tmu_triminfo_apbif");
-        if (IS_ERR(data->clk_sec)) {
-                if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) {
-                        dev_err(&pdev->dev, "Failed to get triminfo clock\n");
-                        ret = PTR_ERR(data->clk_sec);
-                        goto err_sensor;
-                }
-        } else {
-                ret = clk_prepare(data->clk_sec);
-                if (ret) {
-                        dev_err(&pdev->dev, "Failed to get clock\n");
-                        goto err_sensor;
+        if (data->soc != SOC_ARCH_ARTPEC8) {
+                data->clk_sec = devm_clk_get(&pdev->dev, "tmu_triminfo_apbif");
+                if (IS_ERR(data->clk_sec)) {
+                        if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) {
+                                dev_err(&pdev->dev,
+                                        "Failed to get triminfo clock\n");
+                                ret = PTR_ERR(data->clk_sec);
+                                goto err_sensor;
+                        }
+                } else {
+                        ret = clk_prepare(data->clk_sec);
+                        if (ret) {
+                                dev_err(&pdev->dev, "Failed to get clock\n");
+                                goto err_sensor;
+                        }
                 }
         }
 
@@ -1070,6 +1617,7 @@ static int exynos_tmu_probe(struct platform_device *pdev)
         switch (data->soc) {
         case SOC_ARCH_EXYNOS5433:
         case SOC_ARCH_EXYNOS7:
+        case SOC_ARCH_ARTPEC8:
                 data->sclk = devm_clk_get(&pdev->dev, "tmu_sclk");
                 if (IS_ERR(data->sclk)) {
                         dev_err(&pdev->dev, "Failed to get sclk\n");
@@ -1087,24 +1635,26 @@ static int exynos_tmu_probe(struct platform_device *pdev)
                 break;
         }
 
+        if (data->soc != SOC_ARCH_ARTPEC8) {
         /*
          * data->tzd must be registered before calling exynos_tmu_initialize(),
          * requesting irq and calling exynos_tmu_control().
          */
-        data->tzd = thermal_zone_of_sensor_register(&pdev->dev, 0, data,
-                                                    &exynos_sensor_ops);
-        if (IS_ERR(data->tzd)) {
-                ret = PTR_ERR(data->tzd);
-                if (ret != -EPROBE_DEFER)
-                        dev_err(&pdev->dev, "Failed to register sensor: %d\n",
-                                ret);
-                goto err_sclk;
-        }
+                data->tzd = thermal_zone_of_sensor_register(&pdev->dev, 0, data,
+                                                            &exynos_sensor_ops);
+                if (IS_ERR(data->tzd)) {
+                        ret = PTR_ERR(data->tzd);
+                        if (ret != -EPROBE_DEFER)
+                                dev_err(&pdev->dev,
+                                        "Failed to register sensor: %d\n", ret);
+                        goto err_sclk;
+                }
 
-        ret = exynos_tmu_initialize(pdev);
-        if (ret) {
-                dev_err(&pdev->dev, "Failed to initialize TMU\n");
-                goto err_thermal;
+                ret = exynos_tmu_initialize(pdev);
+                if (ret) {
+                        dev_err(&pdev->dev, "Failed to initialize TMU\n");
+                        goto err_thermal;
+                }
         }
 
         ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
@@ -1118,7 +1668,13 @@ static int exynos_tmu_probe(struct platform_device *pdev)
         return 0;
 
 err_thermal:
-        thermal_zone_of_sensor_unregister(&pdev->dev, data->tzd);
+        if (data->soc == SOC_ARCH_ARTPEC8) {
+                for (sensor_idx = 0; sensor_idx < data->nr_remote; sensor_idx++)
+                        thermal_zone_of_sensor_unregister(
+                                dev, data->sensor[sensor_idx].tzd);
+        } else {
+                thermal_zone_of_sensor_unregister(&pdev->dev, data->tzd);
+        }
 err_sclk:
         clk_disable_unprepare(data->sclk);
 err_clk:
@@ -1137,17 +1693,27 @@ static int exynos_tmu_remove(struct platform_device *pdev)
 {
         struct exynos_tmu_data *data = platform_get_drvdata(pdev);
         struct thermal_zone_device *tzd = data->tzd;
+        struct device *dev = &pdev->dev;
+        int i;
 
-        thermal_zone_of_sensor_unregister(&pdev->dev, tzd);
-        exynos_tmu_control(pdev, false);
+        if (data->soc == SOC_ARCH_ARTPEC8) {
+                for (i = 0; i < data->nr_remote; i++)
+                        thermal_zone_of_sensor_unregister(dev, data->sensor[i].tzd);
+                exynos_tmu_control(pdev, false);
 
-        clk_disable_unprepare(data->sclk);
-        clk_unprepare(data->clk);
-        if (!IS_ERR(data->clk_sec))
-                clk_unprepare(data->clk_sec);
+                clk_unprepare(data->clk);
+        } else {
+                thermal_zone_of_sensor_unregister(&pdev->dev, tzd);
+                exynos_tmu_control(pdev, false);
 
-        if (!IS_ERR(data->regulator))
-                regulator_disable(data->regulator);
+                clk_disable_unprepare(data->sclk);
+                clk_unprepare(data->clk);
+                if (!IS_ERR(data->clk_sec))
+                        clk_unprepare(data->clk_sec);
+
+                if (!IS_ERR(data->regulator))
+                        regulator_disable(data->regulator);
+        }
 
         return 0;
 }
-- 
2.9.5
 
 




[Index of Archives]     [Linux SoC Development]     [Linux Rockchip Development]     [Linux for Synopsys ARC Processors]    
  • [Linux on Unisoc (RDA Micro) SoCs]     [Linux Actions SoC]     [Linux USB Development]     [Video for Linux]     [Linux Audio Users]     [Linux SCSI]     [Yosemite News]

  •   Powered by Linux