Re: [PATCH 2/3] thermal: artpec8-tmu: Add tmu driver for artpec8

From: Alim Akhtar
Date: Thu Mar 10 2022 - 02:04:27 EST


Hello Sang Min,

On Thu, Mar 10, 2022 at 7:43 AM Sang Min Kim <hypmean.kim@xxxxxxxxxxx> wrote:
>
>
> Add tmu driver to handle thermal management for artpec8 SoC.
>
> This driver is derived from the tmu of exynos and additionally supports
> the thermal zone of multiple remote sensors of artpec8.
>
> Signed-off-by: sangmin kim <hypmean.kim@xxxxxxxxxxx>
> ---

Any reason for adding a new TMU driver here? Can this SoC use the
existing exynos_tmu driver?
In case IP is similar and there are few differences in terms of
registers offset etc,
you can use a compatible name to differentiate the same.


> drivers/thermal/samsung/artpec8_tmu.c | 754 ++++++++++++++++++++++++++++++++++
> 1 file changed, 754 insertions(+)
> create mode 100644 drivers/thermal/samsung/artpec8_tmu.c
>
> diff --git a/drivers/thermal/samsung/artpec8_tmu.c b/drivers/thermal/samsung/artpec8_tmu.c
> new file mode 100644
> index 0000000..d973827
> --- /dev/null
> +++ b/drivers/thermal/samsung/artpec8_tmu.c
> @@ -0,0 +1,754 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * artpec8_tmu.c - Samsung TMU (Thermal Management Unit)
> + *
> + * Copyright (C) 2014 Samsung Electronics
> + * Bartlomiej Zolnierkiewicz <b.zolnierkie@xxxxxxxxxxx>
> + * Lukasz Majewski <l.majewski@xxxxxxxxxxx>
> + *
> + * Copyright (C) 2011 Samsung Electronics
> + * Donggeun Kim <dg77.kim@xxxxxxxxxxx>
> + * Amit Daniel Kachhap <amit.kachhap@xxxxxxxxxx>
> + */
> +
> +#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>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +
> +#include <dt-bindings/thermal/thermal_exynos.h>
> +
> +#include "../thermal_core.h"
> +
> +#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 MCELSIUS 1000
> +
> +#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;
> +};
> +
> +#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 artpec8_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;
> +};
> +
> +/**
> + * struct artpec8_tmu_data : A structure to hold the private data of the TMU
> + driver
> + * @id: identifier of the one instance of the TMU controller.
> + * @base: base address of the single instance of the TMU controller.
> + * @irq: irq number of the TMU controller.
> + * @irq_work: pointer to the irq work structure.
> + * @lock: lock to implement synchronization.
> + * @clk: pointer to the clock structure.
> + * @cal_type: calibration type for temperature
> + * @nr_remote: number of remote temperature sensors.
> + * @sensor: variable size member including the information of each sensor
> + */
> +struct artpec8_tmu_data {
> + int id;
> + void __iomem *base;
> + int irq;
> + struct work_struct irq_work;
> + struct mutex lock;
> + struct clk *clk;
> + u32 cal_type;
> + unsigned int nr_remote;
> +
> + struct artpec8_sensor sensor[0];
> +};
> +
> +static u16 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;
> +}
> +
> +static int 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 artpec8_tmu_set_trip_temp(struct artpec8_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 |= temp_to_code(sensor, temp) << (16 * bit_off);
> + writel(th, data->base + temp_rise + reg_off);
> +}
> +
> +static void artpec8_tmu_set_trip_hyst(struct artpec8_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 |= temp_to_code(sensor, temp - hyst) << (16 * bit_off);
> + writel(th, data->base + temp_fall + reg_off);
> +}
> +
> +static void artpec8_enable_interrupt(struct artpec8_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 artpec8_tmu_control(struct platform_device *pdev, bool on)
> +{
> + struct artpec8_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 void artpec8_tmu_clear_irqs(struct artpec8_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_map_dt_data(struct platform_device *pdev)
> +{
> + struct artpec8_tmu_data *data = platform_get_drvdata(pdev);
> + struct device *dev;
> + struct resource res;
> +
> + if (!data || !pdev->dev.of_node)
> + return -ENODEV;
> +
> + dev = &pdev->dev;
> + data->id = of_alias_get_id(dev->of_node, "tmuctrl");
> + if (data->id < 0)
> + data->id = 0;
> +
> + data->irq = irq_of_parse_and_map(dev->of_node, 0);
> + if (data->irq <= 0) {
> + dev_warn(dev, "failed to get IRQ\n");
> + return -ENODEV;
> + }
> +
> + if (of_address_to_resource(dev->of_node, 0, &res)) {
> + dev_warn(dev, "failed to get Resource 0\n");
> + return -ENODEV;
> + }
> +
> + data->base = devm_ioremap(dev, res.start, resource_size(&res));
> + if (!data->base) {
> + dev_warn(dev, "Failed to ioremap memory\n");
> + return -EADDRNOTAVAIL;
> + }
> +
> + of_property_read_u32(dev->of_node, "remote_sensors",
> + &data->nr_remote);
> +
> + data->cal_type = readl(data->base + ARTPEC8_TMU_REG_TRIMINFO) >>
> + ARTPEC8_CALIB_SEL_SHIFT;
> +
> + return 0;
> +}
> +
> +static void artpec8_tmu_work(struct work_struct *work)
> +{
> + int i;
> + u32 inten, intpend, rise, fall;
> + struct artpec8_sensor *sensor;
> + struct artpec8_tmu_data *data = container_of(work,
> + struct artpec8_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 int artpec8_get_temp(void *p, int *temp)
> +{
> + struct artpec8_sensor *sensor = p;
> + struct artpec8_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 = code_to_temp(sensor, value) * MCELSIUS;
> +
> + mutex_unlock(&data->lock);
> +
> + return ret;
> +}
> +
> +static int artpec8_tmu_set_emulation(void *p, int temp)
> +{
> + struct artpec8_sensor *sensor = p;
> + struct artpec8_tmu_data *data = sensor->tmudev;
> + u32 temp_code, econ;
> +
> + temp /= MCELSIUS;
> +
> + mutex_lock(&data->lock);
> +
> + temp_code = 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;
> +}
> +
> +static const struct thermal_zone_of_device_ops artpec8_ops = {
> + .get_temp = artpec8_get_temp,
> + .set_emul_temp = artpec8_tmu_set_emulation,
> +};
> +
> +static void samsung_tmu_control(struct platform_device *pdev, bool on)
> +{
> + struct artpec8_tmu_data *data = platform_get_drvdata(pdev);
> +
> + mutex_lock(&data->lock);
> + artpec8_tmu_control(pdev, on);
> + mutex_unlock(&data->lock);
> +}
> +
> +static int artpec8_map_sensor_data(struct artpec8_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 artpec8_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 artpec8_sensor_initialize(struct artpec8_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 int artpec8_tmu_initialize(struct platform_device *pdev)
> +{
> + struct artpec8_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)) {
> + ret = -EBUSY;
> + break;
> + }
> +
> + ret = artpec8_sensor_initialize(data, sensor_idx);
> + if (ret)
> + break;
> + }
> +
> + mutex_unlock(&data->lock);
> +
> + return ret;
> +}
> +
> +static irqreturn_t artpec8_tmu_irq(int irq, void *id)
> +{
> + struct artpec8_tmu_data *data = id;
> +
> + disable_irq_nosync(irq);
> + schedule_work(&data->irq_work);
> + enable_irq(data->irq);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static const struct of_device_id artpec8_tmu_match[] = {
> + { .compatible = "axis,artpec8-tmu", },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, artpec8_tmu_match);
> +
> +static int artpec8_tmu_probe(struct platform_device *pdev)
> +{
> + int ret, sensor_idx, nr_remote;
> + struct device *dev;
> + const struct of_device_id *id;
> + struct artpec8_tmu_data *data;
> +
> + if (pdev->dev.of_node)
> + dev = &pdev->dev;
> + else
> + dev = pdev->dev.parent;
> +
> + id = of_match_node(artpec8_tmu_match, dev->of_node);
> + if (id) {
> + data = (struct artpec8_tmu_data *)id->data;
> + } else {
> + dev_warn(dev, "dev id error\n");
> + return -EINVAL;
> + }
> +
> + of_property_read_u32(dev->of_node, "remote_sensors", &nr_remote);
> + data = devm_kzalloc(dev, sizeof(struct artpec8_tmu_data) +
> + (sizeof(struct artpec8_sensor) * nr_remote),
> + GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, data);
> + mutex_init(&data->lock);
> +
> + ret = artpec8_map_dt_data(pdev);
> + if (ret)
> + return ret;
> +
> + ret = artpec8_register_tzd(pdev);
> + if (ret)
> + return -EINVAL;
> +
> + ret = artpec8_tmu_initialize(pdev);
> + if (ret) {
> + dev_warn(dev, "Failed to initialize TMU\n");
> + goto err_thermal;
> + }
> +
> + INIT_WORK(&data->irq_work, artpec8_tmu_work);
> +
> + data->clk = devm_clk_get(dev, "tmu_apbif");
> + if (IS_ERR(data->clk)) {
> + dev_warn(dev, "Failed to get clock\n");
> + ret = PTR_ERR(data->clk);
> + return -ENODEV;
> + }
> +
> + ret = clk_prepare(data->clk);
> + if (ret) {
> + dev_err(dev, "Failed to prepare clock\n");
> + goto err_clk;
> + }
> +
> + ret = devm_request_irq(dev, data->irq, artpec8_tmu_irq,
> + IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(dev), data);
> + if (ret) {
> + dev_warn(dev, "Failed to request irq: %d\n", data->irq);
> + goto err_thermal;
> + }
> +
> + samsung_tmu_control(pdev, true);
> +
> + return 0;
> +
> +err_thermal:
> + for (sensor_idx = 0; sensor_idx < data->nr_remote; sensor_idx++)
> + thermal_zone_of_sensor_unregister(dev,
> + data->sensor[sensor_idx].tzd);
> +err_clk:
> + clk_unprepare(data->clk);
> +
> + return ret;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int artpec8_tmu_suspend(struct device *dev)
> +{
> + samsung_tmu_control(to_platform_device(dev), false);
> +
> + return 0;
> +}
> +
> +static int artpec8_tmu_resume(struct device *dev)
> +{
> + samsung_tmu_control(to_platform_device(dev), true);
> +
> + return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(artpec8_tmu_pm,
> + artpec8_tmu_suspend, artpec8_tmu_resume);
> +#define ARTPEC8_TMU_PM (&artpec8_tmu_pm)
> +#else
> +#define ARTPEC8_TMU_PM NULL
> +#endif
> +
> +static int artpec8_tmu_remove(struct platform_device *pdev)
> +{
> + struct artpec8_tmu_data *data = platform_get_drvdata(pdev);
> + struct device *dev = &pdev->dev;
> + int i;
> +
> + for (i = 0; i < data->nr_remote; i++)
> + thermal_zone_of_sensor_unregister(dev,
> + data->sensor[i].tzd);
> + samsung_tmu_control(pdev, false);
> +
> + clk_unprepare(data->clk);
> +
> + return 0;
> +}
> +
> +static struct platform_driver artpec8_tmu_driver = {
> + .driver = {
> + .name = "artpec8-tmu",
> + .pm = ARTPEC8_TMU_PM,
> + .of_match_table = artpec8_tmu_match,
> + },
> + .probe = artpec8_tmu_probe,
> + .remove = artpec8_tmu_remove,
> +};
> +
> +module_platform_driver(artpec8_tmu_driver);
> +
> +MODULE_DESCRIPTION("ARTPEC8 TMU Driver");
> +MODULE_AUTHOR("Sangmin Kim <hypmean.kim@xxxxxxxxxxx>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:artpec8-tmu");
> --
> 2.9.5
>
>



--
Regards,
Alim