[ARM] tegra: driver for spdif audio
authorRavindra Lokhande <rlokhande@nvidia.com>
Mon, 27 Sep 2010 17:53:41 +0000 (12:53 -0500)
committerIliyan Malchev <malchev@google.com>
Tue, 2 Nov 2010 21:10:42 +0000 (14:10 -0700)
Creates /dev/spdif_out and /dev/spdif_out_ctl for playback and control
settings.  Playback is working.

Signed-off-by: Iliyan Malchev <malchev@google.com>
arch/arm/mach-tegra/Makefile
arch/arm/mach-tegra/include/mach/spdif.h [new file with mode: 0644]
arch/arm/mach-tegra/tegra_spdif_audio.c [new file with mode: 0644]
include/linux/tegra_spdif.h [new file with mode: 0644]

index cb2ad3b36582bec5f72745c3af01c6a1438ba59a..8d5316b1cbd8a47965008acffae59574a669c54e 100644 (file)
@@ -12,6 +12,7 @@ obj-y                                   += powergate.o
 obj-y                                  += suspend.o
 obj-y                                  += fuse.o
 obj-y                                  += tegra_i2s_audio.o
+obj-y                                  += tegra_spdif_audio.o
 obj-y                                  += mc.o
 obj-$(CONFIG_USB_SUPPORT)               += usb_phy.o
 obj-$(CONFIG_FIQ)                       += fiq.o
