| /* |
| * linux/arch/arm/mach-zx297510/pcu.c |
| * |
| * Copyright (C) 2013 ZTE-TSP |
| * |
| * 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. |
| */ |
| #include <linux/errno.h> |
| #include <linux/irq.h> |
| #include <linux/module.h> |
| |
| #include <asm/io.h> |
| |
| #include <mach/iomap.h> |
| #include <mach/zx-pm.h> |
| #include <mach/zx297510-pm.h> |
| #include <mach/pcu.h> |
| #include <mach/spinlock.h> |
| |
| /* |
| * return external intterrupt number from ex8-ex15, |
| * return value is 8-15 |
| */ |
| unsigned int pcu_int_vector_8in1(void) |
| { |
| void __iomem *reg_base = EX_INT_VECTOR_REG; |
| unsigned int vector_8in1 = 0; |
| |
| vector_8in1 = ioread32(reg_base); |
| |
| return ((vector_8in1&0x7) + 8); |
| } |
| EXPORT_SYMBOL(pcu_int_vector_8in1); |
| |
| |
| |
| /*external int 8-15 need extra clear*/ |
| void pcu_int_clear_8in1(PCU_INT_INDEX index) |
| { |
| void __iomem *reg_base = EX_INT_TOP_CLEAR_REG; |
| unsigned int vector=0; |
| |
| if ( (index >= PCU_EX8_INT)&&(index <= PCU_EX15_INT) ){ |
| /* |
| *in 7510 platform, 8in1 interrupt would be used by different cores. |
| *when any core installs a new 8in1 interrupt, another core may be |
| * responding another 8in1 interrupt, so 8in1 interrupt shouldn't be |
| *cleared. in this case, nothing to be done. but a new problem comes, |
| * the core install new 8in1 interrupt will receive a fake interrupt. |
| */ |
| vector=pcu_int_vector_8in1(); |
| if (index != (vector + PCU_EX0_INT) ) |
| return; |
| |
| reg_spin_lock(); |
| iowrite32(0x1,reg_base); |
| reg_spin_unlock(); |
| } |
| } |
| EXPORT_SYMBOL(pcu_int_clear_8in1); |
| |
| /*only pulse int need be clear*/ |
| void pcu_int_clear(PCU_INT_INDEX index) |
| { |
| unsigned int reg_num = index&0x100; |
| unsigned int bit_mask = 1<<(index&0x1f); |
| void __iomem *reg_base = NULL; |
| unsigned int tmp; |
| |
| if (reg_num == 0x100) |
| reg_base = PCU_BASE_VA + 0xec; |
| else |
| reg_base = PCU_BASE_VA + 0x80; |
| |
| reg_spin_lock(); |
| tmp = ioread32(reg_base); |
| tmp |= bit_mask; |
| iowrite32(tmp,reg_base); |
| reg_spin_unlock(); |
| |
| pcu_int_clear_8in1(index); |
| } |
| EXPORT_SYMBOL(pcu_int_clear); |
| |
| |
| void pcu_int_set_type(PCU_INT_INDEX index,unsigned int type) |
| { |
| unsigned int reg_num = index&0x100; |
| unsigned int bit_index = index&0x1f; |
| unsigned int bit_mask = 1<<bit_index; |
| |
| void __iomem *level_regbase = NULL; |
| void __iomem *pol_regbase = NULL; |
| unsigned int tmp; |
| unsigned int level; |
| unsigned int pol; |
| |
| if (reg_num == 0x100){ |
| level_regbase = PCU_BASE_VA + 0xe4; |
| pol_regbase = PCU_BASE_VA + 0xe8; |
| } |
| else{ |
| level_regbase = PCU_BASE_VA + 0x60; |
| pol_regbase = PCU_BASE_VA + 0x70; |
| } |
| |
| |
| switch (type) { |
| case IRQ_TYPE_LEVEL_HIGH: |
| level = 1; |
| pol = 1; |
| break; |
| case IRQ_TYPE_EDGE_RISING: |
| level = 0; |
| pol = 1; |
| break; |
| case IRQ_TYPE_LEVEL_LOW: |
| level = 1; |
| pol= 0; |
| break; |
| case IRQ_TYPE_EDGE_FALLING: |
| level = 0; |
| pol = 0; |
| break; |
| default: |
| BUG(); |
| } |
| |
| reg_spin_lock(); |
| /* set level*/ |
| tmp = ioread32(level_regbase); |
| tmp &= ~bit_mask; |
| tmp |= level<<bit_index; |
| iowrite32(tmp,level_regbase); |
| |
| /* set polarity */ |
| tmp = ioread32(pol_regbase); |
| tmp &= ~bit_mask; |
| tmp |= pol<<bit_index; |
| iowrite32(tmp,pol_regbase); |
| reg_spin_unlock(); |
| |
| } |
| EXPORT_SYMBOL(pcu_int_set_type); |
| |
| |
| |
| /* low power function */ |
| |
| extern unsigned int wake_source_int_for_suspend[]; |
| extern unsigned int wake_source_int_for_dpidle[]; |
| /** |
| * enable wake source for cpu |
| * |
| */ |
| static void pcu_enable_wake_source(unsigned int *wake_source) |
| { |
| unsigned int int1, int2, int_timer; |
| |
| int1 = wake_source[0]; |
| int2 = wake_source[1]; |
| int_timer = wake_source[2]; |
| |
| reg_spin_lock(); |
| |
| pm_clr_reg(AP_INT_WAKE_DIS_REG1, int1); |
| pm_clr_reg(AP_INT_WAKE_DIS_REG2, int2); |
| pm_clr_reg(TIMER_INT_WAKE_DIS_REG, int_timer); |
| |
| reg_spin_unlock(); |
| } |
| |
| static void pcu_set_wake_source(cpu_sleep_type_t sleep_type) |
| { |
| if(CPU_SLEEP_TYPE_LP1 == sleep_type) |
| { |
| pcu_enable_wake_source(wake_source_int_for_suspend); |
| } |
| else if(CPU_SLEEP_TYPE_IDLE_LP2 == sleep_type || CPU_SLEEP_TYPE_LP3 == sleep_type) |
| { |
| pcu_enable_wake_source(wake_source_int_for_dpidle); |
| } |
| } |
| |
| /** |
| * init pcu when pm initial. |
| * |
| */ |
| void zx297510_pcu_init(void) |
| { |
| pr_info("[SLP] Power/PCU_INIT \n"); |
| |
| pm_clr_reg(ARM_AP_CONFIG_REG, PCU_MODE_MASK); |
| pm_set_pll_used(); |
| pm_set_reg(ARM_AP_SLEEP_TIME_REG, PCU_AP_SLEEP_TIME_DIS); |
| |
| pm_write_reg(AP_L2_POWER_ENABLE_REG, 0); |
| |
| pm_write_reg(AP_INT_WAKE_DIS_REG1, 0xFFFFFFFF); |
| pm_write_reg(AP_INT_WAKE_DIS_REG2, 0xFFFFFFFF); |
| |
| pm_set_reg(AP_LOW_POWER_REG1, 0); /* bypass disable */ |
| pm_set_reg(AP_LOW_POWER_REG2, 0); /* 1 - enter DS 0 - exit DS */ |
| } |
| |
| /** |
| * set&enable PCU for interrupt/clock/powerdomain/pll/iram. |
| * |
| */ |
| int zx297510_set_pcu(void) |
| { |
| cpu_sleep_type_t sleep_type; |
| u32 sleep_time; |
| |
| sleep_type = pm_get_sleep_type(); |
| sleep_time = pm_get_sleep_time(); |
| |
| pm_set_pll_used(); |
| |
| if(CPU_SLEEP_TYPE_LP1 == sleep_type) /* shutdown */ |
| { |
| pm_set_reg(ARM_AP_CONFIG_REG, PCU_SHUTDOWN_MODE); |
| pm_set_reg(ARM_AP_CONFIG_REG, PCU_MG_CLK_RESET|PCU_L2_CLK_GATE|PCU_PLL_OFF); |
| |
| pm_write_reg(ARM_AP_SLEEP_TIME_REG, 0xffffffff); |
| |
| pm_write_reg(AP_L2_POWER_ENABLE_REG, 1); |
| |
| } |
| else if(CPU_SLEEP_TYPE_IDLE_LP2 == sleep_type) /* dormant */ |
| { |
| pm_set_reg(ARM_AP_CONFIG_REG, PCU_DORMANT_MODE); |
| pm_set_reg(ARM_AP_CONFIG_REG, PCU_MG_CLK_RESET|PCU_L2_CLK_GATE|PCU_PLL_OFF); |
| |
| pm_write_reg(ARM_AP_SLEEP_TIME_REG, sleep_time); |
| } |
| else if(CPU_SLEEP_TYPE_LP3 == sleep_type) |
| { |
| pm_set_reg(ARM_AP_CONFIG_REG, PCU_STANDBY_MODE); |
| pm_clr_reg(ARM_AP_CONFIG_REG, PCU_PLL_OFF); |
| pm_set_reg(ARM_AP_CONFIG_REG, PCU_MG_CLK_RESET|PCU_L2_CLK_GATE); |
| |
| pm_write_reg(ARM_AP_SLEEP_TIME_REG, sleep_time); |
| } |
| else |
| WARN_ON(1); |
| |
| pcu_set_wake_source(sleep_type); |
| |
| return 0; |
| } |
| |
| /** |
| * clear&disable PCU for interrupt/clock/powerdomain/pll/iram. |
| * |
| */ |
| int zx297510_clear_pcu(void) |
| { |
| reg_spin_lock(); |
| |
| pm_clr_reg(ARM_AP_CONFIG_REG, PCU_MODE_MASK); |
| |
| pm_write_reg(AP_L2_POWER_ENABLE_REG, 0); |
| |
| pm_write_reg(AP_INT_WAKE_DIS_REG1, 0xFFFFFFFF); |
| pm_write_reg(AP_INT_WAKE_DIS_REG2, 0xFFFFFFFF); |
| |
| pm_set_reg(TIMER_INT_WAKE_DIS_REG, WAKE_SRC_TIMER3); |
| |
| reg_spin_unlock(); |
| |
| return 0; |
| } |
| |
| /** |
| * when sleep/dormant/poweroff, pll will be closed. |
| * |
| */ |
| void pcu_set_pll_used(unsigned pll) |
| { |
| if(PCU_USE_PLL_MAIN == pll) |
| pm_clr_reg(ARM_AP_CONFIG_REG, pll); |
| else |
| pm_set_reg(ARM_AP_CONFIG_REG, pll); |
| } |
| |
| /** |
| * get wakeup setting. |
| * |
| */ |
| void pcu_get_wakeup_setting(unsigned int *pointer) |
| { |
| pointer[0] = pm_read_reg(AP_INT_WAKE_DIS_REG1); |
| pointer[1] = pm_read_reg(AP_INT_WAKE_DIS_REG2); |
| pointer[2] = pm_read_reg(TIMER_INT_WAKE_DIS_REG); |
| } |
| |
| void pcu_clr_timer3_int(void) |
| { |
| reg_spin_lock(); |
| |
| pm_set_reg(TIMER_INT_DDR_SW_CLEAR_REG, WAKE_SRC_TIMER3); |
| |
| reg_spin_unlock(); |
| } |
| |
| /** |
| * set wakeup enable. |
| * |
| * now only support ext_int. |
| */ |
| void pcu_irq_wakeup(PCU_INT_INDEX index, int enable) |
| { |
| if ( (index < PCU_EX0_INT) || (index > PCU_EX15_INT) ) |
| return; |
| |
| if(enable) |
| { |
| wake_source_int_for_suspend[0] |= (1<<index); |
| wake_source_int_for_dpidle[0] |= (1<<index); |
| } |
| else |
| { |
| wake_source_int_for_suspend[0] &= ~(1<<index); |
| wake_source_int_for_dpidle[0] &= ~(1<<index); |
| } |
| } |
| |