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