blob: bc95ea272adce6af9b99422a2e67a94d238e5b92 [file] [log] [blame]
/*
* sound/soc/ambarella_i2s.c
*
* History:
* 2008/03/03 - [Eric Lee] created file
* 2008/04/16 - [Eric Lee] Removed the compiling warning
* 2009/01/22 - [Anthony Ginger] Port to 2.6.28
* 2009/03/05 - [Cao Rongrong] Update from 2.6.22.10
* 2009/06/10 - [Cao Rongrong] Port to 2.6.29
* 2009/06/29 - [Cao Rongrong] Support more mclk and fs
* 2010/10/25 - [Cao Rongrong] Port to 2.6.36+
* 2011/03/20 - [Cao Rongrong] Port to 2.6.38
*
* Copyright (C) 2004-2009, Ambarella, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This 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/delay.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include <plat/dma.h>
#include <plat/audio.h>
#include "ambarella_pcm.h"
#include "ambarella_i2s.h"
static unsigned int capture_enabled = 1;
module_param(capture_enabled, uint, 0644);
MODULE_PARM_DESC(capture_enabled, "capture_enabled.");
static unsigned int bclk_reverse = 0;
module_param(bclk_reverse, uint, 0644);
MODULE_PARM_DESC(bclk_reverse, "bclk_reverse.");
static DEFINE_MUTEX(clock_reg_mutex);
static inline void dai_tx_enable(void)
{
amba_setbitsl(I2S_INIT_REG, I2S_TX_ENABLE_BIT);
}
static inline void dai_rx_enable(void)
{
amba_setbitsl(I2S_INIT_REG, I2S_RX_ENABLE_BIT);
}
static inline void dai_tx_disable(void)
{
amba_clrbitsl(I2S_INIT_REG, I2S_TX_ENABLE_BIT);
}
static inline void dai_rx_disable(void)
{
if(capture_enabled)
amba_clrbitsl(I2S_INIT_REG, I2S_RX_ENABLE_BIT);
}
static inline void dai_tx_fifo_rst(void)
{
amba_setbitsl(I2S_INIT_REG, I2S_TX_FIFO_RESET_BIT);
}
static inline void dai_rx_fifo_rst(void)
{
if(capture_enabled)
amba_setbitsl(I2S_INIT_REG, I2S_RX_FIFO_RESET_BIT);
}
static inline void dai_fifo_rst(void)
{
if(capture_enabled)
amba_setbitsl(I2S_INIT_REG, I2S_FIFO_RESET_BIT);
}
static int ambarella_i2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
if(substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
dai_rx_disable();
dai_rx_fifo_rst();
} else {
dai_tx_disable();
dai_tx_fifo_rst();
}
return 0;
}
static int ambarella_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int ret = 0;
/* Add a rule to enforce the DMA buffer align. */
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
if (ret)
goto ambarella_i2s_startup_exit;
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
if (ret)
goto ambarella_i2s_startup_exit;
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
goto ambarella_i2s_startup_exit;
return 0;
ambarella_i2s_startup_exit:
return ret;
}
static int ambarella_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai)
{
struct amb_i2s_priv *priv_data = snd_soc_dai_get_drvdata(cpu_dai);
struct ambarella_i2s_interface *i2s_intf = &priv_data->amb_i2s_intf;
u32 clock_reg;
/* Disable tx/rx before initializing */
dai_tx_disable();
dai_rx_disable();
i2s_intf->sfreq = params_rate(params);
i2s_intf->channels = params_channels(params);
amba_writel(I2S_REG(I2S_CHANNEL_SELECT_OFFSET), i2s_intf->channels / 2 - 1);
/* Set format */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
i2s_intf->multi24 = 0;
i2s_intf->tx_ctrl = I2S_TX_UNISON_BIT;
i2s_intf->word_len = 15;
i2s_intf->shift = 0;
if (i2s_intf->mode == I2S_DSP_MODE) {
i2s_intf->slots = i2s_intf->channels - 1;
i2s_intf->word_pos = 15;
} else {
i2s_intf->slots = 0;
i2s_intf->word_pos = 0;
}
priv_data->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
break;
case SNDRV_PCM_FORMAT_S24_LE:
i2s_intf->multi24 = I2S_24BITMUX_MODE_ENABLE;
i2s_intf->tx_ctrl = 0;
i2s_intf->word_len = 23;
i2s_intf->shift = 1;
if (i2s_intf->mode == I2S_DSP_MODE) {
i2s_intf->slots = i2s_intf->channels - 1;
i2s_intf->word_pos = 0; /* ignored */
} else {
i2s_intf->slots = 0;
i2s_intf->word_pos = 0; /* ignored */
}
priv_data->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
break;
case SNDRV_PCM_FORMAT_S32_LE:
i2s_intf->multi24 = I2S_24BITMUX_MODE_ENABLE;
i2s_intf->tx_ctrl = 0;
i2s_intf->word_len = 31;
i2s_intf->shift = 0;
if (i2s_intf->mode == I2S_DSP_MODE) {
i2s_intf->slots = i2s_intf->channels - 1;
i2s_intf->word_pos = 0; /* ignored */
} else {
i2s_intf->slots = 0;
i2s_intf->word_pos = 0; /* ignored */
}
priv_data->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
break;
default:
goto hw_params_exit;
}
if (priv_data->dai_master == true) {
i2s_intf->tx_ctrl |= I2S_TX_WS_MST_BIT;
i2s_intf->rx_ctrl |= I2S_RX_WS_MST_BIT;
} else {
i2s_intf->tx_ctrl &= ~I2S_TX_WS_MST_BIT;
i2s_intf->rx_ctrl &= ~I2S_RX_WS_MST_BIT;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
amba_writel(I2S_TX_CTRL_REG, i2s_intf->tx_ctrl);
amba_writel(I2S_TX_FIFO_LTH_REG, 0x10);
amba_writel(I2S_GATEOFF_REG, (i2s_intf->shift << 0));
} else {
amba_writel(I2S_RX_CTRL_REG, i2s_intf->rx_ctrl);
amba_writel(I2S_RX_FIFO_GTH_REG, 0x20);
amba_writel(I2S_GATEOFF_REG, (i2s_intf->shift << 1));
}
amba_writel(I2S_MODE_REG, i2s_intf->mode);
amba_writel(I2S_WLEN_REG, i2s_intf->word_len);
amba_writel(I2S_WPOS_REG, i2s_intf->word_pos);
amba_writel(I2S_SLOT_REG, i2s_intf->slots);
amba_writel(I2S_24BITMUX_MODE_REG, i2s_intf->multi24);
/* Set clock */
clk_set_rate(priv_data->mclk, i2s_intf->mclk);
mutex_lock(&clock_reg_mutex);
clock_reg = amba_readl(I2S_CLOCK_REG);
clock_reg &= ~DAI_CLOCK_MASK;
clock_reg |= i2s_intf->div;
if (priv_data->dai_master == true)
clock_reg |= I2S_CLK_MASTER_MODE;
else
clock_reg &= ~I2S_CLK_MASTER_MODE;
if (bclk_reverse)
clock_reg &= ~I2S_CLK_TX_PO_FALL;
else
clock_reg |= I2S_CLK_TX_PO_FALL;
amba_writel(I2S_CLOCK_REG, clock_reg);
mutex_unlock(&clock_reg_mutex);
dai_rx_enable();
dai_tx_enable();
msleep(1);
/* Notify HDMI that the audio interface is changed */
ambarella_audio_notify_transition(&priv_data->amb_i2s_intf,
AUDIO_NOTIFY_SETHWPARAMS);
return 0;
hw_params_exit:
dai_rx_enable();
dai_tx_enable();
return -EINVAL;
}
static int ambarella_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *cpu_dai)
{
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dai_tx_enable();
else
dai_rx_enable();
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK){
dai_tx_disable();
dai_tx_fifo_rst();
}else{
dai_rx_disable();
dai_rx_fifo_rst();
}
break;
default:
break;
}
return 0;
}
/*
* Set Ambarella I2S DAI format
*/
static int ambarella_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct amb_i2s_priv *priv_data = snd_soc_dai_get_drvdata(cpu_dai);
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_LEFT_J:
priv_data->amb_i2s_intf.mode = I2S_LEFT_JUSTIFIED_MODE;
break;
case SND_SOC_DAIFMT_RIGHT_J:
priv_data->amb_i2s_intf.mode = I2S_RIGHT_JUSTIFIED_MODE;
break;
case SND_SOC_DAIFMT_I2S:
priv_data->amb_i2s_intf.mode = I2S_I2S_MODE;
break;
case SND_SOC_DAIFMT_DSP_A:
priv_data->amb_i2s_intf.mode = I2S_DSP_MODE;
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
priv_data->dai_master = true;
break;
case SND_SOC_DAIFMT_CBM_CFM:
if (priv_data->amb_i2s_intf.mode != I2S_I2S_MODE) {
printk("DAI can't work in slave mode without standard I2S format!\n");
return -EINVAL;
}
priv_data->dai_master = false;
break;
default:
return -EINVAL;
}
return 0;
}
static int ambarella_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
struct amb_i2s_priv *priv_data = snd_soc_dai_get_drvdata(cpu_dai);
switch (clk_id) {
case AMBARELLA_CLKSRC_ONCHIP:
priv_data->amb_i2s_intf.clksrc = clk_id;
priv_data->amb_i2s_intf.mclk = freq;
break;
case AMBARELLA_CLKSRC_EXTERNAL:
default:
printk("CLK SOURCE (%d) is not supported yet\n", clk_id);
return -EINVAL;
}
return 0;
}
static int ambarella_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
int div_id, int div)
{
struct amb_i2s_priv *priv_data = snd_soc_dai_get_drvdata(cpu_dai);
switch (div_id) {
case AMBARELLA_CLKDIV_LRCLK:
priv_data->amb_i2s_intf.div = div;
break;
default:
return -EINVAL;
}
return 0;
}
#ifdef CONFIG_PM
static int ambarella_i2s_dai_suspend(struct snd_soc_dai *dai)
{
struct amb_i2s_priv *priv_data = snd_soc_dai_get_drvdata(dai);
struct ambarella_i2s_interface *i2s_intf = &priv_data->amb_i2s_intf;
priv_data->clock_reg = amba_readl(I2S_CLOCK_REG);
i2s_intf->mode = amba_readl(I2S_MODE_REG);
i2s_intf->word_len = amba_readl(I2S_WLEN_REG);
i2s_intf->word_pos = amba_readl(I2S_WPOS_REG);
i2s_intf->slots = amba_readl(I2S_SLOT_REG);
i2s_intf->channels = amba_readl(I2S_REG(I2S_CHANNEL_SELECT_OFFSET));
i2s_intf->rx_ctrl = amba_readl(I2S_RX_CTRL_REG);
i2s_intf->tx_ctrl = amba_readl(I2S_TX_CTRL_REG);
i2s_intf->rx_fifo_len = amba_readl(I2S_RX_FIFO_GTH_REG);
i2s_intf->tx_fifo_len = amba_readl(I2S_TX_FIFO_LTH_REG);
i2s_intf->multi24 = amba_readl(I2S_24BITMUX_MODE_REG);
return 0;
}
static int ambarella_i2s_dai_resume(struct snd_soc_dai *dai)
{
struct amb_i2s_priv *priv_data = snd_soc_dai_get_drvdata(dai);
struct ambarella_i2s_interface *i2s_intf = &priv_data->amb_i2s_intf;
amba_writel(I2S_MODE_REG, i2s_intf->mode);
amba_writel(I2S_WLEN_REG, i2s_intf->word_len);
amba_writel(I2S_WPOS_REG, i2s_intf->word_pos);
amba_writel(I2S_SLOT_REG, i2s_intf->slots);
amba_writel(I2S_REG(I2S_CHANNEL_SELECT_OFFSET), i2s_intf->channels);
amba_writel(I2S_RX_CTRL_REG, i2s_intf->rx_ctrl);
amba_writel(I2S_TX_CTRL_REG, i2s_intf->tx_ctrl);
amba_writel(I2S_RX_FIFO_GTH_REG, i2s_intf->rx_fifo_len);
amba_writel(I2S_TX_FIFO_LTH_REG, i2s_intf->tx_fifo_len);
amba_writel(I2S_24BITMUX_MODE_REG,i2s_intf->multi24);
amba_writel(I2S_CLOCK_REG, priv_data->clock_reg);
return 0;
}
#else /* CONFIG_PM */
#define ambarella_i2s_dai_suspend NULL
#define ambarella_i2s_dai_resume NULL
#endif /* CONFIG_PM */
static int ambarella_i2s_dai_probe(struct snd_soc_dai *dai)
{
u32 sfreq, clk_div = 3;
struct amb_i2s_priv *priv_data = snd_soc_dai_get_drvdata(dai);
struct ambarella_i2s_interface *i2s_intf = &priv_data->amb_i2s_intf;
dai->capture_dma_data = &priv_data->capture_dma_data;
dai->playback_dma_data = &priv_data->playback_dma_data;
if (priv_data->default_mclk == 12288000) {
sfreq = 48000;
} else if (priv_data->default_mclk == 11289600){
sfreq = 44100;
} else {
priv_data->default_mclk = 12288000;
sfreq = 48000;
}
clk_set_rate(priv_data->mclk, priv_data->default_mclk);
/*
* Dai default smapling rate, polarity configuration.
* Note: Just be configured, actually BCLK and LRCLK will not
* output to outside at this time.
*/
if(capture_enabled)
amba_writel(I2S_CLOCK_REG, clk_div | I2S_CLK_TX_PO_FALL);
priv_data->dai_master = true;
i2s_intf->mode = I2S_I2S_MODE;
i2s_intf->clksrc = AMBARELLA_CLKSRC_ONCHIP;
i2s_intf->mclk = priv_data->default_mclk;
i2s_intf->div = clk_div;
i2s_intf->sfreq = sfreq;
i2s_intf->word_len = 15;
i2s_intf->word_pos = 0;
i2s_intf->slots = 0;
i2s_intf->channels = 2;
/* reset fifo */
dai_tx_enable();
dai_rx_enable();
dai_fifo_rst();
/* Notify HDMI that the audio interface is initialized */
ambarella_audio_notify_transition(&priv_data->amb_i2s_intf, AUDIO_NOTIFY_INIT);
return 0;
}
static int ambarella_i2s_dai_remove(struct snd_soc_dai *dai)
{
struct amb_i2s_priv *priv_data = snd_soc_dai_get_drvdata(dai);
/* Disable I2S clock output */
amba_writel(I2S_CLOCK_REG, 0x0);
/* Notify that the audio interface is removed */
ambarella_audio_notify_transition(&priv_data->amb_i2s_intf,
AUDIO_NOTIFY_REMOVE);
return 0;
}
static const struct snd_soc_dai_ops ambarella_i2s_dai_ops = {
.prepare = ambarella_i2s_prepare,
.startup = ambarella_i2s_startup,
.hw_params = ambarella_i2s_hw_params,
.trigger = ambarella_i2s_trigger,
.set_fmt = ambarella_i2s_set_fmt,
.set_sysclk = ambarella_i2s_set_sysclk,
.set_clkdiv = ambarella_i2s_set_clkdiv,
};
static struct snd_soc_dai_driver ambarella_i2s_dai = {
.probe = ambarella_i2s_dai_probe,
.remove = ambarella_i2s_dai_remove,
.suspend = ambarella_i2s_dai_suspend,
.resume = ambarella_i2s_dai_resume,
.playback = {
.channels_min = 2,
.channels_max = 0, // initialized in ambarella_i2s_probe function
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE),
},
.capture = {
.channels_min = 2,
.channels_max = 0, // initialized in ambarella_i2s_probe function
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE),
},
.ops = &ambarella_i2s_dai_ops,
.symmetric_rates = 1,
};
static const struct snd_soc_component_driver ambarella_i2s_component = {
.name = "ambarella-i2s",
};
static int ambarella_i2s_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct amb_i2s_priv *priv_data;
struct pinctrl *pinctrl;
int channels, rval;
priv_data = devm_kzalloc(&pdev->dev, sizeof(*priv_data), GFP_KERNEL);
if (priv_data == NULL)
return -ENOMEM;
priv_data->playback_dma_data.addr = I2S_TX_LEFT_DATA_DMA_REG;
priv_data->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
priv_data->playback_dma_data.maxburst = 32;
priv_data->capture_dma_data.addr = I2S_RX_DATA_DMA_REG;
priv_data->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
priv_data->capture_dma_data.maxburst = 32;
priv_data->mclk = clk_get(&pdev->dev, "gclk_audio");
if (IS_ERR(priv_data->mclk)) {
dev_err(&pdev->dev, "Get audio clk failed!\n");
return PTR_ERR(priv_data->mclk);
}
dev_set_drvdata(&pdev->dev, priv_data);
rval = of_property_read_u32(np, "amb,i2s-channels", &channels);
if (rval < 0) {
dev_err(&pdev->dev, "Get channels failed! %d\n", rval);
return -ENXIO;
}
of_property_read_u32(np, "amb,default-mclk", &priv_data->default_mclk);
ambarella_i2s_dai.playback.channels_max = channels;
ambarella_i2s_dai.capture.channels_max = channels;
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl))
return PTR_ERR(pinctrl);
rval = snd_soc_register_component(&pdev->dev,
&ambarella_i2s_component, &ambarella_i2s_dai, 1);
if (rval < 0){
dev_err(&pdev->dev, "register DAI failed\n");
return rval;
}
rval = ambarella_pcm_platform_register(&pdev->dev);
if (rval) {
dev_err(&pdev->dev, "register PCM failed: %d\n", rval);
snd_soc_unregister_component(&pdev->dev);
return rval;
}
return 0;
}
static int ambarella_i2s_remove(struct platform_device *pdev)
{
ambarella_pcm_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
return 0;
}
static const struct of_device_id ambarella_i2s_dt_ids[] = {
{ .compatible = "ambarella,i2s", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ambarella_i2s_dt_ids);
static struct platform_driver ambarella_i2s_driver = {
.probe = ambarella_i2s_probe,
.remove = ambarella_i2s_remove,
.driver = {
.name = "ambarella-i2s",
.owner = THIS_MODULE,
.of_match_table = ambarella_i2s_dt_ids,
},
};
module_platform_driver(ambarella_i2s_driver);
MODULE_AUTHOR("Cao Rongrong <rrcao@ambarella.com>");
MODULE_DESCRIPTION("Ambarella Soc I2S Interface");
MODULE_LICENSE("GPL");