| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (C) 2008 by Michael Sevakis |
| * |
| * 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. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| #include <stdlib.h> |
| #include "system.h" |
| #include "kernel.h" |
| #include "audio.h" |
| #include "sound.h" |
| #include "avic-imx31.h" |
| #include "clkctl-imx31.h" |
| |
| /* 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); |
| } |
| } |
| |
| 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(); |
| } |
| |
| 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); |
| } |
| |
| 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; |
| } |
| } |
| |
| 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; |
| } |
| |
| void pcm_play_dma_start(const void *addr, size_t 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) |
| { |
| 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 |
| when pcm_apply_settings is called. Do not apply the setting to the |
| 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 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) |
| { |
| 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 */ |