| /**************************************************************************** |
| * arch/arm/src/lc823450/lc823450_i2s.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <arch/board/board.h> |
| #include <nuttx/config.h> |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| #include <inttypes.h> |
| |
| #include <nuttx/sched.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/semaphore.h> |
| #include <nuttx/audio/audio.h> |
| #include <nuttx/audio/i2s.h> |
| |
| #include "arm_internal.h" |
| #include "lc823450_dma.h" |
| #include "lc823450_i2s.h" |
| #include "lc823450_syscontrol.h" |
| #include "lc823450_clockconfig.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* #define SHOW_BUFFERING */ |
| |
| #define I2S_HAS_IOCTL |
| |
| #define BUFID(n) (n - 'A') |
| |
| #define LC823450_AUDIO_REGBASE 0x40060000 |
| |
| #define ABUF_REGBASE (LC823450_AUDIO_REGBASE + 0x0000) |
| #define SSRC_REGBASE (LC823450_AUDIO_REGBASE + 0x1000) |
| #define BEEP_REGBASE (LC823450_AUDIO_REGBASE + 0x1200) |
| #define DGMIC_REGBASE (LC823450_AUDIO_REGBASE + 0x1500) |
| #define PCKGEN_REGBASE (LC823450_AUDIO_REGBASE + 0x1600) |
| #define ASRC_REGBASE (LC823450_AUDIO_REGBASE + 0x1a00) |
| #define MP3DEC_REGBASE (LC823450_AUDIO_REGBASE + 0x2000) |
| #define AUDCTL_REGBASE (LC823450_AUDIO_REGBASE + 0x4000) |
| |
| /* Audio Buffer */ |
| |
| #define ABUFCLR (ABUF_REGBASE + 0x0000) |
| |
| #define ABUFACCEN (ABUF_REGBASE + 0x0004) |
| #define ABUFACCEN_RDCTEN(n) (1 << (BUFID(n) + 16)) |
| #define ABUFACCEN_CDCEN(n) (1 << (BUFID(n) + 0)) |
| |
| #define ABUFIRQEN0 (ABUF_REGBASE + 0x0008) |
| #define ABUFIRQEN0_BOLIRQEN(n) (1 << (BUFID(n) + 16)) |
| #define ABUFIRQEN0_BULIRQEN(n) (1 << (BUFID(n) + 0)) |
| |
| #define ABUFSTS1 (ABUF_REGBASE + 0x0034) |
| #define ABUFSTS1_BOLVL(n) (1 << (BUFID(n) + 16)) |
| #define ABUFSTS1_BULVL(n) (1 << (BUFID(n) + 0)) |
| |
| #define BUF_BASE(n) (ABUF_REGBASE + 0x00c0 + (4 * BUFID(n))) |
| #define BUF_SIZE(n) (ABUF_REGBASE + 0x0100 + (4 * BUFID(n))) |
| #define BUF_ULVL(n) (ABUF_REGBASE + 0x0140 + (4 * BUFID(n))) |
| #define BUF_OLVL(n) (ABUF_REGBASE + 0x0180 + (4 * BUFID(n))) |
| #define BUF_DTCAP(n) (ABUF_REGBASE + 0x01c0 + (4 * BUFID(n))) |
| #define BUF_ACCESS(n) (ABUF_REGBASE + 0x0300 + (4 * BUFID(n))) |
| |
| #define BUFCTL(n) (ABUF_REGBASE + 0x0080 + (4 * BUFID(n))) |
| #define BUFCTL_MONO (1 << 0) |
| |
| /* SSRC */ |
| |
| #define SSRC_MODE (SSRC_REGBASE + 0x0000) |
| #define SSRC_FSI (SSRC_REGBASE + 0x0004) |
| #define SSRC_FSO (SSRC_REGBASE + 0x0008) |
| #define SSRC_SRESETB (SSRC_REGBASE + 0x0010) |
| #define SSRC_STATUS (SSRC_REGBASE + 0x0014) |
| |
| /* Audio Control */ |
| |
| #define CLOCKEN (AUDCTL_REGBASE + 0x000) |
| #define CLOCKEN_FCE_ASRC (1 << 29) |
| #define CLOCKEN_FCE_PCKGEN (1 << 28) |
| #define CLOCKEN_FCE_PCMPS0 (1 << 17) |
| #define CLOCKEN_FCE_BEEP (1 << 16) |
| #define CLOCKEN_FCE_VOLPS0 (1 << 13) |
| #define CLOCKEN_FCE_VOLSP0 (1 << 11) |
| #define CLOCKEN_FCE_DGMIC (1 << 7) |
| #define CLOCKEN_FCE_VOLD (1 << 6) |
| #define CLOCKEN_FCE_SSRC (1 << 4) |
| #define CLOCKEN_FCE_MUTED (1 << 3) |
| #define CLOCKEN_FCE_MP3DEC (1 << 2) |
| |
| #define AUDSEL (AUDCTL_REGBASE + 0x01c) |
| #define AUDSEL_PCM0_MODE (1 << 17) |
| #define AUDSEL_PCM0_MODEM (1 << 16) |
| #define AUDSEL_PCMSEL (1 << 4) |
| #define AUDSEL_DECSEL (1 << 0) |
| |
| #define PSCTL (AUDCTL_REGBASE + 0x110) |
| |
| /* Volume (SP0) */ |
| |
| #define VOLSP0_CONT (AUDCTL_REGBASE + 0x320) |
| #define VOL_CONT_DIRECT (1 << 20) |
| |
| #define PCMOUTEN (AUDCTL_REGBASE + 0x500) |
| #define PCMOUTEN_DOUT0EN (1 << 3) |
| #define PCMOUTEN_LRCK0EN (1 << 2) |
| #define PCMOUTEN_MCLK0EN (1 << 1) |
| #define PCMOUTEN_BCK0EN (1 << 0) |
| |
| #define PCMCTL (AUDCTL_REGBASE + 0x504) |
| |
| /* BEEP */ |
| |
| #define BEEP_CTL (BEEP_REGBASE + 0x0000) |
| #define BEEP_BYPASS (BEEP_REGBASE + 0x0004) |
| #define BEEP_COEFF (BEEP_REGBASE + 0x0008) |
| #define BEEP_TIME (BEEP_REGBASE + 0x000c) |
| |
| /* Audio PLL */ |
| |
| #define AUDIOPLL_REGBASE (LC823450_OSCSYS_REGBASE + 0x2000) |
| #define AUDPLLCNT (AUDIOPLL_REGBASE + 0x00) |
| #define AUDPLLMDIV (AUDIOPLL_REGBASE + 0x04) |
| #define AUDPLLNDIV (AUDIOPLL_REGBASE + 0x08) |
| |
| #define DGMICCTL (DGMIC_REGBASE + 0x00) |
| #define ALCCTL (DGMIC_REGBASE + 0x30) |
| |
| /* ASRC */ |
| |
| #define ASRC_MODE (ASRC_REGBASE + 0x0000) |
| #define ASRC_FSI (ASRC_REGBASE + 0x0004) |
| #define ASRC_FSO (ASRC_REGBASE + 0x0008) |
| #define ASRC_SRESETB (ASRC_REGBASE + 0x0010) |
| #define ASRC_STATUS (ASRC_REGBASE + 0x0014) |
| |
| /* MP3 Decoder */ |
| |
| #define MP3DEC_ERR (MP3DEC_REGBASE + 0x08) |
| #define MP3DEC_BUFM (MP3DEC_REGBASE + 0x1c) |
| #define MP3DEC_ERRMODE (MP3DEC_REGBASE + 0x20) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* The state of the one I2S peripheral */ |
| |
| struct lc823450_i2s_s |
| { |
| struct i2s_dev_s dev; /* Externally visible I2S interface */ |
| }; |
| |
| static bool _b_input_started = false; |
| static uint32_t _i2s_tx_th_bytes; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static uint32_t lc823450_i2s_rxsamplerate(struct i2s_dev_s *dev, |
| uint32_t rate); |
| static uint32_t lc823450_i2s_rxdatawidth(struct i2s_dev_s *dev, int bits); |
| static int lc823450_i2s_receive(struct i2s_dev_s *dev, |
| struct ap_buffer_s *apb, |
| i2s_callback_t callback, void *arg, |
| uint32_t timeout); |
| |
| static uint32_t lc823450_i2s_txsamplerate(struct i2s_dev_s *dev, |
| uint32_t rate); |
| static uint32_t lc823450_i2s_txdatawidth(struct i2s_dev_s *dev, int bits); |
| static int lc823450_i2s_send(struct i2s_dev_s *dev, |
| struct ap_buffer_s *apb, |
| i2s_callback_t callback, void *arg, |
| uint32_t timeout); |
| |
| static void lc823450_i2s_setchannel(char id, uint8_t ch); |
| static void lc823450_i2s_mp3dec(bool enable); |
| |
| static int lc823450_i2s_ioctl(struct i2s_dev_s *dev, |
| int cmd, unsigned long arg); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* I2S device operations */ |
| |
| static const struct i2s_ops_s g_i2sops = |
| { |
| /* Receiver methods */ |
| |
| .i2s_rxsamplerate = lc823450_i2s_rxsamplerate, |
| .i2s_rxdatawidth = lc823450_i2s_rxdatawidth, |
| .i2s_receive = lc823450_i2s_receive, |
| |
| /* Transmitter methods */ |
| |
| .i2s_txsamplerate = lc823450_i2s_txsamplerate, |
| .i2s_txdatawidth = lc823450_i2s_txdatawidth, |
| .i2s_send = lc823450_i2s_send, |
| |
| #ifdef I2S_HAS_IOCTL |
| /* Ioctl */ |
| |
| .i2s_ioctl = lc823450_i2s_ioctl, |
| #endif |
| }; |
| |
| static DMA_HANDLE _hrxdma; |
| static sem_t _sem_rxdma = SEM_INITIALIZER(0); |
| static sem_t _sem_buf_over = SEM_INITIALIZER(0); |
| |
| static DMA_HANDLE _htxdma; |
| static sem_t _sem_txdma = SEM_INITIALIZER(0); |
| static sem_t _sem_buf_under = SEM_INITIALIZER(0); |
| |
| /**************************************************************************** |
| * Public Data |
| ****************************************************************************/ |
| |
| extern unsigned int XT1OSC_CLK; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: _setup_audio_pll |
| ****************************************************************************/ |
| |
| static void _setup_audio_pll(uint32_t freq) |
| { |
| uint32_t m = 0; |
| uint32_t n = 0; |
| |
| DEBUGASSERT(24000000 == XT1OSC_CLK); |
| |
| switch (freq) |
| { |
| case 44100: |
| m = 625; |
| n = 3528; |
| break; |
| |
| case 48000: |
| m = 125; |
| n = 768; |
| break; |
| |
| default: |
| DEBUGPANIC(); |
| } |
| |
| /* Set divider */ |
| |
| putreg32(n, AUDPLLNDIV); |
| putreg32(m, AUDPLLMDIV); |
| |
| /* Audio PLL standby=off, Audio PLL unreset */ |
| |
| putreg32(0x0503, AUDPLLCNT); |
| |
| /* TODO: Wait */ |
| |
| nxsig_usleep(50 * 1000); |
| |
| /* Switch to the PLL */ |
| |
| modifyreg32(AUDCLKCNT, |
| 0x0, |
| 0x03 /* AUDCLKSEL=Audio PLL */ |
| ); |
| |
| /* TODO: Clock divider settings */ |
| |
| modifyreg32(AUDCLKCNT, |
| 0x0, |
| 0x0200 /* AUDDIV=2 */ |
| ); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_rxsamplerate |
| ****************************************************************************/ |
| |
| static uint32_t lc823450_i2s_rxsamplerate(struct i2s_dev_s *dev, |
| uint32_t rate) |
| { |
| /* Change ASRC FSO rate */ |
| |
| /* Stop/Reset/Unreset ASRC */ |
| |
| putreg32(0, ASRC_MODE); |
| putreg32(0, ASRC_SRESETB); |
| putreg32(1, ASRC_SRESETB); |
| while (getreg32(ASRC_STATUS) & 0x1); |
| |
| /* Setup FSO */ |
| |
| putreg32((rate) << 0, ASRC_FSO); |
| |
| /* Restart ASRC */ |
| |
| putreg32(0x1, ASRC_MODE); |
| while (getreg32(ASRC_STATUS) != 0x1); |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_rxdatawidth |
| ****************************************************************************/ |
| |
| static uint32_t lc823450_i2s_rxdatawidth(struct i2s_dev_s *dev, int bits) |
| { |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_setchannel |
| ****************************************************************************/ |
| |
| static void lc823450_i2s_setchannel(char id, uint8_t ch) |
| { |
| switch (ch) |
| { |
| case 1: |
| modifyreg32(BUFCTL(id), 0, BUFCTL_MONO); |
| break; |
| |
| case 2: |
| modifyreg32(BUFCTL(id), BUFCTL_MONO, 0); |
| break; |
| |
| default: |
| DEBUGPANIC(); |
| break; |
| } |
| |
| modifyreg32(ABUFCLR, 0, BUFID(id)); |
| } |
| |
| /**************************************************************************** |
| * Name: _setup_tx_threshold (threshold to start playback) |
| ****************************************************************************/ |
| |
| static void _setup_tx_threshold(uint32_t tx_th) |
| { |
| if (0 == tx_th) |
| { |
| /* default tx threshould : 1024bytes */ |
| |
| _i2s_tx_th_bytes = 1024; |
| } |
| else |
| { |
| /* tx_th: 0 to 100 (%) */ |
| |
| _i2s_tx_th_bytes = getreg32(BUF_SIZE('C')) * tx_th / 100; |
| } |
| |
| /* NOTE: Buffer Under Level is not controlled by tx threshold */ |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_rxdatawidth |
| ****************************************************************************/ |
| |
| static int lc823450_i2s_ioctl(struct i2s_dev_s *dev, int cmd, |
| unsigned long arg) |
| { |
| const struct audio_caps_desc_s *cap_desc; |
| uint32_t tx_th; |
| uint32_t rate[2]; |
| uint8_t ch[2]; |
| uint8_t fmt[2]; |
| |
| switch (cmd) |
| { |
| case AUDIOIOC_CONFIGURE: |
| cap_desc = (const struct audio_caps_desc_s *)((uintptr_t)arg); |
| DEBUGASSERT(NULL != cap_desc); |
| |
| tx_th = cap_desc->caps.ac_controls.w >> 24; |
| rate[1] = cap_desc->caps.ac_controls.w & 0xfffff; |
| ch[1] = cap_desc->caps.ac_channels; |
| fmt[1] = cap_desc->caps.ac_format.hw; |
| |
| if (cap_desc->caps.ac_type & AUDIO_TYPE_OUTPUT) |
| { |
| _setup_tx_threshold(tx_th); |
| |
| rate[0] = getreg32(SSRC_FSI) >> 13; |
| ch[0] = (getreg32(BUFCTL('C')) & BUFCTL_MONO) ? 1 : 2; |
| fmt[0] = getreg32(AUDSEL) & AUDSEL_DECSEL ? |
| AUDIO_FMT_MP3 : AUDIO_FMT_PCM; |
| |
| if (rate[0] != rate[1]) |
| { |
| audinfo("change output rate: %" PRId32 " -> %" PRId32 "\n", |
| rate[0], rate[1]); |
| lc823450_i2s_txsamplerate(dev, rate[1]); |
| } |
| |
| if (ch[0] != ch[1]) |
| { |
| audinfo("change output ch: %d -> %d\n", ch[0], ch[1]); |
| lc823450_i2s_setchannel('C', ch[1]); |
| } |
| |
| if (fmt[0] != fmt[1]) |
| { |
| lc823450_i2s_mp3dec(fmt[1] == AUDIO_FMT_MP3 ? true : false); |
| } |
| } |
| |
| if (cap_desc->caps.ac_type & AUDIO_TYPE_INPUT) |
| { |
| rate[0] = getreg32(ASRC_FSO); |
| ch[0] = (getreg32(BUFCTL('J')) & BUFCTL_MONO) ? 1 : 2; |
| |
| if (rate[0] != rate[1]) |
| { |
| audinfo("change input rate: %" PRId32 " -> %" PRId32 "\n", |
| rate[0], rate[1]); |
| lc823450_i2s_rxsamplerate(dev, rate[1]); |
| } |
| |
| if (ch[0] != ch[1]) |
| { |
| audinfo("change input ch: %d -> %d\n", ch[0], ch[1]); |
| lc823450_i2s_setchannel('J', ch[1]); |
| } |
| } |
| |
| break; |
| |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: _i2s_rxdma_callback |
| ****************************************************************************/ |
| |
| static void _i2s_rxdma_callback(DMA_HANDLE hdma, void *arg, int result) |
| { |
| sem_t *waitsem = (sem_t *)arg; |
| nxsem_post(waitsem); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_receive |
| ****************************************************************************/ |
| |
| static int lc823450_i2s_receive(struct i2s_dev_s *dev, |
| struct ap_buffer_s *apb, |
| i2s_callback_t callback, |
| void *arg, |
| uint32_t timeout) |
| { |
| int ret = OK; |
| |
| #if 1 /* TODO: should move to rxsamplerate later */ |
| if (false == _b_input_started) |
| { |
| _b_input_started = true; |
| |
| /* Start J Buffer */ |
| |
| modifyreg32(ABUFACCEN, 0, ABUFACCEN_CDCEN('J')); |
| |
| /* J Buffer : ACLTALN=0, ACLTEN=0 */ |
| |
| modifyreg32(BUFCTL('J'), 0x3 << 8, 0); |
| } |
| #endif |
| |
| /* Enable J Buffer Over Level IRQ */ |
| |
| modifyreg32(ABUFIRQEN0, 0, ABUFIRQEN0_BOLIRQEN('J')); |
| |
| /* Wait for Audio Buffer */ |
| |
| ret = nxsem_wait_uninterruptible(&_sem_buf_over); |
| if (ret < 0) |
| { |
| /* Disable J Buffer Over Level IRQ */ |
| |
| modifyreg32(ABUFIRQEN0, ABUFIRQEN0_BOLIRQEN('J'), 0); |
| |
| /* Stop J Buffer */ |
| |
| modifyreg32(ABUFACCEN, ABUFACCEN_CDCEN('J'), 0); |
| |
| return ret; |
| } |
| |
| volatile uint32_t *ptr = (uint32_t *)&apb->samp[apb->curbyte]; |
| uint32_t n = apb->nmaxbytes; |
| |
| /* Setup and start DMA for I2S */ |
| |
| lc823450_dmasetup(_hrxdma, |
| LC823450_DMA_DSTINC | |
| LC823450_DMA_SRCWIDTH_WORD | |
| LC823450_DMA_DSTWIDTH_WORD, |
| (uint32_t)BUF_ACCESS('J'), (uint32_t)ptr, n / 4); |
| |
| lc823450_dmastart(_hrxdma, |
| _i2s_rxdma_callback, |
| &_sem_rxdma); |
| |
| ret = nxsem_wait_uninterruptible(&_sem_rxdma); |
| if (ret < 0) |
| { |
| /* Stop DMA because semwait failed */ |
| |
| lc823450_dmastop(_hrxdma); |
| |
| return ret; |
| } |
| |
| /* Invoke the callback handler */ |
| |
| callback(dev, apb, arg, 0); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: _i2s_txdma_callback |
| ****************************************************************************/ |
| |
| static void _i2s_txdma_callback(DMA_HANDLE hdma, void *arg, int result) |
| { |
| sem_t *waitsem = (sem_t *)arg; |
| nxsem_post(waitsem); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_txsamplerate |
| ****************************************************************************/ |
| |
| static uint32_t lc823450_i2s_txsamplerate(struct i2s_dev_s *dev, |
| uint32_t rate) |
| { |
| /* Change SSRC FSI rate */ |
| |
| /* Stop/Reset/Unreset SSRC */ |
| |
| putreg32(0, SSRC_MODE); |
| putreg32(0, SSRC_SRESETB); |
| putreg32(1, SSRC_SRESETB); |
| while (getreg32(SSRC_STATUS) & 0x1); |
| |
| /* Setup FSI */ |
| |
| putreg32(rate << 13, SSRC_FSI); |
| |
| /* Restart SSRC */ |
| |
| putreg32(0x1, SSRC_MODE); |
| while (getreg32(SSRC_STATUS) != 0x1); |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_txdatawidth |
| ****************************************************************************/ |
| |
| static uint32_t lc823450_i2s_txdatawidth(struct i2s_dev_s *dev, int bits) |
| { |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: _i2s_isr |
| ****************************************************************************/ |
| |
| static int _i2s_isr(int irq, void *context, void *arg) |
| { |
| uint32_t status = getreg32(ABUFSTS1); |
| uint32_t irqen0 = getreg32(ABUFIRQEN0); |
| |
| /* Check C Buffer Under Level */ |
| |
| if ((irqen0 & ABUFIRQEN0_BULIRQEN('C')) && (status & ABUFSTS1_BULVL('C'))) |
| { |
| /* Disable C Buffer Under Level IRQ */ |
| |
| modifyreg32(ABUFIRQEN0, ABUFIRQEN0_BULIRQEN('C'), 0); |
| |
| /* post semaphore for the waiter */ |
| |
| nxsem_post(&_sem_buf_under); |
| } |
| |
| /* Check J Buffer Over Level */ |
| |
| if ((irqen0 & ABUFIRQEN0_BOLIRQEN('J')) && (status & ABUFSTS1_BOLVL('J'))) |
| { |
| /* Disable J Buffer Over Level IRQ */ |
| |
| modifyreg32(ABUFIRQEN0, ABUFIRQEN0_BOLIRQEN('J'), 0); |
| |
| /* post semaphore for the waiter */ |
| |
| nxsem_post(&_sem_buf_over); |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_send |
| ****************************************************************************/ |
| |
| static int lc823450_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, |
| i2s_callback_t callback, void *arg, |
| uint32_t timeout) |
| { |
| volatile uint32_t *ptr = (uint32_t *)&apb->samp[apb->curbyte]; |
| uint32_t n = apb->nbytes; |
| uint32_t bufc_enabled; |
| uint32_t decsel; |
| int ret = OK; |
| |
| DEBUGASSERT(0 < n); |
| |
| decsel = getreg32(AUDSEL) & AUDSEL_DECSEL; |
| |
| if (0 == getreg32(BUF_DTCAP('C'))) |
| { |
| /* C buffer is empty. Start buffering by disabling access control */ |
| |
| modifyreg32(ABUFACCEN, ABUFACCEN_CDCEN('C'), 0); |
| } |
| |
| bufc_enabled = getreg32(ABUFACCEN) & ABUFACCEN_CDCEN('C'); |
| |
| if (bufc_enabled) |
| { |
| /* Enable C Buffer Under Level IRQ */ |
| |
| modifyreg32(ABUFIRQEN0, 0, ABUFIRQEN0_BULIRQEN('C')); |
| |
| /* Wait for Audio Buffer */ |
| |
| ret = nxsem_wait_uninterruptible(&_sem_buf_under); |
| if (ret < 0) |
| { |
| /* Disable C Buffer Under Level IRQ */ |
| |
| modifyreg32(ABUFIRQEN0, ABUFIRQEN0_BULIRQEN('C'), 0); |
| |
| return ret; |
| } |
| } |
| |
| if (0 == decsel && (n & 0x3)) |
| { |
| auderr("** PCM data is not word-aligned (n=%" PRId32 ") **\n", n); |
| |
| /* Set size to align on a word boundary */ |
| |
| n &= ~0x3; |
| |
| if (0 == n) |
| { |
| goto out; |
| } |
| } |
| |
| /* Setup and start DMA for I2S */ |
| |
| if (n & 0x3) |
| { |
| lc823450_dmasetup(_htxdma, |
| LC823450_DMA_SRCINC | |
| LC823450_DMA_SRCWIDTH_BYTE | |
| LC823450_DMA_DSTWIDTH_BYTE, |
| (uint32_t)ptr, (uint32_t)BUF_ACCESS('C'), n); |
| } |
| else |
| { |
| lc823450_dmasetup(_htxdma, |
| LC823450_DMA_SRCINC | |
| LC823450_DMA_SRCWIDTH_WORD | |
| LC823450_DMA_DSTWIDTH_WORD, |
| (uint32_t)ptr, (uint32_t)BUF_ACCESS('C'), n / 4); |
| } |
| |
| lc823450_dmastart(_htxdma, |
| _i2s_txdma_callback, |
| &_sem_txdma); |
| |
| ret = nxsem_wait_uninterruptible(&_sem_txdma); |
| if (ret < 0) |
| { |
| /* Stop DMA because semwait failed */ |
| |
| lc823450_dmastop(_htxdma); |
| |
| return ret; |
| } |
| |
| #ifdef SHOW_BUFFERING |
| if (0 == bufc_enabled) |
| { |
| audinfo("buffering (remain=%d)\n", getreg32(BUF_DTCAP('C'))); |
| } |
| #endif |
| |
| /* Start C Buffer */ |
| |
| if (0 == bufc_enabled && _i2s_tx_th_bytes < getreg32(BUF_DTCAP('C'))) |
| { |
| modifyreg32(ABUFACCEN, 0, ABUFACCEN_CDCEN('C')); |
| } |
| |
| out: |
| |
| /* Invoke the callback handler */ |
| |
| callback(dev, apb, arg, 0); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_dmic_enable |
| ****************************************************************************/ |
| |
| static void lc823450_dmic_enable(void) |
| { |
| /* Disable clock for DGMIC */ |
| |
| modifyreg32(CLOCKEN, CLOCKEN_FCE_DGMIC, 0); |
| |
| /* ALC=off */ |
| |
| modifyreg32(ALCCTL, 0x1, 0x0); |
| |
| /* DGMICCTL: XFDSP=direct,XEQT=XHPF=off,XMOD=75%,XFS64=64fs */ |
| |
| putreg32(0x70000009, DGMICCTL); |
| |
| /* Enable clock for DGMIC */ |
| |
| modifyreg32(CLOCKEN, 0, CLOCKEN_FCE_DGMIC); |
| |
| /* AUDSEL: PCMSEL=1 (dmic) */ |
| |
| modifyreg32(AUDSEL, 0, AUDSEL_PCMSEL); |
| |
| #if 1 |
| /* TODO: should be moved to another function */ |
| |
| /* Enable clock for VOLUME(SP0) */ |
| |
| modifyreg32(CLOCKEN, 0, CLOCKEN_FCE_VOLSP0); |
| |
| /* Audio Buffer Access Control: enable redirect E */ |
| |
| modifyreg32(ABUFACCEN, 0, ABUFACCEN_RDCTEN('E')); |
| |
| /* Set the redirect source of I Buffer to E */ |
| |
| modifyreg32(BUFCTL('I'), 0, BUFID('E') << 16); |
| |
| /* Enable CDCI */ |
| |
| modifyreg32(ABUFACCEN, 0, ABUFACCEN_CDCEN('I')); |
| |
| /* Enable ASRC clock */ |
| |
| modifyreg32(CLOCKEN, 0, CLOCKEN_FCE_ASRC); |
| |
| /* ASRC = 44.1k(in)/44.1k(out) */ |
| |
| putreg32(44100 << 13, ASRC_FSI); |
| putreg32(44100 << 0, ASRC_FSO); |
| |
| /* Adjust volume SP0 (+33dB) */ |
| |
| putreg32(VOL_CONT_DIRECT | |
| 33 << 8 | 33, |
| VOLSP0_CONT); |
| |
| audinfo("ASRC_FSIO=%" PRId32 "\n", getreg32(ASRC_FSO)); |
| audinfo("DTCAP(I)=0x%" PRIx32 "\n", getreg32(BUF_DTCAP('I'))); |
| audinfo("DTCAP(J)=0x%" PRIx32 "\n", getreg32(BUF_DTCAP('J'))); |
| |
| /* Start ASRC */ |
| |
| putreg32(0x1, ASRC_MODE); |
| while (getreg32(ASRC_STATUS) != 0x1); |
| |
| /* J Buffer : ACLTALN=1, ACLTEN=1 */ |
| |
| modifyreg32(BUFCTL('J'), 0, 0x3 << 8); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_mp3dec |
| ****************************************************************************/ |
| |
| static void lc823450_i2s_mp3dec(bool enable) |
| { |
| if (enable) |
| { |
| modifyreg32(AUDSEL, 0, AUDSEL_DECSEL); |
| modifyreg32(CLOCKEN, 0, CLOCKEN_FCE_MP3DEC); |
| putreg32(0x1, MP3DEC_ERRMODE); |
| } |
| else |
| { |
| modifyreg32(CLOCKEN, CLOCKEN_FCE_MP3DEC, 0); |
| modifyreg32(AUDSEL, AUDSEL_DECSEL, 0); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_beeptest |
| ****************************************************************************/ |
| |
| #ifdef BEEP_TEST |
| static void lc823450_i2s_beeptest(void) |
| { |
| /* Enable clock */ |
| |
| modifyreg32(CLOCKEN, 0, CLOCKEN_FCE_BEEP); |
| |
| /* Set BEEP params */ |
| |
| putreg32(0x0, BEEP_BYPASS); |
| putreg32(0x123ca6, BEEP_COEFF); /* 1kHz@fs=44.1k */ |
| putreg32(0xffff, BEEP_TIME); |
| |
| /* Start */ |
| |
| putreg32(0x3, BEEP_CTL); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lc823450_i2s_configure |
| ****************************************************************************/ |
| |
| static int lc823450_i2s_configure(void) |
| { |
| uint32_t base = 0; |
| |
| _setup_audio_pll(44100); |
| |
| /* Unreset Audio Buffer */ |
| |
| putreg32(MRSTCNTEXT3_AUDIOBUF_RSTB, |
| MRSTCNTEXT3); |
| |
| /* Enable clock to Audio Buffer */ |
| |
| putreg32(MCLKCNTEXT3_AUDIOBUF_CLKEN, |
| MCLKCNTEXT3); |
| |
| /* C Buffer : size=56KB, under level=55kB */ |
| |
| putreg32(base, BUF_BASE('C')); |
| putreg32(1024 * 56, BUF_SIZE('C')); |
| base += 1024 * 56; |
| putreg32(1024 * 55, BUF_ULVL('C')); |
| |
| /* Setup F Buffer : size=512B */ |
| |
| putreg32(base, BUF_BASE('F')); |
| putreg32(512, BUF_SIZE('F')); |
| base += 512; |
| |
| /* Setup I Buffer : size=2KB (TODO) */ |
| |
| putreg32(base, BUF_BASE('I')); |
| putreg32(2048 * 2, BUF_SIZE('I')); |
| base += (2048 * 2); |
| |
| /* Setup J Buffer: size=4KB, over level=1KB */ |
| |
| putreg32(base, BUF_BASE('J')); |
| putreg32(4096, BUF_SIZE('J')); |
| base += 4096; |
| putreg32(1024, BUF_OLVL('J')); |
| |
| /* Clear Audio Buffer */ |
| |
| putreg32(0xffff, ABUFCLR); |
| |
| /* Access Enable */ |
| |
| modifyreg32(ABUFACCEN, |
| 0, |
| ABUFACCEN_RDCTEN('D') | |
| ABUFACCEN_CDCEN('F') |
| ); |
| |
| /* Source of F Buffer is D */ |
| |
| modifyreg32(BUFCTL('F'), 0, BUFID('D') << 16); |
| |
| /* PCM0: BCK0/LRCK0=master, MCLK0=master */ |
| |
| putreg32(AUDSEL_PCM0_MODE | |
| AUDSEL_PCM0_MODEM, |
| AUDSEL); |
| |
| /* LRCK0/BCK0: 1/1fs, BCK0:64fs, BCK1:64fs */ |
| |
| putreg32(0x00001010, |
| PCMCTL); |
| |
| /* Enable DOUT0/LRCK0/MCL0/BCK0 */ |
| |
| putreg32(PCMOUTEN_DOUT0EN | |
| PCMOUTEN_LRCK0EN | |
| PCMOUTEN_MCLK0EN | |
| PCMOUTEN_BCK0EN, |
| PCMOUTEN); |
| |
| /* Stereo, PCMDLY=1, LRCK active low, |
| * MSB first and left justified, 32bit |
| */ |
| |
| putreg32(0x64, PSCTL); |
| |
| /* Enable function clocks */ |
| |
| putreg32(CLOCKEN_FCE_PCKGEN | |
| CLOCKEN_FCE_PCMPS0 | |
| CLOCKEN_FCE_VOLPS0 | |
| CLOCKEN_FCE_MUTED | |
| CLOCKEN_FCE_SSRC | |
| CLOCKEN_FCE_VOLD, |
| CLOCKEN); |
| |
| /* SSRC = 44.1k(in)/44.1k(out) */ |
| |
| putreg32(44100 << 13, SSRC_FSI); |
| putreg32(44100 << 0, SSRC_FSO); |
| |
| /* Start SSRC */ |
| |
| putreg32(0x1, SSRC_MODE); |
| while (getreg32(SSRC_STATUS) != 0x1); |
| |
| audinfo("DTCAP(C)=0x%08x\n", BUF_DTCAP('C')); |
| audinfo("DTCAP(I)=0x%08x\n", BUF_DTCAP('I')); |
| audinfo("DTCAP(J)=0x%08x\n", BUF_DTCAP('J')); |
| |
| /* Setup default tx threshold */ |
| |
| _setup_tx_threshold(0); |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: lc823450_i2sdev_initialize |
| ****************************************************************************/ |
| |
| struct i2s_dev_s *lc823450_i2sdev_initialize(void) |
| { |
| struct lc823450_i2s_s *priv = NULL; |
| |
| /* The support STM32 parts have only a single I2S port */ |
| |
| /* Allocate a new state structure for this chip select. NOTE that there |
| * is no protection if the same chip select is used in two different |
| * chip select structures. |
| */ |
| |
| priv = kmm_zalloc(sizeof(struct lc823450_i2s_s)); |
| if (!priv) |
| { |
| i2serr("ERROR: Failed to allocate a chip select structure\n"); |
| return NULL; |
| } |
| |
| /* Initialize the common parts for the I2S device structure */ |
| |
| priv->dev.ops = &g_i2sops; |
| |
| lc823450_i2s_configure(); |
| |
| #ifdef BEEP_TEST |
| lc823450_i2s_beeptest(); |
| #endif |
| |
| #if 1 |
| /* NOTE: should be moved to another codec driver */ |
| |
| lc823450_dmic_enable(); |
| #endif |
| |
| _hrxdma = lc823450_dmachannel(DMA_CHANNEL_VIRTUAL); |
| _htxdma = lc823450_dmachannel(DMA_CHANNEL_VIRTUAL); |
| |
| #ifdef CONFIG_SMP |
| cpu_set_t cpuset0; |
| cpu_set_t cpuset1; |
| |
| CPU_ZERO(&cpuset1); |
| CPU_SET(0, &cpuset1); |
| |
| /* Backup the current affinity */ |
| |
| nxsched_get_affinity(nxsched_gettid(), sizeof(cpuset0), &cpuset0); |
| |
| /* Set the new affinity which assigns to CPU0 */ |
| |
| nxsched_set_affinity(nxsched_gettid(), sizeof(cpuset1), &cpuset1); |
| nxsig_usleep(10 * 1000); |
| #endif |
| |
| irq_attach(LC823450_IRQ_AUDIOBUF0, _i2s_isr, NULL); |
| |
| /* Enable IRQ for Audio Buffer */ |
| |
| up_enable_irq(LC823450_IRQ_AUDIOBUF0); |
| |
| #ifdef CONFIG_SMP |
| /* Restore the original affinity */ |
| |
| nxsched_set_affinity(nxsched_gettid(), sizeof(cpuset0), &cpuset0); |
| nxsig_usleep(10 * 1000); |
| #endif |
| |
| /* Success exit */ |
| |
| return &priv->dev; |
| } |
| |
| /**************************************************************************** |
| * Name: up_audio_bufcapacity |
| ****************************************************************************/ |
| |
| uint32_t up_audio_bufcapacity(void) |
| { |
| return (100 * getreg32(BUF_DTCAP('C'))) / getreg32(BUF_SIZE('C')); |
| } |