From: Jaiganesh Narayanan njaigane@codeaurora.org
This patch adds the alsa based audio driver for IPQ4019 ASoC.
Signed-off-by: Jaiganesh Narayanan njaigane@codeaurora.org --- sound/soc/qcom/Kconfig | 47 ++ sound/soc/qcom/Makefile | 1 + sound/soc/qcom/ipq4019/Makefile | 16 + sound/soc/qcom/ipq4019/ipq4019-adss.c | 407 ++++++++++++++ sound/soc/qcom/ipq4019/ipq4019-adss.h | 432 +++++++++++++++ sound/soc/qcom/ipq4019/ipq4019-codec.c | 475 +++++++++++++++++ sound/soc/qcom/ipq4019/ipq4019-codec.h | 91 ++++ sound/soc/qcom/ipq4019/ipq4019-cpu-dai.c | 687 ++++++++++++++++++++++++ sound/soc/qcom/ipq4019/ipq4019-mbox.c | 825 +++++++++++++++++++++++++++++ sound/soc/qcom/ipq4019/ipq4019-mbox.h | 146 +++++ sound/soc/qcom/ipq4019/ipq4019-pcm-i2s.c | 609 +++++++++++++++++++++ sound/soc/qcom/ipq4019/ipq4019-pcm-spdif.c | 664 +++++++++++++++++++++++ sound/soc/qcom/ipq4019/ipq4019-pcm-tdm.c | 609 +++++++++++++++++++++ sound/soc/qcom/ipq4019/ipq4019-pcm.h | 37 ++ sound/soc/qcom/ipq4019/ipq4019-stereo.c | 313 +++++++++++ sound/soc/qcom/ipq4019/ipq4019.c | 121 +++++ 16 files changed, 5480 insertions(+) create mode 100644 sound/soc/qcom/ipq4019/Makefile create mode 100644 sound/soc/qcom/ipq4019/ipq4019-adss.c create mode 100644 sound/soc/qcom/ipq4019/ipq4019-adss.h create mode 100644 sound/soc/qcom/ipq4019/ipq4019-codec.c create mode 100644 sound/soc/qcom/ipq4019/ipq4019-codec.h create mode 100644 sound/soc/qcom/ipq4019/ipq4019-cpu-dai.c create mode 100644 sound/soc/qcom/ipq4019/ipq4019-mbox.c create mode 100644 sound/soc/qcom/ipq4019/ipq4019-mbox.h create mode 100644 sound/soc/qcom/ipq4019/ipq4019-pcm-i2s.c create mode 100644 sound/soc/qcom/ipq4019/ipq4019-pcm-spdif.c create mode 100644 sound/soc/qcom/ipq4019/ipq4019-pcm-tdm.c create mode 100644 sound/soc/qcom/ipq4019/ipq4019-pcm.h create mode 100644 sound/soc/qcom/ipq4019/ipq4019-stereo.c create mode 100644 sound/soc/qcom/ipq4019/ipq4019.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 8ec9a07..de1f5b1 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -43,3 +43,50 @@ config SND_SOC_APQ8016_SBC Support for Qualcomm Technologies LPASS audio block in APQ8016 SOC-based systems. Say Y if you want to use audio devices on MI2S. + +config SND_SOC_IPQ4019 + tristate "Soc Audio support for IPQ4019 platforms" + depends on SND_SOC_QCOM + select SND_SOC_IPQ4019_ADSS + select SND_SOC_IPQ4019_CPU_DAI + select SND_SOC_IPQ4019_STEREO + select SND_SOC_IPQ4019_MBOX + select SND_SOC_IPQ4019_PCM_I2S + select SND_SOC_IPQ4019_CODEC + select SND_SOC_IPQ4019_PCM_TDM + select SND_SOC_IPQ4019_PCM_SPDIF + help + Say Y or M to if you want to add support for SoC audio on + Qualcomm Atheros IPQ4019 based board. + +config SND_SOC_IPQ4019_ADSS + tristate + depends on SND_SOC_IPQ4019 + +config SND_SOC_IPQ4019_CPU_DAI + tristate + depends on SND_SOC_IPQ4019 + +config SND_SOC_IPQ4019_STEREO + tristate + depends on SND_SOC_IPQ4019 + +config SND_SOC_IPQ4019_MBOX + tristate + depends on SND_SOC_IPQ4019 + +config SND_SOC_IPQ4019_PCM_I2S + tristate + depends on SND_SOC_IPQ4019 + +config SND_SOC_IPQ4019_CODEC + tristate + depends on SND_SOC_IPQ4019 + +config SND_SOC_IPQ4019_PCM_TDM + tristate + depends on SND_SOC_IPQ4019 + +config SND_SOC_IPQ4019_PCM_SPDIF + tristate + depends on SND_SOC_IPQ4019 diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index 79e5c50..f326b6a 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -15,3 +15,4 @@ snd-soc-apq8016-sbc-objs := apq8016_sbc.o
obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o +obj-$(CONFIG_SND_SOC_IPQ4019) += ipq4019/ diff --git a/sound/soc/qcom/ipq4019/Makefile b/sound/soc/qcom/ipq4019/Makefile new file mode 100644 index 0000000..ee46f51 --- /dev/null +++ b/sound/soc/qcom/ipq4019/Makefile @@ -0,0 +1,16 @@ +# QCA IPQ4019 Sound Card Support +snd-soc-ipq4019-objs := ipq4019.o +snd-soc-ipq4019-cpu-dai-objs := ipq4019-cpu-dai.o +snd-soc-ipq4019-pcm-i2s-objs := ipq4019-pcm-i2s.o +snd-soc-ipq4019-pcm-tdm-objs := ipq4019-pcm-tdm.o +snd-soc-ipq4019-pcm-spdif-objs := ipq4019-pcm-spdif.o + +obj-$(CONFIG_SND_SOC_IPQ4019) += ipq4019.o +obj-$(CONFIG_SND_SOC_IPQ4019_ADSS) += ipq4019-adss.o +obj-$(CONFIG_SND_SOC_IPQ4019_CPU_DAI) += snd-soc-ipq4019-cpu-dai.o +obj-$(CONFIG_SND_SOC_IPQ4019_STEREO) += ipq4019-stereo.o +obj-$(CONFIG_SND_SOC_IPQ4019_MBOX) += ipq4019-mbox.o +obj-$(CONFIG_SND_SOC_IPQ4019_PCM_I2S) += snd-soc-ipq4019-pcm-i2s.o +obj-$(CONFIG_SND_SOC_IPQ4019_CODEC) += ipq4019-codec.o +obj-$(CONFIG_SND_SOC_IPQ4019_PCM_SPDIF) += snd-soc-ipq4019-pcm-spdif.o +obj-$(CONFIG_SND_SOC_IPQ4019_PCM_TDM) += snd-soc-ipq4019-pcm-tdm.o diff --git a/sound/soc/qcom/ipq4019/ipq4019-adss.c b/sound/soc/qcom/ipq4019/ipq4019-adss.c new file mode 100644 index 0000000..a3d21a193 --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-adss.c @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/bitops.h> +#include <sound/pcm.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/reset.h> +#include <linux/spinlock.h> + +#include "ipq4019-adss.h" + +static void __iomem *adss_audio_local_base; +void __iomem *adss_audio_spdifin_base; +static struct reset_control *audio_blk_rst; +static spinlock_t i2s_ctrl_lock; +static spinlock_t tdm_ctrl_lock; +static spinlock_t glb_mode_lock; + +/* Channel Number Per Frame for Transmitter/Receiver + * Real value = val + 1 + */ +void ipq4019_glb_tdm_ctrl_ch_num(uint32_t val, uint32_t dir) +{ + uint32_t cfg; + unsigned long flags; + + spin_lock_irqsave(&tdm_ctrl_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); + + if (dir == PLAYBACK) { + cfg &= ~(GLB_TDM_CTRL_TX_CHAN_NUM_MASK); + cfg |= GLB_TDM_CTRL_TX_CHAN_NUM(val); + } else if (dir == CAPTURE) { + cfg &= ~(GLB_TDM_CTRL_RX_CHAN_NUM_MASK); + cfg |= GLB_TDM_CTRL_RX_CHAN_NUM(val); + } + writel(cfg, adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); + spin_unlock_irqrestore(&tdm_ctrl_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_tdm_ctrl_ch_num); + +/* FSYNC Hi Duration for Transmitter/Receiver */ +void ipq4019_glb_tdm_ctrl_sync_num(uint32_t val, uint32_t dir) +{ + uint32_t cfg; + unsigned long flags; + + spin_lock_irqsave(&tdm_ctrl_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); + + if (dir == PLAYBACK) { + cfg &= ~(GLB_TDM_CTRL_TX_SYNC_NUM_MASK); + cfg |= GLB_TDM_CTRL_TX_SYNC_NUM(val); + } else if (dir == CAPTURE) { + cfg &= ~(GLB_TDM_CTRL_RX_SYNC_NUM_MASK); + cfg |= GLB_TDM_CTRL_RX_SYNC_NUM(val); + } + writel(cfg, adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); + spin_unlock_irqrestore(&tdm_ctrl_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_tdm_ctrl_sync_num); + +/* Serial Data Delay for transmitter/receiver */ +void ipq4019_glb_tdm_ctrl_delay(uint32_t delay, uint32_t dir) +{ + uint32_t cfg; + unsigned long flags; + + spin_lock_irqsave(&tdm_ctrl_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); + + if (dir == PLAYBACK) { + cfg &= ~(GLB_TDM_CTRL_TX_DELAY); + if (delay) + cfg |= GLB_TDM_CTRL_TX_DELAY; + } else if (dir == CAPTURE) { + cfg &= ~(GLB_TDM_CTRL_RX_DELAY); + if (delay) + cfg |= GLB_TDM_CTRL_RX_DELAY; + } + writel(cfg, adss_audio_local_base + ADSS_GLB_TDM_CTRL_REG); + spin_unlock_irqrestore(&tdm_ctrl_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_tdm_ctrl_delay); + +/* I2S Interface Enable */ +static void ipq4019_glb_i2s_interface_en(int enable) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&i2s_ctrl_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_CHIP_CTRL_I2S_REG); + cfg &= ~GLB_CHIP_CTRL_I2S_INTERFACE_EN; + if (enable) + cfg |= GLB_CHIP_CTRL_I2S_INTERFACE_EN; + writel(cfg, adss_audio_local_base + ADSS_GLB_CHIP_CTRL_I2S_REG); + spin_unlock_irqrestore(&i2s_ctrl_lock, flags); + /* + * As per the audio controller susbsytem after writing to + * the register wait 5ms for the i2s settle down. + */ + mdelay(5); +} +EXPORT_SYMBOL(ipq4019_glb_i2s_interface_en); + +/* Enable Stereo0/Stereo1/Stereo2 channel */ +void ipq4019_glb_stereo_ch_en(int enable, int stereo_ch) +{ + uint32_t cfg; + unsigned long flags; + + spin_lock_irqsave(&i2s_ctrl_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_CHIP_CTRL_I2S_REG); + if (stereo_ch == STEREO0) { + cfg &= ~(GLB_CHIP_CTRL_I2S_STEREO0_GLB_EN); + cfg |= GLB_CHIP_CTRL_I2S_STEREO0_GLB_EN; + } else if (stereo_ch == STEREO1) { + cfg &= ~(GLB_CHIP_CTRL_I2S_STEREO1_GLB_EN); + cfg |= GLB_CHIP_CTRL_I2S_STEREO1_GLB_EN; + } else if (stereo_ch == STEREO2) { + cfg &= ~(GLB_CHIP_CTRL_I2S_STEREO2_GLB_EN); + cfg |= GLB_CHIP_CTRL_I2S_STEREO2_GLB_EN; + } + writel(cfg, adss_audio_local_base + ADSS_GLB_CHIP_CTRL_I2S_REG); + spin_unlock_irqrestore(&i2s_ctrl_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_stereo_ch_en); + +/* + * I2S Module Reset + */ +static void ipq4019_glb_i2s_reset(void) +{ + writel(GLB_I2S_RESET_VAL, adss_audio_local_base + ADSS_GLB_I2S_RST_REG); + mdelay(5); + writel(0x0, adss_audio_local_base + ADSS_GLB_I2S_RST_REG); +} + +/* + * Enable I2S/TDM and Playback/Capture Audio Mode + */ +void ipq4019_glb_audio_mode(int mode, int dir) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&glb_mode_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + if (mode == I2S && dir == PLAYBACK) { + cfg &= ~GLB_AUDIO_MODE_XMIT_MASK; + cfg |= GLB_AUDIO_MODE_XMIT_I2S; + } else if (mode == I2S && dir == CAPTURE) { + cfg &= ~GLB_AUDIO_MODE_RECV_MASK; + cfg |= GLB_AUDIO_MODE_RECV_I2S; + } else if (mode == TDM && dir == PLAYBACK) { + cfg &= ~GLB_AUDIO_MODE_XMIT_MASK; + cfg |= GLB_AUDIO_MODE_XMIT_TDM; + } else if (mode == TDM && dir == CAPTURE) { + cfg &= ~GLB_AUDIO_MODE_RECV_MASK; + cfg |= GLB_AUDIO_MODE_RECV_TDM; + } + writel(cfg, adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + spin_unlock_irqrestore(&glb_mode_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_audio_mode); + +/* + * I2S0 TX Data Port Enable + * + * Todo : + * Check if bits 6:4 configures only + * I2S0 or other channels as well + */ +void ipq4019_glb_tx_data_port_en(u32 enable) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&glb_mode_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + cfg &= ~GLB_AUDIO_MODE_I2S0_TXD_OE; + if (enable) + cfg |= GLB_AUDIO_MODE_I2S0_TXD_OE; + writel(cfg, adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + spin_unlock_irqrestore(&glb_mode_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_tx_data_port_en); + +/* + * I2S3 RX Data Port Enable + */ +void ipq4019_glb_rx_data_port_en(u32 enable) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&glb_mode_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + cfg &= ~GLB_AUDIO_MODE_I2S3_RXD_OE; + if (enable) + cfg |= GLB_AUDIO_MODE_I2S3_RXD_OE; + writel(cfg, adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + spin_unlock_irqrestore(&glb_mode_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_rx_data_port_en); + +/* + * Cross 1K Boundary + */ +void ipq4019_glb_audio_mode_B1K(void) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&glb_mode_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + cfg &= ~GLB_AUDIO_MODE_B1K; + cfg |= GLB_AUDIO_MODE_B1K; + writel(cfg, adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + spin_unlock_irqrestore(&glb_mode_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_audio_mode_B1K); + +/* + * Frame Sync Port Enable for I2S0 TX + */ +void ipq4019_glb_tx_framesync_port_en(u32 enable) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&glb_mode_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + cfg &= ~GLB_AUDIO_MODE_I2S0_FS_OE; + if (enable) + cfg |= GLB_AUDIO_MODE_I2S0_FS_OE; + writel(cfg, adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + spin_unlock_irqrestore(&glb_mode_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_tx_framesync_port_en); + +/* + * Frame Sync Port Enable for I2S3 RX + */ +void ipq4019_glb_rx_framesync_port_en(u32 enable) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&glb_mode_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + cfg &= ~GLB_AUDIO_MODE_I2S3_FS_OE; + if (enable) + cfg |= GLB_AUDIO_MODE_I2S3_FS_OE; + writel(cfg, adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + spin_unlock_irqrestore(&glb_mode_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_rx_framesync_port_en); + +void ipq4019_glb_clk_enable_oe(u32 dir) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&i2s_ctrl_lock, flags); + cfg = readl(adss_audio_local_base + ADSS_GLB_CLK_I2S_CTRL_REG); + + if (dir == PLAYBACK) { + cfg |= (GLB_CLK_I2S_CTRL_TX_BCLK_OE | + GLB_CLK_I2S_CTRL_TX_MCLK_OE); + } else { + cfg |= (GLB_CLK_I2S_CTRL_RX_BCLK_OE | + GLB_CLK_I2S_CTRL_RX_MCLK_OE); + } + writel(cfg, adss_audio_local_base + ADSS_GLB_CLK_I2S_CTRL_REG); + spin_unlock_irqrestore(&i2s_ctrl_lock, flags); +} +EXPORT_SYMBOL(ipq4019_glb_clk_enable_oe); + +void ipq4019_spdifin_ctrl_spdif_en(uint32_t enable) +{ + uint32_t reg_val; + + reg_val = readl(adss_audio_spdifin_base + ADSS_SPDIFIN_SPDIF_CTRL_REG); + + if (enable) + reg_val |= SPDIF_CTRL_SPDIF_ENABLE; + else + reg_val &= ~SPDIF_CTRL_SPDIF_ENABLE; + + writel(reg_val, adss_audio_spdifin_base + ADSS_SPDIFIN_SPDIF_CTRL_REG); + +} +EXPORT_SYMBOL(ipq4019_spdifin_ctrl_spdif_en); + +void ipq4019_spdifin_cfg(void) +{ + uint32_t reg_val; + + reg_val = readl(adss_audio_spdifin_base + ADSS_SPDIFIN_SPDIF_CTRL_REG); + reg_val &= ~(SPDIF_CTRL_CHANNEL_MODE + | SPDIF_CTRL_VALIDITYCHECK + | SPDIF_CTRL_PARITYCHECK); + reg_val |= (SPDIF_CTRL_USE_FIFO_IF + | SPDIF_CTRL_SFR_ENABLE + | SPDIF_CTRL_FIFO_ENABLE); + writel(reg_val, adss_audio_spdifin_base + ADSS_SPDIFIN_SPDIF_CTRL_REG); +} +EXPORT_SYMBOL(ipq4019_spdifin_cfg); + +void ipq4019_glb_spdif_out_en(uint32_t enable) +{ + int32_t cfg; + + cfg = readl(adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); + cfg &= ~(GLB_AUDIO_MODE_SPDIF_OUT_OE); + if (enable) + cfg |= GLB_AUDIO_MODE_SPDIF_OUT_OE; + writel(cfg, adss_audio_local_base + ADSS_GLB_AUDIO_MODE_REG); +} +EXPORT_SYMBOL(ipq4019_glb_spdif_out_en); + +static const struct of_device_id ipq4019_audio_adss_id_table[] = { + { .compatible = "qca,ipq4019-audio-adss" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ipq4019_audio_adss_id_table); + +static int ipq4019_audio_adss_probe(struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + adss_audio_local_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(adss_audio_local_base)) + return PTR_ERR(adss_audio_local_base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + adss_audio_spdifin_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(adss_audio_spdifin_base)) + return PTR_ERR(adss_audio_spdifin_base); + + audio_blk_rst = devm_reset_control_get(&pdev->dev, "blk_rst"); + if (IS_ERR(audio_blk_rst)) + return PTR_ERR(audio_blk_rst); + + spin_lock_init(&i2s_ctrl_lock); + spin_lock_init(&glb_mode_lock); + spin_lock_init(&tdm_ctrl_lock); + + /* + * Reset order is critical here. + * First audio block should be out of reset, + * followed by I2S block. + * Since the audio block is brought out of + * reset by hardware by default, it is not + * required to be done in software explicitly. + */ + ipq4019_glb_i2s_reset(); + + ipq4019_glb_i2s_interface_en(ENABLE); + + ipq4019_glb_audio_mode_B1K(); + + return 0; +} + +static int ipq4019_audio_adss_remove(struct platform_device *pdev) +{ + ipq4019_glb_i2s_interface_en(DISABLE); + return 0; +} + +static struct platform_driver ipq4019_audio_adss_driver = { + .probe = ipq4019_audio_adss_probe, + .remove = ipq4019_audio_adss_remove, + .driver = { + .name = "ipq4019-adss", + .of_match_table = ipq4019_audio_adss_id_table, + }, +}; + +module_platform_driver(ipq4019_audio_adss_driver); + +MODULE_ALIAS("platform:ipq4019-adss"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("IPQ4019 Audio subsytem driver"); diff --git a/sound/soc/qcom/ipq4019/ipq4019-adss.h b/sound/soc/qcom/ipq4019/ipq4019-adss.h new file mode 100644 index 0000000..87f3e0f --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-adss.h @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef IPQ4019_ADSS_H +#define IPQ4019_ADSS_H + +/* ADSS AUDIO Registers */ + +#define ADSS_BASE 0x7700000 +#define ADSS_RANGE 0x20000 + +/* ADSS_AUDIO_LOCAL_REG Registers */ + +#define ADSS_GLB_PCM_MBOX_CTRL_REG 0x0C + +#define ADSS_GLB_CHIP_CTRL_I2S_REG 0x10 +#define GLB_CHIP_CTRL_I2S_INTERFACE_EN BIT(0) +#define GLB_CHIP_CTRL_I2S_STEREO0_GLB_EN BIT(1) +#define GLB_CHIP_CTRL_I2S_STEREO1_GLB_EN BIT(2) +#define GLB_CHIP_CTRL_I2S_STEREO2_GLB_EN BIT(3) + +#define ADSS_GLB_I2S_RST_REG 0x14 +#define GLB_I2S_RST_CTRL_MBOX0 BIT(0) +#define GLB_I2S_RST_CTRL_I2S0 BIT(1) +#define GLB_I2S_RST_CTRL_MBOX3 BIT(2) +#define GLB_I2S_RESET_VAL 0xF + +#define ADSS_GLB_CLK_I2S_CTRL_REG 0x18 +#define GLB_CLK_I2S_CTRL_TX_BCLK_OE BIT(28) +#define GLB_CLK_I2S_CTRL_RX_BCLK_OE BIT(27) +#define GLB_CLK_I2S_CTRL_RX_MCLK_OE BIT(16) +#define GLB_CLK_I2S_CTRL_TX_MCLK_OE BIT(17) + +#define ADSS_GLB_TDM_CTRL_REG 0x1C +#define GLB_TDM_CTRL_TX_CHAN_NUM(x) (x << 0) +#define GLB_TDM_CTRL_TX_CHAN_NUM_MASK 0xF +#define GLB_TDM_CTRL_TX_SYNC_NUM(x) (x << 4) +#define GLB_TDM_CTRL_TX_SYNC_NUM_MASK (0x1F << 4) +#define GLB_TDM_CTRL_RX_CHAN_NUM(x) (x << 16) +#define GLB_TDM_CTRL_RX_CHAN_NUM_MASK (0xF << 16) +#define GLB_TDM_CTRL_RX_SYNC_NUM(x) (x << 20) +#define GLB_TDM_CTRL_RX_SYNC_NUM_MASK (0x1F << 20) +#define GLB_TDM_CTRL_TX_DELAY BIT(25) +#define GLB_TDM_CTRL_RX_DELAY BIT(26) + +#define ADSS_GLB_AUDIO_MODE_REG 0x30 +#define GLB_AUDIO_MODE_RECV_MASK BIT(2) +#define GLB_AUDIO_MODE_XMIT_MASK BIT(0) +#define GLB_AUDIO_MODE_RECV_I2S (0 << 2) +#define GLB_AUDIO_MODE_RECV_TDM BIT(2) +#define GLB_AUDIO_MODE_XMIT_I2S (0 << 0) +#define GLB_AUDIO_MODE_XMIT_TDM BIT(0) +#define GLB_AUDIO_MODE_I2S0_TXD_OE (7 << 4) +#define GLB_AUDIO_MODE_I2S0_FS_OE BIT(7) +#define GLB_AUDIO_MODE_I2S3_FS_OE BIT(8) +#define GLB_AUDIO_MODE_I2S3_RXD_OE BIT(9) +#define GLB_AUDIO_MODE_SPDIF_OUT_OE BIT(10) +#define GLB_AUDIO_MODE_B1K BIT(28) + +#define ADSS_MBOX_STEREO_AUDIO_BASE (ADSS_BASE + 0x8000) + +/* ADSS_MBOX_STEREO_AUDIO_BASE + 0x0 */ +#define ADSS_MBOX0_AUDIO_BASE 0x0 +#define ADSS_MBOX1_AUDIO_BASE 0x2000 +#define ADSS_MBOX2_AUDIO_BASE 0x4000 +#define ADSS_MBOX3_AUDIO_BASE 0x6000 + +#define ADSS_MBOXn_MBOX_FIFO0_REG 0x0 +#define MBOX_FIFO_RESET_TX_INIT BIT(0) +#define MBOX_FIFO_RESET_RX_INIT BIT(2) + +#define ADSS_MBOXn_MBOX_FIFO_STATUS0_REG 0x08 + +#define ADSS_MBOXn_MBOX_DMA_POLICY_REG 0x10 +#define MBOX_DMA_POLICY_SW_RESET BIT(31) +#define MBOX_DMA_POLICY_TX_INT_TYPE BIT(17) +#define MBOX_DMA_POLICY_RX_INT_TYPE BIT(16) +#define MBOX_DMA_POLICY_RXD_16BIT_SWAP BIT(10) +#define MBOX_DMA_POLICY_RXD_END_SWAP BIT(8) +#define ADSS_MBOX_DMA_POLICY_SRAM_AC(x) ((((x) >> 28) & 0xf) << 12) +#define ADSS_MBOX_DMA_POLICY_TX_FIFO_THRESHOLD(x) ((((x) & 0xf) << 4)) + +#define ADSS_MBOXn_MBOXn_DMA_RX_DESCRIPTOR_BASE_REG 0x18 + +#define ADSS_MBOXn_MBOXn_DMA_RX_CONTROL_REG 0x1C +#define ADSS_MBOXn_DMA_RX_CONTROL_STOP BIT(0) +#define ADSS_MBOXn_DMA_RX_CONTROL_START BIT(1) +#define ADSS_MBOXn_DMA_RX_CONTROL_RESUME BIT(2) + +#define ADSS_MBOXn_MBOXn_DMA_TX_DESCRIPTOR_BASE_REG 0x20 + +#define ADSS_MBOXn_MBOXn_DMA_TX_CONTROL_REG 0x24 +#define ADSS_MBOXn_DMA_TX_CONTROL_STOP BIT(0) +#define ADSS_MBOXn_DMA_TX_CONTROL_START BIT(1) +#define ADSS_MBOXn_DMA_TX_CONTROL_RESUME BIT(2) + +#define ADSS_MBOXn_MBOX_FRAME_REG 0x38 +#define ADSS_MBOXn_FIFO_TIMEOUT_REG 0x40 + +#define ADSS_MBOXn_MBOX_INT_STATUS_REG 0x44 +#define MBOX_INT_STATUS_TX_DMA_COMPLETE BIT(6) +#define MBOX_INT_STATUS_RX_DMA_COMPLETE BIT(10) + +#define ADSS_MBOXn_MBOX_INT_ENABLE_REG 0x4C +#define MBOX_INT_ENABLE_RX_DMA_COMPLETE BIT(10) +#define MBOX_INT_STATUS_RX_UNDERFLOW BIT(4) +#define MBOX_INT_STATUS_RX_FIFO_UNDERFLOW BIT(12) +#define MBOX_INT_ENABLE_TX_DMA_COMPLETE BIT(6) +#define MBOX_INT_STATUS_TX_OVERFLOW BIT(5) +#define MBOX_INT_STATUS_TX_FIFO_OVERFLOW BIT(13) + +#define ADSS_MBOXn_MBOX_FIFO_RESET_REG 0x58 +#define MBOX_FIFO_RESET_TX_INIT BIT(0) +#define MBOX_FIFO_RESET_RX_INIT BIT(2) + +#define ADSS_MBOXn_MBOX_DEBUG_CHAIN0_REG 0x60 +#define ADSS_MBOXn_MBOX_DEBUG_CHAIN1_REG 0x64 +#define ADSS_MBOXn_MBOX_DEBUG_CHAIN0_SIGNALS_REG 0x68 +#define ADSS_MBOXn_MBOX_DEBUG_CHAIN1_SIGNALS_REG 0x6C + +/* ADSS_STEREO0_AUDIO_STEREO_REG Registers */ + +#define ADSS_STEREO0_AUDIO_BASE 0x9000 +#define ADSS_STEREO1_AUDIO_BASE 0xB000 +#define ADSS_STEREO2_AUDIO_BASE 0xD000 +#define ADSS_STEREO3_AUDIO_BASE 0xF000 + +#define STEREO0_OFFSET 0x0 +#define STEREO1_OFFSET 0x2000 +#define STEREO2_OFFSET 0x4000 +#define STEREO3_OFFSET 0x6000 + +#define ADSS_STEREOn_STEREO0_CONFIG_REG 0x0 +#define STEREOn_CONFIG_MIC_SWAP BIT(24) +#define STEREOn_CONFIG_SPDIF_ENABLE BIT(23) +#define STEREOn_CONFIG_ENABLE BIT(21) +#define STEREOn_CONFIG_MIC_RESET BIT(20) +#define STEREOn_CONFIG_RESET BIT(19) +#define STEREOn_CONFIG_I2S_DELAY (0 << 18) +#define STEREOn_CONFIG_PCM_SWAP BIT(17) +#define STEREOn_CONFIG_MIC_WORD_SIZE_32 BIT(16) +#define STEREOn_CONFIG_MIC_WORD_SIZE_16 (0 << 16) +#define STEREOn_CONFIG_STEREO_MODE (0 << 14) +#define STEREOn_CONFIG_MONO_MODE BIT(14) +#define STEREOn_CONFIG_STEREO_MONO_MASK (3 << 14) +#define STEREOn_CONFIG_DATA_WORD_SIZE(x) ((x) << 12) +#define STEREOn_CONFIG_DATA_WORD_SIZE_MASK (3 << 12) +#define STEREOn_CONFIG_I2S_WORD_SIZE_32 BIT(11) +#define STEREOn_CONFIG_I2S_WORD_SIZE_16 (0 << 11) +#define STEREOn_CONFIG_MCK_SEL BIT(10) +#define STEREOn_CONFIG_SAMPLE_CNT_CLEAR_TYPE BIT(9) +#define STEREOn_CONFIG_MASTER BIT(8) + +#define MAX_STEREO_ENTRIES 4 +#define TDM_SYNC_NUM 2 +#define TDM_DELAY 0 +#define MCLK_MULTI 4 + +/* ADSS_AUDIO_PCM_REG Registers */ + +#define ADSS_AUDIO_PCM_REG_BASE ADSS_BASE + 0x4000 + +#define AADSS_PCM_BITMAP_REG 0x0 + +#define AADSS_PCM_CTRL_REG 0x04 +#define PCM_CTRL_TX2RX_LP_EN(x) (x << 31) +#define PCM_CTRL_RX2TX_LP_EN(x) (x << 30) +#define PCM_CTRL_CPU_MODE(x) (x << 29) +#define PCM_CTRL_PCM_GCLK_EN(x) (x << 28) +#define PCM_CTRL_FRAME_SYNC_LEN(x) (x << 26) +#define PCM_CTRL_PCM_CLK_MODE(x) (x << 25) +#define PCM_CTRL_PCM_SLOT_MODE(x) (x << 24) +#define PCM_CTRL_PCM_DCLK_MODE(x) (x << 4) +#define PCM_CTRL_PCM_TX_PHASE(x) (x << 2) +#define PCM_CTRL_PCM_RX_PHASE(x) (x << 0) + +#define AADSS_PCM_OFFSET_REG 0x08 +#define AADSS_PCM_START_REG 0x0C +#define AADSS_PCM_INT_STATUS_REG 0x10 +#define AADSS_PCM_INT_ENABLE_REG 0x14 +#define AADSS_PCM_RX_DATA_8BIT_REG 0x18 +#define AADSS_PCM_TX_DATA_8BIT_REG 0x1C +#define AADSS_PCM_DIVIDER_REG 0x20 +#define AADSS_PCM_TH_REG 0x24 +#define AADSS_PCM_FIFO_CNT_REG 0x28 +#define AADSS_PCM_FIFO_ERR_SLOT_REG 0x2C +#define AADSS_PCM_RX_DATA_16BIT_REG 0x30 +#define AADSS_PCM_TX_DATA_16BIT_REG 0x34 + +/* I2S Parameters */ +#define IPQ4019_I2S_NO_OF_PERIODS (130) +#define IPQ4019_I2S_PERIOD_BYTES_MIN ALIGN(4032, L1_CACHE_BYTES) +#define IPQ4019_I2S_BUFF_SIZE (IPQ4019_I2S_PERIOD_BYTES_MIN * \ + IPQ4019_I2S_NO_OF_PERIODS) +#define IPQ4019_I2S_CAPTURE_BUFF_SIZE (IPQ4019_I2S_PERIOD_BYTES_MIN * \ + IPQ4019_I2S_NO_OF_PERIODS) + +/* TDM Parameters */ +#define IPQ4019_TDM_NO_OF_PERIODS (260) +#define IPQ4019_TDM_PERIOD_BYTES_MIN ALIGN(4032, L1_CACHE_BYTES) +#define IPQ4019_TDM_BUFF_SIZE (IPQ4019_TDM_PERIOD_BYTES_MIN * \ + IPQ4019_TDM_NO_OF_PERIODS) +#define IPQ4019_TDM_CAPTURE_BUFF_SIZE (IPQ4019_TDM_PERIOD_BYTES_MIN * \ + IPQ4019_TDM_NO_OF_PERIODS) + +/* SPDIF area */ + + +/* ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG Registers */ + +#define ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE ADSS_BASE + 0x6000 + +#define AADSS_MBOXSPDIFIN_MBOX_FIFO0_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x0) + +#define AADSS_MBOXSPDIFIN_MBOX_FIFO_STATUS0_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x08) + +#define AADSS_MBOXSPDIFIN_MBOX_DMA_POLICY_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x10) + +#define AADSS_MBOXSPDIFIN_MBOX0_DMA_RX_DESCRIPTOR_BASE_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x18) + +#define AADSS_MBOXSPDIFIN_MBOX0_DMA_RX_CONTROL_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x1C) + +#define AADSS_MBOXSPDIFIN_MBOX0_DMA_TX_DESCRIPTOR_BASE_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x20) + +#define AADSS_MBOXSPDIFIN_MBOX0_DMA_TX_CONTROL_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x24) + +#define AADSS_MBOXSPDIFIN_MBOX_FRAME_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x38) + +#define AADSS_MBOXSPDIFIN_FIFO_TIMEOUT_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x40) + +#define AADSS_MBOXSPDIFIN_MBOX_INT_STATUS_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x44) + +#define AADSS_MBOXSPDIFIN_MBOX_INT_ENABLE_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x4C) + +#define AADSS_MBOXSPDIFIN_MBOX_FIFO_RESET_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x58) + +#define AADSS_MBOXSPDIFIN_MBOX_DEBUG_CHAIN0_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x60) + +#define AADSS_MBOXSPDIFIN_MBOX_DEBUG_CHAIN1_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x64) + +#define AADSS_MBOXSPDIFIN_MBOX_DEBUG_CHAIN0_SIGNALS_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x68) + +#define AADSS_MBOXSPDIFIN_MBOX_DEBUG_CHAIN1_SIGNALS_REG \ + ((ADSS_MBOXSPDIFIN_AUDIO_MBOX_REG_BASE) + 0x6C) + +/* ADSS_SPDIFIN_AUDIO_SPDIF_BASE Registers */ + +#define ADSS_SPDIFIN_SPDIF_CTRL_REG (0x00) +#define SPDIF_CTRL_INTREQ_MASK BIT(31) +#define SPDIF_CTRL_BEGIN_MASK BIT(30) +#define SPDIF_CTRL_LOCK_MASK BIT(29) +#define SPDIF_CTRL_SYNCERR_MASK BIT(28) +#define SPDIF_CTRL_AFULL_MASK BIT(27) +#define SPDIF_CTRL_FULL_MASK BIT(26) +#define SPDIF_CTRL_AEMPTY_MASK BIT(25) +#define SPDIF_CTRL_EMPTY_MASK BIT(24) +#define SPDIF_CTRL_OVRERR_MASK BIT(23) +#define SPDIF_CTRL_UNDERR_MASK BIT(22) +#define SPDIF_CTRL_PARITY_MASK BIT(21) +#define SPDIF_CTRL_USE_FIFO_IF BIT(19) +#define SPDIF_CTRL_SETPREAMBB BIT(18) +#define SPDIF_CTRL_DUPLICATE BIT(17) +#define SPDIF_CTRL_CHANNEL_MODE BIT(16) +#define SPDIF_CTRL_VALIDITYCHECK BIT(15) +#define SPDIF_CTRL_PARITYGEN BIT(14) +#define SPDIF_CTRL_PARITYCHECK BIT(13) +#define SPDIF_CTRL_TR_MODE BIT(12) +#define SPDIF_CTRL_CLK_ENABLE BIT(11) +#define SPDIF_CTRL_FIFO_ENABLE BIT(10) +#define SPDIF_CTRL_SPDIF_ENABLE BIT(9) +#define SPDIF_CTRL_SFR_ENABLE BIT(8) +#define SPDIF_CTRL_TSAMPLERATE BIT(7) + +#define ADSS_SPDIFIN_STEREO0_VOLUME (0x04) +#define ADSS_SPDIFIN_FIFO_CTRL_REG (0x08) +#define ADSS_SPDIFIN_START_REG_REG (0x0C) +#define ADSS_SPDIFIN_SELFIFO_REG (0x10) + + +#define ADSS_AUDIO_SPDIF_MISC_REG 0x150 +#define AUDIO_SPDIF_MISC_AUTO_SCALE_DIV_MASK (0xF << 1) +#define AUDIO_SPDIF_MISC_AUTO_SCALE_DIV(x) (x << 1) + +#define ADSS_AUDIO_SPDIF_CBCR_REG 0x154 + +#define ADSS_AUDIO_SPDIFDIV2_MISC_REG 0x158 +#define AUDIO_SPDIFDIV2_MISC_AUTO_SCALE_DIV_MASK (0xF << 1) +#define AUDIO_SPDIFDIV2_MISC_AUTO_SCALE_DIV(x) (x << 1) + +#define ADSS_AUDIO_SPDIFDIV2_CBCR_REG 0x15C +#define ADSS_AUDIO_SPDIFINFAST_CMD_RCGR_REG 0x1E0 +#define AUDIO_SPDIFINFAST_CMD_RCGR_ROOT_EN (1 << 1) +#define AUDIO_SPDIFINFAST_CMD_RCGR_UPDATE (1 << 0) + +#define ADSS_AUDIO_SPDIFINFAST_CFG_RCGR_REG 0x1E4 +#define AUDIO_SPDIFINFAST_CFG_RCGR_SRC_SEL(x) (x << 8) +#define AUDIO_SPDIFINFAST_CFG_RCGR_SRC_DIV(x) (x << 0) + +#define ADSS_AUDIO_SPDIFINFAST_CBCR_REG 0x1EC +#define AUDIO_SPDIFINFAST 49152000 + +#define SNDRV_PCM_FMTBIT_S24_3 SNDRV_PCM_FMTBIT_S24_3LE + +/* Enumerations */ + +enum intf { + I2S, + TDM, + SPDIF, + I2S1, + I2S2, + MAX_INTF +}; + +enum dir { + PLAYBACK, + CAPTURE +}; + +enum cfg { + DISABLE, + ENABLE +}; + +/* Supported Channels */ +enum channels { + CH_STEREO = 2, + CH_3_1 = 4, + CH_5_1 = 6, + CH_7_1 = 8 +}; + +enum ipq4019_samp_freq { + FREQ_8000 = 8000, + FREQ_11025 = 11025, + FREQ_16000 = 16000, + FREQ_22050 = 22050, + FREQ_32000 = 32000, + FREQ_44100 = 44100, + FREQ_48000 = 48000, + FREQ_64000 = 64000, + FREQ_88200 = 88200, + FREQ_96000 = 96000, + FREQ_176400 = 176400, + FREQ_192000 = 192000, +}; + +#define RATE_16000_96000 \ + (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +enum stereo_ch { + STEREO0, + STEREO1, + STEREO2, + STEREO3 +}; + +enum bit_width { + __BIT_8 = 8, + __BIT_16 = 16, + __BIT_24 = 24, + __BIT_32 = 32, + __BIT_INVAL = -1 +}; + +/* ADSS APIs */ +extern void ipq4019_glb_audio_mode(int mode, int dir); +extern void ipq4019_glb_tx_data_port_en(uint32_t enable); +extern void ipq4019_glb_rx_data_port_en(uint32_t enable); +extern void ipq4019_glb_tx_framesync_port_en(uint32_t enable); +extern void ipq4019_glb_rx_framesync_port_en(uint32_t enable); +extern void ipq4019_glb_clk_enable_oe(uint32_t dir); +/* Stereo APIs */ +extern void ipq4019_stereo_config_reset(uint32_t reset, uint32_t stereo_offset); +extern void ipq4019_stereo_config_mic_reset(uint32_t reset, + uint32_t stereo_offset); +extern void ipq4019_stereo_config_enable(uint32_t enable, + uint32_t stereo_offset); +extern int ipq4019_cfg_bit_width(uint32_t bit_width, uint32_t stereo_offset); +extern void ipq4019_config_stereo_mode(uint32_t mode, uint32_t stereo_offset); +extern void ipq4019_config_master(uint32_t enable, uint32_t stereo_offset); +extern void ipq4019_config_mclk_sel(uint32_t stereo_offset, uint32_t val); +extern void ipq4019_config_sample_cnt_clear_type(uint32_t stereo_offset); + +/* APIs in DAI driver */ +extern int ipq4019_get_mbox_id(struct snd_pcm_substream *substream, int intf); +extern int ipq4019_get_stereo_id(struct snd_pcm_substream *substream, int intf); +extern u32 ipq4019_get_act_bit_width(u32 bit_width); +extern void ipq4019_stereo_spdif_enable(uint32_t enable, uint32_t stereo_id); +extern void ipq4019_stereo_spdif_pcmswap(uint32_t enable, uint32_t stereo_id); +extern void ipq4019_spdifin_ctrl_spdif_en(uint32_t enable); +extern void ipq4019_glb_spdif_out_en(uint32_t enable); +extern void ipq4019_spdifin_cfg(void); +extern void ipq4019_glb_tdm_ctrl_ch_num(uint32_t val, uint32_t dir); +extern void ipq4019_glb_tdm_ctrl_sync_num(uint32_t val, uint32_t dir); +extern void ipq4019_glb_tdm_ctrl_delay(uint32_t delay, uint32_t dir); + +void ipq4019_glb_audio_mode_B1K(void); + +#endif diff --git a/sound/soc/qcom/ipq4019/ipq4019-codec.c b/sound/soc/qcom/ipq4019/ipq4019-codec.c new file mode 100644 index 0000000..61421d9 --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-codec.c @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <sound/control.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <sound/initval.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "ipq4019-adss.h" +#include "ipq4019-codec.h" + +struct audio_hw_params audio_params; + +static const u8 akd4613_reg[AK4613_MAX_REG] = { + 0x0F, 0x07, 0x3F, 0x20, 0x20, 0x55, 0x05, 0x07, + 0x0F, 0x07, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +uint8_t ipq4019_compare_hw_params(struct audio_hw_params *curr_params) +{ + if ((curr_params->bit_width == audio_params.bit_width) && + (curr_params->freq == audio_params.freq) && + (curr_params->channels == audio_params.channels)) + return 0; + else + return -EINVAL; +} + +/* DFS : Sampling Speed + * + * DFS1 DFS0 Sampling Speed Mode (fs) + * 0 0 Normal Speed Mode 32kHz~48kHz (default) + * 0 1 Double Speed Mode 64kHz~96kHz + * 1 0 Quad Speed Mode 128kHz~192kHz + * 1 1 N/A - + */ + +static int ipq4019_codec_i2c_set_dfs(struct snd_soc_codec *codec, int mode) +{ + uint32_t reg; + + if (mode > QUAD_SPEED) { + pr_err("%s: %d: Invalid DFS mode", __func__, __LINE__); + return -EINVAL; + } + + reg = snd_soc_read(codec, AKD4613_04_CTRL2); + + reg &= ~(AKD4613_DFS_MASK); + reg |= AKD4613_DFS(mode); + + snd_soc_write(codec, AKD4613_04_CTRL2, reg); + + return 0; +} + +/* CKS : Master Clock Input Frequency Select + * + * CKS1 CKS0 Normal Speed Double Speed Quad Speed + * 0 0 256fs 256fs 128fs + * 0 1 384fs 256fs 128fs + * 1 0 512fs 256fs 128fs (default) + * 1 1 512fs 256fs 128fs + */ + +static int ipq4019_codec_i2c_set_cks(struct snd_soc_codec *codec, + int config, int mode) +{ + uint32_t cks_val; + uint32_t reg; + + if (mode == NORMAL_SPEED) { + if (config == FS_256) + cks_val = 0; + else if (config == FS_384) + cks_val = 1; + else if (config == FS_512) + cks_val = 2; + else + cks_val = -EINVAL; + } else if (mode == DOUBLE_SPEED) { + if (config == FS_256) + cks_val = 2; + else + cks_val = -EINVAL; + } else if (mode == QUAD_SPEED) { + if (config == FS_128) + cks_val = 2; + else + cks_val = -EINVAL; + } else { + pr_err("%s: %d: Invalid DFS mode", __func__, __LINE__); + return -EINVAL; + } + + if (cks_val < 0) { + pr_err("%s: %d: Invalid CKS config", __func__, __LINE__); + return cks_val; + } + + reg = snd_soc_read(codec, AKD4613_04_CTRL2); + + reg &= ~(AKD4613_CKS_MASK); + reg |= AKD4613_CKS(cks_val); + + snd_soc_write(codec, AKD4613_04_CTRL2, reg); + + return 0; +} + +static int ipq4019_codec_i2c_set_tdm_mode(struct snd_soc_codec *codec, + int tdm_mode) +{ + uint32_t reg; + + if (tdm_mode >= TDM_MAX) { + pr_err("%s: %d: Invalid DFS mode", __func__, __LINE__); + return -EINVAL; + } + + reg = snd_soc_read(codec, AKD4613_03_CTRL1); + + reg &= ~(AKD4613_TDM_MODE_MASK); + reg |= AKD4613_TDM_MODE(tdm_mode); + + snd_soc_write(codec, AKD4613_03_CTRL1, reg); + + return 0; +} + +static int ipq4019_codec_i2c_set_dif(struct snd_soc_codec *codec, + int dif_val) +{ + uint32_t reg; + + reg = snd_soc_read(codec, AKD4613_03_CTRL1); + + reg &= ~(AKD4613_DIF_MASK); + reg |= AKD4613_DIF(dif_val); + + snd_soc_write(codec, AKD4613_03_CTRL1, reg); + + return 0; +} + +static void ipq4019_codec_i2c_write_defaults(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < AK4613_MAX_REG; i++) + snd_soc_write(codec, i, akd4613_reg[i]); + udelay(10); +} + +static int ipq4019_codec_audio_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec; + + codec = dai->codec; + + /* I2S and TDM cannot co-exist. CPU DAI startup would + * have already checked this case, by this time. + */ + if (!dai->active) + ipq4019_codec_i2c_write_defaults(codec); + + return 0; +} + +static int ipq4019_codec_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec; + int samp_rate = params_rate(params); + u32 bit_width = params_format(params); + int channels = params_channels(params); + int bit_act; + int dfs, cks, tdm_mode, dif; + uint32_t intf = dai->driver->id; + struct audio_hw_params curr_params; + + bit_act = ipq4019_get_act_bit_width(bit_width); + if (bit_act == __BIT_INVAL) + return -EINVAL; + + curr_params.freq = samp_rate; + curr_params.channels = channels; + curr_params.bit_width = bit_act; + + codec = dai->codec; + + /* + * Since CLKS in the codec are shared by I2S TX and RX channels, + * Rx and Tx when used simulatneoulsy will have to use the same channel, + * sampling frequency and bit widths. So compare the settings and then + * update the codec settings. + */ + + if (dai->active > 1) { + if (ipq4019_compare_hw_params(&curr_params)) { + /* Playback and capture settings do not match */ + pr_err("\nPlayback and capture settings do not match\n"); + return -EINVAL; + } + /* Settings match, codec settings are already done*/ + return 0; + } + + audio_params.freq = samp_rate; + audio_params.channels = channels; + audio_params.bit_width = bit_act; + + if (intf == I2S) { + /* default values */ + dfs = NORMAL_SPEED; + cks = FS_512; + tdm_mode = STEREO; + dif = DIF_I2S_MODE; + + } else if (intf == TDM) { + /* Codec settings for 8 channels */ + dfs = DOUBLE_SPEED; + cks = FS_256; + tdm_mode = TDM_256; + dif = DIF_LR_MODE3; + } else { + pr_err("\n%s Invalid interface\n", __func__); + return -EINVAL; + } + + ipq4019_codec_i2c_set_dfs(codec, dfs); + ipq4019_codec_i2c_set_cks(codec, cks, dfs); + ipq4019_codec_i2c_set_tdm_mode(codec, tdm_mode); + ipq4019_codec_i2c_set_dif(codec, dif); + udelay(10); + + return 0; +} + +static int ipq4019_codec_audio_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + dev_dbg(dai->dev, "%s:%d\n", __func__, __LINE__); + return 0; +} + +static void ipq4019_codec_audio_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + dev_dbg(dai->dev, "%s:%d\n", __func__, __LINE__); +} + +static struct snd_soc_dai_ops ipq4019_codec_audio_ops = { + .startup = ipq4019_codec_audio_startup, + .hw_params = ipq4019_codec_audio_hw_params, + .prepare = ipq4019_codec_audio_prepare, + .shutdown = ipq4019_codec_audio_shutdown, +}; + +static struct snd_soc_dai_driver ipq4019_codec_dais[] = { + { + .name = "qca-i2s-codec-dai", + .playback = { + .stream_name = "qca-i2s-playback", + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + }, + .capture = { + .stream_name = "qca-i2s-capture", + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + }, + .ops = &ipq4019_codec_audio_ops, + .id = I2S, + }, + { + .name = "qca-tdm-codec-dai", + .playback = { + .stream_name = "qca-tdm-playback", + .channels_min = CH_STEREO, + .channels_max = CH_7_1, + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + }, + .capture = { + .stream_name = "qca-tdm-capture", + .channels_min = CH_STEREO, + .channels_max = CH_7_1, + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + }, + .ops = &ipq4019_codec_audio_ops, + .id = TDM, + }, + { + .name = "qca-i2s1-codec-dai", + .playback = { + .stream_name = "qca-i2s1-playback", + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + }, + }, + { + .name = "qca-i2s2-codec-dai", + .playback = { + .stream_name = "qca-i2s2-playback", + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + }, + }, + { + .name = "qca-spdif-codec-dai", + .playback = { + .stream_name = "qca-spdif-playback", + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24_3, + }, + .capture = { + .stream_name = "qca-spdif-capture", + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24_3, + }, + }, +}; + +static int ipq4019_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return -ENOTSUPP; +} + +static const struct snd_kcontrol_new vol_ctrl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "playback volume", + .access = (SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE), + .info = ipq4019_info, +}; + +unsigned int ipq4019_codec_i2c_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(codec->control_data, (u8)(reg & 0xFF)); + if (ret < 0) + pr_err("\ti2c read error %s(%d)\n", __func__, ret); + + return ret; +} + +static const struct snd_soc_codec_driver ipq4019_codec = { + .num_controls = 0, + .reg_cache_size = ARRAY_SIZE(akd4613_reg), + .reg_word_size = sizeof(u8), + .reg_cache_default = akd4613_reg, +}; + +static const struct of_device_id ipq4019_codec_id_table[] = { + { .compatible = "qca,ipq4019-codec" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ipq4019_codec_id_table); + +static int ipq4019_codec_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + int ret; + + ret = snd_soc_register_codec(&i2c->dev, + &ipq4019_codec, ipq4019_codec_dais, + ARRAY_SIZE(ipq4019_codec_dais)); + if (ret < 0) + pr_err("\nsnd_soc_register_codec failed (%d)\n", ret); + + return ret; +} + +static int ipq4019_codec_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct of_device_id ipq4019_codec_of_match[] = { + { .compatible = "qca,ipq4019-codec" }, + {}, +}; + +static const struct i2c_device_id ipq4019_codec_i2c_id[] = { + { "qca_codec", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ipq4019_codec_i2c_id); + +static struct i2c_driver ipq4019_codec_i2c_driver = { + .driver = { + .name = "qca_codec", + .owner = THIS_MODULE, + .of_match_table = ipq4019_codec_of_match, + }, + .probe = ipq4019_codec_i2c_probe, + .remove = ipq4019_codec_i2c_remove, + .id_table = ipq4019_codec_i2c_id, +}; + +static int ipq4019_codec_init(void) +{ + int ret; + + ret = i2c_add_driver(&ipq4019_codec_i2c_driver); + if (ret < 0) + pr_err("%s: %d: Failed to add I2C driver", __func__, __LINE__); + + return ret; +} +module_init(ipq4019_codec_init); + +static void ipq4019_codec_exit(void) +{ + i2c_del_driver(&ipq4019_codec_i2c_driver); +} +module_exit(ipq4019_codec_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("IPQ4019 Codec Driver"); diff --git a/sound/soc/qcom/ipq4019/ipq4019-codec.h b/sound/soc/qcom/ipq4019/ipq4019-codec.h new file mode 100644 index 0000000..bf91c0f --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-codec.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#define AKD4613_00_PM1 0x00 +#define AKD4613_01_PM2 0x01 +#define AKD4613_02_PM3 0x02 +#define AKD4613_03_CTRL1 0x03 +#define AKD4613_04_CTRL2 0x04 +#define AKD4613_05_DE_EMP1 0x05 +#define AKD4613_06_DE_EMP2 0x06 +#define AKD4613_07_OVRFLW_DT 0x07 +#define AKD4613_08_ZERO_DT 0x08 +#define AKD4613_09_IP_CTRL 0x09 +#define AKD4613_0A_OP_CTRL 0x0A +#define AKD4613_0B_LOUT1_CTRL 0x0B +#define AKD4613_0C_ROUT1_CTRL 0x0C +#define AKD4613_0D_LOUT2_CTRL 0x0D +#define AKD4613_0E_ROUT2_CTRL 0x0E +#define AKD4613_0F_LOUT3_CTRL 0x0F +#define AKD4613_10_ROUT3_CTRL 0x10 +#define AKD4613_11_LOUT4_CTRL 0x11 +#define AKD4613_12_ROUT4_CTRL 0x12 +#define AKD4613_13_LOUT5_CTRL 0x13 +#define AKD4613_14_ROUT5_CTRL 0x14 +#define AKD4613_15_LOUT6_CTRL 0x15 +#define AKD4613_16_ROUT6_CTRL 0x16 + +#define AK4613_MAX_REG (AKD4613_16_ROUT6_CTRL + 1) + +/* AKD4613_03_CTRL1 */ +#define AKD4613_DIF_MASK (7 << 3) +#define AKD4613_DIF(x) (x << 3) +#define AKD4613_DIF_I2S_MODE (4 << 3) +#define AKD4613_TDM_MODE_MASK (3 << 6) +#define AKD4613_TDM_MODE(x) (x << 6) + +/* AKD4613_04_CTRL2 */ +#define AKD4613_CKS_MASK (0x3 << 4) +#define AKD4613_CKS(x) (x << 4) +#define AKD4613_DFS_MASK (0x3 << 2) +#define AKD4613_DFS(x) (x << 2) + +struct audio_hw_params { + uint8_t channels; + uint32_t freq; + uint8_t bit_width; +}; + +enum dfs { + NORMAL_SPEED, + DOUBLE_SPEED, + QUAD_SPEED, + NA +}; + +enum cks { + FS_128, + FS_256, + FS_384, + FS_512 +}; + +enum tdm_mode { + STEREO, + TDM_512, + TDM_256, + TDM_128, + TDM_MAX +}; + +enum dif { + DIF_LR_MODE0, + DIF_LR_MODE1, + DIF_LR_MODE2, + DIF_LR_MODE3, + DIF_I2S_MODE +}; diff --git a/sound/soc/qcom/ipq4019/ipq4019-cpu-dai.c b/sound/soc/qcom/ipq4019/ipq4019-cpu-dai.c new file mode 100644 index 0000000..8f33e91 --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-cpu-dai.c @@ -0,0 +1,687 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/of_device.h> +#include <linux/clk-provider.h> +#include <linux/clk.h> + +#include "ipq4019-mbox.h" +#include "ipq4019-adss.h" + +struct dai_priv_st { + int stereo_tx; + int stereo_rx; + int mbox_tx; + int mbox_rx; + int tx_enabled; + int rx_enabled; + struct platform_device *pdev; +}; +static struct dai_priv_st dai_priv[MAX_INTF]; + +static struct clk *audio_tx_bclk; +static struct clk *audio_tx_mclk; +static struct clk *audio_rx_bclk; +static struct clk *audio_rx_mclk; +static struct clk *audio_spdif_src; +static struct clk *audio_spdif_div2; +static struct clk *audio_spdifinfast_src; + +/* Get Stereo channel ID based on I2S intf and direction */ +int ipq4019_get_stereo_id(struct snd_pcm_substream *substream, int intf) +{ + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + return dai_priv[intf].stereo_tx; + case SNDRV_PCM_STREAM_CAPTURE: + return dai_priv[intf].stereo_rx; + } + return -EINVAL; +} +EXPORT_SYMBOL(ipq4019_get_stereo_id); + +/* Get MBOX channel ID based on I2S/TDM/SPDIF intf and direction */ +int ipq4019_get_mbox_id(struct snd_pcm_substream *substream, int intf) +{ + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + return dai_priv[intf].mbox_tx; + case SNDRV_PCM_STREAM_CAPTURE: + return dai_priv[intf].mbox_rx; + } + return -EINVAL; +} +EXPORT_SYMBOL(ipq4019_get_mbox_id); + +u32 ipq4019_get_act_bit_width(u32 bit_width) +{ + switch (bit_width) { + case SNDRV_PCM_FORMAT_S8: + case SNDRV_PCM_FORMAT_U8: + return __BIT_8; + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_U16_BE: + return __BIT_16; + case SNDRV_PCM_FORMAT_S24_3LE: + case SNDRV_PCM_FORMAT_S24_3BE: + case SNDRV_PCM_FORMAT_U24_3LE: + case SNDRV_PCM_FORMAT_U24_3BE: + return __BIT_32; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_BE: + case SNDRV_PCM_FORMAT_U24_LE: + case SNDRV_PCM_FORMAT_U24_BE: + return __BIT_24; + case SNDRV_PCM_FORMAT_S32_LE: + case SNDRV_PCM_FORMAT_S32_BE: + case SNDRV_PCM_FORMAT_U32_LE: + case SNDRV_PCM_FORMAT_U32_BE: + return __BIT_32; + } + return __BIT_INVAL; +} +EXPORT_SYMBOL(ipq4019_get_act_bit_width); + +static int ipq4019_audio_clk_get(struct clk **clk, struct device *dev, + const char *id) +{ + *clk = devm_clk_get(dev, id); + if (IS_ERR(*clk)) { + dev_err(dev, "%s: Error in %s\n", __func__, id); + return PTR_ERR(*clk); + } + + return 0; +} + +static int ipq4019_audio_clk_set(struct clk *clk, struct device *dev, + u32 val) +{ + int ret; + + ret = clk_set_rate(clk, val); + if (ret != 0) { + dev_err_ratelimited(dev, "Error in setting %s\n", + __clk_get_name(clk)); + return ret; + } + + ret = clk_prepare_enable(clk); + if (ret != 0) { + dev_err_ratelimited(dev, "Error in enable %s\n", + __clk_get_name(clk)); + return ret; + } + + return 0; +} + +static void ipq4019_audio_clk_disable(struct clk **clk, struct device *dev) +{ + if (__clk_is_enabled(*clk)) + clk_disable_unprepare(*clk); +} + +static int ipq4019_audio_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u32 intf = dai->driver->id; + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + /* Check if the direction is enabled */ + if (dai_priv[intf].tx_enabled != ENABLE) + return -EFAULT; + + ipq4019_glb_tx_data_port_en(ENABLE); + ipq4019_glb_tx_framesync_port_en(ENABLE); + break; + case SNDRV_PCM_STREAM_CAPTURE: + /* Check if the direction is enabled */ + if (dai_priv[intf].rx_enabled != ENABLE) + return -EFAULT; + + ipq4019_glb_rx_data_port_en(ENABLE); + ipq4019_glb_rx_framesync_port_en(ENABLE); + break; + default: + return -EINVAL; + } + + if (intf == I2S || intf == I2S1 || intf == I2S2) { + /* Select I2S mode */ + ipq4019_glb_audio_mode(I2S, substream->stream); + } else if (intf == TDM) { + /* Select TDM mode */ + ipq4019_glb_audio_mode(TDM, substream->stream); + + /* Set TDM Ctrl register */ + ipq4019_glb_tdm_ctrl_sync_num(TDM_SYNC_NUM, substream->stream); + ipq4019_glb_tdm_ctrl_delay(TDM_DELAY, substream->stream); + } + + return 0; +} + +static int ipq4019_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + u32 bit_width, channels, rate; + u32 intf = dai->driver->id; + u32 stereo_id = ipq4019_get_stereo_id(substream, intf); + u32 mbox_id = ipq4019_get_mbox_id(substream, intf); + u32 bit_act; + int ret; + u32 mclk, bclk; + struct device *dev = &(dai_priv[intf].pdev->dev); + + bit_width = params_format(params); + channels = params_channels(params); + rate = params_rate(params); + + bit_act = ipq4019_get_act_bit_width(bit_width); + + if (intf == TDM) { + /* Set TDM number of channels */ + ipq4019_glb_tdm_ctrl_ch_num((channels-1), substream->stream); + mclk = bclk = rate * bit_act * channels; + } else { + bclk = rate * bit_act * channels; + mclk = bclk * MCLK_MULTI; + } + + ipq4019_glb_clk_enable_oe(substream->stream); + + ipq4019_config_master(ENABLE, stereo_id); + + ret = ipq4019_cfg_bit_width(bit_width, stereo_id); + if (ret) { + pr_err("BitWidth %d not supported ret: %d\n", bit_width, ret); + return ret; + } + + ipq4019_stereo_config_enable(DISABLE, stereo_id); + + ipq4019_stereo_config_reset(ENABLE, stereo_id); + ipq4019_stereo_config_mic_reset(ENABLE, stereo_id); + + mdelay(5); + + ret = ipq4019_mbox_fifo_reset(mbox_id); + if (ret) { + pr_err("%s: ret: %d Error in dma fifo reset\n", + __func__, ret); + return ret; + } + + ipq4019_stereo_config_reset(DISABLE, stereo_id); + ipq4019_stereo_config_mic_reset(DISABLE, stereo_id); + ipq4019_stereo_config_enable(ENABLE, stereo_id); + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + ret = ipq4019_audio_clk_set(audio_tx_mclk, dev, mclk); + if (ret) + return ret; + + ret = ipq4019_audio_clk_set(audio_tx_bclk, dev, bclk); + if (ret) + return ret; + break; + + case SNDRV_PCM_STREAM_CAPTURE: + ret = ipq4019_audio_clk_set(audio_rx_mclk, dev, mclk); + if (ret) + return ret; + + ret = ipq4019_audio_clk_set(audio_rx_bclk, dev, bclk); + if (ret) + return ret; + break; + } + + return 0; +} + +static void ipq4019_audio_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u32 intf = dai->driver->id; + struct device *dev = &(dai_priv[intf].pdev->dev); + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + ipq4019_glb_tx_data_port_en(DISABLE); + ipq4019_glb_tx_framesync_port_en(DISABLE); + + /* Disable the clocks */ + ipq4019_audio_clk_disable(&audio_tx_bclk, dev); + ipq4019_audio_clk_disable(&audio_tx_mclk, dev); + break; + case SNDRV_PCM_STREAM_CAPTURE: + ipq4019_glb_rx_data_port_en(DISABLE); + ipq4019_glb_rx_framesync_port_en(DISABLE); + + /* Disable the clocks */ + ipq4019_audio_clk_disable(&audio_rx_bclk, dev); + ipq4019_audio_clk_disable(&audio_rx_mclk, dev); + break; + } + + /* Disable the I2S Stereo block */ + ipq4019_stereo_config_enable(DISABLE, + ipq4019_get_stereo_id(substream, intf)); +} + +static struct snd_soc_dai_ops ipq4019_audio_ops = { + .startup = ipq4019_audio_startup, + .hw_params = ipq4019_audio_hw_params, + .shutdown = ipq4019_audio_shutdown, +}; + +static int ipq4019_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + uint32_t bit_width, channels, rate, bit_act; + int ret; + uint32_t stereo_id = ipq4019_get_stereo_id(substream, SPDIF); + uint32_t mclk, bclk; + struct device *dev = &(dai_priv[SPDIF].pdev->dev); + uint32_t spdif_bclk; + uint32_t spdif_mclk; + + bit_width = params_format(params); + channels = params_channels(params); + rate = params_rate(params); + bit_act = ipq4019_get_act_bit_width(bit_width); + + bclk = rate * bit_act * channels; + mclk = bclk * MCLK_MULTI; + + /* SPDIF subframe is always 32 bit and 2 channels */ + spdif_bclk = rate * 32 * 2; + spdif_mclk = spdif_bclk * 2; + + if (substream->stream == PLAYBACK) { + /* Set the clocks */ + ret = ipq4019_audio_clk_set(audio_tx_mclk, dev, mclk); + if (ret) + return ret; + + ret = ipq4019_audio_clk_set(audio_tx_bclk, dev, bclk); + if (ret) + return ret; + + ret = ipq4019_audio_clk_set(audio_spdif_src, dev, + spdif_mclk); + if (ret) + return ret; + + ret = ipq4019_audio_clk_set(audio_spdif_div2, dev, + spdif_bclk); + if (ret) + return ret; + + ipq4019_glb_clk_enable_oe(substream->stream); + + /* Set MASTER mode */ + ipq4019_config_master(ENABLE, stereo_id); + + /* Configure bit width */ + ret = ipq4019_cfg_bit_width(bit_width, stereo_id); + if (ret) { + pr_err("%s: BitWidth %d not supported\n", + __func__, bit_width); + return ret; + } + + } else if (substream->stream == CAPTURE) { + /* Set the clocks */ + ret = ipq4019_audio_clk_set(audio_spdifinfast_src, dev, + AUDIO_SPDIFINFAST); + if (ret) + return ret; + } + + return 0; +} + +static int ipq4019_spdif_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + dev_dbg(dai->dev, "%s:%d\n", __func__, __LINE__); + return 0; +} + +static int ipq4019_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Check if the direction is enabled */ + if (dai_priv[SPDIF].tx_enabled != ENABLE) + goto error; + + ipq4019_glb_tx_data_port_en(ENABLE); + ipq4019_glb_tx_framesync_port_en(ENABLE); + ipq4019_glb_spdif_out_en(ENABLE); + /* Select I2S/TDM */ + ipq4019_glb_audio_mode(I2S, substream->stream); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /* Check if the direction is enabled */ + if (dai_priv[SPDIF].rx_enabled != ENABLE) + goto error; + ipq4019_spdifin_ctrl_spdif_en(DISABLE); + + ipq4019_glb_rx_data_port_en(ENABLE); + ipq4019_glb_rx_framesync_port_en(ENABLE); + ipq4019_glb_audio_mode(I2S, substream->stream); + ipq4019_spdifin_cfg(); + } + + return ret; +error: + pr_err("%s: Direction not enabled\n", __func__); + return -EFAULT; +} + +static void ipq4019_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct device *dev = &(dai_priv[SPDIF].pdev->dev); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ipq4019_glb_tx_data_port_en(DISABLE); + ipq4019_glb_tx_framesync_port_en(DISABLE); + + /* Disable the clocks */ + ipq4019_audio_clk_disable(&audio_tx_bclk, dev); + ipq4019_audio_clk_disable(&audio_tx_mclk, dev); + ipq4019_audio_clk_disable(&audio_spdif_src, dev); + ipq4019_audio_clk_disable(&audio_spdif_div2, dev); + + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ipq4019_glb_rx_data_port_en(DISABLE); + ipq4019_glb_rx_framesync_port_en(DISABLE); + + /* Disable the clocks */ + ipq4019_audio_clk_disable(&audio_spdifinfast_src, dev); + } +} + +static int ipq4019_spdif_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + dev_dbg(dai->dev, "%s:%d\n", __func__, __LINE__); + return 0; +} + +static struct snd_soc_dai_ops ipq4019_spdif_ops = { + .startup = ipq4019_spdif_startup, + .prepare = ipq4019_spdif_prepare, + .hw_params = ipq4019_spdif_hw_params, + .shutdown = ipq4019_spdif_shutdown, + .set_fmt = ipq4019_spdif_set_fmt, +}; + +static struct snd_soc_dai_driver ipq4019_cpu_dais[] = { + { + .playback = { + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + }, + .capture = { + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + }, + .ops = &ipq4019_audio_ops, + .id = I2S, + .name = "qca-i2s-dai" + }, + { + .playback = { + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .channels_min = CH_STEREO, + .channels_max = CH_7_1, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + }, + .capture = { + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .channels_min = CH_STEREO, + .channels_max = CH_7_1, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + }, + .ops = &ipq4019_audio_ops, + .id = TDM, + .name = "qca-tdm-dai" + }, + { + .playback = { + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .channels_min = 2, + .channels_max = 2, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + }, + .ops = &ipq4019_audio_ops, + .id = I2S1, + .name = "qca-i2s1-dai" + }, + { + .playback = { + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .channels_min = 2, + .channels_max = 2, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + }, + .ops = &ipq4019_audio_ops, + .id = I2S2, + .name = "qca-i2s2-dai" + }, + { + .playback = { + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24_3, + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + }, + .capture = { + .rates = RATE_16000_96000, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24_3, + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + }, + .ops = &ipq4019_spdif_ops, + .id = SPDIF, + .name = "qca-spdif-dai" + }, +}; + +static const struct snd_soc_component_driver ipq4019_i2s_component = { + .name = "qca-cpu-dai", +}; + +static const struct of_device_id ipq4019_cpu_dai_id_table[] = { + { .compatible = "qca,ipq4019-i2s", .data = (void *)I2S }, + { .compatible = "qca,ipq4019-tdm", .data = (void *)TDM}, + { .compatible = "qca,ipq4019-spdif", .data = (void *)SPDIF}, + { .compatible = "qca,ipq4019-i2s1", .data = (void *)I2S1}, + { .compatible = "qca,ipq4019-i2s2", .data = (void *)I2S2}, + {}, +}; +MODULE_DEVICE_TABLE(of, ipq4019_cpu_dai_id_table); + +static int ipq4019_dai_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct device_node *np = pdev->dev.of_node; + int ret; + int intf; + + match = of_match_device(ipq4019_cpu_dai_id_table, &pdev->dev); + if (!match) + return -ENODEV; + + intf = (u32)match->data; + + /* TX is enabled only when both DMA and Stereo TX channel + * is specified in the DTSi + */ + if (!(of_property_read_u32(np, "dma-tx-channel", + &dai_priv[intf].mbox_tx) + || of_property_read_u32(np, "stereo-tx-port", + &dai_priv[intf].stereo_tx))) { + dai_priv[intf].tx_enabled = ENABLE; + } + + /* RX is enabled only when both DMA and Stereo RX channel + * is specified in the DTSi. + */ + if (!(of_property_read_u32(np, "dma-rx-channel", + &dai_priv[intf].mbox_rx))) { + if (intf == SPDIF) { + dai_priv[intf].rx_enabled = ENABLE; + dai_priv[intf].stereo_rx = MAX_STEREO_ENTRIES; + } else if (!(of_property_read_u32(np, "stereo-rx-port", + &dai_priv[intf].stereo_rx))) { + dai_priv[intf].rx_enabled = ENABLE; + } + } + + /* Either TX or Rx should have been enabled for a DMA/Stereo Channel */ + if (!(dai_priv[intf].tx_enabled || dai_priv[intf].rx_enabled)) { + dev_err(&pdev->dev, "%s: error reading node properties\n", + np->name); + return -EFAULT; + } + + /* Get Clks */ + audio_tx_mclk = devm_clk_get(&pdev->dev, "audio_tx_mclk"); + + if (IS_ERR(audio_tx_mclk)) { + dev_err(&pdev->dev, "Could not get tx_mclk\n"); + return PTR_ERR(audio_tx_mclk); + } + + audio_tx_bclk = devm_clk_get(&pdev->dev, "audio_tx_bclk"); + + if (IS_ERR(audio_tx_bclk)) { + dev_err(&pdev->dev, "Could not get tx_bclk\n"); + return PTR_ERR(audio_tx_bclk); + } + + if (intf == SPDIF) { + ret = ipq4019_audio_clk_get(&audio_spdif_src, &pdev->dev, + "audio_spdif_src"); + if (ret) + return ret; + + ret = ipq4019_audio_clk_get(&audio_spdif_div2, &pdev->dev, + "audio_spdif_div2"); + if (ret) + return ret; + + ret = ipq4019_audio_clk_get(&audio_spdifinfast_src, &pdev->dev, + "audio_spdifinfast_src"); + if (ret) + return ret; + } else { + audio_rx_mclk = devm_clk_get(&pdev->dev, "audio_rx_mclk"); + if (IS_ERR(audio_rx_mclk)) { + dev_err(&pdev->dev, "Could not get rx_mclk\n"); + return PTR_ERR(audio_rx_mclk); + } + + audio_rx_bclk = devm_clk_get(&pdev->dev, "audio_rx_bclk"); + if (IS_ERR(audio_rx_bclk)) { + dev_err(&pdev->dev, "Could not get rx_bclk\n"); + return PTR_ERR(audio_rx_bclk); + } + } + + dai_priv[intf].pdev = pdev; + ret = snd_soc_register_component(&pdev->dev, &ipq4019_i2s_component, + ipq4019_cpu_dais, ARRAY_SIZE(ipq4019_cpu_dais)); + if (ret) + dev_err(&pdev->dev, + "ret: %d error registering soc dais\n", ret); + + return ret; +} + +static int ipq4019_dai_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + return 0; +} + +static struct platform_driver ipq4019_dai_driver = { + .probe = ipq4019_dai_probe, + .remove = ipq4019_dai_remove, + .driver = { + .name = "qca-cpu-dai", + .of_match_table = ipq4019_cpu_dai_id_table, + }, +}; + +module_platform_driver(ipq4019_dai_driver); + +MODULE_ALIAS("platform:qca-cpu-dai"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("IPQ4019 CPU DAI DRIVER"); diff --git a/sound/soc/qcom/ipq4019/ipq4019-mbox.c b/sound/soc/qcom/ipq4019/ipq4019-mbox.c new file mode 100644 index 0000000..2a3bee9 --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-mbox.c @@ -0,0 +1,825 @@ +/* + * Copyright (c) 2015-2016 The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/dma-mapping.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <sound/soc.h> + +#include "ipq4019-mbox.h" + +/* When the mailbox operation is started, the mailbox would get one descriptor + * for the current data transfer and prefetch one more descriptor. When less + * than 3 descriptors are configured, then it is possible that before the CPU + * handles the interrupt, the mailbox could check the pre fetched descriptor + * and stop the DMA transfer. + * To handle this, the design is use multiple descriptors, but they would + * point to the same buffer address. This way more number of descriptors + * would satisfy the mbox requirement, and reusing the buffer address would + * satisfy the upper layer's buffer requirement + * + * The value of 5 of repetition times was derived from trial and error testing + * for minimum number of repetitions that would result in MBOX operations + * without stopping. + */ +#define MBOX_MIN_DESC_NUM 3 +#define MBOX_DESC_REPEAT_NUM 5 + +enum { + CHN_DISABLED = 0x00, + CHN_ENABLED = 0x01, /* from dtsi */ + CHN_STARTED = 0x02, /* dma inited */ + CHN_STATUS_DISABLE = 0xFF, +}; + +static struct ipq4019_mbox_rt_priv *mbox_rtime[ADSS_MBOX_NR_CHANNELS]; + +struct ipq4019_mbox_desc + *ipq4019_mbox_get_last_played(unsigned int channel_id) +{ + struct ipq4019_mbox_desc *desc, *prev; + unsigned int ndescs, i; + uint32_t index; + uint32_t dir; + + index = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + if (!mbox_rtime[index]) + return NULL; + + ndescs = mbox_rtime[index]->dir_priv[dir].ndescs; + /* Point to the last desc */ + prev = &mbox_rtime[index]->dir_priv[dir].dma_virt_head[ndescs - 1]; + + /* Point to the first desc */ + desc = &mbox_rtime[index]->dir_priv[dir].dma_virt_head[0]; + + for (i = 0; i < ndescs; i++) { + if (desc->OWN == 1 && prev->OWN == 0) + return desc; + prev = desc; + desc += 1; + } + + /* If we didn't find the last played buffer, return NULL */ + return NULL; +} +EXPORT_SYMBOL(ipq4019_mbox_get_last_played); + +uint32_t ipq4019_mbox_get_elapsed_size(uint32_t channel_id) +{ + struct ipq4019_mbox_desc *desc; + unsigned int i, size_played = 0; + uint32_t index; + uint32_t dir; + + index = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + if (!mbox_rtime[index]) + return size_played; + + desc = mbox_rtime[index]->dir_priv[dir].dma_virt_head; + + for (i = 0; i < mbox_rtime[index]->dir_priv[dir].ndescs; i++) { + if (desc->OWN == 0) { + desc->OWN = 1; + desc->ei = 1; + size_played += desc->size; + } + desc += 1; + } + + return size_played; +} +EXPORT_SYMBOL(ipq4019_mbox_get_elapsed_size); + +static struct ipq4019_mbox_desc *get_next( + struct ipq4019_mbox_rt_dir_priv *rtdir, + struct ipq4019_mbox_desc *desc) +{ + struct ipq4019_mbox_desc *end; + + end = rtdir->dma_virt_head + rtdir->ndescs; + + desc++; + + if (desc >= end) + desc = rtdir->dma_virt_head; + + return desc; +} + +void ipq4019_mbox_desc_own(u32 channel_id, int desc_no, int own) +{ + struct ipq4019_mbox_desc *desc; + struct ipq4019_mbox_rt_dir_priv *rtdir; + u32 chan; + u32 dir; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + rtdir = &mbox_rtime[chan]->dir_priv[dir]; + + desc = rtdir->dma_virt_head; + desc += desc_no; + + rtdir->write = desc_no; + + desc->OWN = own; + desc->ei = 1; +} +EXPORT_SYMBOL(ipq4019_mbox_desc_own); + +u32 ipq4019_mbox_get_played_offset(u32 channel_id) +{ + struct ipq4019_mbox_desc *desc, *write; + struct ipq4019_mbox_rt_dir_priv *rtdir; + unsigned int i, size_played = 0; + u32 chan; + u32 dir; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + rtdir = &mbox_rtime[chan]->dir_priv[dir]; + + desc = rtdir->dma_virt_head; + write = &rtdir->dma_virt_head[rtdir->write]; + + desc += rtdir->read; + + for (i = 0; i < rtdir->ndescs; i++) { + if (desc->OWN == 0) { + size_played = desc->size; + rtdir->read = (rtdir->read + 1) % rtdir->ndescs; + } else { + break; + } + + if (desc != write) + break; + + desc = get_next(rtdir, desc); + } + + return size_played * rtdir->read; +} +EXPORT_SYMBOL(ipq4019_mbox_get_played_offset); + +uint32_t ipq4019_mbox_get_played_offset_set_own(u32 channel_id) +{ + struct ipq4019_mbox_desc *desc, *last_played, *prev; + struct ipq4019_mbox_rt_dir_priv *rtdir; + unsigned int i, desc_own, size_played = 0; + u32 chan, dir; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + rtdir = &mbox_rtime[chan]->dir_priv[dir]; + last_played = NULL; + + /* Point to the last desc */ + prev = &rtdir->dma_virt_head[rtdir->ndescs - 1]; + desc_own = prev->OWN; + + /* point to first desc */ + desc = &rtdir->dma_virt_head[0]; + + for (i = 0; i < rtdir->ndescs; i++) { + if (prev->OWN == 0) { + if (i == (rtdir->ndescs - 1)) { + if (desc_own == 1) + last_played = desc; + } else if (desc->OWN == 1) { + last_played = desc; + } + prev->OWN = 1; + prev->ei = 1; + } + prev = desc; + desc += 1; + } + if (last_played) { + desc = &rtdir->dma_virt_head[0]; + size_played = last_played->BufPtr - desc->BufPtr; + } else { + pr_debug("%s last played buf not found\n", __func__); + rtdir->last_played_is_null++; + } + + return size_played; +} +EXPORT_SYMBOL(ipq4019_mbox_get_played_offset_set_own); + +int ipq4019_mbox_fifo_reset(int channel_id) +{ + void __iomem *mbox_reg; + u32 chan, dir; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + if (!mbox_rtime[chan]) + return -EINVAL; + + mbox_reg = mbox_rtime[chan]->mbox_reg_base; + + switch (dir) { + case PLAYBACK: + writel(MBOX_FIFO_RESET_TX_INIT, + mbox_reg + ADSS_MBOXn_MBOX_FIFO_RESET_REG); + break; + case CAPTURE: + writel(MBOX_FIFO_RESET_RX_INIT, + mbox_reg + ADSS_MBOXn_MBOX_FIFO_RESET_REG); + break; + } + + return 0; +} +EXPORT_SYMBOL(ipq4019_mbox_fifo_reset); + +int ipq4019_mbox_dma_start(int channel_id) +{ + void __iomem *mbox_reg; + u32 chan, dir; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + if (!mbox_rtime[chan]) + return -EINVAL; + + mbox_reg = mbox_rtime[chan]->mbox_reg_base; + mbox_rtime[index]->mbox_started = 1; + + switch (dir) { + case PLAYBACK: + writel(ADSS_MBOXn_DMA_RX_CONTROL_START, + mbox_reg + ADSS_MBOXn_MBOXn_DMA_RX_CONTROL_REG); + break; + + case CAPTURE: + writel(ADSS_MBOXn_DMA_TX_CONTROL_START, + mbox_reg + ADSS_MBOXn_MBOXn_DMA_TX_CONTROL_REG); + break; + } + + return 0; +} +EXPORT_SYMBOL(ipq4019_mbox_dma_start); + +int ipq4019_mbox_dma_resume(int channel_id) +{ + void __iomem *mbox_reg; + u32 chan, dir; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + if (!mbox_rtime[chan]) + return -EINVAL; + + if (!mbox_rtime[index]->mbox_started) + return 0; + + mbox_reg = mbox_rtime[chan]->mbox_reg_base; + + switch (dir) { + case PLAYBACK: + writel(ADSS_MBOXn_DMA_RX_CONTROL_RESUME, + mbox_reg + ADSS_MBOXn_MBOXn_DMA_RX_CONTROL_REG); + break; + + case CAPTURE: + writel(ADSS_MBOXn_DMA_TX_CONTROL_RESUME, + mbox_reg + ADSS_MBOXn_MBOXn_DMA_TX_CONTROL_REG); + break; + } + + return 0; +} +EXPORT_SYMBOL(ipq4019_mbox_dma_resume); + +int ipq4019_mbox_dma_stop(int channel_id, u32 delay_in_ms) +{ + void __iomem *mbox_reg; + struct ipq4019_mbox_rt_dir_priv *mbox_cb; + u32 chan, dir; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + if (!mbox_rtime[chan]) + return -EINVAL; + + mbox_reg = mbox_rtime[chan]->mbox_reg_base; + mbox_rtime[index]->mbox_started = 0; + + switch (dir) { + case PLAYBACK: + writel(ADSS_MBOXn_DMA_RX_CONTROL_STOP, + mbox_reg + ADSS_MBOXn_MBOXn_DMA_RX_CONTROL_REG); + break; + + case CAPTURE: + writel(ADSS_MBOXn_DMA_TX_CONTROL_STOP, + mbox_reg + ADSS_MBOXn_MBOXn_DMA_TX_CONTROL_REG); + break; + } + + /* + * Per the documentation: + * ______________________________________________________ + * Programming a one to this bit causes the DMA engine to + * stop transferring any more data from this descriptor + * chain. If a transfer is already in progress DMA enters + * a stop state after completing the current descriptor + * ______________________________________________________ + */ + mdelay(delay_in_ms); + + mbox_cb = &mbox_rtime[chan]->dir_priv[dir]; + mbox_cb->read = 0; + mbox_cb->write = 0; + + return 0; +} +EXPORT_SYMBOL(ipq4019_mbox_dma_stop); + +static bool ipq4019_is_other_chn_active(u32 chan, u32 dir) +{ + if (dir == PLAYBACK) + return (test_bit(CHN_STARTED, + &mbox_rtime[chan]->dir_priv[CAPTURE].status)); + else + return (test_bit(CHN_STARTED, + &mbox_rtime[chan]->dir_priv[PLAYBACK].status)); +} + +int ipq4019_mbox_dma_reset_swap(int channel_id) +{ + unsigned int val; + void __iomem *mbox_reg; + u32 chan; + + chan = ipq4019_convert_id_to_channel(channel_id); + + if (!mbox_rtime[chan]) + return -EINVAL; + + mbox_reg = mbox_rtime[chan]->mbox_reg_base; + + val = readl(mbox_reg + ADSS_MBOXn_MBOX_DMA_POLICY_REG); + val &= ~(MBOX_DMA_POLICY_RXD_END_SWAP | MBOX_DMA_POLICY_RXD_16BIT_SWAP); + + writel(val, mbox_reg + ADSS_MBOXn_MBOX_DMA_POLICY_REG); + + return 0; +} +EXPORT_SYMBOL(ipq4019_mbox_dma_reset_swap); + +int ipq4019_mbox_dma_swap(int channel_id, snd_pcm_format_t format) +{ + unsigned int val; + void __iomem *mbox_reg; + u32 chan; + + chan = ipq4019_convert_id_to_channel(channel_id); + + if (!mbox_rtime[chan]) + return -EINVAL; + + mbox_reg = mbox_rtime[chan]->mbox_reg_base; + + val = readl(mbox_reg + ADSS_MBOXn_MBOX_DMA_POLICY_REG); + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: + val |= MBOX_DMA_POLICY_RXD_16BIT_SWAP; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + case SNDRV_PCM_FORMAT_S24_3BE: + val |= MBOX_DMA_POLICY_RXD_END_SWAP; + break; + default: + /* Nothing to do */ + break; + } + writel(val, mbox_reg + ADSS_MBOXn_MBOX_DMA_POLICY_REG); + + return 0; +} +EXPORT_SYMBOL(ipq4019_mbox_dma_swap); + +static void ipq4019_mbox_intr_en(void __iomem *mbox_reg, unsigned int mask) +{ + unsigned int val; + + val = readl(mbox_reg + ADSS_MBOXn_MBOX_INT_ENABLE_REG); + val |= mask; + writel(val, mbox_reg + ADSS_MBOXn_MBOX_INT_ENABLE_REG); +} + +static void ipq4019_mbox_intr_disable(void __iomem *mbox_reg, unsigned int mask) +{ + unsigned int val; + + val = readl(mbox_reg + ADSS_MBOXn_MBOX_INT_ENABLE_REG); + val &= ~mask; + writel(val, mbox_reg + ADSS_MBOXn_MBOX_INT_ENABLE_REG); +} + +int ipq4019_mbox_dma_prepare(int channel_id) +{ + struct ipq4019_mbox_desc *desc; + unsigned int val; + void __iomem *mbox_reg; + dma_addr_t phys_addr; + u32 chan, dir; + struct ipq4019_mbox_rt_dir_priv *mbox_cb; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + if (!mbox_rtime[chan]) + return -EINVAL; + + mbox_reg = mbox_rtime[chan]->mbox_reg_base; + mbox_cb = &mbox_rtime[chan]->dir_priv[dir]; + + /* do not reset DMA registers if the other direction is active */ + if (!ipq4019_is_other_chn_active(chan, dir)) { + + val = readl(mbox_reg + ADSS_MBOXn_MBOX_DMA_POLICY_REG); + val |= MBOX_DMA_POLICY_SW_RESET; + writel(val, mbox_reg + ADSS_MBOXn_MBOX_DMA_POLICY_REG); + val &= ~MBOX_DMA_POLICY_SW_RESET; + writel(val, mbox_reg + ADSS_MBOXn_MBOX_DMA_POLICY_REG); + } + + desc = mbox_cb->dma_virt_head; + phys_addr = mbox_cb->dma_phys_head; + val = readl(mbox_reg + ADSS_MBOXn_MBOX_DMA_POLICY_REG); + + if (dir == PLAYBACK) { + /* Request the DMA channel to the controller */ + val |= MBOX_DMA_POLICY_RX_INT_TYPE; + + /* The direction is indicated from the DMA engine perspective + * i.e. we'll be using the RX registers for Playback and + * the TX registers for capture + */ + + val |= ADSS_MBOX_DMA_POLICY_SRAM_AC(phys_addr); + writel(val, mbox_reg + ADSS_MBOXn_MBOX_DMA_POLICY_REG); + writel(phys_addr & MBOX_DMA_MASK, + mbox_reg + ADSS_MBOXn_MBOXn_DMA_RX_DESCRIPTOR_BASE_REG); + ipq4019_mbox_intr_en(mbox_reg, MBOX_INT_ENABLE_RX_DMA_COMPLETE); + } else { + + val |= MBOX_DMA_POLICY_TX_INT_TYPE | + ADSS_MBOX_DMA_POLICY_TX_FIFO_THRESHOLD(6); + val |= ADSS_MBOX_DMA_POLICY_SRAM_AC(phys_addr); + writel(val, mbox_reg + ADSS_MBOXn_MBOX_DMA_POLICY_REG); + writel(phys_addr & MBOX_DMA_MASK, + mbox_reg + ADSS_MBOXn_MBOXn_DMA_TX_DESCRIPTOR_BASE_REG); + ipq4019_mbox_intr_en(mbox_reg, MBOX_INT_ENABLE_TX_DMA_COMPLETE); + } + + return 0; +} +EXPORT_SYMBOL(ipq4019_mbox_dma_prepare); + +void ipq4019_mbox_vuc_setup(int channel_id) +{ + uint32_t index, dir; + struct ipq4019_mbox_desc *desc; + int ndescs; + int i; + + index = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + ndescs = mbox_rtime[index]->dir_priv[dir].ndescs; + desc = mbox_rtime[index]->dir_priv[dir].dma_virt_head; + + /* Copy VUC from previous descriptors */ + for (i = 0; i < ndescs; i++) { + /* Setup V bits as 1, Acc to IEC 60958-3 Standard + * for non PCM data, we need to set invalid for + * both channels + * There are 6 DWORDS (192 bits) for Channel A + * and 6 DWORDS (192 bits) for channel B + */ + desc[i].vuc_dword[CHANNEL_A_VDWORD_1] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_A_VDWORD_2] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_A_VDWORD_3] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_A_VDWORD_4] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_A_VDWORD_5] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_A_VDWORD_6] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_B_VDWORD_1] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_B_VDWORD_2] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_B_VDWORD_3] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_B_VDWORD_4] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_B_VDWORD_5] = ADSS_MBOX_INVALID_PCM; + desc[i].vuc_dword[CHANNEL_B_VDWORD_6] = ADSS_MBOX_INVALID_PCM; + + /* Now setup C bits, acc to IEC-60958-3 */ + desc[i].vuc_dword[CHANNEL_A_CDWORD_1] = SPDIF_CONSUMER_COMPRESD; + desc[i].vuc_dword[CHANNEL_B_CDWORD_2] = SPDIF_CONSUMER_COMPRESD; + } +} +EXPORT_SYMBOL(ipq4019_mbox_vuc_setup); + +int ipq4019_mbox_form_ring(int channel_id, dma_addr_t baseaddr, u8 *area, + int period_bytes, int bufsize, int own_bit) +{ + struct ipq4019_mbox_desc *desc, *_desc_p; + dma_addr_t desc_p, baseaddr_const; + unsigned int i, ndescs; + u32 chan, dir; + struct ipq4019_mbox_rt_dir_priv *mbox_cb; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + if (!mbox_rtime[chan]) + return -EINVAL; + + mbox_cb = &mbox_rtime[chan]->dir_priv[dir]; + ndescs = DIV_ROUND_UP(bufsize, period_bytes); + + if (ndescs < MBOX_MIN_DESC_NUM) + ndescs *= MBOX_DESC_REPEAT_NUM; + + desc = (struct ipq4019_mbox_desc *)(area + (ndescs * period_bytes)); + desc_p = baseaddr + (ndescs * period_bytes); + + memset(desc, 0, ndescs * sizeof(struct ipq4019_mbox_desc)); + + mbox_cb->read = 0; + mbox_cb->write = 0; + mbox_cb->ndescs = ndescs; + mbox_cb->dma_virt_head = desc; + mbox_cb->dma_phys_head = desc_p; + _desc_p = (struct ipq4019_mbox_desc *)desc_p; + + baseaddr_const = baseaddr; + + for (i = 0; i < ndescs; i++, desc++) { + desc->OWN = own_bit; + desc->ei = 1; + desc->BufPtr = baseaddr & MBOX_DMA_MASK; + desc->NextPtr = (unsigned long)&_desc_p[(i + 1) % ndescs]; + desc->size = period_bytes; + desc->length = desc->size; + baseaddr += ALIGN(period_bytes, L1_CACHE_BYTES); + if (baseaddr >= (baseaddr_const + bufsize)) { + if (bufsize % period_bytes) + desc->size = bufsize % period_bytes; + else + desc->size = period_bytes; + + baseaddr = baseaddr_const; + } + } + + return 0; +} +EXPORT_SYMBOL(ipq4019_mbox_form_ring); + +int ipq4019_mbox_dma_release(int channel_id) +{ + u32 chan, dir; + struct ipq4019_mbox_rt_dir_priv *mbox_cb; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + mbox_cb = &mbox_rtime[chan]->dir_priv[dir]; + + if (test_bit(CHN_STARTED, &mbox_cb->status)) { + ipq4019_mbox_intr_disable(mbox_rtime[chan]->mbox_reg_base, + (MBOX_INT_ENABLE_TX_DMA_COMPLETE | + MBOX_INT_ENABLE_RX_DMA_COMPLETE)); + /* + * ALSA framework calls ipq4019_mbox_dma_stop() before + * calling close API. + */ + mbox_cb->dma_virt_head = NULL; + + clear_bit(CHN_STARTED, &mbox_cb->status); + return 0; + } + + return -ENXIO; +} +EXPORT_SYMBOL(ipq4019_mbox_dma_release); + +static void irq_proc_status(struct ipq4019_mbox_rt_dir_priv *priv, int irq, + u32 status, int cb, int stats, u32 *mask, u32 bit) +{ + if (status & bit) { + *mask |= bit; + if (cb && priv->callback) + priv->callback(irq, priv->dai_priv); + + if (stats) + priv->err_stats++; + } +} + +static irqreturn_t ipq4019_mbox_dma_irq(int irq, void *dev_id) +{ + unsigned int status, mask = 0; + struct ipq4019_mbox_rt_priv *curr_rtime = dev_id; + void __iomem *mbox_reg = curr_rtime->mbox_reg_base; + struct ipq4019_mbox_rt_dir_priv *p = &curr_rtime->dir_priv[PLAYBACK]; + struct ipq4019_mbox_rt_dir_priv *c = &curr_rtime->dir_priv[CAPTURE]; + + status = readl(mbox_reg + ADSS_MBOXn_MBOX_INT_STATUS_REG); + + irq_proc_status(p, irq, status, 1, 0, &mask, + MBOX_INT_STATUS_RX_DMA_COMPLETE); + irq_proc_status(c, irq, status, 1, 0, &mask, + MBOX_INT_STATUS_TX_DMA_COMPLETE); + irq_proc_status(p, irq, status, 0, 1, &mask, + MBOX_INT_STATUS_RX_UNDERFLOW); + irq_proc_status(p, irq, status, 0, 1, &mask, + MBOX_INT_STATUS_RX_FIFO_UNDERFLOW); + irq_proc_status(c, irq, status, 0, 1, &mask, + MBOX_INT_STATUS_TX_OVERFLOW); + irq_proc_status(c, irq, status, 0, 1, &mask, + MBOX_INT_STATUS_TX_FIFO_OVERFLOW); + + if (mask) { + writel(status & ~mask, + mbox_reg + ADSS_MBOXn_MBOX_INT_STATUS_REG); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +int ipq4019_mbox_dma_deinit(u32 channel_id) +{ + u32 chan, dir; + struct ipq4019_mbox_rt_dir_priv *mbox_cb; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + mbox_cb = &mbox_rtime[chan]->dir_priv[dir]; + if (test_bit(CHN_STARTED, &mbox_cb->status)) + clear_bit(CHN_STARTED, &mbox_cb->status); + + mbox_cb->dai_priv = NULL; + mbox_cb->callback = NULL; + mbox_cb->dev = NULL; + + return 0; +} +EXPORT_SYMBOL(ipq4019_mbox_dma_deinit); + +int ipq4019_mbox_dma_init(struct device *dev, int channel_id, + irq_handler_t callback, void *private_data) +{ + u32 chan; + u32 dir; + struct ipq4019_mbox_rt_dir_priv *mbox_cb; + + chan = ipq4019_convert_id_to_channel(channel_id); + dir = ipq4019_convert_id_to_dir(channel_id); + + if (chan >= ADSS_MBOX_NR_CHANNELS) + return -EINVAL; + + if (!mbox_rtime[chan]) + return -EINVAL; + + mbox_cb = &mbox_rtime[chan]->dir_priv[dir]; + + if (!(mbox_cb->status & CHN_ENABLED)) + return -EINVAL; + + if (test_and_set_bit(CHN_STARTED, &mbox_cb->status)) + return -EBUSY; + + mbox_cb->dai_priv = private_data; + mbox_cb->callback = callback; + mbox_cb->dev = dev; + + return 0; +} +EXPORT_SYMBOL(ipq4019_mbox_dma_init); + +static int ipq4019_mbox_probe(struct platform_device *pdev) +{ + struct device_node *np; + int irq; + u32 tx_channel; + u32 rx_channel; + u32 id; + void __iomem *reg_base; + struct resource *res; + int rc; + + np = pdev->dev.of_node; + + if (of_property_read_u32(np, "dma-index", &id)) { + dev_err(&pdev->dev, + "unable to read (dma-index) from device node %s\n", + np->name); + return -EINVAL; + } + + if (id >= ADSS_MBOX_NR_CHANNELS) + return -EINVAL; + + if (of_property_read_u32(np, "tx-channel", &tx_channel)) + tx_channel = CHN_STATUS_DISABLE; + + if (of_property_read_u32(np, "rx-channel", &rx_channel)) + rx_channel = CHN_STATUS_DISABLE; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "%s: %d: Error getting mbox resource\n", + __func__, __LINE__); + return -EINVAL; + } + + /* + * Read interrupt and store + */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "%s: MBOX %d IRQ %d is not provided\n", + __func__, id, irq); + return irq; + } + + reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(reg_base)) + return PTR_ERR(reg_base); + + mbox_rtime[id] = devm_kzalloc(&pdev->dev, + sizeof(struct ipq4019_mbox_rt_priv), GFP_KERNEL); + if (!mbox_rtime[id]) + return -ENOMEM; + + rc = devm_request_irq(&pdev->dev, irq, ipq4019_mbox_dma_irq, 0, + "ipq4019-mbox", mbox_rtime[id]); + if (rc) { + dev_err(&pdev->dev, "request_irq() failed with ret: %d\n", rc); + return rc; + } + + mbox_rtime[id]->mbox_reg_base = reg_base; + mbox_rtime[id]->dir_priv[PLAYBACK].channel_id = tx_channel; + mbox_rtime[id]->dir_priv[CAPTURE].channel_id = rx_channel; + mbox_rtime[id]->dir_priv[PLAYBACK].status = + (tx_channel == CHN_STATUS_DISABLE) ? CHN_DISABLED : CHN_ENABLED; + mbox_rtime[id]->dir_priv[CAPTURE].status = + (rx_channel == CHN_STATUS_DISABLE) ? CHN_DISABLED : CHN_ENABLED; + mbox_rtime[id]->irq_no = irq; + + return 0; +} + +static const struct of_device_id ipq4019_mbox_table[] = { + { .compatible = "qca,ipq4019-mbox" }, + {}, +}; + +static struct platform_driver ipq4019_mbox_driver = { + .probe = ipq4019_mbox_probe, + .driver = { + .name = "ipq4019-mbox", + .of_match_table = ipq4019_mbox_table, + }, +}; + +module_platform_driver(ipq4019_mbox_driver); + +MODULE_ALIAS("platform:ipq4019-mbox"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("IPQ4019 MBOX DRIVER"); diff --git a/sound/soc/qcom/ipq4019/ipq4019-mbox.h b/sound/soc/qcom/ipq4019/ipq4019-mbox.h new file mode 100644 index 0000000..f2e5ede --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-mbox.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2015-2016 The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _IPQ4019_MBOX_H_ +#define _IPQ4019_MBOX_H_ + +#include "ipq4019-adss.h" + +#define ADSS_MBOX_INVALID_PCM (0xFFFFFFFF) +#define ADSS_MBOX_REG_BASE (0x7700000 + 0x6000) +#define ADSS_MBOX_RANGE (0xFA000) +#define ADSS_MBOX_SPDIF_IRQ (163 + 32) +#define ADSS_MBOX0_IRQ (156 + 32) +#define ADSS_MBOX1_IRQ (157 + 32) +#define ADSS_MBOX2_IRQ (158 + 32) +#define ADSS_MBOX3_IRQ (159 + 32) + +#define CHANNEL_A_VDWORD_START 0 +#define CHANNEL_B_VDWORD_START 18 + +#define CHANNEL_A_VDWORD_1 (CHANNEL_A_VDWORD_START + 0) +#define CHANNEL_A_VDWORD_2 (CHANNEL_A_VDWORD_START + 1) +#define CHANNEL_A_VDWORD_3 (CHANNEL_A_VDWORD_START + 2) +#define CHANNEL_A_VDWORD_4 (CHANNEL_A_VDWORD_START + 3) +#define CHANNEL_A_VDWORD_5 (CHANNEL_A_VDWORD_START + 4) +#define CHANNEL_A_VDWORD_6 (CHANNEL_A_VDWORD_START + 5) + +#define CHANNEL_B_VDWORD_1 (CHANNEL_B_VDWORD_START + 0) +#define CHANNEL_B_VDWORD_2 (CHANNEL_B_VDWORD_START + 1) +#define CHANNEL_B_VDWORD_3 (CHANNEL_B_VDWORD_START + 2) +#define CHANNEL_B_VDWORD_4 (CHANNEL_B_VDWORD_START + 3) +#define CHANNEL_B_VDWORD_5 (CHANNEL_B_VDWORD_START + 4) +#define CHANNEL_B_VDWORD_6 (CHANNEL_B_VDWORD_START + 5) + +#define CHANNEL_A_CDWORD_START 12 +#define CHANNEL_B_CDWORD_START 30 + +#define CHANNEL_A_CDWORD_1 (CHANNEL_A_CDWORD_START + 0) +#define CHANNEL_B_CDWORD_2 (CHANNEL_B_CDWORD_START + 0) + +/* Acc to IEC 60958-3, bit 0.0 = 0 is consumer + * bit 0.1 = 1is compressed playback + * bit 3.0 = 1 is sampling freq No specified + */ +#define SPDIF_CONSUMER_COMPRESD 0x01000006 +#define MBOX_MIN_DESC_NUM 3 +#define MBOX_DESC_REPEAT_NUM 5 + +enum { + ADSS_MBOX_NR_CHANNELS = 5, +}; + +struct ipq4019_mbox_desc { + unsigned int length : 12, /* bit 11-00 */ + size : 12, /* bit 23-12 */ + vuc : 1, /* bit 24 */ + ei : 1, /* bit 25 */ + rsvd1 : 4, /* bit 29-26 */ + EOM : 1, /* bit 30 */ + OWN : 1, /* bit 31 */ + BufPtr : 28, /* bit 27-00 */ + rsvd2 : 4, /* bit 31-28 */ + NextPtr : 28, /* bit 27-00 */ + rsvd3 : 4; /* bit 31-28 */ + + unsigned int vuc_dword[36]; +}; + +#define MBOX_DMA_MASK DMA_BIT_MASK(28) + +struct ipq4019_mbox_rt_dir_priv { + /* Desc array in virtual space */ + struct ipq4019_mbox_desc *dma_virt_head; + + /* Desc array for DMA */ + dma_addr_t dma_phys_head; + struct device *dev; + unsigned int ndescs; + irq_handler_t callback; + void *dai_priv; + unsigned long status; + u32 channel_id; + u32 err_stats; + u32 last_played_is_null; + u32 write; + u32 read; +}; + +struct ipq4019_mbox_rt_priv { + int irq_no; + void __iomem *mbox_reg_base; + struct ipq4019_mbox_rt_dir_priv dir_priv[2]; + int mbox_started; +}; + +/* Replaces struct ath_i2s_softc */ +struct ipq4019_pcm_pltfm_priv { + struct snd_pcm_substream *playback; + struct snd_pcm_substream *capture; +}; + +int ipq4019_mbox_fifo_reset(int channel_id); +int ipq4019_mbox_dma_start(int channel_id); +int ipq4019_mbox_dma_stop(int channel_id, u32 delay_in_ms); +int ipq4019_mbox_dma_reset_swap(int channel_id); +int ipq4019_mbox_dma_swap(int channel_id, snd_pcm_format_t format); +int ipq4019_mbox_dma_prepare(int channel_id); +int ipq4019_mbox_dma_resume(int channel_id); +int ipq4019_mbox_form_ring(int channel_id, dma_addr_t baseaddr, u8 *base, + int period_bytes, int bufsize, int own_bit); +int ipq4019_mbox_dma_release(int channel); +int ipq4019_mbox_dma_init(struct device *dev, int channel_id, + irq_handler_t callback, void *private_data); +void ipq4019_mbox_vuc_setup(int channel_id); +u32 ipq4019_mbox_get_played_offset(u32 channel_id); +int ipq4019_mbox_dma_deinit(u32 channel_id); +void ipq4019_mbox_desc_own(u32 channel_id, int desc_no, int own); +struct ipq4019_mbox_desc *ipq4019_mbox_get_last_played(unsigned int channel_id); +uint32_t ipq4019_mbox_get_elapsed_size(uint32_t channel_id); +void ipq4019_mbox_vuc_setup(int channel_id); +uint32_t ipq4019_mbox_get_played_offset_set_own(u32 channel_id); + +static inline u32 ipq4019_convert_id_to_channel(u32 id) +{ + return (id / 2); +} + +static inline u32 ipq4019_convert_id_to_dir(u32 id) +{ + return (id % 2); +} + +#endif /* _IPQ40XX_MBOX_H_ */ diff --git a/sound/soc/qcom/ipq4019/ipq4019-pcm-i2s.c b/sound/soc/qcom/ipq4019/ipq4019-pcm-i2s.c new file mode 100644 index 0000000..e93d999 --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-pcm-i2s.c @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2015-2016 The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/control.h> +#include <sound/pcm_params.h> + +#include "ipq4019-adss.h" +#include "ipq4019-pcm.h" + +static struct snd_pcm_hardware ipq4019_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = RATE_16000_96000, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .buffer_bytes_max = IPQ4019_I2S_BUFF_SIZE, + .period_bytes_max = IPQ4019_I2S_BUFF_SIZE / 2, + .period_bytes_min = IPQ4019_I2S_PERIOD_BYTES_MIN, + .periods_min = IPQ4019_I2S_NO_OF_PERIODS, + .periods_max = IPQ4019_I2S_NO_OF_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware ipq4019_pcm_hardware_capture = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = RATE_16000_96000, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .buffer_bytes_max = IPQ4019_I2S_BUFF_SIZE, + .period_bytes_max = IPQ4019_I2S_BUFF_SIZE / 2, + .period_bytes_min = IPQ4019_I2S_PERIOD_BYTES_MIN, + .periods_min = IPQ4019_I2S_NO_OF_PERIODS, + .periods_max = IPQ4019_I2S_NO_OF_PERIODS, + .fifo_size = 0, +}; + +static size_t ip4019_dma_buffer_size(struct snd_pcm_hardware *pcm_hw) +{ + return (pcm_hw->buffer_bytes_max + + (pcm_hw->periods_min * sizeof(struct ipq4019_mbox_desc))); +} + +static struct device *ss2dev(struct snd_pcm_substream *substream) +{ + return substream->pcm->card->dev; +} + +/* + * The MBOX descriptors and buffers should lie within the same 256MB + * region. Because, the buffer address pointer (in the descriptor structure) + * and descriptor base address pointer register share the same MSB 4 bits + * which is configured in MBOX DMA Policy register. + * + * Hence ensure that the entire allocated region falls in a 256MB region. + */ +static int ipq4019_mbox_buf_is_aligned(void *c_ptr, ssize_t size) +{ + u32 ptr = (u32)c_ptr; + + return (ptr & 0xF0000000) == ((ptr + size - 1) & 0xF0000000); +} + +static int ipq4019_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_pcm_hardware *pcm_hw = NULL; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + u8 *area; + dma_addr_t addr; + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + pcm_hw = &ipq4019_pcm_hardware_playback; + break; + case SNDRV_PCM_STREAM_CAPTURE: + pcm_hw = &ipq4019_pcm_hardware_capture; + break; + default: + dev_err(ss2dev(substream), "Invalid stream: %d\n", + substream->stream); + return -EINVAL; + } + + size = ip4019_dma_buffer_size(pcm_hw); + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + /* + * |<-- buffers -->|<-- desc -->| + * +----+----+----+----+----+----+-+-+-+-+-+-+-+ + * | | | | | | | | | | | | | | + * | | | | | | | | | | | | | | + * | | | | | | | | | | | | | | + * | | | | | | | | | | | | | | + * | | | | | | | | | | | | | | + * +----+----+----+----+----+----+-+-+-+-+-+-+-+ + * ^ ^ | | . . . + * | | | | + * +----|------------------------+ | + * +--------------------------+ + */ + + /* + * Currently payload uses uncached memory. + * TODO: Eventually we will move to cached memory for payload + * and dma_map_single() will be used for Invalidating/Flushing + * the buffers. + */ + + area = dma_alloc_coherent(pcm->card->dev, size, &addr, GFP_KERNEL); + + if (!area) { + dev_info(ss2dev(substream), "Alloc coherent memory failed\n"); + return -ENOMEM; + } + + if (!ipq4019_mbox_buf_is_aligned(area, size)) { + dev_info(ss2dev(substream), + "First allocation %p not within 256M region\n", area); + + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + /* + * If we are here, the previously allocated buffer is not + * usable for the driver. Have to free it anyway regardless + * of the success/failure of the second allocation. + */ + dma_free_coherent(pcm->card->dev, size, area, addr); + if (!buf->area) { + dev_info(ss2dev(substream), + "Second Alloc coherent memory failed\n"); + return -ENOMEM; + } + } else { + buf->area = area; + buf->addr = addr; + } + + buf->bytes = pcm_hw->buffer_bytes_max; + + return 0; +} + +static void ipq4019_pcm_free_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_hardware *pcm_hw = NULL; + struct snd_dma_buffer *buf; + size_t size; + + substream = pcm->streams[stream].substream; + buf = &substream->dma_buffer; + + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + pcm_hw = &ipq4019_pcm_hardware_playback; + break; + case SNDRV_PCM_STREAM_CAPTURE: + pcm_hw = &ipq4019_pcm_hardware_capture; + break; + default: + dev_err(ss2dev(substream), "Invalid stream: %d\n", + substream->stream); + return; + } + + size = ip4019_dma_buffer_size(pcm_hw); + + dma_free_coherent(pcm->card->dev, size, buf->area, buf->addr); + + buf->addr = 0; + buf->area = NULL; + +} + +static irqreturn_t ipq4019_pcm_irq(int intrsrc, void *data) +{ + struct snd_pcm_substream *substream = data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = runtime->private_data; + + if (pcm_rtpriv->mmap_flag) + pcm_rtpriv->curr_pos = + ipq4019_mbox_get_played_offset_set_own( + pcm_rtpriv->channel); + else + pcm_rtpriv->curr_pos = + ipq4019_mbox_get_played_offset(pcm_rtpriv->channel); + + snd_pcm_period_elapsed(substream); + + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t ipq4019_pcm_i2s_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = runtime->private_data; + + return bytes_to_frames(runtime, pcm_rtpriv->curr_pos); +} + +static int ipq4019_pcm_i2s_copy(struct snd_pcm_substream *substream, int chan, + snd_pcm_uframes_t hwoff, void __user *ubuf, + snd_pcm_uframes_t frames) +{ + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = runtime->private_data; + char *hwbuf; + u32 offset, size; + + offset = frames_to_bytes(runtime, hwoff); + size = frames_to_bytes(runtime, frames); + + hwbuf = buf->area + offset; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (copy_from_user(hwbuf, ubuf, size)) + return -EFAULT; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (copy_to_user(ubuf, hwbuf, size)) + return -EFAULT; + } + + ipq4019_mbox_desc_own(pcm_rtpriv->channel, offset / size, 1); + + ipq4019_mbox_dma_resume(pcm_rtpriv->channel); + + return 0; +} + +static int ipq4019_pcm_i2s_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = runtime->private_data; + + pcm_rtpriv->mmap_flag = 1; + + return dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); +} + +static int ipq4019_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + + +static int ipq4019_pcm_i2s_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = runtime->private_data; + u32 ret; + + ret = ipq4019_mbox_dma_prepare(pcm_rtpriv->channel); + if (ret) { + dev_err(ss2dev(substream), + "Error in dma prepare: channel: %d ret: %d\n", + pcm_rtpriv->channel, ret); + return ret; + } + + pcm_rtpriv->last_played = NULL; + + return 0; +} + +static int ipq4019_pcm_i2s_close(struct snd_pcm_substream *substream) +{ + struct ipq4019_pcm_rt_priv *pcm_rtpriv = + substream->runtime->private_data; + u32 ret; + + pcm_rtpriv->mmap_flag = 0; + + ret = ipq4019_mbox_dma_release(pcm_rtpriv->channel); + if (ret) + dev_err(ss2dev(substream), + "Error in dma release. ret: %d\n", ret); + + kfree(pcm_rtpriv); + + return 0; +} + +static int ipq4019_pcm_i2s_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = runtime->private_data; + u32 desc_duration; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + ret = ipq4019_mbox_dma_start(pcm_rtpriv->channel); + if (ret) { + dev_err(ss2dev(substream), + "Error in dma start. ret: %d\n", ret); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = ipq4019_mbox_dma_resume(pcm_rtpriv->channel); + if (ret) { + dev_err(ss2dev(substream), + "Error in dma resume. ret: %d\n", ret); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* + * For e.g. the number of bytes needed to represent 1 second + * worth of audio data for sampling frequency, bit width, stereo + * combination of 16KHz, 32-bits and stereo, the calculation is + * as follows + * + * For 1 second, + * 16KHz * 32 bits * 2 (left & right channel of stereo) + * = 16000 * 4 bytes * 2 + * = 128000 bytes + * + * Hence the duration will be + * desc_buffer_size_in_bytes / 128000 * 1 sec + */ + desc_duration = + frames_to_bytes(runtime, runtime->period_size) * 1000 / + (runtime->rate * + DIV_ROUND_UP(runtime->sample_bits, 8) * + runtime->channels); + + dev_dbg(ss2dev(substream), + "period_size:%u rate:%u sample_bits:%u channels:%u desc_delay:%u\n", + frames_to_bytes(runtime, runtime->period_size), + runtime->rate, runtime->sample_bits, runtime->channels, + desc_duration); + + ret = ipq4019_mbox_dma_stop(pcm_rtpriv->channel, desc_duration); + if (ret) { + dev_err(ss2dev(substream), + "Error in dma stop. ret: %d\n", ret); + } + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int ipq4019_pcm_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = runtime->private_data; + int ret; + unsigned int period_size, sample_size, sample_rate, frames, channels; + + ret = ipq4019_mbox_form_ring(pcm_rtpriv->channel, + substream->dma_buffer.addr, + substream->dma_buffer.area, + params_period_bytes(hw_params), + params_buffer_bytes(hw_params), + (substream->stream == SNDRV_PCM_STREAM_CAPTURE)); + if (ret) { + dev_dbg(ss2dev(substream), + "Error dma form ring ret: %d\n", ret); + return ret; + } + + period_size = params_period_bytes(hw_params); + sample_size = snd_pcm_format_size(params_format(hw_params), 1); + sample_rate = params_rate(hw_params); + channels = params_channels(hw_params); + frames = period_size / (sample_size * channels); + + pcm_rtpriv->period_size = params_period_bytes(hw_params); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + runtime->dma_bytes = params_buffer_bytes(hw_params); + return 0; +} + +static int ipq4019_pcm_i2s_open(struct snd_pcm_substream *substream) +{ + int ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + u32 intf = dai->driver->id; + + pcm_rtpriv = kmalloc(sizeof(struct ipq4019_pcm_rt_priv), GFP_KERNEL); + if (!pcm_rtpriv) + return -ENOMEM; + + dev_dbg(ss2dev(substream), "%s: 0x%xB allocated at 0x%08x\n", + __func__, sizeof(*pcm_rtpriv), (u32) pcm_rtpriv); + pcm_rtpriv->last_played = NULL; + pcm_rtpriv->dev = substream->pcm->card->dev; + pcm_rtpriv->channel = ipq4019_get_mbox_id(substream, intf); + pcm_rtpriv->curr_pos = 0; + pcm_rtpriv->mmap_flag = 0; + substream->runtime->private_data = pcm_rtpriv; + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + runtime->dma_bytes = + ipq4019_pcm_hardware_playback.buffer_bytes_max; + snd_soc_set_runtime_hwparams(substream, + &ipq4019_pcm_hardware_playback); + break; + case SNDRV_PCM_STREAM_CAPTURE: + runtime->dma_bytes = + ipq4019_pcm_hardware_capture.buffer_bytes_max; + snd_soc_set_runtime_hwparams(substream, + &ipq4019_pcm_hardware_capture); + break; + default: + dev_err(ss2dev(substream), "Invalid stream: %d\n", + substream->stream); + ret = -EINVAL; + goto error; + } + + ret = ipq4019_mbox_dma_init(pcm_rtpriv->dev, + pcm_rtpriv->channel, ipq4019_pcm_irq, substream); + if (ret) { + dev_err(ss2dev(substream), + "Error initializing dma. ret: %d\n", ret); + goto error; + } + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(ss2dev(substream), + "snd_pcm_hw_constraint_integer failed ret: %d\n", + ret); + goto error_hw_const; + } + + return 0; +error_hw_const: + ipq4019_mbox_dma_deinit(pcm_rtpriv->channel); +error: + kfree(pcm_rtpriv); + return ret; +} + +static struct snd_pcm_ops ipq4019_asoc_pcm_i2s_ops = { + .open = ipq4019_pcm_i2s_open, + .hw_params = ipq4019_pcm_i2s_hw_params, + .hw_free = ipq4019_pcm_hw_free, + .trigger = ipq4019_pcm_i2s_trigger, + .ioctl = snd_pcm_lib_ioctl, + .close = ipq4019_pcm_i2s_close, + .prepare = ipq4019_pcm_i2s_prepare, + .mmap = ipq4019_pcm_i2s_mmap, + .pointer = ipq4019_pcm_i2s_pointer, + .copy = ipq4019_pcm_i2s_copy, +}; + +static void ipq4019_asoc_pcm_i2s_free(struct snd_pcm *pcm) +{ + ipq4019_pcm_free_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); + ipq4019_pcm_free_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); +} + +static int ipq4019_asoc_pcm_i2s_new(struct snd_soc_pcm_runtime *prtd) +{ + struct snd_card *card = prtd->card->snd_card; + struct snd_pcm *pcm = prtd->pcm; + int ret = 0, pback = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &card->dev->coherent_dma_mask; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = ipq4019_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) { + dev_err(card->dev, + "Error allocating playback dma. ret: %d\n", + ret); + return -ENOMEM; + } + pback = 1; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = ipq4019_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) { + dev_err(card->dev, + "Error allocating capture dma buf. ret: %d\n", + ret); + if (pback) + ipq4019_pcm_free_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + return -ENOMEM; + } + } + + return ret; +} + +static struct snd_soc_platform_driver ipq4019_asoc_pcm_i2s_platform = { + .ops = &ipq4019_asoc_pcm_i2s_ops, + .pcm_new = ipq4019_asoc_pcm_i2s_new, + .pcm_free = ipq4019_asoc_pcm_i2s_free, +}; + +static const struct of_device_id ipq4019_pcm_i2s_id_table[] = { + { .compatible = "qca,ipq4019-pcm-i2s" }, + { .compatible = "qca,ipq4019-pcm-i2s1" }, + { .compatible = "qca,ipq4019-pcm-i2s2" }, + { /* Sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ipq4019_pcm_i2s_id_table); + +static int ipq4019_pcm_i2s_driver_probe(struct platform_device *pdev) +{ + int ret; + + ret = snd_soc_register_platform(&pdev->dev, + &ipq4019_asoc_pcm_i2s_platform); + if (ret) + dev_err(&pdev->dev, + "Failed to register i2s pcm device ret: %d\n", ret); + return ret; +} + +static int ipq4019_pcm_i2s_driver_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver ipq4019_pcm_i2s_driver = { + .probe = ipq4019_pcm_i2s_driver_probe, + .remove = ipq4019_pcm_i2s_driver_remove, + .driver = { + .name = "qca-pcm-i2s", + .owner = THIS_MODULE, + .of_match_table = ipq4019_pcm_i2s_id_table, + }, +}; + +module_platform_driver(ipq4019_pcm_i2s_driver); + +MODULE_ALIAS("platform:qca-pcm-i2s"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("IPQ4019 PCM I2S Platform Driver"); diff --git a/sound/soc/qcom/ipq4019/ipq4019-pcm-spdif.c b/sound/soc/qcom/ipq4019/ipq4019-pcm-spdif.c new file mode 100644 index 0000000..08dac4a --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-pcm-spdif.c @@ -0,0 +1,664 @@ +/* + * Copyright (c) 2015-2016 The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/control.h> +#include <sound/pcm_params.h> + +#include "ipq4019-pcm.h" +#include "ipq4019-adss.h" + +static struct snd_pcm_hardware ipq4019_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24_3, + .rates = RATE_16000_96000, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .buffer_bytes_max = IPQ4019_I2S_BUFF_SIZE, + .period_bytes_max = IPQ4019_I2S_BUFF_SIZE / 2, + .period_bytes_min = IPQ4019_I2S_PERIOD_BYTES_MIN, + .periods_min = IPQ4019_I2S_NO_OF_PERIODS, + .periods_max = IPQ4019_I2S_NO_OF_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware ipq4019_pcm_hardware_capture = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24_3, + .rates = RATE_16000_96000, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + .channels_min = CH_STEREO, + .channels_max = CH_STEREO, + .buffer_bytes_max = IPQ4019_I2S_BUFF_SIZE, + .period_bytes_max = IPQ4019_I2S_BUFF_SIZE / 2, + .period_bytes_min = IPQ4019_I2S_PERIOD_BYTES_MIN, + .periods_min = IPQ4019_I2S_NO_OF_PERIODS, + .periods_max = IPQ4019_I2S_NO_OF_PERIODS, + .fifo_size = 0, +}; + +static size_t ip4019_dma_buffer_size(struct snd_pcm_hardware *pcm_hw) +{ + return pcm_hw->buffer_bytes_max + + (pcm_hw->periods_min * sizeof(struct ipq4019_mbox_desc)); +} + +/* + * The MBOX descriptors and buffers should lie within the same 256MB + * region. Because, the buffer address pointer (in the descriptor structure) + * and descriptor base address pointer register share the same MSB 4 bits + * which is configured in MBOX DMA Policy register. + * + * Hence ensure that the entire allocated region falls in a 256MB region. + */ +static int ipq4019_mbox_buf_is_aligned(void *c_ptr, ssize_t size) +{ + u32 ptr = (u32)c_ptr; + + return (ptr & 0xF0000000) == ((ptr + size - 1) & 0xF0000000); +} + +static struct device *ss2dev(struct snd_pcm_substream *substream) +{ + return substream->pcm->card->dev; +} + +static int ipq4019_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct snd_pcm_hardware *pcm_hw = NULL; + size_t size; + u8 *area; + dma_addr_t addr; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pcm_hw = &ipq4019_pcm_hardware_playback; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_hw = &ipq4019_pcm_hardware_capture; + else + return -EINVAL; + + size = ip4019_dma_buffer_size(pcm_hw); + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + area = dma_alloc_coherent(pcm->card->dev, size, &addr, GFP_KERNEL); + if (!area) { + dev_info(ss2dev(substream), "Alloc coherent memory failed\n"); + return -ENOMEM; + } + + if (!ipq4019_mbox_buf_is_aligned(area, size)) { + dev_info(ss2dev(substream), + "First allocation %p not within 256M region\n", area); + + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + /* + * If we are here, the previously allocated buffer is not + * usable for the driver. Have to free it anyway regardless + * of the success/failure of the second allocation. + */ + dma_free_coherent(pcm->card->dev, size, area, addr); + if (!buf->area) { + dev_info(ss2dev(substream), + "Second Alloc coherent memory failed\n"); + return -ENOMEM; + } + } else { + buf->area = area; + buf->addr = addr; + } + + buf->bytes = pcm_hw->buffer_bytes_max; + + return 0; +} + +static void ipq4019_pcm_free_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_hardware *pcm_hw = NULL; + struct snd_dma_buffer *buf; + size_t size; + + substream = pcm->streams[stream].substream; + buf = &substream->dma_buffer; + + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + pcm_hw = &ipq4019_pcm_hardware_playback; + break; + case SNDRV_PCM_STREAM_CAPTURE: + pcm_hw = &ipq4019_pcm_hardware_capture; + break; + } + + size = ip4019_dma_buffer_size(pcm_hw); + + dma_free_coherent(pcm->card->dev, size, buf->area, buf->addr); + + buf->area = NULL; +} + +static irqreturn_t ipq4019_pcm_irq(int intrsrc, void *data) +{ + uint32_t processed_size; + int offset; + uint32_t *ptr; + + struct snd_pcm_substream *substream = data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = + (struct ipq4019_pcm_rt_priv *)runtime->private_data; + + /* Store the last played buffer in the runtime priv struct */ + pcm_rtpriv->last_played = + ipq4019_mbox_get_last_played(pcm_rtpriv->channel); + + /* Set the OWN bits */ + processed_size = ipq4019_mbox_get_elapsed_size(pcm_rtpriv->channel); + pcm_rtpriv->processed_size = processed_size; + + if (processed_size > pcm_rtpriv->period_size) + snd_printd("Processed more than one period bytes : %d\n", + processed_size); + + /* Need to extract the data part alone in case of Rx */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (pcm_rtpriv->last_played == NULL) + offset = 0; + else + offset = (pcm_rtpriv->last_played->BufPtr - + (runtime->dma_addr & 0xFFFFFFF)); + + if (offset > 0) { + ptr = (uint32_t *)((char *)runtime->dma_area + offset - + processed_size); + + if (ptr < (uint32_t *)runtime->dma_area) + goto ack; + } + } + + snd_pcm_period_elapsed(substream); + + if (pcm_rtpriv->last_played == NULL) { + snd_printd("BUG: ISR called but no played buf found\n"); + goto ack; + } + +ack: + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t ipq4019_pcm_spdif_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + snd_pcm_uframes_t ret; + + pcm_rtpriv = runtime->private_data; + + if (pcm_rtpriv->last_played == NULL) + ret = 0; + else + ret = (pcm_rtpriv->last_played->BufPtr - + (runtime->dma_addr & 0xFFFFFFF)); + ret = bytes_to_frames(runtime, ret); + return ret; +} + +static int ipq4019_pcm_spdif_copy(struct snd_pcm_substream *substream, int chan, + snd_pcm_uframes_t hwoff, void __user *ubuf, + snd_pcm_uframes_t frames) +{ + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = runtime->private_data; + char *hwbuf; + u32 offset, size; + + offset = frames_to_bytes(runtime, hwoff); + size = frames_to_bytes(runtime, frames); + + hwbuf = buf->area + offset; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (copy_from_user(hwbuf, ubuf, size)) + return -EFAULT; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (copy_to_user(ubuf, hwbuf, size)) + return -EFAULT; + } + + ipq4019_mbox_desc_own(pcm_rtpriv->channel, offset / size, 1); + + ipq4019_mbox_dma_resume(pcm_rtpriv->channel); + + return 0; +} + +static int ipq4019_pcm_spdif_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); +} + +static int ipq4019_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + + +static int ipq4019_pcm_spdif_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + + uint32_t ret; + + pcm_rtpriv = runtime->private_data; + + ret = ipq4019_mbox_dma_prepare(pcm_rtpriv->channel); + if (ret) { + pr_err("%s: %d: Error in dma prepare : channel : %d\n", + __func__, __LINE__, pcm_rtpriv->channel); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + return ret; + } + + /* Set to swap the words */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = ipq4019_mbox_dma_swap(pcm_rtpriv->channel, + runtime->format); + if (ret) { + pr_err("%s: %d: Error in dma swap : channel : %d\n", + __func__, __LINE__, pcm_rtpriv->channel); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + return ret; + } + + /* SWAP at PCM level for 24 bit samples */ + if ((substream->runtime->format == SNDRV_PCM_FORMAT_S24_3LE) || + (substream->runtime->format == SNDRV_PCM_FORMAT_S24_3BE)) + ipq4019_stereo_spdif_pcmswap(ENABLE, + ipq4019_get_stereo_id(substream, SPDIF)); + } + + /* Set the ownership bits */ + ipq4019_mbox_get_elapsed_size(pcm_rtpriv->channel); + + pcm_rtpriv->last_played = NULL; + + return ret; +} + +static int ipq4019_pcm_spdif_close(struct snd_pcm_substream *substream) +{ + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + uint32_t ret; + + pcm_rtpriv = substream->runtime->private_data; + ret = ipq4019_mbox_dma_release(pcm_rtpriv->channel); + if (ret) { + pr_err("%s: %d: Error in dma release\n", + __func__, __LINE__); + } + + /* Reset the swap */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = ipq4019_mbox_dma_reset_swap(pcm_rtpriv->channel); + if (ret) { + pr_err("%s: %d: Error in dma release\n", + __func__, __LINE__); + } + + if ((substream->runtime->format == SNDRV_PCM_FORMAT_S24_3LE) || + (substream->runtime->format == SNDRV_PCM_FORMAT_S24_3BE)) + ipq4019_stereo_spdif_pcmswap(DISABLE, + ipq4019_get_stereo_id(substream, SPDIF)); + } + + kfree(pcm_rtpriv); + + return ret; +} + +static int ipq4019_pcm_spdif_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + int ret; + u32 desc_duration; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = + substream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + /* Enable the SPDIF Stereo block for operation */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ipq4019_stereo_spdif_enable(ENABLE, + ipq4019_get_stereo_id(substream, + SPDIF)); + else + ipq4019_spdifin_ctrl_spdif_en(ENABLE); + + ret = ipq4019_mbox_dma_start(pcm_rtpriv->channel); + if (ret) { + pr_err("%s: %d: Error in dma start\n", + __func__, __LINE__); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = ipq4019_mbox_dma_resume(pcm_rtpriv->channel); + if (ret) { + pr_err("%s: %d: Error in dma resume\n", + __func__, __LINE__); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + /* Disable the SPDIF Stereo block */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ipq4019_stereo_spdif_enable(DISABLE, + ipq4019_get_stereo_id(substream, + SPDIF)); + else + ipq4019_spdifin_ctrl_spdif_en(DISABLE); + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* + * For e.g. the number of bytes needed to represent 1 second + * worth of audio data for sampling frequency, bit width, stereo + * combination of 16KHz, 32-bits and stereo, the calculation is + * as follows + * + * For 1 second, + * 16KHz * 32 bits * 2 (left & right channel of stereo) + * = 16000 * 4 bytes * 2 + * = 128000 bytes + * + * Hence the duration will be + * desc_buffer_size_in_bytes / 128000 * 1 sec + */ + desc_duration = + frames_to_bytes(runtime, runtime->period_size) * 1000 / + (runtime->rate * + DIV_ROUND_UP(runtime->sample_bits, 8) * + runtime->channels); + + ret = ipq4019_mbox_dma_stop(pcm_rtpriv->channel, desc_duration); + if (ret) { + pr_err("%s: %d: Error in dma stop\n", + __func__, __LINE__); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + } + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int ipq4019_pcm_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + int ret; + unsigned int period_size, sample_size, sample_rate, frames, channels; + + pr_debug("%s %d\n", __func__, __LINE__); + + pcm_rtpriv = runtime->private_data; + ret = ipq4019_mbox_form_ring(pcm_rtpriv->channel, + substream->dma_buffer.addr, + substream->dma_buffer.area, + params_period_bytes(hw_params), + params_buffer_bytes(hw_params), + (substream->stream == SNDRV_PCM_STREAM_CAPTURE)); + if (ret) { + pr_err("%s: %d: Error dma form ring\n", __func__, __LINE__); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + return ret; + } + + period_size = params_period_bytes(hw_params); + sample_size = snd_pcm_format_size(params_format(hw_params), 1); + sample_rate = params_rate(hw_params); + channels = params_channels(hw_params); + frames = period_size / (sample_size * channels); + + pcm_rtpriv->period_size = params_period_bytes(hw_params); + + /* Check whether this is a compressed play or not + * if its a compressed play set VUC + */ + if (hw_params->reserved[0]) + ipq4019_mbox_vuc_setup(pcm_rtpriv->channel); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + runtime->dma_bytes = params_buffer_bytes(hw_params); + return ret; +} + +static int ipq4019_pcm_spdif_open(struct snd_pcm_substream *substream) +{ + int ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + + pr_debug("%s %d\n", __func__, __LINE__); + + pcm_rtpriv = kmalloc(sizeof(struct ipq4019_pcm_rt_priv), GFP_KERNEL); + if (!pcm_rtpriv) + return -ENOMEM; + + snd_printd("%s: 0x%xB allocated at 0x%08x\n", + __func__, sizeof(*pcm_rtpriv), (u32) pcm_rtpriv); + pcm_rtpriv->last_played = NULL; + pcm_rtpriv->dev = substream->pcm->card->dev; + pcm_rtpriv->channel = ipq4019_get_mbox_id(substream, SPDIF); + pcm_rtpriv->curr_pos = 0; + pcm_rtpriv->mmap_flag = 0; + substream->runtime->private_data = pcm_rtpriv; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->dma_bytes = + ipq4019_pcm_hardware_playback.buffer_bytes_max; + snd_soc_set_runtime_hwparams(substream, + &ipq4019_pcm_hardware_playback); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->dma_bytes = + ipq4019_pcm_hardware_capture.buffer_bytes_max; + snd_soc_set_runtime_hwparams(substream, + &ipq4019_pcm_hardware_capture); + + } else { + pr_err("%s: Invalid stream\n", __func__); + ret = -EINVAL; + goto error; + } + ret = ipq4019_mbox_dma_init(pcm_rtpriv->dev, + pcm_rtpriv->channel, ipq4019_pcm_irq, substream); + if (ret) { + pr_err("%s: %d: Error initializing dma\n", + __func__, __LINE__); + goto error; + } + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + pr_err("%s: snd_pcm_hw_constraint_integer failed\n", __func__); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + goto error; + } + + return 0; +error: + kfree(pcm_rtpriv); + return ret; +} + +static struct snd_pcm_ops ipq4019_asoc_pcm_spdif_ops = { + .open = ipq4019_pcm_spdif_open, + .hw_params = ipq4019_pcm_spdif_hw_params, + .hw_free = ipq4019_pcm_hw_free, + .trigger = ipq4019_pcm_spdif_trigger, + .ioctl = snd_pcm_lib_ioctl, + .close = ipq4019_pcm_spdif_close, + .prepare = ipq4019_pcm_spdif_prepare, + .mmap = ipq4019_pcm_spdif_mmap, + .pointer = ipq4019_pcm_spdif_pointer, + .copy = ipq4019_pcm_spdif_copy, +}; + +static void ipq4019_asoc_pcm_spdif_free(struct snd_pcm *pcm) +{ + ipq4019_pcm_free_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); + ipq4019_pcm_free_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); +} + +static int ipq4019_asoc_pcm_spdif_new(struct snd_soc_pcm_runtime *prtd) +{ + struct snd_card *card = prtd->card->snd_card; + struct snd_pcm *pcm = prtd->pcm; + + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &card->dev->coherent_dma_mask; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = ipq4019_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + + if (ret) { + pr_err("%s: %d: Error allocating dma buf\n", + __func__, __LINE__); + return -ENOMEM; + } + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = ipq4019_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) { + pr_err("%s: %d: Error allocating dma buf\n", + __func__, __LINE__); + ipq4019_pcm_free_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + return -ENOMEM; + } + } + + return ret; +} + +static struct snd_soc_platform_driver ipq4019_asoc_pcm_spdif_platform = { + .ops = &ipq4019_asoc_pcm_spdif_ops, + .pcm_new = ipq4019_asoc_pcm_spdif_new, + .pcm_free = ipq4019_asoc_pcm_spdif_free, +}; + +static const struct of_device_id ipq4019_pcm_spdif_id_table[] = { + { .compatible = "qca,ipq4019-pcm-spdif" }, + { /* Sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ipq4019_pcm_spdif_id_table); + +static int ipq4019_pcm_spdif_driver_probe(struct platform_device *pdev) +{ + int ret; + + pr_debug("%s %d\n", __func__, __LINE__); + ret = snd_soc_register_platform(&pdev->dev, + &ipq4019_asoc_pcm_spdif_platform); + if (ret) + dev_err(&pdev->dev, "%s: Failed to register spdif pcm device\n", + __func__); + return ret; +} + +static int ipq4019_pcm_spdif_driver_remove(struct platform_device *pdev) +{ + pr_debug("%s %d\n", __func__, __LINE__); + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver ipq4019_pcm_spdif_driver = { + .probe = ipq4019_pcm_spdif_driver_probe, + .remove = ipq4019_pcm_spdif_driver_remove, + .driver = { + .name = "qca-pcm-spdif", + .owner = THIS_MODULE, + .of_match_table = ipq4019_pcm_spdif_id_table, + }, +}; + +module_platform_driver(ipq4019_pcm_spdif_driver); + +MODULE_ALIAS("platform:qca-pcm-spdif"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("IPQ4019 PCM SPDIF Platform Driver"); diff --git a/sound/soc/qcom/ipq4019/ipq4019-pcm-tdm.c b/sound/soc/qcom/ipq4019/ipq4019-pcm-tdm.c new file mode 100644 index 0000000..657a4a6 --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-pcm-tdm.c @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/control.h> +#include <sound/pcm_params.h> + +#include "ipq4019-pcm.h" +#include "ipq4019-adss.h" + +static struct snd_pcm_hardware ipq4019_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = RATE_16000_96000, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + .channels_min = CH_STEREO, + .channels_max = CH_7_1, + .buffer_bytes_max = IPQ4019_TDM_BUFF_SIZE, + .period_bytes_max = IPQ4019_TDM_BUFF_SIZE / 2, + .period_bytes_min = IPQ4019_TDM_PERIOD_BYTES_MIN, + .periods_min = IPQ4019_TDM_NO_OF_PERIODS, + .periods_max = IPQ4019_TDM_NO_OF_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware ipq4019_pcm_hardware_capture = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = RATE_16000_96000, + .rate_min = FREQ_16000, + .rate_max = FREQ_96000, + .channels_min = CH_STEREO, + .channels_max = CH_7_1, + .buffer_bytes_max = IPQ4019_TDM_BUFF_SIZE, + .period_bytes_max = IPQ4019_TDM_BUFF_SIZE / 2, + .period_bytes_min = IPQ4019_TDM_PERIOD_BYTES_MIN, + .periods_min = IPQ4019_TDM_NO_OF_PERIODS, + .periods_max = IPQ4019_TDM_NO_OF_PERIODS, + .fifo_size = 0, +}; + +static size_t ip4019_dma_buffer_size(struct snd_pcm_hardware *pcm_hw) +{ + return (pcm_hw->buffer_bytes_max + + (pcm_hw->periods_min * sizeof(struct ipq4019_mbox_desc))); +} + +static struct device *ss2dev(struct snd_pcm_substream *substream) +{ + return substream->pcm->card->dev; +} + +/* + * The MBOX descriptors and buffers should lie within the same 256MB + * region. Because, the buffer address pointer (in the descriptor structure) + * and descriptor base address pointer register share the same MSB 4 bits + * which is configured in MBOX DMA Policy register. + * + * Hence ensure that the entire allocated region falls in a 256MB region. + */ +static int ipq4019_mbox_buf_is_aligned(void *c_ptr, ssize_t size) +{ + u32 ptr = (u32)c_ptr; + + return (ptr & 0xF0000000) == ((ptr + size - 1) & 0xF0000000); +} + +static int ipq4019_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_pcm_hardware *pcm_hw = NULL; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + u8 *area; + dma_addr_t addr; + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + pcm_hw = &ipq4019_pcm_hardware_playback; + break; + case SNDRV_PCM_STREAM_CAPTURE: + pcm_hw = &ipq4019_pcm_hardware_capture; + break; + default: + dev_err(ss2dev(substream), "Invalid stream: %d\n", + substream->stream); + return -EINVAL; + } + + size = ip4019_dma_buffer_size(pcm_hw); + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + /* + * |<-- buffers -->|<-- desc -->| + * +----+----+----+----+----+----+-+-+-+-+-+-+-+ + * | | | | | | | | | | | | | | + * | | | | | | | | | | | | | | + * | | | | | | | | | | | | | | + * | | | | | | | | | | | | | | + * | | | | | | | | | | | | | | + * +----+----+----+----+----+----+-+-+-+-+-+-+-+ + * ^ ^ | | . . . + * | | | | + * +----|------------------------+ | + * +--------------------------+ + */ + + /* + * Currently payload uses uncached memory. + * TODO: Eventually we will move to cached memory for payload + * and dma_map_single() will be used for Invalidating/Flushing + * the buffers. + */ + + area = dma_alloc_coherent(pcm->card->dev, size, &addr, GFP_KERNEL); + + if (!area) { + dev_info(ss2dev(substream), "Alloc coherent memory failed\n"); + return -ENOMEM; + } + + if (!ipq4019_mbox_buf_is_aligned(area, size)) { + dev_info(ss2dev(substream), + "First allocation %p not within 256M region\n", area); + + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + /* + * If we are here, the previously allocated buffer is not + * usable for the driver. Have to free it anyway regardless + * of the success/failure of the second allocation. + */ + dma_free_coherent(pcm->card->dev, size, area, addr); + if (!buf->area) { + dev_info(ss2dev(substream), + "Second Alloc coherent memory failed\n"); + return -ENOMEM; + } + } else { + buf->area = area; + buf->addr = addr; + } + + buf->bytes = pcm_hw->buffer_bytes_max; + + return 0; +} + +static void ipq4019_pcm_free_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_hardware *pcm_hw = NULL; + struct snd_dma_buffer *buf; + size_t size; + + substream = pcm->streams[stream].substream; + buf = &substream->dma_buffer; + + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + pcm_hw = &ipq4019_pcm_hardware_playback; + break; + case SNDRV_PCM_STREAM_CAPTURE: + pcm_hw = &ipq4019_pcm_hardware_capture; + break; + } + + size = ip4019_dma_buffer_size(pcm_hw); + + dma_free_coherent(pcm->card->dev, size, buf->area, buf->addr); + + buf->addr = 0; + buf->area = NULL; +} + +static irqreturn_t ipq4019_pcm_irq(int intrsrc, void *data) +{ + struct snd_pcm_substream *substream = data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = + (struct ipq4019_pcm_rt_priv *)runtime->private_data; + + pcm_rtpriv->curr_pos = + ipq4019_mbox_get_played_offset(pcm_rtpriv->channel); + + snd_pcm_period_elapsed(substream); + + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t ipq4019_pcm_tdm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + snd_pcm_uframes_t ret; + + pcm_rtpriv = runtime->private_data; + + ret = bytes_to_frames(runtime, pcm_rtpriv->curr_pos); + return ret; +} + +static int ipq4019_pcm_tdm_copy(struct snd_pcm_substream *substream, int chan, + snd_pcm_uframes_t hwoff, void __user *ubuf, + snd_pcm_uframes_t frames) +{ + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = runtime->private_data; + char *hwbuf; + u32 offset, size; + + offset = frames_to_bytes(runtime, hwoff); + size = frames_to_bytes(runtime, frames); + + hwbuf = buf->area + offset; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (copy_from_user(hwbuf, ubuf, size)) + return -EFAULT; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (copy_to_user(ubuf, hwbuf, size)) + return -EFAULT; + } + + ipq4019_mbox_desc_own(pcm_rtpriv->channel, offset / size, 1); + + ipq4019_mbox_dma_resume(pcm_rtpriv->channel); + + return 0; +} + +static int ipq4019_pcm_tdm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); +} + +static int ipq4019_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + + +static int ipq4019_pcm_tdm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + + uint32_t ret; + + pcm_rtpriv = runtime->private_data; + ret = ipq4019_mbox_dma_prepare(pcm_rtpriv->channel); + if (ret) { + pr_err("%s: %d: Error in dma prepare : channel : %d\n", + __func__, __LINE__, pcm_rtpriv->channel); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + return ret; + } + + /* Set the ownership bits */ + ipq4019_mbox_get_elapsed_size(pcm_rtpriv->channel); + + pcm_rtpriv->last_played = NULL; + + return ret; +} + +static int ipq4019_pcm_tdm_close(struct snd_pcm_substream *substream) +{ + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + u32 intf = dai->driver->id; + uint32_t ret; + + ipq4019_stereo_config_enable(DISABLE, + ipq4019_get_stereo_id(substream, intf)); + + pcm_rtpriv = substream->runtime->private_data; + if (!pcm_rtpriv) + return -EINVAL; + + ret = ipq4019_mbox_dma_release(pcm_rtpriv->channel); + if (ret) { + pr_err("%s: %d: Error in dma release\n", + __func__, __LINE__); + } + + kfree(pcm_rtpriv); + + return 0; +} + +static int ipq4019_pcm_tdm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret; + u32 desc_duration; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv = + substream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + + ret = ipq4019_mbox_dma_start(pcm_rtpriv->channel); + if (ret) { + pr_err("%s: %d: Error in dma start\n", + __func__, __LINE__); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = ipq4019_mbox_dma_resume(pcm_rtpriv->channel); + if (ret) { + pr_err("%s: %d: Error in dma resume\n", + __func__, __LINE__); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* + * For e.g. the number of bytes needed to represent 1 second + * worth of audio data for sampling frequency, bit width, stereo + * combination of 16KHz, 32-bits and stereo, the calculation is + * as follows + * + * For 1 second, + * 16KHz * 32 bits * 2 (left & right channel of stereo) + * = 16000 * 4 bytes * 2 + * = 128000 bytes + * + * Hence the duration will be + * desc_buffer_size_in_bytes / 128000 * 1 sec + */ + desc_duration = + frames_to_bytes(runtime, runtime->period_size) * 1000 / + (runtime->rate * + DIV_ROUND_UP(runtime->sample_bits, 8) * + runtime->channels); + + ret = ipq4019_mbox_dma_stop(pcm_rtpriv->channel, desc_duration); + if (ret) { + pr_err("%s: %d: Error in dma stop\n", + __func__, __LINE__); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + } + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int ipq4019_pcm_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + int ret; + unsigned int period_size, sample_size, sample_rate, frames, channels; + + pr_debug("%s %d\n", __func__, __LINE__); + + pcm_rtpriv = runtime->private_data; + + ret = ipq4019_mbox_form_ring(pcm_rtpriv->channel, + substream->dma_buffer.addr, + substream->dma_buffer.area, + params_period_bytes(hw_params), + params_buffer_bytes(hw_params), + (substream->stream == SNDRV_PCM_STREAM_CAPTURE)); + if (ret) { + pr_err("%s: %d: Error dma form ring\n", __func__, __LINE__); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + return ret; + } + + period_size = params_period_bytes(hw_params); + sample_size = snd_pcm_format_size(params_format(hw_params), 1); + sample_rate = params_rate(hw_params); + channels = params_channels(hw_params); + frames = period_size / (sample_size * channels); + + pcm_rtpriv->period_size = params_period_bytes(hw_params); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + runtime->dma_bytes = params_buffer_bytes(hw_params); + return ret; +} + +static int ipq4019_pcm_tdm_open(struct snd_pcm_substream *substream) +{ + int ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ipq4019_pcm_rt_priv *pcm_rtpriv; + + pr_debug("%s %d\n", __func__, __LINE__); + + pcm_rtpriv = kmalloc(sizeof(struct ipq4019_pcm_rt_priv), GFP_KERNEL); + + if (!pcm_rtpriv) + return -ENOMEM; + + snd_printd("%s: 0x%xB allocated at 0x%08x\n", + __func__, sizeof(*pcm_rtpriv), (u32) pcm_rtpriv); + pcm_rtpriv->last_played = NULL; + pcm_rtpriv->dev = substream->pcm->card->dev; + pcm_rtpriv->channel = ipq4019_get_mbox_id(substream, TDM); + pcm_rtpriv->curr_pos = 0; + pcm_rtpriv->mmap_flag = 0; + substream->runtime->private_data = pcm_rtpriv; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->dma_bytes = + ipq4019_pcm_hardware_playback.buffer_bytes_max; + snd_soc_set_runtime_hwparams(substream, + &ipq4019_pcm_hardware_playback); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->dma_bytes = + ipq4019_pcm_hardware_capture.buffer_bytes_max; + snd_soc_set_runtime_hwparams(substream, + &ipq4019_pcm_hardware_capture); + + } else { + pr_err("%s: Invalid stream\n", __func__); + ret = -EINVAL; + goto error; + } + ret = ipq4019_mbox_dma_init(pcm_rtpriv->dev, + pcm_rtpriv->channel, ipq4019_pcm_irq, substream); + if (ret) { + pr_err("%s: %d: Error initializing dma\n", + __func__, __LINE__); + goto error; + } + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + pr_err("%s: snd_pcm_hw_constraint_integer failed\n", __func__); + ipq4019_mbox_dma_release(pcm_rtpriv->channel); + goto error; + } + + return 0; +error: + kfree(pcm_rtpriv); + return ret; +} + +static struct snd_pcm_ops ipq4019_asoc_pcm_tdm_ops = { + .open = ipq4019_pcm_tdm_open, + .hw_params = ipq4019_pcm_tdm_hw_params, + .hw_free = ipq4019_pcm_hw_free, + .trigger = ipq4019_pcm_tdm_trigger, + .ioctl = snd_pcm_lib_ioctl, + .close = ipq4019_pcm_tdm_close, + .prepare = ipq4019_pcm_tdm_prepare, + .mmap = ipq4019_pcm_tdm_mmap, + .pointer = ipq4019_pcm_tdm_pointer, + .copy = ipq4019_pcm_tdm_copy, +}; + +static void ipq4019_asoc_pcm_tdm_free(struct snd_pcm *pcm) +{ + ipq4019_pcm_free_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); + ipq4019_pcm_free_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); +} + +static int ipq4019_asoc_pcm_tdm_new(struct snd_soc_pcm_runtime *prtd) +{ + struct snd_card *card = prtd->card->snd_card; + struct snd_pcm *pcm = prtd->pcm; + + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &card->dev->coherent_dma_mask; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = ipq4019_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + + if (ret) { + pr_err("%s: %d: Error allocating dma buf\n", + __func__, __LINE__); + return -ENOMEM; + } + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = ipq4019_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) { + pr_err("%s: %d: Error allocating dma buf\n", + __func__, __LINE__); + ipq4019_pcm_free_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + return -ENOMEM; + } + } + + return ret; +} + +static struct snd_soc_platform_driver ipq4019_asoc_pcm_tdm_platform = { + .ops = &ipq4019_asoc_pcm_tdm_ops, + .pcm_new = ipq4019_asoc_pcm_tdm_new, + .pcm_free = ipq4019_asoc_pcm_tdm_free, +}; + +static const struct of_device_id ipq4019_pcm_tdm_id_table[] = { + { .compatible = "qca,ipq4019-pcm-tdm" }, + { /* Sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ipq4019_pcm_tdm_id_table); + +static int ipq4019_pcm_tdm_driver_probe(struct platform_device *pdev) +{ + int ret; + + pr_debug("%s %d\n", __func__, __LINE__); + ret = snd_soc_register_platform(&pdev->dev, + &ipq4019_asoc_pcm_tdm_platform); + if (ret) + dev_err(&pdev->dev, "%s: Failed to register tdm pcm device\n", + __func__); + return ret; +} + +static int ipq4019_pcm_tdm_driver_remove(struct platform_device *pdev) +{ + pr_debug("%s %d\n", __func__, __LINE__); + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver ipq4019_pcm_tdm_driver = { + .probe = ipq4019_pcm_tdm_driver_probe, + .remove = ipq4019_pcm_tdm_driver_remove, + .driver = { + .name = "qca-pcm-tdm", + .owner = THIS_MODULE, + .of_match_table = ipq4019_pcm_tdm_id_table, + }, +}; + +module_platform_driver(ipq4019_pcm_tdm_driver); + +MODULE_ALIAS("platform:qca-pcm-tdm"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("IPQ4019 PCM TDM Platform Driver"); diff --git a/sound/soc/qcom/ipq4019/ipq4019-pcm.h b/sound/soc/qcom/ipq4019/ipq4019-pcm.h new file mode 100644 index 0000000..0c467d6 --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-pcm.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _IPQ40XX_PCM_H_ +#define _IPQ40XX_PCM_H_ + +#include <linux/sound.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> + +#include "ipq4019-mbox.h" + +struct ipq4019_pcm_rt_priv { + int channel; + struct device *dev; + struct ipq4019_mbox_desc *last_played; + unsigned int processed_size; + uint32_t period_size; + uint32_t curr_pos; + int mmap_flag; +}; + +#endif /* _IPQ40XX_PCM_H_ */ diff --git a/sound/soc/qcom/ipq4019/ipq4019-stereo.c b/sound/soc/qcom/ipq4019/ipq4019-stereo.c new file mode 100644 index 0000000..52e0c29 --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019-stereo.c @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <linux/delay.h> +#include <linux/spinlock.h> + +#include "ipq4019-adss.h" + +struct stereo_priv_data { + void __iomem *stereo_base; + spinlock_t stereo_lock; +}; + +static struct stereo_priv_data stereo_priv[MAX_STEREO_ENTRIES]; + +/* + * + * Stereo buffers and I2S state reset + */ +void ipq4019_stereo_config_reset(u32 reset, u32 stereo_id) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&stereo_priv[stereo_id].stereo_lock, flags); + cfg = readl(stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + cfg &= ~STEREOn_CONFIG_RESET; + if (reset) + cfg |= STEREOn_CONFIG_RESET; + writel(cfg, stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + spin_unlock_irqrestore(&stereo_priv[stereo_id].stereo_lock, flags); +} +EXPORT_SYMBOL(ipq4019_stereo_config_reset); + +/* + * MIC buffers reset + */ +void ipq4019_stereo_config_mic_reset(u32 reset, u32 stereo_id) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&stereo_priv[stereo_id].stereo_lock, flags); + cfg = readl(stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + cfg &= ~STEREOn_CONFIG_MIC_RESET; + if (reset) + cfg |= STEREOn_CONFIG_MIC_RESET; + writel(cfg, stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + spin_unlock_irqrestore(&stereo_priv[stereo_id].stereo_lock, flags); +} +EXPORT_SYMBOL(ipq4019_stereo_config_mic_reset); + +/* + * Enable the I2S Stereo block for operation + */ +void ipq4019_stereo_config_enable(u32 enable, u32 stereo_id) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&stereo_priv[stereo_id].stereo_lock, flags); + cfg = readl(stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + cfg &= ~STEREOn_CONFIG_ENABLE; + if (enable) + cfg |= STEREOn_CONFIG_ENABLE; + writel(cfg, stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + spin_unlock_irqrestore(&stereo_priv[stereo_id].stereo_lock, flags); +} +EXPORT_SYMBOL(ipq4019_stereo_config_enable); + +/* + * Enable the SPDIF Stereo block for operation + */ +void ipq4019_stereo_spdif_enable(uint32_t enable, uint32_t stereo_id) +{ + uint32_t cfg; + + cfg = readl(stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + cfg &= ~(STEREOn_CONFIG_SPDIF_ENABLE); + if (enable) + cfg |= STEREOn_CONFIG_SPDIF_ENABLE; + writel(cfg, stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); +} +EXPORT_SYMBOL(ipq4019_stereo_spdif_enable); + +/* + * Enable/disable the swap within PCM sample + */ +void ipq4019_stereo_spdif_pcmswap(uint32_t enable, uint32_t stereo_id) +{ + uint32_t cfg; + + cfg = readl(stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + + cfg &= ~(STEREOn_CONFIG_PCM_SWAP); + if (enable) + cfg |= STEREOn_CONFIG_PCM_SWAP; + + writel(cfg, stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); +} +EXPORT_SYMBOL(ipq4019_stereo_spdif_pcmswap); + +/* Configure + * Data word size : Word size loaded into the PCM + * register from the MBOX FIFO. + * I2S word size : Word size sent to the external I2S DAC. + * When set to 32 bit words the PCM data + * will be left justified in the I2S word. + */ +int ipq4019_cfg_bit_width(u32 bit_width, u32 stereo_id) +{ + u32 cfg, mask = 0; + unsigned long flags; + + switch (bit_width) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: + mask |= (STEREOn_CONFIG_DATA_WORD_SIZE(1) | + STEREOn_CONFIG_I2S_WORD_SIZE_16 | + STEREOn_CONFIG_MIC_WORD_SIZE_16); + break; + case SNDRV_PCM_FORMAT_S24_3LE: + case SNDRV_PCM_FORMAT_S24_3BE: + mask |= (STEREOn_CONFIG_DATA_WORD_SIZE(2) | + STEREOn_CONFIG_I2S_WORD_SIZE_32 | + STEREOn_CONFIG_MIC_WORD_SIZE_16); + break; + case SNDRV_PCM_FORMAT_S32_LE: + case SNDRV_PCM_FORMAT_S32_BE: + mask |= (STEREOn_CONFIG_DATA_WORD_SIZE(3) | + STEREOn_CONFIG_I2S_WORD_SIZE_32 | + STEREOn_CONFIG_MIC_WORD_SIZE_32); + break; + default: + return -ENOTSUPP; + } + + spin_lock_irqsave(&stereo_priv[stereo_id].stereo_lock, flags); + cfg = readl(stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + cfg &= ~STEREOn_CONFIG_DATA_WORD_SIZE_MASK; + cfg &= ~STEREOn_CONFIG_I2S_WORD_SIZE_32; + cfg &= ~STEREOn_CONFIG_MIC_WORD_SIZE_32; + cfg |= mask; + writel(cfg, stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + spin_unlock_irqrestore(&stereo_priv[stereo_id].stereo_lock, flags); + + return 0; +} +EXPORT_SYMBOL(ipq4019_cfg_bit_width); + +/* + * Configure stereo/mono mode + */ +void ipq4019_config_stereo_mode(u32 mode, u32 stereo_id) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&stereo_priv[stereo_id].stereo_lock, flags); + cfg = readl(stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + cfg &= ~STEREOn_CONFIG_STEREO_MONO_MASK; + if (mode == CH_STEREO) + cfg |= STEREOn_CONFIG_STEREO_MODE; + writel(cfg, stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + spin_unlock_irqrestore(&stereo_priv[stereo_id].stereo_lock, flags); +} +EXPORT_SYMBOL(ipq4019_config_stereo_mode); + +/* + * Configure master mode + */ +void ipq4019_config_master(u32 enable, u32 stereo_id) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&stereo_priv[stereo_id].stereo_lock, flags); + cfg = readl(stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + cfg &= ~STEREOn_CONFIG_MASTER; + if (enable) + cfg |= STEREOn_CONFIG_MASTER; + writel(cfg, stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + spin_unlock_irqrestore(&stereo_priv[stereo_id].stereo_lock, flags); +} +EXPORT_SYMBOL(ipq4019_config_master); + +/* Selects the raw clock source between + * divided audio clock and input master clock + * Val 0: Raw master clock is divided audio PLL clock + * Val 1: Raw master clock is MCLK IN + */ +void ipq4019_config_mclk_sel(u32 stereo_id, u32 val) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&stereo_priv[stereo_id].stereo_lock, flags); + cfg = readl(stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + cfg &= ~STEREOn_CONFIG_MCK_SEL; + cfg |= val; + writel(cfg, stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + spin_unlock_irqrestore(&stereo_priv[stereo_id].stereo_lock, flags); + +} +EXPORT_SYMBOL(ipq4019_config_mclk_sel); + +/* + * Strategy to clear the sample counter TX and RX registers + */ +void ipq4019_config_sample_cnt_clear_type(u32 stereo_id) +{ + u32 cfg; + unsigned long flags; + + spin_lock_irqsave(&stereo_priv[stereo_id].stereo_lock, flags); + cfg = readl(stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + /* 0 - write an explicit zero data through software + * to the TX and RX sample counter registers + * 1 - software read of the TX and RX sample counter + * registers clears the counter registers + */ + cfg |= STEREOn_CONFIG_SAMPLE_CNT_CLEAR_TYPE; /* Write 1 */ + writel(cfg, stereo_priv[stereo_id].stereo_base + + ADSS_STEREOn_STEREO0_CONFIG_REG); + spin_unlock_irqrestore(&stereo_priv[stereo_id].stereo_lock, flags); +} +EXPORT_SYMBOL(ipq4019_config_sample_cnt_clear_type); + +static const struct of_device_id ipq4019_audio_stereo_id_table[] = { + { .compatible = "qca,ipq4019-stereo" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ipq4019_audio_stereo_id_table); + +static int ipq4019_audio_stereo_probe(struct platform_device *pdev) +{ + struct resource *res; + struct stereo_priv_data *spd; + struct device_node *np = pdev->dev.of_node; + u32 stereo_port_id = 0; + + if (of_property_read_u32(np, "stereo-index", &stereo_port_id)) { + dev_err(&pdev->dev, "Error reading stereo-index\n"); + return -EINVAL; + } + if (stereo_port_id >= MAX_STEREO_ENTRIES) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spd = &stereo_priv[stereo_port_id]; + spd->stereo_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(spd->stereo_base)) + return PTR_ERR(spd->stereo_base); + + spin_lock_init(&spd->stereo_lock); + return 0; +} + +static struct platform_driver ipq4019_audio_stereo_driver = { + .probe = ipq4019_audio_stereo_probe, + .driver = { + .name = "ipq4019-stereo", + .of_match_table = ipq4019_audio_stereo_id_table, + }, +}; + +module_platform_driver(ipq4019_audio_stereo_driver); + +MODULE_ALIAS("platform:ipq4019-stereo"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("IPQ4019 AUDIO Stereo driver"); diff --git a/sound/soc/qcom/ipq4019/ipq4019.c b/sound/soc/qcom/ipq4019/ipq4019.c new file mode 100644 index 0000000..a08fa5e6 --- /dev/null +++ b/sound/soc/qcom/ipq4019/ipq4019.c @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/jack.h> +#include <linux/io.h> + +static struct snd_soc_dai_link ipq4019_snd_dai[] = { + { + .name = "IPQ4019 Media1", + .stream_name = "I2S", + /* CPU DAI Name */ + .cpu_dai_name = "qca-i2s-dai", + /* Platform Driver Name */ + .platform_name = "7709000.qca-pcm-i2s", + /* Codec DAI Name */ + .codec_dai_name = "qca-i2s-codec-dai", + /*Codec Driver Name */ + .codec_name = "qca_codec.0-0012", + .dai_fmt = (SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS), + }, + { + .name = "IPQ4019 Media2", + .stream_name = "TDM", + .cpu_dai_name = "qca-tdm-dai", + .platform_name = "7709000.qca-pcm-tdm", + .codec_dai_name = "qca-tdm-codec-dai", + .codec_name = "qca_codec.0-0012", + }, + { + .name = "IPQ4019 Media3", + .stream_name = "I2S1", + .cpu_dai_name = "qca-i2s1-dai", + .platform_name = "770b000.qca-pcm-i2s1", + .codec_dai_name = "qca-i2s1-codec-dai", + .codec_name = "qca_codec.0-0012", + }, + { + .name = "IPQ4019 Media4", + .stream_name = "I2S2", + .cpu_dai_name = "qca-i2s2-dai", + .platform_name = "770d000.qca-pcm-i2s2", + .codec_dai_name = "qca-i2s2-codec-dai", + .codec_name = "qca_codec.0-0012", + }, + { + .name = "IPQ4019 Media5", + .stream_name = "SPDIF", + .cpu_dai_name = "qca-spdif-dai", + .platform_name = "7707000.qca-pcm-spdif", + .codec_dai_name = "qca-spdif-codec-dai", + .codec_name = "qca_codec.0-0012", + }, +}; + +static struct snd_soc_card snd_soc_card_qca = { + .name = "ipq4019_snd_card", + .dai_link = ipq4019_snd_dai, + .num_links = ARRAY_SIZE(ipq4019_snd_dai), +}; + +static const struct of_device_id ipq4019_audio_id_table[] = { + { .compatible = "qca,ipq4019-audio" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ipq4019_audio_id_table); + +static int ipq4019_audio_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_card *card = &snd_soc_card_qca; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret); + + return ret; +} + +static struct platform_driver ipq4019_audio_driver = { + .driver = { + .name = "ipq4019_audio", + .of_match_table = ipq4019_audio_id_table, + }, + .probe = ipq4019_audio_probe, +}; + +module_platform_driver(ipq4019_audio_driver); + +MODULE_ALIAS("platform:ipq4019_audio"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("ALSA SoC IPQ4019 Machine Driver");