From d8ed3dad2b9beb6cab09f00ea1011a7d35e860de Mon Sep 17 00:00:00 2001 From: Jongpill Lee Date: Tue, 5 Jul 2011 13:36:16 +0530 Subject: EXYNOS4210: Remove regarding busfreq codes Since busfreq codes can be handlend on busfreq driver, this patch removes regarding busfreq codes on cpufreq. Signed-off-by: Jongpill Lee Signed-off-by: SangWook Ju Signed-off-by: Kukjin Kim --- drivers/cpufreq/exynos4210-cpufreq.c | 180 +---------------------------------- 1 file changed, 3 insertions(+), 177 deletions(-) diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c index b7c3a84c4cf..7a88ed9262a 100644 --- a/drivers/cpufreq/exynos4210-cpufreq.c +++ b/drivers/cpufreq/exynos4210-cpufreq.c @@ -31,16 +31,8 @@ static struct clk *mout_mpll; static struct clk *mout_apll; static struct regulator *arm_regulator; -static struct regulator *int_regulator; static struct cpufreq_freqs freqs; -static unsigned int memtype; - -enum exynos4_memory_type { - DDR2 = 4, - LPDDR2, - DDR3, -}; enum cpufreq_level_index { L0, L1, L2, L3, CPUFREQ_LEVEL_END, @@ -93,87 +85,24 @@ static unsigned int clkdiv_cpu1[CPUFREQ_LEVEL_END][2] = { { 3, 0 }, }; -static unsigned int clkdiv_dmc0[CPUFREQ_LEVEL_END][8] = { - /* - * Clock divider value for following - * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD - * DIVDMCP, DIVCOPY2, DIVCORE_TIMERS } - */ - - /* DMC L0: 400MHz */ - { 3, 1, 1, 1, 1, 1, 3, 1 }, - - /* DMC L1: 400MHz */ - { 3, 1, 1, 1, 1, 1, 3, 1 }, - - /* DMC L2: 266.7MHz */ - { 7, 1, 1, 2, 1, 1, 3, 1 }, - - /* DMC L3: 200MHz */ - { 7, 1, 1, 3, 1, 1, 3, 1 }, -}; - -static unsigned int clkdiv_top[CPUFREQ_LEVEL_END][5] = { - /* - * Clock divider value for following - * { DIVACLK200, DIVACLK100, DIVACLK160, DIVACLK133, DIVONENAND } - */ - - /* ACLK200 L0: 200MHz */ - { 3, 7, 4, 5, 1 }, - - /* ACLK200 L1: 200MHz */ - { 3, 7, 4, 5, 1 }, - - /* ACLK200 L2: 160MHz */ - { 4, 7, 5, 7, 1 }, - - /* ACLK200 L3: 133.3MHz */ - { 5, 7, 7, 7, 1 }, -}; - -static unsigned int clkdiv_lr_bus[CPUFREQ_LEVEL_END][2] = { - /* - * Clock divider value for following - * { DIVGDL/R, DIVGPL/R } - */ - - /* ACLK_GDL/R L0: 200MHz */ - { 3, 1 }, - - /* ACLK_GDL/R L1: 200MHz */ - { 3, 1 }, - - /* ACLK_GDL/R L2: 160MHz */ - { 4, 1 }, - - /* ACLK_GDL/R L3: 133.3MHz */ - { 5, 1 }, -}; - struct cpufreq_voltage_table { unsigned int index; /* any */ unsigned int arm_volt; /* uV */ - unsigned int int_volt; }; static struct cpufreq_voltage_table exynos4_volt_table[CPUFREQ_LEVEL_END] = { { .index = L0, .arm_volt = 1200000, - .int_volt = 1100000, }, { .index = L1, .arm_volt = 1100000, - .int_volt = 1100000, }, { .index = L2, .arm_volt = 1000000, - .int_volt = 1000000, }, { .index = L3, .arm_volt = 900000, - .int_volt = 1000000, }, }; @@ -242,80 +171,6 @@ static void exynos4_set_clkdiv(unsigned int div_index) do { tmp = __raw_readl(S5P_CLKDIV_STATCPU1); } while (tmp & 0x11); - - /* Change Divider - DMC0 */ - - tmp = __raw_readl(S5P_CLKDIV_DMC0); - - tmp &= ~(S5P_CLKDIV_DMC0_ACP_MASK | S5P_CLKDIV_DMC0_ACPPCLK_MASK | - S5P_CLKDIV_DMC0_DPHY_MASK | S5P_CLKDIV_DMC0_DMC_MASK | - S5P_CLKDIV_DMC0_DMCD_MASK | S5P_CLKDIV_DMC0_DMCP_MASK | - S5P_CLKDIV_DMC0_COPY2_MASK | S5P_CLKDIV_DMC0_CORETI_MASK); - - tmp |= ((clkdiv_dmc0[div_index][0] << S5P_CLKDIV_DMC0_ACP_SHIFT) | - (clkdiv_dmc0[div_index][1] << S5P_CLKDIV_DMC0_ACPPCLK_SHIFT) | - (clkdiv_dmc0[div_index][2] << S5P_CLKDIV_DMC0_DPHY_SHIFT) | - (clkdiv_dmc0[div_index][3] << S5P_CLKDIV_DMC0_DMC_SHIFT) | - (clkdiv_dmc0[div_index][4] << S5P_CLKDIV_DMC0_DMCD_SHIFT) | - (clkdiv_dmc0[div_index][5] << S5P_CLKDIV_DMC0_DMCP_SHIFT) | - (clkdiv_dmc0[div_index][6] << S5P_CLKDIV_DMC0_COPY2_SHIFT) | - (clkdiv_dmc0[div_index][7] << S5P_CLKDIV_DMC0_CORETI_SHIFT)); - - __raw_writel(tmp, S5P_CLKDIV_DMC0); - - do { - tmp = __raw_readl(S5P_CLKDIV_STAT_DMC0); - } while (tmp & 0x11111111); - - /* Change Divider - TOP */ - - tmp = __raw_readl(S5P_CLKDIV_TOP); - - tmp &= ~(S5P_CLKDIV_TOP_ACLK200_MASK | S5P_CLKDIV_TOP_ACLK100_MASK | - S5P_CLKDIV_TOP_ACLK160_MASK | S5P_CLKDIV_TOP_ACLK133_MASK | - S5P_CLKDIV_TOP_ONENAND_MASK); - - tmp |= ((clkdiv_top[div_index][0] << S5P_CLKDIV_TOP_ACLK200_SHIFT) | - (clkdiv_top[div_index][1] << S5P_CLKDIV_TOP_ACLK100_SHIFT) | - (clkdiv_top[div_index][2] << S5P_CLKDIV_TOP_ACLK160_SHIFT) | - (clkdiv_top[div_index][3] << S5P_CLKDIV_TOP_ACLK133_SHIFT) | - (clkdiv_top[div_index][4] << S5P_CLKDIV_TOP_ONENAND_SHIFT)); - - __raw_writel(tmp, S5P_CLKDIV_TOP); - - do { - tmp = __raw_readl(S5P_CLKDIV_STAT_TOP); - } while (tmp & 0x11111); - - /* Change Divider - LEFTBUS */ - - tmp = __raw_readl(S5P_CLKDIV_LEFTBUS); - - tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK); - - tmp |= ((clkdiv_lr_bus[div_index][0] << S5P_CLKDIV_BUS_GDLR_SHIFT) | - (clkdiv_lr_bus[div_index][1] << S5P_CLKDIV_BUS_GPLR_SHIFT)); - - __raw_writel(tmp, S5P_CLKDIV_LEFTBUS); - - do { - tmp = __raw_readl(S5P_CLKDIV_STAT_LEFTBUS); - } while (tmp & 0x11); - - /* Change Divider - RIGHTBUS */ - - tmp = __raw_readl(S5P_CLKDIV_RIGHTBUS); - - tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK); - - tmp |= ((clkdiv_lr_bus[div_index][0] << S5P_CLKDIV_BUS_GDLR_SHIFT) | - (clkdiv_lr_bus[div_index][1] << S5P_CLKDIV_BUS_GPLR_SHIFT)); - - __raw_writel(tmp, S5P_CLKDIV_RIGHTBUS); - - do { - tmp = __raw_readl(S5P_CLKDIV_STAT_RIGHTBUS); - } while (tmp & 0x11); } static void exynos4_set_apll(unsigned int index) @@ -404,7 +259,7 @@ static int exynos4_target(struct cpufreq_policy *policy, unsigned int relation) { unsigned int index, old_index; - unsigned int arm_volt, int_volt; + unsigned int arm_volt; freqs.old = exynos4_getspeed(policy->cpu); @@ -424,26 +279,21 @@ static int exynos4_target(struct cpufreq_policy *policy, /* get the voltage value */ arm_volt = exynos4_volt_table[index].arm_volt; - int_volt = exynos4_volt_table[index].int_volt; cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); /* control regulator */ - if (freqs.new > freqs.old) { + if (freqs.new > freqs.old) /* Voltage up */ regulator_set_voltage(arm_regulator, arm_volt, arm_volt); - regulator_set_voltage(int_regulator, int_volt, int_volt); - } /* Clock Configuration Procedure */ exynos4_set_frequency(old_index, index); /* control regulator */ - if (freqs.new < freqs.old) { + if (freqs.new < freqs.old) /* Voltage down */ regulator_set_voltage(arm_regulator, arm_volt, arm_volt); - regulator_set_voltage(int_regulator, int_volt, int_volt); - } cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); @@ -519,27 +369,6 @@ static int __init exynos4_cpufreq_init(void) goto out; } - int_regulator = regulator_get(NULL, "vdd_int"); - if (IS_ERR(int_regulator)) { - printk(KERN_ERR "failed to get resource %s\n", "vdd_int"); - goto out; - } - - /* - * Check DRAM type. - * Because DVFS level is different according to DRAM type. - */ - memtype = __raw_readl(S5P_VA_DMC0 + S5P_DMC0_MEMCON_OFFSET); - memtype = (memtype >> S5P_DMC0_MEMTYPE_SHIFT); - memtype &= S5P_DMC0_MEMTYPE_MASK; - - if ((memtype < DDR2) && (memtype > DDR3)) { - printk(KERN_ERR "%s: wrong memtype= 0x%x\n", __func__, memtype); - goto out; - } else { - printk(KERN_DEBUG "%s: memtype= 0x%x\n", __func__, memtype); - } - return cpufreq_register_driver(&exynos4_driver); out: @@ -558,9 +387,6 @@ out: if (!IS_ERR(arm_regulator)) regulator_put(arm_regulator); - if (!IS_ERR(int_regulator)) - regulator_put(int_regulator); - printk(KERN_ERR "%s: failed initialization\n", __func__); return -EINVAL; -- cgit v1.2.3 From 0a75037e551780bf2d224f7b3db691bb9aa7dd34 Mon Sep 17 00:00:00 2001 From: Jongpill Lee Date: Tue, 5 Jul 2011 13:36:17 +0530 Subject: EXYNOS4210: Change CPU table and divider This patch adds support 1.2GHz CPU frequency and changes CPU table and divider for stable working. Signed-off-by: Jongpill Lee Signed-off-by: SangWook Ju Signed-off-by: Kukjin Kim --- drivers/cpufreq/exynos4210-cpufreq.c | 69 +++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c index 7a88ed9262a..a1bdea48fbc 100644 --- a/drivers/cpufreq/exynos4210-cpufreq.c +++ b/drivers/cpufreq/exynos4210-cpufreq.c @@ -35,14 +35,15 @@ static struct regulator *arm_regulator; static struct cpufreq_freqs freqs; enum cpufreq_level_index { - L0, L1, L2, L3, CPUFREQ_LEVEL_END, + L0, L1, L2, L3, L4, CPUFREQ_LEVEL_END, }; static struct cpufreq_frequency_table exynos4_freq_table[] = { - {L0, 1000*1000}, - {L1, 800*1000}, - {L2, 400*1000}, - {L3, 100*1000}, + {L0, 1200*1000}, + {L1, 1000*1000}, + {L2, 800*1000}, + {L3, 500*1000}, + {L4, 200*1000}, {0, CPUFREQ_TABLE_END}, }; @@ -53,17 +54,20 @@ static unsigned int clkdiv_cpu0[CPUFREQ_LEVEL_END][7] = { * DIVATB, DIVPCLK_DBG, DIVAPLL } */ - /* ARM L0: 1000MHz */ - { 0, 3, 7, 3, 3, 0, 1 }, + /* ARM L0: 1200MHz */ + { 0, 3, 7, 3, 4, 1, 7 }, - /* ARM L1: 800MHz */ - { 0, 3, 7, 3, 3, 0, 1 }, + /* ARM L1: 1000MHz */ + { 0, 3, 7, 3, 4, 1, 7 }, - /* ARM L2: 400MHz */ - { 0, 1, 3, 1, 3, 0, 1 }, + /* ARM L2: 800MHz */ + { 0, 3, 7, 3, 3, 1, 7 }, - /* ARM L3: 100MHz */ - { 0, 0, 1, 0, 3, 1, 1 }, + /* ARM L3: 500MHz */ + { 0, 3, 7, 3, 3, 1, 7 }, + + /* ARM L4: 200MHz */ + { 0, 1, 3, 1, 3, 1, 0 }, }; static unsigned int clkdiv_cpu1[CPUFREQ_LEVEL_END][2] = { @@ -72,16 +76,19 @@ static unsigned int clkdiv_cpu1[CPUFREQ_LEVEL_END][2] = { * { DIVCOPY, DIVHPM } */ - /* ARM L0: 1000MHz */ - { 3, 0 }, + /* ARM L0: 1200MHz */ + { 5, 0 }, + + /* ARM L1: 1000MHz */ + { 4, 0 }, - /* ARM L1: 800MHz */ + /* ARM L2: 800MHz */ { 3, 0 }, - /* ARM L2: 400MHz */ + /* ARM L3: 500MHz */ { 3, 0 }, - /* ARM L3: 100MHz */ + /* ARM L4: 200MHz */ { 3, 0 }, }; @@ -93,31 +100,37 @@ struct cpufreq_voltage_table { static struct cpufreq_voltage_table exynos4_volt_table[CPUFREQ_LEVEL_END] = { { .index = L0, - .arm_volt = 1200000, + .arm_volt = 1350000, }, { .index = L1, - .arm_volt = 1100000, + .arm_volt = 1300000, }, { .index = L2, - .arm_volt = 1000000, + .arm_volt = 1200000, }, { .index = L3, - .arm_volt = 900000, + .arm_volt = 1100000, + }, { + .index = L4, + .arm_volt = 1050000, }, }; static unsigned int exynos4_apll_pms_table[CPUFREQ_LEVEL_END] = { - /* APLL FOUT L0: 1000MHz */ + /* APLL FOUT L0: 1200MHz */ + ((150 << 16) | (3 << 8) | 1), + + /* APLL FOUT L1: 1000MHz */ ((250 << 16) | (6 << 8) | 1), - /* APLL FOUT L1: 800MHz */ + /* APLL FOUT L2: 800MHz */ ((200 << 16) | (6 << 8) | 1), - /* APLL FOUT L2 : 400MHz */ - ((200 << 16) | (6 << 8) | 2), + /* APLL FOUT L3: 500MHz */ + ((250 << 16) | (6 << 8) | 2), - /* APLL FOUT L3: 100MHz */ - ((200 << 16) | (6 << 8) | 4), + /* APLL FOUT L4: 200MHz */ + ((200 << 16) | (6 << 8) | 3), }; static int exynos4_verify_speed(struct cpufreq_policy *policy) -- cgit v1.2.3 From fd899eba028e72659bf6572c974a92652e90dd23 Mon Sep 17 00:00:00 2001 From: Jongpill Lee Date: Tue, 5 Jul 2011 13:36:18 +0530 Subject: EXYNOS4210: Cleanup sequence and unused codes This patch modifies following. 1. Remove unused register access 2. Change sequence of changing frequency 3. Minor optimization Signed-off-by: Jongpill Lee Signed-off-by: SangWook Ju Signed-off-by: Jonghwan Choi Signed-off-by: Kukjin Kim --- drivers/cpufreq/exynos4210-cpufreq.c | 138 ++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 50 deletions(-) diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c index a1bdea48fbc..4dc2b81af39 100644 --- a/drivers/cpufreq/exynos4210-cpufreq.c +++ b/drivers/cpufreq/exynos4210-cpufreq.c @@ -34,10 +34,16 @@ static struct regulator *arm_regulator; static struct cpufreq_freqs freqs; +struct cpufreq_clkdiv { + unsigned int clkdiv; +}; + enum cpufreq_level_index { L0, L1, L2, L3, L4, CPUFREQ_LEVEL_END, }; +static struct cpufreq_clkdiv exynos4_clkdiv_table[CPUFREQ_LEVEL_END]; + static struct cpufreq_frequency_table exynos4_freq_table[] = { {L0, 1200*1000}, {L1, 1000*1000}, @@ -149,20 +155,7 @@ static void exynos4_set_clkdiv(unsigned int div_index) /* Change Divider - CPU0 */ - tmp = __raw_readl(S5P_CLKDIV_CPU); - - tmp &= ~(S5P_CLKDIV_CPU0_CORE_MASK | S5P_CLKDIV_CPU0_COREM0_MASK | - S5P_CLKDIV_CPU0_COREM1_MASK | S5P_CLKDIV_CPU0_PERIPH_MASK | - S5P_CLKDIV_CPU0_ATB_MASK | S5P_CLKDIV_CPU0_PCLKDBG_MASK | - S5P_CLKDIV_CPU0_APLL_MASK); - - tmp |= ((clkdiv_cpu0[div_index][0] << S5P_CLKDIV_CPU0_CORE_SHIFT) | - (clkdiv_cpu0[div_index][1] << S5P_CLKDIV_CPU0_COREM0_SHIFT) | - (clkdiv_cpu0[div_index][2] << S5P_CLKDIV_CPU0_COREM1_SHIFT) | - (clkdiv_cpu0[div_index][3] << S5P_CLKDIV_CPU0_PERIPH_SHIFT) | - (clkdiv_cpu0[div_index][4] << S5P_CLKDIV_CPU0_ATB_SHIFT) | - (clkdiv_cpu0[div_index][5] << S5P_CLKDIV_CPU0_PCLKDBG_SHIFT) | - (clkdiv_cpu0[div_index][6] << S5P_CLKDIV_CPU0_APLL_SHIFT)); + tmp = exynos4_clkdiv_table[div_index].clkdiv; __raw_writel(tmp, S5P_CLKDIV_CPU); @@ -227,14 +220,12 @@ static void exynos4_set_frequency(unsigned int old_index, unsigned int new_index unsigned int tmp; if (old_index > new_index) { - /* The frequency changing to L0 needs to change apll */ - if (freqs.new == exynos4_freq_table[L0].frequency) { - /* 1. Change the system clock divider values */ - exynos4_set_clkdiv(new_index); - - /* 2. Change the apll m,p,s value */ - exynos4_set_apll(new_index); - } else { + /* + * L1/L3, L2/L4 Level change require + * to only change s divider value + */ + if (((old_index == L3) && (new_index == L1)) || + ((old_index == L4) && (new_index == L2))) { /* 1. Change the system clock divider values */ exynos4_set_clkdiv(new_index); @@ -243,24 +234,32 @@ static void exynos4_set_frequency(unsigned int old_index, unsigned int new_index tmp &= ~(0x7 << 0); tmp |= (exynos4_apll_pms_table[new_index] & 0x7); __raw_writel(tmp, S5P_APLL_CON0); - } - } - - else if (old_index < new_index) { - /* The frequency changing from L0 needs to change apll */ - if (freqs.old == exynos4_freq_table[L0].frequency) { - /* 1. Change the apll m,p,s value */ - exynos4_set_apll(new_index); - - /* 2. Change the system clock divider values */ - exynos4_set_clkdiv(new_index); } else { + /* Clock Configuration Procedure */ + /* 1. Change the system clock divider values */ + exynos4_set_clkdiv(new_index); + /* 2. Change the apll m,p,s value */ + exynos4_set_apll(new_index); + } + } else if (old_index < new_index) { + /* + * L1/L3, L2/L4 Level change require + * to only change s divider value + */ + if (((old_index == L1) && (new_index == L3)) || + ((old_index == L2) && (new_index == L4))) { /* 1. Change just s value in apll m,p,s value */ tmp = __raw_readl(S5P_APLL_CON0); tmp &= ~(0x7 << 0); tmp |= (exynos4_apll_pms_table[new_index] & 0x7); __raw_writel(tmp, S5P_APLL_CON0); + /* 2. Change the system clock divider values */ + exynos4_set_clkdiv(new_index); + } else { + /* Clock Configuration Procedure */ + /* 1. Change the apll m,p,s value */ + exynos4_set_apll(new_index); /* 2. Change the system clock divider values */ exynos4_set_clkdiv(new_index); } @@ -303,12 +302,13 @@ static int exynos4_target(struct cpufreq_policy *policy, /* Clock Configuration Procedure */ exynos4_set_frequency(old_index, index); + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + /* control regulator */ if (freqs.new < freqs.old) /* Voltage down */ regulator_set_voltage(arm_regulator, arm_volt, arm_volt); - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); return 0; } @@ -340,7 +340,12 @@ static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy) * Each cpu is bound to the same speed. * So the affected cpu is all of the cpus. */ - cpumask_setall(policy->cpus); + if (!cpu_online(1)) { + cpumask_copy(policy->related_cpus, cpu_possible_mask); + cpumask_copy(policy->cpus, cpu_online_mask); + } else { + cpumask_setall(policy->cpus); + } return cpufreq_frequency_table_cpuinfo(policy, exynos4_freq_table); } @@ -360,47 +365,80 @@ static struct cpufreq_driver exynos4_driver = { static int __init exynos4_cpufreq_init(void) { + int i; + unsigned int tmp; + cpu_clk = clk_get(NULL, "armclk"); if (IS_ERR(cpu_clk)) return PTR_ERR(cpu_clk); moutcore = clk_get(NULL, "moutcore"); if (IS_ERR(moutcore)) - goto out; + goto err_moutcore; mout_mpll = clk_get(NULL, "mout_mpll"); if (IS_ERR(mout_mpll)) - goto out; + goto err_mout_mpll; mout_apll = clk_get(NULL, "mout_apll"); if (IS_ERR(mout_apll)) - goto out; + goto err_mout_apll; arm_regulator = regulator_get(NULL, "vdd_arm"); if (IS_ERR(arm_regulator)) { printk(KERN_ERR "failed to get resource %s\n", "vdd_arm"); - goto out; + goto err_vdd_arm; } - return cpufreq_register_driver(&exynos4_driver); + tmp = __raw_readl(S5P_CLKDIV_CPU); -out: - if (!IS_ERR(cpu_clk)) - clk_put(cpu_clk); + for (i = L0; i < CPUFREQ_LEVEL_END; i++) { + tmp &= ~(S5P_CLKDIV_CPU0_CORE_MASK | + S5P_CLKDIV_CPU0_COREM0_MASK | + S5P_CLKDIV_CPU0_COREM1_MASK | + S5P_CLKDIV_CPU0_PERIPH_MASK | + S5P_CLKDIV_CPU0_ATB_MASK | + S5P_CLKDIV_CPU0_PCLKDBG_MASK | + S5P_CLKDIV_CPU0_APLL_MASK); + + tmp |= ((clkdiv_cpu0[i][0] << S5P_CLKDIV_CPU0_CORE_SHIFT) | + (clkdiv_cpu0[i][1] << S5P_CLKDIV_CPU0_COREM0_SHIFT) | + (clkdiv_cpu0[i][2] << S5P_CLKDIV_CPU0_COREM1_SHIFT) | + (clkdiv_cpu0[i][3] << S5P_CLKDIV_CPU0_PERIPH_SHIFT) | + (clkdiv_cpu0[i][4] << S5P_CLKDIV_CPU0_ATB_SHIFT) | + (clkdiv_cpu0[i][5] << S5P_CLKDIV_CPU0_PCLKDBG_SHIFT) | + (clkdiv_cpu0[i][6] << S5P_CLKDIV_CPU0_APLL_SHIFT)); + + exynos4_clkdiv_table[i].clkdiv = tmp; + } - if (!IS_ERR(moutcore)) - clk_put(moutcore); + if (cpufreq_register_driver(&exynos4_driver)) { + pr_err("failed to register cpufreq driver\n"); + goto err_cpufreq; + } - if (!IS_ERR(mout_mpll)) - clk_put(mout_mpll); + return 0; +err_cpufreq: + if (!IS_ERR(arm_regulator)) + regulator_put(arm_regulator); +err_vdd_arm: if (!IS_ERR(mout_apll)) clk_put(mout_apll); - if (!IS_ERR(arm_regulator)) - regulator_put(arm_regulator); +err_mout_apll: + if (!IS_ERR(mout_mpll)) + clk_put(mout_mpll); + +err_mout_mpll: + if (!IS_ERR(moutcore)) + clk_put(moutcore); + +err_moutcore: + if (!IS_ERR(cpu_clk)) + clk_put(cpu_clk); - printk(KERN_ERR "%s: failed initialization\n", __func__); + pr_debug("%s: failed initialization\n", __func__); return -EINVAL; } -- cgit v1.2.3 From ceb077bd2729f64b4680f81dc8dee308890ac52f Mon Sep 17 00:00:00 2001 From: Jongpill Lee Date: Tue, 5 Jul 2011 13:36:19 +0530 Subject: EXYNOS4210: Add Support for DVS Lock Signed-off-by: Jongpill Lee Signed-off-by: SangWook Ju Signed-off-by: Jonghwan Choi Signed-off-by: Kukjin Kim --- arch/arm/mach-exynos4/include/mach/cpufreq.h | 39 +++++++ drivers/cpufreq/exynos4210-cpufreq.c | 167 ++++++++++++++++++++++++++- 2 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 arch/arm/mach-exynos4/include/mach/cpufreq.h diff --git a/arch/arm/mach-exynos4/include/mach/cpufreq.h b/arch/arm/mach-exynos4/include/mach/cpufreq.h new file mode 100644 index 00000000000..7e0093111da --- /dev/null +++ b/arch/arm/mach-exynos4/include/mach/cpufreq.h @@ -0,0 +1,39 @@ +/* linux/arch/arm/mach-exynos4/include/mach/cpufreq.h + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * EXYNOS4 - CPUFreq support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * CPU frequency level index for using cpufreq lock API + * This should be same with cpufreq_frequency_table + */ +enum cpufreq_level_request { + CPU_L0, /* 1200MHz */ + CPU_L1, /* 1000MHz */ + CPU_L2, /* 800MHz */ + CPU_L3, /* 500MHz */ + CPU_L4, /* 200MHz */ + CPU_LEVEL_END, +}; + +enum cpufreq_lock_ID { + DVFS_LOCK_ID_G2D, /* G2D */ + DVFS_LOCK_ID_TV, /* TV */ + DVFS_LOCK_ID_MFC, /* MFC */ + DVFS_LOCK_ID_USB, /* USB */ + DVFS_LOCK_ID_CAM, /* CAM */ + DVFS_LOCK_ID_PM, /* PM */ + DVFS_LOCK_ID_USER, /* USER */ + DVFS_LOCK_ID_END, +}; + +int exynos4_cpufreq_lock(unsigned int nId, + enum cpufreq_level_request cpufreq_level); +void exynos4_cpufreq_lock_free(unsigned int nId); diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c index 4dc2b81af39..d263241b2d0 100644 --- a/drivers/cpufreq/exynos4210-cpufreq.c +++ b/drivers/cpufreq/exynos4210-cpufreq.c @@ -17,14 +17,21 @@ #include #include #include +#include +#include #include #include #include +#include #include #include +static bool exynos4_cpufreq_init_done; +static DEFINE_MUTEX(set_freq_lock); +static DEFINE_MUTEX(set_cpu_freq_lock); + static struct clk *cpu_clk; static struct clk *moutcore; static struct clk *mout_mpll; @@ -53,6 +60,12 @@ static struct cpufreq_frequency_table exynos4_freq_table[] = { {0, CPUFREQ_TABLE_END}, }; +/* This defines are for cpufreq lock */ +#define CPUFREQ_MIN_LEVEL (CPUFREQ_LEVEL_END - 1) +unsigned int cpufreq_lock_id; +unsigned int cpufreq_lock_val[DVFS_LOCK_ID_END]; +unsigned int cpufreq_lock_level = CPUFREQ_MIN_LEVEL; + static unsigned int clkdiv_cpu0[CPUFREQ_LEVEL_END][7] = { /* * Clock divider value for following @@ -272,22 +285,31 @@ static int exynos4_target(struct cpufreq_policy *policy, { unsigned int index, old_index; unsigned int arm_volt; + int ret = 0; + + mutex_lock(&set_freq_lock); freqs.old = exynos4_getspeed(policy->cpu); if (cpufreq_frequency_table_target(policy, exynos4_freq_table, - freqs.old, relation, &old_index)) - return -EINVAL; + freqs.old, relation, &old_index)) { + ret = -EINVAL; + goto out; + } if (cpufreq_frequency_table_target(policy, exynos4_freq_table, - target_freq, relation, &index)) - return -EINVAL; + target_freq, relation, &index)) { + ret = -EINVAL; + goto out; + } freqs.new = exynos4_freq_table[index].frequency; freqs.cpu = policy->cpu; - if (freqs.new == freqs.old) - return 0; + if (freqs.new == freqs.old) { + ret = -EINVAL; + goto out; + } /* get the voltage value */ arm_volt = exynos4_volt_table[index].arm_volt; @@ -309,9 +331,98 @@ static int exynos4_target(struct cpufreq_policy *policy, /* Voltage down */ regulator_set_voltage(arm_regulator, arm_volt, arm_volt); +out: + mutex_unlock(&set_freq_lock); + + return ret; +} + +atomic_t exynos4_cpufreq_lock_count; + +int exynos4_cpufreq_lock(unsigned int id, + enum cpufreq_level_request cpufreq_level) +{ + int i, old_idx = 0; + unsigned int freq_old, freq_new, arm_volt; + + if (!exynos4_cpufreq_init_done) + return 0; + + if (cpufreq_lock_id & (1 << id)) { + printk(KERN_ERR "%s:Device [%d] already locked cpufreq\n", + __func__, id); + return 0; + } + mutex_lock(&set_cpu_freq_lock); + cpufreq_lock_id |= (1 << id); + cpufreq_lock_val[id] = cpufreq_level; + + /* If the requested cpufreq is higher than current min frequency */ + if (cpufreq_level < cpufreq_lock_level) + cpufreq_lock_level = cpufreq_level; + + mutex_unlock(&set_cpu_freq_lock); + + /* + * If current frequency is lower than requested freq, + * it needs to update + */ + mutex_lock(&set_freq_lock); + freq_old = exynos4_getspeed(0); + freq_new = exynos4_freq_table[cpufreq_level].frequency; + if (freq_old < freq_new) { + /* Find out current level index */ + for (i = 0 ; i < CPUFREQ_LEVEL_END ; i++) { + if (freq_old == exynos4_freq_table[i].frequency) { + old_idx = exynos4_freq_table[i].index; + break; + } else if (i == (CPUFREQ_LEVEL_END - 1)) { + printk(KERN_ERR "%s: Level not found\n", + __func__); + mutex_unlock(&set_freq_lock); + return -EINVAL; + } else { + continue; + } + } + freqs.old = freq_old; + freqs.new = freq_new; + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + /* get the voltage value */ + arm_volt = exynos4_volt_table[cpufreq_level].arm_volt; + regulator_set_voltage(arm_regulator, arm_volt, + arm_volt); + + exynos4_set_frequency(old_idx, cpufreq_level); + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + } + mutex_unlock(&set_freq_lock); return 0; } +EXPORT_SYMBOL_GPL(exynos4_cpufreq_lock); + +void exynos4_cpufreq_lock_free(unsigned int id) +{ + int i; + + if (!exynos4_cpufreq_init_done) + return; + + mutex_lock(&set_cpu_freq_lock); + cpufreq_lock_id &= ~(1 << id); + cpufreq_lock_val[id] = CPUFREQ_MIN_LEVEL; + cpufreq_lock_level = CPUFREQ_MIN_LEVEL; + if (cpufreq_lock_id) { + for (i = 0; i < DVFS_LOCK_ID_END; i++) { + if (cpufreq_lock_val[i] < cpufreq_lock_level) + cpufreq_lock_level = cpufreq_lock_val[i]; + } + } + mutex_unlock(&set_cpu_freq_lock); +} +EXPORT_SYMBOL_GPL(exynos4_cpufreq_lock_free); #ifdef CONFIG_PM static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy) @@ -325,6 +436,28 @@ static int exynos4_cpufreq_resume(struct cpufreq_policy *policy) } #endif +static int exynos4_cpufreq_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + if (exynos4_cpufreq_lock(DVFS_LOCK_ID_PM, CPU_L0)) + return NOTIFY_BAD; + pr_debug("PM_SUSPEND_PREPARE for CPUFREQ\n"); + return NOTIFY_OK; + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + pr_debug("PM_POST_SUSPEND for CPUFREQ\n"); + exynos4_cpufreq_lock_free(DVFS_LOCK_ID_PM); + return NOTIFY_OK; + } + return NOTIFY_DONE; +} + +static struct notifier_block exynos4_cpufreq_notifier = { + .notifier_call = exynos4_cpufreq_notifier_event, +}; + static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy) { policy->cur = policy->min = policy->max = exynos4_getspeed(policy->cpu); @@ -350,6 +483,20 @@ static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy) return cpufreq_frequency_table_cpuinfo(policy, exynos4_freq_table); } +static int exynos4_cpufreq_reboot_notifier_call(struct notifier_block *this, + unsigned long code, void *_cmd) +{ + if (exynos4_cpufreq_lock(DVFS_LOCK_ID_PM, CPU_L0)) + return NOTIFY_BAD; + + printk(KERN_INFO "REBOOT Notifier for CPUFREQ\n"); + return NOTIFY_DONE; +} + +static struct notifier_block exynos4_cpufreq_reboot_notifier = { + .notifier_call = exynos4_cpufreq_reboot_notifier_call, +}; + static struct cpufreq_driver exynos4_driver = { .flags = CPUFREQ_STICKY, .verify = exynos4_verify_speed, @@ -390,6 +537,11 @@ static int __init exynos4_cpufreq_init(void) goto err_vdd_arm; } + register_pm_notifier(&exynos4_cpufreq_notifier); + register_reboot_notifier(&exynos4_cpufreq_reboot_notifier); + + exynos4_cpufreq_init_done = true; + tmp = __raw_readl(S5P_CLKDIV_CPU); for (i = L0; i < CPUFREQ_LEVEL_END; i++) { @@ -419,6 +571,9 @@ static int __init exynos4_cpufreq_init(void) return 0; err_cpufreq: + unregister_reboot_notifier(&exynos4_cpufreq_reboot_notifier); + unregister_pm_notifier(&exynos4_cpufreq_notifier); + if (!IS_ERR(arm_regulator)) regulator_put(arm_regulator); -- cgit v1.2.3 From 3918a3c61ea129534f6c9343df59a73f23a3a331 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Thu, 1 Dec 2011 18:51:39 +0530 Subject: thermal: Add a new trip type to use cooling device instance number This patch adds a new trip type THERMAL_TRIP_STATE_ACTIVE. This trip behaves same as THERMAL_TRIP_ACTIVE but also passes the cooling device instance number. This helps the cooling device registered as different instances to perform appropriate cooling action decision in the set_cur_state call back function. Also since the trip temperature's are in ascending order so some logic is put in place to skip the un-necessary checks. Signed-off-by: Amit Daniel Kachhap --- Documentation/thermal/sysfs-api.txt | 4 ++-- drivers/thermal/thermal_sys.c | 27 ++++++++++++++++++++++++++- include/linux/thermal.h | 1 + 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Documentation/thermal/sysfs-api.txt b/Documentation/thermal/sysfs-api.txt index b61e46f449a..5c1d44e0df5 100644 --- a/Documentation/thermal/sysfs-api.txt +++ b/Documentation/thermal/sysfs-api.txt @@ -184,8 +184,8 @@ trip_point_[0-*]_temp trip_point_[0-*]_type Strings which indicate the type of the trip point. - E.g. it can be one of critical, hot, passive, active[0-*] for ACPI - thermal zone. + E.g. it can be one of critical, hot, passive, active[0-1], + state-active[0-*] for ACPI thermal zone. RO, Optional cdev[0-*] diff --git a/drivers/thermal/thermal_sys.c b/drivers/thermal/thermal_sys.c index 708f8e92771..2a42296a1f6 100644 --- a/drivers/thermal/thermal_sys.c +++ b/drivers/thermal/thermal_sys.c @@ -192,6 +192,8 @@ trip_point_type_show(struct device *dev, struct device_attribute *attr, return sprintf(buf, "passive\n"); case THERMAL_TRIP_ACTIVE: return sprintf(buf, "active\n"); + case THERMAL_TRIP_STATE_ACTIVE: + return sprintf(buf, "state-active\n"); default: return sprintf(buf, "unknown\n"); } @@ -1035,7 +1037,7 @@ EXPORT_SYMBOL(thermal_cooling_device_unregister); void thermal_zone_device_update(struct thermal_zone_device *tz) { int count, ret = 0; - long temp, trip_temp; + long temp, trip_temp, max_state, last_trip_change = 0; enum thermal_trip_type trip_type; struct thermal_cooling_device_instance *instance; struct thermal_cooling_device *cdev; @@ -1086,6 +1088,29 @@ void thermal_zone_device_update(struct thermal_zone_device *tz) cdev->ops->set_cur_state(cdev, 0); } break; + case THERMAL_TRIP_STATE_ACTIVE: + list_for_each_entry(instance, &tz->cooling_devices, + node) { + if (instance->trip != count) + continue; + + if (temp <= last_trip_change) + continue; + + cdev = instance->cdev; + cdev->ops->get_max_state(cdev, &max_state); + + if ((temp >= trip_temp) && + ((count + 1) <= max_state)) + cdev->ops->set_cur_state(cdev, + count + 1); + else if ((temp < trip_temp) && + (count <= max_state)) + cdev->ops->set_cur_state(cdev, count); + + last_trip_change = trip_temp; + } + break; case THERMAL_TRIP_PASSIVE: if (temp >= trip_temp || tz->passive) thermal_zone_device_passive(tz, temp, diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 47b4a27e6e9..d7d0a27d1a0 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -42,6 +42,7 @@ enum thermal_trip_type { THERMAL_TRIP_PASSIVE, THERMAL_TRIP_HOT, THERMAL_TRIP_CRITICAL, + THERMAL_TRIP_STATE_ACTIVE, }; struct thermal_zone_device_ops { -- cgit v1.2.3 From 0e939cfbb42214337ecd1782c4e233972ed90630 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Tue, 13 Dec 2011 20:40:01 +0530 Subject: thermal: Add generic cpu cooling implementation This patch adds support for generic cpu thermal cooling low level implementations using frequency scaling and cpuhotplugg currently. Different cpu related cooling devices can be registered by the user and the binding of these cooling devices to the corresponding trip points can be easily done as the registration API's return the cooling device pointer. Signed-off-by: Amit Daniel Kachhap --- Documentation/thermal/cpu-cooling-api.txt | 52 +++++ drivers/thermal/Kconfig | 11 ++ drivers/thermal/Makefile | 1 + drivers/thermal/cpu_cooling.c | 302 ++++++++++++++++++++++++++++++ include/linux/cpu_cooling.h | 45 +++++ 5 files changed, 411 insertions(+) create mode 100644 Documentation/thermal/cpu-cooling-api.txt create mode 100644 drivers/thermal/cpu_cooling.c create mode 100644 include/linux/cpu_cooling.h diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt new file mode 100644 index 00000000000..d30b4f276a9 --- /dev/null +++ b/Documentation/thermal/cpu-cooling-api.txt @@ -0,0 +1,52 @@ +CPU cooling api's How To +=================================== + +Written by Amit Daniel Kachhap + +Updated: 13 Dec 2011 + +Copyright (c) 2011 Samsung Electronics Co., Ltd(http://www.samsung.com) + +0. Introduction + +The generic cpu cooling(freq clipping, cpuhotplug) provides +registration/unregistration api's to the user. The binding of the cooling +devices to the trip types is left for the user. The registration api's returns +the cooling device pointer. + +1. cpufreq cooling api's + +1.1 cpufreq registration api's +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register( + struct freq_pctg_table *tab_ptr, unsigned int tab_size, + const struct cpumask *mask_val) + + This interface function registers the cpufreq cooling device with the name + "thermal-cpufreq". + + tab_ptr: The table containing the percentage of frequency to be clipped for + each cooling state. + .freq_clip_pctg[NR_CPUS]:Percentage of frequency to be clipped for each + cpu. + .polling_interval: polling interval for this cooling state. + tab_size: the total number of cooling state. + mask_val: all the allowed cpu's where frequency clipping can happen. + +1.1.2 void cpufreq_cooling_unregister(void) + + This interface function unregisters the "thermal-cpufreq" cooling device. + + +1.2 cpuhotplug registration api's + +1.2.1 struct thermal_cooling_device *cpuhotplug_cooling_register( + const struct cpumask *mask_val) + + This interface function registers the cpuhotplug cooling device with the name + "thermal-cpuhotplug". + + mask_val: all the allowed cpu's which can be hotplugged out. + +1.1.2 void cpuhotplug_cooling_unregister(void) + + This interface function unregisters the "thermal-cpuhotplug" cooling device. diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index f7f71b2d310..298c1cdcd38 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -18,3 +18,14 @@ config THERMAL_HWMON depends on THERMAL depends on HWMON=y || HWMON=THERMAL default y + +config CPU_THERMAL + bool "generic cpu cooling support" + depends on THERMAL + help + This implements the generic cpu cooling mechanism through frequency + reduction, cpu hotplug and any other ways of reducing temperature. An + ACPI version of this already exists(drivers/acpi/processor_thermal.c). + This will be useful for platforms using the generic thermal interface + and not the ACPI interface. + If you want this support, you should say Y or M here. diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 31108a01c22..655cbc42529 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_THERMAL) += thermal_sys.o +obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c new file mode 100644 index 00000000000..cdd148c0036 --- /dev/null +++ b/drivers/thermal/cpu_cooling.c @@ -0,0 +1,302 @@ +/* + * linux/drivers/thermal/cpu_cooling.c + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd(http://www.samsung.com) + * Copyright (C) 2011 Amit Daniel + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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; version 2 of the License. + * + * 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 + +#ifdef CONFIG_CPU_FREQ +struct cpufreq_cooling_device { + struct thermal_cooling_device *cool_dev; + struct freq_pctg_table *tab_ptr; + unsigned int tab_size; + unsigned int cpufreq_state; + const struct cpumask *allowed_cpus; +}; + +static struct cpufreq_cooling_device *cpufreq_device; + +/*Below codes defines functions to be used for cpufreq as cooling device*/ +static bool is_cpufreq_valid(int cpu) +{ + struct cpufreq_policy policy; + if (!cpufreq_get_policy(&policy, cpu)) + return true; + return false; +} + +static int cpufreq_apply_cooling(int cooling_state) +{ + int cpuid, this_cpu = smp_processor_id(); + + if (!is_cpufreq_valid(this_cpu)) + return 0; + + if (cooling_state > cpufreq_device->tab_size) + return -EINVAL; + + /*Check if last cooling level is same as current cooling level*/ + if (cpufreq_device->cpufreq_state == cooling_state) + return 0; + + cpufreq_device->cpufreq_state = cooling_state; + + for_each_cpu(cpuid, cpufreq_device->allowed_cpus) { + if (is_cpufreq_valid(cpuid)) + cpufreq_update_policy(cpuid); + } + + return 0; +} + +static int thermal_cpufreq_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cpufreq_policy *policy = data; + struct freq_pctg_table *th_table; + unsigned long max_freq = 0; + unsigned int cpu = policy->cpu, th_pctg = 0, level; + + if (event != CPUFREQ_ADJUST) + return 0; + + level = cpufreq_device->cpufreq_state; + + if (level > 0) { + th_table = + &(cpufreq_device->tab_ptr[level - 1]); + th_pctg = th_table->freq_clip_pctg[cpu]; + } + + max_freq = + (policy->cpuinfo.max_freq * (100 - th_pctg)) / 100; + + cpufreq_verify_within_limits(policy, 0, max_freq); + + return 0; +} + +/* + * cpufreq cooling device callback functions + */ +static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = cpufreq_device->tab_size; + return 0; +} + +static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = cpufreq_device->cpufreq_state; + return 0; +} + +/*This cooling may be as PASSIVE/STATE_ACTIVE type*/ +static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + cpufreq_apply_cooling(state); + return 0; +} + +/* bind cpufreq callbacks to cpufreq cooling device */ +static struct thermal_cooling_device_ops cpufreq_cooling_ops = { + .get_max_state = cpufreq_get_max_state, + .get_cur_state = cpufreq_get_cur_state, + .set_cur_state = cpufreq_set_cur_state, +}; + +static struct notifier_block thermal_cpufreq_notifier_block = { + .notifier_call = thermal_cpufreq_notifier, +}; + +struct thermal_cooling_device *cpufreq_cooling_register( + struct freq_pctg_table *tab_ptr, unsigned int tab_size, + const struct cpumask *mask_val) +{ + struct thermal_cooling_device *cool_dev; + + if (tab_ptr == NULL || tab_size == 0) + return ERR_PTR(-EINVAL); + + cpufreq_device = + kzalloc(sizeof(struct cpufreq_cooling_device), GFP_KERNEL); + + if (!cpufreq_device) + return ERR_PTR(-ENOMEM); + + cool_dev = thermal_cooling_device_register("thermal-cpufreq", NULL, + &cpufreq_cooling_ops); + if (!cool_dev) { + kfree(cpufreq_device); + return ERR_PTR(-EINVAL); + } + + cpufreq_device->tab_ptr = tab_ptr; + cpufreq_device->tab_size = tab_size; + cpufreq_device->cool_dev = cool_dev; + cpufreq_device->allowed_cpus = mask_val; + + cpufreq_register_notifier(&thermal_cpufreq_notifier_block, + CPUFREQ_POLICY_NOTIFIER); + return cool_dev; +} +EXPORT_SYMBOL(cpufreq_cooling_register); + +void cpufreq_cooling_unregister(void) +{ + cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, + CPUFREQ_POLICY_NOTIFIER); + thermal_cooling_device_unregister(cpufreq_device->cool_dev); + kfree(cpufreq_device); +} +EXPORT_SYMBOL(cpufreq_cooling_unregister); +#else /*!CONFIG_CPU_FREQ*/ +struct thermal_cooling_device *cpufreq_cooling_register( + struct freq_pctg_table *tab_ptr, unsigned int tab_size) +{ + return NULL; +} +EXPORT_SYMBOL(cpufreq_cooling_register); +void cpufreq_cooling_unregister(void) +{ + return; +} +EXPORT_SYMBOL(cpufreq_cooling_unregister); +#endif /*CONFIG_CPU_FREQ*/ + +#ifdef CONFIG_HOTPLUG_CPU + +struct hotplug_cooling_device { + struct thermal_cooling_device *cool_dev; + unsigned int hotplug_state; + const struct cpumask *allowed_cpus; +}; +static struct hotplug_cooling_device *hotplug_device; + +/* + * cpu hotplug cooling device callback functions + */ +static int cpuhotplug_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = 1; + return 0; +} + +static int cpuhotplug_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + /*This cooling device may be of type ACTIVE, so state field + can be 0 or 1*/ + *state = hotplug_device->hotplug_state; + return 0; +} + +/*This cooling may be as PASSIVE/STATE_ACTIVE type*/ +static int cpuhotplug_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + int cpuid, this_cpu = smp_processor_id(); + + if (hotplug_device->hotplug_state == state) + return 0; + + /*This cooling device may be of type ACTIVE, so state field + can be 0 or 1*/ + if (state == 1) { + for_each_cpu(cpuid, hotplug_device->allowed_cpus) { + if (cpu_online(cpuid) && (cpuid != this_cpu)) + cpu_down(cpuid); + } + } else if (state == 0) { + for_each_cpu(cpuid, hotplug_device->allowed_cpus) { + if (!cpu_online(cpuid) && (cpuid != this_cpu)) + cpu_up(cpuid); + } + } else + return -EINVAL; + + hotplug_device->hotplug_state = state; + + return 0; +} +/* bind hotplug callbacks to cpu hotplug cooling device */ +static struct thermal_cooling_device_ops cpuhotplug_cooling_ops = { + .get_max_state = cpuhotplug_get_max_state, + .get_cur_state = cpuhotplug_get_cur_state, + .set_cur_state = cpuhotplug_set_cur_state, +}; + +struct thermal_cooling_device *cpuhotplug_cooling_register( + const struct cpumask *mask_val) +{ + struct thermal_cooling_device *cool_dev; + + hotplug_device = + kzalloc(sizeof(struct hotplug_cooling_device), GFP_KERNEL); + + if (!hotplug_device) + return ERR_PTR(-ENOMEM); + + cool_dev = thermal_cooling_device_register("thermal-cpuhotplug", NULL, + &cpuhotplug_cooling_ops); + if (!cool_dev) { + kfree(hotplug_device); + return ERR_PTR(-EINVAL); + } + + hotplug_device->cool_dev = cool_dev; + hotplug_device->hotplug_state = 0; + hotplug_device->allowed_cpus = mask_val; + + return cool_dev; +} +EXPORT_SYMBOL(cpuhotplug_cooling_register); + +void cpuhotplug_cooling_unregister(void) +{ + thermal_cooling_device_unregister(hotplug_device->cool_dev); + kfree(hotplug_device); +} +EXPORT_SYMBOL(cpuhotplug_cooling_unregister); +#else /*!CONFIG_HOTPLUG_CPU*/ +struct thermal_cooling_device *cpuhotplug_cooling_register( + const struct cpumask *mask_val) +{ + return NULL; +} +EXPORT_SYMBOL(cpuhotplug_cooling_register); +void cpuhotplug_cooling_unregister(void) +{ + return; +} +EXPORT_SYMBOL(cpuhotplug_cooling_unregister); +#endif /*CONFIG_HOTPLUG_CPU*/ diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h new file mode 100644 index 00000000000..0c573751f57 --- /dev/null +++ b/include/linux/cpu_cooling.h @@ -0,0 +1,45 @@ +/* + * linux/include/linux/cpu_cooling.h + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd(http://www.samsung.com) + * Copyright (C) 2011 Amit Daniel + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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; version 2 of the License. + * + * 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 __CPU_COOLING_H__ +#define __CPU_COOLING_H__ + +#include + +struct freq_pctg_table { + unsigned int freq_clip_pctg[NR_CPUS]; + unsigned int polling_interval; +}; + +extern struct thermal_cooling_device *cpufreq_cooling_register( + struct freq_pctg_table *tab_ptr, unsigned int tab_size, + const struct cpumask *mask_val); + +extern void cpufreq_cooling_unregister(void); + +extern struct thermal_cooling_device *cpuhotplug_cooling_register( + const struct cpumask *mask_val); + +extern void cpuhotplug_cooling_unregister(void); + +#endif /* __CPU_COOLING_H__ */ -- cgit v1.2.3 From a3cb3d54f43aa6fdae32be9b0e12b66a0ccedfc3 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Fri, 25 Nov 2011 15:15:18 +0530 Subject: ARM: EXYNOS4: Add tmu hwmon driver Signed-off-by: Amit Daniel Kachhap --- Documentation/hwmon/exynos4_tmu | 81 +++++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/exynos4_tmu.c | 524 ++++++++++++++++++++++++++++++ include/linux/platform_data/exynos4_tmu.h | 83 +++++ 5 files changed, 699 insertions(+) create mode 100644 Documentation/hwmon/exynos4_tmu create mode 100644 drivers/hwmon/exynos4_tmu.c create mode 100644 include/linux/platform_data/exynos4_tmu.h diff --git a/Documentation/hwmon/exynos4_tmu b/Documentation/hwmon/exynos4_tmu new file mode 100644 index 00000000000..c3c6b41db60 --- /dev/null +++ b/Documentation/hwmon/exynos4_tmu @@ -0,0 +1,81 @@ +Kernel driver exynos4_tmu +================= + +Supported chips: +* ARM SAMSUNG EXYNOS4 series of SoC + Prefix: 'exynos4-tmu' + Datasheet: Not publicly available + +Authors: Donggeun Kim + +Description +----------- + +This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC. + +The chip only exposes the measured 8-bit temperature code value +through a register. +Temperature can be taken from the temperature code. +There are three equations converting from temperature to temperature code. + +The three equations are: + 1. Two point trimming + Tc = (T - 25) * (TI2 - TI1) / (85 - 25) + TI1 + + 2. One point trimming + Tc = T + TI1 - 25 + + 3. No trimming + Tc = T + 50 + + Tc: Temperature code, T: Temperature, + TI1: Trimming info for 25 degree Celsius (stored at TRIMINFO register) + Temperature code measured at 25 degree Celsius which is unchanged + TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register) + Temperature code measured at 85 degree Celsius which is unchanged + +TMU(Thermal Management Unit) in EXYNOS4 generates interrupt +when temperature exceeds pre-defined levels. +The maximum number of configurable threshold is four. +The threshold levels are defined as follows: + Level_0: current temperature > trigger_level_0 + threshold + Level_1: current temperature > trigger_level_1 + threshold + Level_2: current temperature > trigger_level_2 + threshold + Level_3: current temperature > trigger_level_3 + threshold + + The threshold and each trigger_level are set + through the corresponding registers. + +When an interrupt occurs, this driver notify user space of +one of four threshold levels for the interrupt +through kobject_uevent_env and sysfs_notify functions. +Although an interrupt condition for level_0 can be set, +it is not notified to user space through sysfs_notify function. + +Sysfs Interface +--------------- +name name of the temperature sensor + RO + +temp1_input temperature + RO + +temp1_max temperature for level_1 interrupt + RO + +temp1_crit temperature for level_2 interrupt + RO + +temp1_emergency temperature for level_3 interrupt + RO + +temp1_max_alarm alarm for level_1 interrupt + RO + +temp1_crit_alarm + alarm for level_2 interrupt + RO + +temp1_emergency_alarm + alarm for level_3 interrupt + RO diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 0b62c3c6b7c..c6fb7611dd1 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -303,6 +303,16 @@ config SENSORS_DS1621 This driver can also be built as a module. If so, the module will be called ds1621. +config SENSORS_EXYNOS4_TMU + tristate "Temperature sensor on Samsung EXYNOS4" + depends on EXYNOS4_DEV_TMU + help + If you say yes here you get support for TMU (Thermal Managment + Unit) on SAMSUNG EXYNOS4 series of SoC. + + This driver can also be built as a module. If so, the module + will be called exynos4-tmu. + config SENSORS_I5K_AMB tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets" depends on PCI && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 3c9ccefea79..dbd8963ec7f 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_SENSORS_DS1621) += ds1621.o obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o +obj-$(CONFIG_SENSORS_EXYNOS4_TMU) += exynos4_tmu.o obj-$(CONFIG_SENSORS_F71805F) += f71805f.o obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o obj-$(CONFIG_SENSORS_F75375S) += f75375s.o diff --git a/drivers/hwmon/exynos4_tmu.c b/drivers/hwmon/exynos4_tmu.c new file mode 100644 index 00000000000..faa0884f61f --- /dev/null +++ b/drivers/hwmon/exynos4_tmu.c @@ -0,0 +1,524 @@ +/* + * exynos4_tmu.c - Samsung EXYNOS4 TMU (Thermal Management Unit) + * + * Copyright (C) 2011 Samsung Electronics + * Donggeun Kim + * + * 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 + +#define EXYNOS4_TMU_REG_TRIMINFO 0x0 +#define EXYNOS4_TMU_REG_CONTROL 0x20 +#define EXYNOS4_TMU_REG_STATUS 0x28 +#define EXYNOS4_TMU_REG_CURRENT_TEMP 0x40 +#define EXYNOS4_TMU_REG_THRESHOLD_TEMP 0x44 +#define EXYNOS4_TMU_REG_TRIG_LEVEL0 0x50 +#define EXYNOS4_TMU_REG_TRIG_LEVEL1 0x54 +#define EXYNOS4_TMU_REG_TRIG_LEVEL2 0x58 +#define EXYNOS4_TMU_REG_TRIG_LEVEL3 0x5C +#define EXYNOS4_TMU_REG_PAST_TEMP0 0x60 +#define EXYNOS4_TMU_REG_PAST_TEMP1 0x64 +#define EXYNOS4_TMU_REG_PAST_TEMP2 0x68 +#define EXYNOS4_TMU_REG_PAST_TEMP3 0x6C +#define EXYNOS4_TMU_REG_INTEN 0x70 +#define EXYNOS4_TMU_REG_INTSTAT 0x74 +#define EXYNOS4_TMU_REG_INTCLEAR 0x78 + +#define EXYNOS4_TMU_GAIN_SHIFT 8 +#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT 24 + +#define EXYNOS4_TMU_TRIM_TEMP_MASK 0xff +#define EXYNOS4_TMU_CORE_ON 3 +#define EXYNOS4_TMU_CORE_OFF 2 +#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET 50 +#define EXYNOS4_TMU_TRIG_LEVEL0_MASK 0x1 +#define EXYNOS4_TMU_TRIG_LEVEL1_MASK 0x10 +#define EXYNOS4_TMU_TRIG_LEVEL2_MASK 0x100 +#define EXYNOS4_TMU_TRIG_LEVEL3_MASK 0x1000 +#define EXYNOS4_TMU_INTCLEAR_VAL 0x1111 + +struct exynos4_tmu_data { + struct exynos4_tmu_platform_data *pdata; + struct device *hwmon_dev; + struct resource *mem; + void __iomem *base; + int irq; + 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 exynos4_tmu_data *data, u8 temp) +{ + struct exynos4_tmu_platform_data *pdata = data->pdata; + int temp_code; + + /* 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 + EXYNOS4_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 exynos4_tmu_data *data, u8 temp_code) +{ + struct exynos4_tmu_platform_data *pdata = data->pdata; + int temp; + + /* 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 - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET; + break; + } +out: + return temp; +} + +static int exynos4_tmu_initialize(struct platform_device *pdev) +{ + struct exynos4_tmu_data *data = platform_get_drvdata(pdev); + struct exynos4_tmu_platform_data *pdata = data->pdata; + unsigned int status, trim_info; + int ret = 0, threshold_code; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + status = readb(data->base + EXYNOS4_TMU_REG_STATUS); + if (!status) { + ret = -EBUSY; + goto out; + } + + /* Save trimming info in order to perform calibration */ + trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO); + data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK; + data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK); + + /* 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 + EXYNOS4_TMU_REG_THRESHOLD_TEMP); + + writeb(pdata->trigger_levels[0], + data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0); + writeb(pdata->trigger_levels[1], + data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1); + writeb(pdata->trigger_levels[2], + data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2); + writeb(pdata->trigger_levels[3], + data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3); + + writel(EXYNOS4_TMU_INTCLEAR_VAL, + data->base + EXYNOS4_TMU_REG_INTCLEAR); +out: + clk_disable(data->clk); + mutex_unlock(&data->lock); + + return ret; +} + +static void exynos4_tmu_control(struct platform_device *pdev, bool on) +{ + struct exynos4_tmu_data *data = platform_get_drvdata(pdev); + struct exynos4_tmu_platform_data *pdata = data->pdata; + unsigned int con, interrupt_en; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT | + pdata->gain << EXYNOS4_TMU_GAIN_SHIFT; + if (on) { + con |= EXYNOS4_TMU_CORE_ON; + interrupt_en = pdata->trigger_level3_en << 12 | + pdata->trigger_level2_en << 8 | + pdata->trigger_level1_en << 4 | + pdata->trigger_level0_en; + } else { + con |= EXYNOS4_TMU_CORE_OFF; + interrupt_en = 0; /* Disable all interrupts */ + } + writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN); + writel(con, data->base + EXYNOS4_TMU_REG_CONTROL); + + clk_disable(data->clk); + mutex_unlock(&data->lock); +} + +static int exynos4_tmu_read(struct exynos4_tmu_data *data) +{ + u8 temp_code; + int temp; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP); + temp = code_to_temp(data, temp_code); + + clk_disable(data->clk); + mutex_unlock(&data->lock); + + return temp; +} + +static void exynos4_tmu_work(struct work_struct *work) +{ + struct exynos4_tmu_data *data = container_of(work, + struct exynos4_tmu_data, irq_work); + + mutex_lock(&data->lock); + clk_enable(data->clk); + + writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR); + + kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE); + + enable_irq(data->irq); + + clk_disable(data->clk); + mutex_unlock(&data->lock); +} + +static irqreturn_t exynos4_tmu_irq(int irq, void *id) +{ + struct exynos4_tmu_data *data = id; + + disable_irq_nosync(irq); + schedule_work(&data->irq_work); + + return IRQ_HANDLED; +} + +static ssize_t exynos4_tmu_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "exynos4-tmu\n"); +} + +static ssize_t exynos4_tmu_show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct exynos4_tmu_data *data = dev_get_drvdata(dev); + int ret; + + ret = exynos4_tmu_read(data); + if (ret < 0) + return ret; + + /* convert from degree Celsius to millidegree Celsius */ + return sprintf(buf, "%d\n", ret * 1000); +} + +static ssize_t exynos4_tmu_show_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct exynos4_tmu_data *data = dev_get_drvdata(dev); + struct exynos4_tmu_platform_data *pdata = data->pdata; + int temp; + unsigned int trigger_level; + + temp = exynos4_tmu_read(data); + if (temp < 0) + return temp; + + trigger_level = pdata->threshold + pdata->trigger_levels[attr->index]; + + return sprintf(buf, "%d\n", !!(temp > trigger_level)); +} + +static ssize_t exynos4_tmu_show_level(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct exynos4_tmu_data *data = dev_get_drvdata(dev); + struct exynos4_tmu_platform_data *pdata = data->pdata; + unsigned int temp = pdata->threshold + + pdata->trigger_levels[attr->index]; + + return sprintf(buf, "%u\n", temp * 1000); +} + +static DEVICE_ATTR(name, S_IRUGO, exynos4_tmu_show_name, NULL); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, exynos4_tmu_show_temp, NULL, 0); + +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, + exynos4_tmu_show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, + exynos4_tmu_show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO, + exynos4_tmu_show_alarm, NULL, 3); + +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, exynos4_tmu_show_level, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, exynos4_tmu_show_level, NULL, 2); +static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO, + exynos4_tmu_show_level, NULL, 3); + +static struct attribute *exynos4_tmu_attributes[] = { + &dev_attr_name.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_emergency.dev_attr.attr, + NULL, +}; + +static const struct attribute_group exynos4_tmu_attr_group = { + .attrs = exynos4_tmu_attributes, +}; + +static int __devinit exynos4_tmu_probe(struct platform_device *pdev) +{ + struct exynos4_tmu_data *data; + struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data; + int ret; + + if (!pdata) { + dev_err(&pdev->dev, "No platform init data supplied.\n"); + return -ENODEV; + } + + data = kzalloc(sizeof(struct exynos4_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) { + ret = data->irq; + dev_err(&pdev->dev, "Failed to get platform irq\n"); + goto err_free; + } + + INIT_WORK(&data->irq_work, exynos4_tmu_work); + + data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!data->mem) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get platform resource\n"); + goto err_free; + } + + data->mem = request_mem_region(data->mem->start, + resource_size(data->mem), pdev->name); + if (!data->mem) { + ret = -ENODEV; + dev_err(&pdev->dev, "Failed to request memory region\n"); + goto err_free; + } + + data->base = ioremap(data->mem->start, resource_size(data->mem)); + if (!data->base) { + ret = -ENODEV; + dev_err(&pdev->dev, "Failed to ioremap memory\n"); + goto err_mem_region; + } + + ret = request_irq(data->irq, exynos4_tmu_irq, + IRQF_TRIGGER_RISING, + "exynos4-tmu", data); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); + goto err_io_remap; + } + + data->clk = clk_get(NULL, "tmu_apbif"); + if (IS_ERR(data->clk)) { + ret = PTR_ERR(data->clk); + dev_err(&pdev->dev, "Failed to get clock\n"); + goto err_irq; + } + + data->pdata = pdata; + platform_set_drvdata(pdev, data); + mutex_init(&data->lock); + + ret = exynos4_tmu_initialize(pdev); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize TMU\n"); + goto err_clk; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &exynos4_tmu_attr_group); + if (ret) { + dev_err(&pdev->dev, "Failed to create sysfs group\n"); + goto err_clk; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Failed to register hwmon device\n"); + goto err_create_group; + } + + exynos4_tmu_control(pdev, true); + + return 0; + +err_create_group: + sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group); +err_clk: + platform_set_drvdata(pdev, NULL); + clk_put(data->clk); +err_irq: + free_irq(data->irq, data); +err_io_remap: + iounmap(data->base); +err_mem_region: + release_mem_region(data->mem->start, resource_size(data->mem)); +err_free: + kfree(data); + + return ret; +} + +static int __devexit exynos4_tmu_remove(struct platform_device *pdev) +{ + struct exynos4_tmu_data *data = platform_get_drvdata(pdev); + + exynos4_tmu_control(pdev, false); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group); + + clk_put(data->clk); + + free_irq(data->irq, data); + + iounmap(data->base); + release_mem_region(data->mem->start, resource_size(data->mem)); + + platform_set_drvdata(pdev, NULL); + + kfree(data); + + return 0; +} + +#ifdef CONFIG_PM +static int exynos4_tmu_suspend(struct platform_device *pdev, pm_message_t state) +{ + exynos4_tmu_control(pdev, false); + + return 0; +} + +static int exynos4_tmu_resume(struct platform_device *pdev) +{ + exynos4_tmu_initialize(pdev); + exynos4_tmu_control(pdev, true); + + return 0; +} +#else +#define exynos4_tmu_suspend NULL +#define exynos4_tmu_resume NULL +#endif + +static struct platform_driver exynos4_tmu_driver = { + .driver = { + .name = "exynos4-tmu", + .owner = THIS_MODULE, + }, + .probe = exynos4_tmu_probe, + .remove = __devexit_p(exynos4_tmu_remove), + .suspend = exynos4_tmu_suspend, + .resume = exynos4_tmu_resume, +}; + +static int __init exynos4_tmu_driver_init(void) +{ + return platform_driver_register(&exynos4_tmu_driver); +} +module_init(exynos4_tmu_driver_init); + +static void __exit exynos4_tmu_driver_exit(void) +{ + platform_driver_unregister(&exynos4_tmu_driver); +} +module_exit(exynos4_tmu_driver_exit); + +MODULE_DESCRIPTION("EXYNOS4 TMU Driver"); +MODULE_AUTHOR("Donggeun Kim "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:exynos4-tmu"); diff --git a/include/linux/platform_data/exynos4_tmu.h b/include/linux/platform_data/exynos4_tmu.h new file mode 100644 index 00000000000..39e038cca59 --- /dev/null +++ b/include/linux/platform_data/exynos4_tmu.h @@ -0,0 +1,83 @@ +/* + * exynos4_tmu.h - Samsung EXYNOS4 TMU (Thermal Management Unit) + * + * Copyright (C) 2011 Samsung Electronics + * Donggeun Kim + * + * 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_EXYNOS4_TMU_H +#define _LINUX_EXYNOS4_TMU_H + +enum calibration_type { + TYPE_ONE_POINT_TRIMMING, + TYPE_TWO_POINT_TRIMMING, + TYPE_NONE, +}; + +/** + * struct exynos4_tmu_platform_data + * @threshold: basic temperature for generating interrupt + * 25 <= threshold <= 125 [unit: degree Celsius] + * @trigger_levels: array for each interrupt levels + * [unit: degree Celsius] + * 0: temperature for trigger_level0 interrupt + * condition for trigger_level0 interrupt: + * current temperature > threshold + trigger_levels[0] + * 1: temperature for trigger_level1 interrupt + * condition for trigger_level1 interrupt: + * current temperature > threshold + trigger_levels[1] + * 2: temperature for trigger_level2 interrupt + * condition for trigger_level2 interrupt: + * current temperature > threshold + trigger_levels[2] + * 3: temperature for trigger_level3 interrupt + * condition for trigger_level3 interrupt: + * current temperature > threshold + trigger_levels[3] + * @trigger_level0_en: + * 1 = enable trigger_level0 interrupt, + * 0 = disable trigger_level0 interrupt + * @trigger_level1_en: + * 1 = enable trigger_level1 interrupt, + * 0 = disable trigger_level1 interrupt + * @trigger_level2_en: + * 1 = enable trigger_level2 interrupt, + * 0 = disable trigger_level2 interrupt + * @trigger_level3_en: + * 1 = enable trigger_level3 interrupt, + * 0 = disable trigger_level3 interrupt + * @gain: gain of amplifier in the positive-TC generator block + * 0 <= gain <= 15 + * @reference_voltage: reference voltage of amplifier + * in the positive-TC generator block + * 0 <= reference_voltage <= 31 + * @cal_type: calibration type for temperature + * + * This structure is required for configuration of exynos4_tmu driver. + */ +struct exynos4_tmu_platform_data { + u8 threshold; + u8 trigger_levels[4]; + bool trigger_level0_en; + bool trigger_level1_en; + bool trigger_level2_en; + bool trigger_level3_en; + + u8 gain; + u8 reference_voltage; + + enum calibration_type cal_type; +}; +#endif /* _LINUX_EXYNOS4_TMU_H */ -- cgit v1.2.3 From d9f151cda68a86d176b0d6773c019eeae1d58301 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 12 Dec 2011 10:44:36 +0530 Subject: thermal: exynos: Add thermal interface support for linux thermal layer This codes uses the generic linux thermal layer and creates a bridge between temperature sensors, linux thermal framework and cooling devices for samsung exynos platform. This layer recieves or monitor the temperature from the sensor and informs the generic thermal layer. Signed-off-by: Amit Daniel Kachhap --- drivers/thermal/Kconfig | 8 + drivers/thermal/Makefile | 1 + drivers/thermal/exynos_thermal.c | 334 +++++++++++++++++++++++++++++++++++++++ include/linux/exynos_thermal.h | 32 ++++ 4 files changed, 375 insertions(+) create mode 100644 drivers/thermal/exynos_thermal.c create mode 100644 include/linux/exynos_thermal.h diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 298c1cdcd38..938953fd1c2 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -29,3 +29,11 @@ config CPU_THERMAL This will be useful for platforms using the generic thermal interface and not the ACPI interface. If you want this support, you should say Y or M here. + +menuconfig SAMSUNG_THERMAL_INTERFACE + bool "Samsung Thermal support" + depends on THERMAL && CPU_THERMAL + help + This is a samsung thermal interface which will be used as + a link between sensors and cooling devices with linux thermal + framework. diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 655cbc42529..c67b6b222be 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_THERMAL) += thermal_sys.o obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o +obj-$(CONFIG_SAMSUNG_THERMAL_INTERFACE) += exynos_thermal.o diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c new file mode 100644 index 00000000000..ff189b145ad --- /dev/null +++ b/drivers/thermal/exynos_thermal.c @@ -0,0 +1,334 @@ +/* linux/drivers/staging/thermal_exynos4/thermal_interface.c + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned int verbose; + +struct exynos4_thermal_zone { + unsigned int idle_interval; + unsigned int active_interval; + struct thermal_zone_device *therm_dev; + struct thermal_cooling_device *cool_dev; + struct platform_device *exynos4_dev; + struct exynos4_tmu_platform_data *sensor_data; +}; +static struct thermal_sensor_info *exynos4_sensor_info; + +static struct exynos4_thermal_zone *th_zone; + +static int exynos4_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + if (exynos4_sensor_info) { + pr_info("Temperature sensor not initialised\n"); + *mode = THERMAL_DEVICE_DISABLED; + } else + *mode = THERMAL_DEVICE_ENABLED; + return 0; +} + +/* + * set operation mode; + * enabled: the thermal layer of the kernel takes care about + * the temperature. + * disabled: temperature sensor is not enabled. + */ +static int exynos4_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + if (!th_zone->therm_dev) { + pr_notice("thermal zone not registered\n"); + return 0; + } + if (mode == THERMAL_DEVICE_ENABLED) + th_zone->therm_dev->polling_delay = + th_zone->active_interval*1000; + else + th_zone->therm_dev->polling_delay = + th_zone->idle_interval*1000; + + thermal_zone_device_update(th_zone->therm_dev); + pr_info("thermal polling set for duration=%d sec\n", + th_zone->therm_dev->polling_delay/1000); + return 0; +} + +/*This may be called from interrupt based temperature sensor*/ +void exynos4_report_trigger(void) +{ + unsigned int th_temp = th_zone->sensor_data->threshold; + unsigned int monitor_temp = th_temp + + th_zone->sensor_data->trigger_levels[1]; + + thermal_zone_device_update(th_zone->therm_dev); + + if (th_zone->therm_dev->last_temperature > monitor_temp) + th_zone->therm_dev->polling_delay = + th_zone->active_interval*1000; + else + th_zone->therm_dev->polling_delay = + th_zone->idle_interval*1000; +} + +static int exynos4_get_trip_type(struct thermal_zone_device *thermal, int trip, + enum thermal_trip_type *type) +{ + if (verbose) + pr_info("%s, trip no=%d\n", __func__, trip); + if (trip == 0 || trip == 1) + *type = THERMAL_TRIP_STATE_ACTIVE; + else if (trip == 2) + *type = THERMAL_TRIP_CRITICAL; + else + return -EINVAL; + + return 0; +} + +static int exynos4_get_trip_temp(struct thermal_zone_device *thermal, int trip, + unsigned long *temp) +{ + unsigned int th_temp = th_zone->sensor_data->threshold; + + /*Monitor zone*/ + if (trip == 0) + *temp = th_temp + th_zone->sensor_data->trigger_levels[1]; + /*Warn zone*/ + else if (trip == 1) + *temp = th_temp + th_zone->sensor_data->trigger_levels[2]; + /*Panic zone*/ + else if (trip == 2) + *temp = th_temp + th_zone->sensor_data->trigger_levels[3]; + else + return -EINVAL; + + return 0; +} + +static int exynos4_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + unsigned int th_temp = th_zone->sensor_data->threshold; + /*Panic zone*/ + *temp = th_temp + th_zone->sensor_data->trigger_levels[3]; + return 0; +} + +static int exynos4_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + /* if the cooling device is the one from exynos4 bind it */ + if (cdev != th_zone->cool_dev) + return 0; + + if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) { + pr_err("error binding cooling dev\n"); + return -EINVAL; + } + if (thermal_zone_bind_cooling_device(thermal, 1, cdev)) { + pr_err("error binding cooling dev\n"); + return -EINVAL; + } + + return 0; +} + +static int exynos4_unbind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + if (cdev != th_zone->cool_dev) + return 0; + if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) { + pr_err("error unbinding cooling dev\n"); + return -EINVAL; + } + return 0; +} + +static int exynos4_get_temp(struct thermal_zone_device *thermal, + unsigned long *t) +{ + int temp = 0; + void *data; + + if (!exynos4_sensor_info) { + pr_info("Temperature sensor not initialised\n"); + return -EINVAL; + } + data = exynos4_sensor_info->private_data; + temp = exynos4_sensor_info->read_temperature(data); + + if (verbose) + pr_notice("temp %d\n", temp); + + *t = temp; + return 0; +} + +/* bind callback functions to thermalzone */ +static struct thermal_zone_device_ops exynos4_dev_ops = { + .bind = exynos4_bind, + .unbind = exynos4_unbind, + .get_temp = exynos4_get_temp, + .get_mode = exynos4_get_mode, + .set_mode = exynos4_set_mode, + .get_trip_type = exynos4_get_trip_type, + .get_trip_temp = exynos4_get_trip_temp, + .get_crit_temp = exynos4_get_crit_temp, +}; + +int exynos4_register_temp_sensor(struct thermal_sensor_info *sensor_info) +{ + exynos4_sensor_info = sensor_info; + return 0; +} + + +static int __devinit exynos4_probe(struct platform_device *device) +{ + return 0; +} + +static int exynos4_remove(struct platform_device *device) +{ + return 0; +} + +static struct platform_driver exynos4_driver = { + .driver = { + .name = "exynos4", + .owner = THIS_MODULE, + }, + .probe = exynos4_probe, + .remove = exynos4_remove, +}; + +static int exynos4_register_platform(void) +{ + int err = 0; + + th_zone = kzalloc(sizeof(struct exynos4_thermal_zone), GFP_KERNEL); + if (!th_zone) + return -ENOMEM; + + err = platform_driver_register(&exynos4_driver); + if (err) + return err; + + th_zone->exynos4_dev = platform_device_alloc("exynos4", -1); + if (!th_zone->exynos4_dev) { + err = -ENOMEM; + goto err_device_alloc; + } + err = platform_device_add(th_zone->exynos4_dev); + if (err) + goto err_device_add; + + return 0; + +err_device_add: + platform_device_put(th_zone->exynos4_dev); +err_device_alloc: + platform_driver_unregister(&exynos4_driver); + return err; +} + +static void exynos4_unregister_platform(void) +{ + platform_device_unregister(th_zone->exynos4_dev); + platform_driver_unregister(&exynos4_driver); + kfree(th_zone); +} + +static int exynos4_register_thermal(void) +{ + if (!exynos4_sensor_info) { + pr_info("Temperature sensor not initialised\n"); + return -EINVAL; + } + + th_zone->sensor_data = exynos4_sensor_info->sensor_data; + if (!th_zone->sensor_data) { + pr_info("Temperature sensor data not initialised\n"); + return -EINVAL; + } + + th_zone->cool_dev = cpufreq_cooling_register( + (struct freq_pctg_table *)th_zone->sensor_data->freq_tab, + th_zone->sensor_data->level_count); + + if (IS_ERR(th_zone->cool_dev)) + return -EINVAL; + + th_zone->therm_dev = thermal_zone_device_register("exynos4-therm", + 3, NULL, &exynos4_dev_ops, 0, 0, 0, 1000); + if (IS_ERR(th_zone->therm_dev)) + return -EINVAL; + + th_zone->active_interval = 1; + th_zone->idle_interval = 10; + exynos4_set_mode(th_zone->therm_dev, THERMAL_DEVICE_DISABLED); + + return 0; +} + +static void exynos4_unregister_thermal(void) +{ + if (th_zone->cool_dev) + cpufreq_cooling_unregister(); + + if (th_zone->therm_dev) + thermal_zone_device_unregister(th_zone->therm_dev); +} + +static int __init exynos4_thermal_init(void) +{ + int err = 0; + + err = exynos4_register_platform(); + if (err) + goto out_err; + + err = exynos4_register_thermal(); + if (err) + goto err_unreg; + + return 0; + +err_unreg: + exynos4_unregister_thermal(); + exynos4_unregister_platform(); + +out_err: + return err; +} + +static void __exit exynos4_thermal_exit(void) +{ + exynos4_unregister_thermal(); + exynos4_unregister_platform(); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Amit Daniel "); +MODULE_DESCRIPTION("samsung Exynos4 thermal monitor driver"); + +module_init(exynos4_thermal_init); +module_exit(exynos4_thermal_exit); diff --git a/include/linux/exynos_thermal.h b/include/linux/exynos_thermal.h new file mode 100644 index 00000000000..15f1a17453d --- /dev/null +++ b/include/linux/exynos_thermal.h @@ -0,0 +1,32 @@ +/* linux/include/linux/exynos_thermal.h + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#ifndef THERMAL_INTERFACE_H +#define THERMAL_INTERFACE_H +/* CPU Zone information */ + +#define SENSOR_NAME_LEN 16 + +#define PANIC_ZONE 4 +#define WARN_ZONE 3 +#define MONITOR_ZONE 2 +#define SAFE_ZONE 1 +#define NO_ACTION 0 + +struct thermal_sensor_info { + char name[SENSOR_NAME_LEN]; + int (*read_temperature)(void *data); + void *private_data; + void *sensor_data; +}; + +extern int exynos4_register_temp_sensor(struct thermal_sensor_info *sensor); +extern void exynos4_report_trigger(void); +#endif -- cgit v1.2.3 From 15bfc7956f3fef104c3b3d14a414f9e80eb94d41 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Tue, 13 Dec 2011 14:57:23 +0530 Subject: thermal: exynos4: Export information from the TMU driver Export information from the hwmon tmu sensor to the samsung exynos kernel thermal framework where different cooling devices and thermal zone are binded. The exported information is based according to the data structure thermal_sensor_info present in exynos_thermal.h. HWMON sysfs functions are commented out as they are also present in generic linux thermal layer. Signed-off-by: Amit Daniel Kachhap --- drivers/hwmon/exynos4_tmu.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/exynos4_tmu.c b/drivers/hwmon/exynos4_tmu.c index faa0884f61f..0d964b5b37c 100644 --- a/drivers/hwmon/exynos4_tmu.c +++ b/drivers/hwmon/exynos4_tmu.c @@ -37,6 +37,9 @@ #include #include +#ifdef CONFIG_SAMSUNG_THERMAL_INTERFACE +#include +#endif #define EXYNOS4_TMU_REG_TRIMINFO 0x0 #define EXYNOS4_TMU_REG_CONTROL 0x20 @@ -248,10 +251,13 @@ static void exynos4_tmu_work(struct work_struct *work) kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE); - enable_irq(data->irq); clk_disable(data->clk); mutex_unlock(&data->lock); +#ifdef CONFIG_SAMSUNG_THERMAL_INTERFACE + exynos4_report_trigger(); +#endif + enable_irq(data->irq); } static irqreturn_t exynos4_tmu_irq(int irq, void *id) @@ -264,6 +270,7 @@ static irqreturn_t exynos4_tmu_irq(int irq, void *id) return IRQ_HANDLED; } +#ifndef CONFIG_SAMSUNG_THERMAL_INTERFACE static ssize_t exynos4_tmu_show_name(struct device *dev, struct device_attribute *attr, char *buf) { @@ -344,6 +351,16 @@ static struct attribute *exynos4_tmu_attributes[] = { static const struct attribute_group exynos4_tmu_attr_group = { .attrs = exynos4_tmu_attributes, }; +#endif +/*CONFIG_SAMSUNG_THERMAL_INTERFACE*/ + +#ifdef CONFIG_SAMSUNG_THERMAL_INTERFACE +static struct thermal_sensor_info exynos4_sensor_info = { + .name = "exynos4-tmu", + .read_temperature = (int (*)(void *))exynos4_tmu_read, +}; +#endif +/*CONFIG_SAMSUNG_THERMAL_INTERFACE*/ static int __devinit exynos4_tmu_probe(struct platform_device *pdev) { @@ -418,11 +435,14 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev) goto err_clk; } +#ifndef CONFIG_SAMSUNG_THERMAL_INTERFACE ret = sysfs_create_group(&pdev->dev.kobj, &exynos4_tmu_attr_group); if (ret) { dev_err(&pdev->dev, "Failed to create sysfs group\n"); goto err_clk; } +#endif +/*CONFIG_SAMSUNG_THERMAL_INTERFACE*/ data->hwmon_dev = hwmon_device_register(&pdev->dev); if (IS_ERR(data->hwmon_dev)) { @@ -432,11 +452,18 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev) } exynos4_tmu_control(pdev, true); +#ifdef CONFIG_SAMSUNG_THERMAL_INTERFACE + (&exynos4_sensor_info)->private_data = data; + (&exynos4_sensor_info)->sensor_data = pdata; + exynos4_register_temp_sensor(&exynos4_sensor_info); +#endif return 0; err_create_group: +#ifndef CONFIG_SAMSUNG_THERMAL_INTERFACE sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group); +#endif err_clk: platform_set_drvdata(pdev, NULL); clk_put(data->clk); @@ -459,7 +486,9 @@ static int __devexit exynos4_tmu_remove(struct platform_device *pdev) exynos4_tmu_control(pdev, false); hwmon_device_unregister(data->hwmon_dev); +#ifndef CONFIG_SAMSUNG_THERMAL_INTERFACE sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group); +#endif clk_put(data->clk); -- cgit v1.2.3 From 6bdad5c64b0482e61d20e22ddcfae40b2aa9f83f Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 12 Dec 2011 11:01:39 +0530 Subject: hwmon: exynos4: Add cpu frequency clip data for certain threshold This patch helps to modify the exynos4 tmu to get the frequency clipping data from the platform if needed for a temperature trip points. Signed-off-by: Amit Daniel Kachhap --- include/linux/platform_data/exynos4_tmu.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/platform_data/exynos4_tmu.h b/include/linux/platform_data/exynos4_tmu.h index 39e038cca59..f655dcdd6b1 100644 --- a/include/linux/platform_data/exynos4_tmu.h +++ b/include/linux/platform_data/exynos4_tmu.h @@ -21,6 +21,7 @@ #ifndef _LINUX_EXYNOS4_TMU_H #define _LINUX_EXYNOS4_TMU_H +#include enum calibration_type { TYPE_ONE_POINT_TRIMMING, @@ -79,5 +80,8 @@ struct exynos4_tmu_platform_data { u8 reference_voltage; enum calibration_type cal_type; + + struct freq_pctg_table freq_tab[4]; + unsigned int level_count; }; #endif /* _LINUX_EXYNOS4_TMU_H */ -- cgit v1.2.3 From e82b9d821ecc1f857142a55b0fb38d0d5256fbaf Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Wed, 14 Dec 2011 21:44:07 +0530 Subject: ARM: exynos4: Add thermal sensor driver platform device support This patch adds necessary source definations needed for TMU driver and the platform device support. Signed-off-by: Amit Daniel Kachhap --- arch/arm/mach-exynos4/Kconfig | 12 ++++++ arch/arm/mach-exynos4/Makefile | 1 + arch/arm/mach-exynos4/clock.c | 4 ++ arch/arm/mach-exynos4/dev-tmu.c | 68 +++++++++++++++++++++++++++++++ arch/arm/mach-exynos4/include/mach/irqs.h | 2 + arch/arm/mach-exynos4/include/mach/map.h | 1 + arch/arm/mach-exynos4/mach-origen.c | 1 + arch/arm/plat-samsung/include/plat/devs.h | 1 + 8 files changed, 90 insertions(+) create mode 100644 arch/arm/mach-exynos4/dev-tmu.c diff --git a/arch/arm/mach-exynos4/Kconfig b/arch/arm/mach-exynos4/Kconfig index 3ceefdbadd1..f83526865d1 100644 --- a/arch/arm/mach-exynos4/Kconfig +++ b/arch/arm/mach-exynos4/Kconfig @@ -46,6 +46,17 @@ config EXYNOS4_DEV_DWMCI help Compile in platform device definitions for DWMCI +config EXYNOS4_DEV_TMU + bool "Exynos4 tmu device support" + default n + depends on ARCH_EXYNOS4 + ---help--- + Compile in platform device definitions for TMU. This macro also + enables compilation hwmon base TMU driver and also allows compilation + of the platform device files. The platform data in this case is trip + temperature and some tmu h/w configurations related parameter. + + config EXYNOS4_SETUP_I2C1 bool help @@ -224,6 +235,7 @@ config MACH_ORIGEN select S3C_DEV_RTC select S3C_DEV_WDT select S3C_DEV_HSMMC2 + select EXYNOS4_DEV_TMU select EXYNOS4_SETUP_SDHCI help Machine support for ORIGEN based on Samsung EXYNOS4210 diff --git a/arch/arm/mach-exynos4/Makefile b/arch/arm/mach-exynos4/Makefile index cbc97670143..8e5c098db7c 100644 --- a/arch/arm/mach-exynos4/Makefile +++ b/arch/arm/mach-exynos4/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_EXYNOS4_DEV_AHCI) += dev-ahci.o obj-$(CONFIG_EXYNOS4_DEV_PD) += dev-pd.o obj-$(CONFIG_EXYNOS4_DEV_SYSMMU) += dev-sysmmu.o obj-$(CONFIG_EXYNOS4_DEV_DWMCI) += dev-dwmci.o +obj-$(CONFIG_EXYNOS4_DEV_TMU) += dev-tmu.o obj-$(CONFIG_EXYNOS4_SETUP_FIMC) += setup-fimc.o obj-$(CONFIG_EXYNOS4_SETUP_FIMD0) += setup-fimd0.o diff --git a/arch/arm/mach-exynos4/clock.c b/arch/arm/mach-exynos4/clock.c index 86964d2e9e1..b90920e526f 100644 --- a/arch/arm/mach-exynos4/clock.c +++ b/arch/arm/mach-exynos4/clock.c @@ -467,6 +467,10 @@ static struct clk init_clocks_off[] = { .name = "adc", .enable = exynos4_clk_ip_peril_ctrl, .ctrlbit = (1 << 15), + }, { + .name = "tmu_apbif", + .enable = exynos4_clk_ip_perir_ctrl, + .ctrlbit = (1 << 17), }, { .name = "keypad", .enable = exynos4_clk_ip_perir_ctrl, diff --git a/arch/arm/mach-exynos4/dev-tmu.c b/arch/arm/mach-exynos4/dev-tmu.c new file mode 100644 index 00000000000..c1e9e9648b6 --- /dev/null +++ b/arch/arm/mach-exynos4/dev-tmu.c @@ -0,0 +1,68 @@ +/* linux/arch/arm/mach-exynos4/dev-tmu.c + * + * Copyright 2011 by SAMSUNG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static struct resource exynos4_tmu_resource[] = { + [0] = { + .start = EXYNOS4_PA_TMU, + .end = EXYNOS4_PA_TMU + 0xFFFF - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_TMU_TRIG0, + .end = IRQ_TMU_TRIG0, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct exynos4_tmu_platform_data default_tmu_data = { + .threshold = 55, + .trigger_levels[0] = 0xFF, + .trigger_levels[1] = 5, + .trigger_levels[2] = 20, + .trigger_levels[3] = 30, + .trigger_level0_en = 0, + .trigger_level1_en = 1, + .trigger_level2_en = 1, + .trigger_level3_en = 1, + .gain = 15, + .reference_voltage = 7, + .cal_type = TYPE_ONE_POINT_TRIMMING, + .freq_tab[0] = { + .freq_clip_pctg[0] = 50, + }, + .freq_tab[1] = { + .freq_clip_pctg[0] = 99, + }, + .freq_tab[2] = { + .freq_clip_pctg[0] = 99, + }, + .level_count = 3, +}; + +struct platform_device exynos4_device_tmu = { + .name = "exynos4-tmu", + .id = -1, + .num_resources = ARRAY_SIZE(exynos4_tmu_resource), + .resource = exynos4_tmu_resource, + .dev = { + .platform_data = &default_tmu_data, + }, +}; +EXPORT_SYMBOL(exynos4_device_tmu); diff --git a/arch/arm/mach-exynos4/include/mach/irqs.h b/arch/arm/mach-exynos4/include/mach/irqs.h index f8952f8f375..94e87a9172c 100644 --- a/arch/arm/mach-exynos4/include/mach/irqs.h +++ b/arch/arm/mach-exynos4/include/mach/irqs.h @@ -119,6 +119,8 @@ #define COMBINER_GROUP(x) ((x) * MAX_IRQ_IN_COMBINER + IRQ_SPI(128)) #define COMBINER_IRQ(x, y) (COMBINER_GROUP(x) + y) +#define IRQ_TMU_TRIG0 COMBINER_IRQ(2, 4) +#define IRQ_TMU_TRIG1 COMBINER_IRQ(3, 4) #define IRQ_SYSMMU_MDMA0_0 COMBINER_IRQ(4, 0) #define IRQ_SYSMMU_SSS_0 COMBINER_IRQ(4, 1) #define IRQ_SYSMMU_FIMC0_0 COMBINER_IRQ(4, 2) diff --git a/arch/arm/mach-exynos4/include/mach/map.h b/arch/arm/mach-exynos4/include/mach/map.h index d32296dc65e..233525bfa43 100644 --- a/arch/arm/mach-exynos4/include/mach/map.h +++ b/arch/arm/mach-exynos4/include/mach/map.h @@ -66,6 +66,7 @@ #define EXYNOS4_PA_COREPERI 0x10500000 #define EXYNOS4_PA_TWD 0x10500600 #define EXYNOS4_PA_L2CC 0x10502000 +#define EXYNOS4_PA_TMU 0x100C0000 #define EXYNOS4_PA_MDMA 0x10810000 #define EXYNOS4_PA_PDMA0 0x12680000 diff --git a/arch/arm/mach-exynos4/mach-origen.c b/arch/arm/mach-exynos4/mach-origen.c index b5f6f38557c..74180441852 100644 --- a/arch/arm/mach-exynos4/mach-origen.c +++ b/arch/arm/mach-exynos4/mach-origen.c @@ -83,6 +83,7 @@ static struct platform_device *origen_devices[] __initdata = { &s3c_device_hsmmc2, &s3c_device_rtc, &s3c_device_wdt, + &exynos4_device_tmu, }; static void __init origen_map_io(void) diff --git a/arch/arm/plat-samsung/include/plat/devs.h b/arch/arm/plat-samsung/include/plat/devs.h index 24ebb1e1de4..04380872048 100644 --- a/arch/arm/plat-samsung/include/plat/devs.h +++ b/arch/arm/plat-samsung/include/plat/devs.h @@ -148,6 +148,7 @@ extern struct platform_device s5p_device_mipi_csis1; extern struct platform_device s5p_device_ehci; extern struct platform_device exynos4_device_sysmmu; +extern struct platform_device exynos4_device_tmu; /* s3c2440 specific devices */ -- cgit v1.2.3