[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20220322082242epcms2p5b5e9e24a947e1d6c4b77fb9c95baf992@epcms2p5>
Date: Tue, 22 Mar 2022 17:22:42 +0900
From: Sang Min Kim <hypmean.kim@...sung.com>
To: "bzolnier@...il.com" <bzolnier@...il.com>,
"krzysztof.kozlowski@...onical.com"
<krzysztof.kozlowski@...onical.com>,
"rafael@...nel.org" <rafael@...nel.org>,
"daniel.lezcano@...aro.org" <daniel.lezcano@...aro.org>,
"amitk@...nel.org" <amitk@...nel.org>,
"rui.zhang@...el.com" <rui.zhang@...el.com>,
ALIM AKHTAR <alim.akhtar@...sung.com>,
"lgirdwood@...il.com" <lgirdwood@...il.com>,
"broonie@...nel.org" <broonie@...nel.org>
CC: "linux-pm@...r.kernel.org" <linux-pm@...r.kernel.org>,
"linux-samsung-soc@...r.kernel.org"
<linux-samsung-soc@...r.kernel.org>,
"linux-arm-kernel@...ts.infradead.org"
<linux-arm-kernel@...ts.infradead.org>,
"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>
Subject: [PATCH 2/2] thermal: exynos: Add support for ARTPEC-8
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@...sung.com>
---
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
Powered by blists - more mailing lists