/* * ALSA SoC Synopsys I2S Audio Layer * * sound/soc/spear/designware_i2s.c * * Copyright (C) 2010 ST Microelectronics * Rajeev Kumar <rajeev-dlh.kumar@st.com> * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied. */ #include <linux/clk.h> #include <linux/device.h> #include <linux/init.h> #include <linux/io.h> #include <linux/interrupt.h> #include <linux/module.h> #include <linux/slab.h> #include <sound/designware_i2s.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> /* common register for all channel */ #define IER 0x000 #define IRER 0x004 #define ITER 0x008 #define CER 0x00C #define CCR 0x010 #define RXFFR 0x014 #define TXFFR 0x018 /* I2STxRxRegisters for all channels */ #define LRBR_LTHR(x) (0x40 * x + 0x020) #define RRBR_RTHR(x) (0x40 * x + 0x024) #define RER(x) (0x40 * x + 0x028) #define TER(x) (0x40 * x + 0x02C) #define RCR(x) (0x40 * x + 0x030) #define TCR(x) (0x40 * x + 0x034) #define ISR(x) (0x40 * x + 0x038) #define IMR(x) (0x40 * x + 0x03C) #define ROR(x) (0x40 * x + 0x040) #define TOR(x) (0x40 * x + 0x044) #define RFCR(x) (0x40 * x + 0x048) #define TFCR(x) (0x40 * x + 0x04C) #define RFF(x) (0x40 * x + 0x050) #define TFF(x) (0x40 * x + 0x054) /* I2SCOMPRegisters */ #define I2S_COMP_PARAM_2 0x01F0 #define I2S_COMP_PARAM_1 0x01F4 #define I2S_COMP_VERSION 0x01F8 #define I2S_COMP_TYPE 0x01FC #define MAX_CHANNEL_NUM 8 #define MIN_CHANNEL_NUM 2 struct dw_i2s_dev { void __iomem *i2s_base; struct clk *clk; int active; unsigned int capability; struct device *dev; /* data related to DMA transfers b/w i2s and DMAC */ struct i2s_dma_data play_dma_data; struct i2s_dma_data capture_dma_data; struct i2s_clk_config_data config; int (*i2s_clk_cfg)(struct i2s_clk_config_data *config); }; static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val) { writel(val, io_base + reg); } static inline u32 i2s_read_reg(void __iomem *io_base, int reg) { return readl(io_base + reg); } static inline void i2s_disable_channels(struct dw_i2s_dev *dev, u32 stream) { u32 i = 0; if (stream == SNDRV_PCM_STREAM_PLAYBACK) { for (i = 0; i < 4; i++) i2s_write_reg(dev->i2s_base, TER(i), 0); } else { for (i = 0; i < 4; i++) i2s_write_reg(dev->i2s_base, RER(i), 0); } } static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream) { u32 i = 0; if (stream == SNDRV_PCM_STREAM_PLAYBACK) { for (i = 0; i < 4; i++) i2s_write_reg(dev->i2s_base, TOR(i), 0); } else { for (i = 0; i < 4; i++) i2s_write_reg(dev->i2s_base, ROR(i), 0); } } static void i2s_start(struct dw_i2s_dev *dev, struct snd_pcm_substream *substream) { i2s_write_reg(dev->i2s_base, IER, 1); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) i2s_write_reg(dev->i2s_base, ITER, 1); else i2s_write_reg(dev->i2s_base, IRER, 1); i2s_write_reg(dev->i2s_base, CER, 1); } static void i2s_stop(struct dw_i2s_dev *dev, struct snd_pcm_substream *substream) { u32 i = 0, irq; i2s_clear_irqs(dev, substream->stream); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { i2s_write_reg(dev->i2s_base, ITER, 0); for (i = 0; i < 4; i++) { irq = i2s_read_reg(dev->i2s_base, IMR(i)); i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30); } } else { i2s_write_reg(dev->i2s_base, IRER, 0); for (i = 0; i < 4; i++) { irq = i2s_read_reg(dev->i2s_base, IMR(i)); i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03); } } if (!dev->active) { i2s_write_reg(dev->i2s_base, CER, 0); i2s_write_reg(dev->i2s_base, IER, 0); } } static int dw_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); struct i2s_dma_data *dma_data = NULL; if (!(dev->capability & DWC_I2S_RECORD) && (substream->stream == SNDRV_PCM_STREAM_CAPTURE)) return -EINVAL; if (!(dev->capability & DWC_I2S_PLAY) && (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) return -EINVAL; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) dma_data = &dev->play_dma_data; else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) dma_data = &dev->capture_dma_data; snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data); return 0; } static int dw_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); struct i2s_clk_config_data *config = &dev->config; u32 ccr, xfer_resolution, ch_reg, irq; int ret; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: config->data_width = 16; ccr = 0x00; xfer_resolution = 0x02; break; case SNDRV_PCM_FORMAT_S24_LE: config->data_width = 24; ccr = 0x08; xfer_resolution = 0x04; break; case SNDRV_PCM_FORMAT_S32_LE: config->data_width = 32; ccr = 0x10; xfer_resolution = 0x05; break; default: dev_err(dev->dev, "designware-i2s: unsuppted PCM fmt"); return -EINVAL; } config->chan_nr = params_channels(params); switch (config->chan_nr) { case EIGHT_CHANNEL_SUPPORT: ch_reg = 3; break; case SIX_CHANNEL_SUPPORT: ch_reg = 2; break; case FOUR_CHANNEL_SUPPORT: ch_reg = 1; break; case TWO_CHANNEL_SUPPORT: ch_reg = 0; break; default: dev_err(dev->dev, "channel not supported\n"); return -EINVAL; } i2s_disable_channels(dev, substream->stream); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { i2s_write_reg(dev->i2s_base, TCR(ch_reg), xfer_resolution); i2s_write_reg(dev->i2s_base, TFCR(ch_reg), 0x02); irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg)); i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x30); i2s_write_reg(dev->i2s_base, TER(ch_reg), 1); } else { i2s_write_reg(dev->i2s_base, RCR(ch_reg), xfer_resolution); i2s_write_reg(dev->i2s_base, RFCR(ch_reg), 0x07); irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg)); i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x03); i2s_write_reg(dev->i2s_base, RER(ch_reg), 1); } i2s_write_reg(dev->i2s_base, CCR, ccr); config->sample_rate = params_rate(params); if (!dev->i2s_clk_cfg) return -EINVAL; ret = dev->i2s_clk_cfg(config); if (ret < 0) { dev_err(dev->dev, "runtime audio clk config fail\n"); return ret; } return 0; } static void dw_i2s_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { snd_soc_dai_set_dma_data(dai, substream, NULL); } static int dw_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); int ret = 0; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: dev->active++; i2s_start(dev, substream); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: dev->active--; i2s_stop(dev, substream); break; default: ret = -EINVAL; break; } return ret; } static struct snd_soc_dai_ops dw_i2s_dai_ops = { .startup = dw_i2s_startup, .shutdown = dw_i2s_shutdown, .hw_params = dw_i2s_hw_params, .trigger = dw_i2s_trigger, }; static const struct snd_soc_component_driver dw_i2s_component = { .name = "dw-i2s", }; #ifdef CONFIG_PM static int dw_i2s_suspend(struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); clk_disable(dev->clk); return 0; } static int dw_i2s_resume(struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); clk_enable(dev->clk); return 0; } #else #define dw_i2s_suspend NULL #define dw_i2s_resume NULL #endif static int dw_i2s_probe(struct platform_device *pdev) { const struct i2s_platform_data *pdata = pdev->dev.platform_data; struct dw_i2s_dev *dev; struct resource *res; int ret; unsigned int cap; struct snd_soc_dai_driver *dw_i2s_dai; if (!pdata) { dev_err(&pdev->dev, "Invalid platform data\n"); return -EINVAL; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "no i2s resource defined\n"); return -ENODEV; } if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), pdev->name)) { dev_err(&pdev->dev, "i2s region already claimed\n"); return -EBUSY; } dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) { dev_warn(&pdev->dev, "kzalloc fail\n"); return -ENOMEM; } dev->i2s_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (!dev->i2s_base) { dev_err(&pdev->dev, "ioremap fail for i2s_region\n"); return -ENOMEM; } cap = pdata->cap; dev->capability = cap; dev->i2s_clk_cfg = pdata->i2s_clk_cfg; /* Set DMA slaves info */ dev->play_dma_data.data = pdata->play_dma_data; dev->capture_dma_data.data = pdata->capture_dma_data; dev->play_dma_data.addr = res->start + I2S_TXDMA; dev->capture_dma_data.addr = res->start + I2S_RXDMA; dev->play_dma_data.max_burst = 16; dev->capture_dma_data.max_burst = 16; dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; dev->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; dev->play_dma_data.filter = pdata->filter; dev->capture_dma_data.filter = pdata->filter; dev->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(dev->clk)) return PTR_ERR(dev->clk); ret = clk_enable(dev->clk); if (ret < 0) goto err_clk_put; dw_i2s_dai = devm_kzalloc(&pdev->dev, sizeof(*dw_i2s_dai), GFP_KERNEL); if (!dw_i2s_dai) { dev_err(&pdev->dev, "mem allocation failed for dai driver\n"); ret = -ENOMEM; goto err_clk_disable; } if (cap & DWC_I2S_PLAY) { dev_dbg(&pdev->dev, " SPEAr: play supported\n"); dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM; dw_i2s_dai->playback.channels_max = pdata->channel; dw_i2s_dai->playback.formats = pdata->snd_fmts; dw_i2s_dai->playback.rates = pdata->snd_rates; } if (cap & DWC_I2S_RECORD) { dev_dbg(&pdev->dev, "SPEAr: record supported\n"); dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM; dw_i2s_dai->capture.channels_max = pdata->channel; dw_i2s_dai->capture.formats = pdata->snd_fmts; dw_i2s_dai->capture.rates = pdata->snd_rates; } dw_i2s_dai->ops = &dw_i2s_dai_ops; dw_i2s_dai->suspend = dw_i2s_suspend; dw_i2s_dai->resume = dw_i2s_resume; dev->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, dev); ret = snd_soc_register_component(&pdev->dev, &dw_i2s_component, dw_i2s_dai, 1); if (ret != 0) { dev_err(&pdev->dev, "not able to register dai\n"); goto err_set_drvdata; } return 0; err_set_drvdata: dev_set_drvdata(&pdev->dev, NULL); err_clk_disable: clk_disable(dev->clk); err_clk_put: clk_put(dev->clk); return ret; } static int dw_i2s_remove(struct platform_device *pdev) { struct dw_i2s_dev *dev = dev_get_drvdata(&pdev->dev); snd_soc_unregister_component(&pdev->dev); dev_set_drvdata(&pdev->dev, NULL); clk_put(dev->clk); return 0; } static struct platform_driver dw_i2s_driver = { .probe = dw_i2s_probe, .remove = dw_i2s_remove, .driver = { .name = "designware-i2s", .owner = THIS_MODULE, }, }; module_platform_driver(dw_i2s_driver); MODULE_AUTHOR("Rajeev Kumar <rajeev-dlh.kumar@st.com>"); MODULE_DESCRIPTION("DESIGNWARE I2S SoC Interface"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:designware_i2s");