Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934192Ab3DKUbQ (ORCPT ); Thu, 11 Apr 2013 16:31:16 -0400 Received: from bear.ext.ti.com ([192.94.94.41]:42790 "EHLO bear.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1758479Ab3DKUbM (ORCPT ); Thu, 11 Apr 2013 16:31:12 -0400 Message-ID: <51671D77.1020504@ti.com> Date: Thu, 11 Apr 2013 16:30:47 -0400 From: Eduardo Valentin User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130308 Thunderbird/17.0.4 MIME-Version: 1.0 To: Amit Daniel Kachhap CC: , Thomas Abraham , Zhang Rui , , , , Kukjin Kim , Subject: Re: [4/9] thermal: exynos: Bifurcate exynos thermal common and tmu controller code References: <1364297642-2746-5-git-send-email-amit.daniel@samsung.com> In-Reply-To: <1364297642-2746-5-git-send-email-amit.daniel@samsung.com> Content-Type: text/plain; charset="ISO-8859-1"; format=flowed Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 71127 Lines: 2359 Amit, On 26-03-2013 07:33, Amit Daniel Kachhap wrote: > This code bifurcates exynos thermal implementation into common and sensor > specific parts as it will simplify adding support for new temperature > sensors. The file is named as exynos4210 because it was original SOC for > which this driver was developed and then later SOC's(5250, 4412) were added > into it. This change is needed to add different TMU sensor for future exynos5 > SOC. > > Signed-off-by: Amit Daniel Kachhap > > --- > drivers/thermal/samsung/Kconfig | 24 +- > drivers/thermal/samsung/Makefile | 4 +- > drivers/thermal/samsung/exynos4210_thermal.c | 658 ++++++++++++++++ > drivers/thermal/samsung/exynos_common.c | 421 ++++++++++ > drivers/thermal/samsung/exynos_common.h | 73 ++ > drivers/thermal/samsung/exynos_thermal.c | 1093 -------------------------- > 6 files changed, 1172 insertions(+), 1101 deletions(-) > create mode 100644 drivers/thermal/samsung/exynos4210_thermal.c > create mode 100644 drivers/thermal/samsung/exynos_common.c > create mode 100644 drivers/thermal/samsung/exynos_common.h > delete mode 100644 drivers/thermal/samsung/exynos_thermal.c > Same comment as in patch 3. Can you please use --find-renames? Besides, it would be interesting if you easier the review process by splitting this patch into 2 at least. One for creating the _common.* and another to rename the remaining to 4210_thermal. BTW, This follow same design pattern as under drivers/stating/ti-soc-thermal/ :-). I had to do similar design by having common and specific split so that I could afford the support for IPs with several sensors. Like OMAP5. > diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig > index 5737b85..cefe693 100644 > --- a/drivers/thermal/samsung/Kconfig > +++ b/drivers/thermal/samsung/Kconfig > @@ -1,11 +1,23 @@ > > -config EXYNOS_THERMAL > - tristate "Temperature sensor on Samsung EXYNOS" > +config EXYNOS_COMMON > + tristate "Common thermal support for EXYNOS SOC's" > depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5) > + help > + If you say yes here you get support for EXYNOS TMU > + (Thermal Management Unit) common registration/unregistration > + functions to the core thermal layer and also to use the generic > + cpu cooling API's. > + > +if EXYNOS_COMMON > + > +config EXYNOS4210_THERMAL > + tristate "Temperature sensor on Samsung EXYNOS series SOC" > + depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412 || SOC_EXYNOS5250) > depends on CPU_THERMAL > help > - If you say yes here you get support for TMU (Thermal Management > - Unit) on SAMSUNG EXYNOS series of SoC. This helps in registering > - the exynos thermal driver with the core thermal layer and cpu > - cooling API's. > + If you say yes here you can enable TMU (Thermal Management Unit) on > + SAMSUNG EXYNOS 4210, 4412, 4414 and 5250 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 fa55df5..d51d0c2 100644 > --- a/drivers/thermal/samsung/Makefile > +++ b/drivers/thermal/samsung/Makefile > @@ -1,5 +1,5 @@ > # > # Samsung thermal specific Makefile > # > -obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o > - > +obj-$(CONFIG_EXYNOS_COMMON) += exynos_common.o > +obj-$(CONFIG_EXYNOS4210_THERMAL) += exynos4210_thermal.o > diff --git a/drivers/thermal/samsung/exynos4210_thermal.c b/drivers/thermal/samsung/exynos4210_thermal.c > new file mode 100644 > index 0000000..09ea8c8 > --- /dev/null > +++ b/drivers/thermal/samsung/exynos4210_thermal.c > @@ -0,0 +1,658 @@ > +/* > + * exynos4210_thermal.c - Samsung EXYNOS 4210, 4412, 5250 TMU > + * (Thermal Management Unit) > + * > + * Copyright (C) 2011 Samsung Electronics > + * Donggeun Kim > + * Amit Daniel Kachhap > + * Amit Daniel Kachhap > + * > + * 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include "exynos_common.h" > + > +/* Exynos generic registers */ > +#define EXYNOS_TMU_REG_TRIMINFO 0x0 > +#define EXYNOS_TMU_REG_CONTROL 0x20 > +#define EXYNOS_TMU_REG_STATUS 0x28 > +#define EXYNOS_TMU_REG_CURRENT_TEMP 0x40 > +#define EXYNOS_TMU_REG_INTEN 0x70 > +#define EXYNOS_TMU_REG_INTSTAT 0x74 > +#define EXYNOS_TMU_REG_INTCLEAR 0x78 > + > +#define EXYNOS_TMU_TRIM_TEMP_MASK 0xff > +#define EXYNOS_TMU_GAIN_SHIFT 8 > +#define EXYNOS_TMU_REF_VOLTAGE_SHIFT 24 > +#define EXYNOS_TMU_CORE_ON 3 > +#define EXYNOS_TMU_CORE_OFF 2 > +#define EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET 50 > + > +/* Exynos4210 specific registers */ > +#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP 0x44 > +#define EXYNOS4210_TMU_REG_TRIG_LEVEL0 0x50 > +#define EXYNOS4210_TMU_REG_TRIG_LEVEL1 0x54 > +#define EXYNOS4210_TMU_REG_TRIG_LEVEL2 0x58 > +#define EXYNOS4210_TMU_REG_TRIG_LEVEL3 0x5C > +#define EXYNOS4210_TMU_REG_PAST_TEMP0 0x60 > +#define EXYNOS4210_TMU_REG_PAST_TEMP1 0x64 > +#define EXYNOS4210_TMU_REG_PAST_TEMP2 0x68 > +#define EXYNOS4210_TMU_REG_PAST_TEMP3 0x6C > + > +#define EXYNOS4210_TMU_TRIG_LEVEL0_MASK 0x1 > +#define EXYNOS4210_TMU_TRIG_LEVEL1_MASK 0x10 > +#define EXYNOS4210_TMU_TRIG_LEVEL2_MASK 0x100 > +#define EXYNOS4210_TMU_TRIG_LEVEL3_MASK 0x1000 > +#define EXYNOS4210_TMU_INTCLEAR_VAL 0x1111 > + > +/* Exynos5250 and Exynos4412 specific registers */ > +#define EXYNOS_TMU_TRIMINFO_CON 0x14 > +#define EXYNOS_THD_TEMP_RISE 0x50 > +#define EXYNOS_THD_TEMP_FALL 0x54 > +#define EXYNOS_EMUL_CON 0x80 > + > +#define EXYNOS_TRIMINFO_RELOAD 0x1 > +#define EXYNOS_TMU_CLEAR_RISE_INT 0x111 > +#define EXYNOS_TMU_CLEAR_FALL_INT (0x111 << 12) > +#define EXYNOS_MUX_ADDR_VALUE 6 > +#define EXYNOS_MUX_ADDR_SHIFT 20 > +#define EXYNOS_TMU_TRIP_MODE_SHIFT 13 > + > +#define EFUSE_MIN_VALUE 40 > +#define EFUSE_MAX_VALUE 100 > + > +#ifdef CONFIG_THERMAL_EMULATION > +#define EXYNOS_EMUL_TIME 0x57F0 > +#define EXYNOS_EMUL_TIME_SHIFT 16 > +#define EXYNOS_EMUL_DATA_SHIFT 8 > +#define EXYNOS_EMUL_DATA_MASK 0xFF > +#define EXYNOS_EMUL_ENABLE 0x1 > +#endif /* CONFIG_THERMAL_EMULATION */ > + > +struct exynos_tmu_data { > + struct exynos_tmu_platform_data *pdata; > + struct resource *mem; > + void __iomem *base; > + int irq; > + enum soc_type soc; > + struct work_struct irq_work; > + struct mutex lock; > + struct clk *clk; > + u8 temp_error1, temp_error2; > +}; > + > +/* > + * 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 (data->soc == SOC_ARCH_EXYNOS4210) > + /* temp should range between 25 and 125 */ > + if (temp < 25 || temp > 125) { > + temp_code = -EINVAL; > + goto out; > + } > + > + switch (pdata->cal_type) { > + case TYPE_TWO_POINT_TRIMMING: > + temp_code = (temp - 25) * > + (data->temp_error2 - data->temp_error1) / > + (85 - 25) + data->temp_error1; > + break; > + case TYPE_ONE_POINT_TRIMMING: > + temp_code = temp + data->temp_error1 - 25; > + break; > + default: > + temp_code = temp + EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET; > + break; > + } > +out: > + 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 (data->soc == SOC_ARCH_EXYNOS4210) > + /* temp_code should range between 75 and 175 */ > + if (temp_code < 75 || temp_code > 175) { > + temp = -ENODATA; > + goto out; > + } > + > + switch (pdata->cal_type) { > + case TYPE_TWO_POINT_TRIMMING: > + temp = (temp_code - data->temp_error1) * (85 - 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 - EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET; > + break; > + } > +out: > + 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, trim_info; > + unsigned int rising_threshold = 0, falling_threshold = 0; > + int ret = 0, threshold_code, i, trigger_levs = 0; > + > + mutex_lock(&data->lock); > + clk_enable(data->clk); > + > + status = readb(data->base + EXYNOS_TMU_REG_STATUS); > + if (!status) { > + ret = -EBUSY; > + goto out; > + } > + > + if (data->soc == SOC_ARCH_EXYNOS) { > + __raw_writel(EXYNOS_TRIMINFO_RELOAD, > + data->base + EXYNOS_TMU_TRIMINFO_CON); > + } > + /* Save trimming info in order to perform calibration */ > + trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); > + data->temp_error1 = trim_info & EXYNOS_TMU_TRIM_TEMP_MASK; > + data->temp_error2 = ((trim_info >> 8) & EXYNOS_TMU_TRIM_TEMP_MASK); > + > + if ((EFUSE_MIN_VALUE > data->temp_error1) || > + (data->temp_error1 > EFUSE_MAX_VALUE) || > + (data->temp_error2 != 0)) > + data->temp_error1 = pdata->efuse_value; > + > + /* Count trigger levels to be enabled */ > + for (i = 0; i < MAX_THRESHOLD_LEVS; i++) > + if (pdata->trigger_levels[i]) > + trigger_levs++; > + > + if (data->soc == SOC_ARCH_EXYNOS4210) { > + /* Write temperature code for threshold */ > + threshold_code = temp_to_code(data, pdata->threshold); > + if (threshold_code < 0) { > + ret = threshold_code; > + goto out; > + } > + writeb(threshold_code, > + data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP); > + for (i = 0; i < trigger_levs; i++) > + writeb(pdata->trigger_levels[i], > + data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4); > + > + writel(EXYNOS4210_TMU_INTCLEAR_VAL, > + data->base + EXYNOS_TMU_REG_INTCLEAR); > + } else if (data->soc == SOC_ARCH_EXYNOS) { > + /* Write temperature code for rising and falling threshold */ > + for (i = 0; i < trigger_levs; i++) { > + threshold_code = temp_to_code(data, > + pdata->trigger_levels[i]); > + if (threshold_code < 0) { > + ret = threshold_code; > + 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 + EXYNOS_THD_TEMP_RISE); > + writel(falling_threshold, > + data->base + EXYNOS_THD_TEMP_FALL); > + > + writel(EXYNOS_TMU_CLEAR_RISE_INT | EXYNOS_TMU_CLEAR_FALL_INT, > + data->base + EXYNOS_TMU_REG_INTCLEAR); > + } > +out: > + clk_disable(data->clk); > + mutex_unlock(&data->lock); > + > + 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); > + clk_enable(data->clk); > + > + con = pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT | > + pdata->gain << EXYNOS_TMU_GAIN_SHIFT; > + > + if (data->soc == SOC_ARCH_EXYNOS) { > + con |= pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT; > + con |= (EXYNOS_MUX_ADDR_VALUE << EXYNOS_MUX_ADDR_SHIFT); > + } > + > + if (on) { > + con |= EXYNOS_TMU_CORE_ON; > + interrupt_en = pdata->trigger_level3_en << 12 | > + pdata->trigger_level2_en << 8 | > + pdata->trigger_level1_en << 4 | > + pdata->trigger_level0_en; > + if (pdata->threshold_falling) > + interrupt_en |= interrupt_en << 16; > + } else { > + con |= EXYNOS_TMU_CORE_OFF; > + interrupt_en = 0; /* Disable all interrupts */ > + } > + writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN); > + writel(con, data->base + EXYNOS_TMU_REG_CONTROL); > + > + clk_disable(data->clk); > + mutex_unlock(&data->lock); > +} > + > +static int exynos_tmu_read(struct exynos_tmu_data *data) > +{ > + u8 temp_code; > + int temp; > + > + mutex_lock(&data->lock); > + clk_enable(data->clk); > + > + temp_code = readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP); > + temp = code_to_temp(data, temp_code); > + > + clk_disable(data->clk); > + mutex_unlock(&data->lock); > + > + return temp; > +} > + > +#ifdef CONFIG_THERMAL_EMULATION > +static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) > +{ > + struct exynos_tmu_data *data = drv_data; > + unsigned int reg; > + int ret = -EINVAL; > + > + if (data->soc == SOC_ARCH_EXYNOS4210) > + goto out; > + > + if (temp && temp < MCELSIUS) > + goto out; > + > + mutex_lock(&data->lock); > + clk_enable(data->clk); > + > + reg = readl(data->base + EXYNOS_EMUL_CON); > + > + if (temp) { > + temp /= MCELSIUS; > + > + reg = (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT) | > + (temp_to_code(data, temp) > + << EXYNOS_EMUL_DATA_SHIFT) | EXYNOS_EMUL_ENABLE; > + } else { > + reg &= ~EXYNOS_EMUL_ENABLE; > + } > + > + writel(reg, data->base + EXYNOS_EMUL_CON); > + > + clk_disable(data->clk); > + mutex_unlock(&data->lock); > + return 0; > +out: > + return ret; > +} > +#else > +static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) > + { return -EINVAL; } > +#endif/*CONFIG_THERMAL_EMULATION*/ > + > +static struct thermal_sensor_conf exynos_sensor_conf = { > + .name = "exynos-therm", > + .read_temperature = (int (*)(void *))exynos_tmu_read, > + .write_emul_temp = exynos_tmu_set_emulation, > +}; > + > +static void exynos_tmu_work(struct work_struct *work) > +{ > + struct exynos_tmu_data *data = container_of(work, > + struct exynos_tmu_data, irq_work); > + > + exynos_report_trigger(&exynos_sensor_conf); > + mutex_lock(&data->lock); > + clk_enable(data->clk); > + if (data->soc == SOC_ARCH_EXYNOS) > + writel(EXYNOS_TMU_CLEAR_RISE_INT | > + EXYNOS_TMU_CLEAR_FALL_INT, > + data->base + EXYNOS_TMU_REG_INTCLEAR); > + else > + writel(EXYNOS4210_TMU_INTCLEAR_VAL, > + data->base + EXYNOS_TMU_REG_INTCLEAR); > + clk_disable(data->clk); > + mutex_unlock(&data->lock); > + > + 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; > +} > + > +#if defined(CONFIG_CPU_EXYNOS4210) > +static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = { > + .threshold = 80, > + .trigger_levels[0] = 5, > + .trigger_levels[1] = 20, > + .trigger_levels[2] = 30, > + .trigger_level0_en = 1, > + .trigger_level1_en = 1, > + .trigger_level2_en = 1, > + .trigger_level3_en = 0, > + .gain = 15, > + .reference_voltage = 7, > + .cal_type = TYPE_ONE_POINT_TRIMMING, > + .freq_tab[0] = { > + .freq_clip_max = 800 * 1000, > + .temp_level = 85, > + }, > + .freq_tab[1] = { > + .freq_clip_max = 200 * 1000, > + .temp_level = 100, > + }, > + .freq_tab_count = 2, > + .type = SOC_ARCH_EXYNOS4210, > +}; > +#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data) > +#else > +#define EXYNOS4210_TMU_DRV_DATA (NULL) > +#endif > + > +#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412) > +static struct exynos_tmu_platform_data const exynos_default_tmu_data = { > + .threshold_falling = 10, > + .trigger_levels[0] = 85, > + .trigger_levels[1] = 103, > + .trigger_levels[2] = 110, > + .trigger_level0_en = 1, > + .trigger_level1_en = 1, > + .trigger_level2_en = 1, > + .trigger_level3_en = 0, > + .gain = 8, > + .reference_voltage = 16, > + .noise_cancel_mode = 4, > + .cal_type = TYPE_ONE_POINT_TRIMMING, > + .efuse_value = 55, > + .freq_tab[0] = { > + .freq_clip_max = 800 * 1000, > + .temp_level = 85, > + }, > + .freq_tab[1] = { > + .freq_clip_max = 200 * 1000, > + .temp_level = 103, > + }, > + .freq_tab_count = 2, > + .type = SOC_ARCH_EXYNOS, > +}; > +#define EXYNOS_TMU_DRV_DATA (&exynos_default_tmu_data) > +#else > +#define EXYNOS_TMU_DRV_DATA (NULL) > +#endif > + > +#ifdef CONFIG_OF > +static const struct of_device_id exynos_tmu_match[] = { > + { > + .compatible = "samsung,exynos4210-tmu", > + .data = (void *)EXYNOS4210_TMU_DRV_DATA, > + }, > + { > + .compatible = "samsung,exynos5250-tmu", > + .data = (void *)EXYNOS_TMU_DRV_DATA, > + }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, exynos_tmu_match); > +#endif > + > +static struct platform_device_id exynos_tmu_driver_ids[] = { > + { > + .name = "exynos4210-tmu", > + .driver_data = (kernel_ulong_t)EXYNOS4210_TMU_DRV_DATA, > + }, > + { > + .name = "exynos5250-tmu", > + .driver_data = (kernel_ulong_t)EXYNOS_TMU_DRV_DATA, > + }, > + { }, > +}; > +MODULE_DEVICE_TABLE(platform, exynos_tmu_driver_ids); > + > +static inline struct exynos_tmu_platform_data *exynos_get_driver_data( > + struct platform_device *pdev) > +{ > +#ifdef CONFIG_OF > + if (pdev->dev.of_node) { > + const struct of_device_id *match; > + match = of_match_node(exynos_tmu_match, pdev->dev.of_node); > + if (!match) > + return NULL; > + return (struct exynos_tmu_platform_data *) match->data; > + } > +#endif > + return (struct exynos_tmu_platform_data *) > + platform_get_device_id(pdev)->driver_data; > +} > + > +static int exynos_tmu_probe(struct platform_device *pdev) > +{ > + struct exynos_tmu_data *data; > + struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data; > + int ret, i; > + > + if (!pdata) > + pdata = exynos_get_driver_data(pdev); > + > + if (!pdata) { > + dev_err(&pdev->dev, "No platform init data supplied.\n"); > + return -ENODEV; > + } > + 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; > + } > + > + data->irq = platform_get_irq(pdev, 0); > + if (data->irq < 0) { > + dev_err(&pdev->dev, "Failed to get platform irq\n"); > + return data->irq; > + } > + > + INIT_WORK(&data->irq_work, exynos_tmu_work); > + > + data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!data->mem) { > + dev_err(&pdev->dev, "Failed to get platform resource\n"); > + return -ENOENT; > + } > + > + data->base = devm_ioremap_resource(&pdev->dev, data->mem); > + if (IS_ERR(data->base)) > + return PTR_ERR(data->base); > + > + ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, > + IRQF_TRIGGER_RISING, "exynos-tmu", data); > + if (ret) { > + dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); > + return ret; > + } > + > + data->clk = clk_get(NULL, "tmu_apbif"); > + if (IS_ERR(data->clk)) { > + dev_err(&pdev->dev, "Failed to get clock\n"); > + return PTR_ERR(data->clk); > + } > + > + if (pdata->type == SOC_ARCH_EXYNOS || > + pdata->type == SOC_ARCH_EXYNOS4210) > + data->soc = pdata->type; > + else { > + ret = -EINVAL; > + dev_err(&pdev->dev, "Platform not supported\n"); > + goto err_clk; > + } > + > + data->pdata = pdata; > + platform_set_drvdata(pdev, data); > + 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); > + > + /* Register the sensor with thermal management interface */ > + (&exynos_sensor_conf)->driver_data = data; > + exynos_sensor_conf.trip_data.trip_count = pdata->trigger_level0_en + > + pdata->trigger_level1_en + pdata->trigger_level2_en + > + pdata->trigger_level3_en; > + > + for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++) > + exynos_sensor_conf.trip_data.trip_val[i] = > + pdata->threshold + pdata->trigger_levels[i]; > + > + exynos_sensor_conf.trip_data.trigger_falling = pdata->threshold_falling; > + > + exynos_sensor_conf.cooling_data.freq_clip_count = > + pdata->freq_tab_count; > + for (i = 0; i < pdata->freq_tab_count; i++) { > + exynos_sensor_conf.cooling_data.freq_data[i].freq_clip_max = > + pdata->freq_tab[i].freq_clip_max; > + exynos_sensor_conf.cooling_data.freq_data[i].temp_level = > + pdata->freq_tab[i].temp_level; > + } > + > + ret = exynos_register_thermal(&exynos_sensor_conf); > + if (ret) { > + dev_err(&pdev->dev, "Failed to register thermal interface\n"); > + goto err_clk; > + } > + > + return 0; > +err_clk: > + platform_set_drvdata(pdev, NULL); > + clk_put(data->clk); > + return ret; > +} > + > +static int exynos_tmu_remove(struct platform_device *pdev) > +{ > + struct exynos_tmu_data *data = platform_get_drvdata(pdev); > + > + exynos_tmu_control(pdev, false); > + > + exynos_unregister_thermal(&exynos_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) > +{ > + exynos_tmu_control(to_platform_device(dev), false); > + > + return 0; > +} > + > +static int exynos_tmu_resume(struct device *dev) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + > + 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 = "exynos-tmu", > + .owner = THIS_MODULE, > + .pm = EXYNOS_TMU_PM, > + .of_match_table = of_match_ptr(exynos_tmu_match), > + }, > + .probe = exynos_tmu_probe, > + .remove = exynos_tmu_remove, > + .id_table = exynos_tmu_driver_ids, > +}; > + > +module_platform_driver(exynos_tmu_driver); > + > +MODULE_DESCRIPTION("EXYNOS TMU Driver"); > +MODULE_AUTHOR("Donggeun Kim "); > +MODULE_LICENSE("GPL"); > +MODULE_ALIAS("platform:exynos-tmu"); > diff --git a/drivers/thermal/samsung/exynos_common.c b/drivers/thermal/samsung/exynos_common.c > new file mode 100644 > index 0000000..649d67c > --- /dev/null > +++ b/drivers/thermal/samsung/exynos_common.c > @@ -0,0 +1,421 @@ > +/* > + * exynos_common.c - Samsung EXYNOS common thermal file > + * > + * Copyright (C) 2013 Samsung Electronics > + * Amit Daniel Kachhap > + * > + * 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include "exynos_common.h" > + > +struct exynos_thermal_zone { > + enum thermal_device_mode mode; > + struct thermal_zone_device *therm_dev; > + struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE]; > + unsigned int cool_dev_size; > + struct platform_device *exynos4_dev; > + struct thermal_sensor_conf *sensor_conf; > + bool bind; > +}; > + > +/* Get mode callback functions for thermal zone */ > +static int exynos_get_mode(struct thermal_zone_device *thermal, > + enum thermal_device_mode *mode) > +{ > + struct exynos_thermal_zone *th_zone = thermal->devdata; > + if (th_zone) > + *mode = th_zone->mode; > + return 0; > +} > + > +/* Set mode callback functions for thermal zone */ > +static int exynos_set_mode(struct thermal_zone_device *thermal, > + enum thermal_device_mode mode) > +{ > + struct exynos_thermal_zone *th_zone = thermal->devdata; > + if (!th_zone) { > + pr_notice("thermal zone not registered\n"); > + return 0; > + } > + > + mutex_lock(&thermal->lock); > + > + if (mode == THERMAL_DEVICE_ENABLED && > + !th_zone->sensor_conf->trip_data.trigger_falling) > + thermal->polling_delay = IDLE_INTERVAL; > + else > + thermal->polling_delay = 0; > + > + mutex_unlock(&thermal->lock); > + > + th_zone->mode = mode; > + thermal_zone_device_update(thermal); > + pr_info("thermal polling set for duration=%d msec\n", > + thermal->polling_delay); > + return 0; > +} > + > + > +/* Get trip type callback functions for thermal zone */ > +static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip, > + enum thermal_trip_type *type) > +{ > + switch (GET_ZONE(trip)) { > + case MONITOR_ZONE: > + case WARN_ZONE: > + *type = THERMAL_TRIP_ACTIVE; > + break; > + case PANIC_ZONE: > + *type = THERMAL_TRIP_CRITICAL; > + break; > + default: > + return -EINVAL; > + } > + return 0; > +} > + > +/* Get trip temperature callback functions for thermal zone */ > +static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip, > + unsigned long *temp) > +{ > + struct exynos_thermal_zone *th_zone = thermal->devdata; > + > + if (trip < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE)) > + return -EINVAL; > + > + *temp = th_zone->sensor_conf->trip_data.trip_val[trip]; > + /* convert the temperature into millicelsius */ > + *temp = *temp * MCELSIUS; > + > + return 0; > +} > + > +/* Get critical temperature callback functions for thermal zone */ > +static int exynos_get_crit_temp(struct thermal_zone_device *thermal, > + unsigned long *temp) > +{ > + int ret; > + /* Panic zone */ > + ret = exynos_get_trip_temp(thermal, GET_TRIP(PANIC_ZONE), temp); > + return ret; > +} > + > +int exynos_get_frequency_level(unsigned int cpu, unsigned int freq) > +{ > + int i = 0, ret = -EINVAL; > + struct cpufreq_frequency_table *table = NULL; > +#ifdef CONFIG_CPU_FREQ > + table = cpufreq_frequency_get_table(cpu); > +#endif > + if (!table) > + return ret; > + > + while (table[i].frequency != CPUFREQ_TABLE_END) { > + if (table[i].frequency == CPUFREQ_ENTRY_INVALID) > + continue; > + if (table[i].frequency == freq) > + return i; > + i++; > + } > + return ret; > +} > + > +/* Bind callback functions for thermal zone */ > +static int exynos_bind(struct thermal_zone_device *thermal, > + struct thermal_cooling_device *cdev) > +{ > + int ret = 0, i, tab_size, level; > + struct freq_clip_table *tab_ptr, *clip_data; > + struct exynos_thermal_zone *th_zone = thermal->devdata; > + struct thermal_sensor_conf *data = th_zone->sensor_conf; > + > + tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data; > + tab_size = data->cooling_data.freq_clip_count; > + > + if (tab_ptr == NULL || tab_size == 0) > + return -EINVAL; > + > + /* find the cooling device registered*/ > + for (i = 0; i < th_zone->cool_dev_size; i++) > + if (cdev == th_zone->cool_dev[i]) > + break; > + > + /* No matching cooling device */ > + if (i == th_zone->cool_dev_size) > + return 0; > + > + /* Bind the thermal zone to the cpufreq cooling device */ > + for (i = 0; i < tab_size; i++) { > + clip_data = (struct freq_clip_table *)&(tab_ptr[i]); > + level = exynos_get_frequency_level(0, clip_data->freq_clip_max); > + if (level < 0) > + return 0; > + switch (GET_ZONE(i)) { > + case MONITOR_ZONE: > + case WARN_ZONE: > + if (thermal_zone_bind_cooling_device(thermal, i, cdev, > + level, 0)) { > + pr_err("error binding cdev inst %d\n", i); > + ret = -EINVAL; > + } > + th_zone->bind = true; > + break; > + default: > + ret = -EINVAL; > + } > + } > + > + return ret; > +} > + > +/* Unbind callback functions for thermal zone */ > +static int exynos_unbind(struct thermal_zone_device *thermal, > + struct thermal_cooling_device *cdev) > +{ > + int ret = 0, i, tab_size; > + struct exynos_thermal_zone *th_zone = thermal->devdata; > + struct thermal_sensor_conf *data = th_zone->sensor_conf; > + > + if (th_zone->bind == false) > + return 0; > + > + tab_size = data->cooling_data.freq_clip_count; > + > + if (tab_size == 0) > + return -EINVAL; > + > + /* find the cooling device registered*/ > + for (i = 0; i < th_zone->cool_dev_size; i++) > + if (cdev == th_zone->cool_dev[i]) > + break; > + > + /* No matching cooling device */ > + if (i == th_zone->cool_dev_size) > + return 0; > + > + /* Bind the thermal zone to the cpufreq cooling device */ > + for (i = 0; i < tab_size; i++) { > + switch (GET_ZONE(i)) { > + case MONITOR_ZONE: > + case WARN_ZONE: > + if (thermal_zone_unbind_cooling_device(thermal, i, > + cdev)) { > + pr_err("error unbinding cdev inst=%d\n", i); > + ret = -EINVAL; > + } > + th_zone->bind = false; > + break; > + default: > + ret = -EINVAL; > + } > + } > + return ret; > +} > + > +/* Get temperature callback functions for thermal zone */ > +static int exynos_get_temp(struct thermal_zone_device *thermal, > + unsigned long *temp) > +{ > + struct exynos_thermal_zone *th_zone = thermal->devdata; > + void *data; > + > + if (!th_zone->sensor_conf) { > + pr_info("Temperature sensor not initialised\n"); > + return -EINVAL; > + } > + data = th_zone->sensor_conf->driver_data; > + *temp = th_zone->sensor_conf->read_temperature(data); > + /* convert the temperature into millicelsius */ > + *temp = *temp * MCELSIUS; > + return 0; > +} > + > +/* Get temperature callback functions for thermal zone */ > +static int exynos_set_emul_temp(struct thermal_zone_device *thermal, > + unsigned long temp) > +{ > + void *data; > + int ret = -EINVAL; > + struct exynos_thermal_zone *th_zone = thermal->devdata; > + > + if (!th_zone->sensor_conf) { > + pr_info("Temperature sensor not initialised\n"); > + return -EINVAL; > + } > + data = th_zone->sensor_conf->driver_data; > + if (th_zone->sensor_conf->write_emul_temp) > + ret = th_zone->sensor_conf->write_emul_temp(data, temp); > + return ret; > +} > + > +/* Get the temperature trend */ > +static int exynos_get_trend(struct thermal_zone_device *thermal, > + int trip, enum thermal_trend *trend) > +{ > + int ret = 0; > + unsigned long trip_temp; > + > + ret = exynos_get_trip_temp(thermal, trip, &trip_temp); > + if (ret < 0) > + return ret; > + > + if (thermal->temperature >= trip_temp) > + *trend = THERMAL_TREND_RAISE_FULL; > + else > + *trend = THERMAL_TREND_DROP_FULL; > + > + return ret; > +} > +/* Operation callback functions for thermal zone */ > +static struct thermal_zone_device_ops const exynos_dev_ops = { > + .bind = exynos_bind, > + .unbind = exynos_unbind, > + .get_temp = exynos_get_temp, > + .set_emul_temp = exynos_set_emul_temp, > + .get_trend = exynos_get_trend, > + .get_mode = exynos_get_mode, > + .set_mode = exynos_set_mode, > + .get_trip_type = exynos_get_trip_type, > + .get_trip_temp = exynos_get_trip_temp, > + .get_crit_temp = exynos_get_crit_temp, > +}; > + > +/* > + * This function may be called from interrupt based temperature sensor > + * when threshold is changed. > + */ > +void exynos_report_trigger(struct thermal_sensor_conf *conf) > +{ > + unsigned int i; > + char data[10]; > + char *envp[] = { data, NULL }; > + struct exynos_thermal_zone *th_zone = conf->pzone_data; > + > + if (!th_zone || !th_zone->therm_dev) > + return; > + if (th_zone->bind == false) { > + for (i = 0; i < th_zone->cool_dev_size; i++) { > + if (!th_zone->cool_dev[i]) > + continue; > + exynos_bind(th_zone->therm_dev, > + th_zone->cool_dev[i]); > + } > + } > + > + thermal_zone_device_update(th_zone->therm_dev); > + > + mutex_lock(&th_zone->therm_dev->lock); > + /* Find the level for which trip happened */ > + for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) { > + if (th_zone->therm_dev->last_temperature < > + th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS) > + break; > + } > + > + if (th_zone->mode == THERMAL_DEVICE_ENABLED && > + !th_zone->sensor_conf->trip_data.trigger_falling) { > + if (i > 0) > + th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL; > + else > + th_zone->therm_dev->polling_delay = IDLE_INTERVAL; > + } > + > + snprintf(data, sizeof(data), "%u", i); > + kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp); > + mutex_unlock(&th_zone->therm_dev->lock); > +} > + > +/* Register with the in-kernel thermal management */ > +int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) > +{ > + int ret, id; > + struct cpumask mask_val; > + struct exynos_thermal_zone *th_zone; > + > + if (!sensor_conf || !sensor_conf->read_temperature) { > + pr_err("Temperature sensor not initialised\n"); > + return -EINVAL; > + } > + > + th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL); > + if (!th_zone) > + return -ENOMEM; > + > + th_zone->sensor_conf = sensor_conf; > + cpumask_set_cpu(0, &mask_val); > + th_zone->cool_dev[0] = cpufreq_cooling_register(&mask_val); > + if (IS_ERR(th_zone->cool_dev[0])) { > + pr_err("Failed to register cpufreq cooling device\n"); > + ret = -EINVAL; > + goto err_unregister; > + } > + th_zone->cool_dev_size++; > + > + th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name, > + EXYNOS_ZONE_COUNT, 0, th_zone, &exynos_dev_ops, NULL, 0, > + sensor_conf->trip_data.trigger_falling ? > + 0 : IDLE_INTERVAL); > + > + if (IS_ERR(th_zone->therm_dev)) { > + pr_err("Failed to register thermal zone device\n"); > + ret = PTR_ERR(th_zone->therm_dev); > + goto err_unregister; > + } > + th_zone->mode = THERMAL_DEVICE_ENABLED; > + sensor_conf->pzone_data = th_zone; > + id = th_zone->therm_dev->id; > + > + pr_info("Exynos: Kernel Thermal[%d] management registered\n", id); > + > + return 0; > + > +err_unregister: > + exynos_unregister_thermal(sensor_conf); > + return ret; > +} > + > +/* Un-Register with the in-kernel thermal management */ > +void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf) > +{ > + int i, id; > + struct exynos_thermal_zone *th_zone = sensor_conf->pzone_data; > + > + if (!th_zone) > + return; > + > + id = th_zone->therm_dev->id; > + if (th_zone->therm_dev) > + thermal_zone_device_unregister(th_zone->therm_dev); > + > + for (i = 0; i < th_zone->cool_dev_size; i++) { > + if (th_zone->cool_dev[i]) > + cpufreq_cooling_unregister(th_zone->cool_dev[i]); > + } > + > + kfree(th_zone); > + pr_info("Exynos: Kernel Thermal[%d] management unregistered\n", id); > +} > diff --git a/drivers/thermal/samsung/exynos_common.h b/drivers/thermal/samsung/exynos_common.h > new file mode 100644 > index 0000000..b8d289e > --- /dev/null > +++ b/drivers/thermal/samsung/exynos_common.h > @@ -0,0 +1,73 @@ > +/* > + * exynos_common.h - Samsung EXYNOS common header file > + * > + * Copyright (C) 2013 Samsung Electronics > + * Amit Daniel Kachhap > + * > + * 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 > + * > + */ > + > +#ifndef _LINUX_EXYNOS_COMMON_H > +#define _LINUX_EXYNOS_COMMON_H > + > +/* In-kernel thermal framework related macros & definations */ > +#define SENSOR_NAME_LEN 16 > +#define MAX_TRIP_COUNT 8 > +#define MAX_COOLING_DEVICE 4 > +#define MAX_THRESHOLD_LEVS 4 > + > +#define ACTIVE_INTERVAL 500 > +#define IDLE_INTERVAL 10000 > +#define MCELSIUS 1000 > + > +/* CPU Zone information */ > +#define PANIC_ZONE 4 > +#define WARN_ZONE 3 > +#define MONITOR_ZONE 2 > +#define SAFE_ZONE 1 > + > +#define GET_ZONE(trip) (trip + 2) > +#define GET_TRIP(zone) (zone - 2) > + > +#define EXYNOS_ZONE_COUNT 3 > + > +struct thermal_trip_point_conf { > + int trip_val[MAX_TRIP_COUNT]; > + int trip_count; > + u8 trigger_falling; > +}; > + > +struct thermal_cooling_conf { > + struct freq_clip_table freq_data[MAX_TRIP_COUNT]; > + int freq_clip_count; > +}; > + > +struct thermal_sensor_conf { > + char name[SENSOR_NAME_LEN]; > + int (*read_temperature)(void *data); > + int (*write_emul_temp)(void *drv_data, unsigned long temp); > + struct thermal_trip_point_conf trip_data; > + struct thermal_cooling_conf cooling_data; > + void *driver_data; > + void *pzone_data; > +}; > + > +/*Functions used exynos based thermal sensor driver*/ > +void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf); > +int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf); > +void exynos_report_trigger(struct thermal_sensor_conf *sensor_conf); > +int exynos_get_frequency_level(unsigned int cpu, unsigned int freq); > +#endif /* _LINUX_EXYNOS_COMMON_H */ > diff --git a/drivers/thermal/samsung/exynos_thermal.c b/drivers/thermal/samsung/exynos_thermal.c > deleted file mode 100644 > index dc9b91b..0000000 > --- a/drivers/thermal/samsung/exynos_thermal.c > +++ /dev/null > @@ -1,1093 +0,0 @@ > -/* > - * exynos_thermal.c - Samsung EXYNOS TMU (Thermal Management Unit) > - * > - * Copyright (C) 2011 Samsung Electronics > - * Donggeun Kim > - * Amit Daniel Kachhap > - * > - * 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 > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > -#include > - > -#include > - > -/* Exynos generic registers */ > -#define EXYNOS_TMU_REG_TRIMINFO 0x0 > -#define EXYNOS_TMU_REG_CONTROL 0x20 > -#define EXYNOS_TMU_REG_STATUS 0x28 > -#define EXYNOS_TMU_REG_CURRENT_TEMP 0x40 > -#define EXYNOS_TMU_REG_INTEN 0x70 > -#define EXYNOS_TMU_REG_INTSTAT 0x74 > -#define EXYNOS_TMU_REG_INTCLEAR 0x78 > - > -#define EXYNOS_TMU_TRIM_TEMP_MASK 0xff > -#define EXYNOS_TMU_GAIN_SHIFT 8 > -#define EXYNOS_TMU_REF_VOLTAGE_SHIFT 24 > -#define EXYNOS_TMU_CORE_ON 3 > -#define EXYNOS_TMU_CORE_OFF 2 > -#define EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET 50 > - > -/* Exynos4210 specific registers */ > -#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP 0x44 > -#define EXYNOS4210_TMU_REG_TRIG_LEVEL0 0x50 > -#define EXYNOS4210_TMU_REG_TRIG_LEVEL1 0x54 > -#define EXYNOS4210_TMU_REG_TRIG_LEVEL2 0x58 > -#define EXYNOS4210_TMU_REG_TRIG_LEVEL3 0x5C > -#define EXYNOS4210_TMU_REG_PAST_TEMP0 0x60 > -#define EXYNOS4210_TMU_REG_PAST_TEMP1 0x64 > -#define EXYNOS4210_TMU_REG_PAST_TEMP2 0x68 > -#define EXYNOS4210_TMU_REG_PAST_TEMP3 0x6C > - > -#define EXYNOS4210_TMU_TRIG_LEVEL0_MASK 0x1 > -#define EXYNOS4210_TMU_TRIG_LEVEL1_MASK 0x10 > -#define EXYNOS4210_TMU_TRIG_LEVEL2_MASK 0x100 > -#define EXYNOS4210_TMU_TRIG_LEVEL3_MASK 0x1000 > -#define EXYNOS4210_TMU_INTCLEAR_VAL 0x1111 > - > -/* Exynos5250 and Exynos4412 specific registers */ > -#define EXYNOS_TMU_TRIMINFO_CON 0x14 > -#define EXYNOS_THD_TEMP_RISE 0x50 > -#define EXYNOS_THD_TEMP_FALL 0x54 > -#define EXYNOS_EMUL_CON 0x80 > - > -#define EXYNOS_TRIMINFO_RELOAD 0x1 > -#define EXYNOS_TMU_CLEAR_RISE_INT 0x111 > -#define EXYNOS_TMU_CLEAR_FALL_INT (0x111 << 12) > -#define EXYNOS_MUX_ADDR_VALUE 6 > -#define EXYNOS_MUX_ADDR_SHIFT 20 > -#define EXYNOS_TMU_TRIP_MODE_SHIFT 13 > - > -#define EFUSE_MIN_VALUE 40 > -#define EFUSE_MAX_VALUE 100 > - > -/* In-kernel thermal framework related macros & definations */ > -#define SENSOR_NAME_LEN 16 > -#define MAX_TRIP_COUNT 8 > -#define MAX_COOLING_DEVICE 4 > -#define MAX_THRESHOLD_LEVS 4 > - > -#define ACTIVE_INTERVAL 500 > -#define IDLE_INTERVAL 10000 > -#define MCELSIUS 1000 > - > -#ifdef CONFIG_THERMAL_EMULATION > -#define EXYNOS_EMUL_TIME 0x57F0 > -#define EXYNOS_EMUL_TIME_SHIFT 16 > -#define EXYNOS_EMUL_DATA_SHIFT 8 > -#define EXYNOS_EMUL_DATA_MASK 0xFF > -#define EXYNOS_EMUL_ENABLE 0x1 > -#endif /* CONFIG_THERMAL_EMULATION */ > - > -/* CPU Zone information */ > -#define PANIC_ZONE 4 > -#define WARN_ZONE 3 > -#define MONITOR_ZONE 2 > -#define SAFE_ZONE 1 > - > -#define GET_ZONE(trip) (trip + 2) > -#define GET_TRIP(zone) (zone - 2) > - > -#define EXYNOS_ZONE_COUNT 3 > - > -struct exynos_tmu_data { > - struct exynos_tmu_platform_data *pdata; > - struct resource *mem; > - void __iomem *base; > - int irq; > - enum soc_type soc; > - struct work_struct irq_work; > - struct mutex lock; > - struct clk *clk; > - u8 temp_error1, temp_error2; > -}; > - > -struct thermal_trip_point_conf { > - int trip_val[MAX_TRIP_COUNT]; > - int trip_count; > - u8 trigger_falling; > -}; > - > -struct thermal_cooling_conf { > - struct freq_clip_table freq_data[MAX_TRIP_COUNT]; > - int freq_clip_count; > -}; > - > -struct thermal_sensor_conf { > - char name[SENSOR_NAME_LEN]; > - int (*read_temperature)(void *data); > - int (*write_emul_temp)(void *drv_data, unsigned long temp); > - struct thermal_trip_point_conf trip_data; > - struct thermal_cooling_conf cooling_data; > - void *driver_data; > - void *pzone_data; > -}; > - > -struct exynos_thermal_zone { > - enum thermal_device_mode mode; > - struct thermal_zone_device *therm_dev; > - struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE]; > - unsigned int cool_dev_size; > - struct platform_device *exynos4_dev; > - struct thermal_sensor_conf *sensor_conf; > - bool bind; > -}; > - > -static void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf); > -static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf); > - > -/* Get mode callback functions for thermal zone */ > -static int exynos_get_mode(struct thermal_zone_device *thermal, > - enum thermal_device_mode *mode) > -{ > - struct exynos_thermal_zone *th_zone = thermal->devdata; > - if (th_zone) > - *mode = th_zone->mode; > - return 0; > -} > - > -/* Set mode callback functions for thermal zone */ > -static int exynos_set_mode(struct thermal_zone_device *thermal, > - enum thermal_device_mode mode) > -{ > - struct exynos_thermal_zone *th_zone = thermal->devdata; > - if (!th_zone) { > - pr_notice("thermal zone not registered\n"); > - return 0; > - } > - > - mutex_lock(&thermal->lock); > - > - if (mode == THERMAL_DEVICE_ENABLED && > - !th_zone->sensor_conf->trip_data.trigger_falling) > - thermal->polling_delay = IDLE_INTERVAL; > - else > - thermal->polling_delay = 0; > - > - mutex_unlock(&thermal->lock); > - > - th_zone->mode = mode; > - thermal_zone_device_update(thermal); > - pr_info("thermal polling set for duration=%d msec\n", > - thermal->polling_delay); > - return 0; > -} > - > - > -/* Get trip type callback functions for thermal zone */ > -static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip, > - enum thermal_trip_type *type) > -{ > - switch (GET_ZONE(trip)) { > - case MONITOR_ZONE: > - case WARN_ZONE: > - *type = THERMAL_TRIP_ACTIVE; > - break; > - case PANIC_ZONE: > - *type = THERMAL_TRIP_CRITICAL; > - break; > - default: > - return -EINVAL; > - } > - return 0; > -} > - > -/* Get trip temperature callback functions for thermal zone */ > -static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip, > - unsigned long *temp) > -{ > - struct exynos_thermal_zone *th_zone = thermal->devdata; > - > - if (trip < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE)) > - return -EINVAL; > - > - *temp = th_zone->sensor_conf->trip_data.trip_val[trip]; > - /* convert the temperature into millicelsius */ > - *temp = *temp * MCELSIUS; > - > - return 0; > -} > - > -/* Get critical temperature callback functions for thermal zone */ > -static int exynos_get_crit_temp(struct thermal_zone_device *thermal, > - unsigned long *temp) > -{ > - int ret; > - /* Panic zone */ > - ret = exynos_get_trip_temp(thermal, GET_TRIP(PANIC_ZONE), temp); > - return ret; > -} > - > -static int exynos_get_frequency_level(unsigned int cpu, unsigned int freq) > -{ > - int i = 0, ret = -EINVAL; > - struct cpufreq_frequency_table *table = NULL; > -#ifdef CONFIG_CPU_FREQ > - table = cpufreq_frequency_get_table(cpu); > -#endif > - if (!table) > - return ret; > - > - while (table[i].frequency != CPUFREQ_TABLE_END) { > - if (table[i].frequency == CPUFREQ_ENTRY_INVALID) > - continue; > - if (table[i].frequency == freq) > - return i; > - i++; > - } > - return ret; > -} > - > -/* Bind callback functions for thermal zone */ > -static int exynos_bind(struct thermal_zone_device *thermal, > - struct thermal_cooling_device *cdev) > -{ > - int ret = 0, i, tab_size, level; > - struct freq_clip_table *tab_ptr, *clip_data; > - struct exynos_thermal_zone *th_zone = thermal->devdata; > - struct thermal_sensor_conf *data = th_zone->sensor_conf; > - > - tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data; > - tab_size = data->cooling_data.freq_clip_count; > - > - if (tab_ptr == NULL || tab_size == 0) > - return -EINVAL; > - > - /* find the cooling device registered*/ > - for (i = 0; i < th_zone->cool_dev_size; i++) > - if (cdev == th_zone->cool_dev[i]) > - break; > - > - /* No matching cooling device */ > - if (i == th_zone->cool_dev_size) > - return 0; > - > - /* Bind the thermal zone to the cpufreq cooling device */ > - for (i = 0; i < tab_size; i++) { > - clip_data = (struct freq_clip_table *)&(tab_ptr[i]); > - level = exynos_get_frequency_level(0, clip_data->freq_clip_max); > - if (level < 0) > - return 0; > - switch (GET_ZONE(i)) { > - case MONITOR_ZONE: > - case WARN_ZONE: > - if (thermal_zone_bind_cooling_device(thermal, i, cdev, > - level, 0)) { > - pr_err("error binding cdev inst %d\n", i); > - ret = -EINVAL; > - } > - th_zone->bind = true; > - break; > - default: > - ret = -EINVAL; > - } > - } > - > - return ret; > -} > - > -/* Unbind callback functions for thermal zone */ > -static int exynos_unbind(struct thermal_zone_device *thermal, > - struct thermal_cooling_device *cdev) > -{ > - int ret = 0, i, tab_size; > - struct exynos_thermal_zone *th_zone = thermal->devdata; > - struct thermal_sensor_conf *data = th_zone->sensor_conf; > - > - if (th_zone->bind == false) > - return 0; > - > - tab_size = data->cooling_data.freq_clip_count; > - > - if (tab_size == 0) > - return -EINVAL; > - > - /* find the cooling device registered*/ > - for (i = 0; i < th_zone->cool_dev_size; i++) > - if (cdev == th_zone->cool_dev[i]) > - break; > - > - /* No matching cooling device */ > - if (i == th_zone->cool_dev_size) > - return 0; > - > - /* Bind the thermal zone to the cpufreq cooling device */ > - for (i = 0; i < tab_size; i++) { > - switch (GET_ZONE(i)) { > - case MONITOR_ZONE: > - case WARN_ZONE: > - if (thermal_zone_unbind_cooling_device(thermal, i, > - cdev)) { > - pr_err("error unbinding cdev inst=%d\n", i); > - ret = -EINVAL; > - } > - th_zone->bind = false; > - break; > - default: > - ret = -EINVAL; > - } > - } > - return ret; > -} > - > -/* Get temperature callback functions for thermal zone */ > -static int exynos_get_temp(struct thermal_zone_device *thermal, > - unsigned long *temp) > -{ > - struct exynos_thermal_zone *th_zone = thermal->devdata; > - void *data; > - > - if (!th_zone->sensor_conf) { > - pr_info("Temperature sensor not initialised\n"); > - return -EINVAL; > - } > - data = th_zone->sensor_conf->driver_data; > - *temp = th_zone->sensor_conf->read_temperature(data); > - /* convert the temperature into millicelsius */ > - *temp = *temp * MCELSIUS; > - return 0; > -} > - > -/* Get temperature callback functions for thermal zone */ > -static int exynos_set_emul_temp(struct thermal_zone_device *thermal, > - unsigned long temp) > -{ > - void *data; > - int ret = -EINVAL; > - struct exynos_thermal_zone *th_zone = thermal->devdata; > - > - if (!th_zone->sensor_conf) { > - pr_info("Temperature sensor not initialised\n"); > - return -EINVAL; > - } > - data = th_zone->sensor_conf->driver_data; > - if (th_zone->sensor_conf->write_emul_temp) > - ret = th_zone->sensor_conf->write_emul_temp(data, temp); > - return ret; > -} > - > -/* Get the temperature trend */ > -static int exynos_get_trend(struct thermal_zone_device *thermal, > - int trip, enum thermal_trend *trend) > -{ > - int ret = 0; > - unsigned long trip_temp; > - > - ret = exynos_get_trip_temp(thermal, trip, &trip_temp); > - if (ret < 0) > - return ret; > - > - if (thermal->temperature >= trip_temp) > - *trend = THERMAL_TREND_RAISE_FULL; > - else > - *trend = THERMAL_TREND_DROP_FULL; > - > - return ret; > -} > -/* Operation callback functions for thermal zone */ > -static struct thermal_zone_device_ops const exynos_dev_ops = { > - .bind = exynos_bind, > - .unbind = exynos_unbind, > - .get_temp = exynos_get_temp, > - .set_emul_temp = exynos_set_emul_temp, > - .get_trend = exynos_get_trend, > - .get_mode = exynos_get_mode, > - .set_mode = exynos_set_mode, > - .get_trip_type = exynos_get_trip_type, > - .get_trip_temp = exynos_get_trip_temp, > - .get_crit_temp = exynos_get_crit_temp, > -}; > - > -/* > - * This function may be called from interrupt based temperature sensor > - * when threshold is changed. > - */ > -static void exynos_report_trigger(struct thermal_sensor_conf *conf) > -{ > - unsigned int i; > - char data[10]; > - char *envp[] = { data, NULL }; > - struct exynos_thermal_zone *th_zone = conf->pzone_data; > - > - if (!th_zone || !th_zone->therm_dev) > - return; > - if (th_zone->bind == false) { > - for (i = 0; i < th_zone->cool_dev_size; i++) { > - if (!th_zone->cool_dev[i]) > - continue; > - exynos_bind(th_zone->therm_dev, > - th_zone->cool_dev[i]); > - } > - } > - > - thermal_zone_device_update(th_zone->therm_dev); > - > - mutex_lock(&th_zone->therm_dev->lock); > - /* Find the level for which trip happened */ > - for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) { > - if (th_zone->therm_dev->last_temperature < > - th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS) > - break; > - } > - > - if (th_zone->mode == THERMAL_DEVICE_ENABLED && > - !th_zone->sensor_conf->trip_data.trigger_falling) { > - if (i > 0) > - th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL; > - else > - th_zone->therm_dev->polling_delay = IDLE_INTERVAL; > - } > - > - snprintf(data, sizeof(data), "%u", i); > - kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp); > - mutex_unlock(&th_zone->therm_dev->lock); > -} > - > -/* Register with the in-kernel thermal management */ > -static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) > -{ > - int ret; > - struct cpumask mask_val; > - struct exynos_thermal_zone *th_zone; > - > - if (!sensor_conf || !sensor_conf->read_temperature) { > - pr_err("Temperature sensor not initialised\n"); > - return -EINVAL; > - } > - > - th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL); > - if (!th_zone) > - return -ENOMEM; > - > - th_zone->sensor_conf = sensor_conf; > - cpumask_set_cpu(0, &mask_val); > - th_zone->cool_dev[0] = cpufreq_cooling_register(&mask_val); > - if (IS_ERR(th_zone->cool_dev[0])) { > - pr_err("Failed to register cpufreq cooling device\n"); > - ret = -EINVAL; > - goto err_unregister; > - } > - th_zone->cool_dev_size++; > - > - th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name, > - EXYNOS_ZONE_COUNT, 0, th_zone, &exynos_dev_ops, NULL, 0, > - sensor_conf->trip_data.trigger_falling ? > - 0 : IDLE_INTERVAL); > - > - if (IS_ERR(th_zone->therm_dev)) { > - pr_err("Failed to register thermal zone device\n"); > - ret = PTR_ERR(th_zone->therm_dev); > - goto err_unregister; > - } > - th_zone->mode = THERMAL_DEVICE_ENABLED; > - sensor_conf->pzone_data = th_zone; > - > - pr_info("Exynos: Kernel Thermal management registered\n"); > - > - return 0; > - > -err_unregister: > - exynos_unregister_thermal(sensor_conf); > - return ret; > -} > - > -/* Un-Register with the in-kernel thermal management */ > -static void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf) > -{ > - int i; > - struct exynos_thermal_zone *th_zone = sensor_conf->pzone_data; > - > - if (!th_zone) > - return; > - > - if (th_zone->therm_dev) > - thermal_zone_device_unregister(th_zone->therm_dev); > - > - for (i = 0; i < th_zone->cool_dev_size; i++) { > - if (th_zone->cool_dev[i]) > - cpufreq_cooling_unregister(th_zone->cool_dev[i]); > - } > - > - kfree(th_zone); > - pr_info("Exynos: Kernel Thermal management unregistered\n"); > -} > - > -/* > - * 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 (data->soc == SOC_ARCH_EXYNOS4210) > - /* temp should range between 25 and 125 */ > - if (temp < 25 || temp > 125) { > - temp_code = -EINVAL; > - goto out; > - } > - > - switch (pdata->cal_type) { > - case TYPE_TWO_POINT_TRIMMING: > - temp_code = (temp - 25) * > - (data->temp_error2 - data->temp_error1) / > - (85 - 25) + data->temp_error1; > - break; > - case TYPE_ONE_POINT_TRIMMING: > - temp_code = temp + data->temp_error1 - 25; > - break; > - default: > - temp_code = temp + EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET; > - break; > - } > -out: > - 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 (data->soc == SOC_ARCH_EXYNOS4210) > - /* temp_code should range between 75 and 175 */ > - if (temp_code < 75 || temp_code > 175) { > - temp = -ENODATA; > - goto out; > - } > - > - switch (pdata->cal_type) { > - case TYPE_TWO_POINT_TRIMMING: > - temp = (temp_code - data->temp_error1) * (85 - 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 - EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET; > - break; > - } > -out: > - 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, trim_info; > - unsigned int rising_threshold = 0, falling_threshold = 0; > - int ret = 0, threshold_code, i, trigger_levs = 0; > - > - mutex_lock(&data->lock); > - clk_enable(data->clk); > - > - status = readb(data->base + EXYNOS_TMU_REG_STATUS); > - if (!status) { > - ret = -EBUSY; > - goto out; > - } > - > - if (data->soc == SOC_ARCH_EXYNOS) { > - __raw_writel(EXYNOS_TRIMINFO_RELOAD, > - data->base + EXYNOS_TMU_TRIMINFO_CON); > - } > - /* Save trimming info in order to perform calibration */ > - trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); > - data->temp_error1 = trim_info & EXYNOS_TMU_TRIM_TEMP_MASK; > - data->temp_error2 = ((trim_info >> 8) & EXYNOS_TMU_TRIM_TEMP_MASK); > - > - if ((EFUSE_MIN_VALUE > data->temp_error1) || > - (data->temp_error1 > EFUSE_MAX_VALUE) || > - (data->temp_error2 != 0)) > - data->temp_error1 = pdata->efuse_value; > - > - /* Count trigger levels to be enabled */ > - for (i = 0; i < MAX_THRESHOLD_LEVS; i++) > - if (pdata->trigger_levels[i]) > - trigger_levs++; > - > - if (data->soc == SOC_ARCH_EXYNOS4210) { > - /* Write temperature code for threshold */ > - threshold_code = temp_to_code(data, pdata->threshold); > - if (threshold_code < 0) { > - ret = threshold_code; > - goto out; > - } > - writeb(threshold_code, > - data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP); > - for (i = 0; i < trigger_levs; i++) > - writeb(pdata->trigger_levels[i], > - data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4); > - > - writel(EXYNOS4210_TMU_INTCLEAR_VAL, > - data->base + EXYNOS_TMU_REG_INTCLEAR); > - } else if (data->soc == SOC_ARCH_EXYNOS) { > - /* Write temperature code for rising and falling threshold */ > - for (i = 0; i < trigger_levs; i++) { > - threshold_code = temp_to_code(data, > - pdata->trigger_levels[i]); > - if (threshold_code < 0) { > - ret = threshold_code; > - 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 + EXYNOS_THD_TEMP_RISE); > - writel(falling_threshold, > - data->base + EXYNOS_THD_TEMP_FALL); > - > - writel(EXYNOS_TMU_CLEAR_RISE_INT | EXYNOS_TMU_CLEAR_FALL_INT, > - data->base + EXYNOS_TMU_REG_INTCLEAR); > - } > -out: > - clk_disable(data->clk); > - mutex_unlock(&data->lock); > - > - 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); > - clk_enable(data->clk); > - > - con = pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT | > - pdata->gain << EXYNOS_TMU_GAIN_SHIFT; > - > - if (data->soc == SOC_ARCH_EXYNOS) { > - con |= pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT; > - con |= (EXYNOS_MUX_ADDR_VALUE << EXYNOS_MUX_ADDR_SHIFT); > - } > - > - if (on) { > - con |= EXYNOS_TMU_CORE_ON; > - interrupt_en = pdata->trigger_level3_en << 12 | > - pdata->trigger_level2_en << 8 | > - pdata->trigger_level1_en << 4 | > - pdata->trigger_level0_en; > - if (pdata->threshold_falling) > - interrupt_en |= interrupt_en << 16; > - } else { > - con |= EXYNOS_TMU_CORE_OFF; > - interrupt_en = 0; /* Disable all interrupts */ > - } > - writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN); > - writel(con, data->base + EXYNOS_TMU_REG_CONTROL); > - > - clk_disable(data->clk); > - mutex_unlock(&data->lock); > -} > - > -static int exynos_tmu_read(struct exynos_tmu_data *data) > -{ > - u8 temp_code; > - int temp; > - > - mutex_lock(&data->lock); > - clk_enable(data->clk); > - > - temp_code = readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP); > - temp = code_to_temp(data, temp_code); > - > - clk_disable(data->clk); > - mutex_unlock(&data->lock); > - > - return temp; > -} > - > -#ifdef CONFIG_THERMAL_EMULATION > -static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) > -{ > - struct exynos_tmu_data *data = drv_data; > - unsigned int reg; > - int ret = -EINVAL; > - > - if (data->soc == SOC_ARCH_EXYNOS4210) > - goto out; > - > - if (temp && temp < MCELSIUS) > - goto out; > - > - mutex_lock(&data->lock); > - clk_enable(data->clk); > - > - reg = readl(data->base + EXYNOS_EMUL_CON); > - > - if (temp) { > - temp /= MCELSIUS; > - > - reg = (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT) | > - (temp_to_code(data, temp) > - << EXYNOS_EMUL_DATA_SHIFT) | EXYNOS_EMUL_ENABLE; > - } else { > - reg &= ~EXYNOS_EMUL_ENABLE; > - } > - > - writel(reg, data->base + EXYNOS_EMUL_CON); > - > - clk_disable(data->clk); > - mutex_unlock(&data->lock); > - return 0; > -out: > - return ret; > -} > -#else > -static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) > - { return -EINVAL; } > -#endif/*CONFIG_THERMAL_EMULATION*/ > - > -static struct thermal_sensor_conf exynos_sensor_conf = { > - .name = "exynos-therm", > - .read_temperature = (int (*)(void *))exynos_tmu_read, > - .write_emul_temp = exynos_tmu_set_emulation, > -}; > - > -static void exynos_tmu_work(struct work_struct *work) > -{ > - struct exynos_tmu_data *data = container_of(work, > - struct exynos_tmu_data, irq_work); > - > - exynos_report_trigger(&exynos_sensor_conf); > - mutex_lock(&data->lock); > - clk_enable(data->clk); > - if (data->soc == SOC_ARCH_EXYNOS) > - writel(EXYNOS_TMU_CLEAR_RISE_INT | > - EXYNOS_TMU_CLEAR_FALL_INT, > - data->base + EXYNOS_TMU_REG_INTCLEAR); > - else > - writel(EXYNOS4210_TMU_INTCLEAR_VAL, > - data->base + EXYNOS_TMU_REG_INTCLEAR); > - clk_disable(data->clk); > - mutex_unlock(&data->lock); > - > - 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; > -} > - > -#if defined(CONFIG_CPU_EXYNOS4210) > -static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = { > - .threshold = 80, > - .trigger_levels[0] = 5, > - .trigger_levels[1] = 20, > - .trigger_levels[2] = 30, > - .trigger_level0_en = 1, > - .trigger_level1_en = 1, > - .trigger_level2_en = 1, > - .trigger_level3_en = 0, > - .gain = 15, > - .reference_voltage = 7, > - .cal_type = TYPE_ONE_POINT_TRIMMING, > - .freq_tab[0] = { > - .freq_clip_max = 800 * 1000, > - .temp_level = 85, > - }, > - .freq_tab[1] = { > - .freq_clip_max = 200 * 1000, > - .temp_level = 100, > - }, > - .freq_tab_count = 2, > - .type = SOC_ARCH_EXYNOS4210, > -}; > -#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data) > -#else > -#define EXYNOS4210_TMU_DRV_DATA (NULL) > -#endif > - > -#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412) > -static struct exynos_tmu_platform_data const exynos_default_tmu_data = { > - .threshold_falling = 10, > - .trigger_levels[0] = 85, > - .trigger_levels[1] = 103, > - .trigger_levels[2] = 110, > - .trigger_level0_en = 1, > - .trigger_level1_en = 1, > - .trigger_level2_en = 1, > - .trigger_level3_en = 0, > - .gain = 8, > - .reference_voltage = 16, > - .noise_cancel_mode = 4, > - .cal_type = TYPE_ONE_POINT_TRIMMING, > - .efuse_value = 55, > - .freq_tab[0] = { > - .freq_clip_max = 800 * 1000, > - .temp_level = 85, > - }, > - .freq_tab[1] = { > - .freq_clip_max = 200 * 1000, > - .temp_level = 103, > - }, > - .freq_tab_count = 2, > - .type = SOC_ARCH_EXYNOS, > -}; > -#define EXYNOS_TMU_DRV_DATA (&exynos_default_tmu_data) > -#else > -#define EXYNOS_TMU_DRV_DATA (NULL) > -#endif > - > -#ifdef CONFIG_OF > -static const struct of_device_id exynos_tmu_match[] = { > - { > - .compatible = "samsung,exynos4210-tmu", > - .data = (void *)EXYNOS4210_TMU_DRV_DATA, > - }, > - { > - .compatible = "samsung,exynos5250-tmu", > - .data = (void *)EXYNOS_TMU_DRV_DATA, > - }, > - {}, > -}; > -MODULE_DEVICE_TABLE(of, exynos_tmu_match); > -#endif > - > -static struct platform_device_id exynos_tmu_driver_ids[] = { > - { > - .name = "exynos4210-tmu", > - .driver_data = (kernel_ulong_t)EXYNOS4210_TMU_DRV_DATA, > - }, > - { > - .name = "exynos5250-tmu", > - .driver_data = (kernel_ulong_t)EXYNOS_TMU_DRV_DATA, > - }, > - { }, > -}; > -MODULE_DEVICE_TABLE(platform, exynos_tmu_driver_ids); > - > -static inline struct exynos_tmu_platform_data *exynos_get_driver_data( > - struct platform_device *pdev) > -{ > -#ifdef CONFIG_OF > - if (pdev->dev.of_node) { > - const struct of_device_id *match; > - match = of_match_node(exynos_tmu_match, pdev->dev.of_node); > - if (!match) > - return NULL; > - return (struct exynos_tmu_platform_data *) match->data; > - } > -#endif > - return (struct exynos_tmu_platform_data *) > - platform_get_device_id(pdev)->driver_data; > -} > - > -static int exynos_tmu_probe(struct platform_device *pdev) > -{ > - struct exynos_tmu_data *data; > - struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data; > - int ret, i; > - > - if (!pdata) > - pdata = exynos_get_driver_data(pdev); > - > - if (!pdata) { > - dev_err(&pdev->dev, "No platform init data supplied.\n"); > - return -ENODEV; > - } > - 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; > - } > - > - data->irq = platform_get_irq(pdev, 0); > - if (data->irq < 0) { > - dev_err(&pdev->dev, "Failed to get platform irq\n"); > - return data->irq; > - } > - > - INIT_WORK(&data->irq_work, exynos_tmu_work); > - > - data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); > - if (!data->mem) { > - dev_err(&pdev->dev, "Failed to get platform resource\n"); > - return -ENOENT; > - } > - > - data->base = devm_ioremap_resource(&pdev->dev, data->mem); > - if (IS_ERR(data->base)) > - return PTR_ERR(data->base); > - > - ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, > - IRQF_TRIGGER_RISING, "exynos-tmu", data); > - if (ret) { > - dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); > - return ret; > - } > - > - data->clk = clk_get(NULL, "tmu_apbif"); > - if (IS_ERR(data->clk)) { > - dev_err(&pdev->dev, "Failed to get clock\n"); > - return PTR_ERR(data->clk); > - } > - > - if (pdata->type == SOC_ARCH_EXYNOS || > - pdata->type == SOC_ARCH_EXYNOS4210) > - data->soc = pdata->type; > - else { > - ret = -EINVAL; > - dev_err(&pdev->dev, "Platform not supported\n"); > - goto err_clk; > - } > - > - data->pdata = pdata; > - platform_set_drvdata(pdev, data); > - 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); > - > - /* Register the sensor with thermal management interface */ > - (&exynos_sensor_conf)->driver_data = data; > - exynos_sensor_conf.trip_data.trip_count = pdata->trigger_level0_en + > - pdata->trigger_level1_en + pdata->trigger_level2_en + > - pdata->trigger_level3_en; > - > - for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++) > - exynos_sensor_conf.trip_data.trip_val[i] = > - pdata->threshold + pdata->trigger_levels[i]; > - > - exynos_sensor_conf.trip_data.trigger_falling = pdata->threshold_falling; > - > - exynos_sensor_conf.cooling_data.freq_clip_count = > - pdata->freq_tab_count; > - for (i = 0; i < pdata->freq_tab_count; i++) { > - exynos_sensor_conf.cooling_data.freq_data[i].freq_clip_max = > - pdata->freq_tab[i].freq_clip_max; > - exynos_sensor_conf.cooling_data.freq_data[i].temp_level = > - pdata->freq_tab[i].temp_level; > - } > - > - ret = exynos_register_thermal(&exynos_sensor_conf); > - if (ret) { > - dev_err(&pdev->dev, "Failed to register thermal interface\n"); > - goto err_clk; > - } > - > - return 0; > -err_clk: > - platform_set_drvdata(pdev, NULL); > - clk_put(data->clk); > - return ret; > -} > - > -static int exynos_tmu_remove(struct platform_device *pdev) > -{ > - struct exynos_tmu_data *data = platform_get_drvdata(pdev); > - > - exynos_tmu_control(pdev, false); > - > - exynos_unregister_thermal(&exynos_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) > -{ > - exynos_tmu_control(to_platform_device(dev), false); > - > - return 0; > -} > - > -static int exynos_tmu_resume(struct device *dev) > -{ > - struct platform_device *pdev = to_platform_device(dev); > - > - 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 = "exynos-tmu", > - .owner = THIS_MODULE, > - .pm = EXYNOS_TMU_PM, > - .of_match_table = of_match_ptr(exynos_tmu_match), > - }, > - .probe = exynos_tmu_probe, > - .remove = exynos_tmu_remove, > - .id_table = exynos_tmu_driver_ids, > -}; > - > -module_platform_driver(exynos_tmu_driver); > - > -MODULE_DESCRIPTION("EXYNOS TMU Driver"); > -MODULE_AUTHOR("Donggeun Kim "); > -MODULE_LICENSE("GPL"); > -MODULE_ALIAS("platform:exynos-tmu"); > -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/