summaryrefslogtreecommitdiff
path: root/sound/soc/sprd/dai/vbc/r1p0/vbc.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/sprd/dai/vbc/r1p0/vbc.c')
-rw-r--r--sound/soc/sprd/dai/vbc/r1p0/vbc.c637
1 files changed, 637 insertions, 0 deletions
diff --git a/sound/soc/sprd/dai/vbc/r1p0/vbc.c b/sound/soc/sprd/dai/vbc/r1p0/vbc.c
new file mode 100644
index 00000000..5c857b90
--- /dev/null
+++ b/sound/soc/sprd/dai/vbc/r1p0/vbc.c
@@ -0,0 +1,637 @@
+/*
+ * sound/soc/sprd/dai/vbc/r1p0/vbc.c
+ *
+ * SPRD SoC VBC -- SpreadTrum SOC for VBC DAI function.
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include "sprd-asoc-debug.h"
+#define pr_fmt(fmt) pr_sprd_fmt(" VBC ") fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/stat.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "sprd-asoc-common.h"
+#include "sprd-pcm.h"
+#include "vbc.h"
+#include "dfm.h"
+
+typedef int (*vbc_dma_set) (int enable);
+
+struct sprd_vbc_buffer_info {
+ int reg;
+ int shift;
+ int mask;
+ int max;
+};
+
+struct sprd_vbc_priv {
+ vbc_dma_set dma_set[2];
+ int (*arch_enable) (int chan);
+ int (*arch_disable) (int chan);
+ int used_chan_count;
+ struct sprd_vbc_buffer_info buf_info;
+ int rate;
+};
+
+struct sprd_dfm_priv dfm = { 0, 0 };
+
+EXPORT_SYMBOL_GPL(dfm);
+
+static struct sprd_pcm_dma_params vbc_pcm_stereo_out = {
+ .name = "VBC PCM Stereo out",
+ .irq_type = BLK_DONE,
+ .desc = {
+ .datawidth = SHORT_WIDTH,
+ .fragmens_len = VBC_FIFO_FRAME_NUM * 2,
+ .src_step = 2,
+ .des_step = 0,
+ },
+ .dev_paddr = {PHYS_VBDA0, PHYS_VBDA1},
+};
+
+static struct sprd_pcm_dma_params vbc_pcm_stereo_in = {
+ .name = "VBC PCM Stereo in",
+ .irq_type = BLK_DONE,
+ .desc = {
+ .datawidth = SHORT_WIDTH,
+ .fragmens_len = VBC_FIFO_FRAME_NUM * 2,
+ .src_step = 0,
+ .des_step = 2,
+ },
+ .dev_paddr = {PHYS_VBAD0, PHYS_VBAD1},
+};
+
+static struct sprd_vbc_priv vbc[VBC_IDX_MAX];
+
+static int vbc_iis_high_for_da1(int enable)
+{
+ vbc_reg_update(VBIISSEL, (enable ? 1 : 0) << VBIISSEL_DA_LRCK,
+ 1 << VBIISSEL_DA_LRCK);
+ return 0;
+}
+
+#if 0
+static int vbc_iis_high_for_ad1(int enable)
+{
+ vbc_reg_update(VBIISSEL, (enable ? 1 : 0) << VBIISSEL_AD_LRCK,
+ 1 << VBIISSEL_AD_LRCK);
+ return 0;
+}
+#endif
+
+static int vbc_set_buffer_size(int vbc_idx, int buffer_size)
+{
+ int val = vbc_reg_read(vbc[vbc_idx].buf_info.reg);
+ WARN_ON(buffer_size > vbc[vbc_idx].buf_info.max);
+ if ((buffer_size > 0)
+ && (buffer_size <= vbc[vbc_idx].buf_info.max)) {
+ val &= ~(vbc[vbc_idx].buf_info.mask);
+ val |= (((buffer_size - 1) << vbc[vbc_idx].buf_info.shift)
+ & vbc[vbc_idx].buf_info.mask);
+ vbc_reg_update(vbc[vbc_idx].buf_info.reg, val,
+ vbc[vbc_idx].buf_info.mask);
+ } else {
+ pr_err("ERR:buffer_size error! %d\n", buffer_size);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int fm_set_vbc_buffer_size(void)
+{
+ int i;
+ for (i = SPRD_VBC_PLAYBACK_COUNT; i < VBC_IDX_MAX; i++) {
+ vbc_set_buffer_size(i, VBC_FIFO_FRAME_NUM);
+ }
+ return 0;
+}
+
+static inline int vbc_sw_write_buffer(int enable)
+{
+ /* Software access ping-pong buffer enable when VBENABE bit low */
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << RAMSW_EN),
+ (1 << RAMSW_EN));
+ return 0;
+}
+
+static inline int vbc_ad0_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBAD0DMA_EN),
+ (1 << VBAD0DMA_EN));
+ return 0;
+}
+
+static inline int vbc_ad1_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBAD1DMA_EN),
+ (1 << VBAD1DMA_EN));
+ return 0;
+}
+
+static inline int vbc_da0_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBDA0DMA_EN),
+ (1 << VBDA0DMA_EN));
+ return 0;
+}
+
+static inline int vbc_da1_dma_set(int enable)
+{
+ vbc_reg_update(VBDABUFFDTA, ((enable ? 1 : 0) << VBDA1DMA_EN),
+ (1 << VBDA1DMA_EN));
+ return 0;
+}
+
+static void vbc_da_buffer_clear(int id)
+{
+ int i;
+ vbc_reg_update(VBDABUFFDTA, ((id ? 1 : 0) << RAMSW_NUMB),
+ (1 << RAMSW_NUMB));
+ for (i = 0; i < VBC_FIFO_FRAME_NUM; i++) {
+ vbc_reg_raw_write(VBDA0, 0);
+ vbc_reg_raw_write(VBDA1, 0);
+ }
+}
+
+static void vbc_da_buffer_clear_all(int vbc_idx)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ vbc[vbc_idx].arch_enable(i);
+ }
+
+ vbc_sw_write_buffer(true);
+ vbc_set_buffer_size(vbc_idx, VBC_FIFO_FRAME_NUM);
+ vbc_da_buffer_clear(1); /* clear data buffer 1 */
+ vbc_da_buffer_clear(0); /* clear data buffer 0 */
+ vbc_sw_write_buffer(false);
+
+ for (i = 0; i < 2; i++) {
+ vbc[vbc_idx].arch_disable(i);
+ }
+}
+
+static int vbc_da_arch_enable(int chan)
+{
+ return vbc_chan_enable(1, VBC_PLAYBACK, chan);
+}
+
+static int vbc_da_arch_disable(int chan)
+{
+ return vbc_chan_enable(0, VBC_PLAYBACK, chan);
+}
+
+static int vbc_ad_arch_enable(int chan)
+{
+ return vbc_chan_enable(1, VBC_CAPTRUE, chan);
+}
+
+static int vbc_ad_arch_disable(int chan)
+{
+ return vbc_chan_enable(0, VBC_CAPTRUE, chan);
+}
+
+static struct sprd_vbc_priv vbc[VBC_IDX_MAX] = {
+ /* All Playback */
+ { /*PlayBack */
+ .dma_set = {vbc_da0_dma_set, vbc_da1_dma_set},
+ .arch_enable = vbc_da_arch_enable,
+ .arch_disable = vbc_da_arch_disable,
+ .buf_info = {VBBUFFSIZE, VBDABUFFERSIZE_SHIFT, VBDABUFFERSIZE_MASK,
+ VBC_FIFO_FRAME_NUM},
+ },
+
+ /* All Capture */
+ { /*Capture for ad01 */
+ .dma_set = {vbc_ad0_dma_set, vbc_ad1_dma_set},
+ .arch_enable = vbc_ad_arch_enable,
+ .arch_disable = vbc_ad_arch_disable,
+ .buf_info = {VBBUFFSIZE, VBADBUFFERSIZE_SHIFT, VBADBUFFERSIZE_MASK,
+ VBC_FIFO_FRAME_NUM},
+ },
+};
+
+/* NOTE:
+ this index need use for the [struct sprd_vbc_priv] vbc[2] index
+ default MUST return 0.
+ */
+static inline int vbc_str_2_index(int stream, int id)
+{
+ if (stream == SNDRV_PCM_STREAM_CAPTURE) {
+ return id + SPRD_VBC_PLAYBACK_COUNT;
+ }
+ return id;
+}
+
+static int vbc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+ int ret;
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+ sp_asoc_pr_dbg("dfm.hw_rate:%d, dfm.sample_rate:%d", dfm.hw_rate,
+ dfm.sample_rate);
+
+ if (dfm.sample_rate != 0) {
+ if ((vbc_idx == VBC_PLAYBACK) || (vbc_idx == VBC_CAPTRUE)) {
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ dfm.sample_rate,
+ dfm.sample_rate);
+ if (ret < 0) {
+ pr_err("constraint error");
+ return ret;
+ }
+ }
+ }
+
+ vbc_power(1);
+
+ vbc_set_buffer_size(vbc_idx, VBC_FIFO_FRAME_NUM);
+ vbc_codec_startup(vbc_idx, dai);
+
+ WARN_ON(!vbc[vbc_idx].arch_enable);
+ WARN_ON(!vbc[vbc_idx].arch_disable);
+ WARN_ON(!vbc[vbc_idx].dma_set[0]);
+ WARN_ON(!vbc[vbc_idx].dma_set[1]);
+
+ return 0;
+}
+
+static void vbc_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+
+ /* vbc da close MUST clear da buffer */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ vbc_da_buffer_clear_all(vbc_idx);
+ }
+
+ vbc_power(0);
+}
+
+static int vbc_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+ struct sprd_pcm_dma_params *dma_data[VBC_IDX_MAX] = {
+ &vbc_pcm_stereo_out,
+ &vbc_pcm_stereo_in,
+ };
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+
+ snd_soc_dai_set_dma_data(dai, substream, dma_data[vbc_idx]);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ default:
+ pr_err("ERR:VBC Only Supports Format S16_LE\n");
+ break;
+ }
+
+ vbc[vbc_idx].used_chan_count = params_channels(params);
+ if (vbc[vbc_idx].used_chan_count > 2) {
+ pr_err("ERR:VBC Can NOT Supports Grate 2 Channels\n");
+ }
+
+ vbc_iis_high_for_da1(1);
+#ifdef CONFIG_SND_SOC_SPRD_VBC_LR_INVERT
+ if (vbc[vbc_idx].used_chan_count == 2) {
+ vbc_iis_high_for_da1(0);
+ }
+#endif
+ vbc[vbc_idx].rate = params_rate(params);
+ return 0;
+}
+
+static int vbc_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+ sp_asoc_pr_dbg("%s VBC(%s)\n", __func__, vbc_get_name(vbc_idx));
+
+ vbc[vbc_idx].rate = 0;
+
+ return 0;
+}
+
+static int vbc_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int vbc_idx;
+ int ret = 0;
+ int i;
+
+#if 0
+ sp_asoc_pr_dbg("%s\n", __func__);
+#endif
+
+ vbc_idx = vbc_str_2_index(substream->stream, dai->id);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ for (i = 0; i < vbc[vbc_idx].used_chan_count; i++) {
+ vbc[vbc_idx].arch_enable(i);
+ vbc[vbc_idx].dma_set[i] (1);
+ }
+ vbc_enable(1);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ for (i = 0; i < vbc[vbc_idx].used_chan_count; i++) {
+ vbc[vbc_idx].arch_disable(i);
+ vbc[vbc_idx].dma_set[i] (0);
+ }
+ vbc_enable(0);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_dai_ops vbc_dai_ops = {
+ .startup = vbc_startup,
+ .shutdown = vbc_shutdown,
+ .hw_params = vbc_hw_params,
+ .trigger = vbc_trigger,
+ .hw_free = vbc_hw_free,
+};
+
+static int dfm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ static const unsigned int dfm_all_rates[] = { 32000, 44100, 48000 };
+ static const struct snd_pcm_hw_constraint_list dfm_rates_constraint = {
+ .count = ARRAY_SIZE(dfm_all_rates),
+ .list = dfm_all_rates,
+ };
+ int ret;
+ int vbc_idx;
+ struct snd_soc_card *card = dai->card;
+ int i;
+
+ sp_asoc_pr_dbg("%s\n, vbc[vbc_idx].rate:da:%d, ad:%d, ad23:%d",
+ __func__, vbc[0].rate, vbc[1].rate, vbc[2].rate);
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &dfm_rates_constraint);
+ if (ret < 0)
+ return ret;
+
+ for (vbc_idx = VBC_PLAYBACK; vbc_idx < VBC_CAPTRUE1; vbc_idx++) {
+ if (vbc[vbc_idx].rate != 0) {
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ vbc[vbc_idx].rate,
+ vbc[vbc_idx].rate);
+ if (ret < 0) {
+ pr_err("constraint error");
+ return ret;
+ }
+ }
+ }
+
+ kfree(snd_soc_dai_get_dma_data(dai, substream));
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+
+ snd_soc_dapm_enable_pin(&dai->codec->dapm, "DFM");
+ snd_soc_dapm_sync(&dai->codec->dapm);
+ for (i = 0; i < card->num_rtd; i++) {
+ card->rtd[i].dai_link->ignore_suspend = 1;
+ }
+ snd_soc_dapm_ignore_suspend(&dai->codec->dapm, "DFM");
+
+ return 0;
+}
+
+static void dfm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = dai->card;
+ int i;
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ snd_soc_dapm_disable_pin(&dai->codec->dapm, "DFM");
+ snd_soc_dapm_sync(&dai->codec->dapm);
+ for (i = 0; i < card->num_rtd; i++) {
+ card->rtd[i].dai_link->ignore_suspend = 0;
+ }
+}
+
+static int dfm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ sp_asoc_pr_dbg("%s \n", __func__);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ default:
+ pr_err("ERR:VBC Only Supports Format S16_LE\n");
+ break;
+ }
+
+ return 0;
+}
+
+static int dfm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ sp_asoc_pr_dbg("%s \n", __func__);
+ dfm.sample_rate = 0;
+ dfm.hw_rate = 0;
+ return 0;
+}
+
+static struct snd_soc_dai_ops dfm_dai_ops = {
+ .startup = dfm_startup,
+ .shutdown = dfm_shutdown,
+ .hw_params = dfm_hw_params,
+ .hw_free = dfm_hw_free,
+};
+
+static struct snd_soc_dai_driver vbc_dai[] = {
+ {
+ .name = "vbc-r1p0",
+ .id = 0,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_max = 96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2, /* AD01 */
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_max = 96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &vbc_dai_ops,
+ },
+ {
+ .name = "vbc-dfm",
+ .id = DFM_MAGIC_ID,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_max = 48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &dfm_dai_ops,
+ }
+};
+
+static const struct snd_soc_component_driver sprd_vbc_component = {
+ .name = "vbc",
+};
+
+static int sprd_vbc_codec_probe(struct platform_device *pdev);
+static int vbc_drv_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret;
+ struct clk *vbc_clk;
+
+ sp_asoc_pr_dbg("%s\n", __func__);
+
+ arch_audio_vbc_switch(AUDIO_TO_ARM_CTRL);
+ ret = arch_audio_vbc_switch(AUDIO_NO_CHANGE);
+ if (ret != AUDIO_TO_ARM_CTRL) {
+ pr_err("Failed to Switch VBC to AP\n");
+ return -1;
+ }
+
+ for (i = 0; i < 2; i++) {
+ vbc_pcm_stereo_out.channels[i] = arch_audio_vbc_da_dma_info(i);
+ vbc_pcm_stereo_in.channels[i] = arch_audio_vbc_ad_dma_info(i);
+ }
+
+ /* 1. probe CODEC */
+ ret = sprd_vbc_codec_probe(pdev);
+
+ if (ret < 0) {
+ goto probe_err;
+ }
+
+ /* 2. probe DAIS */
+ ret =
+ snd_soc_register_component(&pdev->dev, &sprd_vbc_component, vbc_dai,
+ ARRAY_SIZE(vbc_dai));
+
+ if (ret < 0) {
+ pr_err("ERR:Register VBC to DAIS Failed!\n");
+ goto probe_err;
+ }
+
+ vbc_clk = clk_get(&pdev->dev, "clk_vbc");
+ if (IS_ERR(vbc_clk)) {
+ pr_err("Cannot request clk_vbc\n");
+ } else {
+ vbc_clk_set(vbc_clk);
+ }
+
+probe_err:
+ sp_asoc_pr_dbg("return %i\n", ret);
+
+ return ret;
+}
+
+static int sprd_vbc_codec_remove(struct platform_device *pdev);
+static int vbc_drv_remove(struct platform_device *pdev)
+{
+ struct clk *vbc_clk = vbc_clk_get();
+
+ if (vbc_clk) {
+ clk_put(vbc_clk);
+ vbc_clk_set(0);
+ }
+
+ snd_soc_unregister_component(&pdev->dev);
+
+ sprd_vbc_codec_remove(pdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id vbc_of_match[] = {
+ {.compatible = "sprd,vbc-r1p0",},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, vbc_of_match);
+#endif
+
+static struct platform_driver vbc_driver = {
+ .driver = {
+ .name = "vbc-r1p0",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(vbc_of_match),
+ },
+
+ .probe = vbc_drv_probe,
+ .remove = vbc_drv_remove,
+};
+
+module_platform_driver(vbc_driver);
+
+/* include the other module of VBC */
+#include "vbc-comm.c"
+#include "vbc-codec.c"
+
+MODULE_DESCRIPTION("SPRD ASoC VBC CUP-DAI driver");
+MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("cpu-dai:vbc-r1p0");