blob: 4745101fe29a112f6de6e43f17d503d92889eef0 [file] [log] [blame]
/*
* 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