This driver is for LAPIS Semiconductor ML7213 IOH I2S.
Signed-off-by: Tomoya MORINAGA tomoya.rohm@gmail.com --- V4 - Delete redundant "struct platform_device" settings - Move request_irq to at pcm_new() - Allocate memory with dynamically not static - Delete redundant '' deteminations - Add SND_SOC_DAIFMT_IB_NF setting - Move dai_set_clkdiv() to dai_hw_params() - Melt ioh_i2s_save/restore_reg_conf into ml7213i2s_soc_suspend/resume - Delete redundant "#ifdef CONFIG_PM" condition correctly --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/lapis/Kconfig | 4 + sound/soc/lapis/Makefile | 4 + sound/soc/lapis/ioh_i2s.h | 39 ++ sound/soc/lapis/ml7213ioh-plat.c | 1320 ++++++++++++++++++++++++++++++++++++++ sound/soc/lapis/ml7213ioh-plat.h | 376 +++++++++++ 7 files changed, 1745 insertions(+), 0 deletions(-) create mode 100644 sound/soc/lapis/Kconfig create mode 100644 sound/soc/lapis/Makefile create mode 100644 sound/soc/lapis/ioh_i2s.h create mode 100644 sound/soc/lapis/ml7213ioh-plat.c create mode 100644 sound/soc/lapis/ml7213ioh-plat.h
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 91c9855..97d0d33 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -37,6 +37,7 @@ source "sound/soc/ep93xx/Kconfig" source "sound/soc/fsl/Kconfig" source "sound/soc/imx/Kconfig" source "sound/soc/jz4740/Kconfig" +source "sound/soc/lapis/Kconfig" source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" source "sound/soc/kirkwood/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 2feaf37..1f266ee 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_SND_SOC) += ep93xx/ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += imx/ obj-$(CONFIG_SND_SOC) += jz4740/ +obj-$(CONFIG_SND_SOC) += lapis/ obj-$(CONFIG_SND_SOC) += mid-x86/ obj-$(CONFIG_SND_SOC) += mxs/ obj-$(CONFIG_SND_SOC) += nuc900/ diff --git a/sound/soc/lapis/Kconfig b/sound/soc/lapis/Kconfig new file mode 100644 index 0000000..551e385 --- /dev/null +++ b/sound/soc/lapis/Kconfig @@ -0,0 +1,4 @@ +config SND_SOC_ML7213_PLATFORM + tristate "ML7213 IOH ASoC platform driver" + help + This option enables support for the AC Link Controllers in ML7213 IOH SoC. diff --git a/sound/soc/lapis/Makefile b/sound/soc/lapis/Makefile new file mode 100644 index 0000000..aba1630 --- /dev/null +++ b/sound/soc/lapis/Makefile @@ -0,0 +1,4 @@ +# Platform +snd-soc-ml7213-plat-objs := ml7213ioh-plat.o + +obj-$(CONFIG_SND_SOC_ML7213_PLATFORM) += snd-soc-ml7213-plat.o diff --git a/sound/soc/lapis/ioh_i2s.h b/sound/soc/lapis/ioh_i2s.h new file mode 100644 index 0000000..9f19f70 --- /dev/null +++ b/sound/soc/lapis/ioh_i2s.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef ML7213_IOH_I2S +#define ML7213_IOH_I2S + +enum ioh_bclkfs { + ML7213IOH_BCLKFS0 = 0, + ML7213IOH_BCLKFS1, + ML7213IOH_BCLKFS2, + ML7213IOH_BCLKFS3, + ML7213IOH_BCLKFS4, + ML7213IOH_BCLKFS5, +}; + +enum ioh_mclkfs { + ML7213IOH_MCLKFS0 = 6, + ML7213IOH_MCLKFS1, + ML7213IOH_MCLKFS2, + ML7213IOH_MCLKFS3, + ML7213IOH_MCLKFS4, + ML7213IOH_MCLKFS5, +}; + +#endif diff --git a/sound/soc/lapis/ml7213ioh-plat.c b/sound/soc/lapis/ml7213ioh-plat.c new file mode 100644 index 0000000..1725a2c --- /dev/null +++ b/sound/soc/lapis/ml7213ioh-plat.c @@ -0,0 +1,1320 @@ +/* + * 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 <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/initval.h> + +#include "ioh_i2s.h" +#include "ml7213ioh-plat.h" + +static struct ioh_i2s_data *i2s_data; +static struct ioh_i2s_dma *dmadata; + +/* I2S HAL (Hardware Abstruction Layer) */ +static void ioh_i2s_reset(int ch) +{ + iowrite32(1 << ch, i2s_data->iobase + I2SSRST_OFFSET); + iowrite32(0, i2s_data->iobase + I2SSRST_OFFSET); +} + +static void ioh_i2s_enable_interrupts(int ch, int dir) +{ + unsigned int intr_lines = 0; + + switch (dir) { + case SNDRV_PCM_STREAM_CAPTURE: + intr_lines = 1 << (I2S_IMASK_RX_BIT_START + ch); + break; + case SNDRV_PCM_STREAM_PLAYBACK: + intr_lines = 1 << (I2S_IMASK_TX_BIT_START + ch); + break; + } + + /* enable interrupts for specified channel */ + iowrite32(intr_lines, i2s_data->iobase + I2SIMASKCLR_OFFSET); +} + +static void ioh_i2s_disable_interrupts(int ch, int dir) +{ + unsigned int intr_lines; + + intr_lines = ioread32(i2s_data->iobase + I2SIMASK_OFFSET); + + switch (dir) { + case SNDRV_PCM_STREAM_CAPTURE: + intr_lines |= 1 << (I2S_IMASK_RX_BIT_START + ch); + break; + case SNDRV_PCM_STREAM_PLAYBACK: + intr_lines |= 1 << (I2S_IMASK_TX_BIT_START + ch); + break; + } + + /* Mask the specific interrupt bits */ + iowrite32(intr_lines, i2s_data->iobase + I2SIMASK_OFFSET); +} + +#define IOH_FIFO_CLR 0 +#define IOH_FIFO_RUN 1 + +static void ioh_i2s_ctrl_fifo(int ch, int dir, int ctrl) +{ + int offset = ch * 0x800; + u32 val; + u32 reg_addr = 0; + + switch (dir) { + case SNDRV_PCM_STREAM_CAPTURE: + reg_addr = I2SFIFOCRX_OFFSET; + break; + case SNDRV_PCM_STREAM_PLAYBACK: + reg_addr = I2SFIFOCTX_OFFSET; + break; + } + + val = ioread32(i2s_data->iobase + reg_addr + offset); + if (ctrl) + val |= I2S_FIFO_TX_RUN; + else + val |= I2S_FIFO_TX_FCLR; + + iowrite32(val, i2s_data->iobase + reg_addr + offset); +} + +/* Clear interrupt status */ +static void ioh_i2s_clear_sts_ir(int ch, int dir) +{ + int offset = ch * 0x800; + u32 reg_addr = 0; + u32 val = 0; + + switch (dir) { + case SNDRV_PCM_STREAM_CAPTURE: + reg_addr = I2SISTRX_OFFSET; + break; + case SNDRV_PCM_STREAM_PLAYBACK: + reg_addr = I2SISTTX_OFFSET; + break; + } + val = ioread32(i2s_data->iobase + reg_addr + offset) & 0xf; + if (val) + iowrite32(val, i2s_data->iobase + reg_addr + offset); +} + +static void ioh_i2s_clear_dma_mask(int ch, int dir) +{ + u32 val = 0; + u32 mask = 0; + u32 reg_addr = 0; + int offset = ch * 0x800; + + switch (dir) { + case SNDRV_PCM_STREAM_CAPTURE: + reg_addr = I2SMSKRX_OFFSET; + mask = RX_BIT_DMAMSK; + break; + case SNDRV_PCM_STREAM_PLAYBACK: + reg_addr = I2SMSKTX_OFFSET; + mask = TX_BIT_DMAMSK; + break; + } + + val = ioread32(i2s_data->iobase + reg_addr + offset); + val &= ~mask; /* Enable Tx DMA Request */ + + iowrite32(val, i2s_data->iobase + reg_addr + offset); +} + +#define IOH_DIS_IRQ 0 +#define IOH_EN_IRQ 1 + +static void ioh_i2s_irq_ctrl(int ch, int dir, int ctrl) +{ + u32 val; + int offset = ch * 0x800; + + switch (dir) { + case SNDRV_PCM_STREAM_CAPTURE: + val = ioread32(i2s_data->iobase + I2SMSKRX_OFFSET + offset); + if (ctrl) { + val &= ~RX_BIT_AFIMSK; /* Enable Almost empty IR */ + val &= ~RX_BIT_FIMSK; /* Enable Empty IR */ + } else { + val |= RX_BIT_AFIMSK; /* Disble Almost full IR */ + val |= RX_BIT_FIMSK; /* Disble full IR */ + } + iowrite32(val, i2s_data->iobase + I2SMSKRX_OFFSET + offset); + break; + case SNDRV_PCM_STREAM_PLAYBACK: + val = ioread32(i2s_data->iobase + I2SMSKTX_OFFSET + offset); + if (ctrl) { + val &= ~TX_BIT_AEIMSK; /* Enable Almost empty IR */ + val &= ~TX_BIT_EIMSK; /* Enable Empty IR */ + } else { + val |= TX_BIT_AEIMSK; /* Disble Almost empty IR */ + val |= TX_BIT_EIMSK; /* Disble Empty IR */ + } + iowrite32(val, i2s_data->iobase + I2SMSKTX_OFFSET + offset); + break; + } + +} + +/* Linux standard DMA functions */ +static bool ioh_dma_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; + } +} + +int ioh_request_dma_channel( + int ch, struct ioh_i2s_dma *ioh, 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 */ + + switch (dir) { + case DMA_FROM_DEVICE: + ioh->param_rx.width = ioh->dma_rx_width; + ioh->param_rx.dma_dev = &dma_dev->dev; + ioh->param_rx.chan_id = ch * 2 + 1; /* ch Rx=1,3,...11 */ + ioh->param_rx.rx_reg = (dma_addr_t)(i2s_data->mapbase + + ch * 0x800 + I2SDRRXMIRROR_OFFSET); + chan = dma_request_channel(mask, ioh_dma_filter, + &ioh->param_rx); + if (chan == NULL) { + dev_err(i2s_data->dev, "Failed dma_request_channel for" + " I2S %d\n", ch); + return -ENOMEM; + } + ioh->chan_rx = chan; + break; + case DMA_TO_DEVICE: + ioh->param_tx.width = ioh->dma_tx_width; + ioh->param_tx.dma_dev = &dma_dev->dev; + ioh->param_tx.chan_id = ch * 2; /* DMA ch Tx=0,2,...10 */ + + ioh->param_tx.tx_reg = (dma_addr_t)(i2s_data->mapbase + + ch * 0x800 + I2SDRTXMIRROR_OFFSET); + + chan = dma_request_channel(mask, ioh_dma_filter, + &ioh->param_tx); + if (chan == NULL) { + dev_err(i2s_data->dev, "Failed dma_request_channel for" + " I2S %d\n", ch); + return -ENOMEM; + } + ioh->chan_tx = chan; + break; + default: + dev_err(i2s_data->dev, "Invalid direction (%d)\n", dir); + return -EINVAL; + } + + return 0; +} + +static void ioh_i2s_configure_i2s_regs(int ch, int dir) +{ + int offset = ch * 0x800; + + switch (dir) { + case SNDRV_PCM_STREAM_CAPTURE: + /* Rx register */ + iowrite32(I2S_AFULL_THRESH / 2, + i2s_data->iobase + I2SAFRX_OFFSET + offset); + iowrite32(ML7213_I2SAERX_DEFAULT, + i2s_data->iobase + I2SAERX_OFFSET + offset); + iowrite32(ML7213_I2SMSKRX_DEFAULT, + i2s_data->iobase + I2SMSKRX_OFFSET + offset); + iowrite32(ML7213_I2SISTRX_DEFAULT, + i2s_data->iobase + I2SISTRX_OFFSET + offset); + break; + case SNDRV_PCM_STREAM_PLAYBACK: + iowrite32(0, i2s_data->iobase + I2SAFTX_OFFSET + offset); + iowrite32(I2S_AEMPTY_THRESH / 2, + i2s_data->iobase + I2SAETX_OFFSET + offset); + iowrite32(ML7213_I2SMSKTX_DEFAULT, + i2s_data->iobase + I2SMSKTX_OFFSET + offset); + iowrite32(ML7213_I2SISTTX_DEFAULT, + i2s_data->iobase + I2SISTTX_OFFSET + offset); + break; + } +} + +static void i2s_dma_rx_complete(void *arg) +{ + struct ioh_i2s_dma *ioh = (struct ioh_i2s_dma *)arg; + struct ml7213i2s_runtime_data *ioh_rtd = + ioh->rx_substream->runtime->private_data; + + pr_debug("%s in rx_cur_period=%d\n", __func__, ioh->rx_cur_period); + + if (ioh_rtd->rx_stop) { + pr_debug("%s stopped. return.\n", __func__); + return; + } + + ioh->rx_cur_period++; + if (ioh->rx_cur_period == ioh->rx_buf_frags) + ioh->rx_cur_period = 0; + + async_tx_ack(ioh->desc_rx); + if (ioh->rx_substream) + snd_pcm_period_elapsed(ioh->rx_substream); + ioh_i2s_irq_ctrl(ioh->number, SNDRV_PCM_STREAM_CAPTURE, IOH_EN_IRQ); +} + +static void i2s_dma_tx_complete(void *arg) +{ + struct ioh_i2s_dma *ioh = (struct ioh_i2s_dma *)arg; + struct ml7213i2s_runtime_data *ioh_rtd = + ioh->tx_substream->runtime->private_data; + + if (ioh_rtd->tx_stop) { + pr_debug("%s stopped. return.\n", __func__); + return; + } + + ioh->tx_cur_period++; + if (ioh->tx_cur_period == ioh->tx_buf_frags) + ioh->tx_cur_period = 0; + + async_tx_ack(ioh->desc_tx); + + if (ioh->tx_substream) + snd_pcm_period_elapsed(ioh->tx_substream); + + ioh_i2s_irq_ctrl(ioh->number, SNDRV_PCM_STREAM_PLAYBACK, IOH_EN_IRQ); +} + +static void i2s_dma_rx_start(struct ioh_i2s_dma *ioh, char *buff) +{ + struct dma_async_tx_descriptor *desc; + int rx_size; + int rx_num; + int i; + struct scatterlist *sg; + + rx_size = I2S_AFULL_THRESH * 4; + + /* The number of scatter list (Franction area is not used) */ + if (ioh->rx_period_bytes % rx_size) + /* rx_num = The number of scatter list */ + rx_num = ioh->rx_period_bytes / rx_size + 1; + else + rx_num = ioh->rx_period_bytes / rx_size; + + dev_dbg(i2s_data->dev, "%s: rx: scatter_num=%d scatter_size=%d\n", + __func__, rx_num, rx_size); + + ioh->sg_rx_p = + kzalloc(sizeof(struct scatterlist) * rx_num, GFP_ATOMIC); + + sg = ioh->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(buff), rx_size, rx_size * i); + sg_dma_len(sg) = rx_size / 4; + sg_dma_address(sg) = ioh->rx_physical_addr + sg->offset; + } + ioh->rx_nent = rx_num; + + dma_sync_sg_for_device(i2s_data->dev, ioh->sg_rx_p, ioh->rx_nent, + DMA_FROM_DEVICE); + + sg = ioh->sg_rx_p; + + desc = ioh->chan_rx->device->device_prep_slave_sg(ioh->chan_rx, + sg, rx_num, DMA_FROM_DEVICE, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(i2s_data->dev, "%s:device_prep_slave_sg Failed\n", + __func__); + return; + } + ioh_i2s_irq_ctrl(ioh->number, SNDRV_PCM_STREAM_CAPTURE, IOH_DIS_IRQ); + + ioh->desc_rx = desc; + + desc->callback = i2s_dma_rx_complete; + desc->callback_param = ioh; + + desc->tx_submit(desc); +} + +static void i2s_dma_tx_start(struct ioh_i2s_dma *ioh, char *buff) +{ + struct dma_async_tx_descriptor *desc; + int tx_size; + int tx_num; + int i; + struct scatterlist *sg; + + tx_size = I2S_AEMPTY_THRESH * 4; + if (ioh->tx_period_bytes % tx_size) + /* tx_num = The number of scatter list */ + tx_num = ioh->tx_period_bytes / tx_size + 1; + else + tx_num = ioh->tx_period_bytes / tx_size; + + dev_dbg(i2s_data->dev, "%s: tx: scatter_num=%d scatter_size=%d\n", + __func__, tx_num, tx_size); + + ioh->sg_tx_p = + kzalloc(sizeof(struct scatterlist) * tx_num, GFP_ATOMIC); + + sg = ioh->sg_tx_p; + sg_init_table(sg, tx_num); /* Initialize SG table */ + + for (i = 0; i < tx_num; i++, sg++) { + sg_set_page(sg, virt_to_page(buff), tx_size, tx_size * i); + sg_dma_len(sg) = tx_size / 4; + sg_dma_address(sg) = ioh->tx_physical_addr + sg->offset; + } + ioh->tx_nent = tx_num; + + dma_sync_sg_for_device(i2s_data->dev, ioh->sg_tx_p, ioh->tx_nent, + DMA_TO_DEVICE); + + sg = ioh->sg_tx_p; + + desc = ioh->chan_tx->device->device_prep_slave_sg(ioh->chan_tx, + sg, tx_num, DMA_TO_DEVICE, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(i2s_data->dev, "%s:device_prep_slave_sg Failed\n", + __func__); + return; + } + + ioh_i2s_irq_ctrl(ioh->number, SNDRV_PCM_STREAM_PLAYBACK, IOH_DIS_IRQ); + + ioh->desc_tx = desc; + + desc->callback = i2s_dma_tx_complete; + desc->callback_param = ioh; + desc->tx_submit(desc); +} + +static void ioh_release_dma_channel(int ch, int dir) +{ + struct ioh_i2s_dma *ioh; + + ioh = &dmadata[ch]; + + switch (dir) { + case SNDRV_PCM_STREAM_CAPTURE: + dma_sync_sg_for_cpu(i2s_data->dev, ioh->sg_rx_p, ioh->rx_nent, + DMA_FROM_DEVICE); + + if (ioh->chan_rx) { + ioh->chan_rx->device->device_control(ioh->chan_rx, + DMA_TERMINATE_ALL, + 0); + dma_release_channel(ioh->chan_rx); + ioh->chan_rx = NULL; + } + + kfree(ioh->sg_rx_p); + ioh->rx_buf_dma = 0; + break; + case SNDRV_PCM_STREAM_PLAYBACK: + dma_sync_sg_for_cpu(i2s_data->dev, ioh->sg_tx_p, ioh->tx_nent, + DMA_TO_DEVICE); + + if (ioh->chan_tx) { + ioh->chan_tx->device->device_control(ioh->chan_tx, + DMA_TERMINATE_ALL, + 0); + dma_release_channel(ioh->chan_tx); + ioh->chan_tx = NULL; + } + kfree(ioh->sg_tx_p); + ioh->tx_buf_dma = 0; + break; + } +} + +void ioh_i2s_event(u32 idisp, int ch) +{ + unsigned long flags; + unsigned int status; + char *buff; + int offset = ch * 0x800; + struct ioh_i2s_dma *ioh = &dmadata[ch]; + struct ml7213i2s_runtime_data *ioh_rtd; + + spin_lock_irqsave(&i2s_data->tx_lock, flags); + + if (idisp & BIT(ch + 16)) { + dev_dbg(i2s_data->dev, "Rx%d interrupt occures\n", ch); + ioh_rtd = ioh->rx_substream->runtime->private_data; + buff = ioh->rx_dma_addr + + ioh->rx_cur_period * ioh->rx_period_bytes; + status = ioread32(i2s_data->iobase + I2SISTRX_OFFSET + offset); + + if (status & I2S_RX_FINT) + dev_dbg(i2s_data->dev, "%s:I2S%d overrun occurs\n", + __func__, ch); + if (status & I2S_RX_AFINT) + if (!ioh_rtd->rx_stop) + i2s_dma_rx_start(ioh, buff); + + /*Clear the interrupt status */ + iowrite32(status, i2s_data->iobase + I2SISTRX_OFFSET + offset); + } + + if (idisp & BIT(ch)) { + dev_dbg(i2s_data->dev, "Tx%d interrupt occures\n", ch); + ioh_rtd = ioh->tx_substream->runtime->private_data; + buff = ioh->tx_dma_addr + + ioh->tx_cur_period * ioh->tx_period_bytes; + + status = ioread32(i2s_data->iobase + I2SISTTX_OFFSET + offset); + if (status & I2S_TX_EINT) + dev_dbg(i2s_data->dev, "%s:I2S%d under flow occurs\n", + __func__, ch); + if (status & I2S_TX_AEINT) + if (!ioh_rtd->tx_stop) + i2s_dma_tx_start(ioh, buff); + + /*Clear the interrupt status */ + iowrite32(status, i2s_data->iobase + I2SISTTX_OFFSET + offset); + } + spin_unlock_irqrestore(&i2s_data->tx_lock, flags); + return; +} + +static irqreturn_t ioh_i2s_irq(int irq, void *data) +{ + int i; + u32 idisp; + + idisp = ioread32(i2s_data->iobase + I2SIDISP_OFFSET); + for (i = 0; i < MAX_I2S_CH; i++) + ioh_i2s_event(idisp, i); + + return IRQ_HANDLED; +} + +static int snd_card_ml7213i2s_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ml7213i2s_runtime_data *ioh_rtd; + + ioh_rtd = kzalloc(sizeof(*ioh_rtd), GFP_KERNEL); + if (ioh_rtd == NULL) + return -ENOMEM; + + runtime->private_data = ioh_rtd; + runtime->hw = ml7213i2s_pcm_hw; + + /* makes the infrastructure responsible for freeing dma */ + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + snd_soc_set_runtime_hwparams(substream, &ml7213i2s_pcm_hw); + + spin_lock_init(&ioh_rtd->lock); + return 0; +} + +static int snd_card_ml7213i2s_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ch; + + switch (substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + ch = dmadata[substream->number].mapped_rx_ch; + ioh_release_dma_channel(ch, SNDRV_PCM_STREAM_CAPTURE); + break; + case SNDRV_PCM_STREAM_PLAYBACK: + ch = dmadata[substream->number].mapped_tx_ch; + ioh_release_dma_channel(ch, SNDRV_PCM_STREAM_PLAYBACK); + break; + } + + kfree(runtime->private_data); + + 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_soc_pcm_runtime *rtd = substream->private_data; + struct ml7213i2s_runtime_data *ioh_rtd = runtime->private_data; + struct ioh_i2s_dma *dma = + snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + switch (substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + ioh_rtd->rx_dma = dma; + break; + case SNDRV_PCM_STREAM_PLAYBACK: + ioh_rtd->tx_dma = dma; + break; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(hw_params); + + 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 ml7213i2s_runtime_data *ioh_rtd; + ioh_rtd = substream->runtime->private_data; + + switch (substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + ioh_rtd->rx_dma = NULL; + break; + case SNDRV_PCM_STREAM_PLAYBACK: + ioh_rtd->tx_dma = NULL; + break; + } + snd_pcm_set_runtime_buffer(substream, NULL); + + return snd_pcm_lib_free_pages(substream); +} + +static int snd_card_ml7213i2s_pcm_trigger + (struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ml7213i2s_runtime_data *ioh_rtd = runtime->private_data; + int err = 0; + unsigned long flags; + int ch; + struct ioh_i2s_dma *ioh; + + spin_lock_irqsave(&ioh_rtd->lock, flags); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + switch (substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + ioh_rtd->rx_stop = 0; + ch = dmadata[substream->number].mapped_rx_ch; + pr_debug("%s Capture start.(ch=%d)\n", __func__, ch); + ioh = &dmadata[ch]; + ioh->rx_substream = substream; + err = ioh_request_dma_channel(ch, &dmadata[ch], + DMA_FROM_DEVICE); + if (err) + goto err_out; + + ioh_i2s_configure_i2s_regs(ch, + SNDRV_PCM_STREAM_CAPTURE); + ioh_i2s_ctrl_fifo(ch, SNDRV_PCM_STREAM_CAPTURE, + IOH_FIFO_CLR); + ioh_i2s_clear_sts_ir(ch, SNDRV_PCM_STREAM_CAPTURE); + ioh_i2s_irq_ctrl(ch, SNDRV_PCM_STREAM_CAPTURE, + IOH_EN_IRQ); + ioh_i2s_ctrl_fifo(ch, SNDRV_PCM_STREAM_CAPTURE, + IOH_FIFO_RUN); + ioh_i2s_clear_dma_mask(ch, SNDRV_PCM_STREAM_CAPTURE); + ioh_i2s_enable_interrupts(ch, SNDRV_PCM_STREAM_CAPTURE); + break; + case SNDRV_PCM_STREAM_PLAYBACK: + if (!ioh_rtd) + return -1; + if (!substream) + return -1; + ioh_rtd->tx_stop = 0; + ch = dmadata[substream->number].mapped_tx_ch; + pr_debug("%s Playback start.(ch=%d)\n", __func__, ch); + ioh = &dmadata[ch]; + ioh->tx_substream = substream; + err = ioh_request_dma_channel(ch, &dmadata[ch], + DMA_TO_DEVICE); + if (err) + goto err_out; + + ioh_i2s_configure_i2s_regs(ch, + SNDRV_PCM_STREAM_PLAYBACK); + ioh_i2s_ctrl_fifo(ch, SNDRV_PCM_STREAM_PLAYBACK, + IOH_FIFO_CLR); + ioh_i2s_clear_sts_ir(ch, SNDRV_PCM_STREAM_PLAYBACK); + ioh_i2s_irq_ctrl(ch, SNDRV_PCM_STREAM_PLAYBACK, + IOH_EN_IRQ); + ioh_i2s_ctrl_fifo(ch, SNDRV_PCM_STREAM_PLAYBACK, + IOH_FIFO_RUN); + ioh_i2s_clear_dma_mask(ch, SNDRV_PCM_STREAM_PLAYBACK); + ioh_i2s_enable_interrupts(ch, + SNDRV_PCM_STREAM_PLAYBACK); + break; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + switch (substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + ioh_rtd->rx_stop = 1; + ch = dmadata[substream->number].mapped_rx_ch; + pr_debug("%s Capture stop.(ch=%d)\n", __func__, ch); + ioh_i2s_disable_interrupts(ch, + SNDRV_PCM_STREAM_CAPTURE); + ioh_i2s_irq_ctrl(ch, SNDRV_PCM_STREAM_CAPTURE, + IOH_DIS_IRQ); + ioh_i2s_clear_sts_ir(ch, SNDRV_PCM_STREAM_CAPTURE); + ioh_i2s_ctrl_fifo(ch, SNDRV_PCM_STREAM_CAPTURE, + IOH_FIFO_CLR); + break; + case SNDRV_PCM_STREAM_PLAYBACK: + ioh_rtd->tx_stop = 1; + ch = dmadata[substream->number].mapped_tx_ch; + pr_debug("%s Playback stop.(ch=%d)\n", __func__, ch); + ioh_i2s_disable_interrupts(ch, + SNDRV_PCM_STREAM_PLAYBACK); + ioh_i2s_irq_ctrl(ch, SNDRV_PCM_STREAM_PLAYBACK, + IOH_DIS_IRQ); + ioh_i2s_clear_sts_ir(ch, SNDRV_PCM_STREAM_PLAYBACK); + ioh_i2s_ctrl_fifo(ch, SNDRV_PCM_STREAM_PLAYBACK, + IOH_FIFO_CLR); + break; + } + break; + default: + err = -EINVAL; + break; + } +err_out: + spin_unlock_irqrestore(&ioh_rtd->lock, flags); + + return err; +} + +static int snd_card_ml7213i2s_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ml7213i2s_runtime_data *ioh_rtd = runtime->private_data; + switch (substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + ioh_rtd->rx_dma->rx_cur_period = 0; + ioh_rtd->rx_dma->rx_dma_addr = runtime->dma_area; + ioh_rtd->rx_dma->rx_physical_addr = runtime->dma_addr; + ioh_rtd->rx_dma->rx_buffer_bytes = + snd_pcm_lib_buffer_bytes(substream); + ioh_rtd->rx_dma->rx_period_bytes = + snd_pcm_lib_period_bytes(substream); + ioh_rtd->rx_dma->rx_buf_frags = + ioh_rtd->rx_dma->rx_buffer_bytes / + ioh_rtd->rx_dma->rx_period_bytes; + break; + case SNDRV_PCM_STREAM_PLAYBACK: + ioh_rtd->tx_dma->tx_cur_period = 0; + ioh_rtd->tx_dma->tx_dma_addr = runtime->dma_area; + ioh_rtd->tx_dma->tx_physical_addr = runtime->dma_addr; + ioh_rtd->tx_dma->tx_buffer_bytes = + snd_pcm_lib_buffer_bytes(substream); + ioh_rtd->tx_dma->tx_period_bytes = + snd_pcm_lib_period_bytes(substream); + ioh_rtd->tx_dma->tx_buf_frags = + ioh_rtd->tx_dma->tx_buffer_bytes / + ioh_rtd->tx_dma->tx_period_bytes; + break; + } + + return 0; +} + +static snd_pcm_uframes_t +snd_card_ml7213i2s_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct ml7213i2s_runtime_data *ioh_rtd = + substream->runtime->private_data; + unsigned long offset = 0; + + switch (substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + offset = ioh_rtd->rx_dma->rx_cur_period * + ioh_rtd->rx_dma->rx_period_bytes; + break; + case SNDRV_PCM_STREAM_PLAYBACK: + offset = ioh_rtd->tx_dma->tx_cur_period * + ioh_rtd->tx_dma->tx_period_bytes; + break; + } + + return bytes_to_frames(substream->runtime, offset); +} + +static struct snd_pcm_ops snd_card_ml7213i2s_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 u64 idma_mask = DMA_BIT_MASK(32); +static int ml7213ioh_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &idma_mask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + snd_pcm_lib_preallocate_pages_for_all( + pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 0, 64 * 128 * 4 * 64); + + ret = request_irq(i2s_data->pdev->irq, ioh_i2s_irq, IRQF_SHARED, + "ml7213_ioh", i2s_data->pdev); + if (ret != 0) + printk(KERN_ERR "Failed to allocate irq\n"); + + return ret; +} + +static void ml7213ioh_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + free_irq(i2s_data->pdev->irq, i2s_data->pdev); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + buf = &substream->dma_buffer; + if (!buf->area) + continue; + buf->area = NULL; + } +} + +static struct snd_soc_platform_driver ml7213ioh_soc_platform = { + .pcm_new = ml7213ioh_pcm_new, + .pcm_free = ml7213ioh_pcm_free, + .ops = &snd_card_ml7213i2s_ops, +}; + +/* DAI functions */ +static int ml7213i2s_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *dai) +{ + int ch; + int byte; + unsigned int clk; + u32 i2sclkcnt; + u32 bclkfs = 0; + u32 mclkfs = 0; + void *iobase = i2s_data->iobase; + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_U8: + byte = 8; + bclkfs = 32; + break; + case SNDRV_PCM_FORMAT_S16_LE: + byte = 16; + bclkfs = 32; + break; + case SNDRV_PCM_FORMAT_S32_LE: + byte = 24; + bclkfs = 64; + break; + default: + pr_err("%s: Failed not support format\n", __func__); + return -EINVAL; + } + + switch (substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + ch = dmadata[substream->number].mapped_rx_ch; + dmadata[ch].dma_rx_unit = byte; + dmadata[ch].dma_rx_width = PCH_DMA_WIDTH_4_BYTES; + break; + case SNDRV_PCM_STREAM_PLAYBACK: + ch = dmadata[substream->number].mapped_tx_ch; + dmadata[ch].dma_tx_unit = byte; + dmadata[ch].dma_tx_width = PCH_DMA_WIDTH_4_BYTES; + break; + default: + return -EINVAL; + } + + switch (params_rate(hw_params)) { + case 16000: + case 32000: + case 48000: + clk = 12288000; + break; + default: + return -EINVAL; + } + + mclkfs = clk / params_rate(hw_params); + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_S16_LE: + bclkfs = 32; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bclkfs = 64; + break; + default: + pr_err("%s: Failed not support format\n", __func__); + return -EINVAL; + } + + i2sclkcnt = ioread32(i2s_data->iobase + I2SCLKCNT0_OFFSET + 0x10 * ch); + i2sclkcnt |= bclkfs << I2SCLKCNT_BCLKFS_OFFSET; + i2sclkcnt |= mclkfs << I2SCLKCNT_MCLKFS_OFFSET; + iowrite32(i2sclkcnt, iobase + I2SCLKCNT0_OFFSET + 0x10 * ch); + + snd_soc_dai_set_dma_data(dai, substream, &dmadata[ch]); + + return 0; +} + +static int ml7213i2s_dai_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + u32 cmn_reg[MAX_I2S_CH]; + u32 tx_reg[MAX_I2S_CH]; + u32 rx_reg[MAX_I2S_CH]; + int i; + int offset = 0; + void *iobase = i2s_data->iobase; + + /* set master/slave audio interface */ + for (i = 0; i < MAX_I2S_CH; i++, offset = i * 0x800) { + cmn_reg[i] = ioread32(iobase + I2SCLKCNT0_OFFSET + 0x10 * i); + tx_reg[i] = ioread32(iobase + offset + I2SCNTTX_OFFSET); + rx_reg[i] = ioread32(iobase + offset + I2SCNTRX_OFFSET); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + cmn_reg[i] |= I2SCLKCNT_MSSEL; + break; + case SND_SOC_DAIFMT_CBS_CFS: + cmn_reg[i] &= ~I2SCLKCNT_MSSEL; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cmn_reg[i] |= ML7213I2S_LRCLK_FMT_I2S; + tx_reg[i] &= ~ML7213I2S_TX_I2S | ~ML7213I2S_TX_DLY |\ + ~ML7213I2S_TX_MSB_LSB |\ + ~ML7213I2S_TX_LR_POL | ~ML7213I2S_TX_AFT; + rx_reg[i] &= ~ML7213I2S_RX_I2S | ~ML7213I2S_RX_DLY |\ + ~ML7213I2S_RX_MSB_LSB |\ + ~ML7213I2S_RX_LR_POL | ~ML7213I2S_RX_AFT; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + cmn_reg[i] &= ~ML7213I2S_BCLKPOL; + break; + case SND_SOC_DAIFMT_IB_NF: + cmn_reg[i] |= ML7213I2S_BCLKPOL; + break; + default: + return -EINVAL; + } + + iowrite32(cmn_reg[i], iobase + I2SCLKCNT0_OFFSET + 0x10 * i); + iowrite32(tx_reg[i], iobase + offset + I2SCNTTX_OFFSET); + iowrite32(rx_reg[i], iobase + offset + I2SCNTRX_OFFSET); + } + + return 0; +} + +static int ml7213i2s_dai_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + u32 reg[MAX_I2S_CH]; + void *iobase = i2s_data->iobase; + int i; + + for (i = 0; i < MAX_I2S_CH; i++) { + reg[i] = ioread32(iobase + I2SCLKCNT0_OFFSET + 0x10 * i); + if (clk_id == IOH_MASTERCLKSEL_MCLK) + reg[i] &= ~ML7213I2S_MASTER_CLK_SEL; + else if (clk_id == IOH_MASTERCLKSEL_MLBCLK) + reg[i] |= ML7213I2S_MASTER_CLK_SEL; + iowrite32(reg[i], iobase + I2SCLKCNT0_OFFSET + 0x10 * i); + } + + return 0; +} + +static int ml7213i2s_dai_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + int i; + unsigned int slot; + unsigned int tx_mapped = 0, rx_mapped = 0; + + if ((tx_num > MAX_I2S_CH) || (rx_num > MAX_I2S_CH)) + return -EINVAL; + + for (i = 0; i < tx_num; i++) { + slot = tx_slot[i]; + if ((slot < MAX_I2S_CH) && + (!(tx_mapped & (1 << slot)))) { + dmadata[i].mapped_tx_ch = slot; + tx_mapped |= 1 << slot; + } else + return -EINVAL; + } + + for (i = 0; i < rx_num; i++) { + slot = rx_slot[i]; + if ((slot < MAX_I2S_CH) && + (!(rx_mapped & (1 << slot)))) { + dmadata[i].mapped_rx_ch = slot; + rx_mapped |= 1 << slot; + } else + return -EINVAL; + } + + return 0; +} + +#ifdef CONFIG_PM +static int ml7213i2s_soc_suspend(struct snd_soc_dai *cpu_dai) +{ + int i; + void *iobase; + struct ioh_i2s_pm_ch_reg *save; + struct ioh_i2s_pm_ch_reg_cmn *save_cmn; + int offset; + + iobase = i2s_data->iobase; + for (i = 0, offset = 0; i < MAX_I2S_CH; i++, offset = i * 0x800) { + save = &i2s_data->ch_reg_save[i]; + + 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); + } + + save_cmn = &i2s_data->cmn_reg_save; + for (i = 0; i < MAX_I2S_CH; i++) { + save_cmn->i2sclkcnt[i] = + ioread32(i2s_data->iobase + I2SCLKCNT0_OFFSET + 0x10 * i); + } + save_cmn->i2simask = ioread32(i2s_data->iobase + I2SIMASK_OFFSET); + + return 0; +} + +static int ml7213i2s_soc_resume(struct snd_soc_dai *cpu_dai) +{ + int i; + void *iobase; + struct ioh_i2s_pm_ch_reg *save; + int offset; + + iobase = i2s_data->iobase; + save = &i2s_data->ch_reg_save[0]; + for (i = 0, offset = 0; i < MAX_I2S_CH; i++, offset = i * 0x800) { + 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_CH; i++) { + iowrite32(i2s_data->cmn_reg_save.i2sclkcnt[i], + i2s_data->iobase + I2SCLKCNT0_OFFSET + 0x10 * i); + } + + iowrite32(i2s_data->cmn_reg_save.i2simask, + i2s_data->iobase + I2SIMASK_OFFSET); + + return 0; +} +#else +#define ml7213i2s_soc_suspend NULL +#define ml7213i2s_soc_resume NULL +#endif + +static const struct snd_soc_dai_ops ml7213i2s_dai_ops = { + .hw_params = ml7213i2s_dai_hw_params, + .set_fmt = ml7213i2s_dai_set_dai_fmt, + .set_sysclk = ml7213i2s_dai_set_dai_sysclk, + .set_channel_map = ml7213i2s_dai_set_channel_map, +}; + +static struct snd_soc_dai_driver ml7213i2s_dai_data = { + .playback = { + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .rates = ML7213_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .rates = ML7213_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &ml7213i2s_dai_ops, + .suspend = ml7213i2s_soc_suspend, + .resume = ml7213i2s_soc_resume, +}; + +/* 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; + void __iomem *tbl; + unsigned int mapbase; + int i; + + rv = pci_enable_device(pdev); + if (rv) + goto enable_device; + + 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; + } + + i2s_data = devm_kzalloc(&pdev->dev, sizeof(*i2s_data), GFP_KERNEL); + if (!i2s_data) { + dev_err(&pdev->dev, "Can't allocate i2s_data\n"); + rv = -ENOMEM; + goto out_kzalloc_data; + } + + dmadata = devm_kzalloc(&pdev->dev, + sizeof(*dmadata) * MAX_I2S_CH, GFP_KERNEL); + if (!dmadata) { + dev_err(&pdev->dev, "Can't allocate dmadata\n"); + rv = -ENOMEM; + goto out_kzalloc_dma; + } + + i2s_data->dev = &pdev->dev; + i2s_data->pdev = pdev; + i2s_data->iobase = tbl; + i2s_data->mapbase = mapbase; + spin_lock_init(&i2s_data->tx_lock); + + dev_set_name(&pdev->dev, "%s", "ml7213ioh"); + rv = snd_soc_register_dai(&pdev->dev, &ml7213i2s_dai_data); + if (rv < 0) { + printk(KERN_ERR "Failed to snd_soc_register_dai\n"); + goto out_register_dai; + } + + dev_set_name(&pdev->dev, "%s", "ml7213-i2s-audio"); + rv = snd_soc_register_platform(&pdev->dev, &ml7213ioh_soc_platform); + if (rv < 0) { + printk(KERN_ERR "Failed to snd_soc_register_platform\n"); + goto out_soc_register_plat; + } + + for (i = 0; i < MAX_I2S_CH; i++) + dmadata[i].number = i; + + return 0; + +out_soc_register_plat: + snd_soc_unregister_dai(&pdev->dev); +out_register_dai: +out_kzalloc_dma: +out_kzalloc_data: +out_pci_resource: + pci_iounmap(pdev, i2s_data->iobase); +out_ipmap: + pci_disable_device(pdev); +enable_device: + + return rv; +} + +static void ioh_i2s_pci_remove(struct pci_dev *pdev) +{ + int i; + + for (i = 0; i < MAX_I2S_CH; i++) + ioh_i2s_reset(i); + + snd_soc_unregister_platform(&pdev->dev); + snd_soc_unregister_dai(&pdev->dev); + pci_iounmap(pdev, i2s_data->iobase); + pci_disable_device(pdev); +} + +static int ioh_i2s_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + int ret; + + 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 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); + + 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_plat_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_plat_init); +module_exit(ioh_i2s_cleanup); + +MODULE_AUTHOR("Tomoya MORINAGA tomoya.rohm@gmail.com"); +MODULE_DESCRIPTION("LAPIS Semiconductor ML7213 IOH ALSA SoC platform driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/lapis/ml7213ioh-plat.h b/sound/soc/lapis/ml7213ioh-plat.h new file mode 100644 index 0000000..e7fe65a --- /dev/null +++ b/sound/soc/lapis/ml7213ioh-plat.h @@ -0,0 +1,376 @@ +/* + * 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. + */ + +#ifndef ML7213IOH_PLAT_H +#define ML7213IOH_PLAT_H + +#include <linux/interrupt.h> +#include <linux/pch_dma.h> + +#define DRV_NAME "ml7213ioh-i2s-pci" +#define PCI_VENDOR_ID_ROHM 0X10DB +#define PCI_DEVICE_ID_ML7213_I2S 0X8033 + +#define I2SCLKCNT_MSSEL BIT(0) +#define ML7213I2S_BCLKPOL BIT(1) +#define ML7213I2S_LRCLK_FMT (BIT(4) | BIT(5)) +#define ML7213I2S_LRCLK_FMT_I2S BIT(4) + +#define ML7213I2S_TX_I2S BIT(0) +#define ML7213I2S_TX_DLY BIT(12) +#define ML7213I2S_TX_MSB_LSB BIT(13) +#define ML7213I2S_TX_LR_POL BIT(14) +#define ML7213I2S_TX_AFT BIT(15) +#define ML7213I2S_RX_I2S BIT(0) +#define ML7213I2S_RX_DLY BIT(12) +#define ML7213I2S_RX_MSB_LSB BIT(13) +#define ML7213I2S_RX_LR_POL BIT(14) +#define ML7213I2S_RX_AFT BIT(15) +#define ML7213I2S_MASTER_CLK_SEL BIT(2) + +#define ML7213_I2S_RATES \ + (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000) + +/* ioh_bclkfs_t */ +#define IOH_BCLKFS_8FS 0 +#define IOH_BCLKFS_16FS 1 +#define IOH_BCLKFS_32FS 2 +#define IOH_BCLKFS_64FS 3 + +#define I2SCLKCNT_MCLKFS_OFFSET (8) +#define I2SCLKCNT_BCLKFS_OFFSET (12) + +#define I2SCLKCNT0_OFFSET 0x3000 +#define I2SCLKCNT1_OFFSET 0x3010 +#define I2SCLKCNT2_OFFSET 0x3020 +#define I2SCLKCNT3_OFFSET 0x3030 +#define I2SCLKCNT4_OFFSET 0x3040 +#define I2SCLKCNT5_OFFSET 0x3050 +#define I2SISTATUS_OFFSET 0x3080 +#define I2SIDISP_OFFSET 0x3084 +#define I2SIMASK_OFFSET 0x3088 +#define I2SIMASKCLR_OFFSET 0x308C +#define I2SSRST_OFFSET 0x3FFC +#define I2SDRTX_OFFSET 0x0 +#define I2SCNTTX_OFFSET 0x4 +#define I2SFIFOCTX_OFFSET 0x8 +#define I2SAFTX_OFFSET 0xC +#define I2SAETX_OFFSET 0x10 +#define I2SMSKTX_OFFSET 0x14 +#define I2SISTTX_OFFSET 0x18 +#define I2SMONTX_OFFSET 0x1C +#define I2SDRRX_OFFSET 0x20 +#define I2SCNTRX_OFFSET 0x24 +#define I2SFIFOCRX_OFFSET 0x28 +#define I2SAFRX_OFFSET 0x2C +#define I2SAERX_OFFSET 0x30 +#define I2SMSKRX_OFFSET 0x34 +#define I2SISTRX_OFFSET 0x38 +#define I2SMONRX_OFFSET 0x3C +#define FIRST_TX_OFFSET 0x0 +#define FIRST_RX_OFFSET 0x0 + +#define I2SDRTXMIRROR_OFFSET 0x100 +#define I2SDRRXMIRROR_OFFSET 0x400 + +#define I2S_ALL_INTERRUPT_BITS 0x3F003F +#define I2S_IDISP_BITS 0x3F003F +#define I2S_IDISP_TX_BITS 0x00003F +#define I2S_IDISP_RX_BITS 0x3F0000 +#define TX_BIT_FIMSK 0x1 /*Fifo full interrupt mask bit*/ +#define TX_BIT_AFIMSK 0x2 /*Fifo Almost full interrupt mask bit*/ +#define TX_BIT_EIMSK 0x4 /*Fifo empty interrupt mask bit*/ +#define TX_BIT_AEIMSK 0x8 /*Fifo Almost empty interrupt mask bit*/ +#define TX_BIT_DMAMSK 0x10 /*Masks DMA*/ +#define TX_BIT_DMATC 0x100 +#define I2S_TX_ALL_INTR_MASK_BITS (TX_BIT_FIMSK | TX_BIT_AFIMSK | TX_BIT_EIMSK \ + | TX_BIT_AEIMSK) +#define I2S_TX_NORMAL_INTR_MASK_BITS (TX_BIT_FIMSK | TX_BIT_AFIMSK) +#define RX_BIT_FIMSK 0x1 /*Fifo full interrupt mask bit*/ +#define RX_BIT_AFIMSK 0x2 /*Fifo Almost full interrupt mask bit*/ +#define RX_BIT_EIMSK 0x4 /*Fifo empty interrupt mask bit*/ +#define RX_BIT_AEIMSK 0x8 /*Fifo Almost empty interrupt mask bit*/ +#define RX_BIT_DMAMSK 0x10 /*Masks DMA*/ +#define RX_BIT_DMATC 0x100 +#define I2S_RX_ALL_INTR_MASK_BITS (RX_BIT_FIMSK | RX_BIT_AFIMSK | RX_BIT_EIMSK \ + | RX_BIT_AEIMSK) +#define I2S_RX_NORMAL_INTR_MASK_BITS (RX_BIT_EIMSK | RX_BIT_AEIMSK) +#define I2S_TX_FINT 0x1 /*Full Interrupt*/ +#define I2S_TX_AFINT 0x2 /*Almost full interrupt*/ +#define I2S_TX_EINT 0x4 /*Empty interrupt*/ +#define I2S_TX_AEINT 0x8 /*Almost empty interrupt*/ +#define I2S_RX_FINT 0x1 /*Full Interrupt*/ +#define I2S_RX_AFINT 0x2 /*Almost full interrupt*/ +#define I2S_RX_EINT 0x4 /*Empty interrupt*/ +#define I2S_RX_AEINT 0x8 /*Almost empty interrupt*/ + +#define I2S_FIFO_TX_FCLR BIT(0) +#define I2S_FIFO_TX_RUN BIT(4) +#define I2S_FIFO_RX_FCLR BIT(0) +#define I2S_FIFO_RX_RUN BIT(4) + +#define FIFO_CTRL_BIT_TX_RUN 0x10 +#define FIFO_CTRL_BIT_RX_RUN 0x10 +#define I2S_CNT_BIT_TEL 0x1 +#define I2S_IMASK_TX_BIT_START 0 +#define I2S_IMASK_RX_BIT_START 16 + +/* DMA processing */ +#define PERIOD_POS_MAX I2S_DMA_SG_NUM +#define PERIOD_LEN (I2S_AFULL_THRESH * PERIOD_POS_MAX) + +#define SUPPORT_FORMAT (SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) +#define MAX_PERIOD_SIZE (PERIOD_LEN * 4) + +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +#define MAX_I2S_CH 6 /*I2S0 ~ I2S5*/ +#define USE_PERIODS_MIN (I2S_DMA_SG_MAX) +#define USE_PERIODS_MAX (I2S_DMA_SG_MAX) + +#define I2S_AEMPTY_THRESH 64 /* Almost Empty Threshold */ +#define I2S_AFULL_THRESH 64 /* Almost Full Threshold */ + +#define I2S_DMA_SG_NUM (128) +#define I2S_DMA_SG_MAX (64) + +#define IOH_MSSEL_MASTER 1 + +#define ML7213_I2SAERX_DEFAULT 0x1f +#define ML7213_I2SMSKRX_DEFAULT 0x1f +#define ML7213_I2SISTRX_DEFAULT 0xC + +#define ML7213_I2SMSKTX_DEFAULT 0x1f +#define ML7213_I2SISTTX_DEFAULT 0xC + +enum ioh_i2s_fifo_type { + IOH_FIFO_32 = 4, + IOH_FIFO_16 = 2, + IOH_FIFO_8 = 1, +}; + +enum ioh_i2s_status { + IOH_EOK = 0, + IOH_EDONE = 1, + IOH_EUNDERRUN = 2, + IOH_EOVERRUN = 3, + IOH_EFRAMESYNC = 4, +}; + +enum ioh_bclkpol_t { + ioh_BCLKPOL_FALLING = 0, + ioh_BCLKPOL_RISING, +}; + +enum ioh_masterclksel_t { + IOH_MASTERCLKSEL_MCLK = 0, + IOH_MASTERCLKSEL_MLBCLK, +}; + +enum ioh_lrckfmt_t { + IOH_LRCLKFMT_I2S = 1, + IOH_LRCLKFMT_LONGFRAME, + IOH_LRCLKFMT_SHORTFRAME, +}; + +enum ioh_mclkfs_t { + IOH_MCLKFS_64FS = 0, + IOH_MCLKFS_128FS, + IOH_MCLKFS_192FS, + IOH_MCLKFS_256FS, + IOH_MCLKFS_384FS, + IOH_MCLKFS_512FS, + IOH_MCLKFS_768FS, + IOH_MCLKFS_1024FS, +}; + +enum ioh_dlyoff_t { + IOH_DLYOFF_DLY_ON = 0, /* date delat on */ + IOH_DLYOFF_DLY_OFF, /* date delat off */ +}; + +enum ioh_lrpol_t { + IOH_LRPOL_NO_INVERT = 0, /* Low of LRCLK is L data. + High of LRCLK is R data. */ + IOH_LRPOL_INVERT, /* Low of LRCLK is R data. + High of LRCLK is L data. */ +}; + +enum ioh_aft_t { + IOH_AFR_FRONT = 0, + IOH_AFR_BACK, +}; + +struct ml7213i2s_runtime_data { + spinlock_t lock; + int tx_stop; + int rx_stop; + struct ioh_i2s_dma *rx_dma; + struct ioh_i2s_dma *tx_dma; +}; + +struct ioh_i2s_pm_ch_reg { + u32 i2sdrtx; /* Tx: data register */ + u32 i2scnttx; /* Tx: control register */ + u32 i2sfifoctx; /* Tx: FIFO control register */ + u32 i2saftx; /* Tx: almost full threshold setting */ + u32 i2saetx; /* Tx: almost empty threshold setting */ + u32 i2smsktx; /* Tx: interrupt mask settings */ + u32 i2sisttx; /* Tx: for acknowledging interrupts */ + u32 i2scntrx; /* Rx: control register */ + u32 i2sfifocrx; /* Rx: FIFO control register */ + u32 i2safrx; /* Rx: almost full threshold setting */ + u32 i2saerx; /* Rx: almost empty threshold setting */ + u32 i2smskrx; /* Rx: interrupt mask settings */ + u32 i2sistrx; /* Rx: for acknowledging interrupts */ +}; + +struct ioh_i2s_pm_ch_reg_cmn { + u32 i2sclkcnt[MAX_I2S_CH]; /*clock control register(ch0~5) */ + u32 i2simask; /*interrupt mask */ +}; + +struct ioh_i2s_data { + struct device *dev; + void *iobase; + unsigned int mapbase; + spinlock_t tx_lock; + struct ioh_i2s_pm_ch_reg_cmn cmn_reg_save; + struct ioh_i2s_pm_ch_reg ch_reg_save[MAX_I2S_CH]; + struct pci_dev *pdev; +}; + +struct ioh_i2s_dma { + /* Transmit side DMA */ + struct scatterlist *sg_tx_p; + struct scatterlist *sg_rx_p; + + int tx_num; /* The number of sent sg */ + int rx_num; /* The number of sent sg */ + + struct dma_chan *chan_tx; + struct dma_chan *chan_rx; + + int rx_nent; /* The number of rx scatter list */ + int tx_nent; /* The number of tx scatter list */ + + struct dma_async_tx_descriptor *desc_tx; + struct dma_async_tx_descriptor *desc_rx; + + dma_addr_t tx_buf_dma; + dma_addr_t rx_buf_dma; + + struct pch_dma_slave param_tx; + struct pch_dma_slave param_rx; + + int dma_tx_unit; /* 1Byte of 2Byte or 4Byte */ + int dma_rx_unit; /* 1Byte of 2Byte or 4Byte */ + int dma_tx_width; + int dma_rx_width; + + struct snd_pcm_substream *tx_substream; + struct snd_pcm_substream *rx_substream; + + int number; + + int mapped_tx_ch; + int mapped_rx_ch; + + unsigned char *rx_dma_addr; + dma_addr_t rx_physical_addr; + unsigned long rx_buffer_bytes; + unsigned long rx_period_bytes; + + unsigned char *tx_dma_addr; + dma_addr_t tx_physical_addr; + unsigned long tx_buffer_bytes; + unsigned long tx_period_bytes; + + + int rx_buf_frags; + int tx_buf_frags; + + int tx_cur_period; + int rx_cur_period; +}; + +struct ml7213i2s_dai { + struct snd_soc_dai_driver dai; + struct device *dev; + void *iobase; + u32 freq; +}; + +struct ioh_i2s_config_common_reg { + u32 i2sclkcnt; /*clock control register(ch0~5) */ + u32 i2sistatus; /*interrupt status */ + u32 i2sidisp; /*active interrupts */ + u32 i2simask; /*interrupt mask */ + u32 i2simaskclr; /*interrupt mask clear */ +}; + +struct ioh_i2s_config_tx_reg { + u32 i2sdrtx; /*data register */ + u32 i2scnttx; /*control register */ + u32 i2sfifoctx; /*FIFO control register */ + u32 i2saftx; /*almost full threshold setting */ + u32 i2saetx; /*almost empty threshold setting */ + u32 i2smsktx; /*interrupt mask settings */ + u32 i2sisttx; /*for acknowledging interrupts */ + u32 i2smontx; /*monitor register */ +}; + +struct ioh_i2s_config_rx_reg { + u32 i2sdrrx; /* data register */ + u32 i2scntrx; /* control register */ + u32 i2sfifocrx;/* FIFO control register */ + u32 i2safrx; /* almost full threshold setting */ + u32 i2saerx; /* almost empty threshold setting */ + u32 i2smskrx; /* interrupt mask settings */ + u32 i2sistrx; /* for acknowledging interrupts */ + u32 i2smonrx; /* monitor register */ +}; + +struct ioh_i2s_config_reg { + /* The common register settings */ + struct ioh_i2s_config_common_reg cmn; + + /* TX channel settings */ + struct ioh_i2s_config_tx_reg tx; + + /* RX channel settings */ + struct ioh_i2s_config_rx_reg rx; +}; + +static struct snd_pcm_hardware ml7213i2s_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SUPPORT_FORMAT, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 64 * 128 * 4 * 64, + .period_bytes_min = 64 * 4, + .period_bytes_max = 64 * 128 * 4, + .periods_min = 2, + .periods_max = 64, + .fifo_size = 0, +}; +#endif