| /* |
| * linux/arch/arm/mach-zx297520v2/pcu.c |
| * |
| * Copyright (C) 2015 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/board.h> |
| #include <mach/iomap.h> |
| #include <mach/pcu.h> |
| #include <mach/pcu_def.h> |
| #include <mach/irqs.h> |
| #include <mach/spinlock.h> |
| |
| |
| /** |
| * helper function |
| * |
| * get index of pcu_info struct. |
| */ |
| unsigned int pcu_get_index(PCU_INT_INDEX pcu_index) |
| { |
| unsigned int i = 0; |
| |
| for(i=0; i<ARRAY_SIZE(pcu_info); i++) |
| { |
| if(pcu_index == pcu_info[i].index) |
| return i; |
| } |
| |
| return 0xff; |
| } |
| |
| /* |
| * return external interrupt number from ex8-ex15, |
| * return value is 0-7 |
| */ |
| unsigned int pcu_get_8in1_int_source(void) |
| { |
| unsigned int vector_8in1 = 0; |
| |
| vector_8in1 = zx_read_reg(EX_INT_VECTOR_REG); |
| |
| return (vector_8in1&0x7); |
| } |
| |
| |
| /*external int 8-15 need extra clear*/ |
| static void pcu_int_clear_8in1(PCU_INT_INDEX index) |
| { |
| 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_get_8in1_int_source(); |
| if (index != (vector + PCU_EX8_INT) ) |
| return; |
| |
| reg_spin_lock(); |
| zx_write_reg(EX_INT_TOP_CLEAR_REG, 0x1); |
| reg_spin_unlock(); |
| } |
| } |
| |
| /*only pulse int need be clear*/ |
| void pcu_int_clear(PCU_INT_INDEX pcu_index) |
| { |
| unsigned int index; |
| |
| index = pcu_get_index(pcu_index); |
| if(index == 0xff) |
| return; |
| |
| reg_spin_lock(); |
| zx_set_reg(pcu_info[index].int_clr_reg.reg_addr, |
| (1<<pcu_info[index].int_clr_reg.reg_bit_offset)); |
| reg_spin_unlock(); |
| |
| pcu_int_clear_8in1(pcu_index); |
| } |
| EXPORT_SYMBOL(pcu_int_clear); |
| |
| /** |
| * helper function |
| * |
| * get pcu level/pol configuration. |
| */ |
| static void pcu_split_int_type(unsigned int type, unsigned int *level, unsigned int *pol) |
| { |
| 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(); |
| } |
| } |
| |
| /* |
| * set pcu int type, |
| * |
| */ |
| void pcu_int_set_type(PCU_INT_INDEX pcu_index,unsigned int type) |
| { |
| unsigned int level; |
| unsigned int pol; |
| unsigned int index; |
| |
| index = pcu_get_index(pcu_index); |
| if(index == 0xff) |
| return; |
| |
| pcu_split_int_type(type, &level, &pol); |
| |
| reg_spin_lock(); |
| /* set level*/ |
| zx_wrie_bit(pcu_info[index].int_type_reg.reg_addr, |
| pcu_info[index].int_type_reg.reg_bit_offset, |
| level); |
| |
| /* set polarity */ |
| zx_wrie_bit(pcu_info[index].int_pol_reg.reg_addr, |
| pcu_info[index].int_pol_reg.reg_bit_offset, |
| pol); |
| reg_spin_unlock(); |
| |
| pcu_int_clear(pcu_index); |
| } |
| EXPORT_SYMBOL(pcu_int_set_type); |
| |
| /** |
| * init pcu before open irq when system start. |
| * |
| */ |
| void __init pcu_init(void) |
| { |
| int i = 0; |
| |
| pr_info("[PCU] PCU_INIT begin.\n"); |
| |
| /*set default int type/pol, and clear fake int*/ |
| for(i=0; i<ARRAY_SIZE(pcu_info); i++) |
| { |
| if(!irq_type_valid(pcu_info[i].irq_type)) |
| continue; |
| |
| pcu_int_set_type(pcu_info[i].index, pcu_info[i].irq_type); |
| } |
| |
| pm_pcu_init(); |
| |
| pr_info("[PCU] PCU_INIT end.\n"); |
| } |
| |
| /* low power function */ |
| extern unsigned int pm_get_wakesource(void); |
| void pm_pcu_init(void) |
| { |
| zx_clr_reg(ARM_AP_CONFIG_REG, PCU_MODE_MASK); |
| zx_set_reg(ARM_AP_CONFIG_REG, PCU_DPLL_SEL|PCU_L2_CLK_GATE|PCU_PLL_OFF); |
| |
| zx_write_reg(AP_INT_WAKE_DIS_REG, ~(pm_get_wakesource())); |
| } |
| |
| /** |
| * config pcu before poweroff |
| * |
| */ |
| void pm_set_pcu_poweroff(u32 sleep_time) |
| { |
| zx_set_reg(ARM_AP_CONFIG_REG, PCU_POWEROFF_MODE); |
| zx_write_reg(ARM_AP_SLEEP_TIME_REG, sleep_time); |
| } |
| |
| /** |
| * config pcu before sleep |
| * |
| */ |
| void pm_set_pcu_sleep(u32 sleep_time) |
| { |
| zx_set_reg(ARM_AP_CONFIG_REG, PCU_SLEEP_MODE); |
| zx_write_reg(ARM_AP_SLEEP_TIME_REG, sleep_time); |
| } |
| |
| /** |
| * clear pcu sleep mode. |
| * |
| */ |
| void pm_clear_pcu(void) |
| { |
| zx_clr_reg(ARM_AP_CONFIG_REG, PCU_MODE_MASK); |
| } |
| |
| |
| /** |
| * get wakeup setting. |
| * |
| */ |
| unsigned int pcu_get_wakeup_setting(void) |
| { |
| return zx_read_reg(AP_INT_WAKE_DIS_REG); |
| } |
| |
| |
| /** |
| * set wakeup enable. |
| * |
| * |
| */ |
| void pcu_irq_wakeup(PCU_INT_INDEX index, int enable) |
| { |
| if ( (index < PCU_AP_TIMER1_INT) || (index > PCU_GMAC_INT) ) |
| return; |
| |
| if(enable) |
| { |
| zx_clr_reg(AP_INT_WAKE_DIS_REG, (1U << index)); |
| } |
| else |
| { |
| zx_set_reg(AP_INT_WAKE_DIS_REG, (1U << index)); |
| } |
| } |
| |
| /** |
| * set wakeup enable by gic. |
| * |
| * |
| */ |
| int pcu_set_irq_wake(unsigned gic, unsigned on) |
| { |
| PCU_INT_INDEX pcu_index = PCU_UNKNOWN_INT; |
| |
| pcu_index = pcu_get_index_by_gic(gic); |
| if(pcu_index == PCU_UNKNOWN_INT) |
| return -EINVAL; |
| |
| pcu_irq_wakeup(pcu_index, on); |
| |
| return 0; |
| } |
| |
| /** |
| * set pcu type by gic. |
| * |
| * |
| */ |
| int pcu_set_irq_type(unsigned gic, unsigned int type) |
| { |
| PCU_INT_INDEX pcu_index = PCU_UNKNOWN_INT; |
| |
| pcu_index = pcu_get_index_by_gic(gic); |
| if(pcu_index == PCU_UNKNOWN_INT) |
| return -EINVAL; |
| |
| pcu_int_set_type(pcu_index, type); |
| |
| return 0; |
| } |
| |
| /** |
| * clr pcu int by gic. |
| * |
| * |
| */ |
| int pcu_clr_irq_pending(unsigned gic) |
| { |
| PCU_INT_INDEX pcu_index = PCU_UNKNOWN_INT; |
| |
| pcu_index = pcu_get_index_by_gic(gic); |
| if(pcu_index == PCU_UNKNOWN_INT) |
| return -EINVAL; |
| |
| pcu_int_clear(pcu_index); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(pcu_clr_irq_pending); |
| |
| #ifdef CONFIG_PM |
| extern void pm_printk(const char *fmt, ...); |
| #define pm_ram_log(fmt, args...) \ |
| { \ |
| printk(KERN_INFO "[SLP] " fmt, ##args); \ |
| pm_printk("[SLP] " fmt, ##args); \ |
| } |
| |
| extern unsigned int pm_get_sleep_flag(void); |
| |
| void pm_get_wake_cause(void) |
| { |
| |
| if(pm_get_sleep_flag()!= true) |
| return; |
| |
| unsigned int int_status[2]; |
| int i = 0; |
| int index_found = 0xff; |
| unsigned int pcu_wake_setting; |
| |
| /* when wake up, the level is high&the value is 0*/ |
| int_status[0] = zx_read_reg(PCU_INT_READOUT_REG1); |
| int_status[1] = zx_read_reg(PCU_INT_READOUT_REG2); |
| |
| pcu_wake_setting = pcu_get_wakeup_setting(); |
| |
| for(i=0; i<ARRAY_SIZE(pcu_info); i++) |
| { |
| if(pcu_wake_setting&(1<<pcu_info[i].index)) |
| continue; |
| |
| if(int_status[pcu_info[i].status_index/32]&(1<<(pcu_info[i].status_index%32))) |
| continue; |
| |
| index_found = i; |
| break; |
| } |
| |
| if(index_found != 0xff) |
| { |
| pm_ram_log(" wake: %d [%s]\n", pcu_info[index_found].gic_index, pcu_info[index_found].int_name); |
| pm_ram_log(" pcu int status:%x %x\n",int_status[0], int_status[1]); |
| if(pcu_info[index_found].gic_index == ICP_PS2AP_INT) |
| { |
| volatile unsigned int *low_word = ZX29_ICP_APPS_REG+0x8; |
| volatile unsigned int *high_word = ZX29_ICP_APPS_REG+0xC; |
| pm_ram_log(" icp status:%x %x\n",*low_word, *high_word); |
| } |
| } |
| else |
| { |
| pm_ram_log(" wake abnormal\n"); |
| pm_ram_log(" pcu int status:%x %x\n",int_status[0], int_status[1]); |
| } |
| } |
| #endif |
| |