159dfa54cSAmit Daniel Kachhap /* 259dfa54cSAmit Daniel Kachhap * exynos_tmu.c - Samsung EXYNOS TMU (Thermal Management Unit) 359dfa54cSAmit Daniel Kachhap * 459dfa54cSAmit Daniel Kachhap * Copyright (C) 2011 Samsung Electronics 559dfa54cSAmit Daniel Kachhap * Donggeun Kim <dg77.kim@samsung.com> 659dfa54cSAmit Daniel Kachhap * Amit Daniel Kachhap <amit.kachhap@linaro.org> 759dfa54cSAmit Daniel Kachhap * 859dfa54cSAmit Daniel Kachhap * This program is free software; you can redistribute it and/or modify 959dfa54cSAmit Daniel Kachhap * it under the terms of the GNU General Public License as published by 1059dfa54cSAmit Daniel Kachhap * the Free Software Foundation; either version 2 of the License, or 1159dfa54cSAmit Daniel Kachhap * (at your option) any later version. 1259dfa54cSAmit Daniel Kachhap * 1359dfa54cSAmit Daniel Kachhap * This program is distributed in the hope that it will be useful, 1459dfa54cSAmit Daniel Kachhap * but WITHOUT ANY WARRANTY; without even the implied warranty of 1559dfa54cSAmit Daniel Kachhap * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1659dfa54cSAmit Daniel Kachhap * GNU General Public License for more details. 1759dfa54cSAmit Daniel Kachhap * 1859dfa54cSAmit Daniel Kachhap * You should have received a copy of the GNU General Public License 1959dfa54cSAmit Daniel Kachhap * along with this program; if not, write to the Free Software 2059dfa54cSAmit Daniel Kachhap * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 2159dfa54cSAmit Daniel Kachhap * 2259dfa54cSAmit Daniel Kachhap */ 2359dfa54cSAmit Daniel Kachhap 2459dfa54cSAmit Daniel Kachhap #include <linux/clk.h> 2559dfa54cSAmit Daniel Kachhap #include <linux/io.h> 2659dfa54cSAmit Daniel Kachhap #include <linux/interrupt.h> 2759dfa54cSAmit Daniel Kachhap #include <linux/module.h> 2859dfa54cSAmit Daniel Kachhap #include <linux/of.h> 2959dfa54cSAmit Daniel Kachhap #include <linux/platform_device.h> 3059dfa54cSAmit Daniel Kachhap 3159dfa54cSAmit Daniel Kachhap #include "exynos_thermal_common.h" 320c1836a6SAmit Daniel Kachhap #include "exynos_tmu.h" 33e6b7991eSAmit Daniel Kachhap #include "exynos_tmu_data.h" 3459dfa54cSAmit Daniel Kachhap 3559dfa54cSAmit Daniel Kachhap struct exynos_tmu_data { 3659dfa54cSAmit Daniel Kachhap struct exynos_tmu_platform_data *pdata; 3759dfa54cSAmit Daniel Kachhap struct resource *mem; 3859dfa54cSAmit Daniel Kachhap void __iomem *base; 3959dfa54cSAmit Daniel Kachhap int irq; 4059dfa54cSAmit Daniel Kachhap enum soc_type soc; 4159dfa54cSAmit Daniel Kachhap struct work_struct irq_work; 4259dfa54cSAmit Daniel Kachhap struct mutex lock; 4359dfa54cSAmit Daniel Kachhap struct clk *clk; 4459dfa54cSAmit Daniel Kachhap u8 temp_error1, temp_error2; 4559dfa54cSAmit Daniel Kachhap }; 4659dfa54cSAmit Daniel Kachhap 4759dfa54cSAmit Daniel Kachhap /* 4859dfa54cSAmit Daniel Kachhap * TMU treats temperature as a mapped temperature code. 4959dfa54cSAmit Daniel Kachhap * The temperature is converted differently depending on the calibration type. 5059dfa54cSAmit Daniel Kachhap */ 5159dfa54cSAmit Daniel Kachhap static int temp_to_code(struct exynos_tmu_data *data, u8 temp) 5259dfa54cSAmit Daniel Kachhap { 5359dfa54cSAmit Daniel Kachhap struct exynos_tmu_platform_data *pdata = data->pdata; 5459dfa54cSAmit Daniel Kachhap int temp_code; 5559dfa54cSAmit Daniel Kachhap 5659dfa54cSAmit Daniel Kachhap if (data->soc == SOC_ARCH_EXYNOS4210) 5759dfa54cSAmit Daniel Kachhap /* temp should range between 25 and 125 */ 5859dfa54cSAmit Daniel Kachhap if (temp < 25 || temp > 125) { 5959dfa54cSAmit Daniel Kachhap temp_code = -EINVAL; 6059dfa54cSAmit Daniel Kachhap goto out; 6159dfa54cSAmit Daniel Kachhap } 6259dfa54cSAmit Daniel Kachhap 6359dfa54cSAmit Daniel Kachhap switch (pdata->cal_type) { 6459dfa54cSAmit Daniel Kachhap case TYPE_TWO_POINT_TRIMMING: 65bb34b4c8SAmit Daniel Kachhap temp_code = (temp - pdata->first_point_trim) * 6659dfa54cSAmit Daniel Kachhap (data->temp_error2 - data->temp_error1) / 67bb34b4c8SAmit Daniel Kachhap (pdata->second_point_trim - pdata->first_point_trim) + 68bb34b4c8SAmit Daniel Kachhap data->temp_error1; 6959dfa54cSAmit Daniel Kachhap break; 7059dfa54cSAmit Daniel Kachhap case TYPE_ONE_POINT_TRIMMING: 71bb34b4c8SAmit Daniel Kachhap temp_code = temp + data->temp_error1 - pdata->first_point_trim; 7259dfa54cSAmit Daniel Kachhap break; 7359dfa54cSAmit Daniel Kachhap default: 74bb34b4c8SAmit Daniel Kachhap temp_code = temp + pdata->default_temp_offset; 7559dfa54cSAmit Daniel Kachhap break; 7659dfa54cSAmit Daniel Kachhap } 7759dfa54cSAmit Daniel Kachhap out: 7859dfa54cSAmit Daniel Kachhap return temp_code; 7959dfa54cSAmit Daniel Kachhap } 8059dfa54cSAmit Daniel Kachhap 8159dfa54cSAmit Daniel Kachhap /* 8259dfa54cSAmit Daniel Kachhap * Calculate a temperature value from a temperature code. 8359dfa54cSAmit Daniel Kachhap * The unit of the temperature is degree Celsius. 8459dfa54cSAmit Daniel Kachhap */ 8559dfa54cSAmit Daniel Kachhap static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code) 8659dfa54cSAmit Daniel Kachhap { 8759dfa54cSAmit Daniel Kachhap struct exynos_tmu_platform_data *pdata = data->pdata; 8859dfa54cSAmit Daniel Kachhap int temp; 8959dfa54cSAmit Daniel Kachhap 9059dfa54cSAmit Daniel Kachhap if (data->soc == SOC_ARCH_EXYNOS4210) 9159dfa54cSAmit Daniel Kachhap /* temp_code should range between 75 and 175 */ 9259dfa54cSAmit Daniel Kachhap if (temp_code < 75 || temp_code > 175) { 9359dfa54cSAmit Daniel Kachhap temp = -ENODATA; 9459dfa54cSAmit Daniel Kachhap goto out; 9559dfa54cSAmit Daniel Kachhap } 9659dfa54cSAmit Daniel Kachhap 9759dfa54cSAmit Daniel Kachhap switch (pdata->cal_type) { 9859dfa54cSAmit Daniel Kachhap case TYPE_TWO_POINT_TRIMMING: 99bb34b4c8SAmit Daniel Kachhap temp = (temp_code - data->temp_error1) * 100bb34b4c8SAmit Daniel Kachhap (pdata->second_point_trim - pdata->first_point_trim) / 101bb34b4c8SAmit Daniel Kachhap (data->temp_error2 - data->temp_error1) + 102bb34b4c8SAmit Daniel Kachhap pdata->first_point_trim; 10359dfa54cSAmit Daniel Kachhap break; 10459dfa54cSAmit Daniel Kachhap case TYPE_ONE_POINT_TRIMMING: 105bb34b4c8SAmit Daniel Kachhap temp = temp_code - data->temp_error1 + pdata->first_point_trim; 10659dfa54cSAmit Daniel Kachhap break; 10759dfa54cSAmit Daniel Kachhap default: 108bb34b4c8SAmit Daniel Kachhap temp = temp_code - pdata->default_temp_offset; 10959dfa54cSAmit Daniel Kachhap break; 11059dfa54cSAmit Daniel Kachhap } 11159dfa54cSAmit Daniel Kachhap out: 11259dfa54cSAmit Daniel Kachhap return temp; 11359dfa54cSAmit Daniel Kachhap } 11459dfa54cSAmit Daniel Kachhap 11559dfa54cSAmit Daniel Kachhap static int exynos_tmu_initialize(struct platform_device *pdev) 11659dfa54cSAmit Daniel Kachhap { 11759dfa54cSAmit Daniel Kachhap struct exynos_tmu_data *data = platform_get_drvdata(pdev); 11859dfa54cSAmit Daniel Kachhap struct exynos_tmu_platform_data *pdata = data->pdata; 119b8d582b9SAmit Daniel Kachhap const struct exynos_tmu_registers *reg = pdata->registers; 1207ca04e58SAmit Daniel Kachhap unsigned int status, trim_info = 0, con; 12159dfa54cSAmit Daniel Kachhap unsigned int rising_threshold = 0, falling_threshold = 0; 12259dfa54cSAmit Daniel Kachhap int ret = 0, threshold_code, i, trigger_levs = 0; 12359dfa54cSAmit Daniel Kachhap 12459dfa54cSAmit Daniel Kachhap mutex_lock(&data->lock); 12559dfa54cSAmit Daniel Kachhap clk_enable(data->clk); 12659dfa54cSAmit Daniel Kachhap 127b8d582b9SAmit Daniel Kachhap status = readb(data->base + reg->tmu_status); 12859dfa54cSAmit Daniel Kachhap if (!status) { 12959dfa54cSAmit Daniel Kachhap ret = -EBUSY; 13059dfa54cSAmit Daniel Kachhap goto out; 13159dfa54cSAmit Daniel Kachhap } 13259dfa54cSAmit Daniel Kachhap 133b8d582b9SAmit Daniel Kachhap if (data->soc == SOC_ARCH_EXYNOS) 134b8d582b9SAmit Daniel Kachhap __raw_writel(1, data->base + reg->triminfo_ctrl); 135b8d582b9SAmit Daniel Kachhap 13659dfa54cSAmit Daniel Kachhap /* Save trimming info in order to perform calibration */ 137b8d582b9SAmit Daniel Kachhap trim_info = readl(data->base + reg->triminfo_data); 138b8d582b9SAmit Daniel Kachhap data->temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK; 139b8d582b9SAmit Daniel Kachhap data->temp_error2 = ((trim_info >> reg->triminfo_85_shift) & 140b8d582b9SAmit Daniel Kachhap EXYNOS_TMU_TEMP_MASK); 14159dfa54cSAmit Daniel Kachhap 142bb34b4c8SAmit Daniel Kachhap if ((pdata->min_efuse_value > data->temp_error1) || 143bb34b4c8SAmit Daniel Kachhap (data->temp_error1 > pdata->max_efuse_value) || 14459dfa54cSAmit Daniel Kachhap (data->temp_error2 != 0)) 14559dfa54cSAmit Daniel Kachhap data->temp_error1 = pdata->efuse_value; 14659dfa54cSAmit Daniel Kachhap 1477ca04e58SAmit Daniel Kachhap if (pdata->max_trigger_level > MAX_THRESHOLD_LEVS) { 1487ca04e58SAmit Daniel Kachhap dev_err(&pdev->dev, "Invalid max trigger level\n"); 1497ca04e58SAmit Daniel Kachhap goto out; 1507ca04e58SAmit Daniel Kachhap } 1517ca04e58SAmit Daniel Kachhap 1527ca04e58SAmit Daniel Kachhap for (i = 0; i < pdata->max_trigger_level; i++) { 1537ca04e58SAmit Daniel Kachhap if (!pdata->trigger_levels[i]) 1547ca04e58SAmit Daniel Kachhap continue; 1557ca04e58SAmit Daniel Kachhap 1567ca04e58SAmit Daniel Kachhap if ((pdata->trigger_type[i] == HW_TRIP) && 1577ca04e58SAmit Daniel Kachhap (!pdata->trigger_levels[pdata->max_trigger_level - 1])) { 1587ca04e58SAmit Daniel Kachhap dev_err(&pdev->dev, "Invalid hw trigger level\n"); 1597ca04e58SAmit Daniel Kachhap ret = -EINVAL; 1607ca04e58SAmit Daniel Kachhap goto out; 1617ca04e58SAmit Daniel Kachhap } 1627ca04e58SAmit Daniel Kachhap 1637ca04e58SAmit Daniel Kachhap /* Count trigger levels except the HW trip*/ 1647ca04e58SAmit Daniel Kachhap if (!(pdata->trigger_type[i] == HW_TRIP)) 16559dfa54cSAmit Daniel Kachhap trigger_levs++; 1667ca04e58SAmit Daniel Kachhap } 16759dfa54cSAmit Daniel Kachhap 16859dfa54cSAmit Daniel Kachhap if (data->soc == SOC_ARCH_EXYNOS4210) { 16959dfa54cSAmit Daniel Kachhap /* Write temperature code for threshold */ 17059dfa54cSAmit Daniel Kachhap threshold_code = temp_to_code(data, pdata->threshold); 17159dfa54cSAmit Daniel Kachhap if (threshold_code < 0) { 17259dfa54cSAmit Daniel Kachhap ret = threshold_code; 17359dfa54cSAmit Daniel Kachhap goto out; 17459dfa54cSAmit Daniel Kachhap } 17559dfa54cSAmit Daniel Kachhap writeb(threshold_code, 176b8d582b9SAmit Daniel Kachhap data->base + reg->threshold_temp); 17759dfa54cSAmit Daniel Kachhap for (i = 0; i < trigger_levs; i++) 178b8d582b9SAmit Daniel Kachhap writeb(pdata->trigger_levels[i], data->base + 179b8d582b9SAmit Daniel Kachhap reg->threshold_th0 + i * sizeof(reg->threshold_th0)); 18059dfa54cSAmit Daniel Kachhap 181b8d582b9SAmit Daniel Kachhap writel(reg->inten_rise_mask, data->base + reg->tmu_intclear); 18259dfa54cSAmit Daniel Kachhap } else if (data->soc == SOC_ARCH_EXYNOS) { 18359dfa54cSAmit Daniel Kachhap /* Write temperature code for rising and falling threshold */ 1847ca04e58SAmit Daniel Kachhap for (i = 0; 1857ca04e58SAmit Daniel Kachhap i < trigger_levs && i < EXYNOS_MAX_TRIGGER_PER_REG; i++) { 18659dfa54cSAmit Daniel Kachhap threshold_code = temp_to_code(data, 18759dfa54cSAmit Daniel Kachhap pdata->trigger_levels[i]); 18859dfa54cSAmit Daniel Kachhap if (threshold_code < 0) { 18959dfa54cSAmit Daniel Kachhap ret = threshold_code; 19059dfa54cSAmit Daniel Kachhap goto out; 19159dfa54cSAmit Daniel Kachhap } 19259dfa54cSAmit Daniel Kachhap rising_threshold |= threshold_code << 8 * i; 19359dfa54cSAmit Daniel Kachhap if (pdata->threshold_falling) { 19459dfa54cSAmit Daniel Kachhap threshold_code = temp_to_code(data, 19559dfa54cSAmit Daniel Kachhap pdata->trigger_levels[i] - 19659dfa54cSAmit Daniel Kachhap pdata->threshold_falling); 19759dfa54cSAmit Daniel Kachhap if (threshold_code > 0) 19859dfa54cSAmit Daniel Kachhap falling_threshold |= 19959dfa54cSAmit Daniel Kachhap threshold_code << 8 * i; 20059dfa54cSAmit Daniel Kachhap } 20159dfa54cSAmit Daniel Kachhap } 20259dfa54cSAmit Daniel Kachhap 20359dfa54cSAmit Daniel Kachhap writel(rising_threshold, 204b8d582b9SAmit Daniel Kachhap data->base + reg->threshold_th0); 20559dfa54cSAmit Daniel Kachhap writel(falling_threshold, 206b8d582b9SAmit Daniel Kachhap data->base + reg->threshold_th1); 20759dfa54cSAmit Daniel Kachhap 208b8d582b9SAmit Daniel Kachhap writel((reg->inten_rise_mask << reg->inten_rise_shift) | 209b8d582b9SAmit Daniel Kachhap (reg->inten_fall_mask << reg->inten_fall_shift), 210b8d582b9SAmit Daniel Kachhap data->base + reg->tmu_intclear); 2117ca04e58SAmit Daniel Kachhap 2127ca04e58SAmit Daniel Kachhap /* if last threshold limit is also present */ 2137ca04e58SAmit Daniel Kachhap i = pdata->max_trigger_level - 1; 2147ca04e58SAmit Daniel Kachhap if (pdata->trigger_levels[i] && 2157ca04e58SAmit Daniel Kachhap (pdata->trigger_type[i] == HW_TRIP)) { 2167ca04e58SAmit Daniel Kachhap threshold_code = temp_to_code(data, 2177ca04e58SAmit Daniel Kachhap pdata->trigger_levels[i]); 2187ca04e58SAmit Daniel Kachhap if (threshold_code < 0) { 2197ca04e58SAmit Daniel Kachhap ret = threshold_code; 2207ca04e58SAmit Daniel Kachhap goto out; 2217ca04e58SAmit Daniel Kachhap } 2227ca04e58SAmit Daniel Kachhap rising_threshold |= threshold_code << 8 * i; 2237ca04e58SAmit Daniel Kachhap writel(rising_threshold, 2247ca04e58SAmit Daniel Kachhap data->base + reg->threshold_th0); 2257ca04e58SAmit Daniel Kachhap con = readl(data->base + reg->tmu_ctrl); 2267ca04e58SAmit Daniel Kachhap con |= (1 << reg->therm_trip_en_shift); 2277ca04e58SAmit Daniel Kachhap writel(con, data->base + reg->tmu_ctrl); 2287ca04e58SAmit Daniel Kachhap } 22959dfa54cSAmit Daniel Kachhap } 23059dfa54cSAmit Daniel Kachhap out: 23159dfa54cSAmit Daniel Kachhap clk_disable(data->clk); 23259dfa54cSAmit Daniel Kachhap mutex_unlock(&data->lock); 23359dfa54cSAmit Daniel Kachhap 23459dfa54cSAmit Daniel Kachhap return ret; 23559dfa54cSAmit Daniel Kachhap } 23659dfa54cSAmit Daniel Kachhap 23759dfa54cSAmit Daniel Kachhap static void exynos_tmu_control(struct platform_device *pdev, bool on) 23859dfa54cSAmit Daniel Kachhap { 23959dfa54cSAmit Daniel Kachhap struct exynos_tmu_data *data = platform_get_drvdata(pdev); 24059dfa54cSAmit Daniel Kachhap struct exynos_tmu_platform_data *pdata = data->pdata; 241b8d582b9SAmit Daniel Kachhap const struct exynos_tmu_registers *reg = pdata->registers; 24259dfa54cSAmit Daniel Kachhap unsigned int con, interrupt_en; 24359dfa54cSAmit Daniel Kachhap 24459dfa54cSAmit Daniel Kachhap mutex_lock(&data->lock); 24559dfa54cSAmit Daniel Kachhap clk_enable(data->clk); 24659dfa54cSAmit Daniel Kachhap 247b8d582b9SAmit Daniel Kachhap con = readl(data->base + reg->tmu_ctrl); 24859dfa54cSAmit Daniel Kachhap 249d0a0ce3eSAmit Daniel Kachhap if (pdata->reference_voltage) { 250b8d582b9SAmit Daniel Kachhap con &= ~(reg->buf_vref_sel_mask << reg->buf_vref_sel_shift); 251b8d582b9SAmit Daniel Kachhap con |= pdata->reference_voltage << reg->buf_vref_sel_shift; 252d0a0ce3eSAmit Daniel Kachhap } 253d0a0ce3eSAmit Daniel Kachhap 254d0a0ce3eSAmit Daniel Kachhap if (pdata->gain) { 255b8d582b9SAmit Daniel Kachhap con &= ~(reg->buf_slope_sel_mask << reg->buf_slope_sel_shift); 256b8d582b9SAmit Daniel Kachhap con |= (pdata->gain << reg->buf_slope_sel_shift); 257d0a0ce3eSAmit Daniel Kachhap } 258d0a0ce3eSAmit Daniel Kachhap 259d0a0ce3eSAmit Daniel Kachhap if (pdata->noise_cancel_mode) { 260b8d582b9SAmit Daniel Kachhap con &= ~(reg->therm_trip_mode_mask << 261b8d582b9SAmit Daniel Kachhap reg->therm_trip_mode_shift); 262b8d582b9SAmit Daniel Kachhap con |= (pdata->noise_cancel_mode << reg->therm_trip_mode_shift); 26359dfa54cSAmit Daniel Kachhap } 26459dfa54cSAmit Daniel Kachhap 26559dfa54cSAmit Daniel Kachhap if (on) { 266b8d582b9SAmit Daniel Kachhap con |= (1 << reg->core_en_shift); 267d0a0ce3eSAmit Daniel Kachhap interrupt_en = 268b8d582b9SAmit Daniel Kachhap pdata->trigger_enable[3] << reg->inten_rise3_shift | 269b8d582b9SAmit Daniel Kachhap pdata->trigger_enable[2] << reg->inten_rise2_shift | 270b8d582b9SAmit Daniel Kachhap pdata->trigger_enable[1] << reg->inten_rise1_shift | 271b8d582b9SAmit Daniel Kachhap pdata->trigger_enable[0] << reg->inten_rise0_shift; 27259dfa54cSAmit Daniel Kachhap if (pdata->threshold_falling) 273d0a0ce3eSAmit Daniel Kachhap interrupt_en |= 274b8d582b9SAmit Daniel Kachhap interrupt_en << reg->inten_fall0_shift; 27559dfa54cSAmit Daniel Kachhap } else { 276b8d582b9SAmit Daniel Kachhap con &= ~(1 << reg->core_en_shift); 27759dfa54cSAmit Daniel Kachhap interrupt_en = 0; /* Disable all interrupts */ 27859dfa54cSAmit Daniel Kachhap } 279b8d582b9SAmit Daniel Kachhap writel(interrupt_en, data->base + reg->tmu_inten); 280b8d582b9SAmit Daniel Kachhap writel(con, data->base + reg->tmu_ctrl); 28159dfa54cSAmit Daniel Kachhap 28259dfa54cSAmit Daniel Kachhap clk_disable(data->clk); 28359dfa54cSAmit Daniel Kachhap mutex_unlock(&data->lock); 28459dfa54cSAmit Daniel Kachhap } 28559dfa54cSAmit Daniel Kachhap 28659dfa54cSAmit Daniel Kachhap static int exynos_tmu_read(struct exynos_tmu_data *data) 28759dfa54cSAmit Daniel Kachhap { 288b8d582b9SAmit Daniel Kachhap struct exynos_tmu_platform_data *pdata = data->pdata; 289b8d582b9SAmit Daniel Kachhap const struct exynos_tmu_registers *reg = pdata->registers; 29059dfa54cSAmit Daniel Kachhap u8 temp_code; 29159dfa54cSAmit Daniel Kachhap int temp; 29259dfa54cSAmit Daniel Kachhap 29359dfa54cSAmit Daniel Kachhap mutex_lock(&data->lock); 29459dfa54cSAmit Daniel Kachhap clk_enable(data->clk); 29559dfa54cSAmit Daniel Kachhap 296b8d582b9SAmit Daniel Kachhap temp_code = readb(data->base + reg->tmu_cur_temp); 29759dfa54cSAmit Daniel Kachhap temp = code_to_temp(data, temp_code); 29859dfa54cSAmit Daniel Kachhap 29959dfa54cSAmit Daniel Kachhap clk_disable(data->clk); 30059dfa54cSAmit Daniel Kachhap mutex_unlock(&data->lock); 30159dfa54cSAmit Daniel Kachhap 30259dfa54cSAmit Daniel Kachhap return temp; 30359dfa54cSAmit Daniel Kachhap } 30459dfa54cSAmit Daniel Kachhap 30559dfa54cSAmit Daniel Kachhap #ifdef CONFIG_THERMAL_EMULATION 30659dfa54cSAmit Daniel Kachhap static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) 30759dfa54cSAmit Daniel Kachhap { 30859dfa54cSAmit Daniel Kachhap struct exynos_tmu_data *data = drv_data; 309b8d582b9SAmit Daniel Kachhap struct exynos_tmu_platform_data *pdata = data->pdata; 310b8d582b9SAmit Daniel Kachhap const struct exynos_tmu_registers *reg = pdata->registers; 311b8d582b9SAmit Daniel Kachhap unsigned int val; 31259dfa54cSAmit Daniel Kachhap int ret = -EINVAL; 31359dfa54cSAmit Daniel Kachhap 31459dfa54cSAmit Daniel Kachhap if (data->soc == SOC_ARCH_EXYNOS4210) 31559dfa54cSAmit Daniel Kachhap goto out; 31659dfa54cSAmit Daniel Kachhap 31759dfa54cSAmit Daniel Kachhap if (temp && temp < MCELSIUS) 31859dfa54cSAmit Daniel Kachhap goto out; 31959dfa54cSAmit Daniel Kachhap 32059dfa54cSAmit Daniel Kachhap mutex_lock(&data->lock); 32159dfa54cSAmit Daniel Kachhap clk_enable(data->clk); 32259dfa54cSAmit Daniel Kachhap 323b8d582b9SAmit Daniel Kachhap val = readl(data->base + reg->emul_con); 32459dfa54cSAmit Daniel Kachhap 32559dfa54cSAmit Daniel Kachhap if (temp) { 32659dfa54cSAmit Daniel Kachhap temp /= MCELSIUS; 32759dfa54cSAmit Daniel Kachhap 328b8d582b9SAmit Daniel Kachhap val = (EXYNOS_EMUL_TIME << reg->emul_time_shift) | 32959dfa54cSAmit Daniel Kachhap (temp_to_code(data, temp) 330b8d582b9SAmit Daniel Kachhap << reg->emul_temp_shift) | EXYNOS_EMUL_ENABLE; 33159dfa54cSAmit Daniel Kachhap } else { 332b8d582b9SAmit Daniel Kachhap val &= ~EXYNOS_EMUL_ENABLE; 33359dfa54cSAmit Daniel Kachhap } 33459dfa54cSAmit Daniel Kachhap 335b8d582b9SAmit Daniel Kachhap writel(val, data->base + reg->emul_con); 33659dfa54cSAmit Daniel Kachhap 33759dfa54cSAmit Daniel Kachhap clk_disable(data->clk); 33859dfa54cSAmit Daniel Kachhap mutex_unlock(&data->lock); 33959dfa54cSAmit Daniel Kachhap return 0; 34059dfa54cSAmit Daniel Kachhap out: 34159dfa54cSAmit Daniel Kachhap return ret; 34259dfa54cSAmit Daniel Kachhap } 34359dfa54cSAmit Daniel Kachhap #else 34459dfa54cSAmit Daniel Kachhap static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) 34559dfa54cSAmit Daniel Kachhap { return -EINVAL; } 34659dfa54cSAmit Daniel Kachhap #endif/*CONFIG_THERMAL_EMULATION*/ 34759dfa54cSAmit Daniel Kachhap 3484de0bdaaSAmit Daniel Kachhap static struct thermal_sensor_conf exynos_sensor_conf = { 3494de0bdaaSAmit Daniel Kachhap .name = "exynos-therm", 3504de0bdaaSAmit Daniel Kachhap .read_temperature = (int (*)(void *))exynos_tmu_read, 3514de0bdaaSAmit Daniel Kachhap .write_emul_temp = exynos_tmu_set_emulation, 3524de0bdaaSAmit Daniel Kachhap }; 3534de0bdaaSAmit Daniel Kachhap 35459dfa54cSAmit Daniel Kachhap static void exynos_tmu_work(struct work_struct *work) 35559dfa54cSAmit Daniel Kachhap { 35659dfa54cSAmit Daniel Kachhap struct exynos_tmu_data *data = container_of(work, 35759dfa54cSAmit Daniel Kachhap struct exynos_tmu_data, irq_work); 358b8d582b9SAmit Daniel Kachhap struct exynos_tmu_platform_data *pdata = data->pdata; 359b8d582b9SAmit Daniel Kachhap const struct exynos_tmu_registers *reg = pdata->registers; 360a4463c4fSAmit Daniel Kachhap unsigned int val_irq; 36159dfa54cSAmit Daniel Kachhap 3624de0bdaaSAmit Daniel Kachhap exynos_report_trigger(&exynos_sensor_conf); 36359dfa54cSAmit Daniel Kachhap mutex_lock(&data->lock); 36459dfa54cSAmit Daniel Kachhap clk_enable(data->clk); 365b8d582b9SAmit Daniel Kachhap 366a4463c4fSAmit Daniel Kachhap /* TODO: take action based on particular interrupt */ 367a4463c4fSAmit Daniel Kachhap val_irq = readl(data->base + reg->tmu_intstat); 368a4463c4fSAmit Daniel Kachhap /* clear the interrupts */ 369a4463c4fSAmit Daniel Kachhap writel(val_irq, data->base + reg->tmu_intclear); 370b8d582b9SAmit Daniel Kachhap 37159dfa54cSAmit Daniel Kachhap clk_disable(data->clk); 37259dfa54cSAmit Daniel Kachhap mutex_unlock(&data->lock); 37359dfa54cSAmit Daniel Kachhap 37459dfa54cSAmit Daniel Kachhap enable_irq(data->irq); 37559dfa54cSAmit Daniel Kachhap } 37659dfa54cSAmit Daniel Kachhap 37759dfa54cSAmit Daniel Kachhap static irqreturn_t exynos_tmu_irq(int irq, void *id) 37859dfa54cSAmit Daniel Kachhap { 37959dfa54cSAmit Daniel Kachhap struct exynos_tmu_data *data = id; 38059dfa54cSAmit Daniel Kachhap 38159dfa54cSAmit Daniel Kachhap disable_irq_nosync(irq); 38259dfa54cSAmit Daniel Kachhap schedule_work(&data->irq_work); 38359dfa54cSAmit Daniel Kachhap 38459dfa54cSAmit Daniel Kachhap return IRQ_HANDLED; 38559dfa54cSAmit Daniel Kachhap } 38659dfa54cSAmit Daniel Kachhap 38759dfa54cSAmit Daniel Kachhap #ifdef CONFIG_OF 38859dfa54cSAmit Daniel Kachhap static const struct of_device_id exynos_tmu_match[] = { 38959dfa54cSAmit Daniel Kachhap { 39059dfa54cSAmit Daniel Kachhap .compatible = "samsung,exynos4210-tmu", 39159dfa54cSAmit Daniel Kachhap .data = (void *)EXYNOS4210_TMU_DRV_DATA, 39259dfa54cSAmit Daniel Kachhap }, 39359dfa54cSAmit Daniel Kachhap { 39459dfa54cSAmit Daniel Kachhap .compatible = "samsung,exynos4412-tmu", 395e6b7991eSAmit Daniel Kachhap .data = (void *)EXYNOS5250_TMU_DRV_DATA, 39659dfa54cSAmit Daniel Kachhap }, 39759dfa54cSAmit Daniel Kachhap { 39859dfa54cSAmit Daniel Kachhap .compatible = "samsung,exynos5250-tmu", 399e6b7991eSAmit Daniel Kachhap .data = (void *)EXYNOS5250_TMU_DRV_DATA, 40059dfa54cSAmit Daniel Kachhap }, 40159dfa54cSAmit Daniel Kachhap {}, 40259dfa54cSAmit Daniel Kachhap }; 40359dfa54cSAmit Daniel Kachhap MODULE_DEVICE_TABLE(of, exynos_tmu_match); 40459dfa54cSAmit Daniel Kachhap #endif 40559dfa54cSAmit Daniel Kachhap 40659dfa54cSAmit Daniel Kachhap static inline struct exynos_tmu_platform_data *exynos_get_driver_data( 40759dfa54cSAmit Daniel Kachhap struct platform_device *pdev) 40859dfa54cSAmit Daniel Kachhap { 40959dfa54cSAmit Daniel Kachhap #ifdef CONFIG_OF 41059dfa54cSAmit Daniel Kachhap if (pdev->dev.of_node) { 41159dfa54cSAmit Daniel Kachhap const struct of_device_id *match; 41259dfa54cSAmit Daniel Kachhap match = of_match_node(exynos_tmu_match, pdev->dev.of_node); 41359dfa54cSAmit Daniel Kachhap if (!match) 41459dfa54cSAmit Daniel Kachhap return NULL; 41559dfa54cSAmit Daniel Kachhap return (struct exynos_tmu_platform_data *) match->data; 41659dfa54cSAmit Daniel Kachhap } 41759dfa54cSAmit Daniel Kachhap #endif 418*1cd1ecb6SAmit Daniel Kachhap return NULL; 41959dfa54cSAmit Daniel Kachhap } 42059dfa54cSAmit Daniel Kachhap 42159dfa54cSAmit Daniel Kachhap static int exynos_tmu_probe(struct platform_device *pdev) 42259dfa54cSAmit Daniel Kachhap { 42359dfa54cSAmit Daniel Kachhap struct exynos_tmu_data *data; 42459dfa54cSAmit Daniel Kachhap struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data; 42559dfa54cSAmit Daniel Kachhap int ret, i; 42659dfa54cSAmit Daniel Kachhap 42759dfa54cSAmit Daniel Kachhap if (!pdata) 42859dfa54cSAmit Daniel Kachhap pdata = exynos_get_driver_data(pdev); 42959dfa54cSAmit Daniel Kachhap 43059dfa54cSAmit Daniel Kachhap if (!pdata) { 43159dfa54cSAmit Daniel Kachhap dev_err(&pdev->dev, "No platform init data supplied.\n"); 43259dfa54cSAmit Daniel Kachhap return -ENODEV; 43359dfa54cSAmit Daniel Kachhap } 43459dfa54cSAmit Daniel Kachhap data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data), 43559dfa54cSAmit Daniel Kachhap GFP_KERNEL); 43659dfa54cSAmit Daniel Kachhap if (!data) { 43759dfa54cSAmit Daniel Kachhap dev_err(&pdev->dev, "Failed to allocate driver structure\n"); 43859dfa54cSAmit Daniel Kachhap return -ENOMEM; 43959dfa54cSAmit Daniel Kachhap } 44059dfa54cSAmit Daniel Kachhap 44159dfa54cSAmit Daniel Kachhap data->irq = platform_get_irq(pdev, 0); 44259dfa54cSAmit Daniel Kachhap if (data->irq < 0) { 44359dfa54cSAmit Daniel Kachhap dev_err(&pdev->dev, "Failed to get platform irq\n"); 44459dfa54cSAmit Daniel Kachhap return data->irq; 44559dfa54cSAmit Daniel Kachhap } 44659dfa54cSAmit Daniel Kachhap 44759dfa54cSAmit Daniel Kachhap INIT_WORK(&data->irq_work, exynos_tmu_work); 44859dfa54cSAmit Daniel Kachhap 44959dfa54cSAmit Daniel Kachhap data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 45059dfa54cSAmit Daniel Kachhap data->base = devm_ioremap_resource(&pdev->dev, data->mem); 45159dfa54cSAmit Daniel Kachhap if (IS_ERR(data->base)) 45259dfa54cSAmit Daniel Kachhap return PTR_ERR(data->base); 45359dfa54cSAmit Daniel Kachhap 45459dfa54cSAmit Daniel Kachhap ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, 45559dfa54cSAmit Daniel Kachhap IRQF_TRIGGER_RISING, "exynos-tmu", data); 45659dfa54cSAmit Daniel Kachhap if (ret) { 45759dfa54cSAmit Daniel Kachhap dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); 45859dfa54cSAmit Daniel Kachhap return ret; 45959dfa54cSAmit Daniel Kachhap } 46059dfa54cSAmit Daniel Kachhap 46159dfa54cSAmit Daniel Kachhap data->clk = devm_clk_get(&pdev->dev, "tmu_apbif"); 46259dfa54cSAmit Daniel Kachhap if (IS_ERR(data->clk)) { 46359dfa54cSAmit Daniel Kachhap dev_err(&pdev->dev, "Failed to get clock\n"); 46459dfa54cSAmit Daniel Kachhap return PTR_ERR(data->clk); 46559dfa54cSAmit Daniel Kachhap } 46659dfa54cSAmit Daniel Kachhap 46759dfa54cSAmit Daniel Kachhap ret = clk_prepare(data->clk); 46859dfa54cSAmit Daniel Kachhap if (ret) 46959dfa54cSAmit Daniel Kachhap return ret; 47059dfa54cSAmit Daniel Kachhap 47159dfa54cSAmit Daniel Kachhap if (pdata->type == SOC_ARCH_EXYNOS || 47259dfa54cSAmit Daniel Kachhap pdata->type == SOC_ARCH_EXYNOS4210) 47359dfa54cSAmit Daniel Kachhap data->soc = pdata->type; 47459dfa54cSAmit Daniel Kachhap else { 47559dfa54cSAmit Daniel Kachhap ret = -EINVAL; 47659dfa54cSAmit Daniel Kachhap dev_err(&pdev->dev, "Platform not supported\n"); 47759dfa54cSAmit Daniel Kachhap goto err_clk; 47859dfa54cSAmit Daniel Kachhap } 47959dfa54cSAmit Daniel Kachhap 48059dfa54cSAmit Daniel Kachhap data->pdata = pdata; 48159dfa54cSAmit Daniel Kachhap platform_set_drvdata(pdev, data); 48259dfa54cSAmit Daniel Kachhap mutex_init(&data->lock); 48359dfa54cSAmit Daniel Kachhap 48459dfa54cSAmit Daniel Kachhap ret = exynos_tmu_initialize(pdev); 48559dfa54cSAmit Daniel Kachhap if (ret) { 48659dfa54cSAmit Daniel Kachhap dev_err(&pdev->dev, "Failed to initialize TMU\n"); 48759dfa54cSAmit Daniel Kachhap goto err_clk; 48859dfa54cSAmit Daniel Kachhap } 48959dfa54cSAmit Daniel Kachhap 49059dfa54cSAmit Daniel Kachhap exynos_tmu_control(pdev, true); 49159dfa54cSAmit Daniel Kachhap 49259dfa54cSAmit Daniel Kachhap /* Register the sensor with thermal management interface */ 493d58f0a6dSAmit Daniel Kachhap (&exynos_sensor_conf)->driver_data = data; 494bb34b4c8SAmit Daniel Kachhap exynos_sensor_conf.trip_data.trip_count = pdata->trigger_enable[0] + 495bb34b4c8SAmit Daniel Kachhap pdata->trigger_enable[1] + pdata->trigger_enable[2]+ 496bb34b4c8SAmit Daniel Kachhap pdata->trigger_enable[3]; 49759dfa54cSAmit Daniel Kachhap 4985c3cf552SAmit Daniel Kachhap for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++) { 49959dfa54cSAmit Daniel Kachhap exynos_sensor_conf.trip_data.trip_val[i] = 50059dfa54cSAmit Daniel Kachhap pdata->threshold + pdata->trigger_levels[i]; 5015c3cf552SAmit Daniel Kachhap exynos_sensor_conf.trip_data.trip_type[i] = 5025c3cf552SAmit Daniel Kachhap pdata->trigger_type[i]; 5035c3cf552SAmit Daniel Kachhap } 50459dfa54cSAmit Daniel Kachhap 50559dfa54cSAmit Daniel Kachhap exynos_sensor_conf.trip_data.trigger_falling = pdata->threshold_falling; 50659dfa54cSAmit Daniel Kachhap 50759dfa54cSAmit Daniel Kachhap exynos_sensor_conf.cooling_data.freq_clip_count = 50859dfa54cSAmit Daniel Kachhap pdata->freq_tab_count; 50959dfa54cSAmit Daniel Kachhap for (i = 0; i < pdata->freq_tab_count; i++) { 51059dfa54cSAmit Daniel Kachhap exynos_sensor_conf.cooling_data.freq_data[i].freq_clip_max = 51159dfa54cSAmit Daniel Kachhap pdata->freq_tab[i].freq_clip_max; 51259dfa54cSAmit Daniel Kachhap exynos_sensor_conf.cooling_data.freq_data[i].temp_level = 51359dfa54cSAmit Daniel Kachhap pdata->freq_tab[i].temp_level; 51459dfa54cSAmit Daniel Kachhap } 51559dfa54cSAmit Daniel Kachhap 51659dfa54cSAmit Daniel Kachhap ret = exynos_register_thermal(&exynos_sensor_conf); 51759dfa54cSAmit Daniel Kachhap if (ret) { 51859dfa54cSAmit Daniel Kachhap dev_err(&pdev->dev, "Failed to register thermal interface\n"); 51959dfa54cSAmit Daniel Kachhap goto err_clk; 52059dfa54cSAmit Daniel Kachhap } 52159dfa54cSAmit Daniel Kachhap 52259dfa54cSAmit Daniel Kachhap return 0; 52359dfa54cSAmit Daniel Kachhap err_clk: 52459dfa54cSAmit Daniel Kachhap clk_unprepare(data->clk); 52559dfa54cSAmit Daniel Kachhap return ret; 52659dfa54cSAmit Daniel Kachhap } 52759dfa54cSAmit Daniel Kachhap 52859dfa54cSAmit Daniel Kachhap static int exynos_tmu_remove(struct platform_device *pdev) 52959dfa54cSAmit Daniel Kachhap { 53059dfa54cSAmit Daniel Kachhap struct exynos_tmu_data *data = platform_get_drvdata(pdev); 53159dfa54cSAmit Daniel Kachhap 53259dfa54cSAmit Daniel Kachhap exynos_tmu_control(pdev, false); 53359dfa54cSAmit Daniel Kachhap 5344de0bdaaSAmit Daniel Kachhap exynos_unregister_thermal(&exynos_sensor_conf); 53559dfa54cSAmit Daniel Kachhap 53659dfa54cSAmit Daniel Kachhap clk_unprepare(data->clk); 53759dfa54cSAmit Daniel Kachhap 53859dfa54cSAmit Daniel Kachhap return 0; 53959dfa54cSAmit Daniel Kachhap } 54059dfa54cSAmit Daniel Kachhap 54159dfa54cSAmit Daniel Kachhap #ifdef CONFIG_PM_SLEEP 54259dfa54cSAmit Daniel Kachhap static int exynos_tmu_suspend(struct device *dev) 54359dfa54cSAmit Daniel Kachhap { 54459dfa54cSAmit Daniel Kachhap exynos_tmu_control(to_platform_device(dev), false); 54559dfa54cSAmit Daniel Kachhap 54659dfa54cSAmit Daniel Kachhap return 0; 54759dfa54cSAmit Daniel Kachhap } 54859dfa54cSAmit Daniel Kachhap 54959dfa54cSAmit Daniel Kachhap static int exynos_tmu_resume(struct device *dev) 55059dfa54cSAmit Daniel Kachhap { 55159dfa54cSAmit Daniel Kachhap struct platform_device *pdev = to_platform_device(dev); 55259dfa54cSAmit Daniel Kachhap 55359dfa54cSAmit Daniel Kachhap exynos_tmu_initialize(pdev); 55459dfa54cSAmit Daniel Kachhap exynos_tmu_control(pdev, true); 55559dfa54cSAmit Daniel Kachhap 55659dfa54cSAmit Daniel Kachhap return 0; 55759dfa54cSAmit Daniel Kachhap } 55859dfa54cSAmit Daniel Kachhap 55959dfa54cSAmit Daniel Kachhap static SIMPLE_DEV_PM_OPS(exynos_tmu_pm, 56059dfa54cSAmit Daniel Kachhap exynos_tmu_suspend, exynos_tmu_resume); 56159dfa54cSAmit Daniel Kachhap #define EXYNOS_TMU_PM (&exynos_tmu_pm) 56259dfa54cSAmit Daniel Kachhap #else 56359dfa54cSAmit Daniel Kachhap #define EXYNOS_TMU_PM NULL 56459dfa54cSAmit Daniel Kachhap #endif 56559dfa54cSAmit Daniel Kachhap 56659dfa54cSAmit Daniel Kachhap static struct platform_driver exynos_tmu_driver = { 56759dfa54cSAmit Daniel Kachhap .driver = { 56859dfa54cSAmit Daniel Kachhap .name = "exynos-tmu", 56959dfa54cSAmit Daniel Kachhap .owner = THIS_MODULE, 57059dfa54cSAmit Daniel Kachhap .pm = EXYNOS_TMU_PM, 57159dfa54cSAmit Daniel Kachhap .of_match_table = of_match_ptr(exynos_tmu_match), 57259dfa54cSAmit Daniel Kachhap }, 57359dfa54cSAmit Daniel Kachhap .probe = exynos_tmu_probe, 57459dfa54cSAmit Daniel Kachhap .remove = exynos_tmu_remove, 57559dfa54cSAmit Daniel Kachhap }; 57659dfa54cSAmit Daniel Kachhap 57759dfa54cSAmit Daniel Kachhap module_platform_driver(exynos_tmu_driver); 57859dfa54cSAmit Daniel Kachhap 57959dfa54cSAmit Daniel Kachhap MODULE_DESCRIPTION("EXYNOS TMU Driver"); 58059dfa54cSAmit Daniel Kachhap MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); 58159dfa54cSAmit Daniel Kachhap MODULE_LICENSE("GPL"); 58259dfa54cSAmit Daniel Kachhap MODULE_ALIAS("platform:exynos-tmu"); 583