/* * Copyright (C) 2011 LAPIS Semiconductor Co., Ltd. * * 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; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "ioh_i2s.h" #include "ioh_i2s_config.h" #include "ml7213ioh-plat.h" #define USE_PERIODS_MIN (I2S_DMA_SG_MAX) #define USE_PERIODS_MAX (I2S_DMA_SG_MAX) #ifndef add_capture_constraints #define add_capture_constraints(x) 0 #endif #define I2S_WRITE_MASTER_BIT (0) #define I2S_READ_MASTER_BIT (8) #define I2S_WRITE_ENABLE_BIT (0) #define I2S_READ_ENABLE_BIT (8) /* RW flag */ #define SND_CAPTURE_SUBSTREAM 0 #define SND_PLAYBACK_SUBSTREAM 1 /* Codec Device Address */ #define I2SCNTRX_RXTEL BIT(0) #define I2SCNTRX_RXDABIT_8BIT 0 #define I2SCNTRX_RXDABIT_14BIT BIT(8) #define I2SCNTRX_RXDABIT_16BIT BIT(9) #define I2SCNTRX_RXDABIT_18BIT (BIT(8) | BIT(9)) #define I2SCNTRX_RXDABIT_20BIT BIT(10) #define I2SCNTRX_RXDABIT_24BIT (BIT(8) | BIT(10)) #define I2SCNTRX_RXDLYOFF BIT(12) #define I2SCNTRX_RX_LSB BIT(13) #define I2SCNTTX_TXTEL I2SCNTRX_RXTEL #define I2SCNTTX_TXDABIT_8BIT I2SCNTRX_RXDABIT_8BIT #define I2SCNTTX_TXDABIT_14BIT I2SCNTRX_RXDABIT_14BIT #define I2SCNTTX_TXDABIT_16BIT I2SCNTRX_RXDABIT_16BIT #define I2SCNTTX_TXDABIT_18BIT I2SCNTRX_RXDABIT_18BIT #define I2SCNTTX_TXDABIT_20BIT I2SCNTRX_RXDABIT_20BIT #define I2SCNTTX_TXDABIT_24BIT I2SCNTRX_RXDABIT_24BIT #define I2SCNTTX_TXDLYOFF I2SCNTRX_RXDLYOFF #define I2SCNTTX_TX_LSB I2SCNTRX_RX_LSB #define I2SCLKCNT_MCLKFS_64FS 0 #define I2SCLKCNT_MCLKFS_128FS BIT(8) #define I2SCLKCNT_MCLKFS_192FS BIT(9) #define I2SCLKCNT_MCLKFS_256FS (BIT(8) | BIT(9)) #define I2SCLKCNT_MCLKFS_384FS BIT(10) #define I2SCLKCNT_MCLKFS_512FS (BIT(8) | BIT(10)) #define I2SCLKCNT_MCLKFS_768FS (BIT(9) | BIT(10)) #define I2SCLKCNT_MCLKFS_1024FS (BIT(8) | BIT(9) | BIT(10)) #define I2SCLKCNT_BCLKFS_8FS 0 #define I2SCLKCNT_BCLKFS_16FS BIT(12) #define I2SCLKCNT_BCLKFS_32FS BIT(13) #define I2SCLKCNT_BCLKFS_64FS (BIT(12) | BIT(13)) #define I2SCLKCNT_MSSEL BIT(0) #define I2SCLKCNT_BCLKPOL BIT(1) #define I2SCLKCNT_LRCKFMT_I2S 0 #define I2SCLKCNT_LRCKFMT_LONG BIT(5) #define I2SCLKCNT_LRCKFMT_SHORT (BIT(4)|BIT(5)) #define DRV_NAME "ml7213_ioh_i2s" #define PCI_VENDOR_ID_ROHM 0X10DB #define PCI_DEVICE_ID_ML7213_I2S 0X8033 struct snd_ml7213i2s { struct snd_card *card; struct snd_pcm *pcm; struct ioh_i2s_data_pci *pci_dat; }; struct cbdata { struct ioh_i2s_data *priv; int stop; int cnt; }; struct snd_ml7213i2s_pcm { struct snd_ml7213i2s *ml7213i2s; spinlock_t lock; unsigned int irq_pos; unsigned int buf_pos; struct snd_pcm_substream *substream; struct cbdata cbd; /* i2s callback info */ unsigned int channels; unsigned int rw; unsigned int rate; unsigned int ch; unsigned int setup_flag; unsigned int format; unsigned int bclkfs; unsigned int master_mode; unsigned int enable_mode; }; static struct snd_pcm_hardware snd_card_ml7213i2s_capture[MAX_I2S_CH] = { { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH0, .rate_min = USE_RATE_MIN_CH0, .rate_max = USE_RATE_MAX_CH0, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_RX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_RX, .period_bytes_max = MAX_PERIOD_SIZE_RX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH1, .rate_min = USE_RATE_MIN_CH1, .rate_max = USE_RATE_MAX_CH1, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_RX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_RX, .period_bytes_max = MAX_PERIOD_SIZE_RX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH2, .rate_min = USE_RATE_MIN_CH2, .rate_max = USE_RATE_MAX_CH2, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_RX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_RX, .period_bytes_max = MAX_PERIOD_SIZE_RX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH3, .rate_min = USE_RATE_MIN_CH3, .rate_max = USE_RATE_MAX_CH3, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_RX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_RX, .period_bytes_max = MAX_PERIOD_SIZE_RX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH4, .rate_min = USE_RATE_MIN_CH4, .rate_max = USE_RATE_MAX_CH4, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_RX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_RX, .period_bytes_max = MAX_PERIOD_SIZE_RX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH5, .rate_min = USE_RATE_MIN_CH5, .rate_max = USE_RATE_MAX_CH5, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_RX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_RX, .period_bytes_max = MAX_PERIOD_SIZE_RX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, }; static struct snd_pcm_hardware snd_card_ml7213i2s_playback[MAX_I2S_CH] = { { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH0, .rate_min = USE_RATE_MIN_CH0, .rate_max = USE_RATE_MAX_CH0, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_TX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_TX, .period_bytes_max = MAX_PERIOD_SIZE_TX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH1, .rate_min = USE_RATE_MIN_CH1, .rate_max = USE_RATE_MAX_CH1, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_TX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_TX, .period_bytes_max = MAX_PERIOD_SIZE_TX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH2, .rate_min = USE_RATE_MIN_CH2, .rate_max = USE_RATE_MAX_CH2, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_TX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_TX, .period_bytes_max = MAX_PERIOD_SIZE_TX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH3, .rate_min = USE_RATE_MIN_CH3, .rate_max = USE_RATE_MAX_CH3, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_TX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_TX, .period_bytes_max = MAX_PERIOD_SIZE_TX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH4, .rate_min = USE_RATE_MIN_CH4, .rate_max = USE_RATE_MAX_CH4, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_TX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_TX, .period_bytes_max = MAX_PERIOD_SIZE_TX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SUPPORT_FORMAT, .rates = USE_RATE_CH5, .rate_min = USE_RATE_MIN_CH5, .rate_max = USE_RATE_MAX_CH5, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = (MAX_PERIOD_SIZE_TX * USE_PERIODS_MAX), .period_bytes_min = MAX_PERIOD_SIZE_TX, .period_bytes_max = MAX_PERIOD_SIZE_TX, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }, }; static int ignore_overrun = 1; static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ module_param(ignore_overrun, int, 0444); module_param(index, int, 0444); MODULE_PARM_DESC(ignore_overrun, "ignore RX overruns (default=0)"); MODULE_PARM_DESC(index, "Index value for ML7213 IOH I2S Controller Reference"); /***************************************************************************** * I2S HAL (Hardware Abstruction Layer) *****************************************************************************/ static void ioh_i2s_reset(struct ioh_i2s_data *priv) { int channel = priv->ch; iowrite32(1 << channel, priv->iobase + I2SSRST_OFFSET); iowrite32(0, priv->iobase + I2SSRST_OFFSET); } static void ioh_i2s_enable_interrupts(struct ioh_i2s_data *priv, enum dma_data_direction dir) { int channel = priv->ch; unsigned int intr_lines; if (dir) intr_lines = 1 << (I2S_IMASK_RX_BIT_START + channel); else intr_lines = 1 << (I2S_IMASK_TX_BIT_START + channel); /*enable interrupts for specified channel */ iowrite32(intr_lines, priv->iobase + I2SIMASKCLR_OFFSET); } static void ioh_i2s_disable_interrupts(struct ioh_i2s_data *priv, enum dma_data_direction dir) { int channel = priv->ch; unsigned int intr_lines; /*intr_lines&=I2S_ALL_INTERRUPT_BITS; */ intr_lines = ioread32(priv->iobase + I2SIMASK_OFFSET); /*disable interrupts for specified channel */ if (dir) intr_lines |= 1 << (I2S_IMASK_RX_BIT_START + channel); else intr_lines |= 1 << (I2S_IMASK_TX_BIT_START + channel); /*Mask the specific interrupt bits */ iowrite32(intr_lines, priv->iobase + I2SIMASK_OFFSET); } /* Run FIFO */ static void ioh_i2s_run_tx_fifo(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; u32 val; val = ioread32(priv->iobase + I2SFIFOCTX_OFFSET + offset); val |= I2S_FIFO_TX_RUN; iowrite32(val, priv->iobase + I2SFIFOCTX_OFFSET + offset); } /* Clear TX FIFO */ static void ioh_i2s_clear_tx_fifo(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; u32 val; val = ioread32(priv->iobase + I2SFIFOCTX_OFFSET + offset); val |= I2S_FIFO_TX_FCLR; iowrite32(val, priv->iobase + I2SFIFOCTX_OFFSET + offset); } /* Clear interrupt status */ static void ioh_i2s_clear_tx_sts_ir(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; iowrite32(I2S_TX_FINT | I2S_TX_AFINT | I2S_TX_EINT | I2S_TX_AEINT, priv->iobase + I2SISTTX_OFFSET + offset); } /* Run FIFO */ static void ioh_i2s_run_rx_fifo(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; u32 val; val = ioread32(priv->iobase + I2SFIFOCRX_OFFSET + offset); val |= I2S_FIFO_RX_RUN; iowrite32(val, priv->iobase + I2SFIFOCRX_OFFSET + offset); } /* Clear RX FIFO */ static void ioh_i2s_clear_rx_fifo(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; u32 val; val = ioread32(priv->iobase + I2SFIFOCRX_OFFSET + offset); val |= I2S_FIFO_RX_FCLR; iowrite32(val, priv->iobase + I2SFIFOCRX_OFFSET + offset); } /* Clear interrupt status */ static void ioh_i2s_clear_rx_sts_ir(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; iowrite32(I2S_RX_FINT | I2S_RX_AFINT | I2S_RX_EINT | I2S_RX_AEINT, priv->iobase + I2SISTRX_OFFSET + offset); } /* Clear DMA mask setting */ static void ioh_i2s_tx_clear_dma_mask(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; u32 val; val = ioread32(priv->iobase + I2SMSKTX_OFFSET + offset); val &= ~TX_BIT_DMAMSK; /* Enable Tx DMA Request */ iowrite32(val, priv->iobase + I2SMSKTX_OFFSET + offset); } /* Clear DMA mask setting */ static void ioh_i2s_rx_clear_dma_mask(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; u32 val; val = ioread32(priv->iobase + I2SMSKRX_OFFSET + offset); val &= ~RX_BIT_DMAMSK; /* Enable Rx DMA Request */ iowrite32(val, priv->iobase + I2SMSKRX_OFFSET + offset); } /* Clear the mask setting of the corresponding interrupt source bit */ static void ioh_i2s_enable_tx_empty_ir(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; u32 val; val = ioread32(priv->iobase + I2SMSKTX_OFFSET + offset); val &= ~TX_BIT_AEIMSK; /* Enable Almost empty interrupt */ val &= ~TX_BIT_EIMSK; /* Enable Empty interrupt */ iowrite32(val, priv->iobase + I2SMSKTX_OFFSET + offset); } /* Clear the mask setting of the corresponding interrupt source bit */ static void ioh_i2s_enable_rx_full_ir(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; u32 val; val = ioread32(priv->iobase + I2SMSKRX_OFFSET + offset); val &= ~RX_BIT_AFIMSK; /* Enable Almost empty interrupt */ val &= ~RX_BIT_FIMSK; /* Enable Empty interrupt */ iowrite32(val, priv->iobase + I2SMSKRX_OFFSET + offset); } static void ioh_i2s_disable_tx_empty_ir(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; u32 val; val = ioread32(priv->iobase + I2SMSKTX_OFFSET + offset); val |= TX_BIT_AEIMSK; /* Disble Almost empty interrupt */ val |= TX_BIT_EIMSK; /* Disble Empty interrupt */ iowrite32(val, priv->iobase + I2SMSKTX_OFFSET + offset); } static void ioh_i2s_disable_rx_full_ir(struct ioh_i2s_data *priv) { int ch = priv->ch; int offset = ch * 0x800; u32 val; val = ioread32(priv->iobase + I2SMSKRX_OFFSET + offset); val |= RX_BIT_AFIMSK; /* Disble Almost full interrupt */ val |= RX_BIT_FIMSK; /* Disble full interrupt */ iowrite32(val, priv->iobase + I2SMSKRX_OFFSET + offset); } /***************************************************************************** * I2S Middle ware *****************************************************************************/ static bool filter(struct dma_chan *chan, void *slave) { struct pch_dma_slave *param = slave; if ((chan->chan_id == param->chan_id) && (param->dma_dev == chan->device->dev)) { chan->private = param; return true; } else { return false; } } static struct dma_chan *ioh_request_dma_channel_common( struct ioh_i2s_data *priv, char *chan_name, enum dma_data_direction dir) { dma_cap_mask_t mask; struct dma_chan *chan; struct pci_dev *dma_dev; dma_cap_zero(mask); dma_cap_set(DMA_SLAVE, mask); dma_dev = pci_get_bus_and_slot(2, PCI_DEVFN(0, 1)); /* Get DMA's dev information */ if (dir == DMA_FROM_DEVICE) { /* Rx */ priv->param_rx.width = priv->dma_rx_width; priv->param_rx.dma_dev = &dma_dev->dev; priv->param_rx.chan_id = priv->ch * 2 + 1; /* ch Rx=1,3,...11 */ priv->param_rx.rx_reg = (dma_addr_t)(priv->mapbase +\ priv->ch * 0x800 +\ I2SDRRXMIRROR_OFFSET); chan = dma_request_channel(mask, filter, &priv->param_rx); if (chan == NULL) { dev_err(priv->dev, "Failed dma_request_channel %s for" " I2S %s\n", chan_name, priv->rx_name); return NULL; } priv->chan_rx = chan; } else if (dir == DMA_TO_DEVICE) { /* Tx */ priv->param_tx.width = priv->dma_tx_width; priv->param_tx.dma_dev = &dma_dev->dev; priv->param_tx.chan_id = priv->ch * 2; /* DMA ch Tx=0,2,...10 */ priv->param_tx.tx_reg = (dma_addr_t)(priv->mapbase +\ priv->ch * 0x800 +\ I2SDRTXMIRROR_OFFSET); priv->param_tx.tx_reg_list = NULL; chan = dma_request_channel(mask, filter, &priv->param_tx); if (chan == NULL) { dev_err(priv->dev, "Failed dma_request_channel %s for" " I2S %s\n", chan_name, priv->tx_name); return NULL; } priv->chan_tx = chan; } else { dev_err(priv->dev, "%s:Invalid direction (%d)\n", chan_name, dir); return NULL; } return chan; } static void ioh_setup_rx_dma(struct ioh_i2s_data *priv) { ioh_request_dma_channel_common( priv, priv->dma_config->rx_chan, DMA_FROM_DEVICE); } static void ioh_setup_tx_dma(struct ioh_i2s_data *priv) { ioh_request_dma_channel_common( priv, priv->dma_config->tx_chan, DMA_TO_DEVICE); } static void ioh_i2s_ignore_rx_overrun(struct ioh_i2s_data *priv) { priv->ignore_rx_overrun = 1; } static void ioh_i2s_write(struct ioh_i2s_data *priv, const void *data, int len) { int rem1; int rem2; int tx_index; struct scatterlist *sg = priv->sg_tx_p; int t_num = 0; int l; int *ptr_fmt; int *ptr32; short *ptr16; char *ptr8; if (priv->tx_avail >= INTER_BUFF_SIZE) { dev_err(priv->dev, "%s[%d]: internal buffer full\n", __func__, priv->ch); return; } dev_dbg(priv->dev, "%s: [ch%d] len=%d data_head=%p data_complete=%p", __func__, priv->ch, len, priv->tx_data_head, priv->tx_complete); if ((priv->tx_data_head + ((len/priv->dma_tx_unit)*4)) <= priv->tx_tail) { tx_index = (int)(priv->tx_data_head - priv->tx_head) / (I2S_AEMPTY_THRESH * 4); sg = sg + tx_index; t_num = len/(I2S_AEMPTY_THRESH * priv->dma_tx_unit); dma_sync_sg_for_cpu(priv->dev, sg, t_num, DMA_TO_DEVICE); ptr_fmt = (int *)priv->tx_data_head; switch (priv->dma_tx_unit) { case 1: ptr8 = (char *)data; for (l = 0; l < (len/priv->dma_tx_unit); l++) *ptr_fmt++ = (int)*ptr8++; break; case 2: ptr16 = (short *)data; for (l = 0; l < (len/priv->dma_tx_unit); l++) *ptr_fmt++ = (int)*ptr16++; break; case 4: ptr32 = (int *)data; for (l = 0; l < (len/priv->dma_tx_unit); l++) *ptr_fmt++ = *ptr32++; break; } dma_sync_sg_for_device(priv->dev, sg, t_num, DMA_TO_DEVICE); priv->tx_data_head += (len/priv->dma_tx_unit)*4; } else { rem1 = (priv->tx_tail - priv->tx_data_head)/4; rem2 = (len/priv->dma_tx_unit) - rem1; tx_index = (int)(priv->tx_data_head-priv->tx_head) / (I2S_AEMPTY_THRESH * 4); sg = sg + tx_index; t_num = rem1/I2S_AEMPTY_THRESH; dma_sync_sg_for_cpu(priv->dev, sg, t_num, DMA_TO_DEVICE); ptr_fmt = (int *)priv->tx_data_head; switch (priv->dma_tx_unit) { case 1: ptr8 = (char *)data; for (l = 0; l < rem1; l++) *ptr_fmt++ = (int)*ptr8++; break; case 2: ptr16 = (short *)data; for (l = 0; l < rem1; l++) *ptr_fmt++ = (int)*ptr16++; break; case 4: ptr32 = (int *)data; for (l = 0; l < rem1; l++) *ptr_fmt++ = *ptr32++; break; } dma_sync_sg_for_device(priv->dev, sg, t_num, DMA_TO_DEVICE); priv->tx_data_head = priv->tx_head; sg = priv->sg_tx_p; t_num = rem2/I2S_AEMPTY_THRESH; dma_sync_sg_for_cpu(priv->dev, sg, t_num, DMA_TO_DEVICE); ptr_fmt = (int *)priv->tx_data_head; switch (priv->dma_tx_unit) { case 1: ptr8 = (char *)(data+rem1*priv->dma_tx_unit); for (l = 0; l < rem2; l++) *ptr_fmt++ = (int)*ptr8++; break; case 2: ptr16 = (short *)(data+rem1*priv->dma_tx_unit); for (l = 0; l < rem2; l++) *ptr_fmt++ = (int)*ptr16++; break; case 4: ptr32 = (int *)(data+rem1*priv->dma_tx_unit); for (l = 0; l < rem2; l++) *ptr_fmt++ = *ptr32++; break; } dma_sync_sg_for_device(priv->dev, sg, t_num, DMA_TO_DEVICE); priv->tx_data_head += rem2*4; } if (priv->tx_data_head >= priv->tx_tail) priv->tx_data_head = priv->tx_head; dev_dbg(priv->dev, "-->data_head=%p\n", priv->tx_data_head); priv->tx_avail += (len/priv->dma_tx_unit)*4; } static void ioh_i2s_stop_i2s_regs(struct ioh_i2s_data *priv, unsigned long bitrate, struct ioh_i2s_config_reg *config, enum ioh_direction dir, unsigned int flag) { int ch = priv->ch; if (dir) { /* Interrupt stop */ ioh_i2s_disable_rx_full_ir(priv); /* FIFO setting */ ioh_i2s_clear_rx_fifo(priv); ioh_i2s_clear_rx_sts_ir(priv); } else { /* Interrupt stop */ ioh_i2s_disable_tx_empty_ir(priv); /* FIFO setting */ ioh_i2s_clear_tx_fifo(priv); ioh_i2s_clear_tx_sts_ir(priv); } /* Common register */ if (!flag) { iowrite32(config->cmn.i2sclkcnt, priv->iobase + I2SCLKCNT0_OFFSET + 0x10*ch); } } static void ioh_i2s_configure_i2s_regs(struct ioh_i2s_data *priv, unsigned long bitrate, struct ioh_i2s_config_reg *config, enum ioh_direction dir, unsigned int unit) { int ch = priv->ch; int offset = ch * 0x800; /* Common register */ iowrite32(config->cmn.i2sclkcnt, priv->iobase + I2SCLKCNT0_OFFSET + 0x10*ch); if (dir) { priv->dma_rx_unit = unit; /* Rx register */ iowrite32(config->rx.i2scntrx, priv->iobase + I2SCNTRX_OFFSET + offset); iowrite32(config->rx.i2sfifocrx, priv->iobase + I2SFIFOCRX_OFFSET + offset); iowrite32(config->rx.i2safrx, priv->iobase + I2SAFRX_OFFSET + offset); iowrite32(config->rx.i2saerx, priv->iobase + I2SAERX_OFFSET + offset); iowrite32(config->rx.i2smskrx, priv->iobase + I2SMSKRX_OFFSET + offset); iowrite32(config->rx.i2sistrx, priv->iobase + I2SISTRX_OFFSET + offset); /* FIFO setting */ ioh_i2s_clear_rx_fifo(priv); ioh_i2s_run_rx_fifo(priv); /* Interrupt setting */ ioh_i2s_clear_rx_sts_ir(priv); ioh_i2s_enable_rx_full_ir(priv); } else { priv->dma_tx_unit = unit; /* Tx register */ iowrite32(config->tx.i2scnttx, priv->iobase + I2SCNTTX_OFFSET + offset); iowrite32(config->tx.i2sfifoctx, priv->iobase + I2SFIFOCTX_OFFSET + offset); iowrite32(config->tx.i2saftx, priv->iobase + I2SAFTX_OFFSET + offset); iowrite32(config->tx.i2saetx, priv->iobase + I2SAETX_OFFSET + offset); iowrite32(config->tx.i2smsktx, priv->iobase + I2SMSKTX_OFFSET + offset); iowrite32(config->tx.i2sisttx, priv->iobase + I2SISTTX_OFFSET + offset); /* FIFO setting */ ioh_i2s_clear_tx_fifo(priv); ioh_i2s_run_tx_fifo(priv); /* Interrupt setting */ ioh_i2s_clear_tx_sts_ir(priv); ioh_i2s_enable_tx_empty_ir(priv); } } static void i2s_rx_tasklet(unsigned long data) { struct ioh_i2s_data *priv = (struct ioh_i2s_data *)data; int num = 0; if (priv->rxexe_flag) { if (priv->rx_done) { switch (priv->dma_rx_unit) { case 1: num = priv->rx_avail/4; break; case 2: num = priv->rx_avail/2; break; case 4: num = priv->rx_avail; break; } priv->rx_done(priv->rx_callback_data, IOH_EOK, num, num); } } } static void i2s_tx_tasklet(unsigned long data) { struct ioh_i2s_data *priv = (struct ioh_i2s_data *)data; int num = 0; int avail = 0; if (priv->txexe_flag) { if (priv->tx_done) { switch (priv->dma_tx_unit) { case 1: num = (INTER_BUFF_SIZE - priv->tx_avail)/4; avail = priv->tx_avail/4; break; case 2: num = (INTER_BUFF_SIZE - priv->tx_avail)/2; avail = priv->tx_avail/2; break; case 4: num = (INTER_BUFF_SIZE - priv->tx_avail); avail = priv->tx_avail; break; } priv->tx_done(priv->tx_callback_data, IOH_EOK, num, avail); } } } static void ioh_i2s_release(struct ioh_i2s_data *priv, enum ioh_direction dir) { if (!priv) { dev_err(priv->dev, "%s: i2s is NULL\n", __func__); return; } if (dir) { dma_sync_sg_for_cpu(priv->dev, priv->sg_rx_p, priv->rx_nent, DMA_FROM_DEVICE); ioh_i2s_disable_interrupts(priv, IOH_CAPTURE); ioh_i2s_disable_rx_full_ir(priv); if (priv->chan_rx) { priv->chan_rx->device->device_control(priv->chan_rx, DMA_TERMINATE_ALL, 0); dma_release_channel(priv->chan_rx); priv->chan_rx = NULL; } kfree(priv->sg_rx_p); if (priv->rxbuf_virt) dma_free_coherent(priv->dev, INTER_BUFF_SIZE, priv->rxbuf_virt, priv->rx_buf_dma); priv->rxbuf_virt = NULL; priv->rx_buf_dma = 0; atomic_dec(&priv->rx_busy); tasklet_disable(&priv->rx_tasklet); tasklet_kill(&priv->rx_tasklet); } else { dma_sync_sg_for_cpu(priv->dev, priv->sg_tx_p, priv->tx_nent, DMA_TO_DEVICE); ioh_i2s_disable_interrupts(priv, IOH_PLAYBACK); ioh_i2s_disable_tx_empty_ir(priv); if (priv->chan_tx) { priv->chan_tx->device->device_control(priv->chan_tx, DMA_TERMINATE_ALL, 0); dma_release_channel(priv->chan_tx); priv->chan_tx = NULL; } kfree(priv->sg_tx_p); if (priv->txbuf_virt) dma_free_coherent(priv->dev, INTER_BUFF_SIZE, priv->txbuf_virt, priv->tx_buf_dma); priv->txbuf_virt = NULL; priv->tx_buf_dma = 0; atomic_dec(&priv->tx_busy); tasklet_disable(&priv->tx_tasklet); tasklet_kill(&priv->tx_tasklet); } } static struct ioh_i2s_data *ioh_i2s_open(int ch, enum ioh_direction dir, const char *name, void *cbd, void (*cb)(void *cbd, int status, int num, int avail)) { struct ioh_i2s_data *obj = NULL; struct scatterlist *sg; int rx_size; int rx_num; int tx_size; int tx_num; int i; if (ch >= MAX_I2S_IF) { dev_err(obj->dev, "Tried to open i2s with number %d which is more then" " the available number\n", ch); return 0; } obj = kzalloc(sizeof(*obj), GFP_KERNEL); obj->ignore_rx_overrun = 0; obj->dma_tx_width = PCH_DMA_WIDTH_4_BYTES; obj->dma_rx_width = PCH_DMA_WIDTH_4_BYTES; obj->ch = ch; obj->dev = obj->dev; atomic_set(&obj->rx_busy, 0); atomic_set(&obj->tx_busy, 0); strcpy(obj->rx_name, "FREE"); strcpy(obj->tx_name, "FREE"); if (dir) { /* Rx configuration */ if (atomic_read(&obj->rx_busy)) { dev_err(obj->dev, "rx i2s%d have already opened\n", ch); atomic_dec(&obj->rx_busy); return 0; } atomic_inc(&obj->rx_busy); strcpy(obj->rx_name, name); ioh_setup_rx_dma(obj); if (!obj->chan_rx) { dev_err(obj->dev, "%s:ioh_setup_rx_dma failed\n", __func__); return NULL; } obj->rxbuf_virt = dma_alloc_coherent(obj->dev, INTER_BUFF_SIZE, &obj->rx_buf_dma, GFP_KERNEL); if (!obj->rxbuf_virt) { dev_err(obj->dev, "dma_alloc_coherent Failed\n"); return NULL; } rx_size = I2S_AFULL_THRESH * 4; /* The number of scatter list (Franction area is not used) */ rx_num = INTER_BUFF_SIZE / rx_size; dev_dbg(obj->dev, "%s: rx: scatter_num=%d scatter_size=%d\n", __func__, rx_num, rx_size); obj->sg_rx_p =\ kzalloc(sizeof(struct scatterlist) *rx_num, GFP_ATOMIC); sg = obj->sg_rx_p; sg_init_table(sg, rx_num); /* Initialize SG table */ for (i = 0; i < rx_num; i++, sg++) { sg_set_page(sg, virt_to_page(obj->rxbuf_virt), rx_size, rx_size * i); sg_dma_len(sg) = rx_size / 4; sg_dma_address(sg) = obj->rx_buf_dma + sg->offset; } obj->rx_head = (unsigned char *)obj->rxbuf_virt; obj->rx_tail = (unsigned char *)obj->rxbuf_virt + rx_num * rx_size; obj->rx_data_head = (unsigned char *)obj->rxbuf_virt; obj->rx_complete = (unsigned char *)obj->rxbuf_virt; obj->rx_avail = 0; obj->rx_nent = rx_num; dma_sync_sg_for_device(obj->dev, obj->sg_rx_p, obj->rx_nent, DMA_FROM_DEVICE); tasklet_init(&obj->rx_tasklet, i2s_rx_tasklet, (unsigned long)obj); } else { /* Tx configuration */ if (atomic_read(&obj->tx_busy)) { dev_err(obj->dev, "tx i2s%d have already opened\n", ch); atomic_dec(&obj->tx_busy); return 0; } atomic_inc(&obj->tx_busy); strcpy(obj->tx_name, name); ioh_setup_tx_dma(obj); if (!obj->chan_tx) { dev_err(obj->dev, "%s:ioh_setup_tx_dma failed\n", __func__); return NULL; } tx_size = I2S_AEMPTY_THRESH * 4; if (INTER_BUFF_SIZE % tx_size) /* tx_num = The number of scatter list */ tx_num = INTER_BUFF_SIZE / tx_size + 1; else tx_num = INTER_BUFF_SIZE / tx_size; obj->txbuf_virt = dma_alloc_coherent(obj->dev, INTER_BUFF_SIZE, &obj->tx_buf_dma, GFP_KERNEL); if (!obj->txbuf_virt) { dev_err(obj->dev, "dma_alloc_coherent Failed\n"); return NULL; } obj->tx_head = (unsigned char *)obj->txbuf_virt; obj->tx_tail = (unsigned char *)obj->txbuf_virt + INTER_BUFF_SIZE; obj->tx_data_head = (unsigned char *)obj->txbuf_virt; obj->tx_complete = (unsigned char *)obj->txbuf_virt; obj->tx_avail = 0; dev_dbg(obj->dev, "%s: tx: scatter_num=%d scatter_size=%d\n", __func__, tx_num, tx_size); obj->sg_tx_p =\ kzalloc(sizeof(struct scatterlist) *tx_num, GFP_ATOMIC); sg_init_table(obj->sg_tx_p, tx_num); /* Initialize SG table */ sg = obj->sg_tx_p; for (i = 0; i < tx_num; i++, sg++) { if (i == (tx_num - 1)) { if (INTER_BUFF_SIZE % tx_size) { sg_set_page(sg, virt_to_page(obj->txbuf_virt), INTER_BUFF_SIZE % tx_size, tx_size * i); sg_dma_len(sg) = (INTER_BUFF_SIZE % tx_size) / 4; } else { sg_set_page(sg, virt_to_page(obj->txbuf_virt), tx_size, tx_size * i); sg_dma_len(sg) = tx_size\ / 4; } } else { sg_set_page(sg, virt_to_page(obj->txbuf_virt), tx_size, tx_size * i); sg_dma_len(sg) = tx_size / 4; } sg_dma_address(sg) = obj->tx_buf_dma + sg->offset; } obj->tx_nent = tx_num; dma_sync_sg_for_device(obj->dev, obj->sg_tx_p, obj->tx_nent, DMA_TO_DEVICE); tasklet_init(&obj->tx_tasklet, i2s_tx_tasklet, (unsigned long)obj); } return obj; } static void ioh_i2s_read(struct ioh_i2s_data *priv, void *data, int len) { unsigned int rem1 = 0, rem2 = 0; struct scatterlist *sg = priv->sg_rx_p; int rx_index; int t_num = 0; int *ptr_fmt; int *ptr32; short *ptr16; char *ptr8; int l; switch (priv->dma_rx_unit) { case 1: t_num = priv->rx_avail/4; break; case 2: t_num = priv->rx_avail/2; break; case 4: t_num = priv->rx_avail; break; } if (t_num < len) { dev_err(priv->dev, "%s[%d]: internal buffer empty\n", __func__, priv->ch); return; } if ((priv->rx_complete + ((len/priv->dma_rx_unit)*4)) <= priv->rx_tail) { rx_index = (int)(priv->rx_complete - priv->rx_head) / (I2S_AFULL_THRESH * 4); sg = sg + rx_index; t_num = len/(I2S_AFULL_THRESH * priv->dma_rx_unit); dma_sync_sg_for_cpu(priv->dev, sg, t_num, DMA_FROM_DEVICE); ptr_fmt = (int *)priv->rx_complete; switch (priv->dma_rx_unit) { case 1: ptr8 = (char *)data; for (l = 0; l < (len/priv->dma_rx_unit); l++) *ptr8++ = (char)*ptr_fmt++; break; case 2: ptr16 = (short *)data; for (l = 0; l < (len/priv->dma_rx_unit); l++) *ptr16++ = (short)*ptr_fmt++; break; case 4: ptr32 = (int *)data; for (l = 0; l < (len/priv->dma_rx_unit); l++) *ptr32++ = *ptr_fmt++; break; } dma_sync_sg_for_device(priv->dev, sg, t_num, DMA_FROM_DEVICE); priv->rx_complete += (len/priv->dma_rx_unit)*4; } else { rem1 = (priv->rx_tail - priv->rx_complete)/4; rem2 = (len/priv->dma_rx_unit) - rem1; rx_index = (int)(priv->rx_complete-priv->rx_head) / (I2S_AFULL_THRESH * 4); sg = sg + rx_index; t_num = rem1/I2S_AFULL_THRESH; dma_sync_sg_for_cpu(priv->dev, sg, t_num, DMA_FROM_DEVICE); ptr_fmt = (int *)priv->rx_complete; switch (priv->dma_rx_unit) { case 1: ptr8 = (char *)data; for (l = 0; l < rem1; l++) *ptr8++ = (char)*ptr_fmt++; break; case 2: ptr16 = (short *)data; for (l = 0; l < rem1; l++) *ptr16++ = (short)*ptr_fmt++; break; case 4: ptr32 = (int *)data; for (l = 0; l < rem1; l++) *ptr32++ = *ptr_fmt++; break; } dma_sync_sg_for_device(priv->dev, sg, t_num, DMA_FROM_DEVICE); priv->rx_complete = priv->rx_head; sg = priv->sg_rx_p; t_num = rem2/I2S_AFULL_THRESH; dma_sync_sg_for_cpu(priv->dev, sg, t_num, DMA_FROM_DEVICE); ptr_fmt = (int *)priv->rx_complete; switch (priv->dma_rx_unit) { case 1: ptr8 = (char *)(data+rem1*priv->dma_rx_unit); for (l = 0; l < rem2; l++) *ptr8++ = (char)*ptr_fmt++; break; case 2: ptr16 = (short *)(data+rem1*priv->dma_rx_unit); for (l = 0; l < rem2; l++) *ptr16++ = (short)*ptr_fmt++; break; case 4: ptr32 = (int *)(data+rem1*priv->dma_rx_unit); for (l = 0; l < rem2; l++) *ptr32++ = *ptr_fmt++; break; } dma_sync_sg_for_device(priv->dev, sg, t_num, DMA_FROM_DEVICE); priv->rx_complete += rem2*4; } if (priv->rx_complete >= priv->rx_tail) priv->rx_complete = priv->rx_head; priv->rx_avail -= (len/priv->dma_rx_unit)*4; } static void i2s_dma_rx_complete(void *arg) { struct ioh_i2s_data *priv = arg; struct scatterlist *sg = priv->sg_rx_cur; int num = priv->rx_num; int i; async_tx_ack(priv->desc_rx); for (i = 0; i < num; i++, sg++) { priv->rx_data_head += sg_dma_len(sg) * 4; priv->rx_avail += sg_dma_len(sg) * 4; } if (priv->rx_data_head >= priv->rx_tail) priv->rx_data_head = priv->rx_head; ioh_i2s_clear_rx_sts_ir(priv); ioh_i2s_enable_rx_full_ir(priv); } static void i2s_dma_tx_complete(void *arg) { struct ioh_i2s_data *priv = arg; struct scatterlist *sg = priv->sg_tx_cur; int num = priv->tx_num; int i; async_tx_ack(priv->desc_tx); for (i = 0; i < num; i++, sg++) { priv->tx_complete += sg_dma_len(sg) * 4; priv->tx_avail -= sg_dma_len(sg) * 4; } if (priv->tx_complete >= priv->tx_tail) priv->tx_complete = priv->tx_head; ioh_i2s_clear_tx_sts_ir(priv); ioh_i2s_enable_tx_empty_ir(priv); } /***************************************************************************** * Interrupt control *****************************************************************************/ static void i2s_tx_almost_empty_ir(struct ioh_i2s_data *priv) { struct dma_async_tx_descriptor *desc; int num; int tx_comp_index; struct scatterlist *sg = priv->sg_tx_p; dev_dbg(priv->dev, "%s: data_head=%p data_complete=%p\n", __func__, priv->tx_data_head, priv->tx_complete); num = ((int)priv->tx_avail) / (I2S_AEMPTY_THRESH * 4); tx_comp_index = (((int)(priv->tx_complete - priv->tx_head))) /\ (I2S_AEMPTY_THRESH * 4); if ((tx_comp_index + num) >= priv->tx_nent) num = priv->tx_nent - tx_comp_index; if (num > I2S_DMA_SG_NUM) num = I2S_DMA_SG_NUM; if (!num) { dev_err(priv->dev, "%s:Internal buffer empty\n", __func__); tasklet_schedule(&priv->tx_tasklet); return; /* No data to transmit */ } sg = sg + tx_comp_index; /* Point head of sg must be sent */ priv->sg_tx_cur = sg; /* Save tx condition */ priv->tx_num = num; /* Save tx condition */ desc = priv->chan_tx->device->device_prep_slave_sg(priv->chan_tx, sg, num, DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!desc) { dev_err(priv->dev, "%s:device_prep_slave_sg Failed\n", __func__); return; } /* To prevent this function from calling again before DMA completion */ ioh_i2s_disable_tx_empty_ir(priv); priv->desc_tx = desc; desc->callback = i2s_dma_tx_complete; desc->callback_param = priv; atomic_inc(&priv->pending_tx); desc->tx_submit(desc); tasklet_schedule(&priv->tx_tasklet); } void i2s_tx_empty_ir(struct ioh_i2s_data *priv, int ch) { dev_warn(priv->dev, "%s:I2S under flow occurs(ch=%d)\n", __func__, ch); } void i2s_rx_full_ir(struct ioh_i2s_data *priv, int ch) { dev_warn(priv->dev, "%s:I2S overrun occurs(ch=%d)\n", __func__, ch); } static inline void ioh_i2s_interrupt_sub_tx(struct ioh_i2s_data *priv) { unsigned int status; int channel = priv->ch; int offset = channel * 0x800; status = ioread32(priv->iobase + I2SISTTX_OFFSET + offset); if (status & I2S_TX_EINT) i2s_tx_empty_ir(priv, channel); if (status & I2S_TX_AEINT) i2s_tx_almost_empty_ir(priv); /*Clear the interrupt status */ iowrite32(status, priv->iobase + I2SISTTX_OFFSET + offset); } static void i2s_rx_almost_full_ir(struct ioh_i2s_data *priv) { struct dma_async_tx_descriptor *desc; struct scatterlist *sg; int rx_data_index; int num; num = (int)(INTER_BUFF_SIZE - priv->rx_avail) / (I2S_AFULL_THRESH * 4); if (num < 1) { dev_err(priv->dev, "%s:Internal buffer full\n", __func__); tasklet_schedule(&priv->rx_tasklet); return; } sg = priv->sg_rx_p; rx_data_index = ((int)(priv->rx_data_head - priv->rx_head)) /\ (I2S_AFULL_THRESH * 4); if ((rx_data_index + num) >= priv->rx_nent) num = priv->rx_nent - rx_data_index; if (num > I2S_DMA_SG_NUM) num = I2S_DMA_SG_NUM; sg += rx_data_index; desc = priv->chan_rx->device->device_prep_slave_sg(priv->chan_rx, sg, num, DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!desc) { dev_err(priv->dev, "%s:device_prep_slave_sg Failed\n", __func__); return; } priv->sg_rx_cur = sg; /* Save rx condition */ ioh_i2s_disable_rx_full_ir(priv); priv->rx_num = num; priv->desc_rx = desc; desc->callback = i2s_dma_rx_complete; desc->callback_param = priv; desc->tx_submit(desc); tasklet_schedule(&priv->rx_tasklet); } static inline void ioh_i2s_interrupt_sub_rx(struct ioh_i2s_data *priv) { unsigned int status; int channel = priv->ch; int offset = channel * 0x800; status = ioread32(priv->iobase + I2SISTRX_OFFSET + offset); if (status & I2S_RX_FINT) i2s_rx_full_ir(priv, channel); if (status & I2S_RX_AFINT) i2s_rx_almost_full_ir(priv); /*Clear the interrupt status */ iowrite32(status, priv->iobase + I2SISTRX_OFFSET + offset); } void ioh_i2s_event(struct ioh_i2s_data *priv, u32 idisp) { unsigned long flags; spin_lock_irqsave(&priv->tx_lock, flags); if (idisp & BIT(priv->ch + 16)) { dev_dbg(priv->dev, "Rx%d interrupt occures\n", priv->ch); ioh_i2s_interrupt_sub_rx(priv); } if (idisp & BIT(priv->ch)) { dev_dbg(priv->dev, "Tx%d interrupt occures\n", priv->ch); ioh_i2s_interrupt_sub_tx(priv); } spin_unlock_irqrestore(&priv->tx_lock, flags); return; } static irqreturn_t ioh_i2s_irq(int irq, void *data) { struct pci_dev *pdev = (struct pci_dev *)data; struct ioh_i2s_data_pci *drvdata = pci_get_drvdata(pdev); int i; u32 idisp; struct ioh_i2s_data *priv; priv = &drvdata->devs[0]; idisp = ioread32(priv->iobase + I2SIDISP_OFFSET); for (i = 0; i < MAX_I2S_IF; i++) ioh_i2s_event(&drvdata->devs[i], idisp); return IRQ_HANDLED; } /***************************************************************************** * Sound Card *****************************************************************************/ static void i2s_read_period(struct snd_pcm_substream *substream) { struct snd_ml7213i2s_pcm *dpcm; struct cbdata *cbd; int period; void *read_ptr; int read_size; dpcm = substream->runtime->private_data; cbd = &dpcm->cbd; if (!cbd->priv) return; period = substream->runtime->period_size; read_ptr = substream->runtime->dma_area +(snd_pcm_lib_period_bytes(substream) * dpcm->irq_pos); read_size = period * (substream->runtime->sample_bits/8) * (substream->runtime->channels); ioh_i2s_read(cbd->priv, read_ptr, read_size); dpcm->irq_pos = (dpcm->irq_pos + 1) % substream->runtime->periods; } static void read_done(void *callback_data, int status, int num, int avail) { struct snd_pcm_substream *substream = (struct snd_pcm_substream *)callback_data; struct snd_ml7213i2s_pcm *dpcm; struct cbdata *cbd; dpcm = substream->runtime->private_data; if (num < snd_card_ml7213i2s_capture[dpcm->ch].period_bytes_max) return; cbd = &dpcm->cbd; switch (status) { case IOH_EOK: if (!cbd->stop) { i2s_read_period(substream); dpcm->buf_pos = (dpcm->buf_pos + 1) % substream->runtime->periods; snd_pcm_period_elapsed(dpcm->substream); } cbd->cnt++; break; case IOH_EDONE: pr_debug("Done stopping channel %d\n", cbd->stop); cbd->stop = 2; break; case IOH_EOVERRUN: if (ignore_overrun) pr_debug("overrun ignore\n"); else { pr_err("RX overrun\n"); cbd->stop = 2; } break; case IOH_EFRAMESYNC: pr_err("Frame sync error\n"); cbd->stop = 2; break; } if (cbd->stop) pr_debug("stopping... %d\n", cbd->stop); } static int i2s_config_rate_reg(struct ioh_i2s_config_reg *config, unsigned int rate, struct snd_ml7213i2s_pcm *dpcm) { unsigned int i2s_mclk; unsigned int ret; i2s_mclk = i2s_config_table[dpcm->ch+STEREO_OFFSET].i2s_mclk; if ((i2s_mclk/64) == rate) ret = ioh_mclkfs_64fs << I2SCLKCNT_MCLKFS_OFFSET; else if ((i2s_mclk/128) == rate) ret = ioh_mclkfs_128fs << I2SCLKCNT_MCLKFS_OFFSET; else if ((i2s_mclk/192) == rate) ret = ioh_mclkfs_192fs << I2SCLKCNT_MCLKFS_OFFSET; else if ((i2s_mclk/256) == rate) ret = ioh_mclkfs_256fs << I2SCLKCNT_MCLKFS_OFFSET; else if ((i2s_mclk/384) == rate) ret = ioh_mclkfs_384fs << I2SCLKCNT_MCLKFS_OFFSET; else if ((i2s_mclk/512) == rate) ret = ioh_mclkfs_512fs << I2SCLKCNT_MCLKFS_OFFSET; else if ((i2s_mclk/768) == rate) ret = ioh_mclkfs_768fs << I2SCLKCNT_MCLKFS_OFFSET; else if ((i2s_mclk/1024) == rate) ret = ioh_mclkfs_1024fs << I2SCLKCNT_MCLKFS_OFFSET; else ret = 0; return ret; } static int i2s_rx_configure_reg(struct ioh_i2s_config_reg *config, unsigned int rate, struct snd_ml7213i2s_pcm *dpcm) { int ret = 0; memset(config, 0, sizeof(*config)); /* Set ML7213 IOH register default value */ config->cmn.i2simask = 0x003f003f; config->rx.i2saerx = 0x1F; config->rx.i2smskrx = 0x1F; config->rx.i2sistrx = 0xC; /* Configuration */ if (dpcm->channels == 1) { config->rx.i2scntrx = i2s_config_table[dpcm->ch+MONAURAL_OFFSET].i2scntrx | dpcm->format | (ioh_tel_tel_fmt << I2SCNTRX_RX_TEL_OFFSET); config->cmn.i2sclkcnt = dpcm->bclkfs | i2s_config_table[dpcm->ch+MONAURAL_OFFSET].i2sclkcnt; } else { config->rx.i2scntrx = i2s_config_table[dpcm->ch+STEREO_OFFSET].i2scntrx | dpcm->format | (ioh_tel_i2s_fmt << I2SCNTRX_RX_TEL_OFFSET); config->cmn.i2sclkcnt = dpcm->bclkfs | i2s_config_table[dpcm->ch+STEREO_OFFSET].i2sclkcnt; } config->cmn.i2sclkcnt |= i2s_config_rate_reg(config, rate, dpcm); config->rx.i2safrx = I2S_AFULL_THRESH / 2; /* Almost full threshold */ if (((config->cmn.i2sclkcnt & 0x3000) == 0x3000) && ((config->cmn.i2sclkcnt & 0x7) == 0x2)) { pr_err("%s: Failed not support setting\n", __func__); ret = -1; } return ret; } static int setup_i2s_read(struct snd_pcm_substream *substream, int ch) { struct snd_ml7213i2s_pcm *dpcm; struct ioh_i2s_config_reg config; unsigned int master; int ret = 0; unsigned int byte; master = (i2s_config_table[ch].i2sclkcnt >> I2SCLKCNT_MSSEL_OFFSET) & 1; dpcm = substream->runtime->private_data; dpcm->cbd.priv = ioh_i2s_open(ch, IOH_CAPTURE, "radio-i2s-in", substream, read_done); dpcm->setup_flag = 1; if (!dpcm->cbd.priv) { pr_err("%s: Cannot open the device\n", __func__); return -1; } if (ignore_overrun) ioh_i2s_ignore_rx_overrun(dpcm->cbd.priv); ret = i2s_rx_configure_reg(&config, dpcm->rate, dpcm); if (master) dpcm->master_mode |= 1 << (ch + I2S_READ_MASTER_BIT); else dpcm->master_mode |= 0 << (ch + I2S_READ_MASTER_BIT); switch (dpcm->format) { case (ioh_dabit_8bit << I2SCNT_DABIT_OFFSET): byte = 1; break; case (ioh_dabit_16bit << I2SCNT_DABIT_OFFSET): byte = 2; break; case (ioh_dabit_24bit << I2SCNT_DABIT_OFFSET): byte = 4; break; default: pr_err("%s: format error\n", __func__); return -1; break; } ioh_i2s_configure_i2s_regs(dpcm->cbd.priv, 0, &config, IOH_CAPTURE, byte); return ret; } static void i2s_write_period(struct snd_pcm_substream *substream) { struct snd_ml7213i2s_pcm *dpcm; struct cbdata *cbd; int period; void *write_ptr; int write_size; dpcm = substream->runtime->private_data; cbd = &dpcm->cbd; if (!cbd->priv) return; period = substream->runtime->period_size; write_ptr = substream->runtime->dma_area +(snd_pcm_lib_period_bytes(substream) * dpcm->irq_pos); write_size = period * (substream->runtime->sample_bits/8) * (substream->runtime->channels); ioh_i2s_write(cbd->priv, write_ptr, write_size); dpcm->irq_pos = (dpcm->irq_pos + 1) % substream->runtime->periods; } static void write_done(void *callback_data, int status, int num, int avail) { struct snd_pcm_substream *substream = (struct snd_pcm_substream *)callback_data; struct snd_ml7213i2s_pcm *dpcm; struct cbdata *cbd; dpcm = substream->runtime->private_data; if (num < snd_card_ml7213i2s_playback[dpcm->ch].period_bytes_max) return; if (avail >= snd_card_ml7213i2s_playback[dpcm->ch].period_bytes_max*2) return; if (!substream) { pr_debug("%s:!substream NULL\n", __func__); return; } if (!substream->runtime) { pr_debug("%s:!substream->runtime NULL\n", __func__); return; } if (!substream->runtime->private_data) { pr_debug("%s:!substream->runtime->private_data NULL\n", __func__); return; } cbd = &dpcm->cbd; switch (status) { case IOH_EOK: if (!cbd->stop) { i2s_write_period(substream); dpcm->buf_pos = (dpcm->buf_pos + 1) % substream->runtime->periods; snd_pcm_period_elapsed(dpcm->substream); } cbd->cnt++; break; case IOH_EDONE: pr_debug("Done stopping channel %d\n", cbd->stop); cbd->stop = 2; break; default: pr_debug("%s:default(%d)\n", __func__, status); break; } } static int i2s_tx_configure_reg(struct ioh_i2s_config_reg *config, unsigned int rate, struct snd_ml7213i2s_pcm *dpcm) { int ret = 0; memset(config, 0, sizeof(*config)); /* Set ML7213 IOH register default value */ config->cmn.i2simask = 0x003f003f; config->tx.i2saftx = 0x0; config->tx.i2smsktx = 0x1F; config->tx.i2sisttx = 0xC; /* Configuration */ if (dpcm->channels == 1) { config->tx.i2scnttx = i2s_config_table[dpcm->ch+MONAURAL_OFFSET].i2scnttx | dpcm->format | (ioh_tel_tel_fmt << I2SCNTTX_TX_TEL_OFFSET); config->cmn.i2sclkcnt = dpcm->bclkfs | i2s_config_table[dpcm->ch+MONAURAL_OFFSET].i2sclkcnt; } else { config->tx.i2scnttx = i2s_config_table[dpcm->ch+STEREO_OFFSET].i2scnttx | dpcm->format | (ioh_tel_i2s_fmt << I2SCNTTX_TX_TEL_OFFSET); config->cmn.i2sclkcnt = dpcm->bclkfs | i2s_config_table[dpcm->ch+STEREO_OFFSET].i2sclkcnt; } config->cmn.i2sclkcnt |= i2s_config_rate_reg(config, rate, dpcm); config->tx.i2saetx = I2S_AEMPTY_THRESH / 2; /* Almost empty threshold */ if (((config->cmn.i2sclkcnt & 0x3000) == 0x3000) && ((config->cmn.i2sclkcnt & 0x7) == 0x2)) { pr_err("%s: Failed not support setting\n", __func__); ret = -1; } return ret; } static int setup_i2s_write(struct snd_pcm_substream *substream, int ch) { struct snd_ml7213i2s_pcm *dpcm; struct ioh_i2s_config_reg config; unsigned int master; int ret = 0; unsigned int byte; master = (i2s_config_table[ch].i2sclkcnt >> I2SCLKCNT_MSSEL_OFFSET) & 1; dpcm = substream->runtime->private_data; dpcm->cbd.priv = ioh_i2s_open(ch, IOH_PLAYBACK, "radio-i2s-out", substream, write_done); dpcm->setup_flag = 1; if (!dpcm->cbd.priv) { pr_err("%s: Cannot open the device\n", __func__); return -1; } if (ignore_overrun) ioh_i2s_ignore_rx_overrun(dpcm->cbd.priv); ret = i2s_tx_configure_reg(&config, dpcm->rate, dpcm); if (master) dpcm->master_mode |= 1 << (ch + I2S_WRITE_MASTER_BIT); else dpcm->master_mode |= 0 << (ch + I2S_WRITE_MASTER_BIT); switch (dpcm->format) { case (ioh_dabit_8bit << I2SCNT_DABIT_OFFSET): byte = 1; break; case (ioh_dabit_16bit << I2SCNT_DABIT_OFFSET): byte = 2; break; case (ioh_dabit_24bit << I2SCNT_DABIT_OFFSET): byte = 4; break; default: pr_err("%s: format error\n", __func__); return -1; break; } ioh_i2s_configure_i2s_regs(dpcm->cbd.priv, 0, &config, IOH_PLAYBACK, byte); return ret; } static void __snd_card_ml7213i2s_runtime_free(struct snd_pcm_runtime *runtime) { struct snd_ml7213i2s_pcm *dpcm; static int cnt; dpcm = (struct snd_ml7213i2s_pcm *)runtime->private_data; /* FIXME: This is just a big ball of race right now... */ if (!dpcm->cbd.stop) dpcm->cbd.stop = 1; else { while (dpcm->cbd.stop != 2) { if (cnt++ > 100) { pr_debug("oops, failed to close ml7213i2s..\n"); pr_debug("it's ok if i2s isn't running\n"); break; } msleep(20); } } } static void snd_card_ml7213i2s_runtime_capture_free (struct snd_pcm_runtime *runtime) { struct snd_ml7213i2s_pcm *dpcm; dpcm = (struct snd_ml7213i2s_pcm *)runtime->private_data; __snd_card_ml7213i2s_runtime_free(runtime); if (dpcm->setup_flag) ioh_i2s_release(dpcm->cbd.priv, IOH_CAPTURE); kfree(runtime->private_data); } static struct snd_ml7213i2s_pcm * new_pcm_stream(struct snd_pcm_substream *substream) { struct snd_ml7213i2s_pcm *dpcm; dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); if (!dpcm) return dpcm; spin_lock_init(&dpcm->lock); dpcm->substream = substream; return dpcm; } static int snd_card_ml7213i2s_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_ml7213i2s_pcm *dpcm; int err; dpcm = new_pcm_stream(substream); if (dpcm == NULL) return -ENOMEM; runtime->private_data = dpcm; /* makes the infrastructure responsible for freeing dpcm */ runtime->private_free = snd_card_ml7213i2s_runtime_capture_free; if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { dpcm->rw = SND_CAPTURE_SUBSTREAM; runtime->hw = snd_card_ml7213i2s_capture[substream->number]; } else { dpcm->rw = SND_PLAYBACK_SUBSTREAM; runtime->hw = snd_card_ml7213i2s_playback[substream->number]; } dpcm->setup_flag = 0; err = add_capture_constraints(runtime); if (err < 0) return err; return 0; } static int snd_card_ml7213i2s_close(struct snd_pcm_substream *substream) { return 0; } static int snd_card_ml7213i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_ml7213i2s_pcm *dpcm = runtime->private_data; unsigned int enable_flag = 0; unsigned int ch = substream->number; dpcm->channels = params_channels(hw_params); dpcm->ch = substream->number; dpcm->rate = params_rate(hw_params); switch (params_format(hw_params)) { case SNDRV_PCM_FORMAT_U8: dpcm->format = ioh_dabit_8bit << I2SCNT_DABIT_OFFSET; if (dpcm->channels == 1) dpcm->bclkfs = ioh_bclkfs_32fs << I2SCLKCNT_BCLKFS_OFFSET; else dpcm->bclkfs = ioh_bclkfs_32fs << I2SCLKCNT_BCLKFS_OFFSET; break; case SNDRV_PCM_FORMAT_S16_LE: dpcm->format = ioh_dabit_16bit << I2SCNT_DABIT_OFFSET; if (dpcm->channels == 1) dpcm->bclkfs = ioh_bclkfs_32fs << I2SCLKCNT_BCLKFS_OFFSET; else dpcm->bclkfs = ioh_bclkfs_32fs << I2SCLKCNT_BCLKFS_OFFSET; break; case SNDRV_PCM_FORMAT_S32_LE: dpcm->format = ioh_dabit_24bit << I2SCNT_DABIT_OFFSET; if (dpcm->channels == 1) dpcm->bclkfs = ioh_bclkfs_64fs << I2SCLKCNT_BCLKFS_OFFSET; else dpcm->bclkfs = ioh_bclkfs_64fs << I2SCLKCNT_BCLKFS_OFFSET; break; default: pr_err("%s: Failed not support format\n", __func__); return -1; break; } switch (dpcm->rw) { case SND_CAPTURE_SUBSTREAM: if (setup_i2s_read(substream, substream->number)) return -1; dpcm->enable_mode |= 1 << (ch + I2S_READ_ENABLE_BIT); enable_flag = (dpcm->enable_mode >> (ch+I2S_WRITE_ENABLE_BIT)) & 1; break; case SND_PLAYBACK_SUBSTREAM: if (setup_i2s_write(substream, substream->number)) return -1; dpcm->enable_mode |= 1 << (ch + I2S_WRITE_ENABLE_BIT); enable_flag = (dpcm->enable_mode >> (ch+I2S_READ_ENABLE_BIT)) & 1; break; default: return -1; } return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); } static int snd_card_ml7213i2s_hw_free(struct snd_pcm_substream *substream) { struct snd_ml7213i2s_pcm *dpcm = substream->runtime->private_data; struct ioh_i2s_config_reg config; unsigned int flag; unsigned int enable_flag = 0; unsigned int ch = substream->number; switch (dpcm->rw) { case SND_CAPTURE_SUBSTREAM: i2s_rx_configure_reg(&config, dpcm->rate, dpcm); config.cmn.i2sclkcnt &= ~I2SCLKCNT_MSSEL; dpcm->master_mode &= ~(1<<(ch+I2S_READ_MASTER_BIT)); flag = (dpcm->master_mode >> (ch+I2S_WRITE_MASTER_BIT)) & 1; dpcm->enable_mode &= ~(1<<(ch+I2S_READ_ENABLE_BIT)); enable_flag = (dpcm->enable_mode >> (ch+I2S_WRITE_ENABLE_BIT)) & 1; if (dpcm->setup_flag) { ioh_i2s_stop_i2s_regs(dpcm->cbd.priv, 0, &config, IOH_CAPTURE, flag); kfree(dpcm->cbd.priv); } break; case SND_PLAYBACK_SUBSTREAM: i2s_tx_configure_reg(&config, dpcm->rate, dpcm); config.cmn.i2sclkcnt &= ~I2SCLKCNT_MSSEL; dpcm->master_mode &= ~(1<<(ch+I2S_WRITE_MASTER_BIT)); flag = (dpcm->master_mode >> (ch+I2S_READ_MASTER_BIT)) & 1; dpcm->enable_mode &= ~(1<<(ch+I2S_WRITE_ENABLE_BIT)); enable_flag = (dpcm->enable_mode >> (ch+I2S_READ_ENABLE_BIT)) & 1; if (dpcm->setup_flag) { ioh_i2s_stop_i2s_regs(dpcm->cbd.priv, 0, &config, IOH_PLAYBACK, flag); kfree(dpcm->cbd.priv); } break; default: return -1; } if (!enable_flag) { pr_err("%s: enable_flag is NULL\n", __func__); return -1; } return snd_pcm_lib_free_pages(substream); } void ioh_i2s_irq_stop(struct ioh_i2s_data *priv, enum ioh_direction dir) { if (!priv) { dev_err(priv->dev, "%s: i2s is NULL\n", __func__); return; } if (dir) { ioh_i2s_disable_interrupts(priv, IOH_CAPTURE); ioh_i2s_disable_rx_full_ir(priv); ioh_i2s_clear_rx_sts_ir(priv); priv->rxexe_flag = 0; } else { ioh_i2s_disable_interrupts(priv, IOH_PLAYBACK); ioh_i2s_disable_tx_empty_ir(priv); ioh_i2s_clear_tx_sts_ir(priv); priv->txexe_flag = 0; } } void ioh_i2s_write_start(struct ioh_i2s_data *priv, void *callback_data, void (*done) (void *callback_data, int status, int num, int avail)) { priv->txexe_flag = 1; priv->tx_data_head = priv->tx_head; priv->tx_complete = priv->tx_head; priv->tx_avail = 0; priv->tx_callback_data = callback_data; priv->tx_done = done; ioh_i2s_clear_tx_sts_ir(priv); ioh_i2s_tx_clear_dma_mask(priv); ioh_i2s_enable_interrupts(priv, IOH_PLAYBACK); } void ioh_i2s_read_start(struct ioh_i2s_data *priv, void *callback_data, void (*done) (void *callback_data, int status, int num, int avail)) { priv->rxexe_flag = 1; priv->rx_data_head = priv->rx_head; priv->rx_complete = priv->rx_head; priv->rx_avail = 0; priv->rx_callback_data = callback_data; priv->rx_done = done; ioh_i2s_clear_rx_sts_ir(priv); ioh_i2s_rx_clear_dma_mask(priv); ioh_i2s_enable_interrupts(priv, IOH_CAPTURE); } static inline void snd_card_ml7213i2s_pcm_i2s_start(struct snd_pcm_substream *substream) { struct snd_ml7213i2s_pcm *dpcm; struct cbdata *cbd; dpcm = substream->runtime->private_data; cbd = &dpcm->cbd; if (!cbd->priv) return; if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { ioh_i2s_read_start(cbd->priv, substream, read_done); } else { ioh_i2s_write_start(cbd->priv, substream, write_done); i2s_write_period(substream); } } static int snd_card_ml7213i2s_pcm_trigger (struct snd_pcm_substream *substream, int cmd) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_ml7213i2s_pcm *dpcm = runtime->private_data; int err = 0; spin_lock(&dpcm->lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: dpcm->cbd.stop = 0; snd_card_ml7213i2s_pcm_i2s_start(substream); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: pr_debug("stop..\n"); if (!dpcm->cbd.stop) dpcm->cbd.stop = 1; else pr_debug("already stopped %d\n", dpcm->cbd.stop); if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ioh_i2s_irq_stop(dpcm->cbd.priv, IOH_CAPTURE); else ioh_i2s_irq_stop(dpcm->cbd.priv, IOH_PLAYBACK); break; default: err = -EINVAL; break; } spin_unlock(&dpcm->lock); return 0; } static int snd_card_ml7213i2s_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_ml7213i2s_pcm *dpcm = runtime->private_data; dpcm->irq_pos = 0; dpcm->buf_pos = 0; snd_pcm_format_set_silence(runtime->format, runtime->dma_area, bytes_to_samples(runtime, runtime->dma_bytes)); return 0; } static snd_pcm_uframes_t snd_card_ml7213i2s_pcm_pointer(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_ml7213i2s_pcm *dpcm = runtime->private_data; return substream->runtime->period_size*dpcm->buf_pos; } static struct snd_pcm_ops snd_card_ml7213i2s_capture_ops = { .open = snd_card_ml7213i2s_open, .close = snd_card_ml7213i2s_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_card_ml7213i2s_hw_params, .hw_free = snd_card_ml7213i2s_hw_free, .prepare = snd_card_ml7213i2s_pcm_prepare, .trigger = snd_card_ml7213i2s_pcm_trigger, .pointer = snd_card_ml7213i2s_pcm_pointer, }; static int ml7213ioh_pcm_probe(struct snd_soc_platform *platform) { return 0; } static int ml7213ioh_pcm_remove(struct snd_soc_platform *platform) { return 0; } static struct snd_soc_platform_driver ml7213ioh_soc_platform = { .probe = ml7213ioh_pcm_probe, .remove = ml7213ioh_pcm_remove, .ops = &snd_card_ml7213i2s_capture_ops, }; static int __devinit snd_card_ml7213i2s_pcm(struct snd_ml7213i2s *ml7213i2s, int device) { struct snd_pcm *pcm; int err; /* The number of I2S interface is (Rx + Tx) x 6CH */ err = snd_pcm_new(ml7213i2s->card, "ml7213i2s PCM", device, MAX_I2S_TX_CH, MAX_I2S_RX_CH, &pcm); if (err < 0) return err; ml7213i2s->pcm = pcm; pcm->private_data = ml7213i2s; pcm->info_flags = 0; strcpy(pcm->name, "ml7213i2s PCM"); snd_pcm_lib_preallocate_pages_for_all( pcm, SNDRV_DMA_TYPE_CONTINUOUS, snd_dma_continuous_data(GFP_KERNEL), 0, 64*1024); return 0; } static struct snd_device_ops ops = {NULL}; /***************************************************************************** * PCI functions *****************************************************************************/ DEFINE_PCI_DEVICE_TABLE(ioh_pci_tbl) = { { .vendor = PCI_VENDOR_ID_ROHM, .device = PCI_DEVICE_ID_ML7213_I2S, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, }, {0,} }; static int ioh_i2s_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int rv = 0; int i; int err; struct ioh_i2s_data_pci *drvdata; void __iomem *tbl; unsigned int mapbase; struct snd_card *card; struct snd_ml7213i2s *ml7213i2s; int dev = 0; drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); if (!drvdata) return -ENOMEM; pci_set_drvdata(pdev, drvdata); rv = pci_enable_device(pdev); if (rv) goto out_free; tbl = pci_iomap(pdev, 1, 0); if (!tbl) { rv = -ENOMEM; printk(KERN_ERR "pci_iomap failed\n"); goto out_ipmap; } mapbase = pci_resource_start(pdev, 1); if (!mapbase) { rv = -ENOMEM; printk(KERN_ERR "pci_resource_start failed\n"); goto out_pci_resource_start; } drvdata->membase = tbl; drvdata->mapbase = mapbase; drvdata->dev = &pdev->dev; for (i = 0; i < MAX_I2S_IF; i++) { drvdata->devs[i].iobase = tbl; drvdata->devs[i].mapbase = mapbase; spin_lock_init(&drvdata->devs[i].tx_lock); drvdata->devs[i].dma_config = &ioh_dma_config[i]; } rv = request_irq(pdev->irq, ioh_i2s_irq, IRQF_SHARED, "ml7213_ioh", pdev); if (rv != 0) { printk(KERN_ERR "Failed to allocate irq\n"); goto out_irq; } err = snd_card_create(index, "ml7213i2s", THIS_MODULE, sizeof(struct snd_ml7213i2s), &card); if (err < 0) goto out_snd_card_create; ml7213i2s = card->private_data; ml7213i2s->card = card; ml7213i2s->pci_dat = drvdata; err = snd_card_ml7213i2s_pcm(ml7213i2s, 0); if (err < 0) goto snd_card_ml7213i2s_pc; strcpy(card->driver, "ml7213i2s"); strcpy(card->shortname, "ml7213i2s"); sprintf(card->longname, "ml7213i2s %i", dev + 1); snd_card_set_dev(card, &pdev->dev); snd_device_new(card, SNDRV_DEV_LOWLEVEL, ml7213i2s, &ops); err = snd_card_register(card); if (err) goto out_snd_card_register; drvdata->card = card; snd_soc_register_platform(&pdev->dev, &ml7213ioh_soc_platform); return 0; out_snd_card_register: snd_card_ml7213i2s_pc: snd_card_free(card); out_snd_card_create: free_irq(pdev->irq, pdev); out_irq: out_pci_resource_start: pci_iounmap(pdev, tbl); out_ipmap: pci_disable_device(pdev); out_free: kfree(drvdata); return rv; } static void ioh_i2s_pci_remove(struct pci_dev *pdev) { struct ioh_i2s_data_pci *drvdata = pci_get_drvdata(pdev); int i; for (i = 0; i < MAX_I2S_IF; i++) ioh_i2s_reset(&drvdata->devs[i]); snd_soc_unregister_platform(&pdev->dev); kfree(drvdata); pci_disable_device(pdev); pci_iounmap(pdev, drvdata->membase); free_irq(pdev->irq, pdev); snd_card_free(drvdata->card); pci_set_drvdata(pdev, NULL); } static void ioh_i2s_save_reg_conf(struct pci_dev *pdev) { int i; struct ioh_i2s_data_pci *drvdata = pci_get_drvdata(pdev); void *iobase; struct ioh_i2s_pm_ch_reg *save; int offset; for (i = 0, offset = 0; i < MAX_I2S_IF; i++, offset = i * 0x800) { iobase = drvdata->devs[i].iobase; save = &drvdata->devs[i].ch_reg_save; save->i2sdrtx = ioread32(iobase + offset + I2SDRTX_OFFSET); save->i2scnttx = ioread32(iobase + offset + I2SCNTTX_OFFSET); save->i2sfifoctx = ioread32(iobase + offset + I2SFIFOCTX_OFFSET); save->i2saftx = ioread32(iobase + offset + I2SAFTX_OFFSET); save->i2saetx = ioread32(iobase + offset + I2SAETX_OFFSET); save->i2smsktx = ioread32(iobase + offset + I2SMSKTX_OFFSET); save->i2sisttx = ioread32(iobase + offset + I2SISTTX_OFFSET); save->i2scntrx = ioread32(iobase + offset + I2SCNTRX_OFFSET); save->i2sfifocrx = ioread32(iobase + offset + I2SFIFOCRX_OFFSET); save->i2safrx = ioread32(iobase + offset + I2SAFRX_OFFSET); save->i2saerx = ioread32(iobase + offset + I2SAERX_OFFSET); save->i2smskrx = ioread32(iobase + offset + I2SMSKRX_OFFSET); save->i2sistrx = ioread32(iobase + offset + I2SISTRX_OFFSET); } for (i = 0; i < MAX_I2S_IF; i++) { drvdata->cmn_reg_save.i2sclkcnt[i] = ioread32(drvdata->devs[i].iobase + I2SCLKCNT0_OFFSET + 0x10*i); } drvdata->cmn_reg_save.i2simask = ioread32(drvdata->devs[0].iobase + I2SIMASK_OFFSET); } static int ioh_i2s_pci_suspend(struct pci_dev *pdev, pm_message_t state) { int ret; ioh_i2s_save_reg_conf(pdev); ret = pci_save_state(pdev); if (ret) { dev_err(&pdev->dev, " %s -pci_save_state returns %d\n", __func__, ret); return ret; } pci_enable_wake(pdev, PCI_D3hot, 0); pci_disable_device(pdev); pci_set_power_state(pdev, pci_choose_state(pdev, state)); return 0; } static void ioh_i2s_restore_reg_conf(struct pci_dev *pdev) { int i; struct ioh_i2s_data_pci *drvdata = pci_get_drvdata(pdev); void *iobase; struct ioh_i2s_pm_ch_reg *save; int offset; for (i = 0, offset = 0; i < MAX_I2S_IF; i++, offset = i * 0x800) { iobase = drvdata->devs[i].iobase; save = &drvdata->devs[i].ch_reg_save; iowrite32(save->i2sdrtx, iobase + offset + I2SDRTX_OFFSET); iowrite32(save->i2scnttx, iobase + offset + I2SCNTTX_OFFSET); iowrite32(save->i2sfifoctx, iobase + offset + I2SFIFOCTX_OFFSET); iowrite32(save->i2saftx, iobase + offset + I2SAFTX_OFFSET); iowrite32(save->i2saetx, iobase + offset + I2SAETX_OFFSET); iowrite32(save->i2smsktx, iobase + offset + I2SMSKTX_OFFSET); iowrite32(save->i2sisttx, iobase + offset + I2SISTTX_OFFSET); iowrite32(save->i2scntrx, iobase + offset + I2SCNTRX_OFFSET); iowrite32(save->i2sfifocrx, iobase + offset + I2SFIFOCRX_OFFSET); iowrite32(save->i2safrx, iobase + offset + I2SAFRX_OFFSET); iowrite32(save->i2saerx, iobase + offset + I2SAERX_OFFSET); iowrite32(save->i2smskrx, iobase + offset + I2SMSKRX_OFFSET); iowrite32(save->i2sistrx, iobase + offset + I2SISTRX_OFFSET); } for (i = 0; i < MAX_I2S_IF; i++) { iowrite32(drvdata->cmn_reg_save.i2sclkcnt[i], drvdata->devs[i].iobase + I2SCLKCNT0_OFFSET + 0x10*i); } iowrite32(drvdata->cmn_reg_save.i2simask, drvdata->devs[0].iobase + I2SIMASK_OFFSET); } static int ioh_i2s_pci_resume(struct pci_dev *pdev) { int ret; pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev); ret = pci_enable_device(pdev); if (ret) { dev_err(&pdev->dev, "%s-pci_enable_device failed(ret=%d) ", __func__, ret); return ret; } pci_enable_wake(pdev, PCI_D3hot, 0); ioh_i2s_restore_reg_conf(pdev); return 0; } static struct pci_driver ioh_i2s_driver = { .name = DRV_NAME, .probe = ioh_i2s_pci_probe, .remove = __devexit_p(ioh_i2s_pci_remove), .id_table = ioh_pci_tbl, #ifdef CONFIG_PM .suspend = ioh_i2s_pci_suspend, .resume = ioh_i2s_pci_resume, #endif }; static int __init ioh_i2s_init(void) { return pci_register_driver(&ioh_i2s_driver); } static void __exit ioh_i2s_cleanup(void) { pci_unregister_driver(&ioh_i2s_driver); } module_init(ioh_i2s_init); module_exit(ioh_i2s_cleanup); MODULE_AUTHOR("Tomoya MORINAGA "); MODULE_DESCRIPTION("LAPIS Semiconductor ML7213 IOH ALSA SoC platform driver"); MODULE_LICENSE("GPL");