diff --git a/arch/arm/mach-tegra/include/mach/spdif.h b/arch/arm/mach-tegra/include/mach/spdif.h
new file mode 100644 (file)
index 0000000..96103fa
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+ * arch/arm/mach-tegra/include/mach/spdif.h
+ *
+ *
+ * Copyright (c) 2008-2009, 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.
+ */
+
+
+#ifndef __ARCH_ARM_MACH_TEGRA_SPDIF_H
+#define __ARCH_ARM_MACH_TEGRA_SPDIF_H
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+
+/* Offsets from TEGRA_SPDIF_BASE */
+
+#define SPDIF_CTRL_0                   0x0
+#define SPDIF_STATUS_0                 0x4
+#define SPDIF_STROBE_CTRL_0            0x8
+#define SPDIF_DATA_FIFO_CSR_0          0x0C
+#define SPDIF_DATA_OUT_0               0x40
+#define SPDIF_DATA_IN_0                        0x80
+#define SPDIF_CH_STA_RX_A_0            0x100
+#define SPDIF_CH_STA_RX_B_0            0x104
+#define SPDIF_CH_STA_RX_C_0            0x108
+#define SPDIF_CH_STA_RX_D_0            0x10C
+#define SPDIF_CH_STA_RX_E_0            0x110
+#define SPDIF_CH_STA_RX_F_0            0x114
+#define SPDIF_CH_STA_TX_A_0            0x140
+#define SPDIF_CH_STA_TX_B_0            0x144
+#define SPDIF_CH_STA_TX_C_0            0x148
+#define SPDIF_CH_STA_TX_D_0            0x14C
+#define SPDIF_CH_STA_TX_E_0            0x150
+#define SPDIF_CH_STA_TX_F_0            0x154
+#define SPDIF_USR_STA_RX_A_0           0x180
+#define SPDIF_USR_DAT_TX_A_0           0x1C0
+
+/*
+ * Register SPDIF_CTRL_0
+ */
+
+/*
+ * 1=start capturing from left channel,0=start
+ * capturing from right channel.
+ */
+#define SPDIF_CTRL_0_CAP_LC                    (1<<30)
+
+/* SPDIF receiver(RX): 1=enable, 0=disable. */
+#define SPDIF_CTRL_0_RX_EN                     (1<<29)
+
+/* SPDIF Transmitter(TX):      1=enable, 0=disable. */
+#define SPDIF_CTRL_0_TX_EN                     (1<<28)
+
+/* Transmit Channel status:    1=enable, 0=disable. */
+#define SPDIF_CTRL_0_TC_EN                     (1<<27)
+
+/* Transmit user Data:         1=enable, 0=disable. */
+#define SPDIF_CTRL_0_TU_EN                     (1<<26)
+
+/* Interrupt on transmit error:         1=enable, 0=disable. */
+#define SPDIF_CTRL_0_IE_TXE                    (1<<25)
+
+/* Interrupt on receive error:  1=enable, 0=disable. */
+#define SPDIF_CTRL_0_IE_RXE                    (1<<24)
+
+/* Interrupt on invalid preamble:       1=enable, 0=disable. */
+#define SPDIF_CTRL_0_IE_P                      (1<<23)
+
+/* Interrupt on "B" preamble:     1=enable, 0=disable. */
+#define SPDIF_CTRL_0_IE_B                      (1<<22)
+
+/*
+ * Interrupt when block of channel status received:
+ * 1=enable, 0=disable.
+ */
+#define SPDIF_CTRL_0_IE_C                      (1<<21)
+
+/*
+ * Interrupt when a valid information unit (IU) recieve:
+ * 1=enable, 0=disable.
+ */
+#define SPDIF_CTRL_0_IE_U                      (1<<20)
+
+/*
+ * Interrupt when RX user FIFO attn. level is reached:
+ * 1=enable, 0=disable.
+ */
+#define SPDIF_CTRL_0_QE_RU                     (1<<19)
+
+/*
+ * Interrupt when TX user FIFO attn. level is reached:
+ * 1=enable, 0=disable.
+ */
+#define SPDIF_CTRL_0_QE_TU                     (1<<18)
+
+/*
+ * Interrupt when RX data FIFO attn. level is reached:
+ * 1=enable, 0=disable.
+ */
+#define SPDIF_CTRL_0_QE_RX                     (1<<17)
+
+/*
+ * Interrupt when TX data FIFO attn. level is reached:
+ * 1=enable, 0=disable.
+ */
+#define SPDIF_CTRL_0_QE_TX                     (1<<16)
+
+/* Loopback test mode:   1=enable internal loopback, 0=Normal mode. */
+#define SPDIF_CTRL_0_LBK_EN                    (1<<15)
+
+/*
+ * Pack data mode:
+ * 1=Packeted left/right channel data into a single word,
+ * 0=Single data (16 bit needs to be  padded to match the
+ * interface data bit size)
+ */
+#define SPDIF_CTRL_0_PACK               (1<<14)
+
+/*
+ * 00=16bit data
+ * 01=20bit data
+ * 10=24bit data
+ * 11=raw data
+ */
+#define SPDIF_BIT_MODE_MODE16BIT       (0)
+#define SPDIF_BIT_MODE_MODE20BIT       (1)
+#define SPDIF_BIT_MODE_MODE24BIT       (2)
+#define SPDIF_BIT_MODE_MODERAW         (3)
+#define SPDIF_CTRL_0_BIT_MODE_SHIFT    (12)
+
+#define SPDIF_CTRL_0_BIT_MODE_MASK             \
+               ((0x3) << SPDIF_CTRL_0_BIT_MODE_SHIFT)
+#define SPDIF_CTRL_0_BIT_MODE_MODE16BIT                \
+       (SPDIF_BIT_MODE_MODE16BIT << SPDIF_CTRL_0_BIT_MODE_SHIFT)
+#define SPDIF_CTRL_0_BIT_MODE_MODE20BIT                \
+       (SPDIF_BIT_MODE_MODE20BIT << SPDIF_CTRL_0_BIT_MODE_SHIFT)
+#define SPDIF_CTRL_0_BIT_MODE_MODE24BIT                \
+       (SPDIF_BIT_MODE_MODE24BIT << SPDIF_CTRL_0_BIT_MODE_SHIFT)
+#define SPDIF_CTRL_0_BIT_MODE_MODERAW          \
+               (SPDIF_BIT_MODE_MODERAW << SPDIF_CTRL_0_BIT_MODE_SHIFT)
+
+
+/*
+ *  SPDIF Status Register
+ * -------------------------
+ * Note:  IS_P, IS_B, IS_C, and IS_U are sticky bits.
+ * Software must write a 1 to the corresponding bit location
+ * to clear the status.
+ */
+
+/* Register SPDIF_STATUS_0 */
+
+/*
+ * Receiver(RX) shifter is busy receiving data.  1=busy, 0=not busy.
+ * This bit is asserted when the receiver first locked onto the
+ * preamble of the data stream after RX_EN is asserted.  This bit is
+ * deasserted when either,
+ * (a)  the end of a frame is reached after RX_EN is deeasserted, or
+ * (b)  the SPDIF data stream becomes inactive.
+ */
+#define SPDIF_STATUS_0_RX_BSY                  (1<<29)
+
+
+/*
+ * Transmitter(TX) shifter is busy transmitting data.
+ * 1=busy, 0=not busy.
+ * This bit is asserted when TX_EN is asserted.
+ * This bit is deasserted when the end of a frame is reached after
+ * TX_EN is deasserted.
+ */
+#define SPDIF_STATUS_0_TX_BSY                  (1<<28)
+
+/*
+ * TX is busy shifting out channel status.  1=busy, 0=not busy.
+ * This bit is asserted when both TX_EN and TC_EN are asserted and
+ * data from CH_STA_TX_A register is loaded into the internal shifter.
+ * This bit is deasserted when either,
+ * (a) the end of a frame is reached after TX_EN is deasserted, or
+ * (b) CH_STA_TX_F register is loaded into the internal shifter.
+ */
+#define SPDIF_STATUS_0_TC_BSY                  (1<<27)
+
+/*
+ * TX User data FIFO busy.  1=busy, 0=not busy.
+ * This bit is asserted when TX_EN and TXU_EN are asserted and
+ * there's data in the TX user FIFO.  This bit is deassert when either,
+ * (a) the end of a frame is reached after TX_EN is deasserted, or
+ * (b) there's no data left in the TX user FIFO.
+ */
+#define SPDIF_STATUS_0_TU_BSY                  (1<<26)
+
+/* Tx FIFO Underrun error status:      1=error, 0=no error */
+#define SPDIF_STATUS_0_TX_ERR                  (1<<25)
+
+/* Rx FIFO Overrun error status:        1=error, 0=no error */
+#define SPDIF_STATUS_0_RX_ERR                  (1<<24)
+
+/* Preamble status:    1=bad/missing preamble, 0=Preamble ok */
+#define SPDIF_STATUS_0_IS_P                    (1<<23)
+
+/* B-preamble detection status: 0=not detected, 1=B-preamble detected */
+#define SPDIF_STATUS_0_IS_B                    (1<<22)
+
+/*
+ * RX channel block data receive status:
+ * 1=received entire block of channel status,
+ * 0=entire block not recieved yet.
+ */
+#define SPDIF_STATUS_0_IS_C                    (1<<21)
+
+/* RX User Data Valid flag:  1=valid IU detected, 0 = no IU detected. */
+#define SPDIF_STATUS_0_IS_U                    (1<<20)
+
+/*
+ * RX User FIFO Status:
+ * 1=attention level reached, 0=attention level not reached.
+ */
+#define SPDIF_STATUS_0_QS_RU                   (1<<19)
+
+/*
+ * TX User FIFO Status:
+ * 1=attention level reached, 0=attention level not reached.
+ */
+#define SPDIF_STATUS_0_QS_TU                   (1<<18)
+
+/*
+ * RX Data FIFO Status:
+ * 1=attention level reached, 0=attention level not reached.
+ */
+#define SPDIF_STATUS_0_QS_RX                   (1<<17)
+
+/*
+ * TX Data FIFO Status:
+ * 1=attention level reached, 0=attention level not reached.
+ */
+#define SPDIF_STATUS_0_QS_TX                   (1<<16)
+
+
+/* SPDIF FIFO Configuration and Status Register */
+
+/* Register SPDIF_DATA_FIFO_CSR_0 */
+
+#define SPDIF_FIFO_ATN_LVL_ONE_SLOT            0
+#define SPDIF_FIFO_ATN_LVL_FOUR_SLOTS          1
+#define SPDIF_FIFO_ATN_LVL_EIGHT_SLOTS         2
+#define SPDIF_FIFO_ATN_LVL_TWELVE_SLOTS                3
+
+
+/* Clear Receiver User FIFO (RX USR.FIFO) */
+#define SPDIF_DATA_FIFO_CSR_0_RU_CLR           (1<<31)
+
+/*
+ * RX USR.FIFO Attention Level:
+ * 00=1-slot-full, 01=2-slots-full, 10=3-slots-full, 11=4-slots-full.
+ */
+
+#define SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU1           (0)
+#define SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU2           (1)
+#define SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU3           (2)
+#define SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU4           (3)
+
+#define SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_SHIFT         (29)
+#define SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_MASK  \
+               (0x3 << SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU1_WORD_FULL         \
+               (SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU1 <<        \
+                       SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_SHIF)
+#define SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU2_WORD_FULL         \
+               (SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU2 <<        \
+                       SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_SHIF)
+#define SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU3_WORD_FULL         \
+               (SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU3 <<        \
+                       SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_SHIF)
+#define SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU4_WORD_FULL         \
+               (SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_RU4 <<        \
+                       SPDIF_DATA_FIFO_CSR_0_RU_ATN_LVL_SHIF)
+
+/* Number of RX USR.FIFO levels with valid data. */
+#define SPDIF_DATA_FIFO_CSR_0_FULL_COUNT_SHIFT         (24)
+#define SPDIF_DATA_FIFO_CSR_0_FULL_COUNT_MASK                  \
+               (0x1f << SPDIF_DATA_FIFO_CSR_0_FULL_COUNT_SHIFT)
+
+/* Clear Transmitter User FIFO (TX USR.FIFO) */
+#define SPDIF_DATA_FIFO_CSR_0_TU_CLR                    (1<<23)
+
+/*
+ * TxUSR.FIFO Attention Level:
+ * 11=4-slots-empty, 10=3-slots-empty, 01=2-slots-empty, 00=1-slot-empty.
+ */
+
+#define SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU1           (0)
+#define SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU2           (1)
+#define SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU3           (2)
+#define SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU4           (3)
+
+#define SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_SHIFT         (21)
+#define SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_MASK                  \
+               (0x3 << SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU1_WORD_EMPTY                \
+               (SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU1 <<        \
+                       SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU2_WORD_EMPTY                \
+               (SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU2 <<        \
+                       SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU3_WORD_EMPTY                \
+               (SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU3 <<        \
+                       SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU4_WORD_EMPTY                \
+               (SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_TU4 <<        \
+                       SPDIF_DATA_FIFO_CSR_0_TU_ATN_LVL_SHIFT)
+
+/* Number of Tx USR.FIFO levels that could be filled. */
+#define SPDIF_DATA_FIFO_CSR_0_TU_EMPTY_COUNT_SHIFT             (16)
+#define SPDIF_DATA_FIFO_CSR_0_TU_EMPTY_COUNT_FIELD             \
+               ((0x1f) << SPDIF_DATA_FIFO_CSR_0_TU_EMPTY_COUNT_SHIFT)
+
+/* Clear Receiver Data FIFO (RX DATA.FIFO). */
+#define SPDIF_DATA_FIFO_CSR_0_RX_CLR                   (1<<15)
+
+/*
+ * Rx FIFO Attention Level:
+ * 11=12-slots-full, 10=8-slots-full, 01=4-slots-full, 00=1-slot-full.
+ */
+#define SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_SHIFT         (13)
+#define SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_MASK                  \
+               (0x3 << SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_RX1_WORD_FULL         \
+       (SPDIF_FIFO_ATN_LVL_ONE_SLOT <<                         \
+               SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_RX4_WORD_FULL         \
+               (SPDIF_FIFO_ATN_LVL_FOUR_SLOTS <<               \
+                       SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_RX8_WORD_FULL         \
+               (SPDIF_FIFO_ATN_LVL_EIGHT_SLOTS <<              \
+                       SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_RX12_WORD_FULL                \
+               (SPDIF_FIFO_ATN_LVL_TWELVE_SLOTS <<             \
+                       SPDIF_DATA_FIFO_CSR_0_RX_ATN_LVL_SHIFT)
+
+
+/* Number of RX DATA.FIFO levels with valid data */
+#define SPDIF_DATA_FIFO_CSR_0_RX_DATA_FIFO_FULL_COUNT_SHIFT    (8)
+#define SPDIF_DATA_FIFO_CSR_0_RX_DATA_FIFO_FULL_COUNT_FIELD    \
+       ((0x1f) << SPDIF_DATA_FIFO_CSR_0_RX_DATA_FIFO_FULL_COUNT_SHIFT)
+
+/* Clear Transmitter Data FIFO (TX DATA.FIFO) */
+#define SPDIF_DATA_FIFO_CSR_0_TX_CLR                   (1<<7)
+
+/*
+ * Tx FIFO Attention Level:
+ * 11=12-slots-empty, 10=8-slots-empty, 01=4-slots-empty, 00=1-slot-empty
+ */
+#define SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_SHIFT         (5)
+#define SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_MASK                  \
+               (0x3 << SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_TX1_WORD_FULL         \
+               (SPDIF_FIFO_ATN_LVL_ONE_SLOT <<                 \
+                       SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_TX4_WORD_FULL         \
+               (SPDIF_FIFO_ATN_LVL_FOUR_SLOTS <<               \
+                       SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_TX8_WORD_FULL         \
+               (SPDIF_FIFO_ATN_LVL_EIGHT_SLOTS <<              \
+                       SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_SHIFT)
+#define SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_TX12_WORD_FULL                \
+               (SPDIF_FIFO_ATN_LVL_TWELVE_SLOTS <<             \
+                       SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_SHIFT)
+
+
+/* Number of Tx DATA.FIFO levels that could be filled. */
+#define SPDIF_DATA_FIFO_CSR_0_TD_EMPTY_COUNT_SHIFT             (0)
+#define SPDIF_DATA_FIFO_CSR_0_TD_EMPTY_COUNT_MASK              \
+               ((0x1f) << SPDIF_DATA_FIFO_CSR_0_TD_EMPTY_COUNT_SHIFT)
+
+
+#endif /* __ARCH_ARM_MACH_TEGRA_SPDIF_H */
diff --git a/arch/arm/mach-tegra/tegra_spdif_audio.c b/arch/arm/mach-tegra/tegra_spdif_audio.c
new file mode 100644 (file)
index 0000000..9c6ae5b
--- /dev/null
@@ -0,0 +1,1396 @@
+/*
+ * arch/arm/mach-tegra/tegra_spdif_audio.c
+ *
+ * S/PDIF audio driver for NVIDIA Tegra SoCs
+ *
+ * Copyright (c) 2008-2009, 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/module.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/mutex.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/err.h>
+#include <linux/spi/spi.h>
+#include <linux/kfifo.h>
+#include <linux/debugfs.h>
+#include <linux/completion.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/ktime.h>
+#include <linux/sysfs.h>
+#include <linux/pm_qos_params.h>
+#include <linux/delay.h>
+#include <linux/tegra_audio.h>
+
+#include <mach/dma.h>
+#include <mach/iomap.h>
+#include <mach/spdif.h>
+#include <mach/audio.h>
+#include <mach/irqs.h>
+
+#include "clock.h"
+
+#define PCM_BUFFER_MAX_SIZE_ORDER      (PAGE_SHIFT + 2)
+#define PCM_BUFFER_DMA_CHUNK_SIZE_ORDER        PAGE_SHIFT
+#define PCM_BUFFER_THRESHOLD_ORDER     (PCM_BUFFER_MAX_SIZE_ORDER - 1)
+#define PCM_DMA_CHUNK_MIN_SIZE_ORDER   3
+
+#define PCM_IN_BUFFER_PADDING          (1<<6) /* bytes */
+
+/* per stream (input/output) */
+struct audio_stream {
+       int opened;
+       struct mutex lock;
+
+       struct tegra_audio_buf_config buf_config;
+       bool active; /* is DMA or PIO in progress? */
+       void *buffer;
+       dma_addr_t buf_phys;
+       struct kfifo fifo;
+       struct completion fifo_completion;
+       struct scatterlist sg;
+
+       struct tegra_audio_error_counts errors;
+
+       int spdif_fifo_atn_level;
+
+       ktime_t last_dma_ts;
+       struct tegra_dma_channel *dma_chan;
+       bool stop;
+       struct completion stop_completion;
+       spinlock_t dma_req_lock; /* guards dma_has_it */
+       int dma_has_it;
+       struct tegra_dma_req dma_req;
+
+       struct pm_qos_request_list pm_qos;
+};
+
+struct spdif_pio_stats {
+       u32 spdif_interrupt_count;
+       u32 tx_fifo_errors;
+       u32 tx_fifo_written;
+};
+
+
+struct audio_driver_state {
+       struct list_head next;
+
+       struct platform_device *pdev;
+       struct tegra_audio_platform_data *pdata;
+       phys_addr_t spdif_phys;
+       unsigned long spdif_base;
+
+       bool using_dma;
+       unsigned long dma_req_sel;
+
+       int irq; /* for pio mode */
+       struct spdif_pio_stats pio_stats;
+       const int *in_divs;
+       int in_divs_len;
+
+       struct miscdevice misc_out;
+       struct miscdevice misc_out_ctl;
+       struct audio_stream out;
+};
+
+static inline int buf_size(struct audio_stream *s)
+{
+       return 1 << s->buf_config.size;
+}
+
+static inline int chunk_size(struct audio_stream *s)
+{
+       return 1 << s->buf_config.chunk;
+}
+
+static inline int threshold_size(struct audio_stream *s)
+{
+       return 1 << s->buf_config.threshold;
+}
+
+static inline struct audio_driver_state *ads_from_misc_out(struct file *file)
+{
+       struct miscdevice *m = file->private_data;
+       struct audio_driver_state *ads =
+                       container_of(m, struct audio_driver_state, misc_out);
+       BUG_ON(!ads);
+       return ads;
+}
+
+static inline struct audio_driver_state *ads_from_misc_out_ctl(
+               struct file *file)
+{
+       struct miscdevice *m = file->private_data;
+       struct audio_driver_state *ads =
+                       container_of(m, struct audio_driver_state,
+                                       misc_out_ctl);
+       BUG_ON(!ads);
+       return ads;
+}
+
+static inline struct audio_driver_state *ads_from_out(
+                       struct audio_stream *aos)
+{
+       return container_of(aos, struct audio_driver_state, out);
+}
+
+static inline void prevent_suspend(struct audio_stream *as)
+{
+       pr_debug("%s\n", __func__);
+       pm_qos_update_request(&as->pm_qos, 0);
+}
+
+static inline void allow_suspend(struct audio_stream *as)
+{
+       pr_debug("%s\n", __func__);
+       pm_qos_update_request(&as->pm_qos, PM_QOS_DEFAULT_VALUE);
+}
+
+#define I2S_I2S_FIFO_TX_BUSY   I2S_I2S_STATUS_FIFO1_BSY
+#define I2S_I2S_FIFO_TX_QS     I2S_I2S_STATUS_QS_FIFO1
+#define I2S_I2S_FIFO_TX_ERR    I2S_I2S_STATUS_FIFO1_ERR
+
+#define I2S_I2S_FIFO_RX_BUSY   I2S_I2S_STATUS_FIFO2_BSY
+#define I2S_I2S_FIFO_RX_QS     I2S_I2S_STATUS_QS_FIFO2
+#define I2S_I2S_FIFO_RX_ERR    I2S_I2S_STATUS_FIFO2_ERR
+
+#define I2S_FIFO_ERR (I2S_I2S_STATUS_FIFO1_ERR | I2S_I2S_STATUS_FIFO2_ERR)
+
+
+static inline void spdif_writel(unsigned long base, u32 val, u32 reg)
+{
+       writel(val, base + reg);
+}
+
+static inline u32 spdif_readl(unsigned long base, u32 reg)
+{
+       return readl(base + reg);
+}
+
+static inline void spdif_fifo_write(unsigned long base, u32 data)
+{
+       spdif_writel(base, data, SPDIF_DATA_OUT_0);
+}
+
+static int spdif_fifo_set_attention_level(unsigned long base,
+                       unsigned level)
+{
+       u32 val;
+
+       if (level > SPDIF_FIFO_ATN_LVL_TWELVE_SLOTS) {
+               pr_err("%s: invalid fifo level selector %d\n", __func__,
+                       level);
+               return -EINVAL;
+       }
+
+       val = spdif_readl(base, SPDIF_DATA_FIFO_CSR_0);
+
+       val &= ~SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_MASK;
+       val |= level << SPDIF_DATA_FIFO_CSR_0_TX_ATN_LVL_SHIFT;
+
+
+       spdif_writel(base, val, SPDIF_DATA_FIFO_CSR_0);
+       return 0;
+}
+
+static void spdif_fifo_enable(unsigned long base, int on)
+{
+       u32 val = spdif_readl(base, SPDIF_CTRL_0);
+       val &= ~(SPDIF_CTRL_0_TX_EN | SPDIF_CTRL_0_TC_EN);
+       val |= on ? (SPDIF_CTRL_0_TX_EN) : 0;
+       val |= on ? (SPDIF_CTRL_0_TC_EN) : 0;
+
+       spdif_writel(base, val, SPDIF_CTRL_0);
+}
+
+static bool spdif_is_fifo_enabled(unsigned long base)
+{
+       u32 val = spdif_readl(base, SPDIF_CTRL_0);
+       return !!(val & SPDIF_CTRL_0_TX_EN);
+}
+
+static void spdif_fifo_clear(unsigned long base)
+{
+       u32 val = spdif_readl(base, SPDIF_DATA_FIFO_CSR_0);
+       val &= ~(SPDIF_DATA_FIFO_CSR_0_TX_CLR | SPDIF_DATA_FIFO_CSR_0_TU_CLR);
+       val |= SPDIF_DATA_FIFO_CSR_0_TX_CLR | SPDIF_DATA_FIFO_CSR_0_TU_CLR;
+       spdif_writel(base, val, SPDIF_DATA_FIFO_CSR_0);
+}
+
+
+static int spdif_set_bit_mode(unsigned long base, unsigned mode)
+{
+       u32 val = spdif_readl(base, SPDIF_CTRL_0);
+       val &= ~SPDIF_CTRL_0_BIT_MODE_MASK;
+
+       if (mode > SPDIF_BIT_MODE_MODERAW) {
+               pr_err("%s: invalid bit_size selector %d\n", __func__,
+                       mode);
+               return -EINVAL;
+       }
+
+       val |= mode << SPDIF_CTRL_0_BIT_MODE_SHIFT;
+
+       spdif_writel(base, val, SPDIF_CTRL_0);
+       return 0;
+}
+
+static int spdif_set_fifo_packed(unsigned long base, unsigned on)
+{
+       u32 val = spdif_readl(base, SPDIF_CTRL_0);
+       val &= ~SPDIF_CTRL_0_PACK;
+       val |= on ? SPDIF_CTRL_0_PACK : 0;
+       spdif_writel(base, val, SPDIF_CTRL_0);
+       return 0;
+}
+
+
+static void spdif_set_fifo_irq_on_err(unsigned long base, int on)
+{
+       u32 val = spdif_readl(base, SPDIF_CTRL_0);
+       val &= ~SPDIF_CTRL_0_IE_TXE;
+       val |= on ? SPDIF_CTRL_0_IE_TXE : 0;
+       spdif_writel(base, val, SPDIF_CTRL_0);
+}
+
+
+
+static void spdif_enable_fifos(unsigned long base, int on)
+{
+       u32 val = spdif_readl(base, SPDIF_CTRL_0);
+       if (on)
+               val |= SPDIF_CTRL_0_TX_EN | SPDIF_CTRL_0_TC_EN |
+                      SPDIF_CTRL_0_IE_TXE;
+       else
+               val &= ~(SPDIF_CTRL_0_TX_EN | SPDIF_CTRL_0_TC_EN |
+                        SPDIF_CTRL_0_IE_TXE);
+
+       spdif_writel(base, val, SPDIF_CTRL_0);
+}
+
+static inline u32 spdif_get_status(unsigned long base)
+{
+       return spdif_readl(base, SPDIF_STATUS_0);
+}
+
+static inline u32 spdif_get_control(unsigned long base)
+{
+       return spdif_readl(base, SPDIF_CTRL_0);
+}
+
+static inline void spdif_ack_status(unsigned long base)
+{
+       return spdif_writel(base, spdif_readl(base, SPDIF_STATUS_0),
+                               SPDIF_STATUS_0);
+}
+
+static inline u32 spdif_get_fifo_scr(unsigned long base)
+{
+       return spdif_readl(base, SPDIF_DATA_FIFO_CSR_0);
+}
+
+static inline phys_addr_t spdif_get_fifo_phy_base(unsigned long phy_base)
+{
+       return phy_base + SPDIF_DATA_OUT_0;
+}
+
+static inline u32 spdif_get_fifo_full_empty_count(unsigned long base)
+{
+       u32 val = spdif_readl(base, SPDIF_DATA_FIFO_CSR_0);
+       val = val >> SPDIF_DATA_FIFO_CSR_0_TD_EMPTY_COUNT_SHIFT;
+       return val & SPDIF_DATA_FIFO_CSR_0_TD_EMPTY_COUNT_MASK;
+}
+
+
+static int spdif_set_sample_rate(struct audio_driver_state *state,
+                               unsigned int sample_rate)
+{
+       unsigned int clock_freq = 0;
+       unsigned int parent_clock_freq = 0;
+       struct clk *spdif_clk;
+
+       unsigned int ch_sta[] = {
+               0x0, /* 44.1, default values */
+               0xf << 4, /* bits 36-39, original sample freq -- 44.1 */
+               0x0,
+               0x0,
+               0x0,
+               0x0,
+       };
+
+       switch (sample_rate) {
+       case 32000:
+               clock_freq = 4096000; /* 4.0960 MHz */
+               parent_clock_freq = 12288000;
+               ch_sta[0] = 0x3 << 24;
+               ch_sta[1] = 0xC << 4;
+               break;
+       case 44100:
+               clock_freq = 5644800; /* 5.6448 MHz */
+               parent_clock_freq = 11289600;
+               ch_sta[0] = 0x0;
+               ch_sta[1] = 0xF << 4;
+               break;
+       case 48000:
+               clock_freq = 6144000; /* 6.1440MHz */
+               parent_clock_freq = 12288000;
+               ch_sta[0] = 0x2 << 24;
+               ch_sta[1] = 0xD << 4;
+               break;
+       case 88200:
+               clock_freq = 11289600; /* 11.2896 MHz */
+               parent_clock_freq = 11289600;
+               break;
+       case 96000:
+               clock_freq = 12288000; /* 12.288 MHz */
+               parent_clock_freq = 12288000;
+               break;
+       case 176400:
+               clock_freq = 22579200; /* 22.5792 MHz */
+               parent_clock_freq = 11289600;
+               break;
+       case 192000:
+               clock_freq = 24576000; /* 24.5760 MHz */
+               parent_clock_freq = 12288000;
+               break;
+       default:
+               return -1;
+       }
+
+       spdif_clk = clk_get(&state->pdev->dev, NULL);
+       if (!spdif_clk) {
+               dev_err(&state->pdev->dev, "%s: could not get spdif clock\n",
+                               __func__);
+               return -EIO;
+       }
+
+       clk_set_rate(spdif_clk, clock_freq);
+       if (clk_enable(spdif_clk)) {
+               dev_err(&state->pdev->dev,
+                       "%s: failed to enable spdif_clk clock\n", __func__);
+               return -EIO;
+       }
+       pr_info("%s: spdif_clk rate %ld\n", __func__, clk_get_rate(spdif_clk));
+
+       spdif_writel(state->spdif_base, ch_sta[0], SPDIF_CH_STA_TX_A_0);
+       spdif_writel(state->spdif_base, ch_sta[1], SPDIF_CH_STA_TX_B_0);
+       spdif_writel(state->spdif_base, ch_sta[2], SPDIF_CH_STA_TX_C_0);
+       spdif_writel(state->spdif_base, ch_sta[3], SPDIF_CH_STA_TX_D_0);
+       spdif_writel(state->spdif_base, ch_sta[4], SPDIF_CH_STA_TX_E_0);
+       spdif_writel(state->spdif_base, ch_sta[5], SPDIF_CH_STA_TX_F_0);
+
+       return 0;
+}
+
+static int init_stream_buffer(struct audio_stream *,
+               struct tegra_audio_buf_config *cfg, unsigned);
+
+static int setup_dma(struct audio_driver_state *);
+static void tear_down_dma(struct audio_driver_state *);
+static int start_dma_playback(struct audio_stream *);
+static void stop_dma_playback(struct audio_stream *);
+
+static int setup_pio(struct audio_driver_state *);
+static void tear_down_pio(struct audio_driver_state *);
+static int start_pio_playback(struct audio_stream *);
+static void stop_pio_playback(struct audio_stream *);
+
+
+struct sound_ops {
+       int (*setup)(struct audio_driver_state *);
+       void (*tear_down)(struct audio_driver_state *);
+       int (*start_playback)(struct audio_stream *);
+       void (*stop_playback)(struct audio_stream *);
+};
+
+static const struct sound_ops dma_sound_ops = {
+       .setup = setup_dma,
+       .tear_down = tear_down_dma,
+       .start_playback = start_dma_playback,
+       .stop_playback = stop_dma_playback,
+};
+
+static const struct sound_ops pio_sound_ops = {
+       .setup = setup_pio,
+       .tear_down = tear_down_pio,
+       .start_playback = start_pio_playback,
+       .stop_playback = stop_pio_playback,
+};
+
+static const struct sound_ops *sound_ops = &dma_sound_ops;
+
+static int start_playback(struct audio_stream *aos)
+{
+       int rc;
+       unsigned long flags;
+       spin_lock_irqsave(&aos->dma_req_lock, flags);
+       pr_debug("%s: starting playback\n", __func__);
+       rc = sound_ops->start_playback(aos);
+       spin_unlock_irqrestore(&aos->dma_req_lock, flags);
+       if (!rc)
+               prevent_suspend(aos);
+       return rc;
+}
+
+
+static bool stop_playback_if_necessary(struct audio_stream *aos)
+{
+       unsigned long flags;
+       spin_lock_irqsave(&aos->dma_req_lock, flags);
+       if (kfifo_is_empty(&aos->fifo)) {
+               sound_ops->stop_playback(aos);
+       spin_unlock_irqrestore(&aos->dma_req_lock, flags);
+               allow_suspend(aos);
+               return true;
+       }
+       spin_unlock_irqrestore(&aos->dma_req_lock, flags);
+
+       return false;
+}
+
+/* playback */
+static bool wait_till_stopped(struct audio_stream *as)
+{
+       int rc;
+       pr_debug("%s: wait for completion\n", __func__);
+       rc = wait_for_completion_interruptible(&as->stop_completion);
+       allow_suspend(as);
+       pr_debug("%s: done: %d\n", __func__, rc);
+       return true;
+}
+
+/* Ask for playback to stop.  The _nosync means that
+ * as->lock has to be locked by the caller.
+ */
+static void request_stop_nosync(struct audio_stream *as)
+{
+       pr_debug("%s\n", __func__);
+       if (!as->stop) {
+               as->stop = true;
+               wait_till_stopped(as);
+               if (!completion_done(&as->fifo_completion)) {
+                       pr_debug("%s: complete\n", __func__);
+                       complete(&as->fifo_completion);
+               }
+       }
+       kfifo_reset(&as->fifo);
+       as->active = false; /* applies to recording only */
+       pr_debug("%s: done\n", __func__);
+}
+
+static void toggle_dma(struct audio_driver_state *ads)
+{
+       pr_info("%s: %s\n", __func__, ads->using_dma ? "pio" : "dma");
+       sound_ops->tear_down(ads);
+       sound_ops = ads->using_dma ? &pio_sound_ops : &dma_sound_ops;
+       sound_ops->setup(ads);
+       ads->using_dma = !ads->using_dma;
+}
+
+/* DMA */
+
+static int resume_dma_playback(struct audio_stream *aos);
+
+static void setup_dma_tx_request(struct tegra_dma_req *req,
+               struct audio_stream *aos);
+
+static int setup_dma(struct audio_driver_state *ads)
+{
+       int rc;
+       pr_info("%s\n", __func__);
+
+       /* setup audio playback */
+       ads->out.buf_phys = dma_map_single(&ads->pdev->dev, ads->out.buffer,
+                               1 << PCM_BUFFER_MAX_SIZE_ORDER, DMA_TO_DEVICE);
+       BUG_ON(!ads->out.buf_phys);
+       setup_dma_tx_request(&ads->out.dma_req, &ads->out);
+       ads->out.dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT);
+       if (!ads->out.dma_chan) {
+               pr_err("%s: could not allocate output SPDIF DMA channel: %ld\n",
+                       __func__, PTR_ERR(ads->out.dma_chan));
+               rc = -ENODEV;
+               goto fail_tx;
+       }
+       return 0;
+
+
+fail_tx:
+       dma_unmap_single(&ads->pdev->dev, ads->out.buf_phys,
+                       1 << PCM_BUFFER_MAX_SIZE_ORDER, DMA_TO_DEVICE);
+       tegra_dma_free_channel(ads->out.dma_chan);
+       ads->out.dma_chan = 0;
+
+       return rc;
+}
+
+static void tear_down_dma(struct audio_driver_state *ads)
+{
+       pr_info("%s\n", __func__);
+
+       tegra_dma_free_channel(ads->out.dma_chan);
+       ads->out.dma_chan = NULL;
+       dma_unmap_single(&ads->pdev->dev, ads->out.buf_phys,
+                               buf_size(&ads->out),
+                               DMA_TO_DEVICE);
+       ads->out.buf_phys = 0;
+}
+
+static void dma_tx_complete_callback(struct tegra_dma_req *req)
+{
+       unsigned long flags;
+       struct audio_stream *aos = req->dev;
+       int count = req->bytes_transferred;
+       u64 delta_us;
+       u64 max_delay_us = count * 10000 / (4 * 441);
+
+       pr_debug("%s bytes transferred %d\n", __func__, count);
+
+       aos->dma_has_it = false;
+       delta_us = ktime_to_us(ktime_sub(ktime_get_real(), aos->last_dma_ts));
+
+       if (delta_us > max_delay_us) {
+               pr_debug("%s: too late by %lld us\n", __func__,
+                       delta_us - max_delay_us);
+               aos->errors.late_dma++;
+       }
+
+       kfifo_dma_out_finish(&aos->fifo, count);
+       dma_unmap_sg(NULL, &aos->sg, 1, DMA_TO_DEVICE);
+
+       if (!completion_done(&aos->fifo_completion)) {
+               pr_debug("%s: complete (%d avail)\n", __func__,
+                               kfifo_avail(&aos->fifo));
+               complete(&aos->fifo_completion);
+       }
+
+       if (stop_playback_if_necessary(aos)) {
+               pr_debug("%s: done (stopped)\n", __func__);
+               if (!completion_done(&aos->stop_completion)) {
+                       pr_debug("%s: signalling stop completion\n", __func__);
+                       complete(&aos->stop_completion);
+               }
+               return;
+       }
+
+       spin_lock_irqsave(&aos->dma_req_lock, flags);
+       resume_dma_playback(aos);
+       spin_unlock_irqrestore(&aos->dma_req_lock, flags);
+}
+
+static void setup_dma_tx_request(struct tegra_dma_req *req,
+               struct audio_stream *aos)
+{
+       struct audio_driver_state *ads = ads_from_out(aos);
+
+       memset(req, 0, sizeof(*req));
+
+       req->complete = dma_tx_complete_callback;
+       req->dev = aos;
+       req->to_memory = false;
+       req->dest_addr = spdif_get_fifo_phy_base(ads->spdif_phys);
+       req->dest_wrap = 4;
+       req->dest_bus_width = 16;
+       req->source_bus_width = 32;
+       req->source_wrap = 0;
+       req->req_sel = ads->dma_req_sel;
+}
+
+
+/* Called with aos->dma_req_lock taken. */
+static int resume_dma_playback(struct audio_stream *aos)
+{
+       int rc;
+       struct audio_driver_state *ads = ads_from_out(aos);
+       struct tegra_dma_req *req = &aos->dma_req;
+
+       if (aos->dma_has_it) {
+               pr_debug("%s: playback already in progress\n", __func__);
+               return -EALREADY;
+       }
+
+       rc = kfifo_dma_out_prepare(&aos->fifo, &aos->sg,
+                       1, kfifo_len(&aos->fifo));
+       /* stop_playback_if_necessary() already checks to see if the fifo is
+        * empty.
+        */
+       BUG_ON(!rc);
+       rc = dma_map_sg(NULL, &aos->sg, 1, DMA_TO_DEVICE);
+       if (rc < 0) {
+               pr_err("%s: could not map dma memory: %d\n", __func__, rc);
+               return rc;
+       }
+
+#if 0
+       spdif_fifo_clear(ads->spdif_base);
+#endif
+       spdif_fifo_set_attention_level(ads->spdif_base,
+                       aos->spdif_fifo_atn_level);
+
+       req->source_addr = sg_dma_address(&aos->sg);
+       req->size = sg_dma_len(&aos->sg);
+       dma_sync_single_for_device(NULL,
+                       req->source_addr, req->size, DMA_TO_DEVICE);
+
+       /* Don't send all the data yet. */
+       if (req->size > chunk_size(aos))
+               req->size = chunk_size(aos);
+       pr_debug("%s resume playback (%d in fifo, writing %d)\n",
+                       __func__, kfifo_len(&aos->fifo), req->size);
+
+       spdif_fifo_enable(ads->spdif_base, 1);
+
+       aos->last_dma_ts = ktime_get_real();
+       rc = tegra_dma_enqueue_req(aos->dma_chan, req);
+       aos->dma_has_it = !rc;
+       if (!aos->dma_has_it)
+               pr_err("%s: could not enqueue TX DMA req\n", __func__);
+       return rc;
+}
+
+/* Called with aos->dma_req_lock taken. */
+static int start_dma_playback(struct audio_stream *aos)
+{
+       return resume_dma_playback(aos);
+}
+
+/* Called with aos->dma_req_lock taken. */
+static void stop_dma_playback(struct audio_stream *aos)
+{
+       int spin = 0;
+       struct audio_driver_state *ads = ads_from_out(aos);
+       pr_debug("%s\n", __func__);
+       spdif_fifo_enable(ads->spdif_base, 0);
+       while ((spdif_get_status(ads->spdif_base) & SPDIF_STATUS_0_TX_BSY) &&
+                       spin < 100) {
+               udelay(10);
+               if (spin++ > 50)
+                       pr_info("%s: spin %d\n", __func__, spin);
+       }
+       if (spin == 100)
+               pr_warn("%s: spinny\n", __func__);
+}
+
+/* PIO (non-DMA) */
+
+static int setup_pio(struct audio_driver_state *ads)
+{
+       pr_info("%s\n", __func__);
+       enable_irq(ads->irq);
+       return 0;
+}
+
+static void tear_down_pio(struct audio_driver_state *ads)
+{
+       pr_info("%s\n", __func__);
+       disable_irq(ads->irq);
+}
+
+static int start_pio_playback(struct audio_stream *aos)
+{
+       struct audio_driver_state *ads = ads_from_out(aos);
+
+       if (spdif_is_fifo_enabled(ads->spdif_base)) {
+               pr_debug("%s: playback is already in progress\n", __func__);
+               return -EALREADY;
+       }
+
+       pr_debug("%s\n", __func__);
+
+       spdif_fifo_set_attention_level(ads->spdif_base,
+                       aos->spdif_fifo_atn_level);
+#if 0
+       spdif_fifo_clear(ads->spdif_base);
+#endif
+
+       spdif_set_fifo_irq_on_err(ads->spdif_base, 1);
+       spdif_fifo_enable(ads->spdif_base, 1);
+
+       return 0;
+}
+
+static void stop_pio_playback(struct audio_stream *aos)
+{
+       struct audio_driver_state *ads = ads_from_out(aos);
+
+       spdif_set_fifo_irq_on_err(ads->spdif_base, 0);
+       spdif_fifo_enable(ads->spdif_base, 0);
+       while (spdif_get_status(ads->spdif_base) & SPDIF_STATUS_0_TX_BSY)
+               /* spin */;
+
+       pr_info("%s: interrupts %d\n", __func__,
+                       ads->pio_stats.spdif_interrupt_count);
+       pr_info("%s: sent       %d\n", __func__,
+                       ads->pio_stats.tx_fifo_written);
+       pr_info("%s: tx errors  %d\n", __func__,
+                       ads->pio_stats.tx_fifo_errors);
+
+       memset(&ads->pio_stats, 0, sizeof(ads->pio_stats));
+}
+
+
+static irqreturn_t spdif_interrupt(int irq, void *data)
+{
+       struct audio_driver_state *ads = data;
+       u32 status = spdif_get_status(ads->spdif_base);
+
+       pr_debug("%s: %08x\n", __func__, status);
+
+       ads->pio_stats.spdif_interrupt_count++;
+
+       if (status & SPDIF_CTRL_0_IE_TXE)
+               ads->pio_stats.tx_fifo_errors++;
+
+#if 0
+       if (status & SPDIF_STATUS_0_TX_ERR)
+#endif
+               spdif_ack_status(ads->spdif_base);
+
+       if (status & SPDIF_STATUS_0_QS_TX) {
+               int written;
+               int empty;
+               int len;
+               u16 fifo_buffer[32];
+
+               struct audio_stream *out = &ads->out;
+
+               if (!spdif_is_fifo_enabled(ads->spdif_base)) {
+                       pr_debug("%s: tx fifo not enabled, skipping\n",
+                               __func__);
+                       goto done;
+               }
+
+               pr_debug("%s tx fifo is ready\n", __func__);
+
+               if (!completion_done(&out->fifo_completion)) {
+                       pr_debug("%s: tx complete (%d avail)\n", __func__,
+                                       kfifo_avail(&out->fifo));
+                       complete(&out->fifo_completion);
+               }
+
+               if (stop_playback_if_necessary(out)) {
+                       pr_debug("%s: done (stopped)\n", __func__);
+                       if (!completion_done(&out->stop_completion)) {
+                               pr_debug("%s: signalling stop completion\n",
+                                       __func__);
+                               complete(&out->stop_completion);
+                       }
+                       goto done;
+               }
+
+               empty = spdif_get_fifo_full_empty_count(ads->spdif_base);
+
+               len = kfifo_out(&out->fifo, fifo_buffer,
+                               empty * sizeof(u16));
+               len /= sizeof(u16);
+
+               written = 0;
+               while (empty-- && written < len) {
+                       ads->pio_stats.tx_fifo_written += written * sizeof(u16);
+                       spdif_fifo_write(ads->spdif_base,
+                                       fifo_buffer[written++]);
+               }
+
+               /* TODO: Should we check to see if we wrote less than the
+                * FIFO threshold and adjust it if so?
+                */
+
+               if (written) {
+                       /* start the transaction */
+                       pr_debug("%s: enabling fifo (%d samples written)\n",
+                                       __func__, written);
+                       spdif_fifo_enable(ads->spdif_base, 1);
+               }
+       }
+
+done:
+       pr_debug("%s: done %08x\n", __func__,
+                       spdif_get_status(ads->spdif_base));
+       return IRQ_HANDLED;
+}
+
+static ssize_t tegra_spdif_write(struct file *file,
+               const char __user *buf, size_t size, loff_t *off)
+{
+       ssize_t rc = 0, total = 0;
+       unsigned nw = 0;
+
+       struct audio_driver_state *ads = ads_from_misc_out(file);
+
+       mutex_lock(&ads->out.lock);
+
+if (!IS_ALIGNED(size, 4)) {
+               pr_err("%s: user size request %d not aligned to 4\n",
+                       __func__, size);
+               rc = -EINVAL;
+               goto done;
+       }
+
+       pr_debug("%s: write %d bytes, %d available\n", __func__,
+                       size, kfifo_avail(&ads->out.fifo));
+
+again:
+       if (ads->out.stop) {
+               pr_info("%s: playback has been cancelled (%d/%d bytes)\n",
+                               __func__, total, size);
+               goto done;
+       }
+
+       rc = kfifo_from_user(&ads->out.fifo, buf + total, size - total, &nw);
+       if (rc < 0) {
+               pr_err("%s: error copying from user\n", __func__);
+               goto done;
+       }
+
+       rc = start_playback(&ads->out);
+       if (rc < 0 && rc != -EALREADY) {
+               pr_err("%s: could not start playback: %d\n", __func__, rc);
+               goto done;
+       }
+
+       total += nw;
+       if (total < size) {
+               pr_debug("%s: sleep (user %d total %d nw %d)\n", __func__,
+                               size, total, nw);
+               mutex_unlock(&ads->out.lock);
+               rc = wait_for_completion_interruptible(
+                               &ads->out.fifo_completion);
+               mutex_lock(&ads->out.lock);
+               if (rc == -ERESTARTSYS) {
+                       pr_warn("%s: interrupted\n", __func__);
+                       goto done;
+               }
+               pr_debug("%s: awake\n", __func__);
+               goto again;
+       }
+
+       rc = total;
+       *off += total;
+
+done:
+       mutex_unlock(&ads->out.lock);
+       return rc;
+}
+
+static long tegra_spdif_out_ioctl(struct file *file,
+                       unsigned int cmd, unsigned long arg)
+{
+       int rc = 0;
+       struct audio_driver_state *ads = ads_from_misc_out_ctl(file);
+       struct audio_stream *aos = &ads->out;
+
+       mutex_lock(&aos->lock);
+
+       switch (cmd) {
+       case TEGRA_AUDIO_OUT_SET_BUF_CONFIG: {
+               struct tegra_audio_buf_config cfg;
+               if (copy_from_user(&cfg, (void __user *)arg, sizeof(cfg))) {
+                       rc = -EFAULT;
+                       break;
+               }
+               if (kfifo_len(&aos->fifo)) {
+                       pr_err("%s: playback in progress\n", __func__);
+                       rc = -EBUSY;
+                       break;
+               }
+               rc = init_stream_buffer(aos, &cfg, 0);
+               if (rc < 0)
+                       break;
+               aos->buf_config = cfg;
+       }
+               break;
+       case TEGRA_AUDIO_OUT_GET_BUF_CONFIG:
+               if (copy_to_user((void __user *)arg, &aos->buf_config,
+                               sizeof(aos->buf_config)))
+                       rc = -EFAULT;
+               break;
+       case TEGRA_AUDIO_OUT_GET_ERROR_COUNT:
+               if (copy_to_user((void __user *)arg, &aos->errors,
+                               sizeof(aos->errors)))
+                       rc = -EFAULT;
+               if (!rc)
+                       memset(&aos->errors, 0, sizeof(aos->errors));
+               break;
+       case TEGRA_AUDIO_OUT_FLUSH:
+               if (kfifo_len(&aos->fifo)) {
+                       pr_debug("%s: flushing\n", __func__);
+                       request_stop_nosync(aos);
+                       pr_debug("%s: flushed\n", __func__);
+               }
+               aos->stop = false;
+               break;
+       default:
+               rc = -EINVAL;
+       }
+
+       mutex_unlock(&aos->lock);
+       return rc;
+}
+
+static int tegra_spdif_out_open(struct inode *inode, struct file *file)
+{
+       int rc = 0;
+       struct audio_driver_state *ads = ads_from_misc_out(file);
+
+       pr_info("%s\n", __func__);
+
+       mutex_lock(&ads->out.lock);
+       if (!ads->out.opened++) {
+               pr_debug("%s: resetting fifo and error count\n", __func__);
+               ads->out.stop = false;
+               memset(&ads->out.errors, 0, sizeof(ads->out.errors));
+               kfifo_reset(&ads->out.fifo);
+
+       rc = spdif_set_sample_rate(ads, 44100);
+       }
+
+       mutex_unlock(&ads->out.lock);
+
+       return rc;
+}
+
+static int tegra_spdif_out_release(struct inode *inode, struct file *file)
+{
+       struct audio_driver_state *ads = ads_from_misc_out(file);
+       struct clk *spdif_clk;
+
+       pr_info("%s\n", __func__);
+
+       mutex_lock(&ads->out.lock);
+       if (ads->out.opened)
+               ads->out.opened--;
+       if (!ads->out.opened) {
+               stop_playback_if_necessary(&ads->out);
+
+               if (kfifo_len(&ads->out.fifo))
+                       pr_err("%s: output fifo is not empty (%d bytes left)\n",
+                               __func__, kfifo_len(&ads->out.fifo));
+               allow_suspend(&ads->out);
+
+       spdif_clk = clk_get(&ads->pdev->dev, NULL);
+       if (!spdif_clk) {
+                       dev_err(&ads->pdev->dev, "%s: could not get spdif "\
+                                       "clockk\n", __func__);
+               return -EIO;
+       }
+       clk_disable(spdif_clk);
+       }
+       mutex_unlock(&ads->out.lock);
+
+       return 0;
+}
+
+static const struct file_operations tegra_spdif_out_fops = {
+       .owner = THIS_MODULE,
+       .open = tegra_spdif_out_open,
+       .release = tegra_spdif_out_release,
+       .write = tegra_spdif_write,
+};
+
+static int tegra_spdif_ctl_open(struct inode *inode, struct file *file)
+{
+       return 0;
+}
+
+static int tegra_spdif_ctl_release(struct inode *inode, struct file *file)
+{
+       return 0;
+}
+
+static const struct file_operations tegra_spdif_out_ctl_fops = {
+       .owner = THIS_MODULE,
+       .open = tegra_spdif_ctl_open,
+       .release = tegra_spdif_ctl_release,
+       .unlocked_ioctl = tegra_spdif_out_ioctl,
+};
+
+static int init_stream_buffer(struct audio_stream *s,
+                               struct tegra_audio_buf_config *cfg,
+                               unsigned padding)
+{
+       pr_info("%s (size %d threshold %d chunk %d)\n", __func__,
+               cfg->size, cfg->threshold, cfg->chunk);
+
+       if (cfg->chunk < PCM_DMA_CHUNK_MIN_SIZE_ORDER) {
+               pr_err("%s: chunk %d too small (%d min)\n", __func__,
+                       cfg->chunk, PCM_DMA_CHUNK_MIN_SIZE_ORDER);
+               return -EINVAL;
+       }
+
+       if (cfg->chunk > cfg->size) {
+               pr_err("%s: chunk %d > size %d\n", __func__,
+                               cfg->chunk, cfg->size);
+               return -EINVAL;
+       }
+
+       if (cfg->threshold > cfg->size) {
+               pr_err("%s: threshold %d > size %d\n", __func__,
+                               cfg->threshold, cfg->size);
+               return -EINVAL;
+       }
+
+       if ((1 << cfg->size) < padding) {
+               pr_err("%s: size %d < buffer padding %d (bytes)\n", __func__,
+                       cfg->size, padding);
+               return -EINVAL;
+       }
+
+       if (cfg->size > PCM_BUFFER_MAX_SIZE_ORDER) {
+               pr_err("%s: size %d exceeds max %d\n", __func__,
+                       cfg->size, PCM_BUFFER_MAX_SIZE_ORDER);
+               return -EINVAL;
+       }
+
+       if (!s->buffer) {
+               pr_debug("%s: allocating buffer (size %d, padding %d)\n",
+                               __func__, 1 << cfg->size, padding);
+               s->buffer = kmalloc((1 << cfg->size) + padding,
+                               GFP_KERNEL | GFP_DMA);
+       }
+       if (!s->buffer) {
+               pr_err("%s: could not allocate output buffer\n", __func__);
+               return -ENOMEM;
+       }
+
+       kfifo_init(&s->fifo, s->buffer, 1 << cfg->size);
+       sg_init_table(&s->sg, 1);
+       return 0;
+}
+
+
+static int setup_misc_device(struct miscdevice *misc,
+                       const struct file_operations  *fops,
+                       const char *fmt, ...)
+{
+       int rc = 0;
+       va_list args;
+       const int sz = 64;
+
+       va_start(args, fmt);
+
+       memset(misc, 0, sizeof(*misc));
+       misc->minor = MISC_DYNAMIC_MINOR;
+       misc->name  = kmalloc(sz, GFP_KERNEL);
+       if (!misc->name) {
+               rc = -ENOMEM;
+               goto done;
+       }
+
+       vsnprintf((char *)misc->name, sz, fmt, args);
+       misc->fops = fops;
+       if (misc_register(misc)) {
+               pr_err("%s: could not register %s\n", __func__, misc->name);
+               kfree(misc->name);
+               rc = -EIO;
+               goto done;
+       }
+
+done:
+       va_end(args);
+       return rc;
+}
+
+static ssize_t dma_toggle_show(struct device *dev,
+                               struct device_attribute *attr,
+                               char *buf)
+{
+       struct tegra_audio_platform_data *pdata = dev->platform_data;
+       struct audio_driver_state *ads = pdata->driver_data;
+       return sprintf(buf, "%s\n", ads->using_dma ? "dma" : "pio");
+}
+
+static ssize_t dma_toggle_store(struct device *dev,
+                       struct device_attribute *attr,
+                       const char *buf, size_t count)
+{
+       int use_dma;
+       struct tegra_audio_platform_data *pdata = dev->platform_data;
+       struct audio_driver_state *ads = pdata->driver_data;
+
+       if (count < 4)
+               return -EINVAL;
+
+       use_dma = 0;
+       if (!strncmp(buf, "dma", 3))
+               use_dma = 1;
+       else if (strncmp(buf, "pio", 3)) {
+               dev_err(dev, "%s: invalid string [%s]\n", __func__, buf);
+               return -EINVAL;
+       }
+
+       mutex_lock(&ads->out.lock);
+       if (kfifo_len(&ads->out.fifo)) {
+               dev_err(dev, "%s: playback or recording in progress.\n",
+                       __func__);
+               mutex_unlock(&ads->out.lock);
+               return -EBUSY;
+       }
+       if (!!use_dma ^ !!ads->using_dma)
+               toggle_dma(ads);
+       else
+               dev_info(dev, "%s: no change\n", __func__);
+       mutex_unlock(&ads->out.lock);
+
+       return count;
+}
+
+static DEVICE_ATTR(dma_toggle, 0644, dma_toggle_show, dma_toggle_store);
+
+static ssize_t __attr_fifo_atn_read(char *buf, int atn_lvl)
+{
+       switch (atn_lvl) {
+       case SPDIF_FIFO_ATN_LVL_ONE_SLOT:
+               strncpy(buf, "1\n", 2);
+               return 2;
+       case SPDIF_FIFO_ATN_LVL_FOUR_SLOTS:
+               strncpy(buf, "4\n", 2);
+               return 2;
+       case SPDIF_FIFO_ATN_LVL_EIGHT_SLOTS:
+               strncpy(buf, "8\n", 2);
+               return 2;
+       case SPDIF_FIFO_ATN_LVL_TWELVE_SLOTS:
+               strncpy(buf, "12\n", 3);
+               return 3;
+       default:
+               BUG_ON(1);
+               return -EIO;
+       }
+}
+
+static ssize_t __attr_fifo_atn_write(struct audio_driver_state *ads,
+               struct audio_stream *as,
+               int *fifo_lvl,
+               const char *buf, size_t size)
+{
+       int lvl;
+
+       if (size > 3) {
+               pr_err("%s: buffer size %d too big\n", __func__, size);
+               return -EINVAL;
+       }
+
+       if (sscanf(buf, "%d", &lvl) != 1) {
+               pr_err("%s: invalid input string [%s]\n", __func__, buf);
+               return -EINVAL;
+       }
+
+       switch (lvl) {
+       case 1:
+               lvl = SPDIF_FIFO_ATN_LVL_ONE_SLOT;
+               break;
+       case 4:
+               lvl = SPDIF_FIFO_ATN_LVL_FOUR_SLOTS;
+               break;
+       case 8:
+               lvl = SPDIF_FIFO_ATN_LVL_EIGHT_SLOTS;
+               break;
+       case 12:
+               lvl = SPDIF_FIFO_ATN_LVL_TWELVE_SLOTS;
+               break;
+       default:
+               pr_err("%s: invalid attention level %d\n", __func__, lvl);
+               return -EINVAL;
+       }
+
+       *fifo_lvl = lvl;
+       pr_info("%s: fifo level %d\n", __func__, *fifo_lvl);
+
+       return size;
+}
+
+static ssize_t tx_fifo_atn_show(struct device *dev,
+                               struct device_attribute *attr,
+                               char *buf)
+{
+       struct tegra_audio_platform_data *pdata = dev->platform_data;
+       struct audio_driver_state *ads = pdata->driver_data;
+       return __attr_fifo_atn_read(buf, ads->out.spdif_fifo_atn_level);
+}
+
+static ssize_t tx_fifo_atn_store(struct device *dev,
+                       struct device_attribute *attr,
+                       const char *buf, size_t count)
+{
+       ssize_t rc;
+       struct tegra_audio_platform_data *pdata = dev->platform_data;
+       struct audio_driver_state *ads = pdata->driver_data;
+       mutex_lock(&ads->out.lock);
+       if (kfifo_len(&ads->out.fifo)) {
+               pr_err("%s: playback in progress.\n", __func__);
+               rc = -EBUSY;
+               goto done;
+       }
+       rc = __attr_fifo_atn_write(ads, &ads->out,
+                       &ads->out.spdif_fifo_atn_level,
+                       buf, count);
+done:
+       mutex_unlock(&ads->out.lock);
+       return rc;
+}
+
+static DEVICE_ATTR(tx_fifo_atn, 0644, tx_fifo_atn_show, tx_fifo_atn_store);
+
+
+static int tegra_spdif_probe(struct platform_device *pdev)
+{
+       int rc;
+       struct resource *res;
+       struct audio_driver_state *state;
+
+       pr_info("%s\n", __func__);
+
+       state = kzalloc(sizeof(*state), GFP_KERNEL);
+       if (!state)
+               return -ENOMEM;
+
+       state->pdev = pdev;
+       state->pdata = pdev->dev.platform_data;
+       state->pdata->driver_data = state;
+       BUG_ON(!state->pdata);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(&pdev->dev, "no mem resource!\n");
+               return -ENODEV;
+       }
+
+       if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
+               dev_err(&pdev->dev, "memory region already claimed!\n");
+               return -ENOMEM;
+       }
+
+       state->spdif_phys = res->start;
+       state->spdif_base = (unsigned long)ioremap(res->start,
+                       res->end - res->start + 1);
+       if (!state->spdif_base) {
+               dev_err(&pdev->dev, "cannot remap iomem!\n");
+               return -EIO;
+       }
+
+       state->out.spdif_fifo_atn_level = SPDIF_FIFO_ATN_LVL_FOUR_SLOTS;
+
+       res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+       if (!res) {
+               dev_err(&pdev->dev, "no dma resource!\n");
+               return -ENODEV;
+       }
+       state->dma_req_sel = res->start;
+
+       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (!res) {
+               dev_err(&pdev->dev, "no irq resource!\n");
+               return -ENODEV;
+       }
+       state->irq = res->start;
+
+       memset(&state->pio_stats, 0, sizeof(state->pio_stats));
+
+       /* disable interrupts from SPDIF */
+       spdif_fifo_clear(state->spdif_base);
+       spdif_enable_fifos(state->spdif_base, 0);
+
+       spdif_set_bit_mode(state->spdif_base, state->pdata->mode);
+       spdif_set_fifo_packed(state->spdif_base, state->pdata->fifo_fmt);
+
+       state->out.opened = 0;
+       state->out.active = false;
+       mutex_init(&state->out.lock);
+       init_completion(&state->out.fifo_completion);
+       init_completion(&state->out.stop_completion);
+       spin_lock_init(&state->out.dma_req_lock);
+       state->out.buf_phys = 0;
+       state->out.dma_chan = NULL;
+       state->out.dma_has_it = false;
+
+       state->out.buffer = 0;
+       state->out.buf_config.size = PCM_BUFFER_MAX_SIZE_ORDER;
+       state->out.buf_config.threshold = PCM_BUFFER_THRESHOLD_ORDER;
+       state->out.buf_config.chunk = PCM_BUFFER_DMA_CHUNK_SIZE_ORDER;
+       rc = init_stream_buffer(&state->out, &state->out.buf_config, 0);
+       if (rc < 0)
+               return rc;
+
+       pm_qos_add_request(&state->out.pm_qos, PM_QOS_CPU_DMA_LATENCY,
+                               PM_QOS_DEFAULT_VALUE);
+
+       if (request_irq(state->irq, spdif_interrupt,
+                       IRQF_DISABLED, state->pdev->name, state) < 0) {
+               dev_err(&pdev->dev,
+                       "%s: could not register handler for irq %d\n",
+                       __func__, state->irq);
+               return -EIO;
+       }
+
+       rc = setup_misc_device(&state->misc_out,
+                       &tegra_spdif_out_fops,
+                       "spdif_out");
+       if (rc < 0)
+               return rc;
+
+       rc = setup_misc_device(&state->misc_out_ctl,
+                       &tegra_spdif_out_ctl_fops,
+                       "spdif_out_ctl");
+       if (rc < 0)
+               return rc;
+
+       state->using_dma = state->pdata->dma_on;
+       if (!state->using_dma)
+               sound_ops = &pio_sound_ops;
+       sound_ops->setup(state);
+
+       rc = device_create_file(&pdev->dev, &dev_attr_dma_toggle);
+       if (rc < 0) {
+               dev_err(&pdev->dev, "%s: could not create sysfs entry %s: %d\n",
+                       __func__, dev_attr_dma_toggle.attr.name, rc);
+               return rc;
+       }
+
+       rc = device_create_file(&pdev->dev, &dev_attr_tx_fifo_atn);
+       if (rc < 0) {
+               dev_err(&pdev->dev, "%s: could not create sysfs entry %s: %d\n",
+                       __func__, dev_attr_tx_fifo_atn.attr.name, rc);
+               return rc;
+       }
+
+       return 0;
+}
+
+static struct platform_driver tegra_spdif_driver = {
+       .driver = {
+               .name = "spdif_out",
+               .owner = THIS_MODULE,
+       },
+       .probe = tegra_spdif_probe,
+};
+
+static int __init tegra_spdif_init(void)
+{
+       return platform_driver_register(&tegra_spdif_driver);
+}
+
+module_init(tegra_spdif_init);
+MODULE_LICENSE("GPL");
diff --git a/include/linux/tegra_spdif.h b/include/linux/tegra_spdif.h
new file mode 100644 (file)
index 0000000..8d7f645
--- /dev/null
@@ -0,0 +1,56 @@
+/* include/linux/tegra_spdif.h
+ *
+ * SPDIF audio driver for NVIDIA Tegra SoCs
+ *
+ * Copyright (c) 2008-2009, 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.
+ */
+
+#ifndef _TEGRA_SPDIF_H
+#define _TEGRA_SPDIF_H
+
+#include <linux/ioctl.h>
+
+#define TEGRA_SPDIF_MAGIC 's'
+
+
+
+struct tegra_audio_buf_config {
+       unsigned size; /* order */
+       unsigned threshold; /* order */
+       unsigned chunk; /* order */
+};
+
+
+
+#define TEGRA_AUDIO_OUT_SET_BUF_CONFIG _IOW(TEGRA_SPDIF_MAGIC, 0, \
+                       const struct tegra_audio_buf_config *)
+#define TEGRA_AUDIO_OUT_GET_BUF_CONFIG _IOR(TEGRA_SPDIF_MAGIC, 1, \
+                       struct tegra_audio_buf_config *)
+
+#define TEGRA_AUDIO_OUT_GET_ERROR_COUNT        _IOR(TEGRA_SPDIF_MAGIC, 2, \
+                       unsigned *)
+
+struct tegra_audio_out_preload {
+       void *data;
+       size_t len;
+       size_t len_written;
+};
+
+#define TEGRA_AUDIO_OUT_PRELOAD_FIFO   _IOWR(TEGRA_SPDIF_MAGIC, 3, \
+                       struct tegra_audio_out_preload *)
+
+#endif/*_TEGRA_SPDIF_H*/