summaryrefslogtreecommitdiff
path: root/sound/soc/tegra/tegra30_dam.c
diff options
context:
space:
mode:
authorNikesh Oswal <noswal@nvidia.com>2011-10-13 20:30:39 +0530
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:49:38 -0800
commitbe785d114f747fa31645f8f30366ae2a75af0aa6 (patch)
tree9ef1c9696e661d978bea3ecf9f7e21f5146c505b /sound/soc/tegra/tegra30_dam.c
parentb7d5a86cff97b19c60aa7cdb1b1c23592d376d3f (diff)
asoc: tegra: add dam driver
Bug: 862023 Signed-off-by: Nikesh Oswal <noswal@nvidia.com> Change-Id: I55206f3be702da9d969fca2c779e65363dd911fc Reviewed-on: http://git-master/r/57879 Tested-by: Nikesh Oswal <noswal@nvidia.com> Reviewed-by: Stephen Warren <swarren@nvidia.com> Reviewed-by: Scott Peterson <speterson@nvidia.com> Rebase-Id: R8cf6d6e004184b5a5a2c4bcf6578027c96d647f3
Diffstat (limited to 'sound/soc/tegra/tegra30_dam.c')
-rw-r--r--sound/soc/tegra/tegra30_dam.c634
1 files changed, 634 insertions, 0 deletions
diff --git a/sound/soc/tegra/tegra30_dam.c b/sound/soc/tegra/tegra30_dam.c
new file mode 100644
index 000000000000..0828b014d9a7
--- /dev/null
+++ b/sound/soc/tegra/tegra30_dam.c
@@ -0,0 +1,634 @@
+/*
+ * tegra30_dam.c - Tegra 30 DAM driver
+ *
+ * Author: Nikesh Oswal <noswal@nvidia.com>
+ * Copyright (C) 2011 - NVIDIA, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/soc.h>
+#include "tegra30_dam.h"
+#include "tegra30_ahub.h"
+
+#define DRV_NAME "tegra30-dam"
+
+static struct tegra30_dam_context *dams_cont_info[TEGRA30_NR_DAM_IFC];
+
+enum {
+ dam_ch_in0 = 0x0,
+ dam_ch_in1,
+ dam_ch_out,
+ dam_ch_maxnum
+} tegra30_dam_chtype;
+
+struct tegra30_dam_src_step_table step_table[] = {
+ { 8000, 44100, 80 },
+ { 8000, 48000, 0 },
+ { 16000, 44100, 160 },
+ { 16000, 48000, 1 },
+ { 44100, 8000, 441 },
+ { 48000, 8000, 0 },
+ { 44100, 16000, 441 },
+ { 48000, 16000, 0 },
+};
+
+static void tegra30_dam_set_output_samplerate(struct tegra30_dam_context *dam,
+ int fsout);
+static void tegra30_dam_set_input_samplerate(struct tegra30_dam_context *dam,
+ int fsin);
+static int tegra30_dam_set_step_reset(struct tegra30_dam_context *dam,
+ int insample, int outsample);
+static void tegra30_dam_ch0_set_step(struct tegra30_dam_context *dam, int step);
+
+static inline void tegra30_dam_writel(struct tegra30_dam_context *dam,
+ u32 val, u32 reg)
+{
+ dam->ctrlreg_cache[(reg >> 2)] = val;
+ __raw_writel(val, dam->damregs + reg);
+}
+
+static inline u32 tegra30_dam_readl(struct tegra30_dam_context *dam, u32 reg)
+{
+ u32 val = __raw_readl(dam->damregs + reg);
+
+ return val;
+}
+
+#ifdef CONFIG_PM
+int tegra30_dam_suspend(int ifc)
+{
+ int i = 0;
+ struct tegra30_dam_context *dam;
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ return 0;
+}
+
+int tegra30_dam_resume(int ifc)
+{
+ int i = 0;
+ struct tegra30_dam_context *dam;
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ dam = dams_cont_info[ifc];
+
+ if (dam->in_use) {
+ tegra30_dam_enable_clock(ifc);
+
+ for (i = 0; i <= TEGRA30_DAM_CTRL_REGINDEX; i++) {
+ if ((i == TEGRA30_DAM_CTRL_RSVD_6) ||
+ (i == TEGRA30_DAM_CTRL_RSVD_10))
+ continue;
+
+ tegra30_dam_writel(dam, dam->ctrlreg_cache[i],
+ (i << 2));
+ }
+
+ tegra30_dam_disable_clock(ifc);
+ }
+
+ return 0;
+}
+#endif
+
+void tegra30_dam_disable_clock(int ifc)
+{
+ struct tegra30_dam_context *dam;
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ dam = dams_cont_info[ifc];
+ clk_disable(dam->dam_clk);
+ tegra30_ahub_disable_clocks();
+}
+
+int tegra30_dam_enable_clock(int ifc)
+{
+ struct tegra30_dam_context *dam;
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ dam = dams_cont_info[ifc];
+ tegra30_ahub_enable_clocks();
+ clk_enable(dam->dam_clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int tegra30_dam_show(struct seq_file *s, void *unused)
+{
+#define REG(r) { r, #r }
+ static const struct {
+ int offset;
+ const char *name;
+ } regs[] = {
+ REG(TEGRA30_DAM_CTRL),
+ REG(TEGRA30_DAM_CLIP),
+ REG(TEGRA30_DAM_CLIP_THRESHOLD),
+ REG(TEGRA30_DAM_AUDIOCIF_OUT_CTRL),
+ REG(TEGRA30_DAM_CH0_CTRL),
+ REG(TEGRA30_DAM_CH0_CONV),
+ REG(TEGRA30_DAM_AUDIOCIF_CH0_CTRL),
+ REG(TEGRA30_DAM_CH1_CTRL),
+ REG(TEGRA30_DAM_CH1_CONV),
+ REG(TEGRA30_DAM_AUDIOCIF_CH1_CTRL),
+ };
+#undef REG
+
+ struct tegra30_dam_context *dam = s->private;
+ int i;
+
+ clk_enable(dam->dam_clk);
+
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ u32 val = tegra30_dam_readl(dam, regs[i].offset);
+ seq_printf(s, "%s = %08x\n", regs[i].name, val);
+ }
+
+ clk_disable(dam->dam_clk);
+
+ return 0;
+}
+
+static int tegra30_dam_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, tegra30_dam_show, inode->i_private);
+}
+
+static const struct file_operations tegra30_dam_debug_fops = {
+ .open = tegra30_dam_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void tegra30_dam_debug_add(struct tegra30_dam_context *dam, int id)
+{
+ char name[] = DRV_NAME ".0";
+
+ snprintf(name, sizeof(name), DRV_NAME".%1d", id);
+ dam->debug = debugfs_create_file(name, S_IRUGO, snd_soc_debugfs_root,
+ dam, &tegra30_dam_debug_fops);
+}
+
+static void tegra30_dam_debug_remove(struct tegra30_dam_context *dam)
+{
+ if (dam->debug)
+ debugfs_remove(dam->debug);
+}
+#else
+static inline void tegra30_dam_debug_add(struct tegra30_dam_context *dam,
+ int id)
+{
+}
+
+static inline void tegra30_dam_debug_remove(struct tegra30_dam_context *dam)
+{
+}
+#endif
+
+int tegra30_dam_allocate_controller()
+{
+ int i = 0;
+ struct tegra30_dam_context *dam = NULL;
+
+ for (i = 0; i < TEGRA30_NR_DAM_IFC; i++) {
+
+ dam = dams_cont_info[i];
+
+ if (!dam->in_use) {
+ dam->in_use = true;
+ return i;
+ }
+ }
+
+ return -ENOENT;
+}
+
+int tegra30_dam_allocate_channel(int ifc, int chid)
+{
+ int i = 0;
+ struct tegra30_dam_context *dam = NULL;
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ dam = dams_cont_info[ifc];
+
+ if (!dam->ch_alloc[chid]) {
+ dam->ch_alloc[chid] = true;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+int tegra30_dam_free_channel(int ifc, int chid)
+{
+ int i = 0;
+ struct tegra30_dam_context *dam = NULL;
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ dam = dams_cont_info[ifc];
+
+ if (dam->ch_alloc[chid]) {
+ dam->ch_alloc[chid] = false;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+int tegra30_dam_free_controller(int ifc)
+{
+ struct tegra30_dam_context *dam = NULL;
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ dam = dams_cont_info[ifc];
+
+ if (!dam->ch_alloc[dam_ch_in0] &&
+ !dam->ch_alloc[dam_ch_in1]) {
+ dam->in_use = false;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+void tegra30_dam_set_samplerate(int ifc, int chid, int samplerate)
+{
+ struct tegra30_dam_context *dam = dams_cont_info[ifc];
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ switch (chid) {
+ case dam_ch_in0:
+ tegra30_dam_set_input_samplerate(dam, samplerate);
+ dam->ch_insamplerate[dam_ch_in0] = samplerate;
+ tegra30_dam_set_step_reset(dam, samplerate, dam->outsamplerate);
+ break;
+ case dam_ch_in1:
+ if (samplerate != dam->outsamplerate)
+ return -EINVAL;
+ dam->ch_insamplerate[dam_ch_in1] = samplerate;
+ break;
+ case dam_ch_out:
+ tegra30_dam_set_output_samplerate(dam, samplerate);
+ dam->outsamplerate = samplerate;
+ break;
+ default:
+ break;
+ }
+}
+
+void tegra30_dam_set_output_samplerate(struct tegra30_dam_context *dam,
+ int fsout)
+{
+ u32 val;
+
+ val = tegra30_dam_readl(dam, TEGRA30_DAM_CTRL);
+ val &= ~TEGRA30_DAM_CTRL_FSOUT_MASK;
+
+ switch (fsout) {
+ case TEGRA30_AUDIO_SAMPLERATE_8000:
+ val |= TEGRA30_DAM_CTRL_FSOUT_FS8;
+ break;
+ case TEGRA30_AUDIO_SAMPLERATE_16000:
+ val |= TEGRA30_DAM_CTRL_FSOUT_FS16;
+ break;
+ case TEGRA30_AUDIO_SAMPLERATE_44100:
+ val |= TEGRA30_DAM_CTRL_FSOUT_FS44;
+ break;
+ case TEGRA30_AUDIO_SAMPLERATE_48000:
+ val |= TEGRA30_DAM_CTRL_FSOUT_FS48;
+ break;
+ default:
+ break;
+ }
+
+ tegra30_dam_writel(dam, val, TEGRA30_DAM_CTRL);
+}
+
+void tegra30_dam_set_input_samplerate(struct tegra30_dam_context *dam, int fsin)
+{
+ u32 val;
+
+ val = tegra30_dam_readl(dam, TEGRA30_DAM_CH0_CTRL);
+ val &= ~TEGRA30_DAM_CH0_CTRL_FSIN_MASK;
+
+ switch (fsin) {
+ case TEGRA30_AUDIO_SAMPLERATE_8000:
+ val |= TEGRA30_DAM_CH0_CTRL_FSIN_FS8;
+ break;
+ case TEGRA30_AUDIO_SAMPLERATE_16000:
+ val |= TEGRA30_DAM_CH0_CTRL_FSIN_FS16;
+ break;
+ case TEGRA30_AUDIO_SAMPLERATE_44100:
+ val |= TEGRA30_DAM_CH0_CTRL_FSIN_FS44;
+ break;
+ case TEGRA30_AUDIO_SAMPLERATE_48000:
+ val |= TEGRA30_DAM_CH0_CTRL_FSIN_FS48;
+ break;
+ default:
+ break;
+ }
+
+ tegra30_dam_writel(dam, val, TEGRA30_DAM_CH0_CTRL);
+}
+
+int tegra30_dam_set_step_reset(struct tegra30_dam_context *dam,
+ int insample, int outsample)
+{
+ int step_reset = 0;
+ int i = 0;
+
+ for (i = 0; i < ARRAY_SIZE(step_table); i++) {
+ if ((insample == step_table[i].insample) &&
+ (outsample == step_table[i].outsample))
+ step_reset = step_table[i].stepreset;
+ }
+
+ tegra30_dam_ch0_set_step(dam, step_reset);
+
+ return 0;
+}
+
+void tegra30_dam_ch0_set_step(struct tegra30_dam_context *dam, int step)
+{
+ u32 val;
+
+ val = tegra30_dam_readl(dam, TEGRA30_DAM_CH0_CTRL);
+ val &= ~TEGRA30_DAM_CH0_CTRL_STEP_MASK;
+ val |= step << TEGRA30_DAM_CH0_CTRL_STEP_SHIFT;
+ tegra30_dam_writel(dam, val, TEGRA30_DAM_CH0_CTRL);
+}
+
+int tegra30_dam_set_gain(int ifc, int chid, int gain)
+{
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ switch (chid) {
+ case dam_ch_in0:
+ tegra30_dam_writel(dams_cont_info[ifc], gain,
+ TEGRA30_DAM_CH0_CONV);
+ break;
+ case dam_ch_in1:
+ tegra30_dam_writel(dams_cont_info[ifc], gain,
+ TEGRA30_DAM_CH1_CONV);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int tegra30_dam_set_acif(int ifc, int chid, unsigned int audio_channels,
+ unsigned int audio_bits, unsigned int client_channels,
+ unsigned int client_bits)
+{
+ unsigned int reg;
+ unsigned int value = 0;
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ /*ch0 takes input as mono/16bit always*/
+ if ((chid == dam_ch_in0) &&
+ ((client_channels != 1) || (client_bits != 16)))
+ return -EINVAL;
+
+ value |= TEGRA30_CIF_MONOCONV_COPY;
+ value |= TEGRA30_CIF_STEREOCONV_CH0;
+ value |= (audio_channels-1) << TEGRA30_AUDIO_CHANNELS_SHIFT;
+ value |= (((audio_bits>>2)-1)<<TEGRA30_AUDIO_BITS_SHIFT);
+ value |= (client_channels-1) << TEGRA30_CLIENT_CHANNELS_SHIFT;
+ value |= (((client_bits>>2)-1)<<TEGRA30_CLIENT_BITS_SHIFT);
+
+ switch (chid) {
+ case dam_ch_out:
+ value |= TEGRA30_CIF_DIRECTION_TX;
+ reg = TEGRA30_DAM_AUDIOCIF_OUT_CTRL;
+ break;
+ case dam_ch_in0:
+ value |= TEGRA30_CIF_DIRECTION_RX;
+ reg = TEGRA30_DAM_AUDIOCIF_CH0_CTRL;
+ break;
+ case dam_ch_in1:
+ value |= TEGRA30_CIF_DIRECTION_RX;
+ reg = TEGRA30_DAM_AUDIOCIF_CH1_CTRL;
+ break;
+ }
+
+ tegra30_dam_writel(dams_cont_info[ifc], value, reg);
+
+ return 0;
+}
+
+void tegra30_dam_enable(int ifc, int on, int chid)
+{
+ u32 old_val, val, enreg;
+ struct tegra30_dam_context *dam = dams_cont_info[ifc];
+
+ if (ifc >= TEGRA30_NR_DAM_IFC)
+ return -EINVAL;
+
+ if (chid == dam_ch_in0)
+ enreg = TEGRA30_DAM_CH0_CTRL;
+ else
+ enreg = TEGRA30_DAM_CH1_CTRL;
+
+ old_val = val = tegra30_dam_readl(dam, enreg);
+
+ if (on) {
+ if (!dam->ch_enable_refcnt[chid]++)
+ val |= TEGRA30_DAM_CH0_CTRL_EN;
+ } else if (dam->ch_enable_refcnt[chid]) {
+ dam->ch_enable_refcnt[chid]--;
+ if (!dam->ch_enable_refcnt[chid])
+ val &= ~TEGRA30_DAM_CH0_CTRL_EN;
+ }
+
+ if (val != old_val)
+ tegra30_dam_writel(dam, val, enreg);
+
+ old_val = val = tegra30_dam_readl(dam, TEGRA30_DAM_CTRL);
+
+ if (dam->ch_enable_refcnt[dam_ch_in0] ||
+ dam->ch_enable_refcnt[dam_ch_in1])
+ val |= TEGRA30_DAM_CTRL_DAM_EN;
+ else
+ val &= ~TEGRA30_DAM_CTRL_DAM_EN;
+
+ if (old_val != val)
+ tegra30_dam_writel(dam, val, TEGRA30_DAM_CTRL);
+}
+
+void tegra30_dam_ch0_set_datasync(struct tegra30_dam_context *dam, int datasync)
+{
+ u32 val;
+
+ val = tegra30_dam_readl(dam, TEGRA30_DAM_CH0_CTRL);
+ val &= ~TEGRA30_DAM_CH0_CTRL_DATA_SYNC_MASK;
+ val |= datasync << TEGRA30_DAM_DATA_SYNC_SHIFT;
+ tegra30_dam_writel(dam, val, TEGRA30_DAM_CH0_CTRL);
+}
+
+void tegra30_dam_ch1_set_datasync(struct tegra30_dam_context *dam, int datasync)
+{
+ u32 val;
+
+ val = tegra30_dam_readl(dam, TEGRA30_DAM_CH1_CTRL);
+ val &= ~TEGRA30_DAM_CH1_CTRL_DATA_SYNC_MASK;
+ val |= datasync << TEGRA30_DAM_DATA_SYNC_SHIFT;
+ tegra30_dam_writel(dam, val, TEGRA30_DAM_CH1_CTRL);
+}
+
+void tegra30_dam_enable_clip_counter(struct tegra30_dam_context *dam, int on)
+{
+ u32 val;
+
+ val = tegra30_dam_readl(dam, TEGRA30_DAM_CLIP);
+ val &= ~TEGRA30_DAM_CLIP_COUNTER_ENABLE;
+ val |= on ? TEGRA30_DAM_CLIP_COUNTER_ENABLE : 0;
+ tegra30_dam_writel(dam, val, TEGRA30_DAM_CLIP);
+}
+
+static int __devinit tegra30_dam_probe(struct platform_device *pdev)
+{
+ struct resource *res, *region;
+ struct tegra30_dam_context *dam;
+ int ret = 0;
+
+ if ((pdev->id < 0) ||
+ (pdev->id >= TEGRA30_NR_DAM_IFC)) {
+ dev_err(&pdev->dev, "ID %d out of range\n", pdev->id);
+ return -EINVAL;
+ }
+
+ dams_cont_info[pdev->id] = devm_kzalloc(&pdev->dev,
+ sizeof(struct tegra30_dam_context),
+ GFP_KERNEL);
+ if (!dams_cont_info[pdev->id]) {
+ dev_err(&pdev->dev, "Can't allocate dam context\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+ dam = dams_cont_info[pdev->id];
+
+ dam->dam_clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dam->dam_clk)) {
+ dev_err(&pdev->dev, "Can't retrieve dam clock\n");
+ ret = PTR_ERR(dam->dam_clk);
+ goto err_free;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "No memory 0 resource\n");
+ ret = -ENODEV;
+ goto err_clk_put_dam;
+ }
+
+ region = devm_request_mem_region(&pdev->dev, res->start,
+ resource_size(res), pdev->name);
+ if (!region) {
+ dev_err(&pdev->dev, "Memory region 0 already claimed\n");
+ ret = -EBUSY;
+ goto err_clk_put_dam;
+ }
+
+ dam->damregs = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (!dam->damregs) {
+ dev_err(&pdev->dev, "ioremap 0 failed\n");
+ ret = -ENOMEM;
+ goto err_clk_put_dam;
+ }
+
+ platform_set_drvdata(pdev, dam);
+
+ tegra30_dam_debug_add(dam, pdev->id);
+
+ return 0;
+
+err_clk_put_dam:
+ clk_put(dam->dam_clk);
+err_free:
+ dams_cont_info[pdev->id] = NULL;
+exit:
+ return ret;
+}
+
+static int __devexit tegra30_dam_remove(struct platform_device *pdev)
+{
+ struct tegra30_dam_context *dam;
+
+ dam = platform_get_drvdata(pdev);
+ clk_put(dam->dam_clk);
+ tegra30_dam_debug_remove(dam);
+ dams_cont_info[pdev->id] = NULL;
+
+ return 0;
+}
+
+static struct platform_driver tegra30_dam_driver = {
+ .probe = tegra30_dam_probe,
+ .remove = __devexit_p(tegra30_dam_remove),
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init tegra30_dam_modinit(void)
+{
+ return platform_driver_register(&tegra30_dam_driver);
+}
+module_init(tegra30_dam_modinit);
+
+static void __exit tegra30_dam_modexit(void)
+{
+ platform_driver_unregister(&tegra30_dam_driver);
+}
+module_exit(tegra30_dam_modexit);
+
+MODULE_AUTHOR("Nikesh Oswal <noswal@nvidia.com>");
+MODULE_DESCRIPTION("Tegra 30 DAM driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);