From 5df4405317890cc4a84edcfe827a765b52a712c9 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sat, 3 May 2008 15:14:52 +0000 Subject: Gigabeat S: Man it's so loud in here. We have SOUND! Someone please make keymaps consistent; it's rather messy atm. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17327 a1c6a512-1295-4272-9138-f99709370657 --- firmware/target/arm/imx31/debug-imx31.c | 121 ++++++++++- firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c | 228 +++++++++++++++++++-- .../target/arm/imx31/gigabeat-s/system-imx31.c | 10 + .../target/arm/imx31/gigabeat-s/system-target.h | 5 + .../target/arm/imx31/gigabeat-s/wmcodec-imx31.c | 36 +++- 5 files changed, 369 insertions(+), 31 deletions(-) (limited to 'firmware/target/arm/imx31') diff --git a/firmware/target/arm/imx31/debug-imx31.c b/firmware/target/arm/imx31/debug-imx31.c index 94df64b6d7..9fe2eae584 100644 --- a/firmware/target/arm/imx31/debug-imx31.c +++ b/firmware/target/arm/imx31/debug-imx31.c @@ -27,9 +27,128 @@ #include "mc13783.h" #include "adc.h" +#define CONFIG_CLK32_FREQ 32768 +#define CONFIG_HCLK_FREQ 27000000 + +/* Return PLL frequency in HZ */ +static unsigned int decode_pll(unsigned int reg, + unsigned int infreq) +{ + uint64_t mfi = (reg >> 10) & 0xf; + uint64_t mfn = reg & 0x3ff; + uint64_t mfd = ((reg >> 16) & 0x3ff) + 1; + uint64_t pd = ((reg >> 26) & 0xf) + 1; + + mfi = mfi <= 5 ? 5 : mfi; + + return 2*infreq*(mfi * mfd + mfn) / (mfd * pd); +} + +/* Get the PLL reference clock frequency */ +static unsigned int get_pll_ref_clk_freq(void) +{ + if ((CLKCTL_CCMR & (3 << 1)) == (1 << 1)) + return CONFIG_CLK32_FREQ * 1024; + else + return CONFIG_HCLK_FREQ; +} + bool __dbg_hw_info(void) { - return false; + char buf[50]; + int line; + unsigned int pllref; + unsigned int mcu_pllfreq, ser_pllfreq, usb_pllfreq; + uint32_t mpctl, spctl, upctl; + unsigned int freq; + uint32_t regval; + + lcd_setmargins(0, 0); + lcd_clear_display(); + lcd_setfont(FONT_SYSFIXED); + + while (1) + { + line = 0; + mpctl = CLKCTL_MPCTL; + spctl = CLKCTL_SPCTL; + upctl = CLKCTL_UPCTL; + + pllref = get_pll_ref_clk_freq(); + + mcu_pllfreq = decode_pll(mpctl, pllref); + ser_pllfreq = decode_pll(spctl, pllref); + usb_pllfreq = decode_pll(upctl, pllref); + + snprintf(buf, sizeof (buf), "pll_ref_clk: %u", pllref); + lcd_puts(0, line++, buf); line++; + + /* MCU clock domain */ + snprintf(buf, sizeof (buf), "MPCTL: %08lX", mpctl); + lcd_puts(0, line++, buf); + + snprintf(buf, sizeof (buf), " mpl_dpdgck_clk: %u", mcu_pllfreq); + lcd_puts(0, line++, buf); line++; + + regval = CLKCTL_PDR0; + snprintf(buf, sizeof (buf), " PDR0: %08lX", regval); + lcd_puts(0, line++, buf); + + freq = mcu_pllfreq / (((regval & 0x7) + 1)); + snprintf(buf, sizeof (buf), " mcu_clk: %u", freq); + lcd_puts(0, line++, buf); + + freq = mcu_pllfreq / (((regval >> 11) & 0x7) + 1); + snprintf(buf, sizeof (buf), " hsp_clk: %u", freq); + lcd_puts(0, line++, buf); + + freq = mcu_pllfreq / (((regval >> 3) & 0x7) + 1); + snprintf(buf, sizeof (buf), " hclk_clk: %u", freq); + lcd_puts(0, line++, buf); + + snprintf(buf, sizeof (buf), " ipg_clk: %u", + freq / (unsigned)(((regval >> 6) & 0x3) + 1)); + lcd_puts(0, line++, buf); + + snprintf(buf, sizeof (buf), " nfc_clk: %u", + freq / (unsigned)(((regval >> 8) & 0x7) + 1)); + lcd_puts(0, line++, buf); + + line++; + + /* Serial clock domain */ + snprintf(buf, sizeof (buf), "SPCTL: %08lX", spctl); + lcd_puts(0, line++, buf); + snprintf(buf, sizeof (buf), " spl_dpdgck_clk: %u", ser_pllfreq); + lcd_puts(0, line++, buf); + + line++; + + /* USB clock domain */ + snprintf(buf, sizeof (buf), "UPCTL: %08lX", upctl); + lcd_puts(0, line++, buf); + + snprintf(buf, sizeof (buf), " upl_dpdgck_clk: %u", usb_pllfreq); + lcd_puts(0, line++, buf); line++; + + regval = CLKCTL_PDR1; + snprintf(buf, sizeof (buf), " PDR1: %08lX", regval); + lcd_puts(0, line++, buf); + + freq = usb_pllfreq / + ((((regval >> 30) & 0x3) + 1) * (((regval >> 27) & 0x7) + 1)); + snprintf(buf, sizeof (buf), " usb_clk: %u", freq); + lcd_puts(0, line++, buf); + + freq = usb_pllfreq / (((CLKCTL_PDR0 >> 16) & 0x1f) + 1); + snprintf(buf, sizeof (buf), " ipg_per_baud: %u", freq); + lcd_puts(0, line++, buf); + + lcd_update(); + + if (button_get(true) == (DEBUG_CANCEL|BUTTON_REL)) + return false; + } } bool __dbg_ports(void) diff --git a/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c b/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c index e37f6bfbe2..9ac96fd801 100644 --- a/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2006 by Michael Sevakis + * Copyright (C) 2008 by Michael Sevakis * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. @@ -19,69 +19,256 @@ #include #include "system.h" #include "kernel.h" -#include "logf.h" #include "audio.h" #include "sound.h" -#include "file.h" -#include "mmu-imx31.h" +#include "avic-imx31.h" +#include "clkctl-imx31.h" -#if 0 -static int pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ -#endif +/* This isn't DMA-based at the moment and is handled like Portal Player but + * will suffice for starters. */ + +struct dma_data +{ + uint16_t *p; + size_t size; + int locked; + int state; +}; + +static unsigned long pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ + +static struct dma_data dma_play_data = +{ + /* Initialize to a locked, stopped state */ + .p = NULL, + .size = 0, + .locked = 0, + .state = 0 +}; void pcm_play_lock(void) { + if (++dma_play_data.locked == 1) + { + /* Atomically disable transmit interrupt */ + imx31_regmod32(&SSI_SIER1, 0, SSI_SIER_TIE); + } } void pcm_play_unlock(void) { + if (--dma_play_data.locked == 0 && dma_play_data.state != 0) + { + /* Atomically enable transmit interrupt */ + imx31_regmod32(&SSI_SIER1, SSI_SIER_TIE, SSI_SIER_TIE); + } } -#if 0 static void _pcm_apply_settings(void) { + if (pcm_freq != pcm_curr_sampr) + { + pcm_curr_sampr = pcm_freq; + // TODO: audiohw_set_frequency(sr_ctrl); + } +} + +static void __attribute__((interrupt("IRQ"))) SSI1_HANDLER(void) +{ + register pcm_more_callback_type get_more; + + do + { + while (dma_play_data.size > 0) + { + if (SSI_SFCSR_TFCNT0r(SSI_SFCSR1) > 6) + { + return; + } + SSI_STX0_1 = *dma_play_data.p++; + SSI_STX0_1 = *dma_play_data.p++; + dma_play_data.size -= 4; + } + + /* p is empty, get some more data */ + get_more = pcm_callback_for_more; + + if (get_more) + { + get_more((unsigned char **)&dma_play_data.p, + &dma_play_data.size); + } + } + while (dma_play_data.size > 0); + + /* No more data, so disable the FIFO/interrupt */ + pcm_play_dma_stop(); + pcm_play_dma_stopped_callback(); } -#endif void pcm_apply_settings(void) { + int oldstatus = disable_fiq_save(); + + _pcm_apply_settings(); + + restore_fiq(oldstatus); } void pcm_play_dma_init(void) { + imx31_clkctl_module_clock_gating(CG_SSI1, CGM_ON_ALL); + imx31_clkctl_module_clock_gating(CG_SSI2, CGM_ON_ALL); + + /* Reset & disable SSIs */ + SSI_SCR2 &= ~SSI_SCR_SSIEN; + SSI_SCR1 &= ~SSI_SCR_SSIEN; + + SSI_SIER1 = SSI_SIER_TFE0; + SSI_SIER2 = 0; + + /* Set up audio mux */ + + /* Port 1 (internally connected to SSI1) + * All clocking is output sourced from port 4 */ + AUDMUX_PTCR1 = AUDMUX_PTCR_TFS_DIR | AUDMUX_PTCR_TFSEL_PORT4 | + AUDMUX_PTCR_TCLKDIR | AUDMUX_PTCR_TCSEL_PORT4 | + AUDMUX_PTCR_RFSDIR | AUDMUX_PTCR_RFSSEL_PORT4 | + AUDMUX_PTCR_RCLKDIR | AUDMUX_PTCR_RCSEL_PORT4 | + AUDMUX_PTCR_SYN; + + /* Receive data from port 4 */ + AUDMUX_PDCR1 = AUDMUX_PDCR_RXDSEL_PORT4; + /* All clock lines are inputs sourced from the master mode codec and + * sent back to SSI1 through port 1 */ + AUDMUX_PTCR4 = AUDMUX_PTCR_SYN; + + /* Receive data from port 1 */ + AUDMUX_PDCR4 = AUDMUX_PDCR_RXDSEL_PORT1; + + /* Port 2 (internally connected to SSI2) routes clocking to port 5 to + * provide MCLK to the codec */ + /* All port 2 clocks are inputs taken from SSI2 */ + AUDMUX_PTCR2 = 0; + AUDMUX_PDCR2 = 0; + /* Port 5 outputs TCLK sourced from port 2 */ + AUDMUX_PTCR5 = AUDMUX_PTCR_TCLKDIR | AUDMUX_PTCR_TCSEL_PORT2; + AUDMUX_PDCR5 = 0; + + /* Setup SSIs */ + + /* SSI1 - interface for all I2S data */ + SSI_SCR1 = SSI_SCR_SYN | SSI_SCR_I2S_MODE_SLAVE; + SSI_STCR1 = SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSI | + SSI_STCR_TEFS | SSI_STCR_TFEN0; + + /* 16 bits per word, 2 words per frame */ + SSI_STCCR1 = SSI_STRCCR_WL16 | SSI_STRCCR_DCw(2-1) | + SSI_STRCCR_PMw(4-1); + + SSI_STMSK1 = 0; + + /* Receive */ + SSI_SRCR1 = SSI_SRCR_RXBIT0 | SSI_SRCR_RSCKP | SSI_SRCR_RFSI | + SSI_SRCR_REFS | SSI_SRCR_RFEN0; + + /* 16 bits per word, 2 words per frame */ + SSI_SRCCR1 = SSI_STRCCR_WL16 | SSI_STRCCR_DCw(2-1) | + SSI_STRCCR_PMw(4-1); + + /* Receive high watermark - 6 samples in FIFO + * Transmit low watermark - 2 samples in FIFO */ + SSI_SFCSR1 = SSI_SFCSR_RFWM1w(8) | SSI_SFCSR_TFWM1w(1) | + SSI_SFCSR_RFWM0w(6) | SSI_SFCSR_TFWM0w(2); + + SSI_SRMSK1 = 0; + + /* SSI2 - provides MCLK only */ + SSI_SCR2 = 0; + SSI_SRCR2 = 0; + SSI_STCR2 = SSI_STCR_TXDIR; + SSI_STCCR2 = SSI_STRCCR_PMw(0); + + /* Enable SSIs */ + SSI_SCR2 |= SSI_SCR_SSIEN; + audiohw_init(); } void pcm_postinit(void) { audiohw_postinit(); + avic_enable_int(SSI1, IRQ, 8, SSI1_HANDLER); } -#if 0 -/* Connect the DMA and start filling the FIFO */ static void play_start_pcm(void) { + /* Stop transmission (if in progress) */ + SSI_SCR1 &= ~SSI_SCR_TE; + + /* Apply new settings */ + _pcm_apply_settings(); + + /* Enable interrupt on unlock */ + dma_play_data.state = 1; + + /* Fill the FIFO or start when data is used up */ + while (1) + { + if (SSI_SFCSR_TFCNT0r(SSI_SFCSR1) > 6 || dma_play_data.size == 0) + { + SSI_SCR1 |= (SSI_SCR_TE | SSI_SCR_SSIEN); /* Start transmitting */ + return; + } + + SSI_STX0_1 = *dma_play_data.p++; + SSI_STX0_1 = *dma_play_data.p++; + dma_play_data.size -= 4; + } } -/* Disconnect the DMA and wait for the FIFO to clear */ static void play_stop_pcm(void) { + /* Disable interrupt */ + SSI_SIER1 &= ~SSI_SIER_TIE; + + /* Wait for FIFO to empty */ + while (SSI_SFCSR_TFCNT0r(SSI_SFCSR1) > 0); + + /* Disable transmission */ + SSI_SCR1 &= ~(SSI_SCR_TE | SSI_SCR_SSIEN); + + /* Do not enable interrupt on unlock */ + dma_play_data.state = 0; } -#endif void pcm_play_dma_start(const void *addr, size_t size) { - (void)addr; - (void)size; + dma_play_data.p = (void *)(((uintptr_t)addr + 3) & ~3); + dma_play_data.size = (size & ~3); + + play_start_pcm(); } void pcm_play_dma_stop(void) { + play_stop_pcm(); + dma_play_data.size = 0; } void pcm_play_dma_pause(bool pause) { - (void)pause; + if (pause) + { + play_stop_pcm(); + } + else + { + uint32_t addr = (uint32_t)dma_play_data.p; + dma_play_data.p = (void *)((addr + 2) & ~3); + dma_play_data.size &= ~3; + play_start_pcm(); + } } /* Set the pcm frequency hardware will use when play is next started or @@ -89,20 +276,23 @@ void pcm_play_dma_pause(bool pause) hardware here but simply cache it. */ void pcm_set_frequency(unsigned int frequency) { + /* TODO */ (void)frequency; } /* Return the number of bytes waiting - full L-R sample pairs only */ size_t pcm_get_bytes_waiting(void) { - return 0; + return dma_play_data.size & ~3; } /* Return a pointer to the samples and the number of them in *count */ const void * pcm_play_dma_get_peak_buffer(int *count) { - (void)count; - return NULL; + uint32_t addr = (uint32_t)dma_play_data.p; + size_t cnt = dma_play_data.size; + *count = cnt >> 2; + return (void *)((addr + 2) & ~3); } /* Any recording functionality should be implemented similarly */ diff --git a/firmware/target/arm/imx31/gigabeat-s/system-imx31.c b/firmware/target/arm/imx31/gigabeat-s/system-imx31.c index da5026a292..ca82a18fbd 100644 --- a/firmware/target/arm/imx31/gigabeat-s/system-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/system-imx31.c @@ -27,6 +27,16 @@ void system_init(void) gpio_init(); } +void imx31_regmod32(volatile uint32_t *reg_p, uint32_t mask, uint32_t value) +{ + value &= mask; + mask = ~mask; + + int oldlevel = disable_interrupt_save(IRQ_FIQ_STATUS); + *reg_p = (*reg_p & mask) | value; + restore_interrupt(oldlevel); +} + #ifdef BOOTLOADER void system_prepare_fw_start(void) { diff --git a/firmware/target/arm/imx31/gigabeat-s/system-target.h b/firmware/target/arm/imx31/gigabeat-s/system-target.h index 241f215dc1..766de7328f 100644 --- a/firmware/target/arm/imx31/gigabeat-s/system-target.h +++ b/firmware/target/arm/imx31/gigabeat-s/system-target.h @@ -24,16 +24,21 @@ #define CPUFREQ_NORMAL 532000000 +#if 0 static inline void udelay(unsigned int usecs) { volatile signed int stop = EPITCNT1 - usecs; while ((signed int)EPITCNT1 > stop); } +#endif void system_prepare_fw_start(void); void tick_stop(void); void kernel_device_init(void); +void imx31_regmod32(volatile uint32_t *reg_p, uint32_t value, + uint32_t mask); + #define KDEV_INIT #define HAVE_INVALIDATE_ICACHE diff --git a/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c b/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c index 235ae54bad..c8a04ce20e 100644 --- a/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c @@ -7,14 +7,9 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Gigabeat S specific code for the WM8978 codec + * Gigabeat S specific code for the WM8978 codec * - * Based on code from the ipodlinux project - http://ipodlinux.org/ - * Adapted for Rockbox in December 2005 - * - * Original file: linux/arch/armnommu/mach-ipod/audio.c - * - * Copyright (c) 2003-2005 Bernard Leach (leachbj@bouncycastle.org) + * Copyright (C) 2008 Michael Sevakis * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. @@ -23,7 +18,8 @@ * KIND, either express or implied. * ****************************************************************************/ -#include "cpu.h" +#include "config.h" +#include "system.h" #include "kernel.h" #include "sound.h" #include "i2c-imx31.h" @@ -37,15 +33,33 @@ static struct i2c_node wm8978_i2c_node = .ifdr = I2C_IFDR_DIV192, /* 66MHz/.4MHz = 165, closest = 192 = 343750Hz */ /* Just hard-code for now - scaling may require * updating */ - .addr = WM8978_I2C_ADDR, + .addr = WMC_I2C_ADDR, }; void audiohw_init(void) { + /* USB PLL = 338.688MHz, /30 = 11.2896MHz = 256Fs */ + imx31_regmod32(&CLKCTL_PDR1, PDR1_SSI1_PODF | PDR1_SSI2_PODF, + PDR1_SSI1_PODFw(64-1) | PDR1_SSI2_PODFw(5-1)); + imx31_regmod32(&CLKCTL_PDR1, PDR1_SSI1_PRE_PODF | PDR1_SSI2_PRE_PODF, + PDR1_SSI1_PRE_PODFw(4-1) | PDR1_SSI2_PRE_PODFw(1-1)); i2c_enable_node(&wm8978_i2c_node, true); - GPIO3_DR |= (1 << 21); /* Turn on analogue LDO */ - sleep(HZ/10); /* Wait for things to stabilize */ + audiohw_preinit(); + + GPIO3_DR |= (1 << 21); /* Turn on analogue LDO */ +} + +void audiohw_enable_headphone_jack(bool enable) +{ + if (enable) + { + GPIO3_DR |= (1 << 22); /* Turn on headphone jack output */ + } + else + { + GPIO3_DR &= ~(1 << 22); /* Turn off headphone jack output */ + } } void wmcodec_write(int reg, int data) -- cgit v1.2.3