summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/tegra3_i2s.c
diff options
context:
space:
mode:
authorVinod G <vinodg@nvidia.com>2011-01-13 18:03:28 -0800
committerDan Willemsen <dwillemsen@nvidia.com>2011-04-26 15:49:27 -0700
commita5a16a1d8d16a80607932f529d8e3f4143c96d30 (patch)
treee81462ce93806ab69a134c5e1bc1da7153097d95 /arch/arm/mach-tegra/tegra3_i2s.c
parent61f68483c0f4ae1145b31522e63516f0c1d56d46 (diff)
[arm/tegra] Initial version of T30 audio driver
Add driver support to ahub, dam & i2s Original-Change-Id: I7281f787f1dd747c780a2dd55e53412a229ed6c0 Reviewed-on: http://git-master/r/15902 Reviewed-by: Vinod Gopalakrishnakurup <vinodg@nvidia.com> Tested-by: Vinod Gopalakrishnakurup <vinodg@nvidia.com> Reviewed-by: Scott Williams <scwilliams@nvidia.com> Change-Id: I146604556290f3f72f7efc95f32541d667fb0d00
Diffstat (limited to 'arch/arm/mach-tegra/tegra3_i2s.c')
-rw-r--r--arch/arm/mach-tegra/tegra3_i2s.c621
1 files changed, 621 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/tegra3_i2s.c b/arch/arm/mach-tegra/tegra3_i2s.c
new file mode 100644
index 000000000000..b555265be91b
--- /dev/null
+++ b/arch/arm/mach-tegra/tegra3_i2s.c
@@ -0,0 +1,621 @@
+/*
+ * arch/arm/mach-tegra/tegra3_i2s.c
+ *
+ * Copyright (c) 2010-2011, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include "clock.h"
+#include <asm/io.h>
+#include <mach/iomap.h>
+#include <mach/tegra3_i2s.h>
+
+#define check_i2s_ifc(n, ...) if ((n) > NR_I2S_IFC) { \
+ pr_err("%s: invalid audio interface %d\n", __func__, (n)); \
+ return __VA_ARGS__; \
+}
+
+/*static phys_addr_t i2s_phy_base[NR_I2S_IFC] = {
+ TEGRA_I2S0_BASE,
+ TEGRA_I2S1_BASE,
+ TEGRA_I2S2_BASE,
+ TEGRA_I2S3_BASE,
+ TEGRA_I2S4_BASE,
+};*/
+
+static void *i2s_base[NR_I2S_IFC] = {
+ IO_ADDRESS(TEGRA_I2S0_BASE),
+ IO_ADDRESS(TEGRA_I2S1_BASE),
+ IO_ADDRESS(TEGRA_I2S2_BASE),
+ IO_ADDRESS(TEGRA_I2S3_BASE),
+ IO_ADDRESS(TEGRA_I2S4_BASE),
+};
+
+static inline void i2s_writel(int ifc, u32 val, u32 reg)
+{
+ pr_info("i2s Write 0x%x : %08x\n",
+ (unsigned int)i2s_base[ifc] + reg, val);
+ __raw_writel(val, i2s_base[ifc] + reg);
+}
+
+static inline u32 i2s_readl(int ifc, u32 reg)
+{
+ u32 val = __raw_readl(i2s_base[ifc] + reg);
+ pr_info("i2s Read 0x%x : %08x\n",
+ (unsigned int) i2s_base[ifc] + reg, val);
+ return val;
+}
+
+void i2s_dump_registers(int ifc)
+{
+ check_i2s_ifc(ifc);
+ pr_info("%s: \n",__func__);
+ i2s_readl(ifc, I2S_CTRL_0);
+ i2s_readl(ifc, I2S_TIMING_0);
+ i2s_readl(ifc, I2S_OFFSET_0);
+ i2s_readl(ifc, I2S_CH_CTRL_0);
+ i2s_readl(ifc, I2S_AUDIOCIF_I2STX_CTRL_0);
+ i2s_readl(ifc, I2S_AUDIOCIF_I2SRX_CTRL_0);
+ i2s_readl(ifc, I2S_FLOWCTL_0);
+ i2s_readl(ifc, I2S_TX_STEP_0);
+ i2s_readl(ifc, I2S_FLOW_STATUS_0);
+ i2s_readl(ifc, I2S_FLOW_TOTAL_0);
+ i2s_readl(ifc, I2S_FLOW_OVER_0);
+}
+
+void i2s_get_all_regs(int ifc, struct i2s_runtime_data* ird)
+{}
+void i2s_set_all_regs(int ifc, struct i2s_runtime_data* ird)
+{}
+/*
+* Set the fifo mode as Tx or Rx or both
+*/
+void i2s_fifo_enable(int ifc, int tx, int enable)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc);
+
+ /* FIXME : this is to moved to a common
+ place in the audio_switch call
+ */
+ apbif_channel_enable(ifc, tx, enable);
+
+ val = i2s_readl(ifc, I2S_CTRL_0);
+ if (tx != I2S_FIFO_TX) { /* receive */
+ set_reg_mode(val, I2S_CTRL_XFER_EN_RX, enable);
+ }
+ else { /* transmit */
+ set_reg_mode(val, I2S_CTRL_XFER_EN_TX, enable);
+ }
+ i2s_writel(ifc, val, I2S_CTRL_0);
+}
+
+/*
+* Set the 2nd level clock gating
+*/
+void i2s_set_clock_gating(int ifc, int enable)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc);
+
+ val = i2s_readl(ifc, I2S_CTRL_0);
+ set_reg_mode(val, I2S_CTRL_CG_EN, enable);
+ i2s_writel(ifc, val, I2S_CTRL_0);
+}
+
+/*
+* Set i2s soft reset
+*/
+void i2s_set_soft_reset(int ifc, int enable)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc);
+
+ val = i2s_readl(ifc, I2S_CTRL_0);
+ set_reg_mode(val, I2S_CTRL_SOFT_RESET, enable);
+ i2s_writel(ifc, val, I2S_CTRL_0);
+}
+
+/*
+* I2s loopback mode
+*/
+void i2s_set_loopback(int ifc, int on)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc);
+
+ val = i2s_readl(ifc, I2S_CTRL_0);
+ set_reg_mode(val, I2S_CTRL_LPBK_ENABLE, on);
+ i2s_writel(ifc, val, I2S_CTRL_0);
+}
+
+/*
+* I2s master/Slave mode
+*/
+void i2s_set_master(int ifc, int master)
+{
+ u32 val;
+ check_i2s_ifc(ifc);
+ val = i2s_readl(ifc, I2S_CTRL_0);
+ set_reg_mode(val, I2S_CTRL_MASTER_ENABLE, master);
+ i2s_writel(ifc, val, I2S_CTRL_0);
+}
+
+/*
+* I2s lrck polarity
+*/
+void i2s_set_left_right_control_polarity(int ifc, int left_low)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc);
+
+ val = i2s_readl(ifc, I2S_CTRL_0);
+ set_reg_mode(val, I2S_CTRL_LRCK_L_LOW,left_low);
+ i2s_writel(ifc, val, I2S_CTRL_0);
+}
+
+/*
+* Set i2s bit code
+*/
+int i2s_set_bit_code(int ifc, unsigned int bitcode)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+
+ if (bitcode >= AUDIO_BIT_CODE_RSVD) {
+ pr_err("%s: invalid bit-code selector %d\n", __func__, bitcode);
+ return -EINVAL;
+ }
+
+ val = i2s_readl(ifc, I2S_CTRL_0);
+ val &= ~I2S_CTRL_BIT_CODE_MASK;
+ val |= (bitcode << I2S_CTRL_BIT_CODE_SHIFT);
+
+ i2s_writel(ifc, val, I2S_CTRL_0);
+ return 0;
+}
+
+/*
+* Set i2s frame format
+*/
+int i2s_set_bit_format(int ifc, unsigned fmt)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+
+ if (fmt >= AUDIO_FRAME_FORMAT_UNKNOWN) {
+ pr_err("%s: invalid bit-format selector %d\n", __func__, fmt);
+ return -EINVAL;
+ }
+
+ val = i2s_readl(ifc, I2S_CTRL_0);
+ val &= ~I2S_CTRL_FRAME_FORMAT_MASK;
+
+ if ((fmt == AUDIO_FRAME_FORMAT_I2S) ||
+ (fmt == AUDIO_FRAME_FORMAT_RJM) ||
+ (fmt == AUDIO_FRAME_FORMAT_LJM))
+ {
+ val |= I2S_CTRL_FRAME_FORMAT_LRCK;
+ }
+ else /*Dsp,Pcm,Tdm,Nw*/
+ {
+ val |= I2S_CTRL_FRAME_FORMAT_FSYNC;
+ }
+
+ i2s_writel(ifc, val, I2S_CTRL_0);
+ return 0;
+}
+
+/*
+* Set i2s bit size
+*/
+int i2s_set_bit_size(int ifc, unsigned bit_size)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+
+ val = i2s_readl(ifc, I2S_CTRL_0);
+ val &= ~I2S_CTRL_BIT_SIZE_MASK;
+
+ if (bit_size > AUDIO_BIT_SIZE_32) {
+ pr_err("%s: invalid bit_size selector %d\n", __func__,
+ bit_size);
+ return -EINVAL;
+ }
+
+ val |= bit_size << I2S_CTRL_BIT_SIZE_SHIFT;
+
+ i2s_writel(ifc, val, I2S_CTRL_0);
+ return 0;
+}
+/*
+* Channel bit count - used to set the timing register
+*/
+int i2s_set_channel_bit_count(int ifc, int sampling, int bitclk)
+{
+ u32 val;
+ int bitcnt;
+
+ check_i2s_ifc(ifc, -EINVAL);
+
+ bitcnt = bitclk / (2 * sampling) - 1;
+
+ if (bitcnt < 0 || bitcnt >= 1<<11) {
+ pr_err("%s: bit count %d is out of bounds\n", __func__,
+ bitcnt);
+ return -EINVAL;
+ }
+
+ val = bitcnt;
+ if (bitclk % (2 * sampling)) {
+ pr_info("%s: enabling non-symmetric mode\n", __func__);
+ val |= I2S_TIMING_NON_SYM_ENABLE;
+ }
+
+ /* FIXME: I2S/LJM/RJM - No of bit clocks in left or right channel
+ DSP - No of bit clocks in left+right channel
+ TDM/NW/PCM - No of bit clocks in the frame
+ */
+
+ i2s_writel(ifc, val, I2S_TIMING_0);
+ return 0;
+}
+
+/*
+* I2s data offset
+*/
+int i2s_set_data_offset(int ifc, int tx, int dataoffset)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+
+ val = i2s_readl(ifc, I2S_OFFSET_0);
+
+ if (tx != AUDIO_TX_MODE)
+ {
+ val &= ~I2S_OFFSET_RX_DATA_OFFSET_MASK;
+ val |= (dataoffset << I2S_OFFSET_RX_DATA_OFFSET_SHIFT);
+ }
+ else
+ {
+ val &= ~I2S_OFFSET_TX_DATA_OFFSET_MASK;
+ val |= (dataoffset << I2S_OFFSET_TX_DATA_OFFSET_SHIFT);
+ }
+
+ i2s_writel(ifc, val, I2S_OFFSET_0);
+ return 0;
+}
+
+/*
+* I2s edge control
+*/
+int i2s_set_edge_control(int ifc, int edgectrl)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+
+ val = i2s_readl(ifc, I2S_CH_CTRL_0);
+
+ set_reg_mode(val, I2S_CH_CTRL_EGDE_CTRL_NEG_EDGE, edgectrl);
+
+ i2s_writel(ifc, val, I2S_CH_CTRL_0);
+ return 0;
+
+}
+
+/*
+* I2s highz control
+*/
+int i2s_set_highz_control(int ifc, int highzvalue)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+
+ val = i2s_readl(ifc, I2S_CH_CTRL_0);
+
+ val &= ~I2S_CH_CTRL_HIGHZ_CTRL_MASK;
+ val |= (highzvalue << I2S_CH_CTRL_HIGHZ_CTRL_SHIFT);
+
+ i2s_writel(ifc, val, I2S_CH_CTRL_0);
+ return 0;
+}
+
+/*
+* I2s Fsync width
+*/
+int i2s_set_fsync_width(int ifc, int fifo, int fsyncwidth)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+ val = i2s_readl(ifc, I2S_CH_CTRL_0);
+
+ val &= ~I2S_CH_CTRL_FSYNC_WIDTH_MASK;
+ val |= (fsyncwidth << I2S_CH_CTRL_FSYNC_WIDTH_SHIFT);
+
+ i2s_writel(ifc, val, I2S_CH_CTRL_0);
+ return 0;
+}
+
+/*
+* I2s slot control
+*/
+int i2s_set_slot_control(int ifc, int tx, int totalslot, int numslots)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+ val = i2s_readl(ifc, I2S_CH_CTRL_0);
+
+ val &= ~I2S_SLOT_CTRL_TOTAL_SLOT_MASK;
+ val |= (totalslot << I2S_CH_CTRL_FSYNC_WIDTH_SHIFT);
+
+ if (tx != AUDIO_TX_MODE)
+ {
+ val &= ~I2S_SLOT_CTRL_RX_SLOT_MASK;
+ val |= (numslots << I2S_SLOT_CTRL_RX_SLOT_SHIFT);
+ }
+ else
+ {
+ val &= ~I2S_SLOT_CTRL_TX_SLOT_MASK;
+ val |= (numslots << I2S_SLOT_CTRL_TX_SLOT_SHIFT);
+ }
+
+ i2s_writel(ifc, val, I2S_CH_CTRL_0);
+ return 0;
+}
+
+/*
+* I2s bit order
+*/
+int i2s_set_bit_order(int ifc, int tx, int bitorder)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+
+ val = i2s_readl(ifc, I2S_CH_CTRL_0);
+
+ if (tx != AUDIO_TX_MODE)
+ {
+ set_reg_mode(val,I2S_CH_CTRL_RX_BIT_LSB_FIRST, bitorder);
+ }
+ else
+ {
+ set_reg_mode(val, I2S_CH_CTRL_TX_BIT_LSB_FIRST, bitorder);
+ }
+
+ i2s_writel(ifc, val, I2S_CH_CTRL_0);
+ return 0;
+}
+
+
+/*
+* I2s mask bit
+* Used with Pcm Mode to get exact bit size
+*/
+int i2s_set_bit_mask(int ifc, int tx, int maskbit)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+
+ val = i2s_readl(ifc, I2S_CH_CTRL_0);
+
+ if (tx != AUDIO_TX_MODE)
+ {
+ val &= ~I2S_CH_CTRL_RX_MASK_BITS_MASK;
+ val |= (maskbit << I2S_CH_CTRL_RX_MASK_BITS_SHIFT);
+ }
+ else
+ {
+ val &= ~I2S_CH_CTRL_TX_MASK_BITS_MASK;
+ val |= (maskbit << I2S_CH_CTRL_TX_MASK_BITS_SHIFT);
+ }
+
+ i2s_writel(ifc, val, I2S_CH_CTRL_0);
+ return 0;
+}
+
+/*
+* I2s flow control
+*/
+int i2s_set_flow_control(int ifc, int enable, int filtertype, int stepsize)
+{
+ u32 val;
+
+ check_i2s_ifc(ifc, -EINVAL);
+
+ val = i2s_readl(ifc, I2S_FLOWCTL_0);
+
+ set_reg_mode(val, I2S_FLOWCTL_FILTER_QUAD, filtertype);
+
+ i2s_writel(ifc, val, I2S_FLOWCTL_0);
+
+ val = i2s_readl(ifc, I2S_TX_STEP_0);
+
+ val &= ~I2S_TX_STEP_MASK;
+ val |= (stepsize << I2S_TX_STEP_SHIFT);
+
+ i2s_writel(ifc, val, I2S_TX_STEP_0);
+
+ val = i2s_readl(ifc, I2S_CTRL_0);
+ set_reg_mode(val, I2S_CTRL_TX_FLOWCTL_EN, enable);
+ i2s_writel(ifc, val, I2S_CTRL_0);
+ return 0;
+}
+
+int i2s_fifo_set_attention_level(int ifc, int fifo, unsigned level)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+* Make it functional with base audio - code it properly
+*/
+ apbif_fifo_set_attention_level(ifc, fifo, level);
+ return 0;
+}
+
+void i2s_fifo_clear(int ifc, int fifo)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+*/
+}
+
+
+void i2s_set_fifo_irq_on_err(int ifc, int fifo, int on)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+*/
+}
+
+
+void i2s_set_fifo_irq_on_qe(int ifc, int fifo, int on)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+*/
+}
+
+void i2s_enable_fifos(int ifc, int on)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+*/
+}
+
+void i2s_fifo_write(int ifc, int fifo, u32 data)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+*/
+}
+
+u32 i2s_fifo_read(int ifc, int fifo)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+*/
+ return 0;
+}
+
+u32 i2s_get_status(int ifc)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+*/
+ apbif_get_fifo_mode(ifc, AUDIO_TX_MODE);
+ return 0;
+}
+
+u32 i2s_get_control(int ifc)
+{
+ check_i2s_ifc(ifc, 0);
+ return i2s_readl(ifc, I2S_CTRL_0);
+}
+
+void i2s_ack_status(int ifc)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+*/
+}
+
+u32 i2s_get_fifo_scr(int ifc)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+*/
+ return 0;
+}
+
+phys_addr_t i2s_get_fifo_phy_base(int ifc, int fifo)
+{
+ /* FIXME: call the apbif to get the appropriate channel */
+ apbif_get_fifo_phy_base(ifc, fifo);
+ return 0;
+}
+
+u32 i2s_get_fifo_full_empty_count(int ifc, int fifo)
+{
+/* FIXME: fifo are part of apbif channel, so pass call to apbif
+* or provide generic call to apbif to handle this
+*/
+ return 0;
+}
+
+int i2s_get_dma_requestor(int ifc)
+{
+/* FIXME: check with audio switch to get the
+ appropriate apbif channel.
+*/
+ return apbif_get_channel(ifc);
+}
+
+struct clk *i2s_get_clock_by_name(const char *name)
+{
+ return tegra_get_clock_by_name(name);
+}
+
+static struct audio_cif audiocif;
+int i2s_initialize(int ifc)
+{
+ struct audio_cif *tx_audio_cif = &audiocif;
+ i2s_enable_fifos(ifc, 0);
+ i2s_set_left_right_control_polarity(ifc, AUDIO_LRCK_LEFT_LOW); /* low */
+ i2s_set_master(ifc, AUDIO_MASTER_MODE); /* set as master */
+ i2s_set_bit_format(ifc, AUDIO_FRAME_FORMAT_I2S);
+ i2s_set_bit_size(ifc, AUDIO_BIT_SIZE_16);
+ i2s_set_bit_code(ifc, AUDIO_BIT_CODE_LINEAR);
+ i2s_set_data_offset(ifc, AUDIO_TX_MODE, 1);
+ i2s_set_data_offset(ifc, AUDIO_RX_MODE, 1);
+
+ /* FIXME: move all the apbif call to a generic function
+ inside audio_switch code - temporarily added here to
+ get minimum audio function working
+ */
+
+ /* set i2s audiocif */
+ /* setting base value for acif */
+ memset(tx_audio_cif, 0 , sizeof(struct audio_cif));
+ tx_audio_cif->audio_channels = AUDIO_CHANNEL_2;
+ tx_audio_cif->client_channels = AUDIO_CHANNEL_2;
+ tx_audio_cif->audio_bits = AUDIO_BIT_SIZE_16;
+ tx_audio_cif->client_bits = AUDIO_BIT_SIZE_16;
+ audio_switch_set_acif((unsigned int)i2s_base[ifc] +
+ I2S_AUDIOCIF_I2STX_CTRL_0, tx_audio_cif);
+ audio_switch_set_acif((unsigned int)i2s_base[ifc] +
+ I2S_AUDIOCIF_I2SRX_CTRL_0, tx_audio_cif);
+
+ apbif_initialize(ifc, tx_audio_cif);
+ return 0;
+}
+