| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (C) 2006 by Michael Sevakis |
| * Copyright (C) 2005 by Linus Nielsen Feltzing |
| * |
| * 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 "logf.h" |
| #include "audio.h" |
| #include "sound.h" |
| #if defined(HAVE_SPDIF_REC) || defined(HAVE_SPDIF_OUT) |
| #include "spdif.h" |
| #endif |
| #include "pcm-internal.h" |
| |
| #define IIS_PLAY_DEFPARM ( (freq_ent[FPARM_CLOCKSEL] << 12) | \ |
| (IIS_PLAY & (7 << 8)) | \ |
| (4 << 2) ) /* 64 bit clocks / word clock */ |
| #define IIS_FIFO_RESET (1 << 11) |
| #define PDIR2_FIFO_RESET (1 << 9) |
| |
| #if defined(IAUDIO_X5) || defined(IAUDIO_M5) || defined(IAUDIO_M3) |
| #define IIS_PLAY IIS1CONFIG |
| #else |
| #define IIS_PLAY IIS2CONFIG |
| #endif |
| |
| #ifdef HAVE_SPDIF_OUT |
| /* EBU TX auto sync, PDIR2 fifo auto sync, IIS1 fifo auto sync */ |
| #define AUDIOGLOB_DEFPARM ((1 << 10) | (1 << 8) | (1 << 7)) |
| #else |
| /* PDIR2 fifo auto sync, IIS1 fifo auto sync */ |
| #define AUDIOGLOB_DEFPARM ((1 << 8) | (1 << 7)) |
| #endif |
| |
| /** Sample rates **/ |
| #define PLLCR_SET_AUDIO_BITS_DEFPARM \ |
| ((freq_ent[FPARM_CLSEL] << 28) | (1 << 22)) |
| |
| #define FPARM_CLOCKSEL 0 |
| #define FPARM_CLSEL 1 |
| |
| /* SCLK = Fs * bit clocks per word |
| * so SCLK should be Fs * 64 |
| * |
| * CLOCKSEL sets SCLK freq based on Audio CLK |
| * 0x0c SCLK = Audio CLK/2 88200 * 64 = 5644800 Hz |
| * 0x06 SCLK = Audio CLK/4 44100 * 64 = 2822400 Hz |
| * 0x04 SCLK = Audio CLK/8 22050 * 64 = 1411200 Hz |
| * 0x02 SCLK = Audio CLK/16 11025 * 64 = 705600 Hz |
| * |
| * CLSEL sets MCLK1/2 DAC freq based on XTAL freq |
| * 0x01 MCLK1/2 = XTAL freq |
| * 0x02 MCLK1/2 = XTAL/2 freq |
| * |
| * Audio CLK can be XTAL freq or XTAL/2 freq (bit22 in PLLCR) |
| * we always set bit22 so Audio CLK is always XTAL freq |
| */ |
| |
| #if CONFIG_CPU == MCF5249 && defined(HAVE_UDA1380) |
| static const unsigned char pcm_freq_parms[HW_NUM_FREQ][2] = |
| { |
| [HW_FREQ_88] = { 0x0c, 0x01 }, |
| [HW_FREQ_44] = { 0x06, 0x01 }, |
| [HW_FREQ_22] = { 0x04, 0x02 }, |
| [HW_FREQ_11] = { 0x02, 0x02 }, |
| }; |
| #endif |
| |
| #if CONFIG_CPU == MCF5249 && defined(HAVE_WM8750) |
| /* We run codec in master mode. |
| * Codec can reconstruct all frequencies |
| * from single 11.2896 MHz master clock |
| */ |
| static const unsigned char pcm_freq_parms[HW_NUM_FREQ][2] = |
| { |
| [HW_FREQ_88] = { 0x00, 0x01 }, |
| [HW_FREQ_44] = { 0x00, 0x01 }, |
| [HW_FREQ_22] = { 0x00, 0x01 }, |
| [HW_FREQ_11] = { 0x00, 0x01 }, |
| }; |
| |
| #endif |
| |
| #if (CONFIG_CPU == MCF5250 || CONFIG_CPU == MCF5249) && defined(HAVE_TLV320) |
| static const unsigned char pcm_freq_parms[HW_NUM_FREQ][2] = |
| { |
| [HW_FREQ_88] = { 0x0c, 0x01 }, |
| [HW_FREQ_44] = { 0x06, 0x01 }, |
| [HW_FREQ_22] = { 0x04, 0x01 }, |
| [HW_FREQ_11] = { 0x02, 0x02 }, |
| }; |
| #endif |
| |
| static const unsigned char *freq_ent; |
| |
| /* Lock status struct for playback and recording */ |
| struct dma_lock |
| { |
| int locked; |
| unsigned long state; |
| }; |
| |
| static void iis_play_reset(void) |
| { |
| or_l(IIS_FIFO_RESET, &IIS_PLAY); |
| and_l(~IIS_FIFO_RESET, &IIS_PLAY); |
| PDOR3 = 0; |
| } |
| |
| static bool is_playback_monitoring(void) |
| { |
| return (IIS_PLAY & (7 << 8)) == (3 << 8); |
| } |
| |
| static void iis_play_reset_if_playback(bool if_playback) |
| { |
| int level = set_irq_level(DMA_IRQ_LEVEL); |
| if (is_playback_monitoring() == if_playback) |
| iis_play_reset(); |
| restore_irq(level); |
| } |
| |
| /* apply audio settings */ |
| /* This clears the reset bit to enable monitoring immediately if monitoring |
| recording sources or always if playback is in progress - we might be |
| switching samplerates on the fly */ |
| void pcm_dma_apply_settings(void) |
| { |
| int level = set_irq_level(DMA_IRQ_LEVEL); |
| |
| /* remember table entry */ |
| freq_ent = pcm_freq_parms[pcm_fsel]; |
| |
| /* Reprogramming bits 15-12 requires FIFO to be in a reset |
| condition - Users Manual 17-8, Note 11 */ |
| or_l(IIS_FIFO_RESET, &IIS_PLAY); |
| /* Important for TLV320 - this must happen in the correct order |
| or starting recording will sound absolutely awful once in |
| awhile - audiohw_set_frequency then coldfire_set_pllcr_audio_bits |
| */ |
| IIS_PLAY = IIS_PLAY_DEFPARM | IIS_FIFO_RESET; |
| restore_irq(level); |
| |
| audiohw_set_frequency(pcm_fsel); |
| coldfire_set_pllcr_audio_bits(PLLCR_SET_AUDIO_BITS_DEFPARM); |
| |
| level = set_irq_level(DMA_IRQ_LEVEL); |
| |
| IIS_PLAY = IIS_PLAY_DEFPARM; |
| |
| if ((DCR0 & DMA_EEXT) != 0 && is_playback_monitoring()) |
| PDOR3 = 0; /* Kick FIFO out of reset by writing to it */ |
| |
| restore_irq(level); |
| } /* pcm_dma_apply_settings */ |
| |
| void pcm_play_dma_init(void) |
| { |
| freq_ent = pcm_freq_parms[pcm_fsel]; |
| |
| AUDIOGLOB = AUDIOGLOB_DEFPARM; |
| DIVR0 = 54; /* DMA0 is mapped into vector 54 in system.c */ |
| and_l(0xffffff00, &DMAROUTE); |
| or_l(DMA0_REQ_AUDIO_1, &DMAROUTE); |
| DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */ |
| BCR0 = 0; /* No bytes waiting */ |
| ICR6 = (6 << 2); /* Enable interrupt at level 6, priority 0 */ |
| |
| /* Setup Coldfire I2S before initializing hardware or changing |
| other settings. */ |
| or_l(IIS_FIFO_RESET, &IIS_PLAY); |
| IIS_PLAY = IIS_PLAY_DEFPARM | IIS_FIFO_RESET; |
| audio_set_output_source(AUDIO_SRC_PLAYBACK); |
| |
| /* Initialize default register values. */ |
| audiohw_init(); |
| |
| audio_input_mux(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); |
| |
| audiohw_set_frequency(pcm_fsel); |
| coldfire_set_pllcr_audio_bits(PLLCR_SET_AUDIO_BITS_DEFPARM); |
| |
| #if defined(HAVE_SPDIF_REC) || defined(HAVE_SPDIF_OUT) |
| spdif_init(); |
| #endif |
| } /* pcm_play_dma_init */ |
| |
| void pcm_postinit(void) |
| { |
| audiohw_postinit(); |
| iis_play_reset(); |
| } |
| |
| /** DMA **/ |
| |
| /**************************************************************************** |
| ** Playback DMA transfer |
| **/ |
| /* For the locks, DMA interrupt must be disabled when manipulating the lock |
| if the handler ever calls these - right now things are arranged so it |
| doesn't */ |
| static struct dma_lock dma_play_lock = |
| { |
| .locked = 0, |
| .state = (1 << 14) /* bit 14 is DMA0 */ |
| }; |
| |
| void pcm_play_lock(void) |
| { |
| if (++dma_play_lock.locked == 1) |
| coldfire_imr_mod(1 << 14, 1 << 14); |
| } |
| |
| void pcm_play_unlock(void) |
| { |
| if (--dma_play_lock.locked == 0) |
| coldfire_imr_mod(dma_play_lock.state, 1 << 14); |
| } |
| |
| /* Set up the DMA transfer that kicks in when the audio FIFO gets empty */ |
| void pcm_play_dma_start(const void *addr, size_t size) |
| { |
| /* Stop any DMA in progress */ |
| pcm_play_dma_stop(); |
| |
| /* Set up DMA transfer */ |
| SAR0 = (unsigned long)addr; /* Source address */ |
| DAR0 = (unsigned long)&PDOR3; /* Destination address */ |
| BCR0 = (unsigned long)size; /* Bytes to transfer */ |
| |
| DCR0 = DMA_INT | DMA_EEXT | DMA_CS | DMA_AA | DMA_SINC | |
| DMA_SSIZE(DMA_SIZE_LINE) | DMA_START; |
| |
| dma_play_lock.state = (0 << 14); |
| } /* pcm_play_dma_start */ |
| |
| /* Stops the DMA transfer and interrupt */ |
| void pcm_play_dma_stop(void) |
| { |
| and_l(~(DMA_EEXT | DMA_INT), &DCR0); /* per request and int OFF */ |
| BCR0 = 0; /* No bytes remaining */ |
| DSR0 = 1; /* Clear interrupt, errors, stop transfer */ |
| |
| iis_play_reset_if_playback(true); |
| |
| dma_play_lock.state = (1 << 14); |
| } /* pcm_play_dma_stop */ |
| |
| void pcm_play_dma_pause(bool pause) |
| { |
| if (pause) |
| { |
| /* pause playback on current buffer */ |
| and_l(~(DMA_EEXT | DMA_INT), &DCR0); /* per request and int OFF */ |
| DSR0 = 1; /* stop channel */ |
| iis_play_reset_if_playback(true); |
| dma_play_lock.state = (1 << 14); |
| } |
| else |
| { |
| /* restart playback on current buffer */ |
| iis_play_reset_if_playback(true); |
| or_l(DMA_INT | DMA_EEXT | DMA_START, &DCR0); /* everything ON */ |
| dma_play_lock.state = (0 << 14); |
| } |
| } /* pcm_play_dma_pause */ |
| |
| size_t pcm_get_bytes_waiting(void) |
| { |
| return BCR0 & 0xffffff; |
| } /* pcm_get_bytes_waiting */ |
| |
| /* DMA0 Interrupt is called when the DMA has finished transfering a chunk |
| from the caller's buffer */ |
| void DMA0(void) __attribute__ ((interrupt_handler, section(".icode"))); |
| void DMA0(void) |
| { |
| unsigned long res = DSR0; |
| void *start; |
| size_t size; |
| |
| and_l(~(DMA_EEXT | DMA_INT), &DCR0); /* per request and int OFF */ |
| DSR0 = 1; /* Clear interrupt and errors */ |
| |
| if (res & 0x70) |
| { |
| logf("DMA0 err: %02x", res); |
| #if 0 |
| logf(" SAR0: %08x", SAR0); |
| logf(" DAR0: %08x", DAR0); |
| logf(" BCR0: %08x", BCR0); |
| logf(" DCR0: %08x", DCR0); |
| #endif |
| } |
| |
| /* Force stop on error */ |
| pcm_play_get_more_callback((res & 0x70) ? NULL : &start, &size); |
| |
| if (size != 0) |
| { |
| SAR0 = (unsigned long)start; /* Source address */ |
| BCR0 = size; /* Bytes to transfer */ |
| or_l(DMA_EEXT | DMA_INT, &DCR0); /* per request and int ON */ |
| |
| /* Call buffer callback */ |
| pcm_play_dma_started_callback(); |
| } |
| /* else inished playing */ |
| } /* DMA0 */ |
| |
| const void * pcm_play_dma_get_peak_buffer(int *count) |
| { |
| unsigned long addr, cnt; |
| |
| /* Make sure interrupt doesn't change the second value after we read the |
| * first value. */ |
| int level = set_irq_level(DMA_IRQ_LEVEL); |
| addr = SAR0; |
| cnt = BCR0; |
| restore_irq(level); |
| |
| *count = (cnt & 0xffffff) >> 2; |
| return (void *)((addr + 2) & ~3); |
| } /* pcm_play_dma_get_peak_buffer */ |
| |
| #ifdef HAVE_RECORDING |
| /**************************************************************************** |
| ** Recording DMA transfer |
| **/ |
| static struct dma_lock dma_rec_lock = |
| { |
| .locked = 0, |
| .state = (1 << 15) /* bit 15 is DMA1 */ |
| }; |
| |
| /* For the locks, DMA interrupt must be disabled when manipulating the lock |
| if the handler ever calls these - right now things are arranged so it |
| doesn't */ |
| void pcm_rec_lock(void) |
| { |
| if (++dma_rec_lock.locked == 1) |
| coldfire_imr_mod(1 << 15, 1 << 15); |
| } |
| |
| void pcm_rec_unlock(void) |
| { |
| if (--dma_rec_lock.locked == 0) |
| coldfire_imr_mod(dma_rec_lock.state, 1 << 15); |
| } |
| |
| void pcm_rec_dma_start(void *addr, size_t size) |
| { |
| /* stop any DMA in progress */ |
| pcm_rec_dma_stop(); |
| |
| and_l(~PDIR2_FIFO_RESET, &DATAINCONTROL); |
| |
| /* Start the DMA transfer.. */ |
| #ifdef HAVE_SPDIF_REC |
| /* clear: ebu1cnew, valnogood, symbolerr, parityerr */ |
| INTERRUPTCLEAR = (1 << 25) | (1 << 24) | (1 << 23) | (1 << 22); |
| #endif |
| |
| SAR1 = (unsigned long)&PDIR2; /* Source address */ |
| DAR1 = (unsigned long)addr; /* Destination address */ |
| BCR1 = (unsigned long)size; /* Bytes to transfer */ |
| |
| DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_AA | DMA_DINC | |
| DMA_DSIZE(DMA_SIZE_LINE) | DMA_START; |
| |
| dma_rec_lock.state = (0 << 15); |
| } /* pcm_rec_dma_start */ |
| |
| void pcm_rec_dma_stop(void) |
| { |
| and_l(~(DMA_EEXT | DMA_INT), &DCR1); /* per request and int OFF */ |
| DSR1 = 1; /* Clear interrupt, errors, stop transfer */ |
| BCR1 = 0; /* No bytes received */ |
| |
| or_l(PDIR2_FIFO_RESET, &DATAINCONTROL); |
| |
| iis_play_reset_if_playback(false); |
| |
| dma_rec_lock.state = (1 << 15); |
| } /* pcm_rec_dma_stop */ |
| |
| void pcm_rec_dma_init(void) |
| { |
| DIVR1 = 55; /* DMA1 is mapped into vector 55 in system.c */ |
| DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */ |
| and_l(0xffff00ff, &DMAROUTE); |
| or_l(DMA1_REQ_AUDIO_2, &DMAROUTE); |
| |
| pcm_rec_dma_stop(); |
| |
| /* Enable interrupt at level 6, priority 1 */ |
| ICR7 = (6 << 2) | (1 << 0); |
| } /* pcm_init_recording */ |
| |
| void pcm_rec_dma_close(void) |
| { |
| pcm_rec_dma_stop(); |
| |
| and_l(0xffff00ff, &DMAROUTE); |
| ICR7 = 0x00; /* Disable interrupt */ |
| dma_rec_lock.state = (0 << 15); |
| } /* pcm_rec_dma_close */ |
| |
| /* DMA1 Interrupt is called when the DMA has finished transfering a chunk |
| into the caller's buffer */ |
| void DMA1(void) __attribute__ ((interrupt_handler, section(".icode"))); |
| void DMA1(void) |
| { |
| unsigned long res = DSR1; |
| int status = 0; |
| void *start; |
| size_t size; |
| |
| and_l(~(DMA_EEXT | DMA_INT), &DCR1); /* per request and int OFF */ |
| DSR1 = 1; /* Clear interrupt and errors */ |
| |
| if (res & 0x70) |
| { |
| status = DMA_REC_ERROR_DMA; |
| logf("DMA1 err: %02x", res); |
| #if 0 |
| logf(" SAR1: %08x", SAR1); |
| logf(" DAR1: %08x", DAR1); |
| logf(" BCR1: %08x", BCR1); |
| logf(" DCR1: %08x", DCR1); |
| #endif |
| } |
| #ifdef HAVE_SPDIF_REC |
| else if (DATAINCONTROL == 0xc038 && |
| (INTERRUPTSTAT & ((1 << 23) | (1 << 22)))) |
| { |
| /* reason: symbolerr, parityerr. |
| * Ignore valnogood since several sources don't set it properly. */ |
| /* clear: ebu1cnew, symbolerr, parityerr */ |
| INTERRUPTCLEAR = (1 << 25) | (1 << 23) | (1 << 22); |
| status = DMA_REC_ERROR_SPDIF; |
| logf("spdif err"); |
| } |
| #endif |
| |
| /* Inform PCM we have more data (or error) */ |
| pcm_rec_more_ready_callback(status, &start, &size); |
| |
| if (size != 0) |
| { |
| DAR1 = (unsigned long)start; /* Destination address */ |
| BCR1 = (unsigned long)size; /* Bytes to transfer */ |
| or_l(DMA_EEXT | DMA_INT, &DCR1); /* per request and int ON */ |
| } |
| } /* DMA1 */ |
| |
| const void * pcm_rec_dma_get_peak_buffer(void) |
| { |
| return (void *)(DAR1 & ~3); |
| } /* pcm_rec_dma_get_peak_buffer */ |
| #endif |