forked from len0rd/rockbox
i.MX31: Remove long udelay from DVFS interrupt handler
Split the ISR into two parts and alllow quick return from first half. Introduces a uevent() API to have a callback happen in a specified number of microseconds. Right now only one event is supported. Change-Id: Ib1666165be2f6082e5275d64961f083cab104f9f
This commit is contained in:
parent
4877f618d6
commit
0a7d941fb9
6 changed files with 140 additions and 88 deletions
|
@ -25,6 +25,7 @@
|
||||||
#define INT_PRIO_DEFAULT 7
|
#define INT_PRIO_DEFAULT 7
|
||||||
#define INT_PRIO_DVFS (INT_PRIO_DEFAULT+1)
|
#define INT_PRIO_DVFS (INT_PRIO_DEFAULT+1)
|
||||||
#define INT_PRIO_DPTC (INT_PRIO_DEFAULT+1)
|
#define INT_PRIO_DPTC (INT_PRIO_DEFAULT+1)
|
||||||
|
#define INT_PRIO_GPT (INT_PRIO_DEFAULT+1)
|
||||||
#define INT_PRIO_SDMA (INT_PRIO_DEFAULT+2)
|
#define INT_PRIO_SDMA (INT_PRIO_DEFAULT+2)
|
||||||
|
|
||||||
enum INT_TYPE
|
enum INT_TYPE
|
||||||
|
|
|
@ -75,6 +75,7 @@ static uint32_t check_regulator_setting(uint32_t setting)
|
||||||
|
|
||||||
|
|
||||||
/** DVFS **/
|
/** DVFS **/
|
||||||
|
#define DVFS_TVWAIT 100 /* Voltage ramp wait time */
|
||||||
static bool dvfs_running = false; /* Has driver enabled DVFS? */
|
static bool dvfs_running = false; /* Has driver enabled DVFS? */
|
||||||
|
|
||||||
/* Request tracking since boot */
|
/* Request tracking since boot */
|
||||||
|
@ -90,10 +91,9 @@ static inline void updten_wait(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do the actual frequency and DVFS pin change - always call with IRQ masked */
|
/* Do the actual frequency and DVFS pin change - always call with IRQ masked */
|
||||||
static void do_dvfs_update(unsigned int level)
|
static void do_dvfs_update(unsigned long pmcr0, unsigned int level)
|
||||||
{
|
{
|
||||||
const struct dvfs_clock_table_entry *setting = &dvfs_clock_table[level];
|
const struct dvfs_clock_table_entry *setting = &dvfs_clock_table[level];
|
||||||
unsigned long pmcr0 = CCM_PMCR0;
|
|
||||||
|
|
||||||
if (pmcr0 & CCM_PMCR0_DPTEN)
|
if (pmcr0 & CCM_PMCR0_DPTEN)
|
||||||
{
|
{
|
||||||
|
@ -136,12 +136,20 @@ static void do_dvfs_update(unsigned int level)
|
||||||
}
|
}
|
||||||
|
|
||||||
CCM_PMCR0 = pmcr0;
|
CCM_PMCR0 = pmcr0;
|
||||||
/* Note: changes to frequency with ints unmaked seem to cause spurious
|
|
||||||
* DVFS interrupts with value CCM_PMCR0_FSVAI_NO_INT. These aren't
|
/* dvfs_int_voltage_wait_complete must be call to complete this; how that
|
||||||
* supposed to happen. Only do the lengthy delay with them enabled. */
|
is accomplished depends upon whether this was an interrupt with DVFS
|
||||||
enable_irq();
|
enabled or a manual setting of the CPU frequency */
|
||||||
udelay(100); /* Software wait for voltage ramp-up */
|
}
|
||||||
disable_irq();
|
|
||||||
|
/* Perform final DVFS frequency change steps after voltage ramp wait */
|
||||||
|
static void dvfs_int_voltage_wait_complete(void)
|
||||||
|
{
|
||||||
|
const struct dvfs_clock_table_entry *setting =
|
||||||
|
&dvfs_clock_table[dvfs_level];
|
||||||
|
|
||||||
|
unsigned long pmcr0 = CCM_PMCR0;
|
||||||
|
|
||||||
CCM_PDR0 = setting->pdr_val;
|
CCM_PDR0 = setting->pdr_val;
|
||||||
|
|
||||||
if (!(pmcr0 & CCM_PMCR0_DFSUP_POST_DIVIDERS))
|
if (!(pmcr0 & CCM_PMCR0_DFSUP_POST_DIVIDERS))
|
||||||
|
@ -155,6 +163,9 @@ static void do_dvfs_update(unsigned int level)
|
||||||
|
|
||||||
cpu_frequency = ccm_get_mcu_clk();
|
cpu_frequency = ccm_get_mcu_clk();
|
||||||
|
|
||||||
|
if (dvfs_running)
|
||||||
|
CCM_PMCR0 &= ~CCM_PMCR0_FSVAIM;
|
||||||
|
|
||||||
if (pmcr0 & CCM_PMCR0_DPTEN)
|
if (pmcr0 & CCM_PMCR0_DPTEN)
|
||||||
{
|
{
|
||||||
update_dptc_counts();
|
update_dptc_counts();
|
||||||
|
@ -165,24 +176,28 @@ static void do_dvfs_update(unsigned int level)
|
||||||
/* Start DVFS, change the set point and stop it */
|
/* Start DVFS, change the set point and stop it */
|
||||||
static void set_current_dvfs_level(unsigned int level)
|
static void set_current_dvfs_level(unsigned int level)
|
||||||
{
|
{
|
||||||
int oldlevel;
|
|
||||||
|
|
||||||
/* Have to wait at least 3 div3 clocks before enabling after being
|
/* Have to wait at least 3 div3 clocks before enabling after being
|
||||||
* stopped. */
|
* stopped before calling. */
|
||||||
udelay(1500);
|
|
||||||
|
|
||||||
oldlevel = disable_irq_save();
|
|
||||||
CCM_PMCR0 |= CCM_PMCR0_DVFEN;
|
|
||||||
do_dvfs_update(level);
|
|
||||||
restore_irq(oldlevel);
|
|
||||||
|
|
||||||
updten_wait();
|
updten_wait();
|
||||||
|
|
||||||
|
int oldlevel = disable_irq_save();
|
||||||
|
CCM_PMCR0 |= CCM_PMCR0_DVFEN;
|
||||||
|
do_dvfs_update(CCM_PMCR0, level);
|
||||||
|
restore_irq(oldlevel);
|
||||||
|
|
||||||
|
udelay(DVFS_TVWAIT);
|
||||||
|
|
||||||
|
oldlevel = disable_irq_save();
|
||||||
|
dvfs_int_voltage_wait_complete();
|
||||||
|
restore_irq(oldlevel);
|
||||||
|
|
||||||
|
updten_wait();
|
||||||
bitclr32(&CCM_PMCR0, CCM_PMCR0_DVFEN);
|
bitclr32(&CCM_PMCR0, CCM_PMCR0_DVFEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DVFS Interrupt handler */
|
/* Interrupt handler for DVFS */
|
||||||
static void USED_ATTR dvfs_int(void)
|
static void dvfs_int(void)
|
||||||
{
|
{
|
||||||
unsigned long pmcr0 = CCM_PMCR0;
|
unsigned long pmcr0 = CCM_PMCR0;
|
||||||
unsigned long fsvai = pmcr0 & CCM_PMCR0_FSVAI;
|
unsigned long fsvai = pmcr0 & CCM_PMCR0_FSVAI;
|
||||||
|
@ -235,14 +250,19 @@ static void USED_ATTR dvfs_int(void)
|
||||||
return; /* Do nothing. Freq change is not required */
|
return; /* Do nothing. Freq change is not required */
|
||||||
} /* end switch */
|
} /* end switch */
|
||||||
|
|
||||||
do_dvfs_update(level);
|
/* Mask DVFS interrupt until voltage wait is complete */
|
||||||
|
pmcr0 |= CCM_PMCR0_FSVAIM;
|
||||||
|
|
||||||
|
do_dvfs_update(pmcr0, level);
|
||||||
|
|
||||||
|
/* Complete this in a few microseconds from now */
|
||||||
|
uevent(DVFS_TVWAIT, dvfs_int_voltage_wait_complete);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Interrupt vector for DVFS */
|
/* Interrupt vector for DVFS */
|
||||||
static __attribute__((naked, interrupt("IRQ"))) void CCM_DVFS_HANDLER(void)
|
static __attribute__((interrupt("IRQ"))) void CCM_DVFS_HANDLER(void)
|
||||||
{
|
{
|
||||||
/* Audio can glitch with the long udelay if nested IRQ isn't allowed. */
|
dvfs_int();
|
||||||
AVIC_NESTED_NI_CALL(dvfs_int, INT_PRIO_DVFS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize the DVFS hardware */
|
/* Initialize the DVFS hardware */
|
||||||
|
@ -312,6 +332,7 @@ static void INIT_ATTR dvfs_init(void)
|
||||||
dvfs_clock_table[DVFS_LEVEL_DEFAULT].pdr_val);
|
dvfs_clock_table[DVFS_LEVEL_DEFAULT].pdr_val);
|
||||||
|
|
||||||
/* Set initial level and working point. */
|
/* Set initial level and working point. */
|
||||||
|
udelay(1500);
|
||||||
set_current_dvfs_level(DVFS_LEVEL_DEFAULT);
|
set_current_dvfs_level(DVFS_LEVEL_DEFAULT);
|
||||||
|
|
||||||
logf("DVFS: Initialized");
|
logf("DVFS: Initialized");
|
||||||
|
@ -537,26 +558,16 @@ void dvfs_stop(void)
|
||||||
if (!dvfs_running)
|
if (!dvfs_running)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
uevent_cancel();
|
||||||
|
|
||||||
/* Mask DVFS interrupts. */
|
/* Mask DVFS interrupts. */
|
||||||
avic_disable_int(INT_CCM_DVFS);
|
avic_disable_int(INT_CCM_DVFS);
|
||||||
bitset32(&CCM_PMCR0, CCM_PMCR0_FSVAIM | CCM_PMCR0_LBMI);
|
bitset32(&CCM_PMCR0, CCM_PMCR0_FSVAIM | CCM_PMCR0_LBMI);
|
||||||
|
|
||||||
if (((CCM_PMCR0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS) !=
|
|
||||||
DVFS_LEVEL_DEFAULT)
|
|
||||||
{
|
|
||||||
int oldlevel;
|
|
||||||
/* Set default frequency level */
|
|
||||||
updten_wait();
|
|
||||||
oldlevel = disable_irq_save();
|
|
||||||
do_dvfs_update(DVFS_LEVEL_DEFAULT);
|
|
||||||
restore_irq(oldlevel);
|
|
||||||
updten_wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Disable DVFS. */
|
|
||||||
bitclr32(&CCM_PMCR0, CCM_PMCR0_DVFEN);
|
|
||||||
dvfs_running = false;
|
dvfs_running = false;
|
||||||
|
|
||||||
|
/* Set default frequency level */
|
||||||
|
set_current_dvfs_level(DVFS_LEVEL_DEFAULT);
|
||||||
logf("DVFS: stopped");
|
logf("DVFS: stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,6 +586,7 @@ void dvfs_set_level(unsigned int level)
|
||||||
level == ((CCM_PMCR0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS))
|
level == ((CCM_PMCR0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
udelay(1500);
|
||||||
set_current_dvfs_level(level);
|
set_current_dvfs_level(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,9 +86,3 @@ void tick_stop(void)
|
||||||
EPITSR1 = EPITSR_OCIF; /* Clear pending */
|
EPITSR1 = EPITSR_OCIF; /* Clear pending */
|
||||||
ccm_module_clock_gating(CG_EPIT1, CGM_OFF); /* Turn off module clock */
|
ccm_module_clock_gating(CG_EPIT1, CGM_OFF); /* Turn off module clock */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void kernel_audio_locking(bool locking)
|
|
||||||
{
|
|
||||||
dvfs_int_mask(locking);
|
|
||||||
}
|
|
|
@ -114,22 +114,12 @@ static void play_dma_callback(void)
|
||||||
|
|
||||||
void pcm_play_lock(void)
|
void pcm_play_lock(void)
|
||||||
{
|
{
|
||||||
/* Need to prevent DVFS from causing interrupt priority inversion if audio
|
|
||||||
* is locked and a DVFS interrupt fires, blocking reenabling of audio by a
|
|
||||||
* low-priority mode for at least the duration of the lengthy DVFS routine.
|
|
||||||
* Not really an issue with state changes but lockout when playing.
|
|
||||||
*
|
|
||||||
* Keep direct use of DVFS code away from here though. This could provide
|
|
||||||
* more services in the future anyway. */
|
|
||||||
kernel_audio_locking(true);
|
|
||||||
++dma_play_data.locked;
|
++dma_play_data.locked;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pcm_play_unlock(void)
|
void pcm_play_unlock(void)
|
||||||
{
|
{
|
||||||
if (--dma_play_data.locked == 0)
|
if (--dma_play_data.locked == 0 && dma_play_data.state != 0)
|
||||||
{
|
|
||||||
if (dma_play_data.state != 0)
|
|
||||||
{
|
{
|
||||||
int oldstatus = disable_irq_save();
|
int oldstatus = disable_irq_save();
|
||||||
int pending = dma_play_data.callback_pending;
|
int pending = dma_play_data.callback_pending;
|
||||||
|
@ -139,9 +129,6 @@ void pcm_play_unlock(void)
|
||||||
if (pending != 0)
|
if (pending != 0)
|
||||||
play_dma_callback();
|
play_dma_callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel_audio_locking(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void pcm_dma_apply_settings(void)
|
void pcm_dma_apply_settings(void)
|
||||||
|
@ -368,15 +355,12 @@ static void rec_dma_callback(void)
|
||||||
|
|
||||||
void pcm_rec_lock(void)
|
void pcm_rec_lock(void)
|
||||||
{
|
{
|
||||||
kernel_audio_locking(true);
|
|
||||||
++dma_rec_data.locked;
|
++dma_rec_data.locked;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pcm_rec_unlock(void)
|
void pcm_rec_unlock(void)
|
||||||
{
|
{
|
||||||
if (--dma_rec_data.locked == 0)
|
if (--dma_rec_data.locked == 0 && dma_rec_data.state != 0)
|
||||||
{
|
|
||||||
if (dma_rec_data.state != 0)
|
|
||||||
{
|
{
|
||||||
int oldstatus = disable_irq_save();
|
int oldstatus = disable_irq_save();
|
||||||
int pending = dma_rec_data.callback_pending;
|
int pending = dma_rec_data.callback_pending;
|
||||||
|
@ -386,9 +370,6 @@ void pcm_rec_unlock(void)
|
||||||
if (pending != 0)
|
if (pending != 0)
|
||||||
rec_dma_callback();
|
rec_dma_callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel_audio_locking(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void pcm_rec_dma_stop(void)
|
void pcm_rec_dma_stop(void)
|
||||||
|
|
|
@ -84,7 +84,62 @@ void watchdog_service(void)
|
||||||
WDOG_WSR = 0xaaaa;
|
WDOG_WSR = 0xaaaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GPT timer routines - basis for udelay **/
|
|
||||||
|
/** uevent APIs **/
|
||||||
|
|
||||||
|
static void (*ucallback)(void) = NULL; /* uevent callback */
|
||||||
|
|
||||||
|
static void cancel_uevent(void)
|
||||||
|
{
|
||||||
|
GPTSR = GPTSR_OF1;
|
||||||
|
GPTIR &= ~GPTIR_OF1IE;
|
||||||
|
ucallback = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __attribute__((interrupt("IRQ"))) GPT_HANDLER(void)
|
||||||
|
{
|
||||||
|
uevent_cb_type cb = ucallback;
|
||||||
|
cancel_uevent();
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uevent(unsigned long usecs, uevent_cb_type callback)
|
||||||
|
{
|
||||||
|
if (!callback || ucallback)
|
||||||
|
return; /* Is busy */
|
||||||
|
|
||||||
|
unsigned long status = disable_interrupt_save(IRQ_FIQ_STATUS);
|
||||||
|
|
||||||
|
ucallback = callback;
|
||||||
|
|
||||||
|
for (int i = 0; i < 1; i++)
|
||||||
|
{
|
||||||
|
unsigned long utime = GPTCNT;
|
||||||
|
unsigned long time = utime + usecs + 1;
|
||||||
|
|
||||||
|
GPTOCR1 = time;
|
||||||
|
GPTSR = GPTSR_OF1;
|
||||||
|
GPTIR |= GPTIR_OF1IE;
|
||||||
|
|
||||||
|
if (TIME_BEFORE(GPTCNT, time))
|
||||||
|
break; /* Didn't miss it */
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_interrupt(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uevent_cancel(void)
|
||||||
|
{
|
||||||
|
unsigned long status = disable_interrupt_save(IRQ_FIQ_STATUS);
|
||||||
|
|
||||||
|
if (ucallback)
|
||||||
|
cancel_uevent();
|
||||||
|
|
||||||
|
restore_interrupt(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** GPT timer routines - basis for udelay/uevent **/
|
||||||
|
|
||||||
/* Start the general-purpose timer (1MHz) */
|
/* Start the general-purpose timer (1MHz) */
|
||||||
void gpt_start(void)
|
void gpt_start(void)
|
||||||
|
@ -102,13 +157,20 @@ void gpt_start(void)
|
||||||
*/
|
*/
|
||||||
GPTCR = GPTCR_FRR | GPTCR_WAITEN | GPTCR_CLKSRC_IPG_CLK;
|
GPTCR = GPTCR_FRR | GPTCR_WAITEN | GPTCR_CLKSRC_IPG_CLK;
|
||||||
GPTPR = ipg_mhz - 1;
|
GPTPR = ipg_mhz - 1;
|
||||||
|
GPTSR = GPTSR_OF1;
|
||||||
GPTCR |= GPTCR_EN;
|
GPTCR |= GPTCR_EN;
|
||||||
|
|
||||||
|
avic_enable_int(INT_GPT, INT_TYPE_IRQ, INT_PRIO_GPT, GPT_HANDLER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stop the general-purpose timer */
|
/* Stop the general-purpose timer */
|
||||||
void gpt_stop(void)
|
void gpt_stop(void)
|
||||||
{
|
{
|
||||||
|
unsigned long status = disable_interrupt_save(IRQ_FIQ_STATUS);
|
||||||
|
avic_disable_int(INT_GPT);
|
||||||
|
cancel_uevent();
|
||||||
GPTCR &= ~GPTCR_EN;
|
GPTCR &= ~GPTCR_EN;
|
||||||
|
restore_interrupt(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
int system_memory_guard(int newmode)
|
int system_memory_guard(int newmode)
|
||||||
|
|
|
@ -44,6 +44,11 @@ static inline unsigned long usec_timer(void)
|
||||||
return GPTCNT;
|
return GPTCNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fire an event usecs microseconds from now */
|
||||||
|
typedef void (* uevent_cb_type)(void);
|
||||||
|
void uevent(unsigned long usecs, uevent_cb_type callback);
|
||||||
|
void uevent_cancel(void);
|
||||||
|
|
||||||
void watchdog_init(unsigned int half_seconds);
|
void watchdog_init(unsigned int half_seconds);
|
||||||
void watchdog_service(void);
|
void watchdog_service(void);
|
||||||
|
|
||||||
|
@ -58,9 +63,6 @@ void tick_stop(void);
|
||||||
void kernel_device_init(void);
|
void kernel_device_init(void);
|
||||||
void system_halt(void);
|
void system_halt(void);
|
||||||
|
|
||||||
/* Handle some audio lockout related tasks */
|
|
||||||
void kernel_audio_locking(bool locking);
|
|
||||||
|
|
||||||
#define KDEV_INIT
|
#define KDEV_INIT
|
||||||
|
|
||||||
struct ARM_REGS {
|
struct ARM_REGS {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue