summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2013-05-11 19:59:01 -0400
committerMichael Sevakis <jethead71@rockbox.org>2013-05-11 21:09:01 -0400
commit0a7d941fb9e8dba5934255e37ceaa9063b38ed25 (patch)
tree5e972b74abfd73c662430672cd57997c4a8056d0
parent4877f618d664f53694132b91fc7712844566bfbb (diff)
downloadrockbox-0a7d941.tar.gz
rockbox-0a7d941.tar.bz2
rockbox-0a7d941.zip
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
-rw-r--r--firmware/target/arm/imx31/avic-imx31.h1
-rw-r--r--firmware/target/arm/imx31/dvfs_dptc-imx31.c90
-rw-r--r--firmware/target/arm/imx31/gigabeat-s/kernel-gigabeat-s.c8
-rw-r--r--firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c53
-rw-r--r--firmware/target/arm/imx31/gigabeat-s/system-gigabeat-s.c68
-rw-r--r--firmware/target/arm/imx31/gigabeat-s/system-target.h8
6 files changed, 140 insertions, 88 deletions
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 {