Message ID | 1364297642-2746-8-git-send-email-amit.daniel@samsung.com (mailing list archive) |
---|---|
State | Changes Requested |
Delegated to: | Zhang Rui |
Headers | show |
Amit, On 26-03-2013 07:34, Amit Daniel Kachhap wrote: > This sensor registers 3 instance of the tmu controller with the thermal zone > and hence reports 3 temperature output. This driver supports upto five trip > points. For critical threshold the driver uses the core driver thermal > framework for shutdown and for non-critical threshold it invokes the hw based > frequency clipping limits. Because of such differences with the existing 4210 > tmu controller, exynos5440 tmu driver is added in a new file. > > Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com> > > --- > drivers/thermal/samsung/Kconfig | 9 + > drivers/thermal/samsung/Makefile | 1 + > drivers/thermal/samsung/exynos5440_thermal.c | 713 ++++++++++++++++++++++++++ This driver does not compile as module: ERROR: "exynos_report_trigger" [drivers/thermal/samsung/exynos5440_thermal.ko] undefined! ERROR: "exynos_get_frequency_level" [drivers/thermal/samsung/exynos5440_thermal.ko] undefined! ERROR: "exynos_unregister_thermal" [drivers/thermal/samsung/exynos5440_thermal.ko] undefined! Besides, this driver is pretty similar to 4210 driver. Are you you cannot isolate the difference into config data? Again, check the driver design for TI SoC thermal (drivers/staging/ti-soc-thermal/ on linux-next) > 3 files changed, 723 insertions(+), 0 deletions(-) > create mode 100644 drivers/thermal/samsung/exynos5440_thermal.c > > diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig > index cefe693..0c7b4eb 100644 > --- a/drivers/thermal/samsung/Kconfig > +++ b/drivers/thermal/samsung/Kconfig > @@ -20,4 +20,13 @@ config EXYNOS4210_THERMAL > initialises the TMU controller and registers/unregisters with exynos > common thermal layer. > > +config EXYNOS5440_THERMAL > + tristate "Temperature sensor on Samsung EXYNOS 5440 SOC" > + depends on SOC_EXYNOS5440 > + help > + If you say yes here you can enable TMU (Thermal Management Unit) > + support on SAMSUNG EXYNOS 5440 series of SoC. This option initialises > + the TMU controller and registers/unregisters with exynos common > + thermal layer. > + > endif > diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile > index d51d0c2..53230cf 100644 > --- a/drivers/thermal/samsung/Makefile > +++ b/drivers/thermal/samsung/Makefile > @@ -3,3 +3,4 @@ > # > obj-$(CONFIG_EXYNOS_COMMON) += exynos_common.o > obj-$(CONFIG_EXYNOS4210_THERMAL) += exynos4210_thermal.o > +obj-$(CONFIG_EXYNOS5440_THERMAL) += exynos5440_thermal.o > diff --git a/drivers/thermal/samsung/exynos5440_thermal.c b/drivers/thermal/samsung/exynos5440_thermal.c > new file mode 100644 > index 0000000..a3c75d3 > --- /dev/null > +++ b/drivers/thermal/samsung/exynos5440_thermal.c > @@ -0,0 +1,713 @@ > +/* > + * exynos5440_thermal.c - Samsung EXYNOS 5440 TMU > + * (Thermal Management Unit) > + * > + * Copyright (C) 2013 Samsung Electronics > + * Amit Daniel Kachhap <amit.daniel@samsung.com> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + */ > + > +#include <linux/clk.h> > +#include <linux/cpufreq.h> > +#include <linux/cpu_cooling.h> > +#include <linux/err.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/kobject.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/of.h> > +#include <linux/of_address.h> > +#include <linux/of_irq.h> > +#include <linux/platform_device.h> > +#include <linux/slab.h> > +#include <linux/thermal.h> > +#include <linux/workqueue.h> > +#include <linux/platform_data/exynos_thermal.h> > + > +#include "exynos_common.h" > + > + > +/* Exynos5440 specific registers */ > +#define TMU_S0_7_TRIM 0x0118 > +#define TMU_S0_7_CTRL 0x0138 > +#define TMU_S0_7_DEBUG 0x0158 > +#define TMU_S0_7_STATUS 0x0178 > +#define TMU_S0_7_COUNTER0 0x0198 > +#define TMU_S0_7_COUNTER1 0x01b8 > +#define TMU_S0_7_COUNTER2 0x01d8 > +#define TMU_S0_7_COUNTER3 0x01f8 > +#define TMU_S0_7_TEMP 0x0208 > +#define TMU_S0_7_TH0 0x0228 > +#define TMU_S0_7_TH1 0x0248 > +#define TMU_S0_7_TH2 0x0268 > +#define TMU_S0_7_PTEMP0 0x0288 > +#define TMU_S0_7_PTEMP1 0x02a8 > +#define TMU_S0_7_PTEMP2 0x02c8 > +#define TMU_S0_7_PTEMP3 0x02e8 > +#define TMU_S0_7_EVTEN 0x0308 > +#define TMU_S0_7_IRQEN 0x0328 > +#define TMU_S0_7_IRQ 0x0348 > +#define TMU_IRQ_STATUS 0x0368 > +#define TMU_PMIN 0x036c > +#define TMU_TEMP 0x0370 > +#define TMU_MISC 0x0374 > + > +/* Exynos5440 specific mask and shifts */ > +#define TMU_TEMP_MASK 0xff > + > +#define TMU_TRIM_DATA_25C_SHIFT 0x0 > +#define TMU_TRIM_DATA_85C_SHIFT 0x8 > + > +#define TMU_BUF_VREF_SEL_MASK 0x1f > +#define TMU_BUF_VREF_SEL_SHIFT 24 > +#define TMU_THERM_TRIP_MODE_MASK 0x7 > +#define TMU_THERM_TRIP_MODE_SHIFT 13 > +#define TMU_THERM_TRIP_EN_SHIFT 12 > +#define TMU_BUF_SLOPE_SEL_MASK 0Xf > +#define TMU_BUF_SLOPE_SEL_SHIFT 8 > +#define TMU_THERM_IRQ_MODE_SHIFT 7 > +#define TMU_CALIB_MODE_MASK 0x3 > +#define TMU_CALIB_MODE_SHIFT 4 > +#define TMU_FILTER_MODE_MASK 0x7 > +#define TMU_FILTER_MODE_SHIFT 1 > +#define TMU_SENSOR_EN_SHIFT 0 > +#define TMU_SENSOR_ENABLE 0x1 > + > +#define TMU_EMU_EN_SHIFT 0 > +#define TMU_TEMP_EMU_SHIFT 8 > +#define TMU_EMUL_ENABLE 1 > + > +#define TMU_STATUS_IDLE_SHIFT 0 > + > +#define TMU_TIME_MASK 0xffff > +#define TMU_TIME_OF_SHIFT 16 > +#define TMU_TIME_ON_SHIFT 0 > + > +#define TMU_CURRENT_TEMP_SHIFT 0 > +#define TMU_FILTERED_TEMP_SHIFT 8 > +#define TMU_RAW_TEMP_SHIFT 16 > +#define TMU_TEMP_SEQNUM 24 > + > +#define TMU_THRES_RISE0_SHIFT 0 > +#define TMU_THRES_RISE1_SHIFT 8 > +#define TMU_THRES_RISE2_SHIFT 16 > +#define TMU_THRES_RISE3_SHIFT 24 > + > +#define TMU_THRES_FALL0_SHIFT 0 > +#define TMU_THRES_FALL1_SHIFT 8 > +#define TMU_THRES_FALL2_SHIFT 16 > +#define TMU_THRES_FALL3_SHIFT 24 > + > +#define TMU_THRES_RISE4_SHIFT 24 > + > +#define TMU_RISE_EVTEN_MASK 0xf > +#define TMU_RISE_EVTEN_SHIFT 0 > +#define TMU_FALL_EVTEN_MASK 0xf > +#define TMU_FALL_EVTEN_SHIFT 4 > + > +#define TMU_RISE_IRQEN_MASK 0xf > +#define TMU_RISE_IRQEN_SHIFT 0 > +#define TMU_FALL_IRQEN_MASK 0xf > +#define TMU_FALL_IRQEN_SHIFT 4 > +#define TMU_CLEAR_RISE_INT TMU_RISE_IRQEN_MASK > +#define TMU_CLEAR_FALL_INT (TMU_FALL_IRQEN_MASK << 4) > + > +#define TMU_PMIN_MASK 0x7 > +#define TMU_PMIN0_SHIFT 0 > +#define TMU_PMIN1_SHIFT 4 > +#define TMU_PMIN2_SHIFT 8 > +#define TMU_PMIN3_SHIFT 12 > +#define TMU_PMIN_SHIFT(x) (4 * x) > +#define TMU_TPMIN_SHIFT 16 > + > +#define TMU_TEMP_MAX_SHIFT 0 > +#define TMU_MAX_RISE_LEVEL 4 > +#define TMU_MAX_FALL_LEVEL 4 > +#define TMU_MAX_SENSOR 8 > + > +#define TMU_DEF_CODE_TO_TEMP_OFFSET 20 > + > +struct exynos_tmu_data { > + int irq; > + int id; > + unsigned int shift; > + enum soc_type soc; > + void __iomem *base; > + struct clk *clk; > + struct work_struct irq_work; > + u8 temp_error1, temp_error2; > + struct mutex lock; > + struct thermal_sensor_conf *reg_conf; > + struct exynos_tmu_platform_data *pdata; > +}; > + > +struct exynos_tmu_common { > + int level[TMU_MAX_SENSOR]; > + int sensor_count; > +}; > +static struct exynos_tmu_common tmu_common; > +/* > + * TMU treats temperature as a mapped temperature code. > + * The temperature is converted differently depending on the calibration type. > + */ > +static int temp_to_code(struct exynos_tmu_data *data, u8 temp) > +{ > + struct exynos_tmu_platform_data *pdata = data->pdata; > + int temp_code; > + > + if (pdata->cal_mode == HW_MODE) > + return temp; > + > + switch (pdata->cal_type) { > + case TYPE_TWO_POINT_TRIMMING: > + temp_code = (temp - 25) * > + (data->temp_error2 - data->temp_error1) / > + (70 - 25) + data->temp_error1; > + break; > + case TYPE_ONE_POINT_TRIMMING: > + temp_code = temp + data->temp_error1 - 25; > + break; > + default: > + temp_code = temp + TMU_DEF_CODE_TO_TEMP_OFFSET; > + break; > + } > + > + return temp_code; > +} > + > +/* > + * Calculate a temperature value from a temperature code. > + * The unit of the temperature is degree Celsius. > + */ > +static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code) > +{ > + struct exynos_tmu_platform_data *pdata = data->pdata; > + int temp; > + > + if (pdata->cal_mode == HW_MODE) > + return temp_code; > + > + switch (pdata->cal_type) { > + case TYPE_TWO_POINT_TRIMMING: > + temp = (temp_code - data->temp_error1) * (70 - 25) / > + (data->temp_error2 - data->temp_error1) + 25; > + break; > + case TYPE_ONE_POINT_TRIMMING: > + temp = temp_code - data->temp_error1 + 25; > + break; > + default: > + temp = temp_code - TMU_DEF_CODE_TO_TEMP_OFFSET; > + break; > + } > + > + return temp; > +} > + > +static int exynos_tmu_initialize(struct platform_device *pdev) > +{ > + struct exynos_tmu_data *data = platform_get_drvdata(pdev); > + struct exynos_tmu_platform_data *pdata = data->pdata; > + unsigned int status, con, trim_info; > + unsigned int rising_threshold = 0, falling_threshold = 0; > + int ret = 0, threshold_code, i, trigger_levs = 0; > + > + status = readl(data->base + data->shift + TMU_S0_7_STATUS); > + status &= 0x1; > + if (!status) > + dev_err(&pdev->dev, "Sensor Initial status is busy\n"); > + > + if (pdata->cal_mode == HW_MODE) > + goto skip_calib_data; > + > + /* Save trimming info in order to perform calibration */ > + trim_info = readl(data->base + data->shift + TMU_S0_7_TRIM); > + data->temp_error1 = trim_info & TMU_TEMP_MASK; > + data->temp_error2 = ((trim_info >> 8) & TMU_TEMP_MASK); > + if (!data->temp_error1) > + data->temp_error1 = pdata->efuse_value & TMU_TEMP_MASK; > + if (!data->temp_error2) > + data->temp_error2 = (pdata->efuse_value >> 8) & TMU_TEMP_MASK; > + > +skip_calib_data: > + /* Count trigger levels to be enabled */ > + for (i = 0; i < MAX_THRESHOLD_LEVS; i++) > + if (pdata->trigger_levels[i]) > + trigger_levs++; > + > + /* Write temperature code for rising and falling threshold */ > + for (i = 0; (i < trigger_levs && i < TMU_MAX_RISE_LEVEL); i++) { > + threshold_code = temp_to_code(data, > + pdata->trigger_levels[i]); > + if (threshold_code < 0) { > + ret = threshold_code; > + dev_err(&pdev->dev, "Invalid threshold=%d level=%d\n", > + threshold_code, i); > + goto out; > + } > + rising_threshold |= threshold_code << 8 * i; > + if (pdata->threshold_falling) { > + threshold_code = temp_to_code(data, > + pdata->trigger_levels[i] - > + pdata->threshold_falling); > + if (threshold_code > 0) > + falling_threshold |= > + threshold_code << 8 * i; > + } > + } > + writel(rising_threshold, > + data->base + data->shift + TMU_S0_7_TH0); > + writel(falling_threshold, > + data->base + data->shift + TMU_S0_7_TH1); > + > + /* if 5th threshold limit is also present */ > + if (i == TMU_MAX_RISE_LEVEL) { > + threshold_code = temp_to_code(data, > + pdata->trigger_levels[i]); > + if (threshold_code < 0) { > + ret = threshold_code; > + dev_err(&pdev->dev, "Invalid threshold=%d level=%d\n", > + threshold_code, i); > + goto out; > + } > + rising_threshold = threshold_code << TMU_THRES_RISE4_SHIFT; > + writel(rising_threshold, > + data->base + data->shift + TMU_S0_7_TH2); > + con = readl(data->base + data->shift + TMU_S0_7_CTRL); > + con |= (1 << TMU_THERM_TRIP_EN_SHIFT); > + writel(con, data->base + data->shift + TMU_S0_7_CTRL); > + } > + > + writel(TMU_CLEAR_RISE_INT | TMU_CLEAR_FALL_INT, > + data->base + data->shift + TMU_S0_7_IRQ); > + > + /* clear all PMIN */ > + writel(0, data->base + TMU_PMIN); > +out: > + return ret; > +} > + > +static void exynos_tmu_control(struct platform_device *pdev, bool on) > +{ > + struct exynos_tmu_data *data = platform_get_drvdata(pdev); > + struct exynos_tmu_platform_data *pdata = data->pdata; > + unsigned int con, interrupt_en; > + > + mutex_lock(&data->lock); > + con = readl(data->base + data->shift + TMU_S0_7_CTRL); > + con &= ~(TMU_BUF_VREF_SEL_MASK << TMU_BUF_VREF_SEL_SHIFT | > + TMU_THERM_TRIP_MODE_MASK << TMU_THERM_TRIP_MODE_SHIFT | > + TMU_BUF_SLOPE_SEL_MASK << TMU_BUF_SLOPE_SEL_SHIFT | > + TMU_CALIB_MODE_MASK << TMU_CALIB_MODE_SHIFT | > + TMU_FILTER_MODE_MASK << TMU_FILTER_MODE_SHIFT | > + TMU_SENSOR_ENABLE << TMU_SENSOR_EN_SHIFT); > + > + con |= pdata->reference_voltage << TMU_BUF_VREF_SEL_SHIFT | > + pdata->gain << TMU_BUF_SLOPE_SEL_SHIFT; > + > + if (pdata->cal_mode == HW_MODE) > + con |= pdata->cal_type << TMU_CALIB_MODE_SHIFT; > + > + con |= pdata->noise_cancel_mode << TMU_THERM_TRIP_MODE_SHIFT; > + > + if (on) { > + con |= TMU_SENSOR_ENABLE; > + interrupt_en = > + pdata->trigger_enable[3] << 3 | > + pdata->trigger_enable[2] << 2 | > + pdata->trigger_enable[1] << 1 | > + pdata->trigger_enable[0] << 0; > + if (pdata->threshold_falling) > + interrupt_en |= interrupt_en << TMU_FALL_IRQEN_SHIFT; > + } else { > + interrupt_en = 0; /* Disable all interrupts */ > + } > + writel(interrupt_en, data->base + data->shift + TMU_S0_7_IRQEN); > + writel(interrupt_en, data->base + data->shift + TMU_S0_7_EVTEN); > + writel(con, data->base + data->shift + TMU_S0_7_CTRL); > + > + mutex_unlock(&data->lock); > +} > + > +static int exynos_tmu_read(struct exynos_tmu_data *data) > +{ > + u8 temp_code; > + int temp; > + > + mutex_lock(&data->lock); Dont you need to enable clocks? > + > + temp_code = readl(data->base + data->shift + TMU_S0_7_TEMP); > + temp_code >>= TMU_CURRENT_TEMP_SHIFT; > + temp_code &= TMU_TEMP_MASK; > + temp = code_to_temp(data, temp_code); > + > + mutex_unlock(&data->lock); > + > + return temp; > +} > + > +#ifdef CONFIG_THERMAL_EMULATION > +static int exynos_tmu_set_emulation(struct exynos_tmu_data *data, > + unsigned long temp) > +{ > + unsigned int reg; > + > + if (temp && temp < MCELSIUS) > + goto out; > + > + mutex_lock(&data->lock); > + reg = readl(data->base + data->shift + TMU_S0_7_DEBUG); > + > + if (temp) { > + temp /= MCELSIUS; > + reg &= ~(TMU_TEMP_MASK << TMU_TEMP_EMU_SHIFT); > + reg |= (temp_to_code(data, temp) << TMU_TEMP_EMU_SHIFT) | > + TMU_EMUL_ENABLE; > + } else { > + reg &= ~TMU_EMUL_ENABLE; > + } > + > + writel(reg, data->base + data->shift + TMU_S0_7_DEBUG); > + mutex_unlock(&data->lock); > + return 0; > +out: > + return -EINVAL; > +} > +#endif > + > +static void exynos_tmu_set_cooling(struct exynos_tmu_data *data, int level, > + unsigned int cur_temp) > +{ > + struct exynos_tmu_platform_data *pdata = data->pdata; > + bool check_rise, change; > + unsigned int thres_temp, freq = 0, val; > + int i, index, max_level = 0; > + > + /* Get the max level across all sensors except this */ > + for (i = 0; i < tmu_common.sensor_count; i++) { > + if (i == data->id) > + continue; > + if (tmu_common.level[i] > max_level) > + max_level = tmu_common.level[i]; > + } > + change = false; > + if (level < TMU_MAX_RISE_LEVEL) { > + thres_temp = readl(data->base + data->shift + TMU_S0_7_TH0); > + thres_temp = (thres_temp >> (level * 8) & TMU_TEMP_MASK); > + check_rise = true; > + tmu_common.level[data->id] = level + 1; > + if (tmu_common.level[data->id] > max_level) > + change = true; > + } else { > + level -= TMU_MAX_RISE_LEVEL; > + thres_temp = readl(data->base + data->shift + TMU_S0_7_TH1); > + thres_temp = (thres_temp >> (level * 8) & TMU_TEMP_MASK); > + check_rise = false; > + tmu_common.level[data->id] = level; > + if (tmu_common.level[data->id] >= max_level) > + change = true; > + } > + > + if (change == false) > + return; > + > + thres_temp = code_to_temp(data, thres_temp); > + if (!check_rise) > + thres_temp += pdata->threshold_falling; > + > + change = false; > + /* find this threshold temp in the patform table cooling data */ > + for (i = 0; i < pdata->freq_tab_count; i++) { > + if (thres_temp != pdata->freq_tab[i].temp_level) > + continue; > + > + if (check_rise && cur_temp >= thres_temp) { > + freq = pdata->freq_tab[i].freq_clip_max; > + change = true; > + } > + if (!check_rise && > + (cur_temp <= (thres_temp - pdata->threshold_falling))) { > + change = true; > + freq = 0; > + } > + } > + > + /* critical threshold temp */ > + if (thres_temp == pdata->trigger_levels[TMU_MAX_RISE_LEVEL - 1]) > + exynos_report_trigger(data->reg_conf); > + > + if (change == false) > + return; > + > + index = 0; > + > + if (freq) { > + index = exynos_get_frequency_level(0, freq); > + if (index < 0) > + return; > + } > + > + val = readl(data->base + TMU_PMIN); > + val &= (~(TMU_PMIN_MASK << TMU_PMIN_SHIFT(level))); > + val |= (index << TMU_PMIN_SHIFT(level)); > + writel(val, data->base + TMU_PMIN); > +} > + > +static void exynos_tmu_work(struct work_struct *work) > +{ > + struct exynos_tmu_data *data = container_of(work, > + struct exynos_tmu_data, irq_work); > + int i, cur_temp; > + unsigned int val_type, val_irq; > + > + if (!data) > + goto out; > + > + val_type = readl(data->base + TMU_IRQ_STATUS); > + > + /* Find which sensor generated this interrupt */ > + if (!((val_type >> data->id) & 0x1)) > + goto out; > + > + cur_temp = exynos_tmu_read(data); > + val_irq = readl(data->base + data->shift + TMU_S0_7_IRQ); > + for (i = 0; i < (TMU_MAX_RISE_LEVEL + TMU_MAX_FALL_LEVEL); i++) { > + if (!((val_irq >> i) & 0x1)) > + continue; > + exynos_tmu_set_cooling(data, i, cur_temp); > + } > + /* clear the interrupts */ > + writel(val_irq, data->base + data->shift + TMU_S0_7_IRQ); > +out: > + enable_irq(data->irq); > +} > + > +static irqreturn_t exynos_tmu_irq(int irq, void *id) > +{ > + struct exynos_tmu_data *data = id; > + > + disable_irq_nosync(irq); > + schedule_work(&data->irq_work); > + > + return IRQ_HANDLED; > +} > + > +static const struct of_device_id exynos_tmu_match[] = { > + { > + .compatible = "samsung,exynos5440-tmu", > + }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, exynos_tmu_match); > + > +int exynos_map_dt_data(struct platform_device *pdev) > +{ > + struct exynos_tmu_data *data = platform_get_drvdata(pdev); > + struct resource res; > + > + if (!data) > + return -ENODEV; > + > + data->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl"); > + if (data->id < 0) > + data->id = 0; > + > + data->shift = data->id * 4; > + > + data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); > + if (data->irq <= 0) { > + dev_err(&pdev->dev, "failed to get IRQ\n"); > + return -ENODEV; > + } > + > + if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { > + dev_err(&pdev->dev, "failed to get Resource\n"); > + return -ENODEV; > + } > + > + /* clear the last 16 bytes */ > + res.start &= (~(0xFFFF)); > + data->base = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); > + if (!data->base) { > + dev_err(&pdev->dev, "Failed to ioremap memory\n"); > + return -ENOMEM; > + } > + return 0; > +} > + > +static int exynos_tmu_probe(struct platform_device *pdev) > +{ > + struct exynos_tmu_data *data; > + struct exynos_tmu_platform_data *pdata; > + struct thermal_sensor_conf *sensor_conf; > + int ret, i; > + > + data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data), > + GFP_KERNEL); > + if (!data) { > + dev_err(&pdev->dev, "Failed to allocate driver structure\n"); > + return -ENOMEM; > + } > + > + pdata = (struct exynos_tmu_platform_data *) > + platform_get_device_id(pdev)->driver_data; > + if (!pdata) { > + dev_err(&pdev->dev, "No platform init data supplied.\n"); > + return -ENODEV; > + } > + > + data->pdata = pdata; > + platform_set_drvdata(pdev, data); > + > + ret = exynos_map_dt_data(pdev); > + if (ret) > + goto unset_data; > + > + INIT_WORK(&data->irq_work, exynos_tmu_work); > + > + ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, > + IRQF_TRIGGER_RISING|IRQF_SHARED, dev_name(&pdev->dev), data); > + if (ret) { > + dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); > + goto unset_data; > + } > + > + data->clk = of_clk_get(pdev->dev.of_node, 0); > + if (IS_ERR(data->clk)) { > + dev_err(&pdev->dev, "Failed to get tmu clock\n"); > + ret = PTR_ERR(data->clk); > + goto unset_data; > + } > + clk_enable(data->clk); > + hmmm ok, you want it to be always running, right? > + mutex_init(&data->lock); > + > + ret = exynos_tmu_initialize(pdev); > + if (ret) { > + dev_err(&pdev->dev, "Failed to initialize TMU\n"); > + goto err_clk; > + } > + > + exynos_tmu_control(pdev, true); > + > + /* Allocate a structure to register with the exynos core thermal */ > + sensor_conf = devm_kzalloc(&pdev->dev, > + sizeof(struct thermal_sensor_conf), GFP_KERNEL); > + if (!sensor_conf) { > + dev_err(&pdev->dev, "Failed to allocate registration struct\n"); > + ret = -ENOMEM; > + goto err_clk; > + } > + data->reg_conf = sensor_conf; > + sprintf(sensor_conf->name, "therm_zone%d", data->id); > + sensor_conf->read_temperature = (int (*)(void *))exynos_tmu_read; > +#ifdef CONFIG_THERMAL_EMULATION > + sensor_conf->write_emul_temp = > + (int (*)(void *, unsigned long))exynos_tmu_set_emulation; > +#endif Do you really need this ifdef here? Cant you do same as you have done for 4210? > + sensor_conf->driver_data = data; > + sensor_conf->trip_data.trip_count = pdata->trigger_enable[0] + > + pdata->trigger_enable[1] + pdata->trigger_enable[2] + > + pdata->trigger_enable[3]; > + > + for (i = 0; i < sensor_conf->trip_data.trip_count; i++) > + sensor_conf->trip_data.trip_val[i] = pdata->trigger_levels[i]; > + > + sensor_conf->trip_data.trigger_falling = pdata->threshold_falling; > + > + /* Register the sensor with thermal management interface */ > + ret = exynos_register_thermal(sensor_conf); > + if (ret) { > + dev_err(&pdev->dev, "Failed to register thermal interface\n"); > + goto err_clk; > + } > + tmu_common.sensor_count++; > + return 0; > +err_clk: > + clk_disable(data->clk); > + clk_put(data->clk); > +unset_data: > + platform_set_drvdata(pdev, NULL); > + return ret; > +} > + > +static int exynos_tmu_remove(struct platform_device *pdev) > +{ > + struct exynos_tmu_data *data = platform_get_drvdata(pdev); > + struct thermal_sensor_conf *sensor_conf = data->reg_conf; > + > + exynos_tmu_control(pdev, false); > + clk_disable(data->clk); > + > + exynos_unregister_thermal(sensor_conf); > + > + clk_put(data->clk); > + > + platform_set_drvdata(pdev, NULL); > + > + return 0; > +} > + > +#ifdef CONFIG_PM_SLEEP > +static int exynos_tmu_suspend(struct device *dev) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + struct exynos_tmu_data *data = platform_get_drvdata(pdev); > + > + exynos_tmu_control(pdev, false); > + clk_disable(data->clk); > + > + return 0; > +} > + > +static int exynos_tmu_resume(struct device *dev) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + struct exynos_tmu_data *data = platform_get_drvdata(pdev); > + > + clk_enable(data->clk); > + exynos_tmu_initialize(pdev); > + exynos_tmu_control(pdev, true); > + > + return 0; > +} > + > +static SIMPLE_DEV_PM_OPS(exynos_tmu_pm, > + exynos_tmu_suspend, exynos_tmu_resume); > +#define EXYNOS_TMU_PM (&exynos_tmu_pm) > +#else > +#define EXYNOS_TMU_PM NULL > +#endif > + > +static struct platform_driver exynos_tmu_driver = { > + .driver = { > + .name = "exynos5440-tmu", > + .owner = THIS_MODULE, > + .pm = EXYNOS_TMU_PM, > + .of_match_table = exynos_tmu_match, > + }, > + .probe = exynos_tmu_probe, > + .remove = exynos_tmu_remove, > +}; > + > +module_platform_driver(exynos_tmu_driver); > + > +MODULE_DESCRIPTION("EXYNOS5440 TMU Driver"); > +MODULE_AUTHOR("Amit Daniel<amit.daniel@samsung.com>"); > +MODULE_LICENSE("GPL"); GPL v2? > +MODULE_ALIAS("platform:exynos5440-tmu"); > -- To unsubscribe from this list: send the line "unsubscribe linux-pm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Eduardo, On Fri, Apr 12, 2013 at 2:34 AM, Eduardo Valentin <eduardo.valentin@ti.com> wrote: > > Amit, > > > On 26-03-2013 07:34, Amit Daniel Kachhap wrote: >> >> This sensor registers 3 instance of the tmu controller with the thermal >> zone >> and hence reports 3 temperature output. This driver supports upto five >> trip >> points. For critical threshold the driver uses the core driver thermal >> framework for shutdown and for non-critical threshold it invokes the hw >> based >> frequency clipping limits. Because of such differences with the existing >> 4210 >> tmu controller, exynos5440 tmu driver is added in a new file. >> >> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com> >> >> --- >> drivers/thermal/samsung/Kconfig | 9 + >> drivers/thermal/samsung/Makefile | 1 + >> drivers/thermal/samsung/exynos5440_thermal.c | 713 >> ++++++++++++++++++++++++++ > > > This driver does not compile as module: > ERROR: "exynos_report_trigger" > [drivers/thermal/samsung/exynos5440_thermal.ko] undefined! > ERROR: "exynos_get_frequency_level" > [drivers/thermal/samsung/exynos5440_thermal.ko] undefined! > ERROR: "exynos_unregister_thermal" > [drivers/thermal/samsung/exynos5440_thermal.ko] undefined! Ok will fix it. > > > Besides, this driver is pretty similar to 4210 driver. Are you you cannot > isolate the difference into config data? Again, check the driver design for > TI SoC thermal (drivers/staging/ti-soc-thermal/ on linux-next) Yes I started with a 4210 file and srarted modifying it but then lot of conditional code started getting inserted and it become very complex. So I splitted this file itself. Will check your implementation. Thanks, Amit Daniel > > >> 3 files changed, 723 insertions(+), 0 deletions(-) >> create mode 100644 drivers/thermal/samsung/exynos5440_thermal.c >> >> diff --git a/drivers/thermal/samsung/Kconfig >> b/drivers/thermal/samsung/Kconfig >> index cefe693..0c7b4eb 100644 >> --- a/drivers/thermal/samsung/Kconfig >> +++ b/drivers/thermal/samsung/Kconfig >> @@ -20,4 +20,13 @@ config EXYNOS4210_THERMAL >> initialises the TMU controller and registers/unregisters with >> exynos >> common thermal layer. >> >> +config EXYNOS5440_THERMAL >> + tristate "Temperature sensor on Samsung EXYNOS 5440 SOC" >> + depends on SOC_EXYNOS5440 >> + help >> + If you say yes here you can enable TMU (Thermal Management Unit) >> + support on SAMSUNG EXYNOS 5440 series of SoC. This option >> initialises >> + the TMU controller and registers/unregisters with exynos common >> + thermal layer. >> + >> endif >> diff --git a/drivers/thermal/samsung/Makefile >> b/drivers/thermal/samsung/Makefile >> index d51d0c2..53230cf 100644 >> --- a/drivers/thermal/samsung/Makefile >> +++ b/drivers/thermal/samsung/Makefile >> @@ -3,3 +3,4 @@ >> # >> obj-$(CONFIG_EXYNOS_COMMON) += exynos_common.o >> obj-$(CONFIG_EXYNOS4210_THERMAL) += exynos4210_thermal.o >> +obj-$(CONFIG_EXYNOS5440_THERMAL) += exynos5440_thermal.o >> diff --git a/drivers/thermal/samsung/exynos5440_thermal.c >> b/drivers/thermal/samsung/exynos5440_thermal.c >> new file mode 100644 >> index 0000000..a3c75d3 >> --- /dev/null >> +++ b/drivers/thermal/samsung/exynos5440_thermal.c >> @@ -0,0 +1,713 @@ >> +/* >> + * exynos5440_thermal.c - Samsung EXYNOS 5440 TMU >> + * (Thermal Management Unit) >> + * >> + * Copyright (C) 2013 Samsung Electronics >> + * Amit Daniel Kachhap <amit.daniel@samsung.com> >> + * >> + * This program is free software; you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License as published by >> + * the Free Software Foundation; either version 2 of the License, or >> + * (at your option) any later version. >> + * >> + * This program is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + * GNU General Public License for more details. >> + * >> + * You should have received a copy of the GNU General Public License >> + * along with this program; if not, write to the Free Software >> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 >> USA >> + * >> + */ >> + >> +#include <linux/clk.h> >> +#include <linux/cpufreq.h> >> +#include <linux/cpu_cooling.h> >> +#include <linux/err.h> >> +#include <linux/interrupt.h> >> +#include <linux/io.h> >> +#include <linux/kernel.h> >> +#include <linux/kobject.h> >> +#include <linux/module.h> >> +#include <linux/mutex.h> >> +#include <linux/of.h> >> +#include <linux/of_address.h> >> +#include <linux/of_irq.h> >> +#include <linux/platform_device.h> >> +#include <linux/slab.h> >> +#include <linux/thermal.h> >> +#include <linux/workqueue.h> >> +#include <linux/platform_data/exynos_thermal.h> >> + >> +#include "exynos_common.h" >> + >> + >> +/* Exynos5440 specific registers */ >> +#define TMU_S0_7_TRIM 0x0118 >> +#define TMU_S0_7_CTRL 0x0138 >> +#define TMU_S0_7_DEBUG 0x0158 >> +#define TMU_S0_7_STATUS 0x0178 >> +#define TMU_S0_7_COUNTER0 0x0198 >> +#define TMU_S0_7_COUNTER1 0x01b8 >> +#define TMU_S0_7_COUNTER2 0x01d8 >> +#define TMU_S0_7_COUNTER3 0x01f8 >> +#define TMU_S0_7_TEMP 0x0208 >> +#define TMU_S0_7_TH0 0x0228 >> +#define TMU_S0_7_TH1 0x0248 >> +#define TMU_S0_7_TH2 0x0268 >> +#define TMU_S0_7_PTEMP0 0x0288 >> +#define TMU_S0_7_PTEMP1 0x02a8 >> +#define TMU_S0_7_PTEMP2 0x02c8 >> +#define TMU_S0_7_PTEMP3 0x02e8 >> +#define TMU_S0_7_EVTEN 0x0308 >> +#define TMU_S0_7_IRQEN 0x0328 >> +#define TMU_S0_7_IRQ 0x0348 >> +#define TMU_IRQ_STATUS 0x0368 >> +#define TMU_PMIN 0x036c >> +#define TMU_TEMP 0x0370 >> +#define TMU_MISC 0x0374 >> + >> +/* Exynos5440 specific mask and shifts */ >> +#define TMU_TEMP_MASK 0xff >> + >> +#define TMU_TRIM_DATA_25C_SHIFT 0x0 >> +#define TMU_TRIM_DATA_85C_SHIFT 0x8 >> + >> +#define TMU_BUF_VREF_SEL_MASK 0x1f >> +#define TMU_BUF_VREF_SEL_SHIFT 24 >> +#define TMU_THERM_TRIP_MODE_MASK 0x7 >> +#define TMU_THERM_TRIP_MODE_SHIFT 13 >> +#define TMU_THERM_TRIP_EN_SHIFT 12 >> +#define TMU_BUF_SLOPE_SEL_MASK 0Xf >> +#define TMU_BUF_SLOPE_SEL_SHIFT 8 >> +#define TMU_THERM_IRQ_MODE_SHIFT 7 >> +#define TMU_CALIB_MODE_MASK 0x3 >> +#define TMU_CALIB_MODE_SHIFT 4 >> +#define TMU_FILTER_MODE_MASK 0x7 >> +#define TMU_FILTER_MODE_SHIFT 1 >> +#define TMU_SENSOR_EN_SHIFT 0 >> +#define TMU_SENSOR_ENABLE 0x1 >> + >> +#define TMU_EMU_EN_SHIFT 0 >> +#define TMU_TEMP_EMU_SHIFT 8 >> +#define TMU_EMUL_ENABLE 1 >> + >> +#define TMU_STATUS_IDLE_SHIFT 0 >> + >> +#define TMU_TIME_MASK 0xffff >> +#define TMU_TIME_OF_SHIFT 16 >> +#define TMU_TIME_ON_SHIFT 0 >> + >> +#define TMU_CURRENT_TEMP_SHIFT 0 >> +#define TMU_FILTERED_TEMP_SHIFT 8 >> +#define TMU_RAW_TEMP_SHIFT 16 >> +#define TMU_TEMP_SEQNUM 24 >> + >> +#define TMU_THRES_RISE0_SHIFT 0 >> +#define TMU_THRES_RISE1_SHIFT 8 >> +#define TMU_THRES_RISE2_SHIFT 16 >> +#define TMU_THRES_RISE3_SHIFT 24 >> + >> +#define TMU_THRES_FALL0_SHIFT 0 >> +#define TMU_THRES_FALL1_SHIFT 8 >> +#define TMU_THRES_FALL2_SHIFT 16 >> +#define TMU_THRES_FALL3_SHIFT 24 >> + >> +#define TMU_THRES_RISE4_SHIFT 24 >> + >> +#define TMU_RISE_EVTEN_MASK 0xf >> +#define TMU_RISE_EVTEN_SHIFT 0 >> +#define TMU_FALL_EVTEN_MASK 0xf >> +#define TMU_FALL_EVTEN_SHIFT 4 >> + >> +#define TMU_RISE_IRQEN_MASK 0xf >> +#define TMU_RISE_IRQEN_SHIFT 0 >> +#define TMU_FALL_IRQEN_MASK 0xf >> +#define TMU_FALL_IRQEN_SHIFT 4 >> +#define TMU_CLEAR_RISE_INT TMU_RISE_IRQEN_MASK >> +#define TMU_CLEAR_FALL_INT (TMU_FALL_IRQEN_MASK << 4) >> + >> +#define TMU_PMIN_MASK 0x7 >> +#define TMU_PMIN0_SHIFT 0 >> +#define TMU_PMIN1_SHIFT 4 >> +#define TMU_PMIN2_SHIFT 8 >> +#define TMU_PMIN3_SHIFT 12 >> +#define TMU_PMIN_SHIFT(x) (4 * x) >> +#define TMU_TPMIN_SHIFT 16 >> + >> +#define TMU_TEMP_MAX_SHIFT 0 >> +#define TMU_MAX_RISE_LEVEL 4 >> +#define TMU_MAX_FALL_LEVEL 4 >> +#define TMU_MAX_SENSOR 8 >> + >> +#define TMU_DEF_CODE_TO_TEMP_OFFSET 20 >> + >> +struct exynos_tmu_data { >> + int irq; >> + int id; >> + unsigned int shift; >> + enum soc_type soc; >> + void __iomem *base; >> + struct clk *clk; >> + struct work_struct irq_work; >> + u8 temp_error1, temp_error2; >> + struct mutex lock; >> + struct thermal_sensor_conf *reg_conf; >> + struct exynos_tmu_platform_data *pdata; >> +}; >> + >> +struct exynos_tmu_common { >> + int level[TMU_MAX_SENSOR]; >> + int sensor_count; >> +}; >> +static struct exynos_tmu_common tmu_common; >> +/* >> + * TMU treats temperature as a mapped temperature code. >> + * The temperature is converted differently depending on the calibration >> type. >> + */ >> +static int temp_to_code(struct exynos_tmu_data *data, u8 temp) >> +{ >> + struct exynos_tmu_platform_data *pdata = data->pdata; >> + int temp_code; >> + >> + if (pdata->cal_mode == HW_MODE) >> + return temp; >> + >> + switch (pdata->cal_type) { >> + case TYPE_TWO_POINT_TRIMMING: >> + temp_code = (temp - 25) * >> + (data->temp_error2 - data->temp_error1) / >> + (70 - 25) + data->temp_error1; >> + break; >> + case TYPE_ONE_POINT_TRIMMING: >> + temp_code = temp + data->temp_error1 - 25; >> + break; >> + default: >> + temp_code = temp + TMU_DEF_CODE_TO_TEMP_OFFSET; >> + break; >> + } >> + >> + return temp_code; >> +} >> + >> +/* >> + * Calculate a temperature value from a temperature code. >> + * The unit of the temperature is degree Celsius. >> + */ >> +static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code) >> +{ >> + struct exynos_tmu_platform_data *pdata = data->pdata; >> + int temp; >> + >> + if (pdata->cal_mode == HW_MODE) >> + return temp_code; >> + >> + switch (pdata->cal_type) { >> + case TYPE_TWO_POINT_TRIMMING: >> + temp = (temp_code - data->temp_error1) * (70 - 25) / >> + (data->temp_error2 - data->temp_error1) + 25; >> + break; >> + case TYPE_ONE_POINT_TRIMMING: >> + temp = temp_code - data->temp_error1 + 25; >> + break; >> + default: >> + temp = temp_code - TMU_DEF_CODE_TO_TEMP_OFFSET; >> + break; >> + } >> + >> + return temp; >> +} >> + >> +static int exynos_tmu_initialize(struct platform_device *pdev) >> +{ >> + struct exynos_tmu_data *data = platform_get_drvdata(pdev); >> + struct exynos_tmu_platform_data *pdata = data->pdata; >> + unsigned int status, con, trim_info; >> + unsigned int rising_threshold = 0, falling_threshold = 0; >> + int ret = 0, threshold_code, i, trigger_levs = 0; >> + >> + status = readl(data->base + data->shift + TMU_S0_7_STATUS); >> + status &= 0x1; >> + if (!status) >> + dev_err(&pdev->dev, "Sensor Initial status is busy\n"); >> + >> + if (pdata->cal_mode == HW_MODE) >> + goto skip_calib_data; >> + >> + /* Save trimming info in order to perform calibration */ >> + trim_info = readl(data->base + data->shift + TMU_S0_7_TRIM); >> + data->temp_error1 = trim_info & TMU_TEMP_MASK; >> + data->temp_error2 = ((trim_info >> 8) & TMU_TEMP_MASK); >> + if (!data->temp_error1) >> + data->temp_error1 = pdata->efuse_value & TMU_TEMP_MASK; >> + if (!data->temp_error2) >> + data->temp_error2 = (pdata->efuse_value >> 8) & >> TMU_TEMP_MASK; >> + >> +skip_calib_data: >> + /* Count trigger levels to be enabled */ >> + for (i = 0; i < MAX_THRESHOLD_LEVS; i++) >> + if (pdata->trigger_levels[i]) >> + trigger_levs++; >> + >> + /* Write temperature code for rising and falling threshold */ >> + for (i = 0; (i < trigger_levs && i < TMU_MAX_RISE_LEVEL); i++) { >> + threshold_code = temp_to_code(data, >> + pdata->trigger_levels[i]); >> + if (threshold_code < 0) { >> + ret = threshold_code; >> + dev_err(&pdev->dev, "Invalid threshold=%d >> level=%d\n", >> + threshold_code, >> i); >> + goto out; >> + } >> + rising_threshold |= threshold_code << 8 * i; >> + if (pdata->threshold_falling) { >> + threshold_code = temp_to_code(data, >> + pdata->trigger_levels[i] - >> + pdata->threshold_falling); >> + if (threshold_code > 0) >> + falling_threshold |= >> + threshold_code << 8 * i; >> + } >> + } >> + writel(rising_threshold, >> + data->base + data->shift + TMU_S0_7_TH0); >> + writel(falling_threshold, >> + data->base + data->shift + TMU_S0_7_TH1); >> + >> + /* if 5th threshold limit is also present */ >> + if (i == TMU_MAX_RISE_LEVEL) { >> + threshold_code = temp_to_code(data, >> + pdata->trigger_levels[i]); >> + if (threshold_code < 0) { >> + ret = threshold_code; >> + dev_err(&pdev->dev, "Invalid threshold=%d >> level=%d\n", >> + threshold_code, >> i); >> + goto out; >> + } >> + rising_threshold = threshold_code << >> TMU_THRES_RISE4_SHIFT; >> + writel(rising_threshold, >> + data->base + data->shift + TMU_S0_7_TH2); >> + con = readl(data->base + data->shift + TMU_S0_7_CTRL); >> + con |= (1 << TMU_THERM_TRIP_EN_SHIFT); >> + writel(con, data->base + data->shift + TMU_S0_7_CTRL); >> + } >> + >> + writel(TMU_CLEAR_RISE_INT | TMU_CLEAR_FALL_INT, >> + data->base + data->shift + TMU_S0_7_IRQ); >> + >> + /* clear all PMIN */ >> + writel(0, data->base + TMU_PMIN); >> +out: >> + return ret; >> +} >> + >> +static void exynos_tmu_control(struct platform_device *pdev, bool on) >> +{ >> + struct exynos_tmu_data *data = platform_get_drvdata(pdev); >> + struct exynos_tmu_platform_data *pdata = data->pdata; >> + unsigned int con, interrupt_en; >> + >> + mutex_lock(&data->lock); >> + con = readl(data->base + data->shift + TMU_S0_7_CTRL); >> + con &= ~(TMU_BUF_VREF_SEL_MASK << TMU_BUF_VREF_SEL_SHIFT | >> + TMU_THERM_TRIP_MODE_MASK << TMU_THERM_TRIP_MODE_SHIFT | >> + TMU_BUF_SLOPE_SEL_MASK << TMU_BUF_SLOPE_SEL_SHIFT | >> + TMU_CALIB_MODE_MASK << TMU_CALIB_MODE_SHIFT | >> + TMU_FILTER_MODE_MASK << TMU_FILTER_MODE_SHIFT | >> + TMU_SENSOR_ENABLE << TMU_SENSOR_EN_SHIFT); >> + >> + con |= pdata->reference_voltage << TMU_BUF_VREF_SEL_SHIFT | >> + pdata->gain << TMU_BUF_SLOPE_SEL_SHIFT; >> + >> + if (pdata->cal_mode == HW_MODE) >> + con |= pdata->cal_type << TMU_CALIB_MODE_SHIFT; >> + >> + con |= pdata->noise_cancel_mode << TMU_THERM_TRIP_MODE_SHIFT; >> + >> + if (on) { >> + con |= TMU_SENSOR_ENABLE; >> + interrupt_en = >> + pdata->trigger_enable[3] << 3 | >> + pdata->trigger_enable[2] << 2 | >> + pdata->trigger_enable[1] << 1 | >> + pdata->trigger_enable[0] << 0; >> + if (pdata->threshold_falling) >> + interrupt_en |= interrupt_en << >> TMU_FALL_IRQEN_SHIFT; >> + } else { >> + interrupt_en = 0; /* Disable all interrupts */ >> + } >> + writel(interrupt_en, data->base + data->shift + TMU_S0_7_IRQEN); >> + writel(interrupt_en, data->base + data->shift + TMU_S0_7_EVTEN); >> + writel(con, data->base + data->shift + TMU_S0_7_CTRL); >> + >> + mutex_unlock(&data->lock); >> +} >> + >> +static int exynos_tmu_read(struct exynos_tmu_data *data) >> +{ >> + u8 temp_code; >> + int temp; >> + >> + mutex_lock(&data->lock); > > > Dont you need to enable clocks? > > >> + >> + temp_code = readl(data->base + data->shift + TMU_S0_7_TEMP); >> + temp_code >>= TMU_CURRENT_TEMP_SHIFT; >> + temp_code &= TMU_TEMP_MASK; >> + temp = code_to_temp(data, temp_code); >> + >> + mutex_unlock(&data->lock); >> + >> + return temp; >> +} >> + >> +#ifdef CONFIG_THERMAL_EMULATION >> +static int exynos_tmu_set_emulation(struct exynos_tmu_data *data, >> + unsigned long temp) >> +{ >> + unsigned int reg; >> + >> + if (temp && temp < MCELSIUS) >> + goto out; >> + >> + mutex_lock(&data->lock); >> + reg = readl(data->base + data->shift + TMU_S0_7_DEBUG); >> + >> + if (temp) { >> + temp /= MCELSIUS; >> + reg &= ~(TMU_TEMP_MASK << TMU_TEMP_EMU_SHIFT); >> + reg |= (temp_to_code(data, temp) << TMU_TEMP_EMU_SHIFT) | >> + TMU_EMUL_ENABLE; >> + } else { >> + reg &= ~TMU_EMUL_ENABLE; >> + } >> + >> + writel(reg, data->base + data->shift + TMU_S0_7_DEBUG); >> + mutex_unlock(&data->lock); >> + return 0; >> +out: >> + return -EINVAL; >> +} >> +#endif >> + >> +static void exynos_tmu_set_cooling(struct exynos_tmu_data *data, int >> level, >> + unsigned int cur_temp) >> +{ >> + struct exynos_tmu_platform_data *pdata = data->pdata; >> + bool check_rise, change; >> + unsigned int thres_temp, freq = 0, val; >> + int i, index, max_level = 0; >> + >> + /* Get the max level across all sensors except this */ >> + for (i = 0; i < tmu_common.sensor_count; i++) { >> + if (i == data->id) >> + continue; >> + if (tmu_common.level[i] > max_level) >> + max_level = tmu_common.level[i]; >> + } >> + change = false; >> + if (level < TMU_MAX_RISE_LEVEL) { >> + thres_temp = readl(data->base + data->shift + >> TMU_S0_7_TH0); >> + thres_temp = (thres_temp >> (level * 8) & TMU_TEMP_MASK); >> + check_rise = true; >> + tmu_common.level[data->id] = level + 1; >> + if (tmu_common.level[data->id] > max_level) >> + change = true; >> + } else { >> + level -= TMU_MAX_RISE_LEVEL; >> + thres_temp = readl(data->base + data->shift + >> TMU_S0_7_TH1); >> + thres_temp = (thres_temp >> (level * 8) & TMU_TEMP_MASK); >> + check_rise = false; >> + tmu_common.level[data->id] = level; >> + if (tmu_common.level[data->id] >= max_level) >> + change = true; >> + } >> + >> + if (change == false) >> + return; >> + >> + thres_temp = code_to_temp(data, thres_temp); >> + if (!check_rise) >> + thres_temp += pdata->threshold_falling; >> + >> + change = false; >> + /* find this threshold temp in the patform table cooling data */ >> + for (i = 0; i < pdata->freq_tab_count; i++) { >> + if (thres_temp != pdata->freq_tab[i].temp_level) >> + continue; >> + >> + if (check_rise && cur_temp >= thres_temp) { >> + freq = pdata->freq_tab[i].freq_clip_max; >> + change = true; >> + } >> + if (!check_rise && >> + (cur_temp <= (thres_temp - pdata->threshold_falling))) { >> + change = true; >> + freq = 0; >> + } >> + } >> + >> + /* critical threshold temp */ >> + if (thres_temp == pdata->trigger_levels[TMU_MAX_RISE_LEVEL - 1]) >> + exynos_report_trigger(data->reg_conf); >> + >> + if (change == false) >> + return; >> + >> + index = 0; >> + >> + if (freq) { >> + index = exynos_get_frequency_level(0, freq); >> + if (index < 0) >> + return; >> + } >> + >> + val = readl(data->base + TMU_PMIN); >> + val &= (~(TMU_PMIN_MASK << TMU_PMIN_SHIFT(level))); >> + val |= (index << TMU_PMIN_SHIFT(level)); >> + writel(val, data->base + TMU_PMIN); >> +} >> + >> +static void exynos_tmu_work(struct work_struct *work) >> +{ >> + struct exynos_tmu_data *data = container_of(work, >> + struct exynos_tmu_data, irq_work); >> + int i, cur_temp; >> + unsigned int val_type, val_irq; >> + >> + if (!data) >> + goto out; >> + >> + val_type = readl(data->base + TMU_IRQ_STATUS); >> + >> + /* Find which sensor generated this interrupt */ >> + if (!((val_type >> data->id) & 0x1)) >> + goto out; >> + >> + cur_temp = exynos_tmu_read(data); >> + val_irq = readl(data->base + data->shift + TMU_S0_7_IRQ); >> + for (i = 0; i < (TMU_MAX_RISE_LEVEL + TMU_MAX_FALL_LEVEL); i++) { >> + if (!((val_irq >> i) & 0x1)) >> + continue; >> + exynos_tmu_set_cooling(data, i, cur_temp); >> + } >> + /* clear the interrupts */ >> + writel(val_irq, data->base + data->shift + TMU_S0_7_IRQ); >> +out: >> + enable_irq(data->irq); >> +} >> + >> +static irqreturn_t exynos_tmu_irq(int irq, void *id) >> +{ >> + struct exynos_tmu_data *data = id; >> + >> + disable_irq_nosync(irq); >> + schedule_work(&data->irq_work); >> + >> + return IRQ_HANDLED; >> +} >> + >> +static const struct of_device_id exynos_tmu_match[] = { >> + { >> + .compatible = "samsung,exynos5440-tmu", >> + }, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(of, exynos_tmu_match); >> + >> +int exynos_map_dt_data(struct platform_device *pdev) >> +{ >> + struct exynos_tmu_data *data = platform_get_drvdata(pdev); >> + struct resource res; >> + >> + if (!data) >> + return -ENODEV; >> + >> + data->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl"); >> + if (data->id < 0) >> + data->id = 0; >> + >> + data->shift = data->id * 4; >> + >> + data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); >> + if (data->irq <= 0) { >> + dev_err(&pdev->dev, "failed to get IRQ\n"); >> + return -ENODEV; >> + } >> + >> + if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { >> + dev_err(&pdev->dev, "failed to get Resource\n"); >> + return -ENODEV; >> + } >> + >> + /* clear the last 16 bytes */ >> + res.start &= (~(0xFFFF)); >> + data->base = devm_ioremap(&pdev->dev, res.start, >> resource_size(&res)); >> + if (!data->base) { >> + dev_err(&pdev->dev, "Failed to ioremap memory\n"); >> + return -ENOMEM; >> + } >> + return 0; >> +} >> + >> +static int exynos_tmu_probe(struct platform_device *pdev) >> +{ >> + struct exynos_tmu_data *data; >> + struct exynos_tmu_platform_data *pdata; >> + struct thermal_sensor_conf *sensor_conf; >> + int ret, i; >> + >> + data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data), >> + GFP_KERNEL); >> + if (!data) { >> + dev_err(&pdev->dev, "Failed to allocate driver >> structure\n"); >> + return -ENOMEM; >> + } >> + >> + pdata = (struct exynos_tmu_platform_data *) >> + platform_get_device_id(pdev)->driver_data; >> + if (!pdata) { >> + dev_err(&pdev->dev, "No platform init data supplied.\n"); >> + return -ENODEV; >> + } >> + >> + data->pdata = pdata; >> + platform_set_drvdata(pdev, data); >> + >> + ret = exynos_map_dt_data(pdev); >> + if (ret) >> + goto unset_data; >> + >> + INIT_WORK(&data->irq_work, exynos_tmu_work); >> + >> + ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, >> + IRQF_TRIGGER_RISING|IRQF_SHARED, dev_name(&pdev->dev), >> data); >> + if (ret) { >> + dev_err(&pdev->dev, "Failed to request irq: %d\n", >> data->irq); >> + goto unset_data; >> + } >> + >> + data->clk = of_clk_get(pdev->dev.of_node, 0); >> + if (IS_ERR(data->clk)) { >> + dev_err(&pdev->dev, "Failed to get tmu clock\n"); >> + ret = PTR_ERR(data->clk); >> + goto unset_data; >> + } >> + clk_enable(data->clk); >> + > > > hmmm ok, you want it to be always running, right? > > >> + mutex_init(&data->lock); >> + >> + ret = exynos_tmu_initialize(pdev); >> + if (ret) { >> + dev_err(&pdev->dev, "Failed to initialize TMU\n"); >> + goto err_clk; >> + } >> + >> + exynos_tmu_control(pdev, true); >> + >> + /* Allocate a structure to register with the exynos core thermal >> */ >> + sensor_conf = devm_kzalloc(&pdev->dev, >> + sizeof(struct thermal_sensor_conf), >> GFP_KERNEL); >> + if (!sensor_conf) { >> + dev_err(&pdev->dev, "Failed to allocate registration >> struct\n"); >> + ret = -ENOMEM; >> + goto err_clk; >> + } >> + data->reg_conf = sensor_conf; >> + sprintf(sensor_conf->name, "therm_zone%d", data->id); >> + sensor_conf->read_temperature = (int (*)(void *))exynos_tmu_read; >> +#ifdef CONFIG_THERMAL_EMULATION >> + sensor_conf->write_emul_temp = >> + (int (*)(void *, unsigned long))exynos_tmu_set_emulation; >> +#endif > > > Do you really need this ifdef here? Cant you do same as you have done for > 4210? > > >> + sensor_conf->driver_data = data; >> + sensor_conf->trip_data.trip_count = pdata->trigger_enable[0] + >> + pdata->trigger_enable[1] + >> pdata->trigger_enable[2] + >> + pdata->trigger_enable[3]; >> + >> + for (i = 0; i < sensor_conf->trip_data.trip_count; i++) >> + sensor_conf->trip_data.trip_val[i] = >> pdata->trigger_levels[i]; >> + >> + sensor_conf->trip_data.trigger_falling = pdata->threshold_falling; >> + >> + /* Register the sensor with thermal management interface */ >> + ret = exynos_register_thermal(sensor_conf); >> + if (ret) { >> + dev_err(&pdev->dev, "Failed to register thermal >> interface\n"); >> + goto err_clk; >> + } >> + tmu_common.sensor_count++; >> + return 0; >> +err_clk: >> + clk_disable(data->clk); >> + clk_put(data->clk); >> +unset_data: >> + platform_set_drvdata(pdev, NULL); >> + return ret; >> +} >> + >> +static int exynos_tmu_remove(struct platform_device *pdev) >> +{ >> + struct exynos_tmu_data *data = platform_get_drvdata(pdev); >> + struct thermal_sensor_conf *sensor_conf = data->reg_conf; >> + >> + exynos_tmu_control(pdev, false); >> + clk_disable(data->clk); >> + >> + exynos_unregister_thermal(sensor_conf); >> + >> + clk_put(data->clk); >> + >> + platform_set_drvdata(pdev, NULL); >> + >> + return 0; >> +} >> + >> +#ifdef CONFIG_PM_SLEEP >> +static int exynos_tmu_suspend(struct device *dev) >> +{ >> + struct platform_device *pdev = to_platform_device(dev); >> + struct exynos_tmu_data *data = platform_get_drvdata(pdev); >> + >> + exynos_tmu_control(pdev, false); >> + clk_disable(data->clk); >> + >> + return 0; >> +} >> + >> +static int exynos_tmu_resume(struct device *dev) >> +{ >> + struct platform_device *pdev = to_platform_device(dev); >> + struct exynos_tmu_data *data = platform_get_drvdata(pdev); >> + >> + clk_enable(data->clk); >> + exynos_tmu_initialize(pdev); >> + exynos_tmu_control(pdev, true); >> + >> + return 0; >> +} >> + >> +static SIMPLE_DEV_PM_OPS(exynos_tmu_pm, >> + exynos_tmu_suspend, exynos_tmu_resume); >> +#define EXYNOS_TMU_PM (&exynos_tmu_pm) >> +#else >> +#define EXYNOS_TMU_PM NULL >> +#endif >> + >> +static struct platform_driver exynos_tmu_driver = { >> + .driver = { >> + .name = "exynos5440-tmu", >> + .owner = THIS_MODULE, >> + .pm = EXYNOS_TMU_PM, >> + .of_match_table = exynos_tmu_match, >> + }, >> + .probe = exynos_tmu_probe, >> + .remove = exynos_tmu_remove, >> +}; >> + >> +module_platform_driver(exynos_tmu_driver); >> + >> +MODULE_DESCRIPTION("EXYNOS5440 TMU Driver"); >> +MODULE_AUTHOR("Amit Daniel<amit.daniel@samsung.com>"); >> +MODULE_LICENSE("GPL"); > > GPL v2? > >> +MODULE_ALIAS("platform:exynos5440-tmu"); >> > -- To unsubscribe from this list: send the line "unsubscribe linux-pm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index cefe693..0c7b4eb 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -20,4 +20,13 @@ config EXYNOS4210_THERMAL initialises the TMU controller and registers/unregisters with exynos common thermal layer. +config EXYNOS5440_THERMAL + tristate "Temperature sensor on Samsung EXYNOS 5440 SOC" + depends on SOC_EXYNOS5440 + help + If you say yes here you can enable TMU (Thermal Management Unit) + support on SAMSUNG EXYNOS 5440 series of SoC. This option initialises + the TMU controller and registers/unregisters with exynos common + thermal layer. + endif diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile index d51d0c2..53230cf 100644 --- a/drivers/thermal/samsung/Makefile +++ b/drivers/thermal/samsung/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_EXYNOS_COMMON) += exynos_common.o obj-$(CONFIG_EXYNOS4210_THERMAL) += exynos4210_thermal.o +obj-$(CONFIG_EXYNOS5440_THERMAL) += exynos5440_thermal.o diff --git a/drivers/thermal/samsung/exynos5440_thermal.c b/drivers/thermal/samsung/exynos5440_thermal.c new file mode 100644 index 0000000..a3c75d3 --- /dev/null +++ b/drivers/thermal/samsung/exynos5440_thermal.c @@ -0,0 +1,713 @@ +/* + * exynos5440_thermal.c - Samsung EXYNOS 5440 TMU + * (Thermal Management Unit) + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap <amit.daniel@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h> +#include <linux/workqueue.h> +#include <linux/platform_data/exynos_thermal.h> + +#include "exynos_common.h" + + +/* Exynos5440 specific registers */ +#define TMU_S0_7_TRIM 0x0118 +#define TMU_S0_7_CTRL 0x0138 +#define TMU_S0_7_DEBUG 0x0158 +#define TMU_S0_7_STATUS 0x0178 +#define TMU_S0_7_COUNTER0 0x0198 +#define TMU_S0_7_COUNTER1 0x01b8 +#define TMU_S0_7_COUNTER2 0x01d8 +#define TMU_S0_7_COUNTER3 0x01f8 +#define TMU_S0_7_TEMP 0x0208 +#define TMU_S0_7_TH0 0x0228 +#define TMU_S0_7_TH1 0x0248 +#define TMU_S0_7_TH2 0x0268 +#define TMU_S0_7_PTEMP0 0x0288 +#define TMU_S0_7_PTEMP1 0x02a8 +#define TMU_S0_7_PTEMP2 0x02c8 +#define TMU_S0_7_PTEMP3 0x02e8 +#define TMU_S0_7_EVTEN 0x0308 +#define TMU_S0_7_IRQEN 0x0328 +#define TMU_S0_7_IRQ 0x0348 +#define TMU_IRQ_STATUS 0x0368 +#define TMU_PMIN 0x036c +#define TMU_TEMP 0x0370 +#define TMU_MISC 0x0374 + +/* Exynos5440 specific mask and shifts */ +#define TMU_TEMP_MASK 0xff + +#define TMU_TRIM_DATA_25C_SHIFT 0x0 +#define TMU_TRIM_DATA_85C_SHIFT 0x8 + +#define TMU_BUF_VREF_SEL_MASK 0x1f +#define TMU_BUF_VREF_SEL_SHIFT 24 +#define TMU_THERM_TRIP_MODE_MASK 0x7 +#define TMU_THERM_TRIP_MODE_SHIFT 13 +#define TMU_THERM_TRIP_EN_SHIFT 12 +#define TMU_BUF_SLOPE_SEL_MASK 0Xf +#define TMU_BUF_SLOPE_SEL_SHIFT 8 +#define TMU_THERM_IRQ_MODE_SHIFT 7 +#define TMU_CALIB_MODE_MASK 0x3 +#define TMU_CALIB_MODE_SHIFT 4 +#define TMU_FILTER_MODE_MASK 0x7 +#define TMU_FILTER_MODE_SHIFT 1 +#define TMU_SENSOR_EN_SHIFT 0 +#define TMU_SENSOR_ENABLE 0x1 + +#define TMU_EMU_EN_SHIFT 0 +#define TMU_TEMP_EMU_SHIFT 8 +#define TMU_EMUL_ENABLE 1 + +#define TMU_STATUS_IDLE_SHIFT 0 + +#define TMU_TIME_MASK 0xffff +#define TMU_TIME_OF_SHIFT 16 +#define TMU_TIME_ON_SHIFT 0 + +#define TMU_CURRENT_TEMP_SHIFT 0 +#define TMU_FILTERED_TEMP_SHIFT 8 +#define TMU_RAW_TEMP_SHIFT 16 +#define TMU_TEMP_SEQNUM 24 + +#define TMU_THRES_RISE0_SHIFT 0 +#define TMU_THRES_RISE1_SHIFT 8 +#define TMU_THRES_RISE2_SHIFT 16 +#define TMU_THRES_RISE3_SHIFT 24 + +#define TMU_THRES_FALL0_SHIFT 0 +#define TMU_THRES_FALL1_SHIFT 8 +#define TMU_THRES_FALL2_SHIFT 16 +#define TMU_THRES_FALL3_SHIFT 24 + +#define TMU_THRES_RISE4_SHIFT 24 + +#define TMU_RISE_EVTEN_MASK 0xf +#define TMU_RISE_EVTEN_SHIFT 0 +#define TMU_FALL_EVTEN_MASK 0xf +#define TMU_FALL_EVTEN_SHIFT 4 + +#define TMU_RISE_IRQEN_MASK 0xf +#define TMU_RISE_IRQEN_SHIFT 0 +#define TMU_FALL_IRQEN_MASK 0xf +#define TMU_FALL_IRQEN_SHIFT 4 +#define TMU_CLEAR_RISE_INT TMU_RISE_IRQEN_MASK +#define TMU_CLEAR_FALL_INT (TMU_FALL_IRQEN_MASK << 4) + +#define TMU_PMIN_MASK 0x7 +#define TMU_PMIN0_SHIFT 0 +#define TMU_PMIN1_SHIFT 4 +#define TMU_PMIN2_SHIFT 8 +#define TMU_PMIN3_SHIFT 12 +#define TMU_PMIN_SHIFT(x) (4 * x) +#define TMU_TPMIN_SHIFT 16 + +#define TMU_TEMP_MAX_SHIFT 0 +#define TMU_MAX_RISE_LEVEL 4 +#define TMU_MAX_FALL_LEVEL 4 +#define TMU_MAX_SENSOR 8 + +#define TMU_DEF_CODE_TO_TEMP_OFFSET 20 + +struct exynos_tmu_data { + int irq; + int id; + unsigned int shift; + enum soc_type soc; + void __iomem *base; + struct clk *clk; + struct work_struct irq_work; + u8 temp_error1, temp_error2; + struct mutex lock; + struct thermal_sensor_conf *reg_conf; + struct exynos_tmu_platform_data *pdata; +}; + +struct exynos_tmu_common { + int level[TMU_MAX_SENSOR]; + int sensor_count; +}; +static struct exynos_tmu_common tmu_common; +/* + * TMU treats temperature as a mapped temperature code. + * The temperature is converted differently depending on the calibration type. + */ +static int temp_to_code(struct exynos_tmu_data *data, u8 temp) +{ + struct exynos_tmu_platform_data *pdata = data->pdata; + int temp_code; + + if (pdata->cal_mode == HW_MODE) + return temp; + + switch (pdata->cal_type) { + case TYPE_TWO_POINT_TRIMMING: + temp_code = (temp - 25) * + (data->temp_error2 - data->temp_error1) / + (70 - 25) + data->temp_error1; + break; + case TYPE_ONE_POINT_TRIMMING: + temp_code = temp + data->temp_error1 - 25; + break; + default: + temp_code = temp + TMU_DEF_CODE_TO_TEMP_OFFSET; + break; + } + + return temp_code; +} + +/* + * Calculate a temperature value from a temperature code. + * The unit of the temperature is degree Celsius. + */ +static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code) +{ + struct exynos_tmu_platform_data *pdata = data->pdata; + int temp; + + if (pdata->cal_mode == HW_MODE) + return temp_code; + + switch (pdata->cal_type) { + case TYPE_TWO_POINT_TRIMMING: + temp = (temp_code - data->temp_error1) * (70 - 25) / + (data->temp_error2 - data->temp_error1) + 25; + break; + case TYPE_ONE_POINT_TRIMMING: + temp = temp_code - data->temp_error1 + 25; + break; + default: + temp = temp_code - TMU_DEF_CODE_TO_TEMP_OFFSET; + break; + } + + return temp; +} + +static int exynos_tmu_initialize(struct platform_device *pdev) +{ + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + struct exynos_tmu_platform_data *pdata = data->pdata; + unsigned int status, con, trim_info; + unsigned int rising_threshold = 0, falling_threshold = 0; + int ret = 0, threshold_code, i, trigger_levs = 0; + + status = readl(data->base + data->shift + TMU_S0_7_STATUS); + status &= 0x1; + if (!status) + dev_err(&pdev->dev, "Sensor Initial status is busy\n"); + + if (pdata->cal_mode == HW_MODE) + goto skip_calib_data; + + /* Save trimming info in order to perform calibration */ + trim_info = readl(data->base + data->shift + TMU_S0_7_TRIM); + data->temp_error1 = trim_info & TMU_TEMP_MASK; + data->temp_error2 = ((trim_info >> 8) & TMU_TEMP_MASK); + if (!data->temp_error1) + data->temp_error1 = pdata->efuse_value & TMU_TEMP_MASK; + if (!data->temp_error2) + data->temp_error2 = (pdata->efuse_value >> 8) & TMU_TEMP_MASK; + +skip_calib_data: + /* Count trigger levels to be enabled */ + for (i = 0; i < MAX_THRESHOLD_LEVS; i++) + if (pdata->trigger_levels[i]) + trigger_levs++; + + /* Write temperature code for rising and falling threshold */ + for (i = 0; (i < trigger_levs && i < TMU_MAX_RISE_LEVEL); i++) { + threshold_code = temp_to_code(data, + pdata->trigger_levels[i]); + if (threshold_code < 0) { + ret = threshold_code; + dev_err(&pdev->dev, "Invalid threshold=%d level=%d\n", + threshold_code, i); + goto out; + } + rising_threshold |= threshold_code << 8 * i; + if (pdata->threshold_falling) { + threshold_code = temp_to_code(data, + pdata->trigger_levels[i] - + pdata->threshold_falling); + if (threshold_code > 0) + falling_threshold |= + threshold_code << 8 * i; + } + } + writel(rising_threshold, + data->base + data->shift + TMU_S0_7_TH0); + writel(falling_threshold, + data->base + data->shift + TMU_S0_7_TH1); + + /* if 5th threshold limit is also present */ + if (i == TMU_MAX_RISE_LEVEL) { + threshold_code = temp_to_code(data, + pdata->trigger_levels[i]); + if (threshold_code < 0) { + ret = threshold_code; + dev_err(&pdev->dev, "Invalid threshold=%d level=%d\n", + threshold_code, i); + goto out; + } + rising_threshold = threshold_code << TMU_THRES_RISE4_SHIFT; + writel(rising_threshold, + data->base + data->shift + TMU_S0_7_TH2); + con = readl(data->base + data->shift + TMU_S0_7_CTRL); + con |= (1 << TMU_THERM_TRIP_EN_SHIFT); + writel(con, data->base + data->shift + TMU_S0_7_CTRL); + } + + writel(TMU_CLEAR_RISE_INT | TMU_CLEAR_FALL_INT, + data->base + data->shift + TMU_S0_7_IRQ); + + /* clear all PMIN */ + writel(0, data->base + TMU_PMIN); +out: + return ret; +} + +static void exynos_tmu_control(struct platform_device *pdev, bool on) +{ + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + struct exynos_tmu_platform_data *pdata = data->pdata; + unsigned int con, interrupt_en; + + mutex_lock(&data->lock); + con = readl(data->base + data->shift + TMU_S0_7_CTRL); + con &= ~(TMU_BUF_VREF_SEL_MASK << TMU_BUF_VREF_SEL_SHIFT | + TMU_THERM_TRIP_MODE_MASK << TMU_THERM_TRIP_MODE_SHIFT | + TMU_BUF_SLOPE_SEL_MASK << TMU_BUF_SLOPE_SEL_SHIFT | + TMU_CALIB_MODE_MASK << TMU_CALIB_MODE_SHIFT | + TMU_FILTER_MODE_MASK << TMU_FILTER_MODE_SHIFT | + TMU_SENSOR_ENABLE << TMU_SENSOR_EN_SHIFT); + + con |= pdata->reference_voltage << TMU_BUF_VREF_SEL_SHIFT | + pdata->gain << TMU_BUF_SLOPE_SEL_SHIFT; + + if (pdata->cal_mode == HW_MODE) + con |= pdata->cal_type << TMU_CALIB_MODE_SHIFT; + + con |= pdata->noise_cancel_mode << TMU_THERM_TRIP_MODE_SHIFT; + + if (on) { + con |= TMU_SENSOR_ENABLE; + interrupt_en = + pdata->trigger_enable[3] << 3 | + pdata->trigger_enable[2] << 2 | + pdata->trigger_enable[1] << 1 | + pdata->trigger_enable[0] << 0; + if (pdata->threshold_falling) + interrupt_en |= interrupt_en << TMU_FALL_IRQEN_SHIFT; + } else { + interrupt_en = 0; /* Disable all interrupts */ + } + writel(interrupt_en, data->base + data->shift + TMU_S0_7_IRQEN); + writel(interrupt_en, data->base + data->shift + TMU_S0_7_EVTEN); + writel(con, data->base + data->shift + TMU_S0_7_CTRL); + + mutex_unlock(&data->lock); +} + +static int exynos_tmu_read(struct exynos_tmu_data *data) +{ + u8 temp_code; + int temp; + + mutex_lock(&data->lock); + + temp_code = readl(data->base + data->shift + TMU_S0_7_TEMP); + temp_code >>= TMU_CURRENT_TEMP_SHIFT; + temp_code &= TMU_TEMP_MASK; + temp = code_to_temp(data, temp_code); + + mutex_unlock(&data->lock); + + return temp; +} + +#ifdef CONFIG_THERMAL_EMULATION +static int exynos_tmu_set_emulation(struct exynos_tmu_data *data, + unsigned long temp) +{ + unsigned int reg; + + if (temp && temp < MCELSIUS) + goto out; + + mutex_lock(&data->lock); + reg = readl(data->base + data->shift + TMU_S0_7_DEBUG); + + if (temp) { + temp /= MCELSIUS; + reg &= ~(TMU_TEMP_MASK << TMU_TEMP_EMU_SHIFT); + reg |= (temp_to_code(data, temp) << TMU_TEMP_EMU_SHIFT) | + TMU_EMUL_ENABLE; + } else { + reg &= ~TMU_EMUL_ENABLE; + } + + writel(reg, data->base + data->shift + TMU_S0_7_DEBUG); + mutex_unlock(&data->lock); + return 0; +out: + return -EINVAL; +} +#endif + +static void exynos_tmu_set_cooling(struct exynos_tmu_data *data, int level, + unsigned int cur_temp) +{ + struct exynos_tmu_platform_data *pdata = data->pdata; + bool check_rise, change; + unsigned int thres_temp, freq = 0, val; + int i, index, max_level = 0; + + /* Get the max level across all sensors except this */ + for (i = 0; i < tmu_common.sensor_count; i++) { + if (i == data->id) + continue; + if (tmu_common.level[i] > max_level) + max_level = tmu_common.level[i]; + } + change = false; + if (level < TMU_MAX_RISE_LEVEL) { + thres_temp = readl(data->base + data->shift + TMU_S0_7_TH0); + thres_temp = (thres_temp >> (level * 8) & TMU_TEMP_MASK); + check_rise = true; + tmu_common.level[data->id] = level + 1; + if (tmu_common.level[data->id] > max_level) + change = true; + } else { + level -= TMU_MAX_RISE_LEVEL; + thres_temp = readl(data->base + data->shift + TMU_S0_7_TH1); + thres_temp = (thres_temp >> (level * 8) & TMU_TEMP_MASK); + check_rise = false; + tmu_common.level[data->id] = level; + if (tmu_common.level[data->id] >= max_level) + change = true; + } + + if (change == false) + return; + + thres_temp = code_to_temp(data, thres_temp); + if (!check_rise) + thres_temp += pdata->threshold_falling; + + change = false; + /* find this threshold temp in the patform table cooling data */ + for (i = 0; i < pdata->freq_tab_count; i++) { + if (thres_temp != pdata->freq_tab[i].temp_level) + continue; + + if (check_rise && cur_temp >= thres_temp) { + freq = pdata->freq_tab[i].freq_clip_max; + change = true; + } + if (!check_rise && + (cur_temp <= (thres_temp - pdata->threshold_falling))) { + change = true; + freq = 0; + } + } + + /* critical threshold temp */ + if (thres_temp == pdata->trigger_levels[TMU_MAX_RISE_LEVEL - 1]) + exynos_report_trigger(data->reg_conf); + + if (change == false) + return; + + index = 0; + + if (freq) { + index = exynos_get_frequency_level(0, freq); + if (index < 0) + return; + } + + val = readl(data->base + TMU_PMIN); + val &= (~(TMU_PMIN_MASK << TMU_PMIN_SHIFT(level))); + val |= (index << TMU_PMIN_SHIFT(level)); + writel(val, data->base + TMU_PMIN); +} + +static void exynos_tmu_work(struct work_struct *work) +{ + struct exynos_tmu_data *data = container_of(work, + struct exynos_tmu_data, irq_work); + int i, cur_temp; + unsigned int val_type, val_irq; + + if (!data) + goto out; + + val_type = readl(data->base + TMU_IRQ_STATUS); + + /* Find which sensor generated this interrupt */ + if (!((val_type >> data->id) & 0x1)) + goto out; + + cur_temp = exynos_tmu_read(data); + val_irq = readl(data->base + data->shift + TMU_S0_7_IRQ); + for (i = 0; i < (TMU_MAX_RISE_LEVEL + TMU_MAX_FALL_LEVEL); i++) { + if (!((val_irq >> i) & 0x1)) + continue; + exynos_tmu_set_cooling(data, i, cur_temp); + } + /* clear the interrupts */ + writel(val_irq, data->base + data->shift + TMU_S0_7_IRQ); +out: + enable_irq(data->irq); +} + +static irqreturn_t exynos_tmu_irq(int irq, void *id) +{ + struct exynos_tmu_data *data = id; + + disable_irq_nosync(irq); + schedule_work(&data->irq_work); + + return IRQ_HANDLED; +} + +static const struct of_device_id exynos_tmu_match[] = { + { + .compatible = "samsung,exynos5440-tmu", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_tmu_match); + +int exynos_map_dt_data(struct platform_device *pdev) +{ + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + struct resource res; + + if (!data) + return -ENODEV; + + data->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl"); + if (data->id < 0) + data->id = 0; + + data->shift = data->id * 4; + + data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); + if (data->irq <= 0) { + dev_err(&pdev->dev, "failed to get IRQ\n"); + return -ENODEV; + } + + if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { + dev_err(&pdev->dev, "failed to get Resource\n"); + return -ENODEV; + } + + /* clear the last 16 bytes */ + res.start &= (~(0xFFFF)); + data->base = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); + if (!data->base) { + dev_err(&pdev->dev, "Failed to ioremap memory\n"); + return -ENOMEM; + } + return 0; +} + +static int exynos_tmu_probe(struct platform_device *pdev) +{ + struct exynos_tmu_data *data; + struct exynos_tmu_platform_data *pdata; + struct thermal_sensor_conf *sensor_conf; + int ret, i; + + data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data), + GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "Failed to allocate driver structure\n"); + return -ENOMEM; + } + + pdata = (struct exynos_tmu_platform_data *) + platform_get_device_id(pdev)->driver_data; + if (!pdata) { + dev_err(&pdev->dev, "No platform init data supplied.\n"); + return -ENODEV; + } + + data->pdata = pdata; + platform_set_drvdata(pdev, data); + + ret = exynos_map_dt_data(pdev); + if (ret) + goto unset_data; + + INIT_WORK(&data->irq_work, exynos_tmu_work); + + ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, + IRQF_TRIGGER_RISING|IRQF_SHARED, dev_name(&pdev->dev), data); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); + goto unset_data; + } + + data->clk = of_clk_get(pdev->dev.of_node, 0); + if (IS_ERR(data->clk)) { + dev_err(&pdev->dev, "Failed to get tmu clock\n"); + ret = PTR_ERR(data->clk); + goto unset_data; + } + clk_enable(data->clk); + + mutex_init(&data->lock); + + ret = exynos_tmu_initialize(pdev); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize TMU\n"); + goto err_clk; + } + + exynos_tmu_control(pdev, true); + + /* Allocate a structure to register with the exynos core thermal */ + sensor_conf = devm_kzalloc(&pdev->dev, + sizeof(struct thermal_sensor_conf), GFP_KERNEL); + if (!sensor_conf) { + dev_err(&pdev->dev, "Failed to allocate registration struct\n"); + ret = -ENOMEM; + goto err_clk; + } + data->reg_conf = sensor_conf; + sprintf(sensor_conf->name, "therm_zone%d", data->id); + sensor_conf->read_temperature = (int (*)(void *))exynos_tmu_read; +#ifdef CONFIG_THERMAL_EMULATION + sensor_conf->write_emul_temp = + (int (*)(void *, unsigned long))exynos_tmu_set_emulation; +#endif + sensor_conf->driver_data = data; + sensor_conf->trip_data.trip_count = pdata->trigger_enable[0] + + pdata->trigger_enable[1] + pdata->trigger_enable[2] + + pdata->trigger_enable[3]; + + for (i = 0; i < sensor_conf->trip_data.trip_count; i++) + sensor_conf->trip_data.trip_val[i] = pdata->trigger_levels[i]; + + sensor_conf->trip_data.trigger_falling = pdata->threshold_falling; + + /* Register the sensor with thermal management interface */ + ret = exynos_register_thermal(sensor_conf); + if (ret) { + dev_err(&pdev->dev, "Failed to register thermal interface\n"); + goto err_clk; + } + tmu_common.sensor_count++; + return 0; +err_clk: + clk_disable(data->clk); + clk_put(data->clk); +unset_data: + platform_set_drvdata(pdev, NULL); + return ret; +} + +static int exynos_tmu_remove(struct platform_device *pdev) +{ + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + struct thermal_sensor_conf *sensor_conf = data->reg_conf; + + exynos_tmu_control(pdev, false); + clk_disable(data->clk); + + exynos_unregister_thermal(sensor_conf); + + clk_put(data->clk); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int exynos_tmu_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + + exynos_tmu_control(pdev, false); + clk_disable(data->clk); + + return 0; +} + +static int exynos_tmu_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + + clk_enable(data->clk); + exynos_tmu_initialize(pdev); + exynos_tmu_control(pdev, true); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(exynos_tmu_pm, + exynos_tmu_suspend, exynos_tmu_resume); +#define EXYNOS_TMU_PM (&exynos_tmu_pm) +#else +#define EXYNOS_TMU_PM NULL +#endif + +static struct platform_driver exynos_tmu_driver = { + .driver = { + .name = "exynos5440-tmu", + .owner = THIS_MODULE, + .pm = EXYNOS_TMU_PM, + .of_match_table = exynos_tmu_match, + }, + .probe = exynos_tmu_probe, + .remove = exynos_tmu_remove, +}; + +module_platform_driver(exynos_tmu_driver); + +MODULE_DESCRIPTION("EXYNOS5440 TMU Driver"); +MODULE_AUTHOR("Amit Daniel<amit.daniel@samsung.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:exynos5440-tmu");
This sensor registers 3 instance of the tmu controller with the thermal zone and hence reports 3 temperature output. This driver supports upto five trip points. For critical threshold the driver uses the core driver thermal framework for shutdown and for non-critical threshold it invokes the hw based frequency clipping limits. Because of such differences with the existing 4210 tmu controller, exynos5440 tmu driver is added in a new file. Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com> --- drivers/thermal/samsung/Kconfig | 9 + drivers/thermal/samsung/Makefile | 1 + drivers/thermal/samsung/exynos5440_thermal.c | 713 ++++++++++++++++++++++++++ 3 files changed, 723 insertions(+), 0 deletions(-) create mode 100644 drivers/thermal/samsung/exynos5440_thermal.c