From 0a7d941fb9e8dba5934255e37ceaa9063b38ed25 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sat, 11 May 2013 19:59:01 -0400 Subject: 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 --- firmware/target/arm/imx31/avic-imx31.h | 1 + firmware/target/arm/imx31/dvfs_dptc-imx31.c | 90 ++++++++++++---------- .../arm/imx31/gigabeat-s/kernel-gigabeat-s.c | 8 +- .../target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c | 53 ++++--------- .../arm/imx31/gigabeat-s/system-gigabeat-s.c | 68 +++++++++++++++- .../target/arm/imx31/gigabeat-s/system-target.h | 8 +- 6 files changed, 140 insertions(+), 88 deletions(-) (limited to 'firmware/target/arm/imx31') diff --git a/firmware/target/arm/imx31/avic-imx31.h b/firmware/target/arm/imx31/avic-imx31.h index 7398c8464c..50b04635ee 100644 --- a/firmware/target/arm/imx31/avic-imx31.h +++ b/firmware/target/arm/imx31/avic-imx31.h @@ -25,6 +25,7 @@ #define INT_PRIO_DEFAULT 7 #define INT_PRIO_DVFS (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) enum INT_TYPE diff --git a/firmware/target/arm/imx31/dvfs_dptc-imx31.c b/firmware/target/arm/imx31/dvfs_dptc-imx31.c index 02955a5aa4..3f9e9a3dd8 100644 --- a/firmware/target/arm/imx31/dvfs_dptc-imx31.c +++ b/firmware/target/arm/imx31/dvfs_dptc-imx31.c @@ -75,6 +75,7 @@ static uint32_t check_regulator_setting(uint32_t setting) /** DVFS **/ +#define DVFS_TVWAIT 100 /* Voltage ramp wait time */ static bool dvfs_running = false; /* Has driver enabled DVFS? */ /* 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 */ -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]; - unsigned long pmcr0 = CCM_PMCR0; if (pmcr0 & CCM_PMCR0_DPTEN) { @@ -106,7 +106,7 @@ static void do_dvfs_update(unsigned int level) pmcr0 &= ~CCM_PMCR0_VSCNT; if (level < ((pmcr0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS)) - { + { pmcr0 |= CCM_PMCR0_UDSC; /* Up scaling, increase */ pmcr0 |= setting->vscnt << CCM_PMCR0_VSCNT_POS; } @@ -136,12 +136,20 @@ static void do_dvfs_update(unsigned int level) } 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 - * supposed to happen. Only do the lengthy delay with them enabled. */ - enable_irq(); - udelay(100); /* Software wait for voltage ramp-up */ - disable_irq(); + + /* dvfs_int_voltage_wait_complete must be call to complete this; how that + is accomplished depends upon whether this was an interrupt with DVFS + enabled or a manual setting of the CPU frequency */ +} + +/* 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; 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(); + if (dvfs_running) + CCM_PMCR0 &= ~CCM_PMCR0_FSVAIM; + if (pmcr0 & CCM_PMCR0_DPTEN) { update_dptc_counts(); @@ -165,24 +176,28 @@ static void do_dvfs_update(unsigned int level) /* Start DVFS, change the set point and stop it */ static void set_current_dvfs_level(unsigned int level) { - int oldlevel; - /* Have to wait at least 3 div3 clocks before enabling after being - * stopped. */ - udelay(1500); + * stopped before calling. */ - oldlevel = disable_irq_save(); + updten_wait(); + + int oldlevel = disable_irq_save(); CCM_PMCR0 |= CCM_PMCR0_DVFEN; - do_dvfs_update(level); + do_dvfs_update(CCM_PMCR0, level); restore_irq(oldlevel); - updten_wait(); + udelay(DVFS_TVWAIT); + + oldlevel = disable_irq_save(); + dvfs_int_voltage_wait_complete(); + restore_irq(oldlevel); + updten_wait(); bitclr32(&CCM_PMCR0, CCM_PMCR0_DVFEN); } -/* DVFS Interrupt handler */ -static void USED_ATTR dvfs_int(void) +/* Interrupt handler for DVFS */ +static void dvfs_int(void) { unsigned long pmcr0 = CCM_PMCR0; 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 */ } /* 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 */ -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. */ - AVIC_NESTED_NI_CALL(dvfs_int, INT_PRIO_DVFS); + dvfs_int(); } /* Initialize the DVFS hardware */ @@ -273,7 +293,7 @@ static void INIT_ATTR dvfs_init(void) /* GP load bits disabled */ bitclr32(&CCM_PMCR1, 0xf); - + /* Initialize DVFS signal weights and detection modes. */ int i; for (i = 0; i < 16; i++) @@ -312,6 +332,7 @@ static void INIT_ATTR dvfs_init(void) dvfs_clock_table[DVFS_LEVEL_DEFAULT].pdr_val); /* Set initial level and working point. */ + udelay(1500); set_current_dvfs_level(DVFS_LEVEL_DEFAULT); logf("DVFS: Initialized"); @@ -537,33 +558,23 @@ void dvfs_stop(void) if (!dvfs_running) return; + uevent_cancel(); + /* Mask DVFS interrupts. */ avic_disable_int(INT_CCM_DVFS); 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; + /* Set default frequency level */ + set_current_dvfs_level(DVFS_LEVEL_DEFAULT); logf("DVFS: stopped"); } /* Is DVFS enabled? */ bool dvfs_enabled(void) { - return dvfs_running; + return dvfs_running; } /* If DVFS is disabled, set the level explicitly */ @@ -575,6 +586,7 @@ void dvfs_set_level(unsigned int level) level == ((CCM_PMCR0 & CCM_PMCR0_DVSUP) >> CCM_PMCR0_DVSUP_POS)) return; + udelay(1500); set_current_dvfs_level(level); } @@ -778,7 +790,7 @@ void dptc_start(void) enable_dptc(); restore_irq(oldlevel); - avic_enable_int(INT_CCM_CLK, INT_TYPE_IRQ, INT_PRIO_DPTC, + avic_enable_int(INT_CCM_CLK, INT_TYPE_IRQ, INT_PRIO_DPTC, CCM_CLK_HANDLER); logf("DPTC: started"); diff --git a/firmware/target/arm/imx31/gigabeat-s/kernel-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/kernel-gigabeat-s.c index f30287d4e9..491bd52a66 100644 --- a/firmware/target/arm/imx31/gigabeat-s/kernel-gigabeat-s.c +++ b/firmware/target/arm/imx31/gigabeat-s/kernel-gigabeat-s.c @@ -56,7 +56,7 @@ void INIT_ATTR tick_start(unsigned int interval_in_ms) EPITCR1 = EPITCR_CLKSRC_IPG_CLK | EPITCR_WAITEN | EPITCR_IOVW | ((2640-1) << EPITCR_PRESCALER_POS) | EPITCR_RLD | EPITCR_OCIEN | EPITCR_ENMOD; - + EPITLR1 = interval_in_ms*25; /* Count down from interval */ EPITCMPR1 = 0; /* Event when counter reaches 0 */ EPITSR1 = EPITSR_OCIF; /* Clear any pending interrupt */ @@ -86,9 +86,3 @@ void tick_stop(void) EPITSR1 = EPITSR_OCIF; /* Clear pending */ ccm_module_clock_gating(CG_EPIT1, CGM_OFF); /* Turn off module clock */ } - - -void kernel_audio_locking(bool locking) -{ - dvfs_int_mask(locking); -} \ No newline at end of file diff --git a/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c index 76789a7dbd..7304bdcff3 100644 --- a/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c +++ b/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c @@ -104,7 +104,7 @@ static void play_dma_callback(void) PCM_DMAST_ERR_DMA : PCM_DMAST_OK; const void *addr; size_t size; - + if (pcm_play_dma_complete_callback(status, &addr, &size)) { play_start_dma(addr, size); @@ -114,33 +114,20 @@ static void play_dma_callback(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; } 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 pending = dma_play_data.callback_pending; - dma_play_data.callback_pending = 0; - restore_irq(oldstatus); - - if (pending != 0) - play_dma_callback(); - } - - kernel_audio_locking(false); + int oldstatus = disable_irq_save(); + int pending = dma_play_data.callback_pending; + dma_play_data.callback_pending = 0; + restore_irq(oldstatus); + + if (pending != 0) + play_dma_callback(); } } @@ -368,26 +355,20 @@ static void rec_dma_callback(void) void pcm_rec_lock(void) { - kernel_audio_locking(true); ++dma_rec_data.locked; } 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 pending = dma_rec_data.callback_pending; - dma_rec_data.callback_pending = 0; - restore_irq(oldstatus); - - if (pending != 0) - rec_dma_callback(); - } - - kernel_audio_locking(false); + int oldstatus = disable_irq_save(); + int pending = dma_rec_data.callback_pending; + dma_rec_data.callback_pending = 0; + restore_irq(oldstatus); + + if (pending != 0) + rec_dma_callback(); } } diff --git a/firmware/target/arm/imx31/gigabeat-s/system-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/system-gigabeat-s.c index 5c70411c30..545905d4c2 100644 --- a/firmware/target/arm/imx31/gigabeat-s/system-gigabeat-s.c +++ b/firmware/target/arm/imx31/gigabeat-s/system-gigabeat-s.c @@ -84,7 +84,62 @@ void watchdog_service(void) 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) */ void gpt_start(void) @@ -102,13 +157,20 @@ void gpt_start(void) */ GPTCR = GPTCR_FRR | GPTCR_WAITEN | GPTCR_CLKSRC_IPG_CLK; GPTPR = ipg_mhz - 1; + GPTSR = GPTSR_OF1; GPTCR |= GPTCR_EN; + + avic_enable_int(INT_GPT, INT_TYPE_IRQ, INT_PRIO_GPT, GPT_HANDLER); } /* Stop the general-purpose timer */ void gpt_stop(void) { + unsigned long status = disable_interrupt_save(IRQ_FIQ_STATUS); + avic_disable_int(INT_GPT); + cancel_uevent(); GPTCR &= ~GPTCR_EN; + restore_interrupt(status); } int system_memory_guard(int newmode) @@ -258,7 +320,7 @@ void dumpregs(void) "mov %3,r12": "=r"(regs.r8),"=r"(regs.r9), "=r"(regs.r10),"=r"(regs.r11):); - + asm volatile ("mov %0,r12\n\t" "mov %1,sp\n\t" "mov %2,lr\n\t" @@ -280,7 +342,7 @@ void dumpregs(void) DEBUGF("R8=0x%x\tR9=0x%x\tR10=0x%x\tR11=0x%x\n",regs.r8,regs.r9,regs.r10,regs.r11); DEBUGF("R12=0x%x\tSP=0x%x\tLR=0x%x\tPC=0x%x\n",regs.r12,regs.sp,regs.lr,regs.pc); //DEBUGF("CPSR=0x%x\t\n",regs.cpsr); - + } #ifdef HAVE_ADJUSTABLE_CPU_FREQ diff --git a/firmware/target/arm/imx31/gigabeat-s/system-target.h b/firmware/target/arm/imx31/gigabeat-s/system-target.h index 44a97c7ca9..b12ca13bb5 100644 --- a/firmware/target/arm/imx31/gigabeat-s/system-target.h +++ b/firmware/target/arm/imx31/gigabeat-s/system-target.h @@ -44,6 +44,11 @@ static inline unsigned long usec_timer(void) 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_service(void); @@ -58,9 +63,6 @@ void tick_stop(void); void kernel_device_init(void); void system_halt(void); -/* Handle some audio lockout related tasks */ -void kernel_audio_locking(bool locking); - #define KDEV_INIT struct ARM_REGS { -- cgit