summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilippe Langlais <philippe.langlais@stericsson.com>2012-06-04 19:45:32 +0800
committerPhilippe Langlais <philippe.langlais@stericsson.com>2012-06-04 19:45:32 +0800
commit5a5c0904d7eab2f2ce51419fbcd4e63247413d16 (patch)
treeda0807c42b20db8689b902e6378099523484c7f2
parent1aba37fc5314da4ea3c3eb759b9e4c52c4404136 (diff)
parent16be2ec95a152e65a7522f23a4c9d019325cc41d (diff)
Merge topic branch 'audio' into integration-linux-ux500
Signed-off-by: Philippe Langlais <philippe.langlais@stericsson.com>
-rw-r--r--Documentation/DocBook/i2s.tmpl97
-rw-r--r--Documentation/DocBook/msp.tmpl104
-rw-r--r--arch/arm/mach-ux500/board-mop500-msp.c193
-rw-r--r--arch/arm/mach-ux500/include/mach/msp.h998
-rw-r--r--drivers/spi/Kconfig14
-rw-r--r--drivers/spi/Makefile1
-rw-r--r--drivers/spi/stm_msp.c1929
-rw-r--r--include/linux/spi/stm_msp.h126
-rw-r--r--include/sound/ux500_ab8500.h36
-rw-r--r--include/sound/ux500_ab8500_ext.h27
-rw-r--r--sound/arm/Kconfig12
-rw-r--r--sound/arm/Makefile4
-rw-r--r--sound/arm/u8500_alsa_ab8500.c2691
-rw-r--r--sound/arm/u8500_alsa_ab8500.h165
-rw-r--r--sound/arm/u8500_alsa_hdmi.c936
-rw-r--r--sound/core/pcm_lib.c2
-rw-r--r--sound/soc/Kconfig3
-rw-r--r--sound/soc/Makefile1
-rw-r--r--sound/soc/codecs/Kconfig22
-rw-r--r--sound/soc/codecs/Makefile16
-rw-r--r--sound/soc/codecs/ab3550.c1429
-rw-r--r--sound/soc/codecs/ab3550.h333
-rwxr-xr-xsound/soc/codecs/ab5500.c1806
-rw-r--r--sound/soc/codecs/ab5500.h408
-rw-r--r--sound/soc/codecs/ab8500_audio.c2956
-rw-r--r--sound/soc/codecs/ab8500_audio.h683
-rw-r--r--sound/soc/codecs/av8100_audio.c527
-rw-r--r--sound/soc/codecs/av8100_audio.h163
-rw-r--r--sound/soc/codecs/cg29xx.c772
-rw-r--r--sound/soc/codecs/cg29xx.h41
-rw-r--r--sound/soc/ux500/Kconfig67
-rw-r--r--sound/soc/ux500/Makefile46
-rwxr-xr-xsound/soc/ux500/u5500.c195
-rw-r--r--sound/soc/ux500/u8500.c245
-rw-r--r--sound/soc/ux500/ux500_ab3550.c76
-rw-r--r--sound/soc/ux500/ux500_ab3550.h19
-rwxr-xr-xsound/soc/ux500/ux500_ab5500.c132
-rwxr-xr-xsound/soc/ux500/ux500_ab5500.h28
-rw-r--r--sound/soc/ux500/ux500_ab8500.c1064
-rw-r--r--sound/soc/ux500/ux500_av8100.c167
-rw-r--r--sound/soc/ux500/ux500_av8100.h19
-rw-r--r--sound/soc/ux500/ux500_cg29xx.c227
-rw-r--r--sound/soc/ux500/ux500_cg29xx.h20
-rw-r--r--sound/soc/ux500/ux500_msp_dai.c947
-rw-r--r--sound/soc/ux500/ux500_msp_dai.h72
-rw-r--r--sound/soc/ux500/ux500_msp_i2s.c837
-rw-r--r--sound/soc/ux500/ux500_msp_i2s.h40
-rw-r--r--sound/soc/ux500/ux500_pcm.c571
-rw-r--r--sound/soc/ux500/ux500_pcm.h50
-rw-r--r--sound/usb/card.c4
50 files changed, 21319 insertions, 2 deletions
diff --git a/Documentation/DocBook/i2s.tmpl b/Documentation/DocBook/i2s.tmpl
new file mode 100644
index 00000000000..6b6c50572e2
--- /dev/null
+++ b/Documentation/DocBook/i2s.tmpl
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" []>
+
+<book id="I2S">
+ <bookinfo>
+ <title>I2S</title>
+
+ <authorgroup>
+ <author>
+ <firstname>Sandeep</firstname>
+ <surname>Kaushik</surname>
+ <affiliation>
+ <address>
+ <email>sandeep.kaushik@st.com</email>
+ </address>
+ </affiliation>
+ </author>
+ </authorgroup>
+
+ <copyright>
+ <year>2008-2009</year>
+ <holder>STMicroelectronics Pvt Ltd</holder>
+ </copyright>
+
+ <subjectset>
+ <subject>
+ <subjectterm>Linux standard functions</subjectterm>
+ </subject>
+ </subjectset>
+
+ <legalnotice>
+ <!-- Do NOT remove the legal notice below -->
+
+ <para>
+ This documentation 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.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ 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., 59 Temple Place, Suite 330, Boston,
+ MA 02111-1307 USA
+ </para>
+
+ <para>
+ For more details see the file COPYING in the source
+ distribution of Linux.
+ </para>
+ </legalnotice>
+ </bookinfo>
+
+<toc></toc>
+ <chapter id="intro">
+ <title>Introduction</title>
+ <para>
+ This Documentation describes the APIs provided by the I2S Bus Driver. I2S bus supports different
+ protocols like I2S, PCM, SPI etc.
+ </para>
+ </chapter>
+
+ <chapter id="bugs">
+ <title>Known Bugs And Assumptions</title>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>None</term>
+ <listitem>
+ <para>
+ None.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </chapter>
+
+ <chapter id="pubfunctions">
+ <title>Public Functions Provided</title>
+ <para>
+ This Section lists the functions exported by the I2S bus driver. These functions cater to all the protocols
+ supported namely: I2S, PCM, SPI.
+ </para>
+!Edrivers/misc/i2s/i2s.c
+ </chapter>
+</book>
diff --git a/Documentation/DocBook/msp.tmpl b/Documentation/DocBook/msp.tmpl
new file mode 100644
index 00000000000..55cec352a76
--- /dev/null
+++ b/Documentation/DocBook/msp.tmpl
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" []>
+
+<book id="MSP">
+ <bookinfo>
+ <title>MSP</title>
+
+ <authorgroup>
+ <author>
+ <firstname>Sandeep</firstname>
+ <surname>Kaushik</surname>
+ <affiliation>
+ <address>
+ <email>sandeep.kaushik@st.com</email>
+ </address>
+ </affiliation>
+ </author>
+ </authorgroup>
+
+ <copyright>
+ <year>2008-2009</year>
+ <holder>STMicroelectronics Pvt Ltd</holder>
+ </copyright>
+
+ <subjectset>
+ <subject>
+ <subjectterm>Linux standard functions</subjectterm>
+ </subject>
+ </subjectset>
+
+ <legalnotice>
+ <!-- Do NOT remove the legal notice below -->
+
+ <para>
+ This documentation 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.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ 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., 59 Temple Place, Suite 330, Boston,
+ MA 02111-1307 USA
+ </para>
+
+ <para>
+ For more details see the file COPYING in the source
+ distribution of Linux.
+ </para>
+ </legalnotice>
+ </bookinfo>
+
+<toc></toc>
+ <chapter id="intro">
+ <title>Introduction</title>
+ <para>
+ This Documentation describes the API's provided by the MSP controller Driver.
+ MSP controller supports different protocols like I2S, PCM, SPI etc.
+ </para>
+ </chapter>
+
+ <chapter id="bugs">
+ <title>Known Bugs And Assumptions</title>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>None</term>
+ <listitem>
+ <para>
+ None.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </chapter>
+
+ <chapter id="pubfunctions">
+ <title>Public Functions Provided</title>
+ <para>
+ Not Applicable.
+ </para>
+ </chapter>
+
+ <chapter id="private">
+ <title>Private Functions</title>
+ <para>
+ This Section lists the functions used by the MSP controller driver.
+ These functions cater to all the protocols supported namely: I2S, PCM, SPI.
+ </para>
+!Idrivers/misc/i2s/msp_i2s.c
+ </chapter>
+</book>
diff --git a/arch/arm/mach-ux500/board-mop500-msp.c b/arch/arm/mach-ux500/board-mop500-msp.c
new file mode 100644
index 00000000000..c5fade6d0c7
--- /dev/null
+++ b/arch/arm/mach-ux500/board-mop500-msp.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/gpio.h>
+#include <linux/gpio/nomadik.h>
+
+#include <plat/ste_dma40.h>
+#include <plat/pincfg.h>
+
+#include <mach/devices.h>
+#include <mach/ste-dma40-db8500.h>
+#include <mach/hardware.h>
+#include <mach/irqs.h>
+#include <mach/msp.h>
+
+#include "board-mop500.h"
+#include "devices-db8500.h"
+#include "pins-db8500.h"
+
+/* MSP1/3 Tx/Rx usage protection */
+static DEFINE_SPINLOCK(msp_rxtx_lock);
+
+/* Reference Count */
+static int msp_rxtx_ref;
+
+static pin_cfg_t mop500_msp1_pins_init[] = {
+ GPIO33_MSP1_TXD | PIN_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE,
+ GPIO34_MSP1_TFS | PIN_INPUT_NOPULL | PIN_SLPM_WAKEUP_DISABLE,
+ GPIO35_MSP1_TCK | PIN_INPUT_NOPULL | PIN_SLPM_WAKEUP_DISABLE,
+ GPIO36_MSP1_RXD | PIN_INPUT_NOPULL | PIN_SLPM_WAKEUP_DISABLE,
+};
+
+static pin_cfg_t mop500_msp1_pins_exit[] = {
+ GPIO33_MSP1_TXD | PIN_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE,
+ GPIO34_MSP1_TFS | PIN_INPUT_NOPULL | PIN_SLPM_WAKEUP_ENABLE,
+ GPIO35_MSP1_TCK | PIN_INPUT_NOPULL | PIN_SLPM_WAKEUP_ENABLE,
+ GPIO36_MSP1_RXD | PIN_INPUT_NOPULL | PIN_SLPM_WAKEUP_ENABLE,
+};
+
+int msp13_i2s_init(void)
+{
+ int retval = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&msp_rxtx_lock, flags);
+ if (msp_rxtx_ref == 0)
+ retval = nmk_config_pins(
+ ARRAY_AND_SIZE(mop500_msp1_pins_init));
+ if (!retval)
+ msp_rxtx_ref++;
+ spin_unlock_irqrestore(&msp_rxtx_lock, flags);
+
+ return retval;
+}
+
+int msp13_i2s_exit(void)
+{
+ int retval = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&msp_rxtx_lock, flags);
+ WARN_ON(!msp_rxtx_ref);
+ msp_rxtx_ref--;
+ if (msp_rxtx_ref == 0)
+ retval = nmk_config_pins_sleep(
+ ARRAY_AND_SIZE(mop500_msp1_pins_exit));
+ spin_unlock_irqrestore(&msp_rxtx_lock, flags);
+
+ return retval;
+}
+
+static struct stedma40_chan_cfg msp0_dma_rx = {
+ .high_priority = true,
+ .dir = STEDMA40_PERIPH_TO_MEM,
+
+ .src_dev_type = DB8500_DMA_DEV31_MSP0_RX_SLIM0_CH0_RX,
+ .dst_dev_type = STEDMA40_DEV_DST_MEMORY,
+
+ .src_info.psize = STEDMA40_PSIZE_LOG_4,
+ .dst_info.psize = STEDMA40_PSIZE_LOG_4,
+
+ /* data_width is set during configuration */
+};
+
+static struct stedma40_chan_cfg msp0_dma_tx = {
+ .high_priority = true,
+ .dir = STEDMA40_MEM_TO_PERIPH,
+
+ .src_dev_type = STEDMA40_DEV_DST_MEMORY,
+ .dst_dev_type = DB8500_DMA_DEV31_MSP0_TX_SLIM0_CH0_TX,
+
+ .src_info.psize = STEDMA40_PSIZE_LOG_4,
+ .dst_info.psize = STEDMA40_PSIZE_LOG_4,
+
+ /* data_width is set during configuration */
+};
+
+static struct msp_i2s_platform_data msp0_platform_data = {
+ .id = MSP_0_I2S_CONTROLLER,
+ .msp_i2s_dma_rx = &msp0_dma_rx,
+ .msp_i2s_dma_tx = &msp0_dma_tx,
+};
+
+static struct stedma40_chan_cfg msp1_dma_rx = {
+ .high_priority = true,
+ .dir = STEDMA40_PERIPH_TO_MEM,
+
+ .src_dev_type = DB8500_DMA_DEV30_MSP3_RX,
+ .dst_dev_type = STEDMA40_DEV_DST_MEMORY,
+
+ .src_info.psize = STEDMA40_PSIZE_LOG_4,
+ .dst_info.psize = STEDMA40_PSIZE_LOG_4,
+
+ /* data_width is set during configuration */
+};
+
+static struct stedma40_chan_cfg msp1_dma_tx = {
+ .high_priority = true,
+ .dir = STEDMA40_MEM_TO_PERIPH,
+
+ .src_dev_type = STEDMA40_DEV_DST_MEMORY,
+ .dst_dev_type = DB8500_DMA_DEV30_MSP1_TX,
+
+ .src_info.psize = STEDMA40_PSIZE_LOG_4,
+ .dst_info.psize = STEDMA40_PSIZE_LOG_4,
+
+ /* data_width is set during configuration */
+};
+
+static struct msp_i2s_platform_data msp1_platform_data = {
+ .id = MSP_1_I2S_CONTROLLER,
+ .msp_i2s_dma_rx = NULL,
+ .msp_i2s_dma_tx = &msp1_dma_tx,
+ .msp_i2s_init = msp13_i2s_init,
+ .msp_i2s_exit = msp13_i2s_exit,
+};
+
+static struct stedma40_chan_cfg msp2_dma_rx = {
+ .high_priority = true,
+ .dir = STEDMA40_PERIPH_TO_MEM,
+
+ .src_dev_type = DB8500_DMA_DEV14_MSP2_RX,
+ .dst_dev_type = STEDMA40_DEV_DST_MEMORY,
+
+ /* MSP2 DMA doesn't work with PSIZE == 4 on DB8500v2 */
+ .src_info.psize = STEDMA40_PSIZE_LOG_1,
+ .dst_info.psize = STEDMA40_PSIZE_LOG_1,
+
+ /* data_width is set during configuration */
+};
+
+static struct stedma40_chan_cfg msp2_dma_tx = {
+ .high_priority = true,
+ .dir = STEDMA40_MEM_TO_PERIPH,
+
+ .src_dev_type = STEDMA40_DEV_DST_MEMORY,
+ .dst_dev_type = DB8500_DMA_DEV14_MSP2_TX,
+
+ .src_info.psize = STEDMA40_PSIZE_LOG_4,
+ .dst_info.psize = STEDMA40_PSIZE_LOG_4,
+
+ .use_fixed_channel = true,
+ .phy_channel = 1,
+
+ /* data_width is set during configuration */
+};
+
+static struct msp_i2s_platform_data msp2_platform_data = {
+ .id = MSP_2_I2S_CONTROLLER,
+ .msp_i2s_dma_rx = &msp2_dma_rx,
+ .msp_i2s_dma_tx = &msp2_dma_tx,
+};
+
+static struct msp_i2s_platform_data msp3_platform_data = {
+ .id = MSP_3_I2S_CONTROLLER,
+ .msp_i2s_dma_rx = &msp1_dma_rx,
+ .msp_i2s_dma_tx = NULL,
+ .msp_i2s_init = msp13_i2s_init,
+ .msp_i2s_exit = msp13_i2s_exit,
+};
+
+void __init mop500_msp_init(struct device *parent)
+{
+ db8500_add_msp0_i2s(parent, &msp0_platform_data);
+ db8500_add_msp1_i2s(parent, &msp1_platform_data);
+ db8500_add_msp2_i2s(parent, &msp2_platform_data);
+ db8500_add_msp3_i2s(parent, &msp3_platform_data);
+}
diff --git a/arch/arm/mach-ux500/include/mach/msp.h b/arch/arm/mach-ux500/include/mach/msp.h
new file mode 100644
index 00000000000..9d3bad233fa
--- /dev/null
+++ b/arch/arm/mach-ux500/include/mach/msp.h
@@ -0,0 +1,998 @@
+/*
+ * Copyright (c) 2009 STMicroelectronics
+ *
+ * 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.
+ */
+
+#ifndef _STM_MSP_HEADER
+#define _STM_MSP_HEADER
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/semaphore.h>
+#include <linux/dmaengine.h>
+#include <linux/irqreturn.h>
+#include <linux/bitops.h>
+#include <plat/ste_dma40.h>
+#include <linux/gpio.h>
+#include <linux/spi/stm_msp.h>
+
+/* Generic config struct. Use the actual values defined below for global
+ * control register
+ */
+
+enum msp_state {
+ MSP_STATE_IDLE = 0,
+ MSP_STATE_CONFIGURED = 1,
+ MSP_STATE_RUN = 2,
+};
+
+enum msp_rx_comparison_enable_mode {
+ MSP_COMPARISON_DISABLED = 0,
+ MSP_COMPARISON_NONEQUAL_ENABLED = 2,
+ MSP_COMPARISON_EQUAL_ENABLED = 3
+};
+
+#define RMCEN_BIT 0
+#define RMCSF_BIT 1
+#define RCMPM_BIT 3
+#define TMCEN_BIT 5
+#define TNCSF_BIT 6
+
+struct msp_multichannel_config {
+ bool rx_multichannel_enable;
+ bool tx_multichannel_enable;
+ enum msp_rx_comparison_enable_mode rx_comparison_enable_mode;
+ u8 padding;
+ u32 comparison_value;
+ u32 comparison_mask;
+ u32 rx_channel_0_enable;
+ u32 rx_channel_1_enable;
+ u32 rx_channel_2_enable;
+ u32 rx_channel_3_enable;
+ u32 tx_channel_0_enable;
+ u32 tx_channel_1_enable;
+ u32 tx_channel_2_enable;
+ u32 tx_channel_3_enable;
+};
+
+/**
+ * struct msp_protocol_desc- MSP Protocol desc structure per MSP.
+ * @rx_phase_mode: rx_phase_mode whether single or dual.
+ * @tx_phase_mode: tx_phase_mode whether single or dual.
+ * @rx_phase2_start_mode: rx_phase2_start_mode whether imediate or after
+ * some delay.
+ * @tx_phase2_start_mode: tx_phase2_start_mode whether imediate or after
+ * some delay.
+ * @rx_bit_transfer_format: MSP or LSB.
+ * @tx_bit_transfer_format: MSP or LSB.
+ * @rx_frame_length_1: Frame1 length 1,2,3..
+ * @rx_frame_length_2: Frame2 length 1,2,3..
+ * @tx_frame_length_1: Frame1 length 1,2,3..
+ * @tx_frame_length_2: Frame2 length 1,2,3..
+ * @rx_element_length_1: Element1 length 1,2,...
+ * @rx_element_length_2: Element2 length 1,2,...
+ * @tx_element_length_1: Element1 length 1,2,...
+ * @tx_element_length_2: Element2 length 1,2,...
+ * @rx_data_delay: Delay in clk cycle after frame sync
+ * @tx_data_delay: Delay in clk cycle after frame sync
+ * @rx_clock_pol: Rxpol whether rising or falling.It indicates pol of bit clock.
+ * @tx_clock_pol: Txpol whether rising or falling.It indicates pol of bit clock.
+ * @rx_frame_sync_pol: Frame sync pol whether rising or Falling.
+ * @tx_frame_sync_pol: Frame sync pol whether rising or Falling.
+ * @rx_half_word_swap: Word swap half word, full word.
+ * @tx_half_word_swap: Word swap half word, full word.
+ * @compression_mode: Compression mode whether Alaw or Ulaw or disabled.
+ * @expansion_mode: Compression mode whether Alaw or Ulaw or disabled.
+ * @spi_clk_mode: Spi clock mode to be enabled or not.
+ * @spi_burst_mode: Spi burst mode to be enabled or not.
+ * @frame_sync_ignore: Frame sync to be ignored or not. Ignore in case of Audio
+ * codec acting as Master.
+ * @frame_period: Frame period (clk cycles) after which new frame sync occurs.
+ * @frame_width: Frame width (clk cycles) after which frame sycn changes state.
+ * @total_clocks_for_one_frame: No. of clk cycles per frame.
+ *
+ * Main Msp protocol descriptor data structure to be used to store various info
+ * in transmit or recevie configuration registers of an MSP.
+ */
+
+struct msp_protocol_desc {
+ u32 rx_phase_mode;
+ u32 tx_phase_mode;
+ u32 rx_phase2_start_mode;
+ u32 tx_phase2_start_mode;
+ u32 rx_bit_transfer_format;
+ u32 tx_bit_transfer_format;
+ u32 rx_frame_length_1;
+ u32 rx_frame_length_2;
+ u32 tx_frame_length_1;
+ u32 tx_frame_length_2;
+ u32 rx_element_length_1;
+ u32 rx_element_length_2;
+ u32 tx_element_length_1;
+ u32 tx_element_length_2;
+ u32 rx_data_delay;
+ u32 tx_data_delay;
+ u32 rx_clock_pol;
+ u32 tx_clock_pol;
+ u32 rx_frame_sync_pol;
+ u32 tx_frame_sync_pol;
+ u32 rx_half_word_swap;
+ u32 tx_half_word_swap;
+ u32 compression_mode;
+ u32 expansion_mode;
+ u32 spi_clk_mode;
+ u32 spi_burst_mode;
+ u32 frame_sync_ignore;
+ u32 frame_period;
+ u32 frame_width;
+ u32 total_clocks_for_one_frame;
+};
+
+enum i2s_direction_t {
+ I2S_DIRECTION_TX = 0,
+ I2S_DIRECTION_RX = 1,
+ I2S_DIRECTION_BOTH = 2
+};
+
+enum i2s_transfer_mode_t {
+ I2S_TRANSFER_MODE_SINGLE_DMA = 0,
+ I2S_TRANSFER_MODE_CYCLIC_DMA = 1,
+ I2S_TRANSFER_MODE_INF_LOOPBACK = 2,
+ I2S_TRANSFER_MODE_NON_DMA = 4,
+};
+
+struct i2s_message {
+ enum i2s_direction_t i2s_direction;
+ void *txdata;
+ void *rxdata;
+ size_t txbytes;
+ size_t rxbytes;
+ int dma_flag;
+ int tx_offset;
+ int rx_offset;
+ /* cyclic dma */
+ bool cyclic_dma;
+ dma_addr_t buf_addr;
+ size_t buf_len;
+ size_t period_len;
+};
+
+enum i2s_flag {
+ DISABLE_ALL = 0,
+ DISABLE_TRANSMIT = 1,
+ DISABLE_RECEIVE = 2,
+};
+
+struct i2s_controller {
+ struct module *owner;
+ unsigned int id;
+ unsigned int class;
+ const struct i2s_algorithm *algo; /* the algorithm to access the bus */
+ void *data;
+ struct mutex bus_lock;
+ struct device dev; /* the controller device */
+ char name[48];
+};
+#define to_i2s_controller(d) container_of(d, struct i2s_controller, dev)
+
+/**
+ * struct trans_data - MSP transfer data structure used during xfer.
+ * @message: i2s message.
+ * @msp: msp structure.
+ * @tx_handler: callback handler for transmit path.
+ * @rx_handler: callback handler for receive path.
+ * @tx_callback_data: callback data for transmit.
+ * @rx_callback_data: callback data for receive.
+ *
+ */
+struct trans_data {
+ struct i2s_message message;
+ struct msp *msp;
+ void (*tx_handler) (void *data);
+ void (*rx_handler) (void *data);
+ void *tx_callback_data;
+ void *rx_callback_data;
+};
+
+/**
+ * struct msp_config- MSP configuration structure used by i2s client.
+ * @input_clock_freq: Input clock frequency default is 48MHz.
+ * @rx_clock_sel: Receive clock selection (Provided by Sample Gen or external
+ * source).
+ * @tx_clock_sel: Transmit clock selection (Provided by Sample Gen or external.
+ * source).
+ * @srg_clock_sel: APB clock or clock dervied from Slave (Audio codec).
+ * @rx_frame_sync_pol: Receive frame sync polarity.
+ * @tx_frame_sync_pol: Transmit frame sync polarity.
+ * @rx_frame_sync_sel: Rx frame sync signal is provided by which source.
+ * External source or by frame generator logic.
+ * @tx_frame_sync_sel: Tx frame sync signal is provided by which source.
+ * External source or by frame generator logic.
+ * @rx_fifo_config: Receive fifo enable or not.
+ * @tx_fifo_config: Transmit fifo enable or not.
+ * @spi_clk_mode: In case of SPI protocol spi modes: Normal, Zero delay or
+ * half cycle delay.
+ * @spi_burst_mode: Spi burst mode is enabled or not.
+ * @loopback_enable: Loopback mode.
+ * @tx_data_enable: Transmit extra delay enable.
+ * @default_protocol_desc: Flag to indicate client defined protocol desc or
+ * statically defined in msp.h.
+ * @protocol_desc: Protocol desc structure filled by i2s client driver.
+ * In case client defined default_prtocol_desc as 0.
+ * @multichannel_configured: multichannel configuration structure.
+ * @multichannel_config: multichannel is enabled or not.
+ * @direction: Transmit, Receive or Both.
+ * @work_mode: Dma, Polling or Interrupt.
+ * @protocol: I2S, PCM, etc.
+ * @frame_freq: Sampling freq at which data is sampled.
+ * @frame_size: size of element.
+ * @data_size: data size which defines the format in which data is written on
+ * transmit or receive fifo. Only three modes 8,16,32 are supported.
+ * @def_elem_len: Flag to indicate whether default element length is to be used
+ * or should be changed acc to data size defined by user at run time.
+ * @iodelay: value for the MSP_IODLY register
+ *
+ * Main Msp configuration data structure used by i2s client driver to fill
+ * various info like data size, frequency etc.
+ */
+struct msp_config {
+ unsigned int input_clock_freq;
+ unsigned int rx_clock_sel;
+ unsigned int tx_clock_sel;
+ unsigned int srg_clock_sel;
+ unsigned int rx_frame_sync_pol;
+ unsigned int tx_frame_sync_pol;
+ unsigned int rx_frame_sync_sel;
+ unsigned int tx_frame_sync_sel;
+ unsigned int rx_fifo_config;
+ unsigned int tx_fifo_config;
+ unsigned int spi_clk_mode;
+ unsigned int spi_burst_mode;
+ unsigned int loopback_enable;
+ unsigned int tx_data_enable;
+ unsigned int default_protocol_desc;
+ struct msp_protocol_desc protocol_desc;
+ int multichannel_configured;
+ struct msp_multichannel_config multichannel_config;
+ unsigned int direction;
+ unsigned int work_mode;
+ unsigned int protocol;
+ unsigned int frame_freq;
+ unsigned int frame_size;
+ enum msp_data_size data_size;
+ unsigned int def_elem_len;
+ unsigned int iodelay;
+};
+
+/*** Protocols ***/
+enum msp_protocol {
+ MSP_I2S_PROTOCOL,
+ MSP_PCM_PROTOCOL,
+ MSP_PCM_COMPAND_PROTOCOL,
+ MSP_AC97_PROTOCOL,
+ MSP_MASTER_SPI_PROTOCOL,
+ MSP_SLAVE_SPI_PROTOCOL,
+ MSP_INVALID_PROTOCOL
+};
+
+/*** Sample Frequencies ***/
+/* These are no longer required, frequencies in Hz can be used directly */
+enum msp_sample_freq {
+ MSP_SAMPLE_FREQ_NOT_SUPPORTED = -1,
+ MSP_SAMPLE_FREQ_8KHZ = 8000,
+ MSP_SAMPLE_FREQ_12KHZ = 12000,
+ MSP_SAMPLE_FREQ_16KHZ = 16000,
+ MSP_SAMPLE_FREQ_24KHZ = 24000,
+ MSP_SAMPLE_FREQ_32KHZ = 32000,
+ MSP_SAMPLE_FREQ_44KHZ = 44000,
+ MSP_SAMPLE_FREQ_48KHZ = 48000,
+ MSP_SAMPLE_FREQ_64KHZ = 64000,
+ MSP_SAMPLE_FREQ_88KHZ = 88000,
+ MSP_SAMPLE_FREQ_96KHZ = 96000,
+ MSP_SAMPLE_FREQ_22KHZ = 22000,
+ MSP_SAMPLE_FREQ_11KHZ = 11000
+};
+
+/*** Input Frequencies ***/
+/* These are no longer required, frequencies in Hz can be used directly */
+enum msp_in_clock_freq {
+ MSP_INPUT_FREQ_1MHZ = 1000,
+ MSP_INPUT_FREQ_2MHZ = 2000,
+ MSP_INPUT_FREQ_3MHZ = 3000,
+ MSP_INPUT_FREQ_4MHZ = 4000,
+ MSP_INPUT_FREQ_5MHZ = 5000,
+ MSP_INPUT_FREQ_6MHZ = 6000,
+ MSP_INPUT_FREQ_8MHZ = 8000,
+ MSP_INPUT_FREQ_11MHZ = 11000,
+ MSP_INPUT_FREQ_12MHZ = 12000,
+ MSP_INPUT_FREQ_16MHZ = 16000,
+ MSP_INPUT_FREQ_22MHZ = 22000,
+ MSP_INPUT_FREQ_24MHZ = 24000,
+ MSP_INPUT_FREQ_48MHZ = 48000
+};
+
+#define MSP_INPUT_FREQ_APB 48000000
+
+/*** Stereo mode. Used for APB data accesses as 16 bits accesses (mono),
+ * 32 bits accesses (stereo).
+ ***/
+enum msp_stereo_mode {
+ MSP_MONO,
+ MSP_STEREO
+};
+
+/* Direction (Transmit/Receive mode) */
+enum msp_direction {
+ MSP_TRANSMIT_MODE,
+ MSP_RECEIVE_MODE,
+ MSP_BOTH_T_R_MODE
+};
+
+/* Dma mode should be used for large transfers,
+ * polling mode should be used for transfers of a few bytes
+ */
+enum msp_xfer_mode {
+ MSP_DMA_MODE,
+ MSP_POLLING_MODE,
+ MSP_INTERRUPT_MODE
+};
+
+/* User client for the MSP */
+enum msp_user {
+ MSP_NO_USER = 0,
+ MSP_USER_SPI,
+ MSP_USER_ALSA,
+ MSP_USER_SAA,
+};
+
+/*Flag structure for MSPx*/
+struct msp_flag {
+ struct semaphore lock;
+ enum msp_user user;
+};
+
+/* User client for the MSP */
+enum msp_mode {
+ MSP_NO_MODE = 0,
+ MSP_MODE_SPI,
+ MSP_MODE_NON_SPI,
+};
+
+/* Transmit and receive configuration register */
+#define MSP_BIG_ENDIAN 0x00000000
+#define MSP_LITTLE_ENDIAN 0x00001000
+#define MSP_UNEXPECTED_FS_ABORT 0x00000000
+#define MSP_UNEXPECTED_FS_IGNORE 0x00008000
+#define MSP_NON_MODE_BIT_MASK 0x00009000
+
+/* Global configuration register */
+#define RX_ENABLE 0x00000001
+#define RX_FIFO_ENABLE 0x00000002
+#define RX_SYNC_SRG 0x00000010
+#define RX_CLK_POL_RISING 0x00000020
+#define RX_CLK_SEL_SRG 0x00000040
+#define TX_ENABLE 0x00000100
+#define TX_FIFO_ENABLE 0x00000200
+#define TX_SYNC_SRG_PROG 0x00001800
+#define TX_SYNC_SRG_AUTO 0x00001000
+#define TX_CLK_POL_RISING 0x00002000
+#define TX_CLK_SEL_SRG 0x00004000
+#define TX_EXTRA_DELAY_ENABLE 0x00008000
+#define SRG_ENABLE 0x00010000
+#define FRAME_GEN_ENABLE 0x00100000
+#define SRG_CLK_SEL_APB 0x00000000
+#define RX_FIFO_SYNC_HI 0x00000000
+#define TX_FIFO_SYNC_HI 0x00000000
+#define SPI_CLK_MODE_NORMAL 0x00000000
+
+/* SPI Clock Modes enumertion
+ * SPI clock modes of MSP provides compatibility with
+ * the SPI protocol.MSP supports 2 SPI transfer formats.
+ * MSP_ZERO_DELAY_SPI_MODE:MSP transmits data over Tx/Rx
+ * Lines immediately after MSPTCK/MSPRCK rising/falling edge.
+ * MSP_HALF_CYCLE_DELY_SPI_MODE:MSP transmits data one-half cycle
+ * ahead of the rising/falling edge of the MSPTCK
+ */
+
+#define MSP_FRAME_SIZE_AUTO -1
+
+
+#define MSP_DR 0x00
+#define MSP_GCR 0x04
+#define MSP_TCF 0x08
+#define MSP_RCF 0x0c
+#define MSP_SRG 0x10
+#define MSP_FLR 0x14
+#define MSP_DMACR 0x18
+
+#define MSP_IMSC 0x20
+#define MSP_RIS 0x24
+#define MSP_MIS 0x28
+#define MSP_ICR 0x2c
+#define MSP_MCR 0x30
+#define MSP_RCV 0x34
+#define MSP_RCM 0x38
+
+#define MSP_TCE0 0x40
+#define MSP_TCE1 0x44
+#define MSP_TCE2 0x48
+#define MSP_TCE3 0x4c
+
+#define MSP_RCE0 0x60
+#define MSP_RCE1 0x64
+#define MSP_RCE2 0x68
+#define MSP_RCE3 0x6c
+#define MSP_IODLY 0x70
+
+#define MSP_ITCR 0x80
+#define MSP_ITIP 0x84
+#define MSP_ITOP 0x88
+#define MSP_TSTDR 0x8c
+
+#define MSP_PID0 0xfe0
+#define MSP_PID1 0xfe4
+#define MSP_PID2 0xfe8
+#define MSP_PID3 0xfec
+
+#define MSP_CID0 0xff0
+#define MSP_CID1 0xff4
+#define MSP_CID2 0xff8
+#define MSP_CID3 0xffc
+
+/* Single or dual phase mode */
+enum msp_phase_mode {
+ MSP_SINGLE_PHASE,
+ MSP_DUAL_PHASE
+};
+
+/* Frame length */
+enum msp_frame_length {
+ MSP_FRAME_LENGTH_1 = 0,
+ MSP_FRAME_LENGTH_2 = 1,
+ MSP_FRAME_LENGTH_4 = 3,
+ MSP_FRAME_LENGTH_8 = 7,
+ MSP_FRAME_LENGTH_12 = 11,
+ MSP_FRAME_LENGTH_16 = 15,
+ MSP_FRAME_LENGTH_20 = 19,
+ MSP_FRAME_LENGTH_32 = 31,
+ MSP_FRAME_LENGTH_48 = 47,
+ MSP_FRAME_LENGTH_64 = 63
+};
+
+/* Element length */
+enum msp_elem_length {
+ MSP_ELEM_LENGTH_8 = 0,
+ MSP_ELEM_LENGTH_10 = 1,
+ MSP_ELEM_LENGTH_12 = 2,
+ MSP_ELEM_LENGTH_14 = 3,
+ MSP_ELEM_LENGTH_16 = 4,
+ MSP_ELEM_LENGTH_20 = 5,
+ MSP_ELEM_LENGTH_24 = 6,
+ MSP_ELEM_LENGTH_32 = 7
+};
+
+enum msp_data_xfer_width {
+ MSP_DATA_TRANSFER_WIDTH_BYTE,
+ MSP_DATA_TRANSFER_WIDTH_HALFWORD,
+ MSP_DATA_TRANSFER_WIDTH_WORD
+};
+
+enum msp_frame_sync {
+ MSP_FRAME_SYNC_UNIGNORE = 0,
+ MSP_FRAME_SYNC_IGNORE = 1,
+
+};
+
+enum msp_phase2_start_mode {
+ MSP_PHASE2_START_MODE_IMEDIATE,
+ MSP_PHASE2_START_MODE_FRAME_SYNC
+};
+
+enum msp_btf {
+ MSP_BTF_MS_BIT_FIRST = 0,
+ MSP_BTF_LS_BIT_FIRST = 1
+};
+
+enum msp_frame_sync_pol {
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH = 0,
+ MSP_FRAME_SYNC_POL_ACTIVE_LOW = 1
+};
+
+/* Data delay (in bit clock cycles) */
+enum msp_delay {
+ MSP_DELAY_0 = 0,
+ MSP_DELAY_1 = 1,
+ MSP_DELAY_2 = 2,
+ MSP_DELAY_3 = 3
+};
+
+/* Configurations of clocks (transmit, receive or sample rate generator) */
+enum msp_edge {
+ MSP_FALLING_EDGE = 0,
+ MSP_RISING_EDGE = 1,
+};
+
+enum msp_hws {
+ MSP_HWS_NO_SWAP = 0,
+ MSP_HWS_BYTE_SWAP_IN_WORD = 1,
+ MSP_HWS_BYTE_SWAP_IN_EACH_HALF_WORD = 2,
+ MSP_HWS_HALF_WORD_SWAP_IN_WORD = 3
+};
+
+enum msp_compress_mode {
+ MSP_COMPRESS_MODE_LINEAR = 0,
+ MSP_COMPRESS_MODE_MU_LAW = 2,
+ MSP_COMPRESS_MODE_A_LAW = 3
+};
+
+enum msp_spi_clock_mode {
+ MSP_SPI_CLOCK_MODE_NON_SPI = 0,
+ MSP_SPI_CLOCK_MODE_ZERO_DELAY = 2,
+ MSP_SPI_CLOCK_MODE_HALF_CYCLE_DELAY = 3
+};
+
+enum msp_spi_burst_mode {
+ MSP_SPI_BURST_MODE_DISABLE = 0,
+ MSP_SPI_BURST_MODE_ENABLE = 1
+};
+
+enum msp_expand_mode {
+ MSP_EXPAND_MODE_LINEAR = 0,
+ MSP_EXPAND_MODE_LINEAR_SIGNED = 1,
+ MSP_EXPAND_MODE_MU_LAW = 2,
+ MSP_EXPAND_MODE_A_LAW = 3
+};
+
+/* Protocol dependant parameters list */
+#define RX_ENABLE_MASK BIT(0)
+#define RX_FIFO_ENABLE_MASK BIT(1)
+#define RX_FRAME_SYNC_MASK BIT(2)
+#define DIRECT_COMPANDING_MASK BIT(3)
+#define RX_SYNC_SEL_MASK BIT(4)
+#define RX_CLK_POL_MASK BIT(5)
+#define RX_CLK_SEL_MASK BIT(6)
+#define LOOPBACK_MASK BIT(7)
+#define TX_ENABLE_MASK BIT(8)
+#define TX_FIFO_ENABLE_MASK BIT(9)
+#define TX_FRAME_SYNC_MASK BIT(10)
+#define TX_MSP_TDR_TSR BIT(11)
+#define TX_SYNC_SEL_MASK (BIT(12) | BIT(11))
+#define TX_CLK_POL_MASK BIT(13)
+#define TX_CLK_SEL_MASK BIT(14)
+#define TX_EXTRA_DELAY_MASK BIT(15)
+#define SRG_ENABLE_MASK BIT(16)
+#define SRG_CLK_POL_MASK BIT(17)
+#define SRG_CLK_SEL_MASK (BIT(19) | BIT(18))
+#define FRAME_GEN_EN_MASK BIT(20)
+#define SPI_CLK_MODE_MASK (BIT(22) | BIT(21))
+#define SPI_BURST_MODE_MASK BIT(23)
+
+#define RXEN_SHIFT 0
+#define RFFEN_SHIFT 1
+#define RFSPOL_SHIFT 2
+#define DCM_SHIFT 3
+#define RFSSEL_SHIFT 4
+#define RCKPOL_SHIFT 5
+#define RCKSEL_SHIFT 6
+#define LBM_SHIFT 7
+#define TXEN_SHIFT 8
+#define TFFEN_SHIFT 9
+#define TFSPOL_SHIFT 10
+#define TFSSEL_SHIFT 11
+#define TCKPOL_SHIFT 13
+#define TCKSEL_SHIFT 14
+#define TXDDL_SHIFT 15
+#define SGEN_SHIFT 16
+#define SCKPOL_SHIFT 17
+#define SCKSEL_SHIFT 18
+#define FGEN_SHIFT 20
+#define SPICKM_SHIFT 21
+#define TBSWAP_SHIFT 28
+
+#define RCKPOL_MASK BIT(0)
+#define TCKPOL_MASK BIT(0)
+#define SPICKM_MASK (BIT(1) | BIT(0))
+#define MSP_RX_CLKPOL_BIT(n) ((n & RCKPOL_MASK) << RCKPOL_SHIFT)
+#define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT)
+#define MSP_SPI_CLK_MODE_BITS(n) ((n & SPICKM_MASK) << SPICKM_SHIFT)
+
+
+
+/* Use this to clear the clock mode bits to non-spi */
+#define MSP_NON_SPI_CLK_MASK (BIT(22) | BIT(21))
+
+#define P1ELEN_SHIFT 0
+#define P1FLEN_SHIFT 3
+#define DTYP_SHIFT 10
+#define ENDN_SHIFT 12
+#define DDLY_SHIFT 13
+#define FSIG_SHIFT 15
+#define P2ELEN_SHIFT 16
+#define P2FLEN_SHIFT 19
+#define P2SM_SHIFT 26
+#define P2EN_SHIFT 27
+#define FRAME_SYNC_SHIFT 15
+
+
+#define P1ELEN_MASK 0x00000007
+#define P2ELEN_MASK 0x00070000
+#define P1FLEN_MASK 0x00000378
+#define P2FLEN_MASK 0x03780000
+#define DDLY_MASK 0x00003000
+#define DTYP_MASK 0x00000600
+#define P2SM_MASK 0x04000000
+#define P2EN_MASK 0x08000000
+#define ENDN_MASK 0x00001000
+#define TFSPOL_MASK 0x00000400
+#define TBSWAP_MASK 0x30000000
+#define COMPANDING_MODE_MASK 0x00000c00
+#define FRAME_SYNC_MASK 0x00008000
+
+#define MSP_P1_ELEM_LEN_BITS(n) (n & P1ELEN_MASK)
+#define MSP_P2_ELEM_LEN_BITS(n) (((n) << P2ELEN_SHIFT) & P2ELEN_MASK)
+#define MSP_P1_FRAME_LEN_BITS(n) (((n) << P1FLEN_SHIFT) & P1FLEN_MASK)
+#define MSP_P2_FRAME_LEN_BITS(n) (((n) << P2FLEN_SHIFT) & P2FLEN_MASK)
+#define MSP_DATA_DELAY_BITS(n) (((n) << DDLY_SHIFT) & DDLY_MASK)
+#define MSP_DATA_TYPE_BITS(n) (((n) << DTYP_SHIFT) & DTYP_MASK)
+#define MSP_P2_START_MODE_BIT(n) ((n << P2SM_SHIFT) & P2SM_MASK)
+#define MSP_P2_ENABLE_BIT(n) ((n << P2EN_SHIFT) & P2EN_MASK)
+#define MSP_SET_ENDIANNES_BIT(n) ((n << ENDN_SHIFT) & ENDN_MASK)
+#define MSP_FRAME_SYNC_POL(n) ((n << TFSPOL_SHIFT) & TFSPOL_MASK)
+#define MSP_DATA_WORD_SWAP(n) ((n << TBSWAP_SHIFT) & TBSWAP_MASK)
+#define MSP_SET_COMPANDING_MODE(n) ((n << DTYP_SHIFT) & COMPANDING_MODE_MASK)
+#define MSP_SET_FRAME_SYNC_IGNORE(n) ((n << FRAME_SYNC_SHIFT) & \
+ FRAME_SYNC_MASK)
+
+/* Flag register */
+#define RX_BUSY BIT(0)
+#define RX_FIFO_EMPTY BIT(1)
+#define RX_FIFO_FULL BIT(2)
+#define TX_BUSY BIT(3)
+#define TX_FIFO_EMPTY BIT(4)
+#define TX_FIFO_FULL BIT(5)
+
+#define RBUSY_SHIFT 0
+#define RFE_SHIFT 1
+#define RFU_SHIFT 2
+#define TBUSY_SHIFT 3
+#define TFE_SHIFT 4
+#define TFU_SHIFT 5
+
+/* Multichannel control register */
+#define RMCEN_SHIFT 0
+#define RMCSF_SHIFT 1
+#define RCMPM_SHIFT 3
+#define TMCEN_SHIFT 5
+#define TNCSF_SHIFT 6
+
+/* Sample rate generator register */
+#define SCKDIV_SHIFT 0
+#define FRWID_SHIFT 10
+#define FRPER_SHIFT 16
+
+#define SCK_DIV_MASK 0x0000003FF
+#define FRAME_WIDTH_BITS(n) (((n) << FRWID_SHIFT) & 0x0000FC00)
+#define FRAME_PERIOD_BITS(n) (((n) << FRPER_SHIFT) & 0x1FFF0000)
+
+/* DMA controller register */
+#define RX_DMA_ENABLE BIT(0)
+#define TX_DMA_ENABLE BIT(1)
+
+#define RDMAE_SHIFT 0
+#define TDMAE_SHIFT 1
+
+/* Interrupt Register */
+#define RECEIVE_SERVICE_INT BIT(0)
+#define RECEIVE_OVERRUN_ERROR_INT BIT(1)
+#define RECEIVE_FRAME_SYNC_ERR_INT BIT(2)
+#define RECEIVE_FRAME_SYNC_INT BIT(3)
+#define TRANSMIT_SERVICE_INT BIT(4)
+#define TRANSMIT_UNDERRUN_ERR_INT BIT(5)
+#define TRANSMIT_FRAME_SYNC_ERR_INT BIT(6)
+#define TRANSMIT_FRAME_SYNC_INT BIT(7)
+#define ALL_INT 0x000000ff
+
+/* MSP test control register */
+#define MSP_ITCR_ITEN BIT(0)
+#define MSP_ITCR_TESTFIFO BIT(1)
+
+/*
+ * Protocol configuration values I2S:
+ * Single phase, 16 bits, 2 words per frame
+ */
+#define I2S_PROTOCOL_DESC \
+{ \
+ MSP_SINGLE_PHASE, \
+ MSP_SINGLE_PHASE, \
+ MSP_PHASE2_START_MODE_IMEDIATE, \
+ MSP_PHASE2_START_MODE_IMEDIATE, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_ELEM_LENGTH_32, \
+ MSP_ELEM_LENGTH_32, \
+ MSP_ELEM_LENGTH_32, \
+ MSP_ELEM_LENGTH_32, \
+ MSP_DELAY_1, \
+ MSP_DELAY_1, \
+ MSP_RISING_EDGE, \
+ MSP_FALLING_EDGE, \
+ MSP_FRAME_SYNC_POL_ACTIVE_LOW, \
+ MSP_FRAME_SYNC_POL_ACTIVE_LOW, \
+ MSP_HWS_NO_SWAP, \
+ MSP_HWS_NO_SWAP, \
+ MSP_COMPRESS_MODE_LINEAR, \
+ MSP_EXPAND_MODE_LINEAR, \
+ MSP_SPI_CLOCK_MODE_NON_SPI, \
+ MSP_SPI_BURST_MODE_DISABLE, \
+ MSP_FRAME_SYNC_IGNORE, \
+ 31, \
+ 15, \
+ 32, \
+}
+
+#define PCM_PROTOCOL_DESC \
+{ \
+ MSP_DUAL_PHASE, \
+ MSP_DUAL_PHASE, \
+ MSP_PHASE2_START_MODE_FRAME_SYNC, \
+ MSP_PHASE2_START_MODE_FRAME_SYNC, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_ELEM_LENGTH_16, \
+ MSP_ELEM_LENGTH_16, \
+ MSP_ELEM_LENGTH_16, \
+ MSP_ELEM_LENGTH_16, \
+ MSP_DELAY_0, \
+ MSP_DELAY_0, \
+ MSP_RISING_EDGE, \
+ MSP_FALLING_EDGE, \
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \
+ MSP_HWS_NO_SWAP, \
+ MSP_HWS_NO_SWAP, \
+ MSP_COMPRESS_MODE_LINEAR, \
+ MSP_EXPAND_MODE_LINEAR, \
+ MSP_SPI_CLOCK_MODE_NON_SPI, \
+ MSP_SPI_BURST_MODE_DISABLE, \
+ MSP_FRAME_SYNC_IGNORE, \
+ 255, \
+ 0, \
+ 256, \
+}
+
+/* Companded PCM: Single phase, 8 bits, 1 word per frame */
+#define PCM_COMPAND_PROTOCOL_DESC \
+{ \
+ MSP_SINGLE_PHASE, \
+ MSP_SINGLE_PHASE, \
+ MSP_PHASE2_START_MODE_FRAME_SYNC, \
+ MSP_PHASE2_START_MODE_FRAME_SYNC, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_DELAY_0, \
+ MSP_DELAY_0, \
+ MSP_RISING_EDGE, \
+ MSP_RISING_EDGE, \
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \
+ MSP_HWS_NO_SWAP, \
+ MSP_HWS_NO_SWAP, \
+ MSP_COMPRESS_MODE_LINEAR, \
+ MSP_EXPAND_MODE_LINEAR, \
+ MSP_SPI_CLOCK_MODE_NON_SPI, \
+ MSP_SPI_BURST_MODE_DISABLE, \
+ MSP_FRAME_SYNC_IGNORE, \
+ 255, \
+ 0, \
+ 256, \
+}
+
+/*
+ * AC97: Double phase, 1 element of 16 bits during first phase,
+ * 12 elements of 20 bits in second phase.
+ */
+#define AC97_PROTOCOL_DESC \
+{ \
+ MSP_DUAL_PHASE, \
+ MSP_DUAL_PHASE, \
+ MSP_PHASE2_START_MODE_FRAME_SYNC, \
+ MSP_PHASE2_START_MODE_FRAME_SYNC, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_12, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_12, \
+ MSP_ELEM_LENGTH_16, \
+ MSP_ELEM_LENGTH_20, \
+ MSP_ELEM_LENGTH_16, \
+ MSP_ELEM_LENGTH_20, \
+ MSP_DELAY_1, \
+ MSP_DELAY_1, \
+ MSP_RISING_EDGE, \
+ MSP_RISING_EDGE, \
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \
+ MSP_HWS_NO_SWAP, \
+ MSP_HWS_NO_SWAP, \
+ MSP_COMPRESS_MODE_LINEAR, \
+ MSP_EXPAND_MODE_LINEAR, \
+ MSP_SPI_CLOCK_MODE_NON_SPI, \
+ MSP_SPI_BURST_MODE_DISABLE, \
+ MSP_FRAME_SYNC_IGNORE, \
+ 255, \
+ 0, \
+ 256, \
+}
+
+#define SPI_MASTER_PROTOCOL_DESC \
+{ \
+ MSP_SINGLE_PHASE, \
+ MSP_SINGLE_PHASE, \
+ MSP_PHASE2_START_MODE_FRAME_SYNC, \
+ MSP_PHASE2_START_MODE_FRAME_SYNC, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_DELAY_1, \
+ MSP_DELAY_1, \
+ MSP_FALLING_EDGE, \
+ MSP_FALLING_EDGE, \
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \
+ MSP_HWS_NO_SWAP, \
+ MSP_HWS_NO_SWAP, \
+ MSP_COMPRESS_MODE_LINEAR, \
+ MSP_EXPAND_MODE_LINEAR, \
+ MSP_SPI_CLOCK_MODE_NON_SPI, \
+ MSP_SPI_BURST_MODE_DISABLE, \
+ MSP_FRAME_SYNC_IGNORE, \
+ 255, \
+ 0, \
+ 256, \
+}
+
+#define SPI_SLAVE_PROTOCOL_DESC \
+{ \
+ MSP_SINGLE_PHASE, \
+ MSP_SINGLE_PHASE, \
+ MSP_PHASE2_START_MODE_FRAME_SYNC, \
+ MSP_PHASE2_START_MODE_FRAME_SYNC, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_BTF_MS_BIT_FIRST, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_FRAME_LENGTH_1, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_ELEM_LENGTH_8, \
+ MSP_DELAY_1, \
+ MSP_DELAY_1, \
+ MSP_FALLING_EDGE, \
+ MSP_FALLING_EDGE, \
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \
+ MSP_HWS_NO_SWAP, \
+ MSP_HWS_NO_SWAP, \
+ MSP_COMPRESS_MODE_LINEAR, \
+ MSP_EXPAND_MODE_LINEAR, \
+ MSP_SPI_CLOCK_MODE_NON_SPI, \
+ MSP_SPI_BURST_MODE_DISABLE, \
+ MSP_FRAME_SYNC_IGNORE, \
+ 255, \
+ 0, \
+ 256, \
+}
+
+#define MSP_FRAME_PERIOD_IN_MONO_MODE 256
+#define MSP_FRAME_PERIOD_IN_STEREO_MODE 32
+#define MSP_FRAME_WIDTH_IN_STEREO_MODE 16
+
+/*
+ * No of registers to backup during
+ * suspend resume
+ */
+#define MAX_MSP_BACKUP_REGS 36
+
+enum enum_i2s_controller {
+ MSP_0_I2S_CONTROLLER = 0,
+ MSP_1_I2S_CONTROLLER,
+ MSP_2_I2S_CONTROLLER,
+ MSP_3_I2S_CONTROLLER,
+};
+
+/**
+ * struct msp - Main msp controller data structure per MSP.
+ * @work_mode: Mode i.e dma, polling or interrupt.
+ * @id: Controller id like MSP1 or MSP2 etc.
+ * @msp_io_error: To indicate error while transferring.
+ * @registers: MSP's register base address.
+ * @actual_data_size: Data size in which data needs to send or receive.
+ * @irq: MSP's irq number.
+ * @i2s_cont: MSP's Controller's structure pointer created per MSP.
+ * @lock: semaphore lock acquired while configuring msp.
+ * @dma_cfg_tx: TX DMA configuration
+ * @dma_cfg_rx: RX DMA configuration
+ * @msp_state: Current state of msp.
+ * @plat_init: MSP's initialization function.
+ * @plat_exit: MSP's Exit function.
+ * @def_elem_len: Flag indicates whether default elem len to be used in
+ * protocol_desc or not.
+ * @reg_enabled: Flag indicates whether regulator has been enabled or not.
+ * @vape_opp_constraint: 1 if constraint is applied to have vape at 100OPP; 0 otherwise
+ *
+ * Main Msp private data structure to be used to store various info of a
+ * particular MSP.Longer description
+ */
+struct msp {
+ int work_mode;
+ enum enum_i2s_controller id;
+ int msp_io_error;
+ void __iomem *registers;
+ enum msp_data_size actual_data_size;
+ struct device *dev;
+ int irq;
+ struct i2s_controller *i2s_cont;
+ struct semaphore lock;
+ struct stedma40_chan_cfg *dma_cfg_rx;
+ struct stedma40_chan_cfg *dma_cfg_tx;
+ enum msp_state msp_state;
+ int (*plat_init) (void);
+ int (*plat_exit) (void);
+ int def_elem_len;
+ struct clk *clk;
+ unsigned int direction;
+ int users;
+ int reg_enabled;
+ int loopback_enable;
+ u32 backup_regs[MAX_MSP_BACKUP_REGS];
+ int vape_opp_constraint;
+};
+
+/**
+ * struct msp_i2s_platform_data - Main msp controller platform data structure.
+ * @id: Controller id like MSP1 or MSP2 etc.
+ * @msp_i2s_dma_rx: RX DMA channel config
+ * @msp_i2s_dma_tx: RX DMA channel config
+ * @msp_i2s_init: MSP's initialization function.
+ * @msp_i2s_exit: MSP's Exit function.
+ * @backup_regs: used for backup registers during suspend resume.
+ *
+ * Platform data structure passed by devices.c file.
+ */
+struct msp_i2s_platform_data {
+ enum enum_i2s_controller id;
+ struct stedma40_chan_cfg *msp_i2s_dma_rx;
+ struct stedma40_chan_cfg *msp_i2s_dma_tx;
+ int (*msp_i2s_init) (void);
+ int (*msp_i2s_exit) (void);
+};
+
+#endif
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 00c024039c9..b8aa82fa2a6 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -113,6 +113,20 @@ config SPI_BITBANG
need it. You only need to select this explicitly to support driver
modules that aren't part of this kernel tree.
+config STM_MSP_SPI
+ tristate "STM MSP CONTROLLER (SPI master)"
+ default y
+ help
+ This enables using the STM MSP controller in master
+ mode.
+
+config SPI_WORKQUEUE
+ bool "SPI_WORKQUEUE"
+ depends on STM_MSP_SPI
+ default n
+ help
+ This feature allow SPI works to be deferred in MSP driver.
+
config SPI_BUTTERFLY
tristate "Parallel port adapter for AVR Butterfly (DEVELOPMENT)"
depends on PARPORT
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 9d75d2198ff..6c10ed18023 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -62,4 +62,5 @@ obj-$(CONFIG_SPI_TLE62X0) += spi-tle62x0.o
obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi-topcliff-pch.o
obj-$(CONFIG_SPI_TXX9) += spi-txx9.o
obj-$(CONFIG_SPI_XILINX) += spi-xilinx.o
+obj-$(CONFIG_STM_MSP_SPI) += stm_msp.o
diff --git a/drivers/spi/stm_msp.c b/drivers/spi/stm_msp.c
new file mode 100644
index 00000000000..65dc316b37b
--- /dev/null
+++ b/drivers/spi/stm_msp.c
@@ -0,0 +1,1929 @@
+/*
+ * drivers/spi/stm_msp.c
+ *
+ * Copyright (C) 2010 STMicroelectronics Pvt. Ltd.
+ *
+ * Author: Sachin Verma <sachin.verma@st.com>
+ *
+ * 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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/spi/spi.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/amba/bus.h>
+#include <linux/spi/stm_msp.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+
+/**
+ * MSP Controller Register Offsets
+ */
+#define MSP_DR(r) (r + 0x000)
+#define MSP_GCR(r) (r + 0x004)
+#define MSP_TCF(r) (r + 0x008)
+#define MSP_RCF(r) (r + 0x00C)
+#define MSP_SRG(r) (r + 0x010)
+#define MSP_FLR(r) (r + 0x014)
+#define MSP_DMACR(r) (r + 0x018)
+#define MSP_IMSC(r) (r + 0x020)
+#define MSP_RIS(r) (r + 0x024)
+#define MSP_MIS(r) (r + 0x028)
+#define MSP_ICR(r) (r + 0x02C)
+#define MSP_MCR(r) (r + 0x030)
+#define MSP_RCV(r) (r + 0x034)
+#define MSP_RCM(r) (r + 0x038)
+#define MSP_TCE0(r) (r + 0x040)
+#define MSP_TCE1(r) (r + 0x044)
+#define MSP_TCE2(r) (r + 0x048)
+#define MSP_TCE3(r) (r + 0x04C)
+#define MSP_RCE0(r) (r + 0x060)
+#define MSP_RCE1(r) (r + 0x064)
+#define MSP_RCE2(r) (r + 0x068)
+#define MSP_RCE3(r) (r + 0x06C)
+#define MSP_PID0(r) (r + 0xFE0)
+#define MSP_PID1(r) (r + 0xFE4)
+#define MSP_PID2(r) (r + 0xFE8)
+#define MSP_PID3(r) (r + 0xFEC)
+
+/**
+ * MSP Global Configuration Register - MSP_GCR
+ */
+#define MSP_GCR_MASK_RXEN ((u32)(0x1UL << 0))
+#define MSP_GCR_MASK_RFFEN ((u32)(0x1UL << 1))
+#define MSP_GCR_MASK_RFSPOL ((u32)(0x1UL << 2))
+#define MSP_GCR_MASK_DCM ((u32)(0x1UL << 3))
+#define MSP_GCR_MASK_RFSSEL ((u32)(0x1UL << 4))
+#define MSP_GCR_MASK_RCKPOL ((u32)(0x1UL << 5))
+#define MSP_GCR_MASK_RCKSEL ((u32)(0x1UL << 6))
+#define MSP_GCR_MASK_LBM ((u32)(0x1UL << 7))
+#define MSP_GCR_MASK_TXEN ((u32)(0x1UL << 8))
+#define MSP_GCR_MASK_TFFEN ((u32)(0x1UL << 9))
+#define MSP_GCR_MASK_TFSPOL ((u32)(0x1UL << 10))
+#define MSP_GCR_MASK_TFSSEL ((u32)(0x3UL << 11))
+#define MSP_GCR_MASK_TCKPOL ((u32)(0x1UL << 13))
+#define MSP_GCR_MASK_TCKSEL ((u32)(0x1UL << 14))
+#define MSP_GCR_MASK_TXDDL ((u32)(0x1UL << 15))
+#define MSP_GCR_MASK_SGEN ((u32)(0x1UL << 16))
+#define MSP_GCR_MASK_SCKPOL ((u32)(0x1UL << 17))
+#define MSP_GCR_MASK_SCKSEL ((u32)(0x3UL << 18))
+#define MSP_GCR_MASK_FGEN ((u32)(0x1UL << 20))
+#define MSP_GCR_MASK_SPICKM ((u32)(0x3UL << 21))
+#define MSP_GCR_MASK_SPIBME ((u32)(0x1UL << 23))
+
+/**
+ * MSP Transmit Configuration Register - MSP_TCF
+ */
+#define MSP_TCF_MASK_TP1ELEN ((u32)(0x7UL << 0))
+#define MSP_TCF_MASK_TP1FLEN ((u32)(0x7FUL << 3))
+#define MSP_TCF_MASK_TDTYP ((u32)(0x3UL << 10))
+#define MSP_TCF_MASK_TENDN ((u32)(0x1UL << 12))
+#define MSP_TCF_MASK_TDDLY ((u32)(0x3UL << 13))
+#define MSP_TCF_MASK_TFSIG ((u32)(0x1UL << 15))
+#define MSP_TCF_MASK_TP2ELEN ((u32)(0x7UL << 16))
+#define MSP_TCF_MASK_TP2FLEN ((u32)(0x7FUL << 19))
+#define MSP_TCF_MASK_TP2SM ((u32)(0x1UL << 26))
+#define MSP_TCF_MASK_TP2EN ((u32)(0x1UL << 27))
+#define MSP_TCF_MASK_TBSWAP ((u32)(0x3UL << 28))
+
+/**
+ * MSP Receive Configuration Register - MSP_RCF
+ */
+#define MSP_RCF_MASK_RP1ELEN ((u32)(0x7UL << 0))
+#define MSP_RCF_MASK_RP1FLEN ((u32)(0x7FUL << 3))
+#define MSP_RCF_MASK_RDTYP ((u32)(0x3UL << 10))
+#define MSP_RCF_MASK_RENDN ((u32)(0x1UL << 12))
+#define MSP_RCF_MASK_RDDLY ((u32)(0x3UL << 13))
+#define MSP_RCF_MASK_RFSIG ((u32)(0x1UL << 15))
+#define MSP_RCF_MASK_RP2ELEN ((u32)(0x7UL << 16))
+#define MSP_RCF_MASK_RP2FLEN ((u32)(0x7FUL << 19))
+#define MSP_RCF_MASK_RP2SM ((u32)(0x1UL << 26))
+#define MSP_RCF_MASK_RP2EN ((u32)(0x1UL << 27))
+#define MSP_RCF_MASK_RBSWAP ((u32)(0x3UL << 28))
+
+/**
+ * MSP Sample Rate Generator Register - MSP_SRG
+ */
+#define MSP_SRG_MASK_SCKDIV ((u32)(0x3FFUL << 0))
+#define MSP_SRG_MASK_FRWID ((u32)(0x3FUL << 10))
+#define MSP_SRG_MASK_FRPER ((u32)(0x1FFFUL << 16))
+
+/**
+ * MSP Flag Register - MSP_FLR
+ */
+#define MSP_FLR_MASK_RBUSY ((u32)(0x1UL << 0))
+#define MSP_FLR_MASK_RFE ((u32)(0x1UL << 1))
+#define MSP_FLR_MASK_RFU ((u32)(0x1UL << 2))
+#define MSP_FLR_MASK_TBUSY ((u32)(0x1UL << 3))
+#define MSP_FLR_MASK_TFE ((u32)(0x1UL << 4))
+#define MSP_FLR_MASK_TFU ((u32)(0x1UL << 5))
+
+/**
+ * MSP DMA Control Register - MSP_DMACR
+ */
+#define MSP_DMACR_MASK_RDMAE ((u32)(0x1UL << 0))
+#define MSP_DMACR_MASK_TDMAE ((u32)(0x1UL << 1))
+
+/**
+ * MSP Interrupt Mask Set/Clear Register - MSP_IMSC
+ */
+#define MSP_IMSC_MASK_RXIM ((u32)(0x1UL << 0))
+#define MSP_IMSC_MASK_ROEIM ((u32)(0x1UL << 1))
+#define MSP_IMSC_MASK_RSEIM ((u32)(0x1UL << 2))
+#define MSP_IMSC_MASK_RFSIM ((u32)(0x1UL << 3))
+#define MSP_IMSC_MASK_TXIM ((u32)(0x1UL << 4))
+#define MSP_IMSC_MASK_TUEIM ((u32)(0x1UL << 5))
+#define MSP_IMSC_MASK_TSEIM ((u32)(0x1UL << 6))
+#define MSP_IMSC_MASK_TFSIM ((u32)(0x1UL << 7))
+#define MSP_IMSC_MASK_RFOIM ((u32)(0x1UL << 8))
+#define MSP_IMSC_MASK_TFOIM ((u32)(0x1UL << 9))
+
+/**
+ * MSP Raw Interrupt status Register - MSP_RIS
+ */
+#define MSP_RIS_MASK_RXRIS ((u32)(0x1UL << 0))
+#define MSP_RIS_MASK_ROERIS ((u32)(0x1UL << 1))
+#define MSP_RIS_MASK_RSERIS ((u32)(0x1UL << 2))
+#define MSP_RIS_MASK_RFSRIS ((u32)(0x1UL << 3))
+#define MSP_RIS_MASK_TXRIS ((u32)(0x1UL << 4))
+#define MSP_RIS_MASK_TUERIS ((u32)(0x1UL << 5))
+#define MSP_RIS_MASK_TSERIS ((u32)(0x1UL << 6))
+#define MSP_RIS_MASK_TFSRIS ((u32)(0x1UL << 7))
+#define MSP_RIS_MASK_RFORIS ((u32)(0x1UL << 8))
+#define MSP_RIS_MASK_TFORIS ((u32)(0x1UL << 9))
+
+/**
+ * MSP Masked Interrupt status Register - MSP_MIS
+ */
+#define MSP_MIS_MASK_RXMIS ((u32)(0x1UL << 0))
+#define MSP_MIS_MASK_ROEMIS ((u32)(0x1UL << 1))
+#define MSP_MIS_MASK_RSEMIS ((u32)(0x1UL << 2))
+#define MSP_MIS_MASK_RFSMIS ((u32)(0x1UL << 3))
+#define MSP_MIS_MASK_TXMIS ((u32)(0x1UL << 4))
+#define MSP_MIS_MASK_TUEMIS ((u32)(0x1UL << 5))
+#define MSP_MIS_MASK_TSEMIS ((u32)(0x1UL << 6))
+#define MSP_MIS_MASK_TFSMIS ((u32)(0x1UL << 7))
+#define MSP_MIS_MASK_RFOMIS ((u32)(0x1UL << 8))
+#define MSP_MIS_MASK_TFOMIS ((u32)(0x1UL << 9))
+
+/**
+ * MSP Interrupt Clear Register - MSP_ICR
+ */
+#define MSP_ICR_MASK_ROEIC ((u32)(0x1UL << 1))
+#define MSP_ICR_MASK_RSEIC ((u32)(0x1UL << 2))
+#define MSP_ICR_MASK_RFSIC ((u32)(0x1UL << 3))
+#define MSP_ICR_MASK_TUEIC ((u32)(0x1UL << 5))
+#define MSP_ICR_MASK_TSEIC ((u32)(0x1UL << 6))
+#define MSP_ICR_MASK_TFSIC ((u32)(0x1UL << 7))
+
+#define GEN_MASK_BITS(val, mask, sb) ((u32)((((u32)val) << (sb)) & (mask)))
+#define MSP_WBITS(reg, val, mask, sb) ((reg) = (((reg) & ~(mask)) |\
+ (((val) << (sb)) & (mask))))
+#define DEFAULT_MSP_REG_DMACR 0x00000000
+#define DEFAULT_MSP_REG_SRG 0x1FFF0000
+
+#define DEFAULT_MSP_REG_GCR ( \
+ GEN_MASK_BITS(MSP_RECEIVER_DISABLED, MSP_GCR_MASK_RXEN, 0) |\
+ GEN_MASK_BITS(MSP_RX_FIFO_ENABLED, MSP_GCR_MASK_RFFEN, 1) |\
+ GEN_MASK_BITS(MSP_LOOPBACK_DISABLED, MSP_GCR_MASK_LBM, 7) |\
+ GEN_MASK_BITS(MSP_TRANSMITTER_DISABLED, MSP_GCR_MASK_TXEN, 8) |\
+ GEN_MASK_BITS(MSP_TX_FIFO_ENABLED, MSP_GCR_MASK_TFFEN, 9) |\
+ GEN_MASK_BITS(MSP_TX_FRAME_SYNC_POL_LOW, MSP_GCR_MASK_TFSPOL, 10)|\
+ GEN_MASK_BITS(MSP_TX_FRAME_SYNC_INT, MSP_GCR_MASK_TFSSEL, 11) |\
+ GEN_MASK_BITS(MSP_TX_CLOCK_POL_HIGH, MSP_GCR_MASK_TCKPOL, 13) |\
+ GEN_MASK_BITS(MSP_IS_SPI_MASTER, MSP_GCR_MASK_TCKSEL, 14) |\
+ GEN_MASK_BITS(MSP_TRANSMIT_DATA_WITHOUT_DELAY, MSP_GCR_MASK_TXDDL, 15)|\
+ GEN_MASK_BITS(MSP_SAMPLE_RATE_GEN_ENABLE, MSP_GCR_MASK_SGEN, 16)|\
+ GEN_MASK_BITS(MSP_CLOCK_INTERNAL, MSP_GCR_MASK_SCKSEL, 18) |\
+ GEN_MASK_BITS(MSP_FRAME_GEN_ENABLE, MSP_GCR_MASK_FGEN, 20) |\
+ GEN_MASK_BITS(MSP_SPI_PHASE_ZERO_CYCLE_DELAY, MSP_GCR_MASK_SPICKM, 21)|\
+ GEN_MASK_BITS(SPI_BURST_MODE_DISABLE, MSP_GCR_MASK_SPIBME, 23)\
+ )
+#define DEFAULT_MSP_REG_RCF ( \
+ GEN_MASK_BITS(MSP_DATA_BITS_32, MSP_RCF_MASK_RP1ELEN, 0) | \
+ GEN_MASK_BITS(MSP_IGNORE_RX_FRAME_SYNC_PULSE, MSP_RCF_MASK_RFSIG, 15) |\
+ GEN_MASK_BITS(MSP_RX_1BIT_DATA_DELAY, MSP_RCF_MASK_RDDLY, 13) | \
+ GEN_MASK_BITS(MSP_RX_ENDIANESS_LSB, MSP_RCF_MASK_RENDN, 12) \
+ )
+
+#define DEFAULT_MSP_REG_TCF ( \
+ GEN_MASK_BITS(MSP_DATA_BITS_32, MSP_TCF_MASK_TP1ELEN, 0) | \
+ GEN_MASK_BITS(MSP_IGNORE_TX_FRAME_SYNC_PULSE, MSP_TCF_MASK_TFSIG, 15) |\
+ GEN_MASK_BITS(MSP_TX_1BIT_DATA_DELAY, MSP_TCF_MASK_TDDLY, 13) | \
+ GEN_MASK_BITS(MSP_TX_ENDIANESS_LSB, MSP_TCF_MASK_TENDN, 12) \
+ )
+
+/**
+ * MSP Receiver/Transmitter states (enabled or disabled)
+ */
+#define MSP_RECEIVER_DISABLED 0
+#define MSP_RECEIVER_ENABLED 1
+#define MSP_TRANSMITTER_DISABLED 0
+#define MSP_TRANSMITTER_ENABLED 1
+
+/**
+ * MSP Receiver/Transmitter FIFO constants
+ */
+#define MSP_LOOPBACK_DISABLED 0
+#define MSP_LOOPBACK_ENABLED 1
+
+#define MSP_TX_FIFO_DISABLED 0
+#define MSP_TX_FIFO_ENABLED 1
+#define MSP_TX_ENDIANESS_MSB 0
+#define MSP_TX_ENDIANESS_LSB 1
+
+#define MSP_RX_FIFO_DISABLED 0
+#define MSP_RX_FIFO_ENABLED 1
+#define MSP_RX_ENDIANESS_MSB 0
+#define MSP_RX_ENDIANESS_LSB 1
+
+#define MSP_TX_FRAME_SYNC_EXT 0x0
+#define MSP_TX_FRAME_SYNC_INT 0x2
+#define MSP_TX_FRAME_SYNC_INT_CFG 0x3
+
+#define MSP_TX_FRAME_SYNC_POL_HIGH 0
+#define MSP_TX_FRAME_SYNC_POL_LOW 1
+
+#define MSP_HANDLE_RX_FRAME_SYNC_PULSE 0
+#define MSP_IGNORE_RX_FRAME_SYNC_PULSE 1
+
+#define MSP_RX_NO_DATA_DELAY 0x0
+#define MSP_RX_1BIT_DATA_DELAY 0x1
+#define MSP_RX_2BIT_DATA_DELAY 0x2
+#define MSP_RX_3BIT_DATA_DELAY 0x3
+
+#define MSP_HANDLE_TX_FRAME_SYNC_PULSE 0
+#define MSP_IGNORE_TX_FRAME_SYNC_PULSE 1
+
+#define MSP_TX_NO_DATA_DELAY 0x0
+#define MSP_TX_1BIT_DATA_DELAY 0x1
+#define MSP_TX_2BIT_DATA_DELAY 0x2
+#define MSP_TX_3BIT_DATA_DELAY 0x3
+
+#define MSP_TX_CLOCK_POL_LOW 0
+#define MSP_TX_CLOCK_POL_HIGH 1
+
+#define MSP_SPI_PHASE_ZERO_CYCLE_DELAY 0x2
+#define MSP_SPI_PHASE_HALF_CYCLE_DELAY 0x3
+
+#define MSP_IS_SPI_SLAVE 0
+#define MSP_IS_SPI_MASTER 1
+
+#define MSP_FRAME_GEN_DISABLE 0
+#define MSP_FRAME_GEN_ENABLE 1
+
+#define MSP_SAMPLE_RATE_GEN_DISABLE 0
+#define MSP_SAMPLE_RATE_GEN_ENABLE 1
+
+#define SPI_BURST_MODE_DISABLE 0
+#define SPI_BURST_MODE_ENABLE 1
+
+#define MSP_TRANSMIT_DATA_WITHOUT_DELAY 0
+#define MSP_TRANSMIT_DATA_WITH_DELAY 1
+
+#define MSP_CLOCK_INTERNAL 0x0 /* 48 MHz */
+
+/* SRG is derived from MSPSCK pin but is resynchronized on MSPRFS
+ * (Receive Frame Sync signal) */
+#define MSP_CLOCK_EXTERNAL 0x2
+#define MSP_CLOCK_EXTERNAL_RESYNC 0x3
+
+#define DISABLE_ALL_MSP_INTERRUPTS (0x0)
+#define ENABLE_ALL_MSP_INTERRUPTS (0x333)
+#define CLEAR_ALL_MSP_INTERRUPTS (0xEE)
+#define DEFAULT_MSP_CLK (48000000)
+#define MAX_SCKDIV (1023)
+
+#define MSP_FIFO_DEPTH 8
+
+/**
+ * Queue State
+ */
+#define QUEUE_RUNNING (0)
+#define QUEUE_STOPPED (1)
+
+#define START_STATE ((void *)0)
+#define RUNNING_STATE ((void *)1)
+#define DONE_STATE ((void *)2)
+#define ERROR_STATE ((void *)-1)
+
+/* Default values */
+#define SPI_DEFAULT_MAX_SPEED_HZ 48000
+#define SPI_TRANSFER_TIMEOUT_MS 5000
+
+/* CONTROLLER COMMANDS */
+enum cntlr_commands {
+ DISABLE_CONTROLLER = 0,
+ ENABLE_CONTROLLER ,
+ DISABLE_ALL_INTERRUPT ,
+ ENABLE_ALL_INTERRUPT ,
+ FLUSH_FIFO ,
+ RESTORE_STATE ,
+ LOAD_DEFAULT_CONFIG ,
+ CLEAR_ALL_INTERRUPT,
+};
+
+struct stm_msp {
+ struct amba_device *adev;
+ struct spi_master *master;
+ struct stm_msp_controller *master_info;
+ void __iomem *regs;
+ struct clk *clk;
+#ifdef CONFIG_SPI_WORKQUEUE
+ struct workqueue_struct *workqueue;
+#endif
+ struct work_struct spi_work;
+ spinlock_t lock;
+ struct list_head queue;
+ int busy;
+ int run;
+ struct tasklet_struct pump_transfers;
+ struct timer_list spi_notify_timer;
+ int spi_io_error;
+ struct spi_message *cur_msg;
+ struct spi_transfer *cur_transfer;
+ struct chip_data *cur_chip;
+ void *tx;
+ void *tx_end;
+ void *rx;
+ void *rx_end;
+ void (*write)(struct stm_msp *stm_msp);
+ void (*read)(struct stm_msp *stm_msp);
+ void (*delay)(struct stm_msp *stm_msp);
+};
+
+/**
+ * struct chip_data - To maintain runtime state of SPICntlr for each client chip
+ * @ctr_regs: void pointer which is assigned a struct having regs of the cntlr.
+ * @chip_id: Chip Id assigned to this client to identify it.
+ * @n_bytes: how many bytes(power of 2) reqd for a given data width of client
+ * @write: function to be used to write when doing xfer for this chip
+ * @null_write: function to be used for dummy write for receiving data.
+ * @read: function to be used to read when doing xfer for this chip
+ * @null_read: function to be used to for dummy read while writting data.
+ * @cs_control: chip select callback provided by chip
+ * @xfer_type: polling/interrupt
+ *
+ * Runtime state of the SPI controller, maintained per chip,
+ * This would be set according to the current message that would be served
+ */
+struct chip_data {
+ void *ctr_regs;
+ u32 chip_id;
+ u8 n_bytes;
+ void (*write) (struct stm_msp *stm_msp);
+ void (*null_write) (struct stm_msp *stm_msp);
+ void (*read) (struct stm_msp *stm_msp);
+ void (*null_read) (struct stm_msp *stm_msp);
+ void (*delay) (struct stm_msp *stm_msp);
+ void (*cs_control) (u32 command);
+ int xfer_type;
+};
+
+/**
+ * struct msp_regs - Used to store MSP controller registry values
+ * used by the driver.
+ * @gcr: global configuration register
+ * @tcf: transmit configuration register
+ * @rcf: receive configuration register
+ * @srg: sample rate generator register
+ * @dmacr: DMA configuration register
+ */
+struct msp_regs {
+ u32 gcr;
+ u32 tcf;
+ u32 rcf;
+ u32 srg;
+ u32 dmacr;
+};
+
+/**
+ * stm_msp_controller_cmd - To execute controller commands for MSP
+ * @stm_msp: SPI driver private data structure
+ * @cmd: Command which is to be executed on the controller
+ */
+static int stm_msp_controller_cmd(struct stm_msp *stm_msp, int cmd)
+{
+ int retval = 0;
+ struct msp_regs *msp_regs = NULL;
+
+ switch (cmd) {
+ case DISABLE_CONTROLLER: {
+ dev_dbg(&stm_msp->adev->dev,
+ "Disabling MSP controller...\n");
+ writel((readl(MSP_GCR(stm_msp->regs)) &
+ (~(MSP_GCR_MASK_TXEN | MSP_GCR_MASK_RXEN))),
+ MSP_GCR(stm_msp->regs));
+ break;
+ }
+ case ENABLE_CONTROLLER: {
+ dev_dbg(&stm_msp->adev->dev,
+ "Enabling MSP controller...\n");
+ writel((readl(MSP_GCR(stm_msp->regs)) |
+ (MSP_GCR_MASK_TXEN | MSP_GCR_MASK_RXEN)),
+ MSP_GCR(stm_msp->regs));
+ break;
+ }
+ case DISABLE_ALL_INTERRUPT: {
+ dev_dbg(&stm_msp->adev->dev,
+ "Disabling all MSP interrupts...\n");
+ writel(DISABLE_ALL_MSP_INTERRUPTS,
+ MSP_IMSC(stm_msp->regs));
+ break;
+ }
+ case ENABLE_ALL_INTERRUPT: {
+ dev_dbg(&stm_msp->adev->dev,
+ "Enabling all MSP interrupts...\n");
+ writel(ENABLE_ALL_MSP_INTERRUPTS,
+ MSP_IMSC(stm_msp->regs));
+ break;
+ }
+ case CLEAR_ALL_INTERRUPT: {
+ dev_dbg(&stm_msp->adev->dev,
+ "Clearing all MSP interrupts...\n");
+ writel(CLEAR_ALL_MSP_INTERRUPTS,
+ MSP_ICR(stm_msp->regs));
+ break;
+ }
+ case FLUSH_FIFO: {
+ unsigned long limit = loops_per_jiffy << 1;
+
+ dev_dbg(&stm_msp->adev->dev, "MSP FIFO flushed\n");
+
+ do {
+ while (!(readl(MSP_FLR(stm_msp->regs)) &
+ MSP_FLR_MASK_RFE)) {
+ readl(MSP_DR(stm_msp->regs));
+ }
+ } while ((readl(MSP_FLR(stm_msp->regs)) &
+ (MSP_FLR_MASK_TBUSY | MSP_FLR_MASK_RBUSY)) &&
+ limit--);
+
+ retval = limit;
+ break;
+ }
+ case RESTORE_STATE: {
+ msp_regs =
+ (struct msp_regs *)stm_msp->cur_chip->ctr_regs;
+
+ dev_dbg(&stm_msp->adev->dev,
+ "Restoring MSP state...\n");
+
+ writel(msp_regs->gcr, MSP_GCR(stm_msp->regs));
+ writel(msp_regs->tcf, MSP_TCF(stm_msp->regs));
+ writel(msp_regs->rcf, MSP_RCF(stm_msp->regs));
+ writel(msp_regs->srg, MSP_SRG(stm_msp->regs));
+ writel(msp_regs->dmacr, MSP_DMACR(stm_msp->regs));
+ writel(DISABLE_ALL_MSP_INTERRUPTS,
+ MSP_IMSC(stm_msp->regs));
+ writel(CLEAR_ALL_MSP_INTERRUPTS,
+ MSP_ICR(stm_msp->regs));
+ break;
+ }
+ case LOAD_DEFAULT_CONFIG: {
+ dev_dbg(&stm_msp->adev->dev,
+ "Loading default MSP config...\n");
+
+ writel(DEFAULT_MSP_REG_GCR, MSP_GCR(stm_msp->regs));
+ writel(DEFAULT_MSP_REG_TCF, MSP_TCF(stm_msp->regs));
+ writel(DEFAULT_MSP_REG_RCF, MSP_RCF(stm_msp->regs));
+ writel(DEFAULT_MSP_REG_SRG, MSP_SRG(stm_msp->regs));
+ writel(DEFAULT_MSP_REG_DMACR, MSP_DMACR(stm_msp->regs));
+ writel(DISABLE_ALL_MSP_INTERRUPTS,
+ MSP_IMSC(stm_msp->regs));
+ writel(CLEAR_ALL_MSP_INTERRUPTS,
+ MSP_ICR(stm_msp->regs));
+ break;
+ }
+ default:
+ dev_dbg(&stm_msp->adev->dev, "Unknown command\n");
+ retval = -1;
+ break;
+ }
+
+ return retval;
+}
+
+/**
+ * giveback - current spi_message is over, schedule next spi_message
+ * @message: current SPI message
+ * @stm_msp: spi driver private data structure
+ *
+ * current spi_message is over, schedule next spi_message and call
+ * callback of this msg.
+ */
+static void giveback(struct spi_message *message, struct stm_msp *stm_msp)
+{
+ struct spi_transfer *last_transfer;
+ unsigned long flags;
+ struct spi_message *msg;
+ void (*curr_cs_control)(u32 command);
+
+ spin_lock_irqsave(&stm_msp->lock, flags);
+ msg = stm_msp->cur_msg;
+
+ curr_cs_control = stm_msp->cur_chip->cs_control;
+
+ stm_msp->cur_msg = NULL;
+ stm_msp->cur_transfer = NULL;
+ stm_msp->cur_chip = NULL;
+#ifdef CONFIG_SPI_WORKQUEUE
+ queue_work(stm_msp->workqueue, &stm_msp->spi_work);
+#else
+ schedule_work(&stm_msp->spi_work);
+#endif
+ spin_unlock_irqrestore(&stm_msp->lock, flags);
+
+ last_transfer = list_entry(msg->transfers.prev,
+ struct spi_transfer, transfer_list);
+
+ if (!last_transfer->cs_change)
+ curr_cs_control(SPI_CHIP_DESELECT);
+
+ msg->state = NULL;
+
+ if (msg->complete)
+ msg->complete(msg->context);
+
+ stm_msp_controller_cmd(stm_msp, DISABLE_CONTROLLER);
+ clk_disable(stm_msp->clk);
+}
+
+/**
+ * spi_notify - Handles Polling hang issue over spi bus.
+ * @data: main driver data
+ * Context: Process.
+ *
+ * This is used to handle error condition in transfer and receive function used
+ * in polling mode.
+ * Sometimes due to passing wrong protocol desc , polling transfer may hang.
+ * To prevent this, timer is added.
+ *
+ * Returns void.
+ */
+static void spi_notify(unsigned long data)
+{
+ struct stm_msp *stm_msp = (struct stm_msp *)data;
+ stm_msp->spi_io_error = 1;
+
+ dev_err(&stm_msp->adev->dev,
+ "Polling is taking time, maybe device not responding\n");
+
+ del_timer(&stm_msp->spi_notify_timer);
+}
+
+/**
+ * stm_msp_transfer - transfer function registered to SPI master framework
+ * @spi: spi device which is requesting transfer
+ * @msg: spi message which is to handled is queued to driver queue
+ *
+ * This function is registered to the SPI framework for this SPI master
+ * controller. It will queue the spi_message in the queue of driver if
+ * the queue is not stopped and return.
+ */
+static int stm_msp_transfer(struct spi_device *spi, struct spi_message *msg)
+{
+ struct stm_msp *stm_msp = spi_master_get_devdata(spi->master);
+ unsigned long flags;
+
+ spin_lock_irqsave(&stm_msp->lock, flags);
+
+ if (stm_msp->run == QUEUE_STOPPED) {
+ spin_unlock_irqrestore(&stm_msp->lock, flags);
+ return -ESHUTDOWN;
+ }
+ dev_err(&spi->dev, "Regular request (No infinite DMA ongoing)\n");
+
+ msg->actual_length = 0;
+ msg->status = -EINPROGRESS;
+ msg->state = START_STATE;
+
+ list_add_tail(&msg->queue, &stm_msp->queue);
+
+ if ((stm_msp->run == QUEUE_RUNNING) && (!stm_msp->busy))
+#ifdef CONFIG_SPI_WORKQUEUE
+ queue_work(stm_msp->workqueue, &stm_msp->spi_work);
+#else
+ schedule_work(&stm_msp->spi_work);
+#endif
+ spin_unlock_irqrestore(&stm_msp->lock, flags);
+ return 0;
+}
+
+/**
+ * next_transfer - Move to the Next transfer in the current spi message
+ * @stm_msp: spi driver private data structure
+ *
+ * This function moves though the linked list of spi transfers in the
+ * current spi message and returns with the state of current spi
+ * message i.e whether its last transfer is done(DONE_STATE) or
+ * Next transfer is ready(RUNNING_STATE)
+ */
+static void *next_transfer(struct stm_msp *stm_msp)
+{
+ struct spi_message *msg = stm_msp->cur_msg;
+ struct spi_transfer *trans = stm_msp->cur_transfer;
+
+ /* Move to next transfer */
+ if (trans->transfer_list.next != &msg->transfers) {
+ stm_msp->cur_transfer = list_entry(trans->transfer_list.next,
+ struct spi_transfer,
+ transfer_list);
+ return RUNNING_STATE;
+ }
+ return DONE_STATE;
+}
+
+static void do_interrupt_transfer(void *data)
+{
+ struct stm_msp *stm_msp = (struct stm_msp *)data;
+
+ stm_msp->tx = (void *)stm_msp->cur_transfer->tx_buf;
+ stm_msp->tx_end = stm_msp->tx + stm_msp->cur_transfer->len;
+
+ stm_msp->rx = (void *)stm_msp->cur_transfer->rx_buf;
+ stm_msp->rx_end = stm_msp->rx + stm_msp->cur_transfer->len;
+
+ stm_msp->write = stm_msp->tx ?
+ stm_msp->cur_chip->write : stm_msp->cur_chip->null_write;
+ stm_msp->read = stm_msp->rx ?
+ stm_msp->cur_chip->read : stm_msp->cur_chip->null_read;
+
+ stm_msp->cur_chip->cs_control(SPI_CHIP_SELECT);
+
+ stm_msp_controller_cmd(stm_msp, ENABLE_ALL_INTERRUPT);
+ stm_msp_controller_cmd(stm_msp, ENABLE_CONTROLLER);
+}
+
+static void do_polling_transfer(void *data)
+{
+ struct stm_msp *stm_msp = (struct stm_msp *)data;
+ struct spi_message *message = NULL;
+ struct spi_transfer *transfer = NULL;
+ struct spi_transfer *previous = NULL;
+ struct chip_data *chip;
+ unsigned long limit = 0;
+ u32 timer_expire = 0;
+
+ chip = stm_msp->cur_chip;
+ message = stm_msp->cur_msg;
+
+ while (message->state != DONE_STATE) {
+ /* Handle for abort */
+ if (message->state == ERROR_STATE)
+ break;
+
+ transfer = stm_msp->cur_transfer;
+
+ /* Delay if requested at end of transfer */
+ if (message->state == RUNNING_STATE) {
+ previous = list_entry(transfer->transfer_list.prev,
+ struct spi_transfer,
+ transfer_list);
+
+ if (previous->delay_usecs)
+ udelay(previous->delay_usecs);
+
+ if (previous->cs_change)
+ stm_msp->cur_chip->cs_control(SPI_CHIP_SELECT);
+ } else {
+ /* START_STATE */
+ message->state = RUNNING_STATE;
+ stm_msp->cur_chip->cs_control(SPI_CHIP_SELECT);
+ }
+
+ /* Configuration Changing Per Transfer */
+ stm_msp->tx = (void *)transfer->tx_buf;
+ stm_msp->tx_end = stm_msp->tx + stm_msp->cur_transfer->len;
+ stm_msp->rx = (void *)transfer->rx_buf;
+ stm_msp->rx_end = stm_msp->rx + stm_msp->cur_transfer->len;
+
+ stm_msp->write = stm_msp->tx ?
+ stm_msp->cur_chip->write :
+ stm_msp->cur_chip->null_write;
+ stm_msp->read = stm_msp->rx ?
+ stm_msp->cur_chip->read :
+ stm_msp->cur_chip->null_read;
+ stm_msp->delay = stm_msp->cur_chip->delay;
+
+ stm_msp_controller_cmd(stm_msp, FLUSH_FIFO);
+ stm_msp_controller_cmd(stm_msp, ENABLE_CONTROLLER);
+
+ timer_expire = stm_msp->cur_transfer->len / 1024;
+
+ if (!timer_expire)
+ timer_expire = SPI_TRANSFER_TIMEOUT_MS;
+ else
+ timer_expire =
+ (stm_msp->cur_transfer->len / 1024) *
+ SPI_TRANSFER_TIMEOUT_MS;
+
+ stm_msp->spi_notify_timer.expires =
+ jiffies + msecs_to_jiffies(timer_expire);
+
+ add_timer(&stm_msp->spi_notify_timer);
+
+ dev_dbg(&stm_msp->adev->dev, "Polling transfer ongoing...\n");
+
+ while (stm_msp->tx < stm_msp->tx_end) {
+
+ stm_msp_controller_cmd(stm_msp, DISABLE_CONTROLLER);
+ stm_msp->read(stm_msp);
+ stm_msp->write(stm_msp);
+
+ stm_msp_controller_cmd(stm_msp, ENABLE_CONTROLLER);
+
+ if (stm_msp->delay)
+ stm_msp->delay(stm_msp);
+
+ if (stm_msp->spi_io_error == 1)
+ break;
+ }
+
+ del_timer(&stm_msp->spi_notify_timer);
+
+ if (stm_msp->spi_io_error == 1)
+ goto out;
+
+ limit = loops_per_jiffy << 1;
+
+ while ((stm_msp->rx < stm_msp->rx_end) && (limit--))
+ stm_msp->read(stm_msp);
+
+ /* Update total byte transfered */
+ message->actual_length += stm_msp->cur_transfer->len;
+
+ if (stm_msp->cur_transfer->cs_change)
+ stm_msp->cur_chip->cs_control(SPI_CHIP_DESELECT);
+
+ stm_msp_controller_cmd(stm_msp, DISABLE_CONTROLLER);
+
+ /* Move to next transfer */
+ message->state = next_transfer(stm_msp);
+ }
+out:
+ /* Handle end of message */
+ if (message->state == DONE_STATE)
+ message->status = 0;
+ else
+ message->status = -EIO;
+
+ giveback(message, stm_msp);
+
+ stm_msp->spi_io_error = 0; /* Reset state for further transfers */
+
+ return;
+}
+
+/**
+ * pump_messages - Workqueue function which processes spi message queue
+ * @work: pointer to work
+ *
+ * This function checks if there is any spi message in the queue that
+ * needs processing and delegate control to appropriate function
+ * do_polling_transfer()/do_interrupt_transfer()/do_dma_transfer()
+ * based on the kind of the transfer
+ *
+ */
+static void pump_messages(struct work_struct *work)
+{
+ struct stm_msp *stm_msp = container_of(work, struct stm_msp, spi_work);
+ unsigned long flags;
+
+ /* Lock queue and check for queue work */
+ spin_lock_irqsave(&stm_msp->lock, flags);
+
+ if (list_empty(&stm_msp->queue) || stm_msp->run == QUEUE_STOPPED) {
+ dev_dbg(&stm_msp->adev->dev, "work_queue: Queue Empty\n");
+ stm_msp->busy = 0;
+ spin_unlock_irqrestore(&stm_msp->lock, flags);
+ return;
+ }
+ /* Make sure we are not already running a message */
+ if (stm_msp->cur_msg) {
+ spin_unlock_irqrestore(&stm_msp->lock, flags);
+ return;
+ }
+
+ clk_enable(stm_msp->clk);
+
+ /* Extract head of queue */
+ stm_msp->cur_msg = list_entry(stm_msp->queue.next,
+ struct spi_message,
+ queue);
+
+ list_del_init(&stm_msp->cur_msg->queue);
+ stm_msp->busy = 1;
+ spin_unlock_irqrestore(&stm_msp->lock, flags);
+
+ /* Initial message state */
+ stm_msp->cur_msg->state = START_STATE;
+ stm_msp->cur_transfer = list_entry(stm_msp->cur_msg->transfers.next,
+ struct spi_transfer,
+ transfer_list);
+
+ /* Setup the SPI using the per chip configuration */
+ stm_msp->cur_chip = spi_get_ctldata(stm_msp->cur_msg->spi);
+ stm_msp_controller_cmd(stm_msp, RESTORE_STATE);
+ stm_msp_controller_cmd(stm_msp, FLUSH_FIFO);
+
+ if (stm_msp->cur_chip->xfer_type == SPI_POLLING_TRANSFER)
+ do_polling_transfer(stm_msp);
+ else if (stm_msp->cur_chip->xfer_type == SPI_INTERRUPT_TRANSFER)
+ do_interrupt_transfer(stm_msp);
+}
+
+/**
+ * pump_transfers - Tasklet function which schedules next interrupt xfer
+ * @data: spi driver private data structure
+ */
+static void pump_transfers(unsigned long data)
+{
+ struct stm_msp *stm_msp = (struct stm_msp *)data;
+ struct spi_message *message = NULL;
+ struct spi_transfer *transfer = NULL;
+ struct spi_transfer *previous = NULL;
+
+ message = stm_msp->cur_msg;
+
+ /* Handle for abort */
+ if (message->state == ERROR_STATE) {
+ message->status = -EIO;
+ giveback(message, stm_msp);
+ return;
+ }
+
+ /* Handle end of message */
+ if (message->state == DONE_STATE) {
+ message->status = 0;
+ giveback(message, stm_msp);
+ return;
+ }
+ transfer = stm_msp->cur_transfer;
+
+ /* Delay if requested at end of transfer */
+ if (message->state == RUNNING_STATE) {
+ previous = list_entry(transfer->transfer_list.prev,
+ struct spi_transfer, transfer_list);
+ if (previous->delay_usecs)
+ udelay(previous->delay_usecs);
+ if (previous->cs_change)
+ stm_msp->cur_chip->cs_control(SPI_CHIP_SELECT);
+ } else {
+ /* START_STATE */
+ message->state = RUNNING_STATE;
+ }
+ stm_msp->tx = (void *)transfer->tx_buf;
+ stm_msp->tx_end = stm_msp->tx + stm_msp->cur_transfer->len;
+ stm_msp->rx = (void *)transfer->rx_buf;
+ stm_msp->rx_end = stm_msp->rx + stm_msp->cur_transfer->len;
+
+ stm_msp->write = stm_msp->tx ?
+ stm_msp->cur_chip->write : stm_msp->cur_chip->null_write;
+ stm_msp->read = stm_msp->rx ?
+ stm_msp->cur_chip->read : stm_msp->cur_chip->null_read;
+
+ stm_msp_controller_cmd(stm_msp, FLUSH_FIFO);
+ stm_msp_controller_cmd(stm_msp, ENABLE_ALL_INTERRUPT);
+}
+
+static int init_queue(struct stm_msp *stm_msp)
+{
+ INIT_LIST_HEAD(&stm_msp->queue);
+ spin_lock_init(&stm_msp->lock);
+
+ stm_msp->run = QUEUE_STOPPED;
+ stm_msp->busy = 0;
+
+ tasklet_init(&stm_msp->pump_transfers, pump_transfers,
+ (unsigned long)stm_msp);
+ INIT_WORK(&stm_msp->spi_work, pump_messages);
+
+#ifdef CONFIG_SPI_WORKQUEUE
+ stm_msp->workqueue = create_singlethread_workqueue(
+ dev_name(&stm_msp->master->dev));
+
+ if (stm_msp->workqueue == NULL)
+ return -EBUSY;
+#endif /* CONFIG_SPI_WORKQUEUE */
+
+ init_timer(&stm_msp->spi_notify_timer);
+
+ stm_msp->spi_notify_timer.expires = jiffies + msecs_to_jiffies(1000);
+ stm_msp->spi_notify_timer.function = spi_notify;
+ stm_msp->spi_notify_timer.data = (unsigned long)stm_msp;
+
+ return 0;
+}
+
+static int start_queue(struct stm_msp *stm_msp)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&stm_msp->lock, flags);
+
+ if (stm_msp->run == QUEUE_RUNNING || stm_msp->busy) {
+ spin_unlock_irqrestore(&stm_msp->lock, flags);
+ return -EBUSY;
+ }
+
+ stm_msp->run = QUEUE_RUNNING;
+ stm_msp->cur_msg = NULL;
+ stm_msp->cur_transfer = NULL;
+ stm_msp->cur_chip = NULL;
+ spin_unlock_irqrestore(&stm_msp->lock, flags);
+ return 0;
+}
+
+static int stop_queue(struct stm_msp *stm_msp)
+{
+ unsigned long flags;
+ unsigned limit = 500;
+ int status = 0;
+
+ spin_lock_irqsave(&stm_msp->lock, flags);
+
+ /* This is a bit lame, but is optimized for the common execution path.
+ * A wait_queue on the stm_msp->busy could be used, but then the common
+ * execution path (pump_messages) would be required to call wake_up or
+ * friends on every SPI message. Do this instead */
+
+ stm_msp->run = QUEUE_STOPPED;
+
+ while (!list_empty(&stm_msp->queue) && stm_msp->busy && limit--) {
+ spin_unlock_irqrestore(&stm_msp->lock, flags);
+ msleep(10);
+ spin_lock_irqsave(&stm_msp->lock, flags);
+ }
+
+ if (!list_empty(&stm_msp->queue) || stm_msp->busy)
+ status = -EBUSY;
+
+ spin_unlock_irqrestore(&stm_msp->lock, flags);
+
+ return status;
+}
+
+static int destroy_queue(struct stm_msp *stm_msp)
+{
+ int status;
+
+ status = stop_queue(stm_msp);
+
+ if (status != 0)
+ return status;
+#ifdef CONFIG_SPI_WORKQUEUE
+ destroy_workqueue(stm_msp->workqueue);
+#endif
+ del_timer_sync(&stm_msp->spi_notify_timer);
+
+ return 0;
+}
+
+/**
+ * stm_msp_null_writer - To Write Dummy Data in Data register
+ * @stm_msp: spi driver private data structure
+ *
+ * This function is set as a write function for transfer which have
+ * Tx transfer buffer as NULL. It simply writes '0' in the Data
+ * register
+ */
+static void stm_msp_null_writer(struct stm_msp *stm_msp)
+{
+ u32 cur_write = 0;
+ u32 status;
+
+ while (1) {
+ status = readl(MSP_FLR(stm_msp->regs));
+
+ if ((status & MSP_FLR_MASK_TFU) ||
+ (stm_msp->tx >= stm_msp->tx_end))
+ return;
+
+ writel(0x0, MSP_DR(stm_msp->regs));
+ stm_msp->tx += (stm_msp->cur_chip->n_bytes);
+ cur_write++;
+
+ if (cur_write == 8)
+ return;
+ }
+}
+
+/**
+ * stm_msp_null_reader - To read data from Data register and discard it
+ * @stm_msp: spi driver private data structure
+ *
+ * This function is set as a reader function for transfer which have
+ * Rx Transfer buffer as null. Read Data is rejected
+ */
+static void stm_msp_null_reader(struct stm_msp *stm_msp)
+{
+ u32 status;
+
+ while (1) {
+ status = readl(MSP_FLR(stm_msp->regs));
+
+ if ((status & MSP_FLR_MASK_RFE) ||
+ (stm_msp->rx >= stm_msp->rx_end))
+ return;
+
+ readl(MSP_DR(stm_msp->regs));
+ stm_msp->rx += (stm_msp->cur_chip->n_bytes);
+ }
+}
+
+/**
+ * stm_msp_u8_writer - Write FIFO data in Data register as a 8 Bit Data
+ * @stm_msp: spi driver private data structure
+ *
+ * This function writes data in Tx FIFO till it is not full
+ * which is indicated by the status register or our transfer is complete.
+ * It also updates the temporary write ptr tx in stm_msp which maintains
+ * current write position in transfer buffer. we do not write data more than
+ * FIFO depth
+ */
+void stm_msp_u8_writer(struct stm_msp *stm_msp)
+{
+ u32 cur_write = 0;
+ u32 status;
+
+ while (1) {
+ status = readl(MSP_FLR(stm_msp->regs));
+
+ if ((status & MSP_FLR_MASK_TFU) ||
+ (stm_msp->tx >= stm_msp->tx_end))
+ return;
+
+ writel((u32)(*(u8 *)(stm_msp->tx)), MSP_DR(stm_msp->regs));
+ stm_msp->tx += (stm_msp->cur_chip->n_bytes);
+ cur_write++;
+
+ if (cur_write == MSP_FIFO_DEPTH)
+ return;
+ }
+}
+
+/**
+ * stm_msp_u8_reader - Read FIFO data in Data register as a 8 Bit Data
+ * @stm_msp: spi driver private data structure
+ *
+ * This function reads data in Rx FIFO till it is not empty
+ * which is indicated by the status register or our transfer is complete.
+ * It also updates the temporary Read ptr rx in stm_msp which maintains
+ * current read position in transfer buffer
+ */
+void stm_msp_u8_reader(struct stm_msp *stm_msp)
+{
+ u32 status;
+
+ while (1) {
+ status = readl(MSP_FLR(stm_msp->regs));
+
+ if ((status & MSP_FLR_MASK_RFE) ||
+ (stm_msp->rx >= stm_msp->rx_end))
+ return;
+
+ *(u8 *)(stm_msp->rx) = (u8)readl(MSP_DR(stm_msp->regs));
+ stm_msp->rx += (stm_msp->cur_chip->n_bytes);
+ }
+}
+
+/**
+ * stm_msp_u16_writer - Write FIFO data in Data register as a 16 Bit Data
+ * @stm_msp: spi driver private data structure
+ *
+ * This function writes data in Tx FIFO till it is not full
+ * which is indicated by the status register or our transfer is complete.
+ * It also updates the temporary write ptr tx in stm_msp which maintains
+ * current write position in transfer buffer. we do not write data more than
+ * FIFO depth
+ */
+void stm_msp_u16_writer(struct stm_msp *stm_msp)
+{
+ u32 cur_write = 0;
+ u32 status;
+
+ while (1) {
+ status = readl(MSP_FLR(stm_msp->regs));
+
+ if ((status & MSP_FLR_MASK_TFU) ||
+ (stm_msp->tx >= stm_msp->tx_end))
+ return;
+
+ writel((u32)(*(u16 *)(stm_msp->tx)), MSP_DR(stm_msp->regs));
+ stm_msp->tx += (stm_msp->cur_chip->n_bytes);
+ cur_write++;
+
+ if (cur_write == MSP_FIFO_DEPTH)
+ return;
+ }
+}
+
+/**
+ * stm_msp_u16_reader - Read FIFO data in Data register as a 16 Bit Data
+ * @stm_msp: spi driver private data structure
+ *
+ * This function reads data in Rx FIFO till it is not empty
+ * which is indicated by the status register or our transfer is complete.
+ * It also updates the temporary Read ptr rx in stm_msp which maintains
+ * current read position in transfer buffer
+ */
+void stm_msp_u16_reader(struct stm_msp *stm_msp)
+{
+ u32 status;
+
+ while (1) {
+ status = readl(MSP_FLR(stm_msp->regs));
+
+ if ((status & MSP_FLR_MASK_RFE) ||
+ (stm_msp->rx >= stm_msp->rx_end))
+ return;
+
+ *(u16 *)(stm_msp->rx) = (u16)readl(MSP_DR(stm_msp->regs));
+ stm_msp->rx += (stm_msp->cur_chip->n_bytes);
+ }
+}
+
+/**
+ * stm_msp_u32_writer - Write FIFO data in Data register as a 32 Bit Data
+ * @stm_msp: spi driver private data structure
+ *
+ * This function writes data in Tx FIFO till it is not full
+ * which is indicated by the status register or our transfer is complete.
+ * It also updates the temporary write ptr tx in stm_msp which maintains
+ * current write position in transfer buffer. we do not write data more than
+ * FIFO depth
+ */
+void stm_msp_u32_writer(struct stm_msp *stm_msp)
+{
+ u32 cur_write = 0;
+ u32 status;
+
+ while (1) {
+ status = readl(MSP_FLR(stm_msp->regs));
+
+ if ((status & MSP_FLR_MASK_TFU) ||
+ (stm_msp->tx >= stm_msp->tx_end))
+ return;
+
+ /* Write Data to Data Register */
+ writel(*(u32 *)(stm_msp->tx), MSP_DR(stm_msp->regs));
+ stm_msp->tx += (stm_msp->cur_chip->n_bytes);
+ cur_write++;
+
+ if (cur_write == MSP_FIFO_DEPTH)
+ return;
+ }
+}
+
+/**
+ * stm_msp_u32_reader - Read FIFO data in Data register as a 32 Bit Data
+ * @stm_msp: spi driver private data structure
+ *
+ * This function reads data in Rx FIFO till it is not empty
+ * which is indicated by the status register or our transfer is complete.
+ * It also updates the temporary Read ptr rx in stm_msp which maintains
+ * current read position in transfer buffer
+ */
+void stm_msp_u32_reader(struct stm_msp *stm_msp)
+{
+ u32 status;
+
+ while (1) {
+ status = readl(MSP_FLR(stm_msp->regs));
+
+ if ((status & MSP_FLR_MASK_RFE) ||
+ (stm_msp->rx >= stm_msp->rx_end))
+ return;
+
+ *(u32 *)(stm_msp->rx) = readl(MSP_DR(stm_msp->regs));
+ stm_msp->rx += (stm_msp->cur_chip->n_bytes);
+ }
+}
+
+/**
+ * stm_msp_interrupt_handler - Interrupt hanlder function
+ */
+static irqreturn_t stm_msp_interrupt_handler(int irq, void *dev_id)
+{
+ struct stm_msp *stm_msp = (struct stm_msp *)dev_id;
+ struct spi_message *msg = stm_msp->cur_msg;
+ u32 irq_status = 0;
+ u32 flag = 0;
+
+ if (!msg) {
+ dev_err(&stm_msp->adev->dev,
+ "Bad message state in interrupt handler");
+ /* Never fail */
+ return IRQ_HANDLED;
+ }
+
+ /* Read the Interrupt Status Register */
+ irq_status = readl(MSP_MIS(stm_msp->regs));
+
+ if (irq_status) {
+ if (irq_status & MSP_MIS_MASK_ROEMIS) { /* Overrun interrupt */
+ /* Bail out our Data has been corrupted */
+ dev_dbg(&stm_msp->adev->dev,
+ "Received ROR interrupt\n");
+
+ stm_msp_controller_cmd(stm_msp, DISABLE_ALL_INTERRUPT);
+ stm_msp_controller_cmd(stm_msp, CLEAR_ALL_INTERRUPT);
+ stm_msp_controller_cmd(stm_msp, DISABLE_CONTROLLER);
+ msg->state = ERROR_STATE;
+ tasklet_schedule(&stm_msp->pump_transfers);
+ return IRQ_HANDLED;
+ }
+
+ stm_msp->read(stm_msp);
+ stm_msp->write(stm_msp);
+
+ if ((stm_msp->tx == stm_msp->tx_end) && (flag == 0)) {
+ flag = 1;
+ /* Disable Transmit interrupt */
+ writel(readl(MSP_IMSC(stm_msp->regs)) &
+ (~MSP_IMSC_MASK_TXIM) & (~MSP_IMSC_MASK_TFOIM),
+ (stm_msp->regs + 0x14));
+ }
+
+ /* Clearing any Xmit underrun error. Overrun already handled */
+ stm_msp_controller_cmd(stm_msp, CLEAR_ALL_INTERRUPT);
+
+ if (stm_msp->rx == stm_msp->rx_end) {
+ stm_msp_controller_cmd(stm_msp, DISABLE_ALL_INTERRUPT);
+ stm_msp_controller_cmd(stm_msp, CLEAR_ALL_INTERRUPT);
+
+ dev_dbg(&stm_msp->adev->dev,
+ "Interrupt transfer completed.\n");
+
+ /* Update total bytes transfered */
+ msg->actual_length += stm_msp->cur_transfer->len;
+
+ if (stm_msp->cur_transfer->cs_change)
+ stm_msp->cur_chip->cs_control(
+ SPI_CHIP_DESELECT);
+
+ /* Move to next transfer */
+ msg->state = next_transfer(stm_msp);
+ tasklet_schedule(&stm_msp->pump_transfers);
+ return IRQ_HANDLED;
+ }
+ }
+ return IRQ_HANDLED;
+}
+
+/**
+ * stm_msp_cleanup - cleanup function registered to SPI master framework
+ * @spi: spi device which is requesting cleanup
+ *
+ * This function is registered to the SPI framework for this SPI master
+ * controller. It will free the runtime state of chip.
+ */
+static void stm_msp_cleanup(struct spi_device *spi)
+{
+ struct chip_data *chip = spi_get_ctldata((struct spi_device *)spi);
+ struct stm_msp *stm_msp = spi_master_get_devdata(spi->master);
+ struct spi_master *master;
+ master = stm_msp->master;
+
+ if (chip) {
+ kfree(chip->ctr_regs);
+ kfree(chip);
+ spi_set_ctldata(spi, NULL);
+ }
+}
+
+/**
+ * null_cs_control - Dummy chip select function
+ * @command: select/delect the chip
+ *
+ * If no chip select function is provided by client this is used as dummy
+ * chip select
+ */
+static void null_cs_control(u32 command)
+{
+ /* Nothing to do */
+ (void)command;
+}
+
+static int verify_msp_controller_parameters(struct stm_msp_config_chip
+ *chip_info)
+{
+
+ /* FIXME: check clock params */
+ if ((chip_info->lbm != SPI_LOOPBACK_ENABLED) &&
+ (chip_info->lbm != SPI_LOOPBACK_DISABLED)) {
+ dev_dbg(chip_info->dev,
+ "Loopback Mode is configured incorrectly\n");
+ return -1;
+ }
+ if ((chip_info->hierarchy != SPI_MASTER) &&
+ (chip_info->hierarchy != SPI_SLAVE)) {
+ dev_dbg(chip_info->dev,
+ "hierarchy is configured incorrectly\n");
+ return -1;
+ }
+ if ((chip_info->endian_rx != SPI_FIFO_MSB) &&
+ (chip_info->endian_rx != SPI_FIFO_LSB)) {
+ dev_dbg(chip_info->dev,
+ "Rx FIFO endianess is configured incorrectly\n");
+ return -1;
+ }
+ if ((chip_info->endian_tx != SPI_FIFO_MSB) &&
+ (chip_info->endian_tx != SPI_FIFO_LSB)) {
+ dev_dbg(chip_info->dev,
+ "Tx FIFO endianess is configured incorrectly\n");
+ return -1;
+ }
+ if ((chip_info->data_size < MSP_DATA_BITS_8) ||
+ (chip_info->data_size > MSP_DATA_BITS_32)) {
+ dev_dbg(chip_info->dev,
+ "MSP DATA Size is configured incorrectly\n");
+ return -1;
+ }
+ if ((chip_info->com_mode != SPI_INTERRUPT_TRANSFER) &&
+ (chip_info->com_mode != SPI_POLLING_TRANSFER)) {
+ dev_dbg(chip_info->dev,
+ "Communication mode is configured incorrectly\n");
+ return -1;
+ }
+ if (((chip_info->proto_params).clk_phase !=
+ SPI_CLK_ZERO_CYCLE_DELAY) &&
+ ((chip_info->proto_params).clk_phase !=
+ SPI_CLK_HALF_CYCLE_DELAY)) {
+ dev_dbg(chip_info->dev,
+ "Clock Phase is configured incorrectly\n");
+ return -1;
+ }
+ if (((chip_info->proto_params).clk_pol !=
+ SPI_CLK_POL_IDLE_LOW) &&
+ ((chip_info->proto_params).clk_pol !=
+ SPI_CLK_POL_IDLE_HIGH)) {
+ dev_dbg(chip_info->dev,
+ "Clk Polarity configured incorrectly\n");
+ return -1;
+ }
+ if (chip_info->cs_control == NULL) {
+ dev_dbg(chip_info->dev,
+ "Chip Select Function is NULL for this chip\n");
+ chip_info->cs_control = null_cs_control;
+ }
+ return 0;
+}
+
+static struct stm_msp_config_chip *allocate_default_msp_chip_cfg(
+ struct spi_device *spi)
+{
+ struct stm_msp_config_chip *chip_info;
+
+ chip_info = kzalloc(sizeof(struct stm_msp_config_chip), GFP_KERNEL);
+
+ if (!chip_info) {
+ dev_err(&spi->dev, "setup - cannot allocate controller data");
+ return NULL;
+ }
+ dev_dbg(&spi->dev, "Allocated Memory for controller data\n");
+
+ chip_info->lbm = SPI_LOOPBACK_DISABLED;
+ chip_info->com_mode = SPI_POLLING_TRANSFER;
+ chip_info->hierarchy = SPI_MASTER;
+ chip_info->endian_tx = SPI_FIFO_LSB;
+ chip_info->endian_rx = SPI_FIFO_LSB;
+ chip_info->data_size = MSP_DATA_BITS_32;
+
+ if (spi->max_speed_hz != 0)
+ chip_info->freq = spi->max_speed_hz;
+ else
+ chip_info->freq = SPI_DEFAULT_MAX_SPEED_HZ;
+
+ chip_info->proto_params.clk_phase = SPI_CLK_HALF_CYCLE_DELAY;
+ chip_info->proto_params.clk_pol = SPI_CLK_POL_IDLE_LOW;
+ chip_info->cs_control = null_cs_control;
+
+ return chip_info;
+}
+
+static void stm_msp_delay(struct stm_msp *stm_msp)
+{
+ udelay(15);
+
+ while (readl(MSP_FLR(stm_msp->regs)) &
+ (MSP_FLR_MASK_RBUSY | MSP_FLR_MASK_TBUSY))
+ udelay(1);
+}
+
+/**
+ * stm_msp_setup - setup function registered to SPI master framework
+ * @spi: spi device which is requesting setup
+ *
+ * This function is registered to the SPI framework for this SPI master
+ * controller. If it is the first time when setup is called by this device,
+ * this function will initialize the runtime state for this chip and save
+ * the same in the device structure. Else it will update the runtime info
+ * with the updated chip info.
+ */
+static int stm_msp_setup(struct spi_device *spi)
+{
+ struct stm_msp_config_chip *chip_info;
+ struct chip_data *curr_cfg;
+ struct spi_master *master;
+ int status = 0;
+ u16 sckdiv = 0;
+ s16 bus_num = 0;
+ struct stm_msp *stm_msp = spi_master_get_devdata(spi->master);
+ struct msp_regs *msp_regs;
+ master = stm_msp->master;
+ bus_num = master->bus_num - 1;
+
+ /* Get controller data */
+ chip_info = spi->controller_data;
+ /* Get controller_state */
+ curr_cfg = spi_get_ctldata(spi);
+
+ if (curr_cfg == NULL) {
+ curr_cfg = kzalloc(sizeof(struct chip_data), GFP_KERNEL);
+
+ if (!curr_cfg) {
+ dev_err(&stm_msp->adev->dev,
+ "setup - cannot allocate controller state");
+ return -ENOMEM;
+ }
+
+ curr_cfg->chip_id = spi->chip_select;
+ curr_cfg->ctr_regs = kzalloc(sizeof(struct msp_regs),
+ GFP_KERNEL);
+
+ if (curr_cfg->ctr_regs == NULL) {
+ dev_err(&stm_msp->adev->dev,
+ "setup - cannot allocate mem for regs");
+ goto err_first_setup;
+ }
+
+ dev_err(&stm_msp->adev->dev,
+ "chip Id = %d\n", curr_cfg->chip_id);
+
+ if (chip_info == NULL) {
+ chip_info = allocate_default_msp_chip_cfg(spi);
+
+ if (!chip_info) {
+ dev_err(&stm_msp->adev->dev,
+ "setup - cannot allocate cntlr data");
+ status = -ENOMEM;
+ goto err_first_setup;
+ }
+
+ spi->controller_data = chip_info;
+ }
+ }
+
+ /* Pointer back to the SPI device */
+ chip_info->dev = &spi->dev;
+
+ if (chip_info->freq == 0) {
+ /* Calculate Specific Freq. */
+ if ((MSP_INTERNAL_CLK == chip_info->clk_freq.clk_src) ||
+ (MSP_EXTERNAL_CLK == chip_info->clk_freq.clk_src)) {
+ sckdiv = chip_info->clk_freq.sckdiv;
+ } else {
+ status = -1;
+ dev_err(&stm_msp->adev->dev,
+ "setup - controller clock data is incorrect");
+ goto err_config_params;
+ }
+ } else {
+ /* Calculate Effective Freq. */
+ sckdiv = (DEFAULT_MSP_CLK / (chip_info->freq)) - 1;
+
+ if (sckdiv > MAX_SCKDIV) {
+ dev_dbg(&stm_msp->adev->dev,
+ "SPI: Cannot set frequency less than 48Khz,"
+ "setting lowest(48 Khz)\n");
+ sckdiv = MAX_SCKDIV;
+ }
+ }
+
+ status = verify_msp_controller_parameters(chip_info);
+
+ if (status) {
+ dev_err(&stm_msp->adev->dev,
+ "setup - controller data is incorrect");
+ goto err_config_params;
+ }
+
+ /* Now set controller state based on controller data */
+ curr_cfg->xfer_type = chip_info->com_mode;
+ curr_cfg->cs_control = chip_info->cs_control;
+ curr_cfg->delay = stm_msp_delay;
+
+ curr_cfg->null_write = stm_msp_null_writer;
+ curr_cfg->null_read = stm_msp_null_reader;
+
+ if (chip_info->data_size <= MSP_DATA_BITS_8) {
+ dev_dbg(&stm_msp->adev->dev, "Less than 8 bits per word...\n");
+
+ curr_cfg->n_bytes = 1;
+ curr_cfg->read = stm_msp_u8_reader;
+ curr_cfg->write = stm_msp_u8_writer;
+ } else if (chip_info->data_size <= MSP_DATA_BITS_16) {
+ dev_dbg(&stm_msp->adev->dev, "Less than 16 bits per word...\n");
+
+ curr_cfg->n_bytes = 2;
+ curr_cfg->read = stm_msp_u16_reader;
+ curr_cfg->write = stm_msp_u16_writer;
+ } else {
+ dev_dbg(&stm_msp->adev->dev, "Less than 32 bits per word...\n");
+
+ curr_cfg->n_bytes = 4;
+ curr_cfg->read = stm_msp_u32_reader;
+ curr_cfg->write = stm_msp_u32_writer;
+ }
+
+ /* Now initialize all register settings reqd. for this chip */
+
+ msp_regs = (struct msp_regs *)(curr_cfg->ctr_regs);
+ msp_regs->gcr = 0x0;
+ msp_regs->tcf = 0x0;
+ msp_regs->rcf = 0x0;
+ msp_regs->srg = 0x0;
+ msp_regs->dmacr = 0x0;
+
+ MSP_WBITS(msp_regs->dmacr, 0x0, MSP_DMACR_MASK_RDMAE, 0);
+ MSP_WBITS(msp_regs->dmacr, 0x0, MSP_DMACR_MASK_TDMAE, 1);
+
+ /* GCR Reg Config */
+
+ MSP_WBITS(msp_regs->gcr,
+ MSP_RECEIVER_DISABLED, MSP_GCR_MASK_RXEN, 0);
+ MSP_WBITS(msp_regs->gcr,
+ MSP_RX_FIFO_ENABLED, MSP_GCR_MASK_RFFEN, 1);
+ MSP_WBITS(msp_regs->gcr,
+ MSP_TRANSMITTER_DISABLED, MSP_GCR_MASK_TXEN, 8);
+ MSP_WBITS(msp_regs->gcr,
+ MSP_TX_FIFO_ENABLED, MSP_GCR_MASK_TFFEN, 9);
+ MSP_WBITS(msp_regs->gcr,
+ MSP_TX_FRAME_SYNC_POL_LOW, MSP_GCR_MASK_TFSPOL, 10);
+ MSP_WBITS(msp_regs->gcr,
+ MSP_TX_FRAME_SYNC_INT, MSP_GCR_MASK_TFSSEL, 11);
+ MSP_WBITS(msp_regs->gcr,
+ MSP_TRANSMIT_DATA_WITH_DELAY, MSP_GCR_MASK_TXDDL, 15);
+ MSP_WBITS(msp_regs->gcr,
+ MSP_SAMPLE_RATE_GEN_ENABLE, MSP_GCR_MASK_SGEN, 16);
+ MSP_WBITS(msp_regs->gcr,
+ MSP_CLOCK_INTERNAL, MSP_GCR_MASK_SCKSEL, 18);
+ MSP_WBITS(msp_regs->gcr,
+ MSP_FRAME_GEN_ENABLE, MSP_GCR_MASK_FGEN, 20);
+ MSP_WBITS(msp_regs->gcr,
+ SPI_BURST_MODE_DISABLE, MSP_GCR_MASK_SPIBME, 23);
+
+ if (chip_info->lbm == SPI_LOOPBACK_ENABLED)
+ MSP_WBITS(msp_regs->gcr,
+ MSP_LOOPBACK_ENABLED, MSP_GCR_MASK_LBM, 7);
+ else
+ MSP_WBITS(msp_regs->gcr,
+ MSP_LOOPBACK_DISABLED, MSP_GCR_MASK_LBM, 7);
+
+ if (chip_info->hierarchy == SPI_MASTER)
+ MSP_WBITS(msp_regs->gcr,
+ MSP_IS_SPI_MASTER, MSP_GCR_MASK_TCKSEL, 14);
+ else
+ MSP_WBITS(msp_regs->gcr,
+ MSP_IS_SPI_SLAVE, MSP_GCR_MASK_TCKSEL, 14);
+
+ if (chip_info->proto_params.clk_phase == SPI_CLK_ZERO_CYCLE_DELAY)
+ MSP_WBITS(msp_regs->gcr,
+ MSP_SPI_PHASE_ZERO_CYCLE_DELAY,
+ MSP_GCR_MASK_SPICKM, 21);
+ else
+ MSP_WBITS(msp_regs->gcr,
+ MSP_SPI_PHASE_HALF_CYCLE_DELAY,
+ MSP_GCR_MASK_SPICKM, 21);
+
+ if (chip_info->proto_params.clk_pol == SPI_CLK_POL_IDLE_HIGH)
+ MSP_WBITS(msp_regs->gcr,
+ MSP_TX_CLOCK_POL_HIGH, MSP_GCR_MASK_TCKPOL, 13);
+ else
+ MSP_WBITS(msp_regs->gcr,
+ MSP_TX_CLOCK_POL_LOW, MSP_GCR_MASK_TCKPOL, 13);
+
+ /* RCF Reg Config */
+ MSP_WBITS(msp_regs->rcf,
+ MSP_IGNORE_RX_FRAME_SYNC_PULSE, MSP_RCF_MASK_RFSIG, 15);
+ MSP_WBITS(msp_regs->rcf,
+ MSP_RX_1BIT_DATA_DELAY, MSP_RCF_MASK_RDDLY, 13);
+
+ if (chip_info->endian_rx == SPI_FIFO_LSB)
+ MSP_WBITS(msp_regs->rcf,
+ MSP_RX_ENDIANESS_LSB, MSP_RCF_MASK_RENDN, 12);
+ else
+ MSP_WBITS(msp_regs->rcf,
+ MSP_RX_ENDIANESS_MSB, MSP_RCF_MASK_RENDN, 12);
+
+ MSP_WBITS(msp_regs->rcf, chip_info->data_size, MSP_RCF_MASK_RP1ELEN, 0);
+
+ /* TCF Reg Config */
+
+ MSP_WBITS(msp_regs->tcf,
+ MSP_IGNORE_TX_FRAME_SYNC_PULSE, MSP_TCF_MASK_TFSIG, 15);
+ MSP_WBITS(msp_regs->tcf,
+ MSP_TX_1BIT_DATA_DELAY, MSP_TCF_MASK_TDDLY, 13);
+
+ if (chip_info->endian_rx == SPI_FIFO_LSB)
+ MSP_WBITS(msp_regs->tcf,
+ MSP_TX_ENDIANESS_LSB, MSP_TCF_MASK_TENDN, 12);
+ else
+ MSP_WBITS(msp_regs->tcf,
+ MSP_TX_ENDIANESS_MSB, MSP_TCF_MASK_TENDN, 12);
+ MSP_WBITS(msp_regs->tcf, chip_info->data_size, MSP_TCF_MASK_TP1ELEN, 0);
+
+ /* SRG Reg Config */
+
+ MSP_WBITS(msp_regs->srg, sckdiv, MSP_SRG_MASK_SCKDIV, 0);
+
+ /* Save controller_state */
+ spi_set_ctldata(spi, curr_cfg);
+
+ return status;
+
+err_config_params:
+err_first_setup:
+
+ kfree(curr_cfg);
+ return status;
+}
+
+static int __init stm_msp_probe(struct amba_device *adev, const struct amba_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct stm_msp_controller *platform_info = adev->dev.platform_data;
+ struct spi_master *master;
+ struct stm_msp *stm_msp = NULL; /* Data for this driver */
+ int irq, status = 0;
+
+ dev_info(dev, "STM MSP driver, device ID: 0x%08x\n", adev->periphid);
+
+ if (platform_info == NULL) {
+ dev_err(dev, "probe - no platform data supplied\n");
+ status = -ENODEV;
+ goto err_no_pdata;
+ }
+
+ /* Allocate master with space for data */
+ master = spi_alloc_master(dev, sizeof(struct stm_msp));
+
+ if (master == NULL) {
+ dev_err(dev, "probe - cannot alloc spi_master\n");
+ status = -ENOMEM;
+ goto err_no_mem;
+ }
+
+ stm_msp = spi_master_get_devdata(master);
+ stm_msp->master = master;
+ stm_msp->master_info = platform_info;
+ stm_msp->adev = adev;
+
+ stm_msp->clk = clk_get(&adev->dev, NULL);
+
+ if (IS_ERR(stm_msp->clk)) {
+ dev_err(dev, "probe - cannot find clock\n");
+ status = PTR_ERR(stm_msp->clk);
+ goto free_master;
+ }
+
+ /* Fetch the Resources, using platform data */
+ status = amba_request_regions(adev, NULL);
+
+ if (status) {
+ status = -ENODEV;
+ goto disable_clk;
+ }
+
+ /* Get Hold of Device Register Area... */
+ stm_msp->regs = ioremap(adev->res.start, resource_size(&adev->res));
+
+ if (stm_msp->regs == NULL) {
+ status = -ENODEV;
+ goto disable_clk;
+ }
+
+ irq = adev->irq[0];
+
+ if (irq <= 0) {
+ status = -ENODEV;
+ goto err_no_iores;
+ }
+
+ stm_msp_controller_cmd(stm_msp, LOAD_DEFAULT_CONFIG);
+
+ /* Required Info for an SPI controller */
+ /* Bus Number Which Assigned to this SPI controller on this board */
+ master->bus_num = (u16) platform_info->id;
+ master->num_chipselect = platform_info->num_chipselect;
+ master->setup = stm_msp_setup;
+ master->cleanup = (void *)stm_msp_cleanup;
+ master->transfer = stm_msp_transfer;
+
+ dev_dbg(dev, "BUSNO: %d\n", master->bus_num);
+
+ /* Initialize and start queue */
+ status = init_queue(stm_msp);
+
+ if (status != 0) {
+ dev_err(dev, "probe - problem initializing queue\n");
+ goto err_init_queue;
+ }
+
+ status = start_queue(stm_msp);
+
+ if (status != 0) {
+ dev_err(dev, "probe - problem starting queue\n");
+ goto err_start_queue;
+ }
+
+ amba_set_drvdata(adev, stm_msp);
+
+ dev_dbg(dev, "probe succeded\n");
+ dev_dbg(dev, "Bus No = %d, IRQ Line = %d, Virtual Addr: %x\n",
+ master->bus_num, irq, (u32)(stm_msp->regs));
+
+ status = request_irq(stm_msp->adev->irq[0],
+ stm_msp_interrupt_handler,
+ 0, stm_msp->master_info->device_name,
+ stm_msp);
+
+ if (status < 0) {
+ dev_err(dev, "probe - cannot get IRQ (%d)\n", status);
+ goto err_irq;
+ }
+
+ /* Register with the SPI framework */
+ status = spi_register_master(master);
+
+ if (status != 0) {
+ dev_err(dev, "probe - problem registering spi master\n");
+ goto err_spi_register;
+ }
+
+ return 0;
+
+err_spi_register:
+ free_irq(stm_msp->adev->irq[0], stm_msp);
+err_irq:
+err_init_queue:
+err_start_queue:
+ destroy_queue(stm_msp);
+err_no_iores:
+ iounmap(stm_msp->regs);
+disable_clk:
+ clk_put(stm_msp->clk);
+free_master:
+ spi_master_put(master);
+err_no_mem:
+err_no_pdata:
+ return status;
+}
+
+static int __exit stm_msp_remove(struct amba_device *adev)
+{
+ struct stm_msp *stm_msp = amba_get_drvdata(adev);
+ int status = 0;
+
+ if (!stm_msp)
+ return 0;
+
+ /* Remove the queue */
+ status = destroy_queue(stm_msp);
+
+ if (status != 0) {
+ dev_err(&adev->dev, "queue remove failed (%d)\n", status);
+ return status;
+ }
+
+ stm_msp_controller_cmd(stm_msp, LOAD_DEFAULT_CONFIG);
+
+ /* Release map resources */
+ iounmap(stm_msp->regs);
+ amba_release_regions(adev);
+ tasklet_disable(&stm_msp->pump_transfers);
+ free_irq(stm_msp->adev->irq[0], stm_msp);
+
+ /* Disconnect from the SPI framework */
+ spi_unregister_master(stm_msp->master);
+
+ clk_put(stm_msp->clk);
+
+ /* Prevent double remove */
+ amba_set_drvdata(adev, NULL);
+ dev_dbg(&adev->dev, "remove succeded\n");
+ return status;
+}
+
+#ifdef CONFIG_PM
+
+/**
+ * stm_msp_suspend - MSP suspend function registered with PM framework.
+ * @dev: Reference to amba device structure of the device
+ * @state: power mgmt state.
+ *
+ * This function is invoked when the system is going into sleep, called
+ * by the power management framework of the linux kernel.
+ */
+static int stm_msp_suspend(struct amba_device *adev, pm_message_t state)
+{
+ struct stm_msp *stm_msp = amba_get_drvdata(adev);
+ int status = 0;
+
+ status = stop_queue(stm_msp);
+
+ if (status != 0) {
+ dev_warn(&adev->dev, "suspend cannot stop queue\n");
+ return status;
+ }
+
+ dev_dbg(&adev->dev, "suspended\n");
+ return 0;
+}
+
+/**
+ * stm_msp_resume - MSP Resume function registered with PM framework.
+ * @dev: Reference to amba device structure of the device
+ *
+ * This function is invoked when the system is coming out of sleep, called
+ * by the power management framework of the linux kernel.
+ */
+static int stm_msp_resume(struct amba_device *adev)
+{
+ struct stm_msp *stm_msp = amba_get_drvdata(adev);
+ int status = 0;
+
+ /* Start the queue running */
+ status = start_queue(stm_msp);
+
+ if (status)
+ dev_err(&adev->dev, "problem starting queue (%d)\n", status);
+ else
+ dev_dbg(&adev->dev, "resumed\n");
+
+ return status;
+}
+
+#else
+#define stm_msp_suspend NULL
+#define stm_msp_resume NULL
+#endif /* CONFIG_PM */
+
+static struct amba_id stm_msp_ids[] = {
+ {
+ .id = 0x00280021,
+ .mask = 0x00ffffff,
+ },
+ {
+ 0,
+ 0,
+ },
+};
+
+static struct amba_driver __refdata stm_msp_driver = {
+ .drv = {
+ .name = "MSP",
+ },
+ .id_table = stm_msp_ids,
+ .probe = stm_msp_probe,
+ .remove = __exit_p(stm_msp_remove),
+ .resume = stm_msp_resume,
+ .suspend = stm_msp_suspend,
+};
+
+static int __init stm_msp_init(void)
+{
+ return amba_driver_register(&stm_msp_driver);
+}
+
+static void __exit stm_msp_exit(void)
+{
+ amba_driver_unregister(&stm_msp_driver);
+}
+
+module_init(stm_msp_init);
+module_exit(stm_msp_exit);
+
+MODULE_AUTHOR("Sachin Verma <sachin.verma@st.com>");
+MODULE_DESCRIPTION("STM MSP (SPI protocol) Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/spi/stm_msp.h b/include/linux/spi/stm_msp.h
new file mode 100644
index 00000000000..501023105cb
--- /dev/null
+++ b/include/linux/spi/stm_msp.h
@@ -0,0 +1,126 @@
+/*
+ * include/linux/spi/stm_msp.h
+ *
+ * Copyright (C) 2010 STMicroelectronics Pvt. Ltd.
+ *
+ * Author: Sachin Verma <sachin.verma@st.com>
+ *
+ * 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.
+ */
+#ifndef _STM_MSP_H
+#define _STM_MSP_H
+
+#include <linux/device.h>
+
+/* CHIP select/deselect commands */
+enum spi_chip_select {
+ SPI_CHIP_SELECT,
+ SPI_CHIP_DESELECT
+};
+
+/* Common configuration for different SPI controllers */
+enum spi_loopback {
+ SPI_LOOPBACK_DISABLED,
+ SPI_LOOPBACK_ENABLED
+};
+
+enum spi_hierarchy {
+ SPI_MASTER,
+ SPI_SLAVE
+};
+
+/* Endianess of FIFO Data */
+enum spi_fifo_endian {
+ SPI_FIFO_MSB,
+ SPI_FIFO_LSB
+};
+
+/* SPI mode of operation (Communication modes) */
+enum spi_mode {
+ SPI_INTERRUPT_TRANSFER,
+ SPI_POLLING_TRANSFER,
+};
+
+enum msp_data_size {
+ MSP_DATA_BITS_DEFAULT = -1,
+ MSP_DATA_BITS_8 = 0x00,
+ MSP_DATA_BITS_10,
+ MSP_DATA_BITS_12,
+ MSP_DATA_BITS_14,
+ MSP_DATA_BITS_16,
+ MSP_DATA_BITS_20,
+ MSP_DATA_BITS_24,
+ MSP_DATA_BITS_32,
+};
+
+enum msp_clk_src {
+ MSP_INTERNAL_CLK = 0x0,
+ MSP_EXTERNAL_CLK,
+};
+
+struct msp_clock_params {
+ enum msp_clk_src clk_src;
+ /* value from 0 to 1023 */
+ u16 sckdiv;
+ /* Used only when MSPSCK clocks the sample rate
+ * generator (SCKSEL = 1Xb):
+ * 0b: The rising edge of MSPSCK clocks the sample rate generator
+ * 1b: The falling edge of MSPSCK clocks the sample rate generator */
+ int sckpol;
+};
+
+/* Motorola SPI protocol specific definitions */
+enum spi_clk_phase {
+ SPI_CLK_ZERO_CYCLE_DELAY = 0x0, /* Receive data on rising edge. */
+ SPI_CLK_HALF_CYCLE_DELAY /* Receive data on falling edge. */
+};
+
+/* SPI Clock Polarity */
+enum spi_clk_pol {
+ SPI_CLK_POL_IDLE_LOW, /* Low inactive level */
+ SPI_CLK_POL_IDLE_HIGH /* High inactive level */
+};
+
+struct motorola_spi_proto_params {
+ enum spi_clk_phase clk_phase;
+ enum spi_clk_pol clk_pol;
+};
+
+struct stm_msp_config_chip {
+ struct device *dev;
+ enum spi_loopback lbm;
+ enum spi_hierarchy hierarchy;
+ enum spi_fifo_endian endian_rx;
+ enum spi_fifo_endian endian_tx;
+ enum spi_mode com_mode;
+ enum msp_data_size data_size;
+ struct msp_clock_params clk_freq;
+ int spi_burst_mode_enable;
+ struct motorola_spi_proto_params proto_params;
+ u32 freq;
+ void (*cs_control)(u32 control);
+};
+
+/**
+ * struct stm_msp_controller - device.platform_data for SPI controller devices.
+ *
+ * @num_chipselect: chipselects are used to distinguish individual
+ * SPI slaves, and are numbered from zero to num_chipselects - 1.
+ * each slave has a chipselect signal, but it's common that not
+ * every chipselect is connected to a slave.
+ */
+struct stm_msp_controller {
+ u8 num_chipselect;
+ u32 id;
+ u32 base_addr;
+ char *device_name;
+};
+#endif /* _STM_MSP_H */
diff --git a/include/sound/ux500_ab8500.h b/include/sound/ux500_ab8500.h
new file mode 100644
index 00000000000..7858bfdb4fa
--- /dev/null
+++ b/include/sound/ux500_ab8500.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Jarmo K. Kuronen <jarmo.kuronen@symbio.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#ifndef UX500_AB8500_H
+#define UX500_AB8500_H
+
+extern struct snd_soc_ops ux500_ab8500_ops[];
+
+struct snd_soc_pcm_runtime;
+
+int ux500_ab8500_startup(struct snd_pcm_substream *substream);
+
+void ux500_ab8500_shutdown(struct snd_pcm_substream *substream);
+
+int ux500_ab8500_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params);
+
+int ux500_ab8500_soc_machine_drv_init(void);
+
+void ux500_ab8500_soc_machine_drv_cleanup(void);
+
+int ux500_ab8500_machine_codec_init(struct snd_soc_pcm_runtime *runtime);
+
+extern void ux500_ab8500_jack_report(int);
+
+#endif
diff --git a/include/sound/ux500_ab8500_ext.h b/include/sound/ux500_ab8500_ext.h
new file mode 100644
index 00000000000..8c2b5d6bdf3
--- /dev/null
+++ b/include/sound/ux500_ab8500_ext.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#ifndef UX500_AB8500_EXT_H
+#define UX500_AB8500_EXT_H
+
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+
+int ux500_ab8500_audio_gpadc_measure(struct ab8500_gpadc *gpadc,
+ u8 channel, bool mode, int *value);
+
+void ux500_ab8500_audio_pwm_vibra(unsigned char speed_left_pos,
+ unsigned char speed_left_neg,
+ unsigned char speed_right_pos,
+ unsigned char speed_right_neg);
+
+#endif
diff --git a/sound/arm/Kconfig b/sound/arm/Kconfig
index 885683a3b0b..86cd48bfd8d 100644
--- a/sound/arm/Kconfig
+++ b/sound/arm/Kconfig
@@ -39,5 +39,17 @@ config SND_PXA2XX_AC97
Say Y or M if you want to support any AC97 codec attached to
the PXA2xx AC97 interface.
+config SND_U8500_ALSA_AB8500
+ tristate "U8500 alsa support for AB8500"
+ depends on SND && STE_DMA40 && U8500_ACODEC && (U8500_AB8500_ED || U8500_AB8500_CUT10)
+ default y
+ select SND_PCM
+ help
+ Say Y here if you have a u8500 based device
+ and want to use alsa for pcm playback and capture.
+
+ To compile this driver as a module, choose M here: the module
+ will be called u8500mod_alsa.
+
endif # SND_ARM
diff --git a/sound/arm/Makefile b/sound/arm/Makefile
index 8c0c851d464..e41f1f4db14 100644
--- a/sound/arm/Makefile
+++ b/sound/arm/Makefile
@@ -14,3 +14,7 @@ snd-pxa2xx-lib-$(CONFIG_SND_PXA2XX_LIB_AC97) += pxa2xx-ac97-lib.o
obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o
snd-pxa2xx-ac97-objs := pxa2xx-ac97.o
+obj-$(CONFIG_SND_U8500_ALSA_AB8500) += u8500mod_alsa.o
+ifneq ($(CONFIG_SND_U8500_ALSA_AB8500),n)
+u8500mod_alsa-objs := u8500_alsa_ab8500.o u8500_alsa_hdmi.o
+endif
diff --git a/sound/arm/u8500_alsa_ab8500.c b/sound/arm/u8500_alsa_ab8500.c
new file mode 100644
index 00000000000..39752388ab1
--- /dev/null
+++ b/sound/arm/u8500_alsa_ab8500.c
@@ -0,0 +1,2691 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Deepak Karda
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2.1 as published
+ * by the Free Software Foundation.
+ */
+
+/* This include must be defined at this point */
+//#include <sound/driver.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/ioctl.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <mach/hardware.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+/* alsa system */
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include "u8500_alsa_ab8500.h"
+#include <mach/msp.h>
+#include <mach/debug.h>
+
+#define ALSA_NAME "DRIVER ALSA"
+
+#define DRIVER_DEBUG CONFIG_STM_ALSA_DEBUG /* enables/disables debug msgs */
+#define DRIVER_DEBUG_PFX ALSA_NAME /* msg header represents this module */
+#define DRIVER_DBG KERN_ERR /* message level */
+
+static struct platform_device *device;
+static int active_user = 0;
+
+/*
+** Externel references
+*/
+#if DRIVER_DEBUG > 0
+t_ab8500_codec_error dump_acodec_registers(void);
+t_ab8500_codec_error dump_msp_registers(void);
+#endif
+
+extern int u8500_acodec_rates[MAX_NO_OF_RATES];
+extern char *lpbk_state_in_texts[NUMBER_LOOPBACK_STATE];
+extern char *switch_state_in_texts[NUMBER_SWITCH_STATE];
+extern char *power_state_in_texts[NUMBER_POWER_STATE];
+extern char *tdm_mode_state_in_texts[NUMBER_TDM_MODE_STATE];
+extern char *direct_rendering_state_in_texts[NUMBER_DIRECT_RENDERING_STATE];
+extern char *pcm_rendering_state_in_texts[NUMBER_PCM_RENDERING_STATE];
+extern char *codec_dest_texts[NUMBER_OUTPUT_DEVICE];
+extern char *codec_in_texts[NUMBER_INPUT_DEVICE];
+extern struct driver_debug_st DBG_ST;
+extern int second_config;
+extern int u8500_register_alsa_hdmi_controls(struct snd_card *card,
+ u8500_acodec_chip_t * u8500_chip);
+extern int snd_card_u8500_alsa_hdmi_new(u8500_acodec_chip_t * chip, int device);
+/*
+** Declaration for local functions
+*/
+static int u8500_analog_lpbk_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_analog_lpbk_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_analog_lpbk_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_digital_lpbk_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_digital_lpbk_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_digital_lpbk_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_playback_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_playback_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_playback_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_capture_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_capture_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_capture_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_playback_sink_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_playback_sink_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_playback_sink_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_capture_src_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_capture_src_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_capture_src_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_playback_switch_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_playback_switch_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_playback_switch_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_capture_switch_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_capture_switch_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_capture_switch_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_playback_power_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_playback_power_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_playback_power_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_capture_power_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_capture_power_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_capture_power_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_tdm_mode_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_tdm_mode_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_tdm_mode_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+static int u8500_direct_rendering_mode_ctrl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info
+ *uinfo);
+static int u8500_direct_rendering_mode_ctrl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value
+ *uinfo);
+static int u8500_direct_rendering_mode_ctrl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value
+ *uinfo);
+static int u8500_register_alsa_controls(struct snd_card *card,
+ u8500_acodec_chip_t * u8500_chip);
+
+static int u8500_pcm_rendering_mode_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_pcm_rendering_mode_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_pcm_rendering_mode_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+#if 0 /* DUMP REGISTER CONTROL */
+static int u8500_dump_register_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_dump_register_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_dump_register_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+#endif /* DUMP REGISTER CONTROL */
+
+static int configure_rate(struct snd_pcm_substream *,
+ t_u8500_acodec_config_need acodec_config_need);
+static void dma_eot_handler(void *data);
+/**
+* configure_rate
+* @substream - pointer to the playback/capture substream structure
+*
+* This functions configures audio codec in to stream frequency frequency
+*/
+
+static int configure_rate(struct snd_pcm_substream *substream,
+ t_u8500_acodec_config_need acodec_config_need)
+{
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ t_codec_sample_frequency sampling_frequency = 0;
+ t_ab8500_codec_direction direction = 0;
+ struct acodec_configuration acodec_config;
+ int stream_id = substream->pstr->stream;
+
+ FUNC_ENTER();
+ switch (chip->freq) {
+ case 48000:
+ sampling_frequency = CODEC_SAMPLING_FREQ_48KHZ;
+ break;
+ default:
+ printk("not supported frequnecy \n");
+ stm_error("not supported frequnecy \n");
+ return -EINVAL;
+ }
+
+ switch (stream_id) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ direction = AB8500_CODEC_DIRECTION_OUT;
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ direction = AB8500_CODEC_DIRECTION_IN;
+ break;
+ default:
+ stm_error(": wrong pcm stream\n");
+ return -EINVAL;
+ }
+
+ stm_dbg(DBG_ST.alsa, "enabling audiocodec audio mode\n");
+ acodec_config.direction = direction;
+ acodec_config.input_frequency = T_CODEC_SAMPLING_FREQ_48KHZ;
+ acodec_config.output_frequency = T_CODEC_SAMPLING_FREQ_48KHZ;
+ acodec_config.mspClockSel = CODEC_MSP_APB_CLOCK;
+ acodec_config.mspInClockFreq = CODEC_MSP_INPUT_FREQ_48MHZ;
+ acodec_config.channels = chip->channels;
+ acodec_config.user = 2;
+ acodec_config.acodec_config_need = acodec_config_need;
+ acodec_config.handler = dma_eot_handler;
+ acodec_config.tx_callback_data =
+ &chip->stream[ALSA_PCM_DEV][SNDRV_PCM_STREAM_PLAYBACK];
+ acodec_config.rx_callback_data =
+ &chip->stream[ALSA_PCM_DEV][SNDRV_PCM_STREAM_CAPTURE];
+ acodec_config.direct_rendering_mode = chip->direct_rendering_mode;
+ acodec_config.tdm8_ch_mode = chip->tdm8_ch_mode;
+ acodec_config.digital_loopback = DISABLE;
+ u8500_acodec_enable_audio_mode(&acodec_config);
+ FUNC_EXIT();
+
+ return 0;
+}
+
+/*
+****************************************************************************************
+* playback vol control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_playback_vol_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .subdevice = 0,
+ .name = "PCM Playback Volume",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_playback_vol_info,
+ .get = u8500_playback_vol_get,
+ .put = u8500_playback_vol_put
+};
+
+/**
+* u8500_playback_vol_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback volume info into user structure.
+*/
+
+static int u8500_playback_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 100;
+ uinfo->value.integer.step = 10;
+ return 0;
+}
+
+/**
+* u8500_playback_vol_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills the current volume setting to user structure.
+*/
+
+static int u8500_playback_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+
+ int *p_left_volume = NULL;
+ int *p_right_volume = NULL;
+
+ p_left_volume = (int *)&uinfo->value.integer.value[0];
+ p_right_volume = (int *)&uinfo->value.integer.value[1];
+
+ u8500_acodec_get_output_volume(chip->output_device, p_left_volume,
+ p_right_volume, USER_ALSA);
+ return 0;
+}
+
+/**
+* u8500_playback_vol_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure.
+*
+* This functions sets the playback audio codec volume .
+*/
+
+static int u8500_playback_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0, error = 0;
+
+ if (chip->output_lvolume != uinfo->value.integer.value[0]
+ || chip->output_rvolume != uinfo->value.integer.value[1]) {
+ chip->output_lvolume = uinfo->value.integer.value[0];
+ chip->output_rvolume = uinfo->value.integer.value[1];
+
+ if (chip->output_lvolume > 100)
+ chip->output_lvolume = 100;
+ else if (chip->output_lvolume < 0)
+ chip->output_lvolume = 0;
+
+ if (chip->output_rvolume > 100)
+ chip->output_rvolume = 100;
+ else if (chip->output_rvolume < 0)
+ chip->output_rvolume = 0;
+
+ error =
+ u8500_acodec_set_output_volume(chip->output_device,
+ chip->output_lvolume,
+ chip->output_rvolume,
+ USER_ALSA);
+
+ if (error) {
+ stm_error
+ (" : set volume for speaker/headphone failed\n");
+ return changed;
+ }
+ changed = 1;
+ }
+
+ return changed;
+}
+
+/*
+****************************************************************************************
+* capture vol control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_capture_vol_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .subdevice = 1,
+ .name = "PCM Capture Volume",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_capture_vol_info,
+ .get = u8500_capture_vol_get,
+ .put = u8500_capture_vol_put
+};
+
+/**
+* u8500_capture_vol_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills capture volume info into user structure.
+*/
+static int u8500_capture_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 100;
+ uinfo->value.integer.step = 10;
+ return 0;
+}
+
+/**
+* u8500_capture_vol_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current capture volume setting to user structure.
+*/
+
+static int u8500_capture_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+
+ int *p_left_volume = NULL;
+ int *p_right_volume = NULL;
+
+ p_left_volume = (int *)&uinfo->value.integer.value[0];
+ p_right_volume = (int *)&uinfo->value.integer.value[1];
+
+ u8500_acodec_get_input_volume(chip->input_device, p_left_volume,
+ p_right_volume, USER_ALSA);
+ return 0;
+}
+
+/**
+* u8500_capture_vol_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure.
+*
+* This functions sets the capture audio codec volume with values provided.
+*/
+
+static int u8500_capture_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0, error = 0;
+
+ if (chip->input_lvolume != uinfo->value.integer.value[0]
+ || chip->input_rvolume != uinfo->value.integer.value[1]) {
+ chip->input_lvolume = uinfo->value.integer.value[0];
+ chip->input_rvolume = uinfo->value.integer.value[1];
+
+ if (chip->input_lvolume > 100)
+ chip->input_lvolume = 100;
+ else if (chip->input_lvolume < 0)
+ chip->input_lvolume = 0;
+
+ if (chip->input_rvolume > 100)
+ chip->input_rvolume = 100;
+ else if (chip->input_rvolume < 0)
+ chip->input_rvolume = 0;
+
+ error = u8500_acodec_set_input_volume(chip->input_device,
+ chip->input_rvolume,
+ chip->input_lvolume,
+ USER_ALSA);
+ if (error) {
+ stm_error(" : set input volume failed\n");
+ return changed;
+ }
+ changed = 1;
+ }
+
+ return changed;
+}
+
+/*
+****************************************************************************************
+* playback sink control *
+****************************************************************************************
+*/
+
+static struct snd_kcontrol_new u8500_playback_sink_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .subdevice = 0,
+ .name = "PCM Playback Sink",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xffff,
+ .info = u8500_playback_sink_info,
+ .get = u8500_playback_sink_get,
+ .put = u8500_playback_sink_put
+};
+
+/**
+* u8500_playback_sink_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_playback_sink_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_OUTPUT_DEVICE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_OUTPUT_DEVICE)
+ uinfo->value.enumerated.item = NUMBER_OUTPUT_DEVICE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ codec_dest_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_playback_sink_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_playback_sink_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = chip->output_device;
+ return 0;
+}
+
+/**
+* u8500_playback_sink_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_playback_sink_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0, error;
+
+ if (chip->output_device != uinfo->value.enumerated.item[0]) {
+ chip->output_device = uinfo->value.enumerated.item[0];
+ error =
+ u8500_acodec_select_output(chip->output_device,
+ USER_ALSA, chip->tdm8_ch_mode);
+ if (error) {
+ stm_error(" : select output failed\n");
+ return changed;
+ }
+ changed = 1;
+ }
+ return changed;
+}
+
+/*
+****************************************************************************************
+* capture src control *
+****************************************************************************************
+*/
+
+static struct snd_kcontrol_new u8500_capture_src_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .subdevice = 1,
+ .name = "PCM Capture Source",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xffff,
+ .info = u8500_capture_src_ctrl_info,
+ .get = u8500_capture_src_ctrl_get,
+ .put = u8500_capture_src_ctrl_put
+};
+
+/**
+* u8500_capture_src_ctrl_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills capture device info into user structure.
+*/
+static int u8500_capture_src_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_INPUT_DEVICE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_INPUT_DEVICE)
+ uinfo->value.enumerated.item = NUMBER_INPUT_DEVICE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ codec_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_capture_src_ctrl_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current capture device selected.
+*/
+static int u8500_capture_src_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = chip->input_device;
+ return 0;
+}
+
+/**
+* u8500_capture_src_ctrl_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure,
+*
+* This functions sets the capture device.
+*/
+static int u8500_capture_src_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0, error;
+
+ if (chip->input_device != uinfo->value.enumerated.item[0]) {
+ chip->input_device = uinfo->value.enumerated.item[0];
+ error =
+ u8500_acodec_select_input(chip->input_device, USER_ALSA,
+ chip->tdm8_ch_mode);
+ if (error) {
+ stm_error(" : select input failed\n");
+ return changed;
+ }
+ changed = 1;
+ }
+ return changed;
+}
+
+/*
+***************************************************************************************
+* analog lpbk control *
+***************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_analog_lpbk_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .name = "Analog Loopback",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_analog_lpbk_info,
+ .get = u8500_analog_lpbk_get,
+ .put = u8500_analog_lpbk_put
+};
+
+/**
+* u8500_analog_lpbk_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_analog_lpbk_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_LOOPBACK_STATE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_LOOPBACK_STATE)
+ uinfo->value.enumerated.item = NUMBER_LOOPBACK_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ lpbk_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_analog_lpbk_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_analog_lpbk_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = chip->analog_lpbk;
+ return 0;
+}
+
+/**
+* u8500_analog_lpbk_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_analog_lpbk_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+
+ if (chip->analog_lpbk != uinfo->value.enumerated.item[0]) {
+ chip->analog_lpbk = uinfo->value.enumerated.item[0];
+
+ error =
+ u8500_acodec_toggle_analog_lpbk(chip->analog_lpbk,
+ USER_ALSA);
+
+ if (AB8500_CODEC_OK != error) {
+ stm_error
+ (" : select u8500_acodec_set_analog_lpbk_state failed\n");
+ return changed;
+ }
+ changed = 1;
+ }
+ return changed;
+}
+
+/*
+****************************************************************************************
+* digital lpbk control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_digital_lpbk_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .name = "Digital Loopback",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_digital_lpbk_info,
+ .get = u8500_digital_lpbk_get,
+ .put = u8500_digital_lpbk_put
+};
+
+/**
+* u8500_digital_lpbk_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_digital_lpbk_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_LOOPBACK_STATE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_LOOPBACK_STATE)
+ uinfo->value.enumerated.item = NUMBER_LOOPBACK_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ lpbk_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_digital_lpbk_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_digital_lpbk_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = chip->digital_lpbk;
+ return 0;
+}
+
+/**
+* u8500_analog_lpbk_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_digital_lpbk_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+
+ if (chip->digital_lpbk != uinfo->value.enumerated.item[0]) {
+ chip->digital_lpbk = uinfo->value.enumerated.item[0];
+
+ error = u8500_acodec_toggle_digital_lpbk(chip->digital_lpbk,
+ chip->output_device,
+ chip->input_device,
+ USER_ALSA,
+ chip->tdm8_ch_mode);
+
+ /*if((error = u8500_acodec_set_output_volume(chip->output_device,50,50,USER_ALSA)))
+ {
+ stm_error(" : set output volume failed\n");
+ return error;
+ }
+
+ if ((error = u8500_acodec_set_input_volume(chip->input_device,50,50,USER_ALSA)))
+ {
+ stm_error(" : set input volume failed\n");
+ return error;
+ } */
+
+ if (AB8500_CODEC_OK != error) {
+ stm_error
+ (" : select u8500_acodec_set_digital_lpbk_state failed\n");
+ return changed;
+ }
+ changed = 1;
+ }
+ return changed;
+}
+
+/*
+****************************************************************************************
+* playback switch control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_playback_switch_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .subdevice = 0,
+ .name = "PCM Playback Mute",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_playback_switch_ctrl_info,
+ .get = u8500_playback_switch_ctrl_get,
+ .put = u8500_playback_switch_ctrl_put
+};
+
+/**
+* u8500_playback_switch_ctrl_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_playback_switch_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_SWITCH_STATE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_SWITCH_STATE)
+ uinfo->value.enumerated.item = NUMBER_SWITCH_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ switch_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_playback_switch_ctrl_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_playback_switch_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = chip->playback_switch;
+ return 0;
+}
+
+/**
+* u8500_playback_switch_ctrl_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_playback_switch_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+
+ if (chip->playback_switch != uinfo->value.enumerated.item[0]) {
+ chip->playback_switch = uinfo->value.enumerated.item[0];
+
+ error =
+ u8500_acodec_toggle_playback_mute_control(chip->
+ output_device,
+ chip->
+ playback_switch,
+ USER_ALSA);
+
+ if (AB8500_CODEC_OK != error) {
+ stm_error
+ (" : select u8500_playback_switch_ctrl_put failed\n");
+ return changed;
+ }
+ changed = 1;
+ }
+ return changed;
+}
+
+/*
+****************************************************************************************
+* Capture switch control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_capture_switch_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .subdevice = 1,
+ .name = "PCM Capture Mute",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_capture_switch_ctrl_info,
+ .get = u8500_capture_switch_ctrl_get,
+ .put = u8500_capture_switch_ctrl_put
+};
+
+/**
+* u8500_capture_switch_ctrl_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_capture_switch_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_SWITCH_STATE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_SWITCH_STATE)
+ uinfo->value.enumerated.item = NUMBER_SWITCH_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ switch_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_capture_switch_ctrl_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_capture_switch_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = chip->capture_switch;
+ return 0;
+}
+
+/**
+* u8500_capture_switch_ctrl_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_capture_switch_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+
+ if (chip->capture_switch != uinfo->value.enumerated.item[0]) {
+ chip->capture_switch = uinfo->value.enumerated.item[0];
+
+ error =
+ u8500_acodec_toggle_capture_mute_control(chip->input_device,
+ chip->
+ capture_switch,
+ USER_ALSA);
+
+ if (AB8500_CODEC_OK != error) {
+ stm_error
+ (" : select u8500_capture_switch_ctrl_put failed\n");
+ return changed;
+ }
+ changed = 1;
+ }
+ return changed;
+}
+
+/*
+****************************************************************************************
+* playback power control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_playback_power_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .subdevice = 0,
+ .name = "PCM Playback Power",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_playback_power_ctrl_info,
+ .get = u8500_playback_power_ctrl_get,
+ .put = u8500_playback_power_ctrl_put
+};
+
+/**
+* u8500_playback_power_ctrl_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_playback_power_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_POWER_STATE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_POWER_STATE)
+ uinfo->value.enumerated.item = NUMBER_POWER_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ power_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_playback_power_ctrl_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_playback_power_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] =
+ u8500_acodec_get_dest_power_state(chip->output_device);
+ return 0;
+}
+
+/**
+* u8500_playback_power_ctrl_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_playback_power_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+ t_u8500_bool_state power_state;
+
+ power_state = u8500_acodec_get_dest_power_state(chip->output_device);
+
+ if (power_state != uinfo->value.enumerated.item[0]) {
+ power_state = uinfo->value.enumerated.item[0];
+
+ error =
+ u8500_acodec_set_dest_power_cntrl(chip->output_device,
+ power_state);
+
+ if (AB8500_CODEC_OK != error) {
+ stm_error
+ (" : select u8500_acodec_set_dest_power_cntrl failed\n");
+ return changed;
+ }
+
+ /* configure the volume settings for the acodec */
+ if ((error =
+ u8500_acodec_set_output_volume(chip->output_device,
+ chip->output_lvolume,
+ chip->output_rvolume,
+ USER_ALSA))) {
+ stm_error(" : set output volume failed\n");
+ return error;
+ }
+ changed = 1;
+ }
+ return changed;
+}
+
+/*
+****************************************************************************************
+* capture power control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_capture_power_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .subdevice = 0,
+ .name = "PCM Capture Power",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_capture_power_ctrl_info,
+ .get = u8500_capture_power_ctrl_get,
+ .put = u8500_capture_power_ctrl_put
+};
+
+/**
+* u8500_capture_power_ctrl_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_capture_power_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_POWER_STATE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_POWER_STATE)
+ uinfo->value.enumerated.item = NUMBER_POWER_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ power_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_capture_power_ctrl_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_capture_power_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] =
+ u8500_acodec_get_src_power_state(chip->input_device);
+ return 0;
+}
+
+/**
+* u8500_capture_power_ctrl_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_capture_power_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+ t_u8500_bool_state power_state;
+
+ power_state = u8500_acodec_get_src_power_state(chip->input_device);
+
+ if (power_state != uinfo->value.enumerated.item[0]) {
+ power_state = uinfo->value.enumerated.item[0];
+
+ error =
+ u8500_acodec_set_src_power_cntrl(chip->input_device,
+ power_state);
+
+ if (AB8500_CODEC_OK != error) {
+ stm_error
+ (" : select u8500_acodec_set_src_power_cntrl failed\n");
+ return changed;
+ }
+ changed = 1;
+ }
+ return changed;
+}
+
+/*
+****************************************************************************************
+* TDM 8 channel mode control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_tdm_mode_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .subdevice = 0,
+ .name = "TDM 8 Channel Mode",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_tdm_mode_ctrl_info,
+ .get = u8500_tdm_mode_ctrl_get,
+ .put = u8500_tdm_mode_ctrl_put
+};
+
+/**
+* u8500_tdm_mode_ctrl_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_tdm_mode_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_TDM_MODE_STATE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_TDM_MODE_STATE)
+ uinfo->value.enumerated.item = NUMBER_TDM_MODE_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ tdm_mode_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_tdm_mode_ctrl_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_tdm_mode_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = chip->tdm8_ch_mode;
+ return 0;
+}
+
+/**
+* u8500_tdm_mode_ctrl_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_tdm_mode_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+
+ chip->tdm8_ch_mode = uinfo->value.enumerated.item[0];
+
+ if (ENABLE == chip->tdm8_ch_mode)
+ printk("\n TDM 8 channel mode enabled\n");
+ else
+ printk("\n TDM 8 channel mode disabled\n");
+
+ changed = 1;
+
+ return changed;
+}
+
+/*
+****************************************************************************************
+* Direct Rendering Mode control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_direct_rendering_mode_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .name = "Direct Rendering Mode",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_direct_rendering_mode_ctrl_info,
+ .get = u8500_direct_rendering_mode_ctrl_get,
+ .put = u8500_direct_rendering_mode_ctrl_put
+};
+
+/**
+* u8500_direct_rendering_mode_ctrl_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_direct_rendering_mode_ctrl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info
+ *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_DIRECT_RENDERING_STATE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_DIRECT_RENDERING_STATE)
+ uinfo->value.enumerated.item =
+ NUMBER_DIRECT_RENDERING_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ direct_rendering_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_direct_rendering_mode_ctrl_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_direct_rendering_mode_ctrl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value
+ *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = chip->direct_rendering_mode;
+ return 0;
+}
+
+/**
+* u8500_direct_rendering_mode_ctrl_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_direct_rendering_mode_ctrl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value
+ *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+
+ chip->direct_rendering_mode = uinfo->value.enumerated.item[0];
+ changed = 1;
+
+ return changed;
+}
+
+/*
+****************************************************************************************
+* PCM Rendering Mode control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_pcm_rendering_mode_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .name = "PCM Rendering Mode",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_pcm_rendering_mode_ctrl_info,
+ .get = u8500_pcm_rendering_mode_ctrl_get,
+ .put = u8500_pcm_rendering_mode_ctrl_put
+};
+
+/**
+* u8500_pcm_rendering_mode_ctrl_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_pcm_rendering_mode_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_PCM_RENDERING_STATE;
+ uinfo->count = 3;
+ if (uinfo->value.enumerated.item >= NUMBER_PCM_RENDERING_STATE)
+ uinfo->value.enumerated.item = NUMBER_PCM_RENDERING_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ pcm_rendering_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_pcm_rendering_mode_ctrl_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_pcm_rendering_mode_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = chip->burst_fifo_mode;
+ uinfo->value.enumerated.item[1] = chip->fm_playback_mode;
+ uinfo->value.enumerated.item[2] = chip->fm_tx_mode;
+ return 0;
+}
+
+/**
+* u8500_pcm_rendering_mode_ctrl_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_pcm_rendering_mode_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+
+ if (RENDERING_PENDING == uinfo->value.enumerated.item[0]) {
+ return changed;
+ }
+ if (chip->burst_fifo_mode != uinfo->value.enumerated.item[0]) {
+ chip->burst_fifo_mode = uinfo->value.enumerated.item[0];
+ u8500_acodec_set_burst_mode_fifo(chip->burst_fifo_mode);
+ }
+
+ chip->fm_playback_mode = uinfo->value.enumerated.item[1];
+ chip->fm_tx_mode = uinfo->value.enumerated.item[2];
+
+ changed = 1;
+
+ return changed;
+}
+
+#if 0 /* DUMP REGISTER CONTROL */
+/*
+****************************************************************************************
+* dump registers control *
+****************************************************************************************
+*/
+
+struct snd_kcontrol_new u8500_dump_register_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 0,
+ .name = "",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_dump_register_ctrl_info,
+ .get = u8500_dump_register_ctrl_get,
+ .put = u8500_dump_register_ctrl_put
+};
+
+/**
+* u8500_dump_register_ctrl_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_dump_register_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_PCM_RENDERING_STATE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_PCM_RENDERING_STATE)
+ uinfo->value.enumerated.item = NUMBER_PCM_RENDERING_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ pcm_rendering_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_dump_register_ctrl_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_dump_register_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = chip->burst_fifo_mode;
+ uinfo->value.enumerated.item[1] = chip->fm_playback_mode;
+ uinfo->value.enumerated.item[2] = chip->fm_tx_mode;
+ return 0;
+}
+
+/**
+* u8500_dump_register_ctrl_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_dump_register_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+
+ if (RENDERING_PENDING == uinfo->value.enumerated.item[0]) {
+ return changed;
+ }
+ if (chip->burst_fifo_mode != uinfo->value.enumerated.item[0]) {
+ chip->burst_fifo_mode = uinfo->value.enumerated.item[0];
+ //u8500_acodec_set_burst_mode_fifo(chip->burst_fifo_mode);
+ }
+
+ chip->fm_playback_mode = uinfo->value.enumerated.item[1];
+ chip->fm_tx_mode = uinfo->value.enumerated.item[2];
+
+ changed = 1;
+
+ return changed;
+}
+
+#endif /* DUMP REGISTER CONTROL */
+
+/* Hardware description , this structure (struct snd_pcm_hardware )
+ * contains the definitions of the fundamental hardware configuration.
+ * This configuration will be applied on the runtime structure
+ */
+static struct snd_pcm_hardware snd_u8500_playback_hw = {
+ .info =
+ (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE),
+ .formats =
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_U16_BE |
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE |
+ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_U24_BE |
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE |
+ SNDRV_PCM_FMTBIT_S32_BE | SNDRV_PCM_FMTBIT_U32_BE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = MIN_RATE_PLAYBACK,
+ .rate_max = MAX_RATE_PLAYBACK,
+ .channels_min = 1,
+ .channels_max = 8,
+ .buffer_bytes_max = NMDK_BUFFER_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = NMDK_BUFFER_SIZE / PAGE_SIZE,
+ .periods_max = NMDK_BUFFER_SIZE / 128
+};
+
+static struct snd_pcm_hardware snd_u8500_capture_hw = {
+ .info =
+ (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE),
+ .formats =
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_U16_BE |
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE |
+ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_U24_BE |
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE |
+ SNDRV_PCM_FMTBIT_S32_BE | SNDRV_PCM_FMTBIT_U32_BE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = MIN_RATE_CAPTURE,
+ .rate_max = MAX_RATE_CAPTURE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .buffer_bytes_max = NMDK_BUFFER_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = NMDK_BUFFER_SIZE / PAGE_SIZE,
+ .periods_max = NMDK_BUFFER_SIZE / 128
+};
+
+static struct snd_pcm_hw_constraint_list constraints_rate = {
+ .count = sizeof(u8500_acodec_rates) / sizeof(u8500_acodec_rates[0]),
+ .list = u8500_acodec_rates,
+ .mask = 0,
+};
+
+/**
+ * snd_u8500_alsa_pcm_close
+ * @substream - pointer to the playback/capture substream structure
+ *
+ * This routine is used by alsa framework to close a pcm stream .
+ * Here a dma pipe is disabled and freed.
+ */
+static int snd_u8500_alsa_pcm_close(struct snd_pcm_substream *substream)
+{
+ int stream_id, error = 0;
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ audio_stream_t *ptr_audio_stream = NULL;
+
+ stream_id = substream->pstr->stream;
+ ptr_audio_stream = &chip->stream[ALSA_PCM_DEV][stream_id];
+
+ if (ENABLE == chip->direct_rendering_mode) {
+ ptr_audio_stream->substream = NULL;
+ return 0;
+ } else {
+ stm_close_alsa(chip, ALSA_PCM_DEV, stream_id);
+
+ /* reset the different variables to default */
+
+ ptr_audio_stream->active = 0;
+ ptr_audio_stream->period = 0;
+ ptr_audio_stream->periods = 0;
+ ptr_audio_stream->old_offset = 0;
+ ptr_audio_stream->substream = NULL;
+ if (!(--active_user)) {
+ /* Disable the MSP1 */
+ error = u8500_acodec_unsetuser(USER_ALSA);
+ u8500_acodec_close(I2S_CLIENT_MSP1, ACODEC_DISABLE_ALL);
+ } else {
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
+ u8500_acodec_close(I2S_CLIENT_MSP1,
+ ACODEC_DISABLE_TRANSMIT);
+ else if (stream_id == SNDRV_PCM_STREAM_CAPTURE)
+ u8500_acodec_close(I2S_CLIENT_MSP1,
+ ACODEC_DISABLE_RECEIVE);
+ }
+
+ stm_hw_free(substream);
+
+ return error;
+ }
+}
+
+void my_write(u32 address, u8 data)
+{
+ ab8500_write(AB8500_AUDIO, address, data);
+}
+
+void dsp_configure_audio_codec(void)
+{
+ //4500 config for both record DMIC1&2 and playback HS stereo
+ //data width is 16 bits
+
+ my_write(0x200, 0x02); // Start-up audio unreset
+ my_write(0x20B, 0x10); // Start-up audio clk audio enable
+ my_write(0x383, 0x06); // Start-up audio Vaudio supply
+
+ my_write(0xD00, 0x88); // General power up=0x88
+ my_write(0xD01, 0x00); // Software Reset=0x0
+ my_write(0xD02, 0xC0); // Digital AD Channels Enable=0xC0
+ my_write(0xD03, 0xC0); // Digital DA Channels Enable=0xC0
+ my_write(0xD04, 0x00); // Low Power and Conf=0x0
+ my_write(0xD05, 0x0F); // Line in Conf=0xF
+ my_write(0xD06, 0xC0); // Analog Inputs Enable=0xC0
+ my_write(0xD07, 0x30); // ADC Enable=0x30
+ my_write(0xD08, 0x30); // Analog Output Enable=0x30
+ my_write(0xD09, 0x30); // Digital Output Enable=0x30
+ my_write(0xD0A, 0x4F); // Mute Enable=0x4F
+ my_write(0xD0B, 0x7F); // Short Circuit Disable=0x7F
+ my_write(0xD0C, 0x80); // Power-up for Headset=0x80
+ my_write(0xD0D, 0x00); // Envelope Threshold=0x0
+ my_write(0xD0E, 0x00); // Envelope Decay Time=0x0
+ my_write(0xD0F, 0xF0); // Class-D Configuration=0xF0
+ my_write(0xD10, 0x32); // PWM VIBNL Configuration=0x32
+ my_write(0xD11, 0x32); // PWM VIBPL Configuration=0x32
+ my_write(0xD12, 0x32); // PWM VIBNR Configuration=0x32
+ my_write(0xD13, 0x32); // PWM VIBPR Configuration=0x32
+ my_write(0xD14, 0x00); // Microphone 1 Gain=0x0
+ my_write(0xD15, 0x00); // Microphone 2 Gain=0x0
+ my_write(0xD16, 0x00); // Left line-in and HS Analog Gain=0x0
+ my_write(0xD17, 0x00); // Right line-in and HS Analog Gain=0x0
+ my_write(0xD18, 0x1F); // Line-in to HSL Gain=0x1F
+ my_write(0xD19, 0x1F); // Line-in to HSR Gain=0x1F
+ my_write(0xD1A, 0xF0); // AD Channel Filters Configuration=0xF0
+ my_write(0xD1B, 0x85); // TDM Configuration 1=0x85
+ my_write(0xD1C, 0x94); // TDM Configuration 2=0x94
+ my_write(0xD1D, 0x02); // TDM loopback control=0x2
+ my_write(0xD1E, 0x00); // TDM format=0x0
+ my_write(0xD1F, 0x10); // AD Data allocation in Slot 0 to 1=0x10
+ my_write(0xD20, 0xCC); // AD Data allocation in Slot 2 to 3=0xCC
+ my_write(0xD21, 0xCC); // AD Data allocation in Slots 4 to 5=0xCC
+ my_write(0xD22, 0xCC); // AD Data allocation in Slots 6 to 7=0xCC
+ my_write(0xD23, 0xCC); // AD Data allocation in Slots 8 to 9=0xCC
+ my_write(0xD24, 0xCC); // AD Data allocation in Slots 10 to 11=0xCC
+ my_write(0xD25, 0xCC); // AD Data allocation in Slots 12 to 13=0xCC
+ my_write(0xD26, 0xCC); // AD Data allocation in Slots 14 to 15=0xCC
+ my_write(0xD27, 0xCC); // AD Data allocation in Slots 16 to 17=0xCC
+ my_write(0xD28, 0xCC); // AD Data allocation in Slots 18 to 19=0xCC
+ my_write(0xD29, 0xCC); // AD Data allocation in Slots 20 to 21=0xCC
+ my_write(0xD2A, 0xCC); // AD Data allocation in Slots 22 to 23=0xCC
+ my_write(0xD2B, 0xCC); // AD Data allocation in Slots 24 to 25=0xCC
+ my_write(0xD2C, 0xCC); // AD Data allocation in Slots 26 to 27=0xCC
+ my_write(0xD2D, 0xCC); // AD Data allocation in Slots 28 to 29=0xCC
+ my_write(0xD2E, 0xCC); // AD Data allocation in Slots 30 to 31=0xCC
+ my_write(0xD2F, 0x00); // AD slot 0/7 tristate=0x0
+ my_write(0xD30, 0x00); // AD slot 8/15 tristate=0x0
+ my_write(0xD31, 0x00); // AD slot 16/23 tristate=0x0
+ my_write(0xD32, 0x00); // AD slot 24/31 tristate=0x0
+ my_write(0xD33, 0x08); // Slots selection for DA path 1=0x8
+ my_write(0xD34, 0x09); // Slots selection for DA path 2=0x9
+ my_write(0xD35, 0x00); // Slots selection for DA path 3=0x0
+ my_write(0xD36, 0x00); // Slots selection for DA path 4=0x0
+ my_write(0xD37, 0x00); // Slots selection for DA path 5=0x0
+ my_write(0xD38, 0x00); // Slots selection for DA path 6=0x0
+ my_write(0xD39, 0x00); // IRQ mask lsb=0x0
+ my_write(0xD3A, 0x00); // IRQ status lsb=0x0
+ my_write(0xD3B, 0x00); // IRQ mask msb=0x0
+ my_write(0xD3C, 0x00); // IRQ status msb=0x0
+ my_write(0xD3D, 0x00); // Fade speed=0x0
+ my_write(0xD3E, 0x00); // DMIC decimator filter=0x0
+ my_write(0xD3F, 0xF0); // muxing lsb=0xF0
+ my_write(0xD40, 0x00); // muxing msb=0x0
+ my_write(0xD41, 0x1F); // AD1 Digital Gain=0x1F
+ my_write(0xD42, 0x1F); // AD2 Digital Gain=0x1F
+ my_write(0xD43, 0x1F); // AD3 Digital Gain=0x1F
+ my_write(0xD44, 0x1F); // AD4 Digital Gain=0x1F
+ my_write(0xD45, 0x1F); // AD5 Digital Gain=0x1F
+ my_write(0xD46, 0x1F); // AD6 Digital Gain=0x1F
+ my_write(0xD47, 0x00); // DA1 digital Gain=0x00
+ my_write(0xD48, 0x00); // DA2 digital Gain=0x00
+ my_write(0xD49, 0x3F); // DA3 digital Gain=0x3F
+ my_write(0xD4A, 0x3F); // DA4 digital Gain=0x3F
+ my_write(0xD4B, 0x3F); // DA5 digital Gain=0x3F
+ my_write(0xD4C, 0x3F); // DA6 digital Gain=0x3F
+ my_write(0xD4D, 0x3F); // AD1 loopback to HFL digital gain=0x3F
+ my_write(0xD4E, 0x3F); // AD2 loopback to HFR digital gain=0x3F
+ my_write(0xD4F, 0x08); // HSL and EAR digital gain=0x8
+ my_write(0xD50, 0x08); // HSR digital gain=0x8
+ my_write(0xD51, 0x1F); // Side tone FIR1 gain=0x1F
+ my_write(0xD52, 0x1F); // Side tone FIR2 gain=0x1F
+ my_write(0xD53, 0x00); // ANC filter control=0x0
+ my_write(0xD54, 0x00); // ANC Warped Delay Line Shift=0x0
+ my_write(0xD55, 0x00); // ANC FIR output Shift=0x0
+ my_write(0xD56, 0x00); // ANC IIR output Shift=0x0
+ my_write(0xD57, 0x00); // ANC FIR coefficients msb=0x0
+ my_write(0xD58, 0x00); // ANC FIR coefficients lsb=0x0
+ my_write(0xD59, 0x00); // ANC IIR coefficients msb=0x0
+ my_write(0xD5A, 0x00); // ANC IIR coefficients lsb=0x0
+ my_write(0xD5B, 0x00); // ANC Warp delay msb=0x0
+ my_write(0xD5C, 0x00); // ANC Warp delay lsb=0x0
+ my_write(0xD5D, 0x00); // ANC FIR peak register MSB=0x0
+ my_write(0xD5E, 0x00); // ANC FIR peak register LSB=0x0
+ my_write(0xD5F, 0x00); // ANC IIR peak register. MSB part=0x0
+ my_write(0xD60, 0x00); // ANC IIR peak register. LSB part=0x0
+ my_write(0xD61, 0x00); // Side tone FIR address=0x0
+ my_write(0xD62, 0x00); // Side tone FIR coefficient MSB=0x0
+ my_write(0xD63, 0x00); // Side tone FIR coefficient LSB=0x0
+ my_write(0xD64, 0x00); // Filters control=0x0
+ my_write(0xD65, 0x00); // Class D EMI Control=0x0
+ my_write(0xD66, 0x00); // Class D control path=0x0
+ my_write(0xD67, 0x00); // Class D control gain=0x0
+ my_write(0xD68, 0x00); // Burst FIFO int control=0x0
+ my_write(0xD69, 0x00); // Burst FIFO length=0x0
+ my_write(0xD6A, 0x00); // Burst FIFO control=0x0
+ my_write(0xD6B, 0x00); // Burst FIFO switch frame=0x0
+ my_write(0xD6C, 0x00); // Burst FIFO wake up delay=0x0
+ my_write(0xD6D, 0x00); // Burst FIFO samples number=0x0
+ my_write(0xD70, 0x00); // CR112=0x0
+ my_write(0xD71, 0x04); // CR113=0x4
+ my_write(0xD72, 0x00); // CR114=0x0
+ my_write(0xD73, 0x00); // CR115=0x0
+ my_write(0xD74, 0x00); // CR116=0x0
+ my_write(0xD75, 0x00); // CR117=0x0
+ my_write(0xD76, 0x00); // CR118=0x0
+ my_write(0xD77, 0x00); // CR119=0x0
+ my_write(0xD78, 0x00); // CR120=0x0
+ my_write(0xD79, 0x00); // CR121=0x0
+ my_write(0xD7A, 0x00); // CR122=0x0
+ my_write(0xD7B, 0x00); // CR123=0x0
+}
+
+static int configure_direct_rendering(struct snd_pcm_substream *substream)
+{
+ int error = 0, stream_id;
+ int status = 0;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ audio_stream_t *ptr_audio_stream = NULL;
+
+ stream_id = substream->pstr->stream;
+
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) {
+ runtime->hw = snd_u8500_playback_hw;
+ } else {
+ runtime->hw = snd_u8500_capture_hw;
+ }
+
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x04))); //MSP_GCR
+
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x08))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x0C))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x10))); //MSP
+
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x30))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x34))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x38))); //MSP
+
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x40))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x44))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x48))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x4C))); //MSP
+
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x60))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x64))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x68))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x6C))); //MSP
+
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x18))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x20))); //MSP
+ writel(0x0, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x2C))); //MSP
+
+ dsp_configure_audio_codec();
+
+#if DRIVER_DEBUG > 0
+ {
+ dump_msp_registers();
+ dump_acodec_registers();
+ }
+#endif
+
+ ptr_audio_stream = &chip->stream[ALSA_PCM_DEV][stream_id];
+
+ ptr_audio_stream->substream = substream;
+
+ FUNC_EXIT();
+ return 0;
+}
+
+/**
+ * snd_u8500_alsa_pcm_open
+ * @substream - pointer to the playback/capture substream structure
+ *
+ * This routine is used by alsa framework to open a pcm stream .
+ * Here a dma pipe is requested and device is configured(default).
+ */
+static int snd_u8500_alsa_pcm_open(struct snd_pcm_substream *substream)
+{
+ int error = 0, stream_id, status = 0;
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ audio_stream_t *ptr_audio_stream = NULL;
+
+ FUNC_ENTER();
+
+ if (ENABLE == chip->direct_rendering_mode) {
+ configure_direct_rendering(substream);
+ return 0;
+ } else {
+ stream_id = substream->pstr->stream;
+ status = u8500_acodec_open(I2S_CLIENT_MSP1, stream_id);
+
+ if (status) {
+ printk("failed in getting open\n");
+ return -1;
+ }
+
+ if (!active_user)
+ error = u8500_acodec_setuser(USER_ALSA);
+ if (error)
+ return error;
+ else
+ active_user++;
+
+ error =
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_rate);
+ if (error < 0) {
+ stm_error
+ (": error initializing hw sample rate constraint\n");
+ return error;
+ }
+
+ /* configure the default sampling rate for the acodec */
+ second_config = 0;
+
+ if ((error = configure_rate(substream, ACODEC_CONFIG_REQUIRED)))
+ return error;
+
+ /* Set the hardware configuration */
+ stream_id = substream->pstr->stream;
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) {
+ runtime->hw = snd_u8500_playback_hw;
+ /* configure the output sink for the acodec */
+ if ((error =
+ u8500_acodec_select_output(chip->output_device,
+ USER_ALSA,
+ chip->tdm8_ch_mode))) {
+ stm_error(" : select output failed\n");
+ return error;
+ }
+
+ /* configure the volume settings for the acodec */
+ if ((error =
+ u8500_acodec_set_output_volume(chip->output_device,
+ chip->
+ output_lvolume,
+ chip->
+ output_rvolume,
+ USER_ALSA))) {
+ stm_error(" : set output volume failed\n");
+ return error;
+ }
+ } else {
+ runtime->hw = snd_u8500_capture_hw;
+ /* configure the input source for the acodec */
+ if ((error =
+ u8500_acodec_select_input(chip->input_device,
+ USER_ALSA,
+ chip->tdm8_ch_mode))) {
+ stm_error(" : select input failed\n");
+ return error;
+ }
+ /*u8500_acodec_set_src_power_cntrl(AB8500_CODEC_SRC_D_MICROPHONE_1,ENABLE);
+ u8500_acodec_set_src_power_cntrl(AB8500_CODEC_SRC_D_MICROPHONE_2,ENABLE);
+
+ u8500_acodec_set_input_volume(AB8500_CODEC_SRC_D_MICROPHONE_1,
+ chip->input_lvolume,
+ chip->input_rvolume,
+ USER_ALSA);
+
+ u8500_acodec_set_input_volume(AB8500_CODEC_SRC_D_MICROPHONE_2,
+ chip->input_lvolume,
+ chip->input_rvolume,
+ USER_ALSA);
+ */
+
+ if ((error =
+ u8500_acodec_set_input_volume(chip->input_device,
+ chip->input_lvolume,
+ chip->input_rvolume,
+ USER_ALSA))) {
+ stm_error(" : set input volume failed\n");
+ return error;
+ }
+ }
+
+ u8500_acodec_set_burst_mode_fifo(chip->burst_fifo_mode);
+
+#if DRIVER_DEBUG > 0
+ {
+ dump_msp_registers();
+ dump_acodec_registers();
+ }
+#endif
+
+ ptr_audio_stream = &chip->stream[ALSA_PCM_DEV][stream_id];
+
+ ptr_audio_stream->substream = substream;
+
+ if (DISABLE == chip->direct_rendering_mode) {
+ stm_config_hw(chip, substream, ALSA_PCM_DEV, stream_id);
+ }
+ sema_init(&(ptr_audio_stream->alsa_sem), 1);
+ init_completion(&(ptr_audio_stream->alsa_com));
+ ptr_audio_stream->state = ALSA_STATE_UNPAUSE;
+
+ FUNC_EXIT();
+ return 0;
+ }
+}
+
+/**
+ * snd_u8500_alsa_pcm_hw_params
+ * @substream - pointer to the playback/capture substream structure
+ * @hw_params - specifies the hw parameters like format/no of channels etc
+ *
+ * This routine is used by alsa framework to allocate a dma buffer
+ * used to transfer the data from user space to kernel space
+ *
+ */
+static int snd_u8500_alsa_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+/**
+ * snd_u8500_alsa_pcm_hw_free
+ * @substream - pointer to the playback/capture substream structure
+ *
+ * This routine is used by alsa framework to deallocate a dma buffer
+ * allocated berfore by snd_u8500_alsa_pcm_hw_params
+ */
+static int snd_u8500_alsa_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ stm_hw_free(substream);
+ return 0;
+}
+
+/**
+ * snd_u8500_alsa_pcm_prepare
+ * @substream - pointer to the playback/capture substream structure
+ *
+ * This callback is called whene the pcm is "prepared" Here is possible
+ * to set the format type ,sample rate ,etc.The callback is called as
+ * well everytime a recovery after an underrun happens.
+ */
+
+static int snd_u8500_alsa_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int error, stream_id;
+
+ FUNC_ENTER();
+
+ if (chip->freq != runtime->rate || chip->channels != runtime->channels) {
+ stm_dbg(DBG_ST.alsa, " freq not same, %d %d\n", chip->freq,
+ runtime->rate);
+ stm_dbg(DBG_ST.alsa, " channels not same, %d %d\n",
+ chip->channels, runtime->channels);
+ if (chip->channels != runtime->channels) {
+ chip->channels = runtime->channels;
+ if ((error =
+ stm_config_hw(chip, substream, ALSA_PCM_DEV,
+ -1))) {
+ stm_dbg(DBG_ST.alsa,
+ "In func %s, stm_config_hw fails",
+ __FUNCTION__);
+ return error;
+ }
+ }
+ chip->freq = runtime->rate;
+ second_config = 1;
+ stream_id = substream->pstr->stream;
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
+ u8500_acodec_close(I2S_CLIENT_MSP1,
+ ACODEC_DISABLE_TRANSMIT);
+ else if (stream_id == SNDRV_PCM_STREAM_CAPTURE)
+ u8500_acodec_close(I2S_CLIENT_MSP1,
+ ACODEC_DISABLE_RECEIVE);
+
+ error = u8500_acodec_open(I2S_CLIENT_MSP1, stream_id);
+ if (error) {
+ printk("failed in getting open\n");
+ return -1;
+ }
+ if ((error =
+ configure_rate(substream, ACODEC_CONFIG_NOT_REQUIRED))) {
+ stm_dbg(DBG_ST.alsa, "In func %s, configure_rate fails",
+ __FUNCTION__);
+ return error;
+ }
+ }
+
+ FUNC_EXIT();
+ return 0;
+}
+
+/**
+ * snd_u8500_alsa_pcm_trigger
+ * @substream - pointer to the playback/capture substream structure
+ * @cmd - specifies the command : start/stop/pause/resume
+ *
+ * This callback is called whene the pcm is started ,stopped or paused
+ * The action is specified in the second argument, SND_PCM_TRIGGER_XXX in
+ * <sound/pcm.h>.
+ * This callback is atomic and the interrupts are disabled , so you can't
+ * call other functions that need interrupts without possible risks
+ */
+static int snd_u8500_alsa_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int stream_id = substream->pstr->stream;
+ audio_stream_t *stream = NULL;
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ int error = 0;
+
+ FUNC_ENTER();
+
+ stream = &chip->stream[ALSA_PCM_DEV][stream_id];
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* Start the pcm engine */
+ stm_dbg(DBG_ST.alsa, " TRIGGER START\n");
+ if (stream->active == 0) {
+ stream->active = 1;
+ stm_trigger_alsa(stream);
+ break;
+ }
+ stm_error(": H/w is busy\n");
+ return -EINVAL;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ stm_dbg(DBG_ST.alsa, " SNDRV_PCM_TRIGGER_PAUSE_PUSH\n");
+ if (stream->active == 1) {
+ stm_pause_alsa(stream);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ stm_dbg(DBG_ST.alsa, " SNDRV_PCM_TRIGGER_PAUSE_RELEASE\n");
+ if (stream->active == 1)
+ stm_unpause_alsa(stream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* Stop the pcm engine */
+ stm_dbg(DBG_ST.alsa, " TRIGGER STOP\n");
+ if (stream->active == 1)
+ stm_stop_alsa(stream);
+ break;
+ default:
+ stm_error(": invalid command in pcm trigger\n");
+ return -EINVAL;
+ }
+
+ FUNC_EXIT();
+ return error;
+}
+
+/**
+ * snd_u8500_alsa_pcm_pointer
+ * @substream - pointer to the playback/capture substream structure
+ *
+ * This callback is called whene the pcm middle layer inquires the current
+ * hardware position on the buffer .The position is returned in frames
+ * ranged from 0 to buffer_size -1
+ */
+static snd_pcm_uframes_t snd_u8500_alsa_pcm_pointer(struct snd_pcm_substream
+ *substream)
+{
+ unsigned int offset;
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ audio_stream_t *stream =
+ &chip->stream[ALSA_PCM_DEV][substream->pstr->stream];
+ struct snd_pcm_runtime *runtime = stream->substream->runtime;
+
+ offset = bytes_to_frames(runtime, stream->old_offset);
+ if (offset < 0 || stream->old_offset < 0)
+ stm_dbg(DBG_ST.alsa, " Offset=%i %i\n", offset,
+ stream->old_offset);
+
+ return offset;
+}
+
+static struct snd_pcm_ops snd_u8500_alsa_playback_ops = {
+ .open = snd_u8500_alsa_pcm_open,
+ .close = snd_u8500_alsa_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_u8500_alsa_pcm_hw_params,
+ .hw_free = snd_u8500_alsa_pcm_hw_free,
+ .prepare = snd_u8500_alsa_pcm_prepare,
+ .trigger = snd_u8500_alsa_pcm_trigger,
+ .pointer = snd_u8500_alsa_pcm_pointer,
+};
+
+static struct snd_pcm_ops snd_u8500_alsa_capture_ops = {
+ .open = snd_u8500_alsa_pcm_open,
+ .close = snd_u8500_alsa_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_u8500_alsa_pcm_hw_params,
+ .hw_free = snd_u8500_alsa_pcm_hw_free,
+ .prepare = snd_u8500_alsa_pcm_prepare,
+ .trigger = snd_u8500_alsa_pcm_trigger,
+ .pointer = snd_u8500_alsa_pcm_pointer,
+};
+
+#ifdef CONFIG_U8500_ACODEC_POLL
+
+/**
+* u8500_alsa_pio_start
+* @stream - pointer to the playback/capture audio_stream_t structure
+*
+* This function sends/receive one chunck of stream data to/from MSP
+*/
+static void u8500_alsa_pio_start(audio_stream_t * stream)
+{
+ unsigned int offset, dma_size, stream_id;
+ int ret_val;
+ struct snd_pcm_substream *substream = stream->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ stream_id = substream->pstr->stream;
+
+ FUNC_ENTER();
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * stream->period;
+ stream->old_offset = offset;
+
+ stm_dbg(DBG_ST.alsa, " Transfer started\n");
+ stm_dbg(DBG_ST.alsa, " address = %x size=%d\n",
+ (runtime->dma_addr + offset), dma_size);
+
+ /* Send our stuff */
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
+#ifdef CONFIG_U8500_ACODEC_DMA
+ u8500_acodec_send_data(I2S_CLIENT_MSP1,
+ (void *)(runtime->dma_addr + offset),
+ dma_size, 1);
+#else
+ u8500_acodec_send_data(I2S_CLIENT_MSP1,
+ (void *)(runtime->dma_area + offset),
+ dma_size, 0);
+#endif
+ else
+#ifdef CONFIG_U8500_ACODEC_DMA
+ u8500_acodec_receive_data(I2S_CLIENT_MSP1,
+ (void *)(runtime->dma_addr + offset),
+ dma_size, 1);
+#else
+ u8500_acodec_receive_data(I2S_CLIENT_MSP1,
+ (void *)(runtime->dma_area + offset),
+ dma_size, 0);
+#endif
+
+ stream->period++;
+ stream->period %= runtime->periods;
+ stream->periods++;
+
+ FUNC_EXIT();
+
+}
+
+/**
+* acodec_feeding_thread
+* @data - void pointer to the playback/capture audio_stream_t structure
+*
+* This thread sends/receive data to MSP while stream is active
+*/
+static int acodec_feeding_thread(void *data)
+{
+ audio_stream_t *stream = (audio_stream_t *) data;
+
+ FUNC_ENTER();
+ daemonize("acodec_feeding_thread");
+ allow_signal(SIGKILL);
+ down(&stream->alsa_sem);
+
+ while ((!signal_pending(current)) && (stream->active)) {
+ if (stream->state == ALSA_STATE_PAUSE)
+ wait_for_completion(&(stream->alsa_com));
+
+ u8500_alsa_pio_start(stream);
+ if (stream->substream)
+ snd_pcm_period_elapsed(stream->substream);
+ }
+
+ up(&stream->alsa_sem);
+
+ FUNC_EXIT();
+ return 0;
+}
+
+/**
+* acodec_feeding_thread
+* @stream - pointer to the playback/capture audio_stream_t structure
+*
+* This function creates a kernel thread .
+*/
+
+int spawn_acodec_feeding_thread(audio_stream_t * stream)
+{
+ pid_t pid;
+
+ FUNC_ENTER();
+
+ pid =
+ kernel_thread(acodec_feeding_thread, stream,
+ CLONE_FS | CLONE_SIGHAND);
+
+ FUNC_EXIT();
+ return 0;
+}
+#endif
+
+/**
+ * dma_eot_handler
+ * @data - pointer to structure set in the dma callback handler
+ *
+ * This is the PCM tasklet handler linked to a pipe, its role is to tell
+ * the PCM middler layer whene the buffer position goes across the prescribed
+ * period size.To inform of this the snd_pcm_period_elapsed is called.
+ *
+ * this callback will be called in case of DMA_EVENT_TC only
+ */
+static void dma_eot_handler(void *data)
+{
+ audio_stream_t *stream = data;
+
+ /* snd_pcm_period_elapsed() is _not_ to be protected
+ */
+ stm_dbg(DBG_ST.alsa,
+ "One transfer complete.. going to start the next one\n");
+
+ if (stream->substream)
+ snd_pcm_period_elapsed(stream->substream);
+ if (stream->state == ALSA_STATE_PAUSE)
+ return;
+ if (stream->active == 1) {
+ u8500_alsa_dma_start(stream);
+ }
+}
+
+/**
+ * u8500_alsa_dma_start - used to transmit or recive a dma chunk
+ * @stream - specifies the playback/record stream structure
+ */
+void u8500_alsa_dma_start(audio_stream_t * stream)
+{
+ unsigned int offset, dma_size, stream_id;
+
+ struct snd_pcm_substream *substream = stream->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u8500_acodec_chip_t *u8500_chip = NULL;
+ stream_id = substream->pstr->stream;
+ u8500_chip = snd_pcm_substream_chip(substream);
+
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * stream->period;
+ stream->old_offset = offset;
+
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
+#ifdef CONFIG_U8500_ACODEC_DMA
+ u8500_acodec_send_data(I2S_CLIENT_MSP1,
+ (void *)(runtime->dma_addr + offset),
+ dma_size, 1);
+#else
+ u8500_acodec_send_data(I2S_CLIENT_MSP1,
+ (void *)(runtime->dma_area + offset),
+ dma_size, 0);
+#endif
+ else
+#ifdef CONFIG_U8500_ACODEC_DMA
+ u8500_acodec_receive_data(I2S_CLIENT_MSP1,
+ (void *)(runtime->dma_addr + offset),
+ dma_size, 1);
+#else
+ u8500_acodec_receive_data(I2S_CLIENT_MSP1,
+ (void *)(runtime->dma_area + offset),
+ dma_size, 0);
+#endif
+
+ stm_dbg(DBG_ST.alsa, " DMA Transfer started\n");
+ stm_dbg(DBG_ST.alsa, " address = %x size=%d\n",
+ (runtime->dma_addr + offset), dma_size);
+
+ stream->period++;
+ stream->period %= runtime->periods;
+ stream->periods++;
+}
+
+/**
+* u8500_audio_init
+* @chip - pointer to u8500_acodec_chip_t structure.
+*
+* This function intialises the u8500 chip structure with default values
+*/
+static void u8500_audio_init(u8500_acodec_chip_t * chip)
+{
+ audio_stream_t *ptr_audio_stream = NULL;
+
+ ptr_audio_stream =
+ &chip->stream[ALSA_PCM_DEV][SNDRV_PCM_STREAM_PLAYBACK];
+ /* Setup DMA stuff */
+ strlcpy(ptr_audio_stream->id, "u8500 playback",
+ sizeof(ptr_audio_stream->id));
+ ptr_audio_stream->stream_id = SNDRV_PCM_STREAM_PLAYBACK;
+
+ /* default initialization for playback */
+ ptr_audio_stream->active = 0;
+ ptr_audio_stream->period = 0;
+ ptr_audio_stream->periods = 0;
+ ptr_audio_stream->old_offset = 0;
+
+ ptr_audio_stream =
+ &chip->stream[ALSA_PCM_DEV][SNDRV_PCM_STREAM_CAPTURE];
+ strlcpy(ptr_audio_stream->id, "u8500 capture",
+ sizeof(ptr_audio_stream->id));
+ ptr_audio_stream->stream_id = SNDRV_PCM_STREAM_CAPTURE;
+
+ /* default initialization for capture */
+ ptr_audio_stream->active = 0;
+ ptr_audio_stream->period = 0;
+ ptr_audio_stream->periods = 0;
+ ptr_audio_stream->old_offset = 0;
+
+ chip->freq = DEFAULT_SAMPLE_RATE;
+ chip->channels = 1;
+ chip->input_lvolume = DEFAULT_GAIN;
+ chip->input_rvolume = DEFAULT_GAIN;
+ chip->output_lvolume = DEFAULT_VOLUME;
+ chip->output_rvolume = DEFAULT_VOLUME;
+ chip->output_device = DEFAULT_OUTPUT_DEVICE;
+ chip->input_device = DEFAULT_INPUT_DEVICE;
+ chip->analog_lpbk = DEFAULT_LOOPBACK_STATE;
+ chip->digital_lpbk = DEFAULT_LOOPBACK_STATE;
+ chip->playback_switch = DEFAULT_SWITCH_STATE;
+ chip->capture_switch = DEFAULT_SWITCH_STATE;
+ chip->tdm8_ch_mode = DEFAULT_TDM8_CH_MODE_STATE;
+ chip->direct_rendering_mode = DEFAULT_DIRECT_RENDERING_STATE;
+ chip->burst_fifo_mode = DEFAULT_BURST_FIFO_STATE;
+ chip->fm_playback_mode = DEFAULT_FM_PLAYBACK_STATE;
+ chip->fm_tx_mode = DEFAULT_FM_TX_STATE;
+
+ //HDMI Default params set
+ chip->hdmi_params.sampling_freq = 48000;
+ chip->hdmi_params.channel_count = 2;
+}
+
+/**
+ * snd_card_u8500_alsa_pcm_new - constructor for a new pcm cmponent
+ * @chip - pointer to chip specific data
+ * @device - specifies the card number
+ */
+static int snd_card_u8500_alsa_pcm_new(u8500_acodec_chip_t * chip, int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ if ((err = snd_pcm_new(chip->card, "u8500", device, 1, 1, &pcm)) < 0) {
+ stm_error(": error in snd_pcm_new\n");
+ return err;
+ }
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_u8500_alsa_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_u8500_alsa_capture_ops);
+
+ pcm->private_data = chip;
+ pcm->info_flags = 0;
+ chip->pcm = pcm;
+ strcpy(pcm->name, "u8500_alsa");
+
+ u8500_audio_init(pcm->private_data);
+ return 0;
+}
+
+static int u8500_register_alsa_controls(struct snd_card *card,
+ u8500_acodec_chip_t * u8500_chip)
+{
+ int error;
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_playback_vol_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_playback_vol_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_capture_vol_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_capture_vol_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_playback_sink_ctrl,
+ u8500_chip))) < 0) {
+ stm_error(": error initializing playback ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_capture_src_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_playback_sink_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_analog_lpbk_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_analog_lpbk_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_digital_lpbk_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_digital_lpbk_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_playback_switch_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_playback_switch_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_capture_switch_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_capture_switch_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_playback_power_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_playback_power_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_capture_power_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_capture_power_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_tdm_mode_ctrl, u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_tdm_mode_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_direct_rendering_mode_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_direct_rendering_mode_ctrl interface \n\n");
+ return (-1);
+ }
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_pcm_rendering_mode_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_pcm_rendering_mode_ctrl interface \n\n");
+ return (-1);
+ }
+
+ return 0;
+}
+
+static int __init u8500_alsa_probe(struct platform_device *devptr)
+{
+ //static int card_count=0;
+ int error;
+ struct snd_card *card, *hdmi_card;
+ u8500_acodec_chip_t *u8500_chip;
+
+ /*Set currently active users to 0 */
+ active_user = 0;
+
+ error = snd_card_create(0, NULL, THIS_MODULE, sizeof(u8500_acodec_chip_t), &card);
+ if (error < 0) {
+ stm_error(": error in snd_card_create\n");
+ return error;
+ }
+
+ u8500_chip = (u8500_acodec_chip_t *) card->private_data;
+ u8500_chip->card = card;
+
+ if ((error = snd_card_u8500_alsa_pcm_new(u8500_chip, 0)) < 0) {
+ stm_error(": pcm interface can't be initialized\n\n");
+ goto nodev;
+ }
+
+ if ((error = snd_card_u8500_alsa_hdmi_new(u8500_chip, 1)) < 0) {
+ stm_error(": alsa HDMI interface can't be initialized\n\n");
+ goto nodev;
+ }
+
+ if (u8500_register_alsa_controls(card, u8500_chip) < 0) {
+ goto nodev;
+ }
+
+ if (u8500_register_alsa_hdmi_controls(card, u8500_chip) < 0) {
+ goto nodev;
+ }
+#if 0
+ ///////////////////////////////$ H D M I $//////////////////////////////////////
+
+ if (card_count == 1) {
+ hdmi_card =
+ snd_card_new(1, NULL, THIS_MODULE,
+ sizeof(u8500_acodec_chip_t));
+ if (hdmi_card == NULL) {
+ stm_error(": error in hdmi - snd_card_new\n");
+ return -ENOMEM;
+ }
+
+ u8500_chip = (u8500_acodec_chip_t *) hdmi_card->private_data;
+ u8500_chip->card = hdmi_card;
+
+ if ((error = snd_card_u8500_alsa_hdmi_new(u8500_chip, 0)) < 0) {
+ stm_error
+ (": alsa HDMI interface can't be initialized\n\n");
+ goto nodev;
+ }
+
+ if (u8500_register_alsa_hdmi_controls(card, u8500_chip) < 0) {
+ goto nodev;
+ }
+ }
+#endif ////////////////////////////////////////////////////////
+
+ /*char driver[16]; driver name
+ char shortname[32]; short name of this soundcard
+ char longname[80]; name of this soundcard
+ char mixername[80]; mixer name
+ char components[80]; card components delimited withspace */
+
+ strcpy(card->driver, "u8500 alsa");
+ strcpy(card->shortname, "u8500 alsa pcm hdmi driver");
+ strcpy(card->longname, "u8500 alsa pcm hdmi driver");
+
+ snd_card_set_dev(card, &devptr->dev);
+
+ if ((error = snd_card_register(card)) == 0) {
+ stm_info("u8500 audio <hdmi> support running..\n");
+ platform_set_drvdata(devptr, card);
+ return 0;
+ }
+
+ nodev:
+ snd_card_free(card);
+ return error;
+}
+
+static int __devexit u8500_alsa_remove(struct platform_device *devptr)
+{
+ snd_card_free(platform_get_drvdata(devptr));
+ platform_set_drvdata(devptr, NULL);
+ stm_info("u8500 audio support stopped\n");
+
+ /*Set currently active users to 0 */
+ active_user = 0;
+
+ return 0;
+}
+
+static struct platform_driver u8500_alsa_driver = {
+ .probe = u8500_alsa_probe,
+ .remove = __devexit_p(u8500_alsa_remove),
+ .driver = {
+ .name = U8500_ALSA_DRIVER,
+ },
+};
+
+/**
+* u8500_alsa_init - Entry function of AB8500 alsa driver
+*
+* This function registers u8500 alsa driver with linux framework
+*/
+static int __init u8500_alsa_init(void)
+{
+ int err;
+
+ if ((err = platform_driver_register(&u8500_alsa_driver)) < 0)
+ return err;
+ device =
+ platform_device_register_simple(U8500_ALSA_DRIVER, -1, NULL, 0);
+ if (IS_ERR(device)) {
+ platform_driver_unregister(&u8500_alsa_driver);
+ return PTR_ERR(device);
+ }
+ //DBG_ST.acodec = 1;
+ //DBG_ST.alsa = 1;
+
+ return 0;
+}
+
+static void __exit u8500_alsa_exit(void)
+{
+ platform_device_unregister(device);
+ platform_driver_unregister(&u8500_alsa_driver);
+}
+
+module_init(u8500_alsa_init);
+module_exit(u8500_alsa_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AB8500 ALSA driver");
diff --git a/sound/arm/u8500_alsa_ab8500.h b/sound/arm/u8500_alsa_ab8500.h
new file mode 100644
index 00000000000..dda802c2156
--- /dev/null
+++ b/sound/arm/u8500_alsa_ab8500.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Deepak Karda
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#ifndef _U8500_ALSA_H_
+#define _U8500_ALSA_H_
+
+#ifdef CONFIG_U8500_AB8500_CUT10
+#include <mach/ab8500_codec_v1_0.h>
+//#include <mach/ab8500_codec_p_v1_0.h>
+#else
+//#include <mach/ab8500_codec_p.h>
+#include <mach/ab8500_codec.h>
+#endif
+#include <mach/u8500_acodec_ab8500.h>
+
+#define DEFAULT_SAMPLE_RATE 48000
+#define NMDK_BUFFER_SIZE (64*1024)
+#define U8500_ALSA_DRIVER "u8500_alsa"
+
+#define MAX_NUMBER_OF_DEVICES 3 /* ALSA_PCM, ALSA_BT, ALSA_HDMI */
+#define MAX_NUMBER_OF_STREAMS 2 /* PLAYBACK, CAPTURE */
+
+#define ALSA_PCM_DEV 0
+#define ALSA_BT_DEV 2
+#define ALSA_HDMI_DEV 1
+
+/* Debugging stuff */
+#ifndef CONFIG_DEBUG_USER
+#define DEBUG_LEVEL 0
+#else
+#define DEBUG_LEVEL 10
+#endif
+
+#if DEBUG_LEVEL > 0
+static int u8500_acodec_debug = DEBUG_LEVEL;
+#define DEBUG(n, args...) do { if (u8500_acodec_debug>(n)) printk(args); } while (0)
+#else
+#define DEBUG(n, args...) do { } while (0)
+#endif
+enum alsa_state {
+ ALSA_STATE_PAUSE,
+ ALSA_STATE_UNPAUSE
+};
+
+/* audio stream definition */
+typedef struct audio_stream_s {
+ char id[64]; /* module identifier string */
+ int stream_id; /* stream identifier */
+ int status;
+ int active; /* we are using this stream for transfer now */
+ int period; /* current transfer period */
+ int periods; /* current count of periods registerd in the DMA engine */
+ enum alsa_state state;
+ unsigned int old_offset;
+ struct snd_pcm_substream *substream;
+ unsigned int exchId;
+ snd_pcm_uframes_t played_frame;
+ struct semaphore alsa_sem;
+ struct completion alsa_com;
+
+} audio_stream_t;
+
+typedef struct hdmi_params_s {
+ int sampling_freq;
+ int channel_count;
+} hdmi_params_t;
+
+/* chip structure definition */
+typedef struct u8500_acodec_s {
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ struct snd_pcm *pcm_hdmi;
+ struct snd_pcm *pcm_bt;
+ unsigned int freq;
+ unsigned int channels;
+ unsigned int input_lvolume;
+ unsigned int input_rvolume;
+ unsigned int output_lvolume;
+ unsigned int output_rvolume;
+ t_ab8500_codec_src input_device;
+ t_ab8500_codec_dest output_device;
+ t_u8500_bool_state analog_lpbk;
+ t_u8500_bool_state digital_lpbk;
+ t_u8500_bool_state playback_switch;
+ t_u8500_bool_state capture_switch;
+ t_u8500_bool_state tdm8_ch_mode;
+ t_u8500_bool_state direct_rendering_mode;
+ t_u8500_pmc_rendering_state burst_fifo_mode;
+ t_u8500_pmc_rendering_state fm_playback_mode;
+ t_u8500_pmc_rendering_state fm_tx_mode;
+ audio_stream_t stream[MAX_NUMBER_OF_DEVICES][MAX_NUMBER_OF_STREAMS];
+ hdmi_params_t hdmi_params;
+} u8500_acodec_chip_t;
+
+void u8500_alsa_dma_start(audio_stream_t * stream);
+
+#if (defined(CONFIG_U8500_ACODEC_DMA) || defined(CONFIG_U8500_ACODEC_INTR))
+
+#define stm_trigger_alsa(x) u8500_alsa_dma_start(x)
+static void inline stm_pause_alsa(audio_stream_t * stream)
+{
+ if (stream->state == ALSA_STATE_UNPAUSE) {
+ stream->state = ALSA_STATE_PAUSE;
+ }
+
+}
+static void inline stm_unpause_alsa(audio_stream_t * stream)
+{
+ if (stream->state == ALSA_STATE_PAUSE) {
+ stream->state = ALSA_STATE_UNPAUSE;
+ stm_trigger_alsa(stream);
+ }
+}
+static void inline stm_stop_alsa(audio_stream_t * stream)
+{
+ stream->active = 0;
+ stream->period = 0;
+
+}
+static void inline stm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_lib_free_pages(substream);
+}
+
+#define stm_close_alsa(x, y,z)
+#define stm_config_hw(w,x, y, z) 0
+
+#else ////// CONFIG_U8500_ACODEC_POLL ////////////
+
+int spawn_acodec_feeding_thread(audio_stream_t * stream);
+//static int configure_dmadev_acodec(struct snd_pcm_substream *substream);
+
+#define stm_trigger_alsa(x) spawn_acodec_feeding_thread(x)
+#define stm_close_alsa(x, y,z)
+#define stm_config_hw(w,x, y, z) 0
+#define stm_hw_free(x)
+static void inline stm_pause_alsa(audio_stream_t * stream)
+{
+ stream->state = ALSA_STATE_PAUSE;
+}
+static void inline stm_unpause_alsa(audio_stream_t * stream)
+{
+ stream->state = ALSA_STATE_UNPAUSE;
+ complete(&stream->alsa_com);
+}
+static void inline stm_stop_alsa(audio_stream_t * stream)
+{
+ stream->active = 0;
+ stream->period = 0;
+ if (stream->state == ALSA_STATE_PAUSE)
+ complete(&stream->alsa_com);
+}
+
+#endif
+#endif /*END OF HEADER FILE */
diff --git a/sound/arm/u8500_alsa_hdmi.c b/sound/arm/u8500_alsa_hdmi.c
new file mode 100644
index 00000000000..dcbc3583ba7
--- /dev/null
+++ b/sound/arm/u8500_alsa_hdmi.c
@@ -0,0 +1,936 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License terms: GNU General Public License (GPL),
+ * version 2.
+ */
+
+/* This include must be defined at this point */
+//#include <sound/driver.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/ioctl.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <mach/hardware.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+/* alsa system */
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include "u8500_alsa_ab8500.h"
+#include <mach/msp.h>
+#include <mach/debug.h>
+
+#define ALSA_NAME "DRIVER ALSA HDMI"
+
+/* enables/disables debug msgs */
+#define DRIVER_DEBUG CONFIG_STM_ALSA_DEBUG
+/* msg header represents this module */
+#define DRIVER_DEBUG_PFX ALSA_NAME
+/* message level */
+#define DRIVER_DBG KERN_ERR
+#define ELEMENT_SIZE 0
+
+extern char *power_state_in_texts[NUMBER_POWER_STATE];
+
+static int u8500_hdmi_power_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int u8500_hdmi_power_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+static int u8500_hdmi_power_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo);
+
+void dump_msp2_registers();
+
+#ifdef CONFIG_U8500_ACODEC_DMA
+
+static void u8500_alsa_hdmi_dma_start(audio_stream_t * stream);
+#define stm_trigger_hdmi(x) u8500_alsa_hdmi_dma_start(x)
+static void inline stm_pause_hdmi(audio_stream_t * stream)
+{
+ if (stream->state == ALSA_STATE_UNPAUSE) {
+ stream->state = ALSA_STATE_PAUSE;
+ }
+}
+static void inline stm_unpause_hdmi(audio_stream_t * stream)
+{
+ if (stream->state == ALSA_STATE_PAUSE) {
+ stream->state = ALSA_STATE_UNPAUSE;
+ stm_trigger_hdmi(stream);
+ }
+}
+static void inline stm_stop_hdmi(audio_stream_t * stream)
+{
+ stream->active = 0;
+ stream->period = 0;
+}
+#else /* Polling */
+
+static int spawn_hdmi_feeding_thread(audio_stream_t * stream);
+static int hdmi_feeding_thread(void *data);
+static void u8500_hdmi_pio_start(audio_stream_t * stream);
+
+#define stm_trigger_hdmi(x) spawn_hdmi_feeding_thread(x);
+
+static void inline stm_pause_hdmi(audio_stream_t * stream)
+{
+ stream->state = ALSA_STATE_PAUSE;
+}
+static void inline stm_unpause_hdmi(audio_stream_t * stream)
+{
+ stream->state = ALSA_STATE_UNPAUSE;
+ complete(&stream->alsa_com);
+}
+static void inline stm_stop_hdmi(audio_stream_t * stream)
+{
+ stream->active = 0;
+ stream->period = 0;
+ if (stream->state == ALSA_STATE_PAUSE)
+ complete(&stream->alsa_com);
+}
+
+#endif
+
+extern struct driver_debug_st DBG_ST;
+extern struct i2sdrv_data *i2sdrv[MAX_I2S_CLIENTS];
+
+static void u8500_audio_hdmi_init(u8500_acodec_chip_t * chip);
+int u8500_register_alsa_hdmi_controls(struct snd_card *card,
+ u8500_acodec_chip_t * u8500_chip);
+static int snd_u8500_alsa_hdmi_open(struct snd_pcm_substream *substream);
+static int snd_u8500_alsa_hdmi_close(struct snd_pcm_substream *substream);
+static int snd_u8500_alsa_hdmi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params);
+static int snd_u8500_alsa_hdmi_hw_free(struct snd_pcm_substream *substream);
+static int snd_u8500_alsa_hdmi_prepare(struct snd_pcm_substream *substream);
+static int snd_u8500_alsa_hdmi_trigger(struct snd_pcm_substream *substream,
+ int cmd);
+static snd_pcm_uframes_t snd_u8500_alsa_hdmi_pointer(struct snd_pcm_substream
+ *substream);
+static int configure_hdmi_rate(struct snd_pcm_substream *);
+static int configure_msp_hdmi(int sampling_freq, int channel_count);
+
+int u8500_hdmi_rates[] = { 32000, 44100, 48000, 64000, 88200,
+ 96000, 128000, 176100, 192000
+};
+
+typedef enum {
+ HDMI_SAMPLING_FREQ_32KHZ = 32,
+ HDMI_SAMPLING_FREQ_44_1KHZ = 44,
+ HDMI_SAMPLING_FREQ_48KHZ = 48,
+ HDMI_SAMPLING_FREQ_64KHZ = 64,
+ HDMI_SAMPLING_FREQ_88_2KHZ = 88,
+ HDMI_SAMPLING_FREQ_96KHZ = 96,
+ HDMI_SAMPLING_FREQ_128KHZ = 128,
+ HDMI_SAMPLING_FREQ_176_1KHZ = 176,
+ HDMI_SAMPLING_FREQ_192KHZ = 192
+} t_hdmi_sample_freq;
+
+static struct snd_pcm_ops snd_u8500_alsa_hdmi_playback_ops = {
+ .open = snd_u8500_alsa_hdmi_open,
+ .close = snd_u8500_alsa_hdmi_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_u8500_alsa_hdmi_hw_params,
+ .hw_free = snd_u8500_alsa_hdmi_hw_free,
+ .prepare = snd_u8500_alsa_hdmi_prepare,
+ .trigger = snd_u8500_alsa_hdmi_trigger,
+ .pointer = snd_u8500_alsa_hdmi_pointer,
+};
+
+static struct snd_pcm_ops snd_u8500_alsa_hdmi_capture_ops = {
+ .open = snd_u8500_alsa_hdmi_open,
+ .close = snd_u8500_alsa_hdmi_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_u8500_alsa_hdmi_hw_params,
+ .hw_free = snd_u8500_alsa_hdmi_hw_free,
+ .prepare = snd_u8500_alsa_hdmi_prepare,
+ .trigger = snd_u8500_alsa_hdmi_trigger,
+ .pointer = snd_u8500_alsa_hdmi_pointer,
+};
+
+/* Hardware description , this structure (struct snd_pcm_hardware )
+ * contains the definitions of the fundamental hardware configuration.
+ * This configuration will be applied on the runtime structure
+ */
+static struct snd_pcm_hardware snd_u8500_hdmi_playback_hw = {
+ .info =
+ (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE),
+ .formats =
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_U16_BE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = MIN_RATE_PLAYBACK,
+ .rate_max = MAX_RATE_PLAYBACK,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = NMDK_BUFFER_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = NMDK_BUFFER_SIZE / PAGE_SIZE,
+ .periods_max = NMDK_BUFFER_SIZE / 128
+};
+
+static struct snd_pcm_hardware snd_u8500_hdmi_capture_hw = {
+ .info =
+ (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE),
+ .formats =
+ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_U16_BE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = MIN_RATE_CAPTURE,
+ .rate_max = MAX_RATE_CAPTURE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = NMDK_BUFFER_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = NMDK_BUFFER_SIZE / PAGE_SIZE,
+ .periods_max = NMDK_BUFFER_SIZE / 128
+};
+
+static struct snd_pcm_hw_constraint_list constraints_hdmi_rate = {
+ .count = sizeof(u8500_hdmi_rates) / sizeof(u8500_hdmi_rates[0]),
+ .list = u8500_hdmi_rates,
+ .mask = 0,
+};
+
+/**
+ * snd_card_u8500_alsa_hdmi_new - constructor for a new pcm cmponent
+ * @chip - pointer to chip specific data
+ * @device - specifies the card number
+ */
+int snd_card_u8500_alsa_hdmi_new(u8500_acodec_chip_t * chip, int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ if ((err =
+ snd_pcm_new(chip->card, "u8500_hdmi", device, 1, 1, &pcm)) < 0) {
+ stm_error(" : error in snd_pcm_new\n");
+ return err;
+ }
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_u8500_alsa_hdmi_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_u8500_alsa_hdmi_capture_ops);
+
+ pcm->private_data = chip;
+ pcm->info_flags = 0;
+ chip->pcm_hdmi = pcm;
+ strcpy(pcm->name, "u8500_alsa_hdmi");
+
+ u8500_audio_hdmi_init(pcm->private_data);
+ return 0;
+}
+
+/**
+* u8500_audio_hdmi_init
+* @chip - pointer to u8500_acodec_chip_t structure.
+*
+* This function intialises the u8500 chip structure with default values
+*/
+static void u8500_audio_hdmi_init(u8500_acodec_chip_t * chip)
+{
+ audio_stream_t *ptr_audio_stream = NULL;
+
+ ptr_audio_stream =
+ &chip->stream[ALSA_HDMI_DEV][SNDRV_PCM_STREAM_PLAYBACK];
+ /* Setup DMA stuff */
+
+ strlcpy(ptr_audio_stream->id, "u8500 hdmi playback",
+ sizeof(ptr_audio_stream->id));
+
+ ptr_audio_stream->stream_id = SNDRV_PCM_STREAM_PLAYBACK;
+
+ /* default initialization for playback */
+ ptr_audio_stream->active = 0;
+ ptr_audio_stream->period = 0;
+ ptr_audio_stream->periods = 0;
+ ptr_audio_stream->old_offset = 0;
+
+ ptr_audio_stream =
+ &chip->stream[ALSA_HDMI_DEV][SNDRV_PCM_STREAM_CAPTURE];
+
+ strlcpy(ptr_audio_stream->id, "u8500 hdmi capture",
+ sizeof(ptr_audio_stream->id));
+
+ ptr_audio_stream->stream_id = SNDRV_PCM_STREAM_CAPTURE;
+
+ /* default initialization for capture */
+ ptr_audio_stream->active = 0;
+ ptr_audio_stream->period = 0;
+ ptr_audio_stream->periods = 0;
+ ptr_audio_stream->old_offset = 0;
+
+}
+
+/**
+ * snd_u8500_alsa_hdmi_open
+ * @substream - pointer to the playback/capture substream structure
+ *
+ * This routine is used by alsa framework to open a pcm stream .
+ * Here a dma pipe is requested and device is configured(default).
+ */
+static int snd_u8500_alsa_hdmi_open(struct snd_pcm_substream *substream)
+{
+ int error = 0, stream_id, status = 0;
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ audio_stream_t *ptr_audio_stream = NULL;
+
+ stream_id = substream->pstr->stream;
+ error = u8500_acodec_setuser(USER_ALSA);
+ status = u8500_acodec_open(I2S_CLIENT_MSP2, stream_id);
+ if (status) {
+ printk("failed in getting open\n");
+ return (-1);
+ }
+
+ error = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_hdmi_rate);
+ if (error < 0) {
+ stm_error
+ (": error initializing hdmi hw sample rate constraint\n");
+ return error;
+ }
+
+ if ((error = configure_hdmi_rate(substream)))
+ return error;
+
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) {
+ runtime->hw = snd_u8500_hdmi_playback_hw;
+ } else {
+ runtime->hw = snd_u8500_hdmi_capture_hw;
+ }
+
+ ptr_audio_stream = &chip->stream[ALSA_HDMI_DEV][stream_id];
+
+ ptr_audio_stream->substream = substream;
+
+ stm_config_hw(chip, substream, ALSA_HDMI_DEV, stream_id);
+ sema_init(&(ptr_audio_stream->alsa_sem), 1);
+ init_completion(&(ptr_audio_stream->alsa_com));
+
+ ptr_audio_stream->state = ALSA_STATE_UNPAUSE;
+ return 0;
+}
+
+/**
+ * snd_u8500_alsa_hdmi_close
+ * @substream - pointer to the playback/capture substream structure
+ *
+ * This routine is used by alsa framework to close a pcm stream .
+ * Here a dma pipe is disabled and freed.
+ */
+
+static int snd_u8500_alsa_hdmi_close(struct snd_pcm_substream *substream)
+{
+ int stream_id, error = 0;
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ audio_stream_t *ptr_audio_stream = NULL;
+
+ stream_id = substream->pstr->stream;
+ ptr_audio_stream = &chip->stream[ALSA_HDMI_DEV][stream_id];
+
+ stm_close_alsa(chip, ALSA_HDMI_DEV, stream_id);
+
+ /* reset the different variables to default */
+
+ ptr_audio_stream->active = 0;
+ ptr_audio_stream->period = 0;
+ ptr_audio_stream->periods = 0;
+ ptr_audio_stream->old_offset = 0;
+ ptr_audio_stream->substream = NULL;
+
+ /* Disable the MSP2 */
+ error = u8500_acodec_unsetuser(USER_ALSA);
+ u8500_acodec_close(I2S_CLIENT_MSP2, ACODEC_DISABLE_ALL);
+
+ return error;
+
+}
+
+/**
+ * snd_u8500_alsa_hdmi_hw_params
+ * @substream - pointer to the playback/capture substream structure
+ * @hw_params - specifies the hw parameters like format/no of channels etc
+ *
+ * This routine is used by alsa framework to allocate a dma buffer
+ * used to transfer the data from user space to kernel space
+ *
+ */
+static int snd_u8500_alsa_hdmi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+/**
+ * snd_u8500_alsa_hdmi_hw_free
+ * @substream - pointer to the playback/capture substream structure
+ *
+ * This routine is used by alsa framework to deallocate a dma buffer
+ * allocated berfore by snd_u8500_alsa_pcm_hw_params
+ */
+static int snd_u8500_alsa_hdmi_hw_free(struct snd_pcm_substream *substream)
+{
+ stm_hw_free(substream);
+ return 0;
+}
+
+/**
+ * snd_u8500_alsa_hdmi_pointer
+ * @substream - pointer to the playback/capture substream structure
+ *
+ * This callback is called whene the pcm middle layer inquires the current
+ * hardware position on the buffer .The position is returned in frames
+ * ranged from 0 to buffer_size -1
+ */
+static snd_pcm_uframes_t snd_u8500_alsa_hdmi_pointer(struct snd_pcm_substream
+ *substream)
+{
+ unsigned int offset;
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ audio_stream_t *stream =
+ &chip->stream[ALSA_HDMI_DEV][substream->pstr->stream];
+ struct snd_pcm_runtime *runtime = stream->substream->runtime;
+
+ offset = bytes_to_frames(runtime, stream->old_offset);
+ if (offset < 0 || stream->old_offset < 0)
+ stm_dbg(DBG_ST.alsa, " Offset=%i %i\n", offset,
+ stream->old_offset);
+
+ return offset;
+}
+
+/**
+ * snd_u8500_alsa_hdmi_prepare
+ * @substream - pointer to the playback/capture substream structure
+ *
+ * This callback is called whene the pcm is "prepared" Here is possible
+ * to set the format type ,sample rate ,etc.The callback is called as
+ * well everytime a recovery after an underrun happens.
+ */
+
+static int snd_u8500_alsa_hdmi_prepare(struct snd_pcm_substream *substream)
+{
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int error;
+
+ if (chip->hdmi_params.sampling_freq != runtime->rate
+ || chip->hdmi_params.channel_count != runtime->channels) {
+ stm_dbg(DBG_ST.alsa, " freq not same, %d %d\n",
+ chip->hdmi_params.sampling_freq, runtime->rate);
+ stm_dbg(DBG_ST.alsa, " channels not same, %d %d\n",
+ chip->hdmi_params.channel_count, runtime->channels);
+ if (chip->hdmi_params.channel_count != runtime->channels) {
+ chip->hdmi_params.channel_count = runtime->channels;
+ if ((error =
+ stm_config_hw(chip, substream, ALSA_HDMI_DEV,
+ -1))) {
+ stm_dbg(DBG_ST.alsa,
+ "In func %s, stm_config_hw fails",
+ __FUNCTION__);
+ return error;
+ }
+ }
+ chip->hdmi_params.sampling_freq = runtime->rate;
+ if ((error = configure_hdmi_rate(substream))) {
+ stm_dbg(DBG_ST.alsa, "In func %s, configure_rate fails",
+ __FUNCTION__);
+ return error;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * snd_u8500_alsa_hdmi_trigger
+ * @substream - pointer to the playback/capture substream structure
+ * @cmd - specifies the command : start/stop/pause/resume
+ *
+ * This callback is called whene the pcm is started ,stopped or paused
+ * The action is specified in the second argument, SND_PCM_TRIGGER_XXX in
+ * <sound/pcm.h>.
+ * This callback is atomic and the interrupts are disabled , so you can't
+ * call other functions that need interrupts without possible risks
+ */
+static int snd_u8500_alsa_hdmi_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int stream_id = substream->pstr->stream;
+ audio_stream_t *stream = NULL;
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+ int error = 0;
+
+ stream = &chip->stream[ALSA_HDMI_DEV][stream_id];
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* Start the pcm engine */
+ stm_dbg(DBG_ST.alsa, " TRIGGER START\n");
+ if (stream->active == 0) {
+ stream->active = 1;
+ stm_trigger_hdmi(stream);
+ break;
+ }
+ stm_error(": H/w is busy\n");
+ return -EINVAL;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ stm_dbg(DBG_ST.alsa, " SNDRV_PCM_TRIGGER_PAUSE_PUSH\n");
+ if (stream->active == 1) {
+ stm_pause_hdmi(stream);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ stm_dbg(DBG_ST.alsa, " SNDRV_PCM_TRIGGER_PAUSE_RELEASE\n");
+ if (stream->active == 1)
+ stm_unpause_hdmi(stream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* Stop the pcm engine */
+ stm_dbg(DBG_ST.alsa, " TRIGGER STOP\n");
+ if (stream->active == 1)
+ stm_stop_hdmi(stream);
+ break;
+ default:
+ stm_error(": invalid command in pcm trigger\n");
+ return -EINVAL;
+ }
+
+ return error;
+
+}
+
+struct snd_kcontrol_new u8500_hdmi_power_ctrl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .device = 1,
+ .subdevice = 0,
+ .name = "HDMI Power",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xfff,
+ .info = u8500_hdmi_power_ctrl_info,
+ .get = u8500_hdmi_power_ctrl_get,
+ .put = u8500_hdmi_power_ctrl_put
+};
+
+/**
+* u8500_hdmi_power_ctrl_info
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions fills playback device info into user structure.
+*/
+static int u8500_hdmi_power_ctrl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->value.enumerated.items = NUMBER_POWER_STATE;
+ uinfo->count = 1;
+ if (uinfo->value.enumerated.item >= NUMBER_POWER_STATE)
+ uinfo->value.enumerated.item = NUMBER_POWER_STATE - 1;
+ strcpy(uinfo->value.enumerated.name,
+ power_state_in_texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+/**
+* u8500_hdmi_power_ctrl_get
+* @kcontrol - pointer to the snd_kcontrol structure
+* @uinfo - pointer to the snd_ctl_elem_info structure, this is filled by the function
+*
+* This functions returns the current playback device selected.
+*/
+static int u8500_hdmi_power_ctrl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ uinfo->value.enumerated.item[0] = 0;
+ return 0;
+}
+
+/**
+* u8500_hdmi_power_ctrl_put
+* @kcontrol - pointer to the snd_kcontrol structure
+* @ .
+*
+* This functions sets the playback device.
+*/
+static int u8500_hdmi_power_ctrl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uinfo)
+{
+ u8500_acodec_chip_t *chip =
+ (u8500_acodec_chip_t *) snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ t_ab8500_codec_error error;
+ t_u8500_bool_state power_state;
+
+ power_state = uinfo->value.enumerated.item[0];
+
+ changed = 1;
+
+ return changed;
+}
+
+int u8500_register_alsa_hdmi_controls(struct snd_card *card,
+ u8500_acodec_chip_t * u8500_chip)
+{
+ int error;
+
+ if ((error =
+ snd_ctl_add(card,
+ snd_ctl_new1(&u8500_hdmi_power_ctrl,
+ u8500_chip))) < 0) {
+ stm_error
+ (": error initializing u8500_hdmi_power_ctrl interface \n\n");
+ return (-1);
+ }
+
+ return 0;
+}
+
+/**
+* configure_hdmi_rate
+* @substream - pointer to the playback/capture substream structure
+*
+* This functions configures audio codec in to stream frequency frequency
+*/
+static int configure_hdmi_rate(struct snd_pcm_substream *substream)
+{
+ t_hdmi_sample_freq hdmi_sampling_freq;
+
+ u8500_acodec_chip_t *chip = snd_pcm_substream_chip(substream);
+
+ switch (chip->hdmi_params.sampling_freq) {
+ case 32000:
+ hdmi_sampling_freq = HDMI_SAMPLING_FREQ_32KHZ;
+ break;
+ case 44100:
+ hdmi_sampling_freq = HDMI_SAMPLING_FREQ_44_1KHZ;
+ break;
+ case 48000:
+ hdmi_sampling_freq = HDMI_SAMPLING_FREQ_48KHZ;
+ break;
+ case 64000:
+ hdmi_sampling_freq = HDMI_SAMPLING_FREQ_64KHZ;
+ break;
+ case 88200:
+ hdmi_sampling_freq = HDMI_SAMPLING_FREQ_88_2KHZ;
+ break;
+ case 96000:
+ hdmi_sampling_freq = HDMI_SAMPLING_FREQ_96KHZ;
+ break;
+ case 128000:
+ hdmi_sampling_freq = HDMI_SAMPLING_FREQ_128KHZ;
+ break;
+ case 176100:
+ hdmi_sampling_freq = HDMI_SAMPLING_FREQ_176_1KHZ;
+ break;
+ case 192000:
+ hdmi_sampling_freq = HDMI_SAMPLING_FREQ_192KHZ;
+ default:
+ stm_error("not supported frequnecy\n");
+ return -EINVAL;
+ }
+
+ configure_msp_hdmi(hdmi_sampling_freq, chip->hdmi_params.channel_count);
+
+ return 0;
+
+}
+
+static int configure_msp_hdmi(int sampling_freq, int channel_count)
+{
+ struct i2s_device *i2s_dev = i2sdrv[I2S_CLIENT_MSP2]->i2s;
+ struct msp_config msp_config;
+ t_ab8500_codec_error error_status = AB8500_CODEC_OK;
+
+ memset(&msp_config, 0, sizeof(msp_config));
+
+
+ if (i2sdrv[I2S_CLIENT_MSP2]->flag) {
+ stm_dbg(DBG_ST.acodec, " I2S controller not available\n");
+ return -1;
+ }
+
+ /* MSP configuration */
+
+ msp_config.tx_clock_sel = 0;
+ msp_config.rx_clock_sel = 0;
+
+ msp_config.tx_frame_sync_sel = 0;
+ msp_config.rx_frame_sync_sel = 0;
+
+ msp_config.input_clock_freq = MSP_INPUT_FREQ_48MHZ;
+ msp_config.srg_clock_sel = 0;
+
+ msp_config.rx_frame_sync_pol = RX_FIFO_SYNC_HI;
+ msp_config.tx_frame_sync_pol = TX_FIFO_SYNC_HI;
+
+ msp_config.rx_fifo_config = 0;
+ msp_config.tx_fifo_config = TX_FIFO_ENABLE;
+
+ msp_config.spi_clk_mode = SPI_CLK_MODE_NORMAL;
+ msp_config.spi_burst_mode = 0;
+ msp_config.tx_data_enable = 0;
+ msp_config.loopback_enable = 0;
+ msp_config.default_protocol_desc = 1;
+ msp_config.direction = MSP_TRANSMIT_MODE;
+ msp_config.protocol = MSP_I2S_PROTOCOL;
+ msp_config.frame_size = ELEMENT_SIZE;
+ msp_config.frame_freq = sampling_freq;
+ msp_config.def_elem_len = 0;
+ /* enable msp for both tr and rx mode with dma data transfer.
+ THIS IS NOW DONE SEPARATELY from SAA. */
+ msp_config.data_size = MSP_DATA_SIZE_16BIT;
+
+#ifdef CONFIG_U8500_ACODEC_DMA
+ msp_config.work_mode = MSP_DMA_MODE;
+#elif defined(CONFIG_U8500_ACODEC_POLL)
+ msp_config.work_mode = MSP_POLLING_MODE;
+#else
+ msp_config.work_mode = MSP_INTERRUPT_MODE;
+#endif
+ msp_config.default_protocol_desc = 0;
+
+ msp_config.protocol_desc.rx_phase_mode = MSP_DUAL_PHASE;
+ msp_config.protocol_desc.tx_phase_mode = MSP_DUAL_PHASE;
+ msp_config.protocol_desc.rx_phase2_start_mode =
+ MSP_PHASE2_START_MODE_FRAME_SYNC;
+ msp_config.protocol_desc.tx_phase2_start_mode =
+ MSP_PHASE2_START_MODE_FRAME_SYNC;
+ msp_config.protocol_desc.rx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ msp_config.protocol_desc.tx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ msp_config.protocol_desc.rx_frame_length_1 = MSP_FRAME_LENGTH_1;
+ msp_config.protocol_desc.rx_frame_length_2 = MSP_FRAME_LENGTH_1;
+ msp_config.protocol_desc.tx_frame_length_1 = MSP_FRAME_LENGTH_1;
+ msp_config.protocol_desc.tx_frame_length_2 = MSP_FRAME_LENGTH_1;
+ msp_config.protocol_desc.rx_element_length_1 = MSP_ELEM_LENGTH_16;
+ msp_config.protocol_desc.rx_element_length_2 = MSP_ELEM_LENGTH_16;
+ msp_config.protocol_desc.tx_element_length_1 = MSP_ELEM_LENGTH_16;
+ msp_config.protocol_desc.tx_element_length_2 = MSP_ELEM_LENGTH_16;
+ msp_config.protocol_desc.rx_data_delay = MSP_DELAY_1;
+ msp_config.protocol_desc.tx_data_delay = MSP_DELAY_1;
+ msp_config.protocol_desc.rx_clock_pol = MSP_RISING_EDGE;
+ msp_config.protocol_desc.tx_clock_pol = 0;
+ msp_config.protocol_desc.rx_frame_sync_pol =
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH;
+ msp_config.protocol_desc.tx_frame_sync_pol =
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH;
+ msp_config.protocol_desc.rx_half_word_swap = MSP_HWS_NO_SWAP;
+ msp_config.protocol_desc.tx_half_word_swap = MSP_HWS_NO_SWAP;
+ msp_config.protocol_desc.compression_mode = MSP_COMPRESS_MODE_LINEAR;
+ msp_config.protocol_desc.expansion_mode = MSP_EXPAND_MODE_LINEAR;
+ msp_config.protocol_desc.spi_clk_mode = MSP_SPI_CLOCK_MODE_NON_SPI;
+ msp_config.protocol_desc.spi_burst_mode = MSP_SPI_BURST_MODE_DISABLE;
+ msp_config.protocol_desc.frame_period = 63;
+ msp_config.protocol_desc.frame_width = 31;
+ msp_config.protocol_desc.total_clocks_for_one_frame = 64;
+ msp_config.multichannel_configured = 0;
+ msp_config.multichannel_config.tx_multichannel_enable = 0;
+ /* Channel 1 and channel 3 */
+ msp_config.multichannel_config.tx_channel_0_enable = 0x0000005;
+ msp_config.multichannel_config.tx_channel_1_enable = 0x0000000;
+ msp_config.multichannel_config.tx_channel_2_enable = 0x0000000;
+ msp_config.multichannel_config.tx_channel_3_enable = 0x0000000;
+ error_status = i2s_setup(i2s_dev->controller, &msp_config);
+
+#ifdef CONFIG_DEBUG
+ {
+ dump_msp2_registers();
+ }
+#endif
+
+ if (error_status < 0) {
+ printk("error in msp enable, error_status is %d\n",
+ error_status);
+ return error_status;
+ }
+
+ return 0;
+
+}
+
+#ifdef CONFIG_U8500_ACODEC_DMA
+/**
+ * u8500_alsa_hdmi_dma_start - used to transmit or recive a dma chunk
+ * @stream - specifies the playback/record stream structure
+ */
+static void u8500_alsa_hdmi_dma_start(audio_stream_t * stream)
+{
+ unsigned int offset, dma_size, stream_id;
+
+ struct snd_pcm_substream *substream = stream->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ stream_id = substream->pstr->stream;
+
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * stream->period;
+ stream->old_offset = offset;
+
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
+#ifdef CONFIG_U8500_ACODEC_DMA
+ u8500_acodec_send_data(I2S_CLIENT_MSP2,
+ (void *)(runtime->dma_addr + offset),
+ dma_size, 1);
+#else
+ u8500_acodec_send_data(I2S_CLIENT_MSP2,
+ (void *)(runtime->dma_area + offset),
+ dma_size, 0);
+#endif
+ else
+#ifdef CONFIG_U8500_ACODEC_DMA
+ u8500_acodec_receive_data(I2S_CLIENT_MSP2,
+ (void *)(runtime->dma_addr + offset),
+ dma_size, 1);
+#else
+ u8500_acodec_receive_data(I2S_CLIENT_MSP2,
+ (void *)(runtime->dma_area + offset),
+ dma_size, 0);
+#endif
+
+ stm_dbg(DBG_ST.alsa, " DMA Transfer started\n");
+ stm_dbg(DBG_ST.alsa, " address = %x size=%d\n",
+ (runtime->dma_addr + offset), dma_size);
+
+ stream->period++;
+ stream->period %= runtime->periods;
+ stream->periods++;
+
+
+}
+
+#else
+
+/**
+* acodec_feeding_thread
+* @stream - pointer to the playback/capture audio_stream_t structure
+*
+* This function creates a kernel thread .
+*/
+
+static int spawn_hdmi_feeding_thread(audio_stream_t * stream)
+{
+ pid_t pid;
+
+ pid =
+ kernel_thread(hdmi_feeding_thread, stream,
+ CLONE_FS | CLONE_SIGHAND);
+
+ return 0;
+}
+
+/**
+* hdmi_feeding_thread
+* @data - void pointer to the playback/capture audio_stream_t structure
+*
+* This thread sends/receive data to MSP while stream is active
+*/
+static int hdmi_feeding_thread(void *data)
+{
+ audio_stream_t *stream = (audio_stream_t *) data;
+
+ daemonize("hdmi_feeding_thread");
+ allow_signal(SIGKILL);
+ down(&stream->alsa_sem);
+
+ while ((!signal_pending(current)) && (stream->active)) {
+ if (stream->state == ALSA_STATE_PAUSE)
+ wait_for_completion(&(stream->alsa_com));
+
+ u8500_hdmi_pio_start(stream);
+ if (stream->substream)
+ snd_pcm_period_elapsed(stream->substream);
+ }
+
+ up(&stream->alsa_sem);
+
+ return 0;
+}
+
+/**
+* u8500_hdmi_pio_start
+* @stream - pointer to the playback/capture audio_stream_t structure
+*
+* This function sends/receive one chunck of stream data to/from MSP
+*/
+static void u8500_hdmi_pio_start(audio_stream_t * stream)
+{
+ unsigned int offset, dma_size, stream_id;
+ struct snd_pcm_substream *substream = stream->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ stream_id = substream->pstr->stream;
+
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ offset = dma_size * stream->period;
+ stream->old_offset = offset;
+
+ stm_dbg(DBG_ST.alsa, " Transfer started\n");
+ stm_dbg(DBG_ST.alsa, " address = %x size=%d\n",
+ (runtime->dma_addr + offset), dma_size);
+
+ /* Send our stuff */
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
+#ifdef CONFIG_U8500_ACODEC_DMA
+ u8500_acodec_send_data(I2S_CLIENT_MSP2,
+ (void *)(runtime->dma_addr + offset),
+ dma_size, 1);
+#else
+ u8500_acodec_send_data(I2S_CLIENT_MSP2,
+ (void *)(runtime->dma_area + offset),
+ dma_size, 0);
+#endif
+ else
+#ifdef CONFIG_U8500_ACODEC_DMA
+ u8500_acodec_receive_data(I2S_CLIENT_MSP2,
+ (void *)(runtime->dma_addr + offset),
+ dma_size, 1);
+#else
+ u8500_acodec_receive_data(I2S_CLIENT_MSP2,
+ (void *)(runtime->dma_area + offset),
+ dma_size, 0);
+#endif
+
+ stream->period++;
+ stream->period %= runtime->periods;
+ stream->periods++;
+}
+#endif
+
+void dump_msp2_registers()
+{
+ int i;
+
+ stm_dbg(DBG_ST.acodec, "\nMSP_2 base add = 0x%x\n",
+ (unsigned int)U8500_MSP2_BASE);
+
+ for (i = 0; i < 0x40; i += 4)
+ stm_dbg(DBG_ST.acodec, "msp[0x%x]=0x%x\n", i,
+ readl((char *)(IO_ADDRESS(U8500_MSP2_BASE) + i)));
+
+ return 0;
+}
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 4d18941178e..88ab2303ce2 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -649,6 +649,8 @@ int snd_interval_refine(struct snd_interval *i, const struct snd_interval *v)
}
} else if (!i->openmin && !i->openmax && i->min == i->max)
i->integer = 1;
+ if (i->max < i->min)
+ i->max = i->min;
if (snd_interval_checkempty(i)) {
snd_interval_none(i);
return -EINVAL;
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 91c985599d3..40017a437fb 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -13,7 +13,7 @@ menuconfig SND_SOC
If you want ASoC support, you should say Y here and also to the
specific driver for your SoC platform below.
-
+
ASoC provides power efficient ALSA support for embedded battery powered
SoC based systems like PDA's, Phones and Personal Media Players.
@@ -48,6 +48,7 @@ source "sound/soc/s6000/Kconfig"
source "sound/soc/sh/Kconfig"
source "sound/soc/tegra/Kconfig"
source "sound/soc/txx9/Kconfig"
+source "sound/soc/ux500/Kconfig"
# Supported codecs
source "sound/soc/codecs/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 2feaf376e94..f5fe62111b8 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -25,3 +25,4 @@ obj-$(CONFIG_SND_SOC) += s6000/
obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += tegra/
obj-$(CONFIG_SND_SOC) += txx9/
+obj-$(CONFIG_SND_SOC) += ux500/
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 59d8efaa17e..a2082934de3 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -107,7 +107,12 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM9705 if SND_SOC_AC97_BUS
select SND_SOC_WM9712 if SND_SOC_AC97_BUS
select SND_SOC_WM9713 if SND_SOC_AC97_BUS
- help
+ select SND_SOC_AB3550
+ select SND_SOC_AB5500
+ select SND_SOC_AB8500
+ select SND_SOC_CG29XX
+ select SND_SOC_AV8100
+ help
Normally ASoC codec drivers are only built if a machine driver which
uses them is also built since they are only usable with a machine
driver. Selecting this option will allow these drivers to be built
@@ -425,6 +430,21 @@ config SND_SOC_WM9712
config SND_SOC_WM9713
tristate
+config SND_SOC_AB3550
+ tristate
+
+config SND_SOC_AB5500
+ tristate
+
+config SND_SOC_AB8500
+ tristate
+
+config SND_SOC_CG29XX
+ tristate
+
+config SND_SOC_AV8100
+ tristate
+
# Amp
config SND_SOC_LM4857
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 6662eb0cdcc..07269695757 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -1,4 +1,7 @@
snd-soc-88pm860x-objs := 88pm860x-codec.o
+snd-soc-ab3550-objs := ab3550.o
+snd-soc-ab5500-objs := ab5500.o
+snd-soc-ab8500_audio-objs := ab8500_audio.o
snd-soc-ac97-objs := ac97.o
snd-soc-ad1836-objs := ad1836.o
snd-soc-ad193x-objs := ad193x.o
@@ -13,6 +16,8 @@ snd-soc-ak4535-objs := ak4535.o
snd-soc-ak4641-objs := ak4641.o
snd-soc-ak4642-objs := ak4642.o
snd-soc-ak4671-objs := ak4671.o
+snd-soc-av8100_audio-objs := av8100_audio.o
+snd-soc-cg29xx-objs := cg29xx.o
snd-soc-cq93vc-objs := cq93vc.o
snd-soc-cs42l51-objs := cs42l51.o
snd-soc-cs42l73-objs := cs42l73.o
@@ -102,6 +107,9 @@ snd-soc-wm-hubs-objs := wm_hubs.o
snd-soc-max9877-objs := max9877.o
snd-soc-tpa6130a2-objs := tpa6130a2.o
+obj-$(CONFIG_SND_SOC_AB3550) += snd-soc-ab3550.o
+obj-$(CONFIG_SND_SOC_AB5500) += snd-soc-ab5500.o
+obj-$(CONFIG_SND_SOC_AB8500) += snd-soc-ab8500_audio.o
obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o
@@ -119,6 +127,8 @@ obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o
obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o
+obj-$(CONFIG_SND_SOC_AV8100) += snd-soc-av8100_audio.o
+obj-$(CONFIG_SND_SOC_CG29XX) += snd-soc-cg29xx.o
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o
@@ -205,3 +215,9 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o
+ifdef CONFIG_SND_SOC_UX500_DEBUG
+CFLAGS_av8100_audio.o := -DDEBUG
+CFLAGS_ab3550.o := -DDEBUG
+CFLAGS_cg29xx.o := -DDEBUG
+CFLAGS_ab8500_audio.o := -DDEBUG
+endif
diff --git a/sound/soc/codecs/ab3550.c b/sound/soc/codecs/ab3550.c
new file mode 100644
index 00000000000..4a15ab64d32
--- /dev/null
+++ b/sound/soc/codecs/ab3550.c
@@ -0,0 +1,1429 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Xie Xiaolei <xie.xiaolei@etericsson.com>,
+ * Roger Nilsson <roger.xr.nilsson@stericsson.com>,
+ * Ola Lilja <ola.o.lilja@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/mfd/abx500.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <asm/atomic.h>
+#include <linux/rwsem.h>
+#include <linux/mutex.h>
+#include <stdarg.h>
+#include "ab3550.h"
+
+
+#define I2C_BANK 0
+
+/* codec private data */
+struct ab3550_codec_dai_data {
+};
+
+static struct device *ab3550_dev;
+
+static u8 virtual_regs[] = {
+ 0, 0
+};
+
+static void set_reg(u8 reg, u8 val)
+{
+ if (!ab3550_dev) {
+ pr_err("%s: The AB3550 codec driver not initialized.\n",
+ __func__);
+ return;
+ }
+ if (reg < AB3550_FIRST_REG)
+ return;
+ else if (reg <= AB3550_LAST_REG) {
+ abx500_set_register_interruptible(
+ ab3550_dev, I2C_BANK, reg, val);
+ } else if (reg - AB3550_LAST_REG - 1 < ARRAY_SIZE(virtual_regs)) {
+ virtual_regs[reg - AB3550_LAST_REG - 1] = val;
+ }
+}
+
+static void mask_set_reg(u8 reg, u8 mask, u8 val)
+{
+ if (!ab3550_dev) {
+ pr_err("%s: The AB3550 codec driver not initialized.\n",
+ __func__);
+ return;
+ }
+ if (reg < AB3550_FIRST_REG)
+ return;
+ else if (reg <= AB3550_LAST_REG) {
+ abx500_mask_and_set_register_interruptible(
+ ab3550_dev, I2C_BANK, reg, mask, val);
+ } else if (reg - AB3550_LAST_REG - 1 < ARRAY_SIZE(virtual_regs)) {
+ virtual_regs[reg - AB3550_LAST_REG - 1] &= ~mask;
+ virtual_regs[reg - AB3550_LAST_REG - 1] |= val & mask;
+ }
+}
+
+static u8 read_reg(u8 reg)
+{
+ if (!ab3550_dev) {
+ pr_err("%s: The AB3550 codec driver not initialized.\n",
+ __func__);
+ return 0;
+ }
+ if (reg < AB3550_FIRST_REG)
+ return 0;
+ else if (reg <= AB3550_LAST_REG) {
+ u8 val;
+ abx500_get_register_interruptible(
+ ab3550_dev, I2C_BANK, reg, &val);
+ return val;
+ } else if (reg - AB3550_LAST_REG - 1 < ARRAY_SIZE(virtual_regs))
+ return virtual_regs[reg - AB3550_LAST_REG - 1];
+ dev_warn(ab3550_dev, "%s: out-of-scope reigster %u.\n",
+ __func__, reg);
+ return 0;
+}
+
+/* Components that can be powered up/down */
+enum enum_widget {
+ widget_ear = 0,
+ widget_auxo1,
+ widget_auxo2,
+
+ widget_spkr,
+ widget_line1,
+ widget_line2,
+
+ widget_dac1,
+ widget_dac2,
+ widget_dac3,
+
+ widget_rx1,
+ widget_rx2,
+ widget_rx3,
+
+ widget_mic1,
+ widget_mic2,
+
+ widget_micbias1,
+ widget_micbias2,
+
+ widget_apga1,
+ widget_apga2,
+
+ widget_tx1,
+ widget_tx2,
+
+ widget_adc1,
+ widget_adc2,
+
+ widget_if0_dld_l,
+ widget_if0_dld_r,
+ widget_if0_uld_l,
+ widget_if0_uld_r,
+ widget_if1_dld_l,
+ widget_if1_dld_r,
+ widget_if1_uld_l,
+ widget_if1_uld_r,
+
+ widget_mic1p1,
+ widget_mic1n1,
+ widget_mic1p2,
+ widget_mic1n2,
+
+ widget_mic2p1,
+ widget_mic2n1,
+ widget_mic2p2,
+ widget_mic2n2,
+
+ widget_clock,
+
+ number_of_widgets
+};
+
+/* This is only meant for debugging */
+static const char *widget_names[] = {
+ "EAR", "AUXO1", "AUXO2", "SPKR", "LINE1", "LINE2",
+ "DAC1", "DAC2", "DAC3",
+ "RX1", "RX2", "RX3",
+ "MIC1", "MIC2",
+ "MIC-BIAS1", "MIC-BIAS2",
+ "APGA1", "APGA2",
+ "TX1", "TX2",
+ "ADC1", "ADC2",
+ "IF0-DLD-L", "IF0-DLD-R", "IF0-ULD-L", "IF0-ULD-R",
+ "IF1-DLD-L", "IF1-DLD-R", "IF1-ULD-L", "IF1-ULD-R",
+ "MIC1P1", "MIC1N1", "MIC1P2", "MIC1N2",
+ "MIC2P1", "MIC2N1", "MIC2P2", "MIC2N2",
+ "CLOCK"
+};
+
+struct widget_pm {
+ enum enum_widget widget;
+ u8 reg;
+ u8 shift;
+
+ unsigned long source_list[BIT_WORD(number_of_widgets) + 1];
+ unsigned long sink_list[BIT_WORD(number_of_widgets) + 1];
+};
+
+static struct widget_pm widget_pm_array[] = {
+ {.widget = widget_ear, .reg = EAR, .shift = EAR_PWR_SHIFT},
+ {.widget = widget_auxo1, .reg = AUXO1, .shift = AUXOx_PWR_SHIFT},
+ {.widget = widget_auxo2, .reg = AUXO2, .shift = AUXOx_PWR_SHIFT},
+ {.widget = widget_spkr, .reg = SPKR, .shift = SPKR_PWR_SHIFT},
+ {.widget = widget_line1, .reg = LINE1, .shift = LINEx_PWR_SHIFT},
+ {.widget = widget_line2, .reg = LINE2, .shift = LINEx_PWR_SHIFT},
+
+ {.widget = widget_dac1, .reg = RX1, .shift = DACx_PWR_SHIFT},
+ {.widget = widget_dac2, .reg = RX2, .shift = DACx_PWR_SHIFT},
+ {.widget = widget_dac3, .reg = RX3, .shift = DACx_PWR_SHIFT},
+
+ {.widget = widget_rx1, .reg = RX1, .shift = RXx_PWR_SHIFT},
+ {.widget = widget_rx2, .reg = RX2, .shift = RXx_PWR_SHIFT},
+ {.widget = widget_rx3, .reg = RX3, .shift = RXx_PWR_SHIFT},
+
+ {.widget = widget_mic1, .reg = MIC1_GAIN, .shift = MICx_PWR_SHIFT},
+ {.widget = widget_mic2, .reg = MIC2_GAIN, .shift = MICx_PWR_SHIFT},
+
+ {.widget = widget_micbias1, .reg = MIC_BIAS1,
+ .shift = MBIAS_PWR_SHIFT},
+ {.widget = widget_micbias2, .reg = MIC_BIAS2,
+ .shift = MBIAS_PWR_SHIFT},
+
+ {.widget = widget_apga1, .reg = ANALOG_LOOP_PGA1,
+ .shift = APGAx_PWR_SHIFT},
+ {.widget = widget_apga2, .reg = ANALOG_LOOP_PGA2,
+ .shift = APGAx_PWR_SHIFT},
+
+ {.widget = widget_tx1, .reg = TX1, .shift = TXx_PWR_SHIFT},
+ {.widget = widget_tx2, .reg = TX2, .shift = TXx_PWR_SHIFT},
+
+ {.widget = widget_adc1, .reg = TX1, .shift = ADCx_PWR_SHIFT},
+ {.widget = widget_adc2, .reg = TX2, .shift = ADCx_PWR_SHIFT},
+
+ {.widget = widget_if0_dld_l, .reg = AB3550_VIRTUAL_REG1,
+ .shift = IF0_DLD_L_PW_SHIFT},
+ {.widget = widget_if0_dld_r, .reg = AB3550_VIRTUAL_REG1,
+ .shift = IF0_DLD_R_PW_SHIFT},
+ {.widget = widget_if0_uld_l, .reg = AB3550_VIRTUAL_REG1,
+ .shift = IF0_ULD_L_PW_SHIFT},
+ {.widget = widget_if0_uld_r, .reg = AB3550_VIRTUAL_REG1,
+ .shift = IF0_ULD_R_PW_SHIFT},
+
+ {.widget = widget_if1_dld_l, .reg = AB3550_VIRTUAL_REG1,
+ .shift = IF1_DLD_L_PW_SHIFT},
+ {.widget = widget_if1_dld_r, .reg = AB3550_VIRTUAL_REG1,
+ .shift = IF1_DLD_R_PW_SHIFT},
+ {.widget = widget_if1_uld_l, .reg = AB3550_VIRTUAL_REG1,
+ .shift = IF1_ULD_L_PW_SHIFT},
+ {.widget = widget_if1_uld_r, .reg = AB3550_VIRTUAL_REG1,
+ .shift = IF1_ULD_R_PW_SHIFT},
+
+ {.widget = widget_mic1p1, .reg = AB3550_VIRTUAL_REG2,
+ .shift = MIC1P1_PW_SHIFT},
+ {.widget = widget_mic1n1, .reg = AB3550_VIRTUAL_REG2,
+ .shift = MIC1N1_PW_SHIFT},
+ {.widget = widget_mic1p2, .reg = AB3550_VIRTUAL_REG2,
+ .shift = MIC1P2_PW_SHIFT},
+ {.widget = widget_mic1n2, .reg = AB3550_VIRTUAL_REG2,
+ .shift = MIC1N2_PW_SHIFT},
+
+ {.widget = widget_mic2p1, .reg = AB3550_VIRTUAL_REG2,
+ .shift = MIC2P1_PW_SHIFT},
+ {.widget = widget_mic2n1, .reg = AB3550_VIRTUAL_REG2,
+ .shift = MIC2N1_PW_SHIFT},
+ {.widget = widget_mic2p2, .reg = AB3550_VIRTUAL_REG2,
+ .shift = MIC2P2_PW_SHIFT},
+ {.widget = widget_mic2n2, .reg = AB3550_VIRTUAL_REG2,
+ .shift = MIC2N2_PW_SHIFT},
+
+ {.widget = widget_clock, .reg = CLOCK, .shift = CLOCK_ENABLE_SHIFT},
+};
+
+DEFINE_MUTEX(ab3550_pm_mutex);
+
+static struct {
+ enum enum_widget stack[number_of_widgets];
+ int p;
+} pm_stack;
+
+struct ab3550_dai_private {
+ unsigned int fmt;
+};
+
+#define pm_stack_as_bitmap ({ \
+ unsigned long bitmap[BIT_WORD(number_of_widgets) + 1]; \
+ int i; \
+ memset(bitmap, 0, sizeof(bitmap)); \
+ for (i = 0; i < pm_stack.p; i++) { \
+ set_bit(pm_stack.stack[i], bitmap); \
+ } \
+ bitmap; \
+ })
+
+/* These are only meant to meet the obligations of DAPM */
+static const struct snd_soc_dapm_widget ab3550_dapm_widgets[] = {
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+};
+
+
+static const char *enum_rx2_select[] = {"I2S0", "I2S1"};
+static const char *enum_i2s_input_select[] = {
+ "tri-state", "MIC1", "MIC2", "mute"
+};
+static const char *enum_apga1_source[] = {"LINEIN1", "MIC1", "MIC2"};
+static const char *enum_apga2_source[] = {"LINEIN2", "MIC1", "MIC2"};
+static const char *enum_dac_side_tone[] = {"TX1", "TX2"};
+static const char *enum_dac_power_mode[] = {"100%", "75%", "55%"};
+static const char *enum_ear_power_mode[] = {"100%", "70%"};
+static const char *enum_auxo_power_mode[] = {
+ "100%", "67%", "50%", "25%", "auto"
+};
+static const char *enum_onoff[] = {"Off", "On"};
+static const char *enum_mbias_hiz_option[] = {"GND", "HiZ"};
+static const char *enum_mbias2_output_voltage[] = {"2.0v", "2.2v"};
+static const char *enum_mic_input_impedance[] = {
+ "12.5 kohm", "25 kohm", "50 kohm"
+};
+static const char *enum_hp_filter[] = {"HP3", "HP1", "bypass"};
+static const char *enum_i2s_word_length[] = {"16 bits", "24 bits"};
+static const char *enum_i2s_mode[] = {"Master Mode", "Slave Mode"};
+static const char *enum_i2s_tristate[] = {"Normal", "Tri-state"};
+static const char *enum_optional_resistor[] = {"disconnected", "connected"};
+static const char *enum_i2s_sample_rate[] = {
+ "8 kHz", "16 kHz", "44.1 kHz", "48 kHz"
+};
+static const char *enum_signal_inversion[] = {"normal", "inverted"};
+
+/* RX2 Select */
+static struct soc_enum soc_enum_rx2_select =
+ SOC_ENUM_SINGLE(RX2, 4, ARRAY_SIZE(enum_rx2_select), enum_rx2_select);
+
+/* I2S0 Input Select */
+static struct soc_enum soc_enum_i2s0_input_select =
+ SOC_ENUM_DOUBLE(INTERFACE0_DATA, 0, 2,
+ ARRAY_SIZE(enum_i2s_input_select),
+ enum_i2s_input_select);
+/* I2S1 Input Select */
+static struct soc_enum soc_enum_i2s1_input_select =
+ SOC_ENUM_DOUBLE(INTERFACE1_DATA, 0, 2,
+ ARRAY_SIZE(enum_i2s_input_select),
+ enum_i2s_input_select);
+
+/* APGA1 Source */
+static struct soc_enum soc_enum_apga1_source =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA1, APGAx_MUX_SHIFT,
+ ARRAY_SIZE(enum_apga1_source), enum_apga1_source);
+
+/* APGA2 Source */
+static struct soc_enum soc_enum_apga2_source =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA2, APGAx_MUX_SHIFT,
+ ARRAY_SIZE(enum_apga2_source), enum_apga2_source);
+
+static struct soc_enum soc_enum_apga1_enable =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA1, APGAx_PWR_SHIFT,
+ ARRAY_SIZE(enum_onoff), enum_onoff);
+
+static struct soc_enum soc_enum_apga2_enable =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA2, APGAx_PWR_SHIFT,
+ ARRAY_SIZE(enum_onoff), enum_onoff);
+
+/* DAC1 Side Tone */
+static struct soc_enum soc_enum_dac1_side_tone =
+ SOC_ENUM_SINGLE(SIDETONE1_PGA, STx_MUX_SHIFT,
+ ARRAY_SIZE(enum_dac_side_tone), enum_dac_side_tone);
+
+/* DAC2 Side Tone */
+static struct soc_enum soc_enum_dac2_side_tone =
+ SOC_ENUM_SINGLE(SIDETONE2_PGA, STx_MUX_SHIFT,
+ ARRAY_SIZE(enum_dac_side_tone), enum_dac_side_tone);
+
+/* DAC1 Power Mode */
+static struct soc_enum soc_enum_dac1_power_mode =
+ SOC_ENUM_SINGLE(RX1, DACx_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_dac_power_mode), enum_dac_power_mode);
+
+/* DAC2 Power Mode */
+static struct soc_enum soc_enum_dac2_power_mode =
+ SOC_ENUM_SINGLE(RX2, DACx_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_dac_power_mode), enum_dac_power_mode);
+
+/* DAC3 Power Mode */
+static struct soc_enum soc_enum_dac3_power_mode =
+ SOC_ENUM_SINGLE(RX3, DACx_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_dac_power_mode), enum_dac_power_mode);
+
+/* EAR Power Mode */
+static struct soc_enum soc_enum_ear_power_mode =
+ SOC_ENUM_SINGLE(EAR, EAR_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_ear_power_mode), enum_ear_power_mode);
+
+/* AUXO Power Mode */
+static struct soc_enum soc_enum_auxo_power_mode =
+ SOC_ENUM_SINGLE(AUXO_PWR_MODE, AUXO_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_auxo_power_mode),
+ enum_auxo_power_mode);
+
+/* MBIAS1 HiZ Option */
+static struct soc_enum soc_enum_mbias1_hiz_option =
+ SOC_ENUM_SINGLE(MIC_BIAS1, MBIAS_PDN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mbias_hiz_option),
+ enum_mbias_hiz_option);
+
+/* MBIAS1 HiZ Option */
+static struct soc_enum soc_enum_mbias2_hiz_option =
+ SOC_ENUM_SINGLE(MIC_BIAS2, MBIAS_PDN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mbias_hiz_option),
+ enum_mbias_hiz_option);
+
+/* MBIAS2 Output voltage */
+static struct soc_enum soc_enum_mbias2_output_voltage =
+ SOC_ENUM_SINGLE(MIC_BIAS2, MBIAS2_OUT_V_SHIFT,
+ ARRAY_SIZE(enum_mbias2_output_voltage),
+ enum_mbias2_output_voltage);
+
+static struct soc_enum soc_enum_mbias2_internal_resistor =
+ SOC_ENUM_SINGLE(MIC_BIAS2_VAD, MBIAS2_R_INT_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_mic1_input_impedance =
+ SOC_ENUM_SINGLE(MIC1_GAIN, MICx_IN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mic_input_impedance),
+ enum_mic_input_impedance);
+
+static struct soc_enum soc_enum_mic2_input_impedance =
+ SOC_ENUM_SINGLE(MIC2_GAIN, MICx_IN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mic_input_impedance),
+ enum_mic_input_impedance);
+
+static struct soc_enum soc_enum_tx1_hp_filter =
+ SOC_ENUM_SINGLE(TX1, TXx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_tx2_hp_filter =
+ SOC_ENUM_SINGLE(TX2, TXx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_st1_hp_filter =
+ SOC_ENUM_SINGLE(SIDETONE1_PGA, STx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_st2_hp_filter =
+ SOC_ENUM_SINGLE(SIDETONE2_PGA, STx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_i2s0_word_length =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_WORDLENGTH_SHIFT,
+ ARRAY_SIZE(enum_i2s_word_length),
+ enum_i2s_word_length);
+
+static struct soc_enum soc_enum_i2s1_word_length =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_WORDLENGTH_SHIFT,
+ ARRAY_SIZE(enum_i2s_word_length),
+ enum_i2s_word_length);
+
+static struct soc_enum soc_enum_i2s0_mode =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_MODE_SHIFT,
+ ARRAY_SIZE(enum_i2s_mode),
+ enum_i2s_mode);
+
+static struct soc_enum soc_enum_i2s1_mode =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_MODE_SHIFT,
+ ARRAY_SIZE(enum_i2s_mode),
+ enum_i2s_mode);
+
+static struct soc_enum soc_enum_i2s0_tristate =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_TRISTATE_SHIFT,
+ ARRAY_SIZE(enum_i2s_tristate),
+ enum_i2s_tristate);
+
+static struct soc_enum soc_enum_i2s1_tristate =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_TRISTATE_SHIFT,
+ ARRAY_SIZE(enum_i2s_tristate),
+ enum_i2s_tristate);
+
+static struct soc_enum soc_enum_i2s0_pulldown_resistor =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_i2s1_pulldown_resistor =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_i2s0_sample_rate =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_SR_SHIFT,
+ ARRAY_SIZE(enum_i2s_sample_rate),
+ enum_i2s_sample_rate);
+
+static struct soc_enum soc_enum_i2s1_sample_rate =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_SR_SHIFT,
+ ARRAY_SIZE(enum_i2s_sample_rate),
+ enum_i2s_sample_rate);
+
+static struct soc_enum soc_enum_line1_inversion =
+ SOC_ENUM_SINGLE(LINE1, LINEx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_line2_inversion =
+ SOC_ENUM_SINGLE(LINE2, LINEx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo1_inversion =
+ SOC_ENUM_SINGLE(AUXO1, AUXOx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo2_inversion =
+ SOC_ENUM_SINGLE(AUXO1, AUXOx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo1_pulldown_resistor =
+ SOC_ENUM_SINGLE(AUXO1, AUXOx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_auxo2_pulldown_resistor =
+ SOC_ENUM_SINGLE(AUXO1, AUXOx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct snd_kcontrol_new ab3550_snd_controls[] = {
+ /* RX Routing */
+ SOC_ENUM("RX2 Select", soc_enum_rx2_select),
+ SOC_SINGLE("LINE1 Adder", LINE1_ADDER, 0, 0x07, 0),
+ SOC_SINGLE("LINE2 Adder", LINE2_ADDER, 0, 0x07, 0),
+ SOC_SINGLE("EAR Adder", EAR_ADDER, 0, 0x07, 0),
+ SOC_SINGLE("SPKR Adder", SPKR_ADDER, 0, 0x07, 0),
+ SOC_SINGLE("AUXO1 Adder", AUXO1_ADDER, 0, 0x07, 0),
+ SOC_SINGLE("AUXO2 Adder", AUXO2_ADDER, 0, 0x07, 0),
+ /* TX Routing */
+ SOC_SINGLE("MIC1 Input Select", MIC1_INPUT_SELECT, 0, 0xff, 0),
+ SOC_SINGLE("MIC2 Input Select", MIC1_INPUT_SELECT, 0, 0xff, 0),
+ SOC_SINGLE("MIC2 to MIC1", MIC2_TO_MIC1, 0, 0x03, 0),
+ SOC_ENUM("I2S0 Input Select", soc_enum_i2s0_input_select),
+ SOC_ENUM("I2S1 Input Select", soc_enum_i2s1_input_select),
+ /* Routing of Side Tone and Analop Loop */
+ SOC_ENUM("APGA1 Source", soc_enum_apga1_source),
+ SOC_ENUM("APGA2 Source", soc_enum_apga2_source),
+ SOC_ENUM("APGA1 Enable", soc_enum_apga1_enable),
+ SOC_ENUM("APGA2 Enable", soc_enum_apga2_enable),
+ SOC_SINGLE("APGA1 Destination", APGA1_ADDER, 0, 0x3f, 0),
+ SOC_SINGLE("APGA2 Destination", APGA2_ADDER, 0, 0x3f, 0),
+ SOC_ENUM("DAC1 Side Tone", soc_enum_dac1_side_tone),
+ SOC_ENUM("DAC2 Side Tone", soc_enum_dac2_side_tone),
+ /* RX Volume Control */
+ SOC_SINGLE("RX-DPGA1 Gain", RX1_DIGITAL_PGA, 0, 0x43, 0),
+ SOC_SINGLE("RX-DPGA2 Gain", RX1_DIGITAL_PGA, 0, 0x43, 0),
+ SOC_SINGLE("RX-DPGA3 Gain", RX3_DIGITAL_PGA, 0, 0x43, 0),
+ SOC_SINGLE("LINE1 Gain", LINE1, LINEx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("LINE2 Gain", LINE2, LINEx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("SPKR Gain", SPKR, SPKR_GAIN_SHIFT, 0x16, 0),
+ SOC_SINGLE("EAR Gain", EAR, EAR_GAIN_SHIFT, 0x0e, 0),
+ SOC_SINGLE("AUXO1 Gain", AUXO1, AUXOx_GAIN_SHIFT, 0x0c, 0),
+ SOC_SINGLE("AUXO2 Gain", AUXO2, AUXOx_GAIN_SHIFT, 0x0c, 0),
+ /* TX Volume Control */
+ SOC_SINGLE("MIC1 Gain", MIC1_GAIN, MICx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("MIC2 Gain", MIC2_GAIN, MICx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("TX-DPGA1 Gain", TX_DIGITAL_PGA1, TXDPGAx_SHIFT, 0x0f, 0),
+ SOC_SINGLE("TX-DPGA2 Gain", TX_DIGITAL_PGA2, TXDPGAx_SHIFT, 0x0f, 0),
+ /* Volume Control of Side Tone and Analog Loop */
+ SOC_SINGLE("ST-PGA1 Gain", SIDETONE1_PGA, STx_PGA_SHIFT, 0x0a, 0),
+ SOC_SINGLE("ST-PGA2 Gain", SIDETONE2_PGA, STx_PGA_SHIFT, 0x0a, 0),
+ SOC_SINGLE("APGA1 Gain", ANALOG_LOOP_PGA1, APGAx_GAIN_SHIFT, 0x1d, 0),
+ SOC_SINGLE("APGA2 Gain", ANALOG_LOOP_PGA2, APGAx_GAIN_SHIFT, 0x1d, 0),
+ /* RX Properties */
+ SOC_ENUM("DAC1 Power Mode", soc_enum_dac1_power_mode),
+ SOC_ENUM("DAC2 Power Mode", soc_enum_dac2_power_mode),
+ SOC_ENUM("DAC3 Power Mode", soc_enum_dac3_power_mode),
+ SOC_ENUM("EAR Power Mode", soc_enum_ear_power_mode),
+ SOC_ENUM("AUXO Power Mode", soc_enum_auxo_power_mode),
+ SOC_ENUM("LINE1 Inversion", soc_enum_line1_inversion),
+ SOC_ENUM("LINE2 Inversion", soc_enum_line2_inversion),
+ SOC_ENUM("AUXO1 Inversion", soc_enum_auxo1_inversion),
+ SOC_ENUM("AUXO2 Inversion", soc_enum_auxo2_inversion),
+ SOC_ENUM("AUXO1 Pulldown Resistor", soc_enum_auxo1_pulldown_resistor),
+ SOC_ENUM("AUXO2 Pulldown Resistor", soc_enum_auxo2_pulldown_resistor),
+ /* TX Properties */
+ SOC_SINGLE("MIC1 VMID", MIC1_VMID_SELECT, 0, 0xff, 0),
+ SOC_SINGLE("MIC2 VMID", MIC2_VMID_SELECT, 0, 0xff, 0),
+ SOC_ENUM("MBIAS1 HiZ Option", soc_enum_mbias1_hiz_option),
+ SOC_ENUM("MBIAS2 HiZ Option", soc_enum_mbias2_hiz_option),
+ SOC_ENUM("MBIAS2 Output Voltage", soc_enum_mbias2_output_voltage),
+ SOC_ENUM("MBIAS2 Internal Resistor", soc_enum_mbias2_internal_resistor),
+ SOC_ENUM("MIC1 Input Impedance", soc_enum_mic1_input_impedance),
+ SOC_ENUM("MIC2 Input Impedance", soc_enum_mic2_input_impedance),
+ SOC_ENUM("TX1 HP Filter", soc_enum_tx1_hp_filter),
+ SOC_ENUM("TX2 HP Filter", soc_enum_tx2_hp_filter),
+ /* Side Tone and Analog Loop Properties */
+ SOC_ENUM("ST1 HP Filter", soc_enum_st1_hp_filter),
+ SOC_ENUM("ST2 HP Filter", soc_enum_st2_hp_filter),
+ /* I2S Interface Properties */
+ SOC_ENUM("I2S0 Word Length", soc_enum_i2s0_word_length),
+ SOC_ENUM("I2S1 Word Length", soc_enum_i2s1_word_length),
+ SOC_ENUM("I2S0 Mode", soc_enum_i2s0_mode),
+ SOC_ENUM("I2S1 Mode", soc_enum_i2s1_mode),
+ SOC_ENUM("I2S0 tri-state", soc_enum_i2s0_tristate),
+ SOC_ENUM("I2S1 tri-state", soc_enum_i2s1_tristate),
+ SOC_ENUM("I2S0 Pulldown Resistor", soc_enum_i2s0_pulldown_resistor),
+ SOC_ENUM("I2S1 Pulldown Resistor", soc_enum_i2s1_pulldown_resistor),
+ SOC_ENUM("I2S0 Sample Rate", soc_enum_i2s0_sample_rate),
+ SOC_ENUM("I2S1 Sample Rate", soc_enum_i2s1_sample_rate),
+ SOC_SINGLE("Interface Loop", INTERFACE_LOOP, 0, 0x0f, 0),
+ SOC_SINGLE("Interface Swap", INTERFACE_SWAP, 0, 0x1f, 0),
+ /* Miscellaneous */
+ SOC_SINGLE("Negative Charge Pump", NEGATIVE_CHARGE_PUMP, 0, 0x03, 0)
+};
+
+/* count the number of 1 */
+#define count_ones(x) ({ \
+ int num; \
+ for (num = 0; x; (x) &= (x) - 1, num++) \
+ ; \
+ num; \
+ })
+
+enum enum_power {
+ POWER_OFF = 0,
+ POWER_ON = 1
+};
+
+enum enum_link {
+ UNLINK = 0,
+ LINK = 1
+};
+
+static enum enum_power get_widget_power_status(enum enum_widget widget)
+{
+ u8 val;
+
+ if (widget >= number_of_widgets)
+ return POWER_OFF;
+ val = read_reg(widget_pm_array[widget].reg);
+ if (val & (1 << widget_pm_array[widget].shift))
+ return POWER_ON;
+ else
+ return POWER_OFF;
+}
+
+static int count_powered_neighbors(const unsigned long *neighbors)
+{
+ unsigned long i;
+ int n = 0;
+ for_each_set_bit(i, neighbors, number_of_widgets) {
+ if (get_widget_power_status(i) == POWER_ON)
+ n++;
+ }
+ return n;
+}
+
+static int has_powered_neighbors(const unsigned long *neighbors)
+{
+ unsigned int i;
+ for_each_set_bit(i, neighbors, number_of_widgets) {
+ if (get_widget_power_status(i) == POWER_ON)
+ return 1;
+ }
+ return 0;
+}
+
+
+static int has_stacked_neighbors(const unsigned long *neighbors)
+{
+ unsigned long *stack_map = pm_stack_as_bitmap;
+ return bitmap_intersects(stack_map, neighbors, number_of_widgets);
+}
+
+static void power_widget_unlocked(enum enum_power onoff,
+ enum enum_widget widget)
+{
+ enum enum_widget w;
+ int done;
+
+ if (widget >= number_of_widgets)
+ return;
+ if (get_widget_power_status(widget) == onoff)
+ return;
+
+ for (w = widget, done = 0; !done;) {
+ unsigned long i;
+ unsigned long *srcs = widget_pm_array[w].source_list;
+ unsigned long *sinks = widget_pm_array[w].sink_list;
+ dev_dbg(ab3550_dev, "%s: processing widget %s.\n",
+ __func__, widget_names[w]);
+
+ if (onoff == POWER_ON &&
+ !bitmap_empty(srcs, number_of_widgets) &&
+ !has_powered_neighbors(srcs)) {
+ pm_stack.stack[pm_stack.p++] = w;
+ for_each_set_bit(i, srcs, number_of_widgets) {
+ pm_stack.stack[pm_stack.p++] = i;
+ }
+ w = pm_stack.stack[--pm_stack.p];
+ continue;
+ } else if (onoff == POWER_OFF &&
+ has_powered_neighbors(sinks)) {
+ int n = 0;
+ pm_stack.stack[pm_stack.p++] = w;
+ for_each_set_bit(i, sinks, number_of_widgets) {
+ if (count_powered_neighbors(
+ widget_pm_array[i].source_list)
+ == 1 &&
+ get_widget_power_status(i) == POWER_ON) {
+ pm_stack.stack[pm_stack.p++] = i;
+ n++;
+ }
+ }
+ if (n) {
+ w = pm_stack.stack[--pm_stack.p];
+ continue;
+ } else
+ --pm_stack.p;
+ }
+ mask_set_reg(widget_pm_array[w].reg,
+ 1 << widget_pm_array[w].shift,
+ onoff == POWER_ON ? 0xff : 0);
+ dev_dbg(ab3550_dev, "%s: widget %s powered %s.\n",
+ __func__, widget_names[w],
+ onoff == POWER_ON ? "on" : "off");
+
+ if (onoff == POWER_ON &&
+ !bitmap_empty(sinks, number_of_widgets) &&
+ !has_powered_neighbors(sinks) &&
+ !has_stacked_neighbors(sinks)) {
+ for_each_set_bit(i, sinks, number_of_widgets) {
+ pm_stack.stack[pm_stack.p++] = i;
+ }
+ w = pm_stack.stack[--pm_stack.p];
+ continue;
+ } else if (onoff == POWER_OFF) {
+ for_each_set_bit(i, srcs, number_of_widgets) {
+ if (!has_powered_neighbors(
+ widget_pm_array[i].sink_list)
+ && get_widget_power_status(i) == POWER_ON
+ && !test_bit(i, pm_stack_as_bitmap)) {
+ pm_stack.stack[pm_stack.p++] = i;
+ }
+ }
+ }
+ if (pm_stack.p > 0)
+ w = pm_stack.stack[--pm_stack.p];
+ else
+ done = 1;
+ }
+}
+
+static void power_widget_locked(enum enum_power onoff,
+ enum enum_widget widget)
+{
+ if (mutex_lock_interruptible(&ab3550_pm_mutex)) {
+ dev_warn(ab3550_dev,
+ "%s: Signal received while waiting on the PM mutex.\n",
+ __func__);
+ return;
+ }
+ power_widget_unlocked(onoff, widget);
+ mutex_unlock(&ab3550_pm_mutex);
+}
+
+static void dump_registers(const char *where, ...)
+{
+ va_list ap;
+ va_start(ap, where);
+ do {
+ short reg = va_arg(ap, int);
+ if (reg < 0)
+ break;
+ dev_dbg(ab3550_dev, "%s from %s> 0x%02X : 0x%02X.\n",
+ __func__, where, reg, read_reg(reg));
+ } while (1);
+ va_end(ap);
+}
+
+/**
+ * update the link between two widgets.
+ * @op: 1 - connect; 0 - disconnect
+ * @src: source of the connection
+ * @sink: sink of the connection
+ */
+static int update_widgets_link(enum enum_link op, enum enum_widget src,
+ enum enum_widget sink,
+ u8 reg, u8 mask, u8 newval)
+{
+ int ret = 0;
+
+ if (mutex_lock_interruptible(&ab3550_pm_mutex)) {
+ dev_warn(ab3550_dev, "%s: A signal is received while waiting on"
+ " the PM mutex.\n", __func__);
+ return -EINTR;
+ }
+
+ switch (op << 2 | test_bit(sink, widget_pm_array[src].sink_list) << 1 |
+ test_bit(src, widget_pm_array[sink].source_list)) {
+ case 3: /* UNLINK, sink in sink_list, src in source_list */
+ case 4: /* LINK, sink not in sink_list, src not in source_list */
+ break;
+ default:
+ ret = -EINVAL;
+ goto end;
+ }
+ switch (((int)op) << 2 | get_widget_power_status(src) << 1 |
+ get_widget_power_status(sink)) {
+ case 3: /* op = 0, src on, sink on */
+ if (count_powered_neighbors(widget_pm_array[sink].source_list)
+ == 1)
+ power_widget_unlocked(POWER_OFF, sink);
+ mask_set_reg(reg, mask, newval);
+ break;
+ case 6: /* op = 1, src on, sink off */
+ mask_set_reg(reg, mask, newval);
+ power_widget_unlocked(POWER_ON, sink);
+ break;
+ default:
+ /* op = 0, src off, sink off */
+ /* op = 0, src off, sink on */
+ /* op = 0, src on, sink off */
+ /* op = 1, src off, sink off */
+ /* op = 1, src off, sink on */
+ /* op = 1, src on, sink on */
+ mask_set_reg(reg, mask, newval);
+ }
+ change_bit(sink, widget_pm_array[src].sink_list);
+ change_bit(src, widget_pm_array[sink].source_list);
+end:
+ mutex_unlock(&ab3550_pm_mutex);
+ return ret;
+};
+
+static enum enum_widget apga_source_translate(u8 reg_value)
+{
+ switch (reg_value) {
+ case 1:
+ return widget_mic1;
+ case 2:
+ return widget_mic2;
+ default:
+ return number_of_widgets;
+ }
+}
+
+static enum enum_widget adder_sink_translate(u8 reg)
+{
+ switch (reg) {
+ case EAR_ADDER:
+ return widget_ear;
+ case AUXO1_ADDER:
+ return widget_auxo1;
+ case AUXO2_ADDER:
+ return widget_auxo2;
+ case SPKR_ADDER:
+ return widget_spkr;
+ case LINE1_ADDER:
+ return widget_line1;
+ case LINE2_ADDER:
+ return widget_line2;
+ case APGA1_ADDER:
+ return widget_apga1;
+ case APGA2_ADDER:
+ return widget_apga2;
+ default:
+ return number_of_widgets;
+ }
+}
+
+static int ab3550_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(&codec->dapm, ab3550_dapm_widgets,
+ ARRAY_SIZE(ab3550_dapm_widgets));
+
+ snd_soc_dapm_add_routes(&codec->dapm, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(&codec->dapm);
+ return 0;
+}
+
+static void power_for_playback(enum enum_power onoff, int ifsel)
+{
+ dev_dbg(ab3550_dev, "%s: interface %d power %s.\n", __func__,
+ ifsel, onoff == POWER_ON ? "on" : "off");
+
+ if (mutex_lock_interruptible(&ab3550_pm_mutex)) {
+ dev_warn(ab3550_dev,
+ "%s: Signal received while waiting on the PM mutex.\n",
+ __func__);
+ return;
+ }
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_dld_l : widget_if1_dld_l);
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_dld_r : widget_if1_dld_r);
+ mutex_unlock(&ab3550_pm_mutex);
+}
+
+static void power_for_capture(enum enum_power onoff, int ifsel)
+{
+ dev_dbg(ab3550_dev, "%s: interface %d power %s", __func__,
+ ifsel, onoff == POWER_ON ? "on" : "off");
+ if (mutex_lock_interruptible(&ab3550_pm_mutex)) {
+ dev_warn(ab3550_dev,
+ "%s: Signal received while waiting on the PM mutex.\n",
+ __func__);
+ return;
+ }
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_uld_l : widget_if1_uld_l);
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_uld_r : widget_if1_uld_r);
+ mutex_unlock(&ab3550_pm_mutex);
+}
+
+static int ab3550_add_controls(struct snd_soc_codec *codec)
+{
+ int err = 0, i, n = ARRAY_SIZE(ab3550_snd_controls);
+
+ pr_debug("%s: %s called.\n", __FILE__, __func__);
+ for (i = 0; i < n; i++) {
+ err = snd_ctl_add(codec->card->snd_card, snd_ctl_new1(
+ &ab3550_snd_controls[i], codec));
+ if (err < 0) {
+ pr_err("%s failed to add control No.%d of %d.\n",
+ __func__, i, n);
+ return err;
+ }
+ }
+ return err;
+}
+
+static int ab3550_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params,
+ struct snd_soc_dai *dai)
+{
+ u8 val;
+ u8 reg = dai->id == 0 ? INTERFACE0 : INTERFACE1;
+
+ if (!ab3550_dev) {
+ pr_err("%s: The AB3550 codec driver not initialized.\n",
+ __func__);
+ return -EAGAIN;
+ }
+ dev_info(ab3550_dev, "%s called.\n", __func__);
+ switch (params_rate(hw_params)) {
+ case 8000:
+ val = I2Sx_SR_8000Hz;
+ break;
+ case 16000:
+ val = I2Sx_SR_16000Hz;
+ break;
+ case 44100:
+ val = I2Sx_SR_44100Hz;
+ break;
+ case 48000:
+ val = I2Sx_SR_48000Hz;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ !dai->capture_active : !dai->playback_active) {
+
+ mask_set_reg(reg, I2Sx_SR_MASK, val << I2Sx_SR_SHIFT);
+ if ((read_reg(reg) & I2Sx_MODE_MASK) == 0) {
+ mask_set_reg(reg, MASTER_GENx_PWR_MASK,
+ 1 << MASTER_GENx_PWR_SHIFT);
+ }
+ }
+ return 0;
+}
+
+static int ab3550_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ dai->playback_active : dai->capture_active) {
+
+ dev_err(ab3550_dev, "%s: A %s stream is already active.\n",
+ __func__,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "PLAYBACK" : "CAPTURE");
+ return -EBUSY;
+ }
+ return 0;
+}
+static int ab3550_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ dev_info(ab3550_dev, "%s called.\n", __func__);
+
+ /* Configure registers for either playback or capture */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ power_for_playback(POWER_ON, dai->id);
+ dump_registers(__func__,
+ dai->id == 0 ? INTERFACE0 : INTERFACE1,
+ RX1, RX2, SPKR, EAR, -1);
+ } else {
+ power_for_capture(POWER_ON, dai->id);
+ dump_registers(__func__, MIC_BIAS1, MIC_BIAS2, MIC1_GAIN, TX1,
+ dai->id == 0 ? INTERFACE0 : INTERFACE1, -1);
+ }
+ return 0;
+}
+
+static void ab3550_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ u8 iface = dai->id == 0 ? INTERFACE0 : INTERFACE1;
+ dev_info(ab3550_dev, "%s called.\n", __func__);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ power_for_playback(POWER_OFF, dai->id);
+ else
+ power_for_capture(POWER_OFF, dai->id);
+ if (!dai->playback_active && !dai->capture_active &&
+ (read_reg(iface) & I2Sx_MODE_MASK) == 0)
+ mask_set_reg(iface, MASTER_GENx_PWR_MASK, 0);
+}
+
+static int ab3550_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ return 0;
+}
+
+static int ab3550_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ u8 iface = (codec_dai->id == 0) ? INTERFACE0 : INTERFACE1;
+ u8 val = 0;
+ dev_info(ab3550_dev, "%s called.\n", __func__);
+
+ switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
+ SND_SOC_DAIFMT_MASTER_MASK)) {
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+ val |= 1 << I2Sx_MODE_SHIFT;
+ break;
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
+ break;
+
+ default:
+ dev_warn(ab3550_dev, "AB3550_dai: unsupported DAI format "
+ "0x%x\n", fmt);
+ return -EINVAL;
+ }
+ if (codec_dai->playback_active && codec_dai->capture_active) {
+ if ((read_reg(iface) & I2Sx_MODE_MASK) == val)
+ return 0;
+ else {
+ dev_err(ab3550_dev,
+ "%s: DAI format set differently "
+ "by an existing stream.\n", __func__);
+ return -EINVAL;
+ }
+ }
+ mask_set_reg(iface, I2Sx_MODE_MASK, val);
+ return 0;
+}
+
+struct snd_soc_dai_driver ab3550_dai_drv[] = {
+ {
+ .name = "ab3550-codec-dai.0",
+ .id = 0,
+ .playback = {
+ .stream_name = "AB3550.0 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB3550_SUPPORTED_RATE,
+ .formats = AB3550_SUPPORTED_FMT,
+ },
+ .capture = {
+ .stream_name = "AB3550.0 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB3550_SUPPORTED_RATE,
+ .formats = AB3550_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .startup = ab3550_pcm_startup,
+ .prepare = ab3550_pcm_prepare,
+ .hw_params = ab3550_pcm_hw_params,
+ .shutdown = ab3550_pcm_shutdown,
+ .set_sysclk = ab3550_set_dai_sysclk,
+ .set_fmt = ab3550_set_dai_fmt,
+ }
+ },
+ .symmetric_rates = 1,
+ },
+ {
+ .name = "ab3550-codec-dai.1",
+ .id = 1,
+ .playback = {
+ .stream_name = "AB3550.1 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB3550_SUPPORTED_RATE,
+ .formats = AB3550_SUPPORTED_FMT,
+ },
+ .capture = {
+ .stream_name = "AB3550.0 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB3550_SUPPORTED_RATE,
+ .formats = AB3550_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .startup = ab3550_pcm_startup,
+ .prepare = ab3550_pcm_prepare,
+ .hw_params = ab3550_pcm_hw_params,
+ .shutdown = ab3550_pcm_shutdown,
+ .set_sysclk = ab3550_set_dai_sysclk,
+ .set_fmt = ab3550_set_dai_fmt,
+ }
+ },
+ .symmetric_rates = 1,
+ }
+};
+EXPORT_SYMBOL_GPL(ab3550_dai_drv);
+
+static int ab3550_codec_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+
+ pr_info("%s: Enter.\n", __func__);
+
+ /* Add controls */
+ if (ab3550_add_controls(codec) < 0)
+ return ret;
+
+ /* Add widgets */
+ ab3550_add_widgets(codec);
+
+ return 0;
+}
+
+static int ab3550_codec_remove(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_free(&codec->dapm);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ab3550_codec_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0);
+
+ return 0;
+}
+
+static int ab3550_codec_resume(struct snd_soc_codec *codec)
+{
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0xff);
+
+ return 0;
+}
+#else
+#define ab3550_codec_resume NULL
+#define ab3550_codec_suspend NULL
+#endif
+
+/*
+ * This function is only called by the SOC framework to
+ * set registers associated to the mixer controls.
+ */
+static int ab3550_codec_write_reg(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ if (reg < MIC_BIAS1 || reg > INTERFACE_SWAP)
+ return -EINVAL;
+ switch (reg) {
+ u8 diff, oldval;
+ case ANALOG_LOOP_PGA1:
+ case ANALOG_LOOP_PGA2: {
+ enum enum_widget apga = reg == ANALOG_LOOP_PGA1 ?
+ widget_apga1 : widget_apga2;
+
+ oldval = read_reg(reg);
+ diff = value ^ oldval;
+
+ /* The APGA is to be turned on/off.
+ * The power bit and the other bits in the
+ * same register won't be changed at the same time
+ * since they belong to different controls.
+ */
+ if (diff & (1 << APGAx_PWR_SHIFT)) {
+ power_widget_locked(value >> APGAx_PWR_SHIFT & 1,
+ apga);
+ } else if (diff & APGAx_MUX_MASK) {
+ enum enum_widget old_source =
+ apga_source_translate(oldval);
+ enum enum_widget new_source =
+ apga_source_translate(value);
+ update_widgets_link(UNLINK, old_source, apga,
+ reg, APGAx_MUX_MASK, 0);
+ update_widgets_link(LINK, new_source, apga,
+ reg, APGAx_MUX_MASK, value);
+ } else {
+ set_reg(reg, value);
+ }
+ break;
+ }
+
+ case APGA1_ADDER:
+ case APGA2_ADDER: {
+ int i;
+ enum enum_widget apga;
+ enum enum_widget apga_dst[] = {
+ widget_auxo2, widget_auxo1, widget_ear, widget_spkr,
+ widget_line2, widget_line1
+ };
+
+ apga = adder_sink_translate(reg);
+ oldval = read_reg(reg);
+ diff = value ^ oldval;
+ for (i = 0; diff; i++) {
+ if (!(diff & 1 << i))
+ continue;
+ diff ^= 1 << i;
+ update_widgets_link(value >> i & 1, apga, apga_dst[i],
+ reg, 1 << i, value);
+ }
+ break;
+ }
+
+ case EAR_ADDER:
+ case AUXO1_ADDER:
+ case AUXO2_ADDER:
+ case SPKR_ADDER:
+ case LINE1_ADDER:
+ case LINE2_ADDER: {
+ int i;
+ enum enum_widget widgets[] = {
+ widget_dac1, widget_dac2, widget_dac3,
+ };
+ oldval = read_reg(reg);
+ diff = value ^ oldval;
+ for (i = 0; diff; i++) {
+ if (!(diff & 1 << i))
+ continue;
+ diff ^= 1 << i;
+ update_widgets_link(value >> i & 1, widgets[i],
+ adder_sink_translate(reg),
+ reg, 1 << i, value);
+ }
+ break;
+ }
+
+ default:
+ set_reg(reg, value);
+ }
+ return 0;
+}
+
+static unsigned int ab3550_codec_read_reg(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return read_reg(reg);
+}
+
+static struct snd_soc_codec_driver ab3550_codec_drv = {
+ .probe = ab3550_codec_probe,
+ .remove = ab3550_codec_remove,
+ .suspend = ab3550_codec_suspend,
+ .resume = ab3550_codec_resume,
+ .read = ab3550_codec_read_reg,
+ .write = ab3550_codec_write_reg,
+};
+EXPORT_SYMBOL_GPL(ab3550_codec_drv);
+
+static inline void init_playback_route(void)
+{
+ update_widgets_link(LINK, widget_if0_dld_l, widget_rx1, 0, 0, 0);
+ update_widgets_link(LINK, widget_rx1, widget_dac1, 0, 0, 0);
+ update_widgets_link(LINK, widget_dac1, widget_spkr,
+ SPKR_ADDER, DAC1_TO_ADDER_MASK, 0xff);
+
+ update_widgets_link(LINK, widget_if0_dld_r, widget_rx2,
+ RX2, RX2_IF_SELECT_MASK, 0);
+ update_widgets_link(LINK, widget_rx2, widget_dac2, 0, 0, 0);
+ update_widgets_link(LINK, widget_dac2, widget_ear,
+ EAR_ADDER, DAC2_TO_ADDER_MASK, 0xff);
+}
+
+static inline void init_capture_route(void)
+{
+ update_widgets_link(LINK, widget_micbias2, widget_mic1p1,
+ 0, 0, 0);
+ update_widgets_link(LINK, widget_micbias2, widget_mic1n1,
+ 0, 0, 0);
+ update_widgets_link(LINK, widget_mic1p1, widget_mic1,
+ MIC1_INPUT_SELECT, MICxP1_SEL_MASK, 0xff);
+ update_widgets_link(LINK, widget_mic1n1, widget_mic1,
+ MIC1_INPUT_SELECT, MICxN1_SEL_MASK, 0xff);
+ update_widgets_link(LINK, widget_mic1, widget_adc1,
+ 0, 0, 0);
+ update_widgets_link(LINK, widget_adc1, widget_tx1,
+ 0, 0, 0);
+ update_widgets_link(LINK, widget_tx1, widget_if0_uld_l,
+ INTERFACE0_DATA, I2Sx_L_DATA_MASK,
+ I2Sx_L_DATA_TX1_MASK);
+ update_widgets_link(LINK, widget_tx1, widget_if0_uld_r,
+ INTERFACE0_DATA, I2Sx_R_DATA_MASK,
+ I2Sx_R_DATA_TX1_MASK);
+}
+
+static inline void init_playback_gain(void)
+{
+ mask_set_reg(RX1_DIGITAL_PGA, RXx_PGA_GAIN_MASK,
+ 0x40 << RXx_PGA_GAIN_SHIFT);
+ mask_set_reg(RX2_DIGITAL_PGA, RXx_PGA_GAIN_MASK,
+ 0x40 << RXx_PGA_GAIN_SHIFT);
+ mask_set_reg(EAR, EAR_GAIN_MASK, 0x06 << EAR_GAIN_SHIFT);
+ mask_set_reg(SPKR, SPKR_GAIN_MASK, 0x6 << SPKR_GAIN_SHIFT);
+}
+
+static inline void init_capture_gain(void)
+{
+ mask_set_reg(MIC1_GAIN, MICx_GAIN_MASK, 0x06 << MICx_GAIN_SHIFT);
+ mask_set_reg(TX_DIGITAL_PGA1, TXDPGAx_MASK, 0x0f << TXDPGAx_SHIFT);
+}
+
+static __devinit int ab3550_codec_drv_probe(struct platform_device *pdev)
+{
+ struct ab3550_codec_dai_data *codec_drvdata;
+ int ret = 0;
+ u8 reg;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ pr_info("%s: Init codec private data.\n", __func__);
+ codec_drvdata = kzalloc(sizeof(struct ab3550_codec_dai_data), GFP_KERNEL);
+ if (codec_drvdata == NULL)
+ return -ENOMEM;
+
+ /* TODO: Add private data to codec_drvdata */
+
+ platform_set_drvdata(pdev, codec_drvdata);
+
+ pr_info("%s: Register codec.\n", __func__);
+ ret = snd_soc_register_codec(&pdev->dev, &ab3550_codec_drv, &ab3550_dai_drv[0], 2);
+ if (ret < 0) {
+ pr_debug("%s: Error: Failed to register codec (ret = %d).\n",
+ __func__,
+ ret);
+ snd_soc_unregister_codec(&pdev->dev);
+ kfree(platform_get_drvdata(pdev));
+ return ret;
+ }
+
+ ab3550_dev = &pdev->dev;
+ /* Initialize the codec registers */
+ for (reg = AB3550_FIRST_REG; reg <= AB3550_LAST_REG; reg++)
+ set_reg(reg, 0);
+
+ mask_set_reg(CLOCK, CLOCK_REF_SELECT_MASK | CLOCK_ENABLE_MASK,
+ 1 << CLOCK_REF_SELECT_SHIFT | 1 << CLOCK_ENABLE_SHIFT);
+ init_playback_route();
+ init_playback_gain();
+ init_capture_route();
+ init_capture_gain();
+ memset(&pm_stack, 0, sizeof(pm_stack));
+
+ return 0;
+}
+
+static int __devexit ab3550_codec_drv_remove(struct platform_device *pdev)
+{
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0);
+
+ ab3550_dev = NULL;
+
+ snd_soc_unregister_codec(&pdev->dev);
+ kfree(platform_get_drvdata(pdev));
+
+ return 0;
+}
+
+static int ab3550_codec_drv_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ return 0;
+}
+
+static int ab3550_codec_drv_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver ab3550_codec_platform_drv = {
+ .driver = {
+ .name = "ab3550-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ab3550_codec_drv_probe,
+ .remove = __devexit_p(ab3550_codec_drv_remove),
+ .suspend = ab3550_codec_drv_suspend,
+ .resume = ab3550_codec_drv_resume,
+};
+
+
+static int __devinit ab3550_codec_platform_drv_init(void)
+{
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ ab3550_dev = NULL;
+
+ ret = platform_driver_register(&ab3550_codec_platform_drv);
+ if (ret != 0)
+ pr_err("Failed to register AB3550 platform driver (%d)!\n", ret);
+
+ return ret;
+}
+
+static void __exit ab3550_codec_platform_drv_exit(void)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ platform_driver_unregister(&ab3550_codec_platform_drv);
+}
+
+
+module_init(ab3550_codec_platform_drv_init);
+module_exit(ab3550_codec_platform_drv_exit);
+
+MODULE_DESCRIPTION("AB3550 Codec driver");
+MODULE_AUTHOR("Xie Xiaolei <xie.xiaolei@stericsson.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/ab3550.h b/sound/soc/codecs/ab3550.h
new file mode 100644
index 00000000000..fe9c77b1a62
--- /dev/null
+++ b/sound/soc/codecs/ab3550.h
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Xie Xiaolei <xie.xiaolei@etericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+#ifndef AB3550_CODEC_REGISTERS_H
+#define AB3550_CODEC_REGISTERS_H
+
+#define AB3550_SUPPORTED_RATE (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define AB3550_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+/* MIC BIAS */
+
+#define MIC_BIAS1 0X31
+#define MIC_BIAS2 0X32
+#define MBIAS2_OUT_V_MASK 0x04
+#define MBIAS2_OUT_V_SHIFT 2
+#define MBIAS_PWR_MASK 0x02
+#define MBIAS_PWR_SHIFT 1
+#define MBIAS_PDN_IMP_MASK 0x01
+#define MBIAS_PDN_IMP_SHIFT 0
+
+#define MIC_BIAS2_VAD 0x33
+#define MBIAS2_R_INT_MASK 0x01
+#define MBIAS2_R_INT_SHIFT 0
+
+/* MIC */
+#define MIC1_GAIN 0x34
+#define MIC2_GAIN 0x35
+#define MICx_GAIN_MASK 0xF0
+#define MICx_GAIN_SHIFT 4
+#define MICx_IN_IMP_MASK 0x0C
+#define MICx_IN_IMP_SHIFT 2
+#define MICx_PWR_MASK 0x01
+#define MICx_PWR_SHIFT 0
+
+#define MIC1_INPUT_SELECT 0x36
+#define MIC2_INPUT_SELECT 0x37
+#define MICxP1_SEL_MASK 0x80
+#define MICxP1_SEL_SHIFT 7
+#define MICxN1_SEL_MASK 0x40
+#define MICxN1_SEL_SHIFT 6
+#define MICxP2_SEL_MASK 0x20
+#define MICxP2_SEL_SHIFT 5
+#define MICxN2_SEL_MASK 0x10
+#define MICxN2_SEL_SHIFT 4
+#define LINEIN_SEL_MASK 0x03
+#define LINEIN_SEL_SHIFT 0
+
+#define MIC1_VMID_SELECT 0x38
+#define MIC2_VMID_SELECT 0x39
+#define VMIDx_ENABLE_MASK 0xC0
+#define VMIDx_ENABLE_SHIFT 6
+#define VMIDx_LINEIN1_N_MASK 0x20
+#define VMIDx_LINEIN1_N_SHIFT 5
+#define VMIDx_LINEIN2_N_MASK 0x10
+#define VMIDx_LINEIN2_N_SHIFT 4
+#define VMIDx_MICxP1_MASK 0x08
+#define VMIDx_MICxP1_SHIFT 3
+#define VMIDx_MICxP2_MASK 0x04
+#define VMIDx_MICxP2_SHIFT 2
+#define VMIDx_MICxN1_MASK 0x02
+#define VMIDx_MICxN1_SHIFT 1
+#define VMIDx_MICxN2_MASK 0x01
+#define VMIDx_MICxN2_SHIFT 0
+
+#define MIC2_TO_MIC1 0x3A
+#define MIC2_TO_MIC1_MASK 0x03
+#define MIC2_TO_MIC1_SHIFT 0
+
+/* Analog Loop */
+#define ANALOG_LOOP_PGA1 0x3B
+#define ANALOG_LOOP_PGA2 0x3C
+#define APGAx_GAIN_MASK 0xF8
+#define APGAx_GAIN_SHIFT 3
+#define APGAx_PWR_MASK 0x04
+#define APGAx_PWR_SHIFT 2
+#define APGAx_MUX_MASK 0x03
+#define APGAx_MUX_SHIFT 0
+#define APGAx_MUX_MIC1_MASK 0x01
+#define APGAx_MUX_MIC1_SHIFT 0
+#define APGAx_MUX_MIC2_MASK 0x02
+#define APGAx_MUX_MIC2_SHIFT 1
+
+
+#define APGA_VMID_SELECT 0x3D
+#define VMID_APGA1_ENABLE_MASK 0xC0
+#define VMID_APGA1_ENABLE_SHIFT 6
+#define VMID_APGA1_LINEIN1_MASK 0x20
+#define VMID_APGA1_LINEIN1_SHIFT 5
+#define VMID_APGA2_ENABLE_MASK 0x0C
+#define VMID_APGA2_ENABLE_SHIFT 2
+#define VMID_APGA2_LINEIN2_MASK 0x02
+#define VMID_APGA2_LINEIN2_SHIFT 1
+
+/* Output Amplifiers */
+#define EAR 0x3E
+#define EAR_PWR_MODE_MASK 0x20
+#define EAR_PWR_MODE_SHIFT 5
+#define EAR_PWR_MASK 0x10
+#define EAR_PWR_SHIFT 4
+#define EAR_GAIN_MASK 0x0F
+#define EAR_GAIN_SHIFT 0
+
+#define AUXO1 0x3F
+#define AUXO2 0x40
+#define AUXOx_PWR_MASK 0x80
+#define AUXOx_PWR_SHIFT 7
+#define AUXOx_INV_MASK 0x40
+#define AUXOx_INV_SHIFT 6
+#define AUXOx_PULLDOWN_MASK 0x20
+#define AUXOx_PULLDOWN_SHIFT 5
+#define AUXOx_GAIN_MASK 0x0F
+#define AUXOx_GAIN_SHIFT 0
+
+#define AUXO_PWR_MODE 0x41
+#define AUT_PWR_MODE_MASK 0x04
+#define AUT_PWR_MODE_SHIFT 2
+#define AUXO_PWR_MODE_MASK 0x03
+#define AUXO_PWR_MODE_SHIFT 0
+
+#define OFFSET_CANCEL 0x42
+#define SPKR_OFF_CANC_MASK 0x04
+#define SPKR_OFF_CANC_SHIFT 2
+#define AUXO_OFF_CANC_MASK 0x02
+#define AUXO_OFF_CANC_SHIFT 1
+#define OFFSET_CLOCK_MASK 0x01
+#define OFFSET_CLOCK_SHIFT 0
+
+#define SPKR 0x43
+#define OVR_CURR_PROT_MASK 0x80
+#define OVR_CURR_PROT_SHIFT 7
+#define SPKR_PWR_MASK 0x40
+#define SPKR_PWR_SHIFT 6
+#define SPKR_GAIN_MASK 0x1F
+#define SPKR_GAIN_SHIFT 0
+
+#define LINE1 0x44
+#define LINE2 0x45
+#define LINEx_PWR_MASK 0x80
+#define LINEx_PWR_SHIFT 7
+#define LINEx_INV_MASK 0x40
+#define LINEx_INV_SHIFT 6
+#define VMID_BUFFx_MASK 0x10
+#define VMID_BUFFx_SHIFT 4
+#define LINEx_GAIN_MASK 0x0F
+#define LINEx_GAIN_SHIFT 0
+
+/* Analog loop Routing */
+
+#define APGA1_ADDER 0x46
+#define APGA2_ADDER 0x47
+#define APGAx_TO_LINE1_MASK 0x20
+#define APGAx_TO_LINE1_SHIFT 0x5F
+#define APGAx_TO_LINE2_MASK 0x10
+#define APGAx_TO_LINE2_SHIFT 4
+#define APGAx_TO_SPKR_MASK 0x08
+#define APGAx_TO_SPKR_SHIFT 3
+#define APGAx_TO_EAR_MASK 0x04
+#define APGAx_TO_EAR_SHIFT 2
+#define APGAx_TO_AUXO1_MASK 0x02
+#define APGAx_TO_AUXO1_SHIFT 1
+#define APGAx_TO_AUXO2_MASK 0x01
+#define APGAx_TO_AUXO2_SHIFT 0
+#define APGAx_ADDER_VALID_BITS_MASK 0x3F
+
+/* Output Amplifiers Routing */
+
+#define EAR_ADDER 0x48
+#define AUXO1_ADDER 0x49
+#define AUXO2_ADDER 0x4A
+#define SPKR_ADDER 0x4B
+#define LINE1_ADDER 0x4C
+#define LINE2_ADDER 0x4D
+#define DAC3_TO_ADDER_MASK 0x04
+#define DAC3_TO_ADDER_SHIFT 2
+#define DAC2_TO_ADDER_MASK 0x02
+#define DAC2_TO_ADDER_SHIFT 1
+#define DAC1_TO_ADDER_MASK 0x01
+#define DAC1_TO_ADDER_SHIFT 0
+
+#define EAR_TO_MIC2 0x4E
+#define EAR_TO_MIC2_MASK 0x01
+#define EAR_TO_MIC2_SHIFT 0
+
+#define SPKR_TO_MIC2 0x4F
+#define SPKR_TO_MIC2_MASK 0x01
+#define SPKR_TO_MIC2_SHIFT 0
+
+#define NEGATIVE_CHARGE_PUMP 0x50
+#define NCP_MODE_MASK 0x02
+#define NCP_MODE_SHIFT 1
+#define NCP_PWR_MASK 0x01
+#define NCP_PWR_SHIFT 0
+
+#define TX1 0x51
+#define TX2 0x52
+#define TXx_HP_FILTER_MASK 0x0C
+#define TXx_HP_FILTER_SHIFT 2
+#define TXx_PWR_MASK 0x02
+#define TXx_PWR_SHIFT 1
+#define ADCx_PWR_MASK 0x01
+#define ADCx_PWR_SHIFT 0
+
+#define RX1 0x53
+#define RX2 0x54
+#define RX2_IF_SELECT_MASK 0x10
+#define RX2_IF_SELECT_SHIFT 4
+#define RX3 0x55
+#define RXx_PWR_MASK 0x08
+#define RXx_PWR_SHIFT 3
+#define DACx_PWR_MASK 0x04
+#define DACx_PWR_SHIFT 2
+#define DACx_PWR_MODE_MASK 0x03
+#define DACx_PWR_MODE_SHIFT 0
+
+#define TX_DIGITAL_PGA1 0X56
+#define TX_DIGITAL_PGA2 0X57
+#define TXDPGAx_MASK 0x0F
+#define TXDPGAx_SHIFT 0
+
+#define RX1_DIGITAL_PGA 0x58
+#define RX2_DIGITAL_PGA 0x59
+#define RX3_DIGITAL_PGA 0x5A
+#define RXx_PGA_GAIN_MASK 0x7F
+#define RXx_PGA_GAIN_SHIFT 0
+
+#define SIDETONE1_PGA 0x5B
+#define SIDETONE2_PGA 0x5C
+#define STx_HP_FILTER_MASK 0x60
+#define STx_HP_FILTER_SHIFT 5
+#define STx_MUX_MASK 0x10
+#define STx_MUX_SHIFT 4
+#define STx_PGA_MASK 0x0F
+#define STx_PGA_SHIFT 0
+
+/* clock */
+
+#define CLOCK 0x5D
+#define CLOCK_REF_SELECT_MASK 0x02
+#define CLOCK_REF_SELECT_SHIFT 1
+#define CLOCK_ENABLE_MASK 0x01
+#define CLOCK_ENABLE_SHIFT 0
+
+/* Interface */
+
+#define INTERFACE0 0x5E
+#define INTERFACE1 0x60
+#define I2Sx_WORDLENGTH_MASK 0x40
+#define I2Sx_WORDLENGTH_SHIFT 6
+#define MASTER_GENx_PWR_MASK 0x20
+#define MASTER_GENx_PWR_SHIFT 5
+#define I2Sx_MODE_MASK 0x10
+#define I2Sx_MODE_SHIFT 4
+#define I2Sx_TRISTATE_MASK 0x08
+#define I2Sx_TRISTATE_SHIFT 3
+#define I2Sx_PULLDOWN_MASK 0x04
+#define I2Sx_PULLDOWN_SHIFT 2
+#define I2Sx_SR_MASK 0x03
+#define I2Sx_SR_SHIFT 0
+#define I2Sx_SR_8000Hz 0
+#define I2Sx_SR_16000Hz 1
+#define I2Sx_SR_44100Hz 2
+#define I2Sx_SR_48000Hz 3
+
+#define INTERFACE0_DATA 0x5F
+#define INTERFACE1_DATA 0x61
+#define I2Sx_L_DATA_MASK 0x0C
+#define I2Sx_L_DATA_TX1_MASK 0x04
+#define I2Sx_L_DATA_TX2_MASK 0x08
+#define I2Sx_L_DATA_SHIFT 2
+#define I2Sx_R_DATA_MASK 0x03
+#define I2Sx_R_DATA_TX1_MASK 0x01
+#define I2Sx_R_DATA_TX2_MASK 0x02
+#define I2Sx_R_DATA_SHIFT 0
+
+#define INTERFACE_LOOP 0x62
+#define I2S0_INT_LOOP_MASK 0x08
+#define I2S0_INT_LOOP_SHIFT 3
+#define I2S0_EXT_LOOP_MASK 0x04
+#define I2S0_EXT_LOOP_SHIFT 2
+#define I2S1_INT_LOOP_MASK 0x02
+#define I2S1_INT_LOOP_SHIFT 1
+#define I2S1_EXT_LOOP_MASK 0x01
+#define I2S1_EXT_LOOP_SHIFT 0
+
+#define INTERFACE_SWAP 0x63
+#define RX_SWAP0_MASK 0x10
+#define RX_SWAP0_SHIFT 4
+#define RX_SWAP1_MASK 0x08
+#define RX_SWAP1_SHIFT 3
+#define IF_SWAP_MASK 0x04
+#define IF_SWAP_SHIFT 2
+#define IO_SWAP0_MASK 0x02
+#define IO_SWAP0_SHIFT 1
+#define IO_SWAP1_MASK 0x01
+#define IO_SWAP1_SHIFT 0
+
+#define AB3550_FIRST_REG MIC_BIAS1
+#define AB3550_LAST_REG INTERFACE_SWAP
+
+#define AB3550_VIRTUAL_REG1 (AB3550_LAST_REG + 1)
+#define IF0_DLD_L_PW_SHIFT 0
+#define IF0_DLD_R_PW_SHIFT 1
+#define IF0_ULD_L_PW_SHIFT 2
+#define IF0_ULD_R_PW_SHIFT 3
+#define IF1_DLD_L_PW_SHIFT 4
+#define IF1_DLD_R_PW_SHIFT 5
+#define IF1_ULD_L_PW_SHIFT 6
+#define IF1_ULD_R_PW_SHIFT 7
+
+#define AB3550_VIRTUAL_REG2 (AB3550_LAST_REG + 2)
+#define MIC1P1_PW_SHIFT 0
+#define MIC1N1_PW_SHIFT 1
+#define MIC1P2_PW_SHIFT 2
+#define MIC1N2_PW_SHIFT 3
+#define MIC2P1_PW_SHIFT 4
+#define MIC2N1_PW_SHIFT 5
+#define MIC2P2_PW_SHIFT 6
+#define MIC2N2_PW_SHIFT 7
+
+
+#endif
diff --git a/sound/soc/codecs/ab5500.c b/sound/soc/codecs/ab5500.c
new file mode 100755
index 00000000000..aff0ad51f6a
--- /dev/null
+++ b/sound/soc/codecs/ab5500.c
@@ -0,0 +1,1806 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Xie Xiaolei <xie.xiaolei@etericsson.com>,
+ * Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Roger Nilsson <roger.xr.nilsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab5500.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <asm/atomic.h>
+#include <linux/rwsem.h>
+#include <linux/mutex.h>
+#include <stdarg.h>
+#include "ab5500.h"
+
+/* No of digital interface on the Codec */
+#define NO_CODEC_DAI_IF 2
+
+/* codec private data */
+struct ab5500_codec_dai_data {
+ bool playback_active;
+ bool capture_active;
+
+};
+
+enum regulator_idx {
+ REGULATOR_DMIC,
+ REGULATOR_AMIC
+};
+
+static struct device *ab5500_dev;
+static struct regulator_bulk_data reg_info[2] = {
+ { .supply = "vdigmic" },
+ { .supply = "v-amic" }
+};
+
+static bool reg_enabled[2] = {
+ false,
+ false
+};
+
+static u8 virtual_regs[] = {
+ 0, 0, 0, 0, 0
+};
+
+static int ab5500_clk_request;
+static DEFINE_MUTEX(ab5500_clk_mutex);
+
+#define set_reg(reg, val) mask_set_reg((reg), 0xff, (val))
+
+static void mask_set_reg(u8 reg, u8 mask, u8 val)
+{
+ u8 newval = mask & val;
+ u8 oldval, diff;
+
+ if (!ab5500_dev) {
+ pr_err("%s: The AB5500 codec driver not initialized.\n",
+ __func__);
+ return;
+ }
+ /* Check if the reg value falls within the
+ * range of AB5500 real registers. If
+ * so, set the mask */
+ if (reg < AB5500_FIRST_REG)
+ return;
+ if (reg <= AB5500_LAST_REG) {
+ abx500_mask_and_set_register_interruptible(
+ ab5500_dev, AB5500_BANK_AUDIO_HEADSETUSB,
+ reg, mask, val);
+ return;
+ }
+ if (reg - AB5500_LAST_REG - 1 >= ARRAY_SIZE(virtual_regs))
+ return;
+
+ /* treatment of virtual registers follows */
+ /*Compute the difference between the new value and the old value.
+ *1.If there is no difference, do nothing.
+ *2.If the difference is in the PWR_SHIFT,
+ *set the PWR masks appropriately.
+ */
+ oldval = virtual_regs[reg - AB5500_LAST_REG - 1];
+ diff = (val ^ oldval) & mask;
+ if (!diff)
+ return;
+
+ switch (reg) {
+ case AB5500_VIRTUAL_REG3:
+ if ((diff & (1 << SPKR1_PWR_SHIFT))) {
+ if ((val & (1 << SPKR1_PWR_SHIFT)) == 0) {
+ /*
+ * If the new value has PWR_SHIFT
+ * disabled, set the
+ * PWR_MASK to 0
+ */
+ mask_set_reg(SPKR1, SPKRx_PWR_MASK, 0);
+ }
+ else {
+ /* Else, set the PWR_MASK values based on the old value. */
+ switch (oldval & SPKR1_MODE_MASK) {
+ case 0:
+ mask_set_reg(SPKR1, SPKRx_PWR_MASK,
+ SPKRx_PWR_VBR_VALUE);
+ break;
+ case 1:
+ mask_set_reg(SPKR1, SPKRx_PWR_MASK,
+ SPKRx_PWR_CLS_D_VALUE);
+ break;
+ case 2:
+ mask_set_reg(SPKR1, SPKRx_PWR_MASK,
+ SPKRx_PWR_CLS_AB_VALUE);
+ break;
+ }
+ }
+ }
+ if ((diff & (1 << SPKR2_PWR_SHIFT))) {
+ if ((val & (1 << SPKR2_PWR_SHIFT)) == 0) {
+ /*
+ * If the new value has PWR_SHIFT
+ * disabled, set the
+ * PWR_MASK to 0
+ */
+ mask_set_reg(SPKR2, SPKRx_PWR_MASK, 0);
+ }
+ else {
+ /* Else, set the PWR_MASK values based on the old value. */
+ switch (oldval & SPKR2_MODE_MASK) {
+ case 0:
+ mask_set_reg(SPKR2, SPKRx_PWR_MASK,
+ SPKRx_PWR_VBR_VALUE);
+ break;
+ case 1:
+ mask_set_reg(SPKR2, SPKRx_PWR_MASK,
+ SPKRx_PWR_CLS_D_VALUE);
+ break;
+ }
+ }
+ }
+
+ break;
+ case AB5500_VIRTUAL_REG4:
+ ;
+ /* configure PWMCTRL_SPKR1, PWMCTRL_SPKR2, etc. */
+ }
+ virtual_regs[reg - AB5500_LAST_REG - 1] &= ~mask;
+ virtual_regs[reg - AB5500_LAST_REG - 1] |= newval;
+}
+
+static u8 read_reg(u8 reg)
+{
+ if (!ab5500_dev) {
+ pr_err("%s: The AB5500 codec driver not initialized.\n",
+ __func__);
+ return 0;
+ }
+ /* Check if the reg value falls within the range of AB5500 real
+ * registers.If so, set the mask */
+ if (reg < AB5500_FIRST_REG)
+ return 0;
+ else if (reg <= AB5500_LAST_REG) {
+ u8 val;
+ abx500_get_register_interruptible(
+ ab5500_dev, AB5500_BANK_AUDIO_HEADSETUSB, reg, &val);
+ return val;
+ } else if (reg - AB5500_LAST_REG - 1 < ARRAY_SIZE(virtual_regs))
+ return virtual_regs[reg - AB5500_LAST_REG - 1];
+ dev_warn(ab5500_dev, "%s: out-of-scope reigster %u.\n",
+ __func__, reg);
+ return 0;
+}
+
+/* Components that can be powered up/down */
+enum enum_widget {
+ widget_ear = 0,
+ widget_auxo1,
+ widget_auxo2,
+ widget_auxo3,
+ widget_auxo4,
+ widget_spkr1,
+ widget_spkr2,
+ widget_spkr1_adder,
+ widget_spkr2_adder,
+ widget_pwm_spkr1,
+ widget_pwm_spkr2,
+ widget_pwm_spkr1n,
+ widget_pwm_spkr1p,
+ widget_pwm_spkr2n,
+ widget_pwm_spkr2p,
+ widget_line1,
+ widget_line2,
+ widget_dac1,
+ widget_dac2,
+ widget_dac3,
+ widget_rx1,
+ widget_rx2,
+ widget_rx3,
+ widget_mic1,
+ widget_mic2,
+ widget_micbias1,
+ widget_micbias2,
+ widget_apga1,
+ widget_apga2,
+ widget_tx1,
+ widget_tx2,
+ widget_adc1,
+ widget_adc2,
+ widget_if0_dld_l,
+ widget_if0_dld_r,
+ widget_if0_uld_l,
+ widget_if0_uld_r,
+ widget_if1_dld_l,
+ widget_if1_dld_r,
+ widget_if1_uld_l,
+ widget_if1_uld_r,
+ widget_mic1p1,
+ widget_mic1n1,
+ widget_mic1p2,
+ widget_mic1n2,
+ widget_mic2p1,
+ widget_mic2n1,
+ widget_mic2p2,
+ widget_mic2n2,
+ widget_clock,
+ number_of_widgets
+};
+
+/* This is only meant for debugging */
+static const char *widget_names[] = {
+ "EAR", "AUXO1", "AUXO2", "AUXO3", "AUXO4",
+ "SPKR1", "SPKR2", "SPKR1_ADDER", "SPKR2_ADDER",
+ "PWM_SPKR1", "PWM_SPKR2",
+ "PWM_SPKR1N", "PWM_SPKR1P",
+ "PWM_SPKR2N", "PWM_SPKR2P",
+ "LINE1", "LINE2",
+ "DAC1", "DAC2", "DAC3",
+ "RX1", "RX2", "RX3",
+ "MIC1", "MIC2",
+ "MIC-BIAS1", "MIC-BIAS2",
+ "APGA1", "APGA2",
+ "TX1", "TX2",
+ "ADC1", "ADC2",
+ "IF0-DLD-L", "IF0-DLD-R", "IF0-ULD-L", "IF0-ULD-R",
+ "IF1-DLD-L", "IF1-DLD-R", "IF1-ULD-L", "IF1-ULD-R",
+ "MIC1P1", "MIC1N1", "MIC1P2", "MIC1N2",
+ "MIC2P1", "MIC2N1", "MIC2P2", "MIC2N2",
+ "CLOCK"
+};
+
+struct widget_pm {
+ enum enum_widget widget;
+ u8 reg;
+ u8 shift;
+
+ unsigned long source_list[BIT_WORD(number_of_widgets) + 1];
+ unsigned long sink_list[BIT_WORD(number_of_widgets) + 1];
+};
+
+static struct widget_pm widget_pm_array[] = {
+ {.widget = widget_ear, .reg = EAR_PWR, .shift = EAR_PWR_SHIFT},
+
+ {.widget = widget_auxo1, .reg = AUXO1, .shift = AUXOx_PWR_SHIFT},
+ {.widget = widget_auxo2, .reg = AUXO2, .shift = AUXOx_PWR_SHIFT},
+ {.widget = widget_auxo3, .reg = AUXO3, .shift = AUXOx_PWR_SHIFT},
+ {.widget = widget_auxo4, .reg = AUXO4, .shift = AUXOx_PWR_SHIFT},
+
+ {.widget = widget_spkr1, .reg = DUMMY_REG, .shift = 0},
+ {.widget = widget_spkr2, .reg = AB5500_VIRTUAL_REG3,
+ .shift = SPKR2_PWR_SHIFT},
+
+ {.widget = widget_spkr1_adder, .reg = AB5500_VIRTUAL_REG3,
+ .shift = SPKR1_ADDER_PWR_SHIFT},
+ {.widget = widget_spkr2_adder, .reg = AB5500_VIRTUAL_REG3,
+ .shift = SPKR2_ADDER_PWR_SHIFT},
+
+ {.widget = widget_pwm_spkr1, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR1_PWR_SHIFT},
+ {.widget = widget_pwm_spkr2, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR2_PWR_SHIFT},
+
+ {.widget = widget_pwm_spkr1n, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR1N_PWR_SHIFT},
+ {.widget = widget_pwm_spkr1p, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR1P_PWR_SHIFT},
+
+ {.widget = widget_pwm_spkr2n, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR2N_PWR_SHIFT},
+ {.widget = widget_pwm_spkr2p, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR2P_PWR_SHIFT},
+
+
+ {.widget = widget_line1, .reg = LINE1, .shift = LINEx_PWR_SHIFT},
+ {.widget = widget_line2, .reg = LINE2, .shift = LINEx_PWR_SHIFT},
+
+ {.widget = widget_dac1, .reg = RX1, .shift = DACx_PWR_SHIFT},
+ {.widget = widget_dac2, .reg = RX2, .shift = DACx_PWR_SHIFT},
+ {.widget = widget_dac3, .reg = RX3, .shift = DACx_PWR_SHIFT},
+
+ {.widget = widget_rx1, .reg = RX1, .shift = RXx_PWR_SHIFT},
+ {.widget = widget_rx2, .reg = RX2, .shift = RXx_PWR_SHIFT},
+ {.widget = widget_rx3, .reg = RX3, .shift = RXx_PWR_SHIFT},
+
+ {.widget = widget_mic1, .reg = MIC1_GAIN, .shift = MICx_PWR_SHIFT},
+ {.widget = widget_mic2, .reg = MIC2_GAIN, .shift = MICx_PWR_SHIFT},
+
+ {.widget = widget_micbias1, .reg = MIC_BIAS1,
+ .shift = MBIASx_PWR_SHIFT},
+ {.widget = widget_micbias2, .reg = MIC_BIAS2,
+ .shift = MBIASx_PWR_SHIFT},
+
+ {.widget = widget_apga1, .reg = ANALOG_LOOP_PGA1,
+ .shift = APGAx_PWR_SHIFT},
+ {.widget = widget_apga2, .reg = ANALOG_LOOP_PGA2,
+ .shift = APGAx_PWR_SHIFT},
+
+ {.widget = widget_tx1, .reg = TX1, .shift = TXx_PWR_SHIFT},
+ {.widget = widget_tx2, .reg = TX2, .shift = TXx_PWR_SHIFT},
+
+ {.widget = widget_adc1, .reg = TX1, .shift = ADCx_PWR_SHIFT},
+ {.widget = widget_adc2, .reg = TX2, .shift = ADCx_PWR_SHIFT},
+
+ {.widget = widget_if0_dld_l, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF0_DLD_L_PW_SHIFT},
+ {.widget = widget_if0_dld_r, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF0_DLD_R_PW_SHIFT},
+ {.widget = widget_if0_uld_l, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF0_ULD_L_PW_SHIFT},
+ {.widget = widget_if0_uld_r, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF0_ULD_R_PW_SHIFT},
+
+ {.widget = widget_if1_dld_l, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF1_DLD_L_PW_SHIFT},
+ {.widget = widget_if1_dld_r, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF1_DLD_R_PW_SHIFT},
+ {.widget = widget_if1_uld_l, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF1_ULD_L_PW_SHIFT},
+ {.widget = widget_if1_uld_r, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF1_ULD_R_PW_SHIFT},
+
+ {.widget = widget_mic1p1, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC1P1_PW_SHIFT},
+ {.widget = widget_mic1n1, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC1N1_PW_SHIFT},
+ {.widget = widget_mic1p2, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC1P2_PW_SHIFT},
+ {.widget = widget_mic1n2, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC1N2_PW_SHIFT},
+
+ {.widget = widget_mic2p1, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC2P1_PW_SHIFT},
+ {.widget = widget_mic2n1, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC2N1_PW_SHIFT},
+ {.widget = widget_mic2p2, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC2P2_PW_SHIFT},
+ {.widget = widget_mic2n2, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC2N2_PW_SHIFT},
+
+ {.widget = widget_clock, .reg = CLOCK, .shift = CLOCK_ENABLE_SHIFT},
+};
+
+DEFINE_MUTEX(ab5500_pm_mutex);
+
+static struct {
+ enum enum_widget stack[number_of_widgets];
+ int p;
+} pm_stack;
+
+#define pm_stack_as_bitmap ({ \
+ unsigned long bitmap[BIT_WORD(number_of_widgets) + 1]; \
+ int i; \
+ memset(bitmap, 0, sizeof(bitmap)); \
+ for (i = 0; i < pm_stack.p; i++) { \
+ set_bit(pm_stack.stack[i], bitmap); \
+ } \
+ bitmap; \
+ })
+
+/* These are only meant to meet the obligations of DAPM */
+static const struct snd_soc_dapm_widget ab5500_dapm_widgets[] = {
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+};
+
+
+struct ab5500_codec_dai_data ab5500_codec_privates[NO_CODEC_DAI_IF] = {
+ {
+ .playback_active = false,
+ .capture_active = false,
+ },
+ {
+ .playback_active = false,
+ .capture_active = false,
+ }
+};
+
+static const char *enum_rx_input_select[] = {
+ "Mute", "TX1", "TX2", "I2S0_DLD_L",
+ "I2S0_DLD_R", "I2S1_DLD_L", "I2S1_DLD_R"
+};
+
+static const char *enum_i2s_uld_select[] = {
+ "Mute", "TX1", "TX2", "I2S0_DLD_L",
+ "I2S0_DLD_R", "I2S1_DLD_L", "I2S1_DLD_R", "tri-state"
+};
+static const char *enum_apga1_source[] = {"LINEIN1", "MIC1", "MIC2", "None"};
+static const char *enum_apga2_source[] = {"LINEIN2", "MIC1", "MIC2", "None"};
+static const char *enum_rx_side_tone[] = {"TX1", "TX2"};
+static const char *enum_dac_power_mode[] = {"100%", "75%", "55%"};
+static const char *enum_ear_power_mode[] = {"100%", "70%", "50%"};
+static const char *enum_auxo_power_mode[] = {
+ "100%", "67%", "50%", "25%", "auto"
+};
+static const char *enum_onoff[] = {"Off", "On"};
+static const char *enum_mbias_pdn_imp[] = {"GND", "HiZ"};
+static const char *enum_mbias2_out_v[] = {"2.0v", "2.2v"};
+static const char *enum_mic_in_imp[] = {
+ "12.5 kohm", "25 kohm", "50 kohm"
+};
+static const char *enum_hp_filter[] = {"HP3", "HP1", "bypass"};
+static const char *enum_i2s_word_length[] = {"16 bits", "24 bits"};
+static const char *enum_i2s_mode[] = {"Master Mode", "Slave Mode"};
+static const char *enum_i2s_tristate[] = {"Normal", "Tri-state"};
+static const char *enum_optional_resistor[] = {"disconnected", "connected"};
+static const char *enum_i2s_sample_rate[] = {
+ "8 kHz", "16 kHz", "44.1 kHz", "48 kHz"
+};
+static const char *enum_tx1_input_select[] = {
+ "ADC1", "DIGMIC1", "DIGMIC2"
+};
+static const char *enum_tx2_input_select[] = {
+ "ADC2", "DIGMIC1", "DIGMIC2"
+};
+static const char *enum_signal_inversion[] = {"normal", "inverted"};
+static const char *enum_spkr1_mode[] = {
+ "SPKR1 power down", "Vibra PWM", "class D amplifier", "class AB amplifier"
+};
+static const char *enum_spkr2_mode[] = {
+ "Vibra PWM", "class D amplifier",
+};
+static const char *enum_pwm_pol[] = {
+ "GND", "VDD"
+};
+/* RX1 Input Select */
+static struct soc_enum soc_enum_rx1_in_sel =
+ SOC_ENUM_SINGLE(RX1, RXx_DATA_SHIFT,
+ ARRAY_SIZE(enum_rx_input_select),
+ enum_rx_input_select);
+
+/* RX2 Input Select */
+static struct soc_enum soc_enum_rx2_in_sel =
+ SOC_ENUM_SINGLE(RX2, RXx_DATA_SHIFT,
+ ARRAY_SIZE(enum_rx_input_select),
+ enum_rx_input_select);
+/* RX3 Input Select */
+static struct soc_enum soc_enum_rx3_in_sel =
+ SOC_ENUM_SINGLE(RX3, RXx_DATA_SHIFT,
+ ARRAY_SIZE(enum_rx_input_select),
+ enum_rx_input_select);
+/* TX1 Input Select */
+static struct soc_enum soc_enum_tx1_in_sel =
+ SOC_ENUM_SINGLE(TX1, TXx_MUX_SHIFT,
+ ARRAY_SIZE(enum_tx1_input_select),
+ enum_tx1_input_select);
+/* TX2 Input Select */
+static struct soc_enum soc_enum_tx2_in_sel =
+ SOC_ENUM_SINGLE(TX2, TXx_MUX_SHIFT,
+ ARRAY_SIZE(enum_tx2_input_select),
+ enum_tx2_input_select);
+
+/* I2S0 ULD Select */
+static struct soc_enum soc_enum_i2s0_input_select =
+ SOC_ENUM_DOUBLE(INTERFACE0_ULD, 0, 4,
+ ARRAY_SIZE(enum_i2s_uld_select),
+ enum_i2s_uld_select);
+/* I2S1 ULD Select */
+static struct soc_enum soc_enum_i2s1_input_select =
+ SOC_ENUM_DOUBLE(INTERFACE1_ULD, 0, 4,
+ ARRAY_SIZE(enum_i2s_uld_select),
+ enum_i2s_uld_select);
+
+/* APGA1 Source */
+static struct soc_enum soc_enum_apga1_source =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA1, APGAx_MUX_SHIFT,
+ ARRAY_SIZE(enum_apga1_source),
+ enum_apga1_source);
+
+/* APGA2 Source */
+static struct soc_enum soc_enum_apga2_source =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA2, APGAx_MUX_SHIFT,
+ ARRAY_SIZE(enum_apga2_source),
+ enum_apga2_source);
+
+static struct soc_enum soc_enum_apga1_enable =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA1, APGAx_PWR_SHIFT,
+ ARRAY_SIZE(enum_onoff), enum_onoff);
+
+static struct soc_enum soc_enum_apga2_enable =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA2, APGAx_PWR_SHIFT,
+ ARRAY_SIZE(enum_onoff), enum_onoff);
+
+/* RX1 Side Tone */
+static struct soc_enum soc_enum_dac1_side_tone =
+ SOC_ENUM_SINGLE(ST1_PGA, STx_MUX_SHIFT,
+ ARRAY_SIZE(enum_rx_side_tone),
+ enum_rx_side_tone);
+
+/* RX2 Side Tone */
+static struct soc_enum soc_enum_dac2_side_tone =
+ SOC_ENUM_SINGLE(ST2_PGA, STx_MUX_SHIFT,
+ ARRAY_SIZE(enum_rx_side_tone),
+ enum_rx_side_tone);
+
+/* DAC1 Power Mode */
+static struct soc_enum soc_enum_dac1_power_mode =
+ SOC_ENUM_SINGLE(RX1, DACx_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_dac_power_mode),
+ enum_dac_power_mode);
+
+/* DAC2 Power Mode */
+static struct soc_enum soc_enum_dac2_power_mode =
+ SOC_ENUM_SINGLE(RX2, DACx_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_dac_power_mode),
+ enum_dac_power_mode);
+
+/* DAC3 Power Mode */
+static struct soc_enum soc_enum_dac3_power_mode =
+ SOC_ENUM_SINGLE(RX3, DACx_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_dac_power_mode),
+ enum_dac_power_mode);
+
+/* EAR Power Mode */
+static struct soc_enum soc_enum_ear_power_mode =
+ SOC_ENUM_SINGLE(EAR_PWR, EAR_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_ear_power_mode),
+ enum_ear_power_mode);
+
+/* AUXO12 Power Mode */
+static struct soc_enum soc_enum_auxo12_power_mode =
+ SOC_ENUM_SINGLE(AUXO12_PWR_MODE, AUXOxy_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_auxo_power_mode),
+ enum_auxo_power_mode);
+
+/* AUXO34 Power Mode */
+static struct soc_enum soc_enum_auxo34_power_mode =
+ SOC_ENUM_SINGLE(AUXO34_PWR_MODE, AUXOxy_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_auxo_power_mode),
+ enum_auxo_power_mode);
+
+/* MBIAS1 PDN Impedance */
+static struct soc_enum soc_enum_mbias1_pdn_imp =
+ SOC_ENUM_SINGLE(MIC_BIAS1, MBIASx_PDN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mbias_pdn_imp),
+ enum_mbias_pdn_imp);
+
+/* MBIAS2 PDN Impedance */
+static struct soc_enum soc_enum_mbias2_pdn_imp =
+ SOC_ENUM_SINGLE(MIC_BIAS2, MBIASx_PDN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mbias_pdn_imp),
+ enum_mbias_pdn_imp);
+
+/* MBIAS2 Output voltage */
+static struct soc_enum soc_enum_mbias2_out_v =
+ SOC_ENUM_SINGLE(MIC_BIAS2, MBIAS2_OUT_V_SHIFT,
+ ARRAY_SIZE(enum_mbias2_out_v),
+ enum_mbias2_out_v);
+
+static struct soc_enum soc_enum_mbias2_int_r =
+ SOC_ENUM_SINGLE(MIC_BIAS2_VAD, MBIAS2_R_INT_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_mic1_in_imp =
+ SOC_ENUM_SINGLE(MIC1_GAIN, MICx_IN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mic_in_imp),
+ enum_mic_in_imp);
+
+static struct soc_enum soc_enum_mic2_in_imp =
+ SOC_ENUM_SINGLE(MIC2_GAIN, MICx_IN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mic_in_imp),
+ enum_mic_in_imp);
+
+static struct soc_enum soc_enum_tx1_hp_filter =
+ SOC_ENUM_SINGLE(TX1, TXx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_tx2_hp_filter =
+ SOC_ENUM_SINGLE(TX2, TXx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_st1_hp_filter =
+ SOC_ENUM_SINGLE(ST1_PGA, STx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_st2_hp_filter =
+ SOC_ENUM_SINGLE(ST2_PGA, STx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_i2s0_word_length =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_WORDLENGTH_SHIFT,
+ ARRAY_SIZE(enum_i2s_word_length),
+ enum_i2s_word_length);
+
+static struct soc_enum soc_enum_i2s1_word_length =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_WORDLENGTH_SHIFT,
+ ARRAY_SIZE(enum_i2s_word_length),
+ enum_i2s_word_length);
+
+static struct soc_enum soc_enum_i2s0_mode =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_MODE_SHIFT,
+ ARRAY_SIZE(enum_i2s_mode),
+ enum_i2s_mode);
+
+static struct soc_enum soc_enum_i2s1_mode =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_MODE_SHIFT,
+ ARRAY_SIZE(enum_i2s_mode),
+ enum_i2s_mode);
+
+static struct soc_enum soc_enum_i2s0_tristate =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_TRISTATE_SHIFT,
+ ARRAY_SIZE(enum_i2s_tristate),
+ enum_i2s_tristate);
+
+static struct soc_enum soc_enum_i2s1_tristate =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_TRISTATE_SHIFT,
+ ARRAY_SIZE(enum_i2s_tristate),
+ enum_i2s_tristate);
+
+static struct soc_enum soc_enum_i2s0_pulldown_resistor =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_i2s1_pulldown_resistor =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_i2s0_sample_rate =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_SR_SHIFT,
+ ARRAY_SIZE(enum_i2s_sample_rate),
+ enum_i2s_sample_rate);
+
+static struct soc_enum soc_enum_i2s1_sample_rate =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_SR_SHIFT,
+ ARRAY_SIZE(enum_i2s_sample_rate),
+ enum_i2s_sample_rate);
+
+static struct soc_enum soc_enum_line1_inversion =
+ SOC_ENUM_SINGLE(LINE1, LINEx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_line2_inversion =
+ SOC_ENUM_SINGLE(LINE2, LINEx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo1_inversion =
+ SOC_ENUM_SINGLE(AUXO1, AUXOx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo2_inversion =
+ SOC_ENUM_SINGLE(AUXO2, AUXOx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo3_inversion =
+ SOC_ENUM_SINGLE(AUXO3, AUXOx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo4_inversion =
+ SOC_ENUM_SINGLE(AUXO4, AUXOx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo1_pulldown_resistor =
+ SOC_ENUM_SINGLE(AUXO1, AUXOx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_auxo2_pulldown_resistor =
+ SOC_ENUM_SINGLE(AUXO2, AUXOx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_spkr1_mode =
+ SOC_ENUM_SINGLE(SPKR1, SPKRx_PWR_SHIFT,
+ ARRAY_SIZE(enum_spkr1_mode),
+ enum_spkr1_mode);
+
+static struct soc_enum soc_enum_spkr2_mode =
+ SOC_ENUM_SINGLE(AB5500_VIRTUAL_REG3, SPKR2_MODE_SHIFT,
+ ARRAY_SIZE(enum_spkr2_mode),
+ enum_spkr2_mode);
+
+static struct soc_enum soc_enum_pwm_spkr1n_pol =
+ SOC_ENUM_SINGLE(PWMCTRL_SPKR1, PWMCTRL_SPKRx_N1_POL_SHIFT,
+ ARRAY_SIZE(enum_pwm_pol), enum_pwm_pol);
+
+static struct soc_enum soc_enum_pwm_spkr1p_pol =
+ SOC_ENUM_SINGLE(PWMCTRL_SPKR1, PWMCTRL_SPKRx_P1_POL_SHIFT,
+ ARRAY_SIZE(enum_pwm_pol), enum_pwm_pol);
+
+static struct soc_enum soc_enum_pwm_spkr2n_pol =
+ SOC_ENUM_SINGLE(PWMCTRL_SPKR2, PWMCTRL_SPKRx_N1_POL_SHIFT,
+ ARRAY_SIZE(enum_pwm_pol), enum_pwm_pol);
+
+static struct soc_enum soc_enum_pwm_spkr2p_pol =
+ SOC_ENUM_SINGLE(PWMCTRL_SPKR2, PWMCTRL_SPKRx_P1_POL_SHIFT,
+ ARRAY_SIZE(enum_pwm_pol), enum_pwm_pol);
+
+static struct snd_kcontrol_new ab5500_snd_controls[] = {
+ /* RX Routing */
+ SOC_ENUM("RX1 Input Select", soc_enum_rx1_in_sel),
+ SOC_ENUM("RX2 Input Select", soc_enum_rx2_in_sel),
+ SOC_ENUM("RX3 Input Select", soc_enum_rx3_in_sel),
+ SOC_SINGLE("LINE1 Adder", LINE1_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("LINE2 Adder", LINE2_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("EAR Adder", EAR_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("SPKR1 Adder", SPKR1_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("SPKR2 Adder", SPKR2_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("AUXO1 Adder", AUXO1_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("AUXO2 Adder", AUXO2_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("AUXO3 Adder", AUXO3_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("AUXO4 Adder", AUXO4_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("SPKR1 PWM Select", AB5500_VIRTUAL_REG5, 0, 0x03, 0),
+ SOC_SINGLE("SPKR2 PWM Select", AB5500_VIRTUAL_REG5, 2, 0x0C, 0),
+ /* TX Routing */
+ SOC_ENUM("TX1 Input Select", soc_enum_tx1_in_sel),
+ SOC_ENUM("TX2 Input Select", soc_enum_tx2_in_sel),
+ SOC_SINGLE("MIC1 Input Select", MIC1_INPUT_SELECT, 0, 0xff, 0),
+ SOC_SINGLE("MIC2 Input Select", MIC2_INPUT_SELECT, 0, 0xff, 0),
+ SOC_SINGLE("MIC2 to MIC1", MIC2_TO_MIC1, 0, 0x03, 0),
+ SOC_ENUM("I2S0 Input Select", soc_enum_i2s0_input_select),
+ SOC_ENUM("I2S1 Input Select", soc_enum_i2s1_input_select),
+ /* Routing of Side Tone and Analop Loop */
+ SOC_ENUM("APGA1 Source", soc_enum_apga1_source),
+ SOC_ENUM("APGA2 Source", soc_enum_apga2_source),
+ SOC_ENUM("APGA1 Enable", soc_enum_apga1_enable),
+ SOC_ENUM("APGA2 Enable", soc_enum_apga2_enable),
+ SOC_ENUM("DAC1 Side Tone", soc_enum_dac1_side_tone),
+ SOC_ENUM("DAC2 Side Tone", soc_enum_dac2_side_tone),
+ /* RX Volume Control */
+ SOC_SINGLE("RX-DPGA1 Gain", RX1_DPGA, 0, 0x43, 0),
+ SOC_SINGLE("RX-DPGA2 Gain", RX2_DPGA, 0, 0x43, 0),
+ SOC_SINGLE("RX-DPGA3 Gain", RX3_DPGA, 0, 0x43, 0),
+ SOC_SINGLE("LINE1 Gain", LINE1, LINEx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("LINE2 Gain", LINE2, LINEx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("SPKR1 Gain", SPKR1, SPKRx_GAIN_SHIFT, 0x16, 0),
+ SOC_SINGLE("SPKR2 Gain", SPKR2, SPKRx_GAIN_SHIFT, 0x16, 0),
+ SOC_SINGLE("EAR Gain", EAR_GAIN, EAR_GAIN_SHIFT, 0x12, 0),
+ SOC_SINGLE("AUXO1 Gain", AUXO1, AUXOx_GAIN_SHIFT, 0x0c, 0),
+ SOC_SINGLE("AUXO2 Gain", AUXO2, AUXOx_GAIN_SHIFT, 0x0c, 0),
+ SOC_SINGLE("AUXO3 Gain", AUXO3, AUXOx_GAIN_SHIFT, 0x0c, 0),
+ SOC_SINGLE("AUXO4 Gain", AUXO4, AUXOx_GAIN_SHIFT, 0x0c, 0),
+ /* TX Volume Control */
+ SOC_SINGLE("MIC1 Gain", MIC1_GAIN, MICx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("MIC2 Gain", MIC2_GAIN, MICx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("TX-DPGA1 Gain", TX_DPGA1, TX_DPGAx_SHIFT, 0x0f, 0),
+ SOC_SINGLE("TX-DPGA2 Gain", TX_DPGA2, TX_DPGAx_SHIFT, 0x0f, 0),
+ /* Volume Control of Side Tone and Analog Loop */
+ SOC_SINGLE("ST-PGA1 Gain", ST1_PGA, STx_PGA_SHIFT, 0x0a, 0),
+ SOC_SINGLE("ST-PGA2 Gain", ST2_PGA, STx_PGA_SHIFT, 0x0a, 0),
+ SOC_SINGLE("APGA1 Gain", ANALOG_LOOP_PGA1, APGAx_GAIN_SHIFT, 0x1d, 0),
+ SOC_SINGLE("APGA2 Gain", ANALOG_LOOP_PGA2, APGAx_GAIN_SHIFT, 0x1d, 0),
+ /* RX Properties */
+ SOC_ENUM("DAC1 Power Mode", soc_enum_dac1_power_mode),
+ SOC_ENUM("DAC2 Power Mode", soc_enum_dac2_power_mode),
+ SOC_ENUM("DAC3 Power Mode", soc_enum_dac3_power_mode),
+ SOC_ENUM("EAR Power Mode", soc_enum_ear_power_mode),
+ SOC_ENUM("AUXO12 Power Mode", soc_enum_auxo12_power_mode),
+ SOC_ENUM("AUXO34 Power Mode", soc_enum_auxo34_power_mode),
+ SOC_ENUM("LINE1 Inversion", soc_enum_line1_inversion),
+ SOC_ENUM("LINE2 Inversion", soc_enum_line2_inversion),
+ SOC_ENUM("AUXO1 Inversion", soc_enum_auxo1_inversion),
+ SOC_ENUM("AUXO2 Inversion", soc_enum_auxo2_inversion),
+ SOC_ENUM("AUXO3 Inversion", soc_enum_auxo3_inversion),
+ SOC_ENUM("AUXO4 Inversion", soc_enum_auxo4_inversion),
+ SOC_ENUM("AUXO1 Pulldown Resistor", soc_enum_auxo1_pulldown_resistor),
+ SOC_ENUM("AUXO2 Pulldown Resistor", soc_enum_auxo2_pulldown_resistor),
+ SOC_ENUM("SPKR1 Mode", soc_enum_spkr1_mode),
+ SOC_ENUM("SPKR2 Mode", soc_enum_spkr2_mode),
+ SOC_ENUM("PWM SPKR1N POL", soc_enum_pwm_spkr1n_pol),
+ SOC_ENUM("PWM SPKR1P POL", soc_enum_pwm_spkr1p_pol),
+ SOC_ENUM("PWM SPKR2N POL", soc_enum_pwm_spkr2n_pol),
+ SOC_ENUM("PWM SPKR2P POL", soc_enum_pwm_spkr2p_pol),
+ /* TX Properties */
+ SOC_SINGLE("MIC1 VMID", MIC1_VMID_SELECT, 0, 0x3f, 0),
+ SOC_SINGLE("MIC2 VMID", MIC2_VMID_SELECT, 0, 0x3f, 0),
+ SOC_ENUM("MBIAS1 PDN Impedance", soc_enum_mbias1_pdn_imp),
+ SOC_ENUM("MBIAS2 PDN Impedance", soc_enum_mbias2_pdn_imp),
+ SOC_ENUM("MBIAS2 Output Voltage", soc_enum_mbias2_out_v),
+ SOC_ENUM("MBIAS2 Internal Resistor", soc_enum_mbias2_int_r),
+ SOC_ENUM("MIC1 Input Impedance", soc_enum_mic1_in_imp),
+ SOC_ENUM("MIC2 Input Impedance", soc_enum_mic2_in_imp),
+ SOC_ENUM("TX1 HP Filter", soc_enum_tx1_hp_filter),
+ SOC_ENUM("TX2 HP Filter", soc_enum_tx2_hp_filter),
+ /* Side Tone and Analog Loop Properties */
+ SOC_ENUM("ST1 HP Filter", soc_enum_st1_hp_filter),
+ SOC_ENUM("ST2 HP Filter", soc_enum_st2_hp_filter),
+ /* I2S Interface Properties */
+ SOC_ENUM("I2S0 Word Length", soc_enum_i2s0_word_length),
+ SOC_ENUM("I2S1 Word Length", soc_enum_i2s1_word_length),
+ SOC_ENUM("I2S0 Mode", soc_enum_i2s0_mode),
+ SOC_ENUM("I2S1 Mode", soc_enum_i2s1_mode),
+ SOC_ENUM("I2S0 tri-state", soc_enum_i2s0_tristate),
+ SOC_ENUM("I2S1 tri-state", soc_enum_i2s1_tristate),
+ SOC_ENUM("I2S0 Pulldown Resistor", soc_enum_i2s0_pulldown_resistor),
+ SOC_ENUM("I2S1 Pulldown Resistor", soc_enum_i2s1_pulldown_resistor),
+ SOC_ENUM("I2S0 Sample Rate", soc_enum_i2s0_sample_rate),
+ SOC_ENUM("I2S1 Sample Rate", soc_enum_i2s1_sample_rate),
+ SOC_SINGLE("Interface Swap", INTERFACE_SWAP, 0, 0x03, 0),
+ /* Miscellaneous */
+ SOC_SINGLE("Negative Charge Pump", NEG_CHARGE_PUMP, 0, 0x03, 0)
+};
+
+/* count the number of 1 */
+#define count_ones(x) ({ \
+ int num; \
+ typeof(x) y = x; \
+ for (num = 0; y; y &= y - 1, num++) \
+ ; \
+ num; \
+ })
+
+enum enum_power {
+ POWER_OFF = 0,
+ POWER_ON = 1
+};
+
+enum enum_link {
+ UNLINK = 0,
+ LINK = 1
+};
+
+static enum enum_power get_widget_power_status(enum enum_widget widget)
+{
+ u8 val;
+
+ if (widget >= number_of_widgets)
+ return POWER_OFF;
+ val = read_reg(widget_pm_array[widget].reg);
+ if (val & (1 << widget_pm_array[widget].shift))
+ return POWER_ON;
+ else
+ return POWER_OFF;
+}
+
+static int count_powered_neighbors(const unsigned long *neighbors)
+{
+ unsigned long i;
+ int n = 0;
+ for_each_set_bit(i, neighbors, number_of_widgets) {
+ if (get_widget_power_status(i) == POWER_ON)
+ n++;
+ }
+ return n;
+}
+
+static int has_powered_neighbors(const unsigned long *neighbors)
+{
+ unsigned int i;
+ for_each_set_bit(i, neighbors, number_of_widgets) {
+ if (get_widget_power_status(i) == POWER_ON)
+ return 1;
+ }
+ return 0;
+}
+
+
+static int has_stacked_neighbors(const unsigned long *neighbors)
+{
+ unsigned long *stack_map = pm_stack_as_bitmap;
+ return bitmap_intersects(stack_map, neighbors, number_of_widgets);
+}
+
+static void power_widget_unlocked(enum enum_power onoff, enum enum_widget widget)
+{
+ enum enum_widget w;
+ int done;
+
+ if (widget >= number_of_widgets)
+ return;
+ if (get_widget_power_status(widget) == onoff)
+ return;
+
+ for (w = widget, done = 0; !done;) {
+ unsigned long i;
+ unsigned long *srcs = widget_pm_array[w].source_list;
+ unsigned long *sinks = widget_pm_array[w].sink_list;
+ dev_dbg(ab5500_dev, "%s: processing widget %s.\n",
+ __func__, widget_names[w]);
+
+ if (onoff == POWER_ON &&
+ !bitmap_empty(srcs, number_of_widgets) &&
+ !has_powered_neighbors(srcs)) {
+ pm_stack.stack[pm_stack.p++] = w;
+ for_each_set_bit(i, srcs, number_of_widgets) {
+ pm_stack.stack[pm_stack.p++] = i;
+ }
+ w = pm_stack.stack[--pm_stack.p];
+ continue;
+ } else if (onoff == POWER_OFF &&
+ has_powered_neighbors(sinks)) {
+ int n = 0;
+ pm_stack.stack[pm_stack.p++] = w;
+ for_each_set_bit(i, sinks, number_of_widgets) {
+ if (count_powered_neighbors(
+ widget_pm_array[i].source_list)
+ == 1 &&
+ get_widget_power_status(i) == POWER_ON) {
+ pm_stack.stack[pm_stack.p++] = i;
+ n++;
+ }
+ }
+ if (n) {
+ w = pm_stack.stack[--pm_stack.p];
+ continue;
+ } else
+ --pm_stack.p;
+ }
+ mask_set_reg(widget_pm_array[w].reg,
+ 1 << widget_pm_array[w].shift,
+ onoff == POWER_ON ? 0xff : 0);
+ dev_dbg(ab5500_dev, "%s: widget %s powered %s.\n",
+ __func__, widget_names[w],
+ onoff == POWER_ON ? "on" : "off");
+ if (onoff == POWER_ON &&
+ !bitmap_empty(sinks, number_of_widgets) &&
+ !has_powered_neighbors(sinks) &&
+ !has_stacked_neighbors(sinks)) {
+ for_each_set_bit(i, sinks, number_of_widgets) {
+ pm_stack.stack[pm_stack.p++] = i;
+ }
+ w = pm_stack.stack[--pm_stack.p];
+ continue;
+ } else if (onoff == POWER_OFF) {
+ for_each_set_bit(i, srcs, number_of_widgets) {
+ if (!has_powered_neighbors(
+ widget_pm_array[i].sink_list)
+ && get_widget_power_status(i) == POWER_ON
+ && !test_bit(i, pm_stack_as_bitmap)) {
+ pm_stack.stack[pm_stack.p++] = i;
+ }
+ }
+ }
+ if (pm_stack.p > 0)
+ w = pm_stack.stack[--pm_stack.p];
+ else
+ done = 1;
+ }
+}
+
+static void power_widget_locked(enum enum_power onoff,
+ enum enum_widget widget)
+{
+ if (mutex_lock_interruptible(&ab5500_pm_mutex)) {
+ dev_warn(ab5500_dev,
+ "%s: Signal received while waiting on the PM mutex.\n",
+ __func__);
+ return;
+ }
+ power_widget_unlocked(onoff, widget);
+ mutex_unlock(&ab5500_pm_mutex);
+}
+
+
+static void dump_registers(const char *where, ...)
+{
+ va_list ap;
+ va_start(ap, where);
+ do {
+ short reg = va_arg(ap, int);
+ if (reg < 0)
+ break;
+ dev_dbg(ab5500_dev, "%s from %s> 0x%02X : 0x%02X.\n",
+ __func__, where, reg, read_reg(reg));
+ } while (1);
+ va_end(ap);
+}
+
+/**
+ * update the link two widgets.
+ * @op: 1 - connect; 0 - disconnect
+ * @src: source of the connection
+ * @sink: sink of the connection
+ */
+static int update_widgets_link(enum enum_link op, enum enum_widget src,
+ enum enum_widget sink,
+ u8 reg, u8 mask, u8 newval)
+{
+ int ret = 0;
+
+ if (mutex_lock_interruptible(&ab5500_pm_mutex)) {
+ dev_warn(ab5500_dev, "%s: A signal is received while waiting on"
+ " the PM mutex.\n", __func__);
+ return -EINTR;
+ }
+
+ switch (op << 2 | test_bit(sink, widget_pm_array[src].sink_list) << 1 |
+ test_bit(src, widget_pm_array[sink].source_list)) {
+ case 3: /* UNLINK, sink in sink_list, src in source_list */
+ case 4: /* LINK, sink not in sink_list, src not in source_list */
+ break;
+ default:
+ ret = -EINVAL;
+ goto end;
+ }
+ switch (((int)op) << 2 | get_widget_power_status(src) << 1 |
+ get_widget_power_status(sink)) {
+ case 3: /* op = 0, src on, sink on */
+ if (count_powered_neighbors(widget_pm_array[sink].source_list)
+ == 1)
+ power_widget_unlocked(POWER_OFF, sink);
+ mask_set_reg(reg, mask, newval);
+ break;
+ case 6: /* op = 1, src on, sink off */
+ mask_set_reg(reg, mask, newval);
+ power_widget_unlocked(POWER_ON, sink);
+ break;
+ default:
+ /* op = 0, src off, sink off */
+ /* op = 0, src off, sink on */
+ /* op = 0, src on, sink off */
+ /* op = 1, src off, sink off */
+ /* op = 1, src off, sink on */
+ /* op = 1, src on, sink on */
+ mask_set_reg(reg, mask, newval);
+ }
+ change_bit(sink, widget_pm_array[src].sink_list);
+ change_bit(src, widget_pm_array[sink].source_list);
+end:
+ mutex_unlock(&ab5500_pm_mutex);
+ return ret;
+};
+
+static enum enum_widget apga_source_translate(u8 reg_value)
+{
+ switch (reg_value) {
+ case 1:
+ return widget_mic1;
+ case 2:
+ return widget_mic2;
+ default:
+ return number_of_widgets;
+ }
+}
+
+static enum enum_widget adder_sink_translate(u8 reg)
+{
+ switch (reg) {
+ case EAR_ADDER:
+ return widget_ear;
+ case AUXO1_ADDER:
+ return widget_auxo1;
+ case AUXO2_ADDER:
+ return widget_auxo2;
+ case AUXO3_ADDER:
+ return widget_auxo3;
+ case AUXO4_ADDER:
+ return widget_auxo4;
+ case SPKR1_ADDER:
+ return widget_spkr1;
+ case SPKR2_ADDER:
+ return widget_spkr2;
+ case LINE1_ADDER:
+ return widget_line1;
+ case LINE2_ADDER:
+ return widget_line2;
+ default:
+ return number_of_widgets;
+ }
+}
+
+static int ab5500_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(&codec->dapm, ab5500_dapm_widgets,
+ ARRAY_SIZE(ab5500_dapm_widgets));
+
+ snd_soc_dapm_add_routes(&codec->dapm, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(&codec->dapm);
+ return 0;
+}
+
+static void power_for_playback(enum enum_power onoff, int ifsel)
+{
+ dev_dbg(ab5500_dev, "%s: interface %d power %s.\n", __func__,
+ ifsel, onoff == POWER_ON ? "on" : "off");
+ if (mutex_lock_interruptible(&ab5500_pm_mutex)) {
+ dev_warn(ab5500_dev,
+ "%s: Signal received while waiting on the PM mutex.\n",
+ __func__);
+ return;
+ }
+ mask_set_reg(ENV_THR, ENV_THR_HIGH_MASK, 0x0F << ENV_THR_HIGH_SHIFT);
+ mask_set_reg(ENV_THR, ENV_THR_LOW_MASK, 0x00 << ENV_THR_LOW_SHIFT);
+ mask_set_reg(DC_CANCEL, DC_CANCEL_AUXO12_MASK,
+ 0x01 << DC_CANCEL_AUXO12_SHIFT);
+
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_dld_l : widget_if1_dld_l);
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_dld_r : widget_if1_dld_r);
+
+ mutex_unlock(&ab5500_pm_mutex);
+}
+
+static int enable_regulator(enum regulator_idx idx)
+{
+ int ret;
+
+ if (reg_enabled[idx])
+ return 0;
+
+ ret = regulator_enable(reg_info[idx].consumer);
+ if (ret != 0) {
+ pr_err("%s: Failure to enable regulator '%s' (ret = %d)\n",
+ __func__, reg_info[idx].supply, ret);
+ return ret;
+ };
+
+ reg_enabled[idx] = true;
+ pr_debug("%s: Enabled regulator '%s', status: %d, %d\n",
+ __func__,
+ reg_info[idx].supply,
+ (int)reg_enabled[0],
+ (int)reg_enabled[1]);
+ return 0;
+}
+
+static void disable_regulator(enum regulator_idx idx)
+{
+ if (!reg_enabled[idx])
+ return;
+
+ regulator_disable(reg_info[idx].consumer);
+
+ reg_enabled[idx] = false;
+ pr_debug("%s: Disabled regulator '%s', status: %d, %d\n",
+ __func__,
+ reg_info[idx].supply,
+ (int)reg_enabled[0],
+ (int)reg_enabled[1]);
+}
+
+static void power_for_capture(enum enum_power onoff, int ifsel)
+{
+ int err;
+ int mask;
+
+ dev_info(ab5500_dev, "%s: interface %d power %s", __func__,
+ ifsel, onoff == POWER_ON ? "on" : "off");
+ if (mutex_lock_interruptible(&ab5500_pm_mutex)) {
+ dev_warn(ab5500_dev,
+ "%s: Signal received while waiting on the PM mutex.\n",
+ __func__);
+ return;
+ }
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_uld_l : widget_if1_uld_l);
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_uld_r : widget_if1_uld_r);
+
+ mask = (read_reg(TX2) & TXx_MUX_MASK) >> TXx_MUX_SHIFT;
+
+ switch (onoff << 2 | mask) {
+ case 0: /* Power off : Amic */
+ disable_regulator(REGULATOR_AMIC);
+ break;
+ case 1: /* Power off : Dmic */
+ case 2:
+ disable_regulator(REGULATOR_DMIC);
+ break;
+ case 4: /* Power on : Amic */
+ err = enable_regulator(REGULATOR_AMIC);
+ if (err < 0)
+ goto unlock;
+ break;
+ case 5: /* Power on : Dmic */
+ case 6:
+ err = enable_regulator(REGULATOR_DMIC);
+ if (err < 0)
+ goto unlock;
+ break;
+ default:
+ pr_debug("%s : Not a valid regulator combination\n",
+ __func__);
+ break;
+ }
+unlock:
+ mutex_unlock(&ab5500_pm_mutex);
+}
+
+static int ab5500_add_controls(struct snd_soc_codec *codec)
+{
+ int err = 0, i, n = ARRAY_SIZE(ab5500_snd_controls);
+
+ pr_info("%s: %s called.\n", __FILE__, __func__);
+ for (i = 0; i < n; i++) {
+ err = snd_ctl_add(codec->card->snd_card, snd_ctl_new1(
+ &ab5500_snd_controls[i], codec));
+ if (err < 0) {
+ pr_err("%s failed to add control No.%d of %d.\n",
+ __func__, i, n);
+ return err;
+ }
+ }
+ return err;
+}
+
+static int ab5500_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ dai->playback_active : dai->capture_active) {
+ dev_err(dai->dev, "A %s stream is already active.\n",
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "playback" : "capture");
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int ab5500_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params,
+ struct snd_soc_dai *dai)
+{
+ u8 val;
+ u8 reg = dai->id == 0 ? INTERFACE0 : INTERFACE1;
+
+ if (!ab5500_dev) {
+ pr_err("%s: The AB5500 codec driver not initialized.\n",
+ __func__);
+ return -EAGAIN;
+ }
+ dev_info(ab5500_dev, "%s called.\n", __func__);
+ switch (params_rate(hw_params)) {
+ case 8000:
+ val = I2Sx_SR_8000Hz;
+ break;
+ case 16000:
+ val = I2Sx_SR_16000Hz;
+ break;
+ case 44100:
+ val = I2Sx_SR_44100Hz;
+ break;
+ case 48000:
+ val = I2Sx_SR_48000Hz;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ !dai->capture_active : !dai->playback_active) {
+
+ mask_set_reg(reg, I2Sx_SR_MASK, val << I2Sx_SR_SHIFT);
+ if ((read_reg(reg) & I2Sx_MODE_MASK) == 0) {
+ mask_set_reg(reg, MASTER_GENx_PWR_MASK,
+ 1 << MASTER_GENx_PWR_SHIFT);
+ }
+ }
+ return 0;
+}
+
+static int ab5500_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ u8 value = (dai->id == 1) ? INTERFACE1 : INTERFACE0;
+
+ dev_info(ab5500_dev, "%s called.\n", __func__);
+
+ /* Configure registers for either playback or capture */
+ if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) &&
+ !(ab5500_codec_privates[dai->id].playback_active == true)) {
+ power_for_playback(POWER_ON, dai->id);
+ ab5500_codec_privates[dai->id].playback_active = true;
+ mask_set_reg(value, I2Sx_TRISTATE_MASK,
+ 0 << I2Sx_TRISTATE_SHIFT);
+ } else if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) &&
+ !(ab5500_codec_privates[dai->id].capture_active == true)) {
+ power_for_capture(POWER_ON, dai->id);
+ ab5500_codec_privates[dai->id].capture_active = true;
+ mask_set_reg(value, I2Sx_TRISTATE_MASK,
+ 0 << I2Sx_TRISTATE_SHIFT);
+
+ }
+ mutex_lock(&ab5500_clk_mutex);
+ ab5500_clk_request++;
+ if (ab5500_clk_request == 1)
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 1 << CLOCK_ENABLE_SHIFT);
+ mutex_unlock(&ab5500_clk_mutex);
+
+ dump_registers(__func__, RX1, AUXO1_ADDER, RX2,
+ AUXO2_ADDER, RX1_DPGA, RX2_DPGA, AUXO1, AUXO2, -1);
+
+ return 0;
+}
+
+static void ab5500_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ u8 iface = (dai->id == 0) ? INTERFACE0 : INTERFACE1;
+ dev_info(ab5500_dev, "%s called.\n", __func__);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ power_for_playback(POWER_OFF, dai->id);
+ ab5500_codec_privates[dai->id].playback_active = false;
+ } else {
+ power_for_capture(POWER_OFF, dai->id);
+ ab5500_codec_privates[dai->id].capture_active = false;
+ }
+ if (!dai->playback_active && !dai->capture_active &&
+ (read_reg(iface) & I2Sx_MODE_MASK) == 0) {
+ mask_set_reg(iface, I2Sx_TRISTATE_MASK,
+ 1 << I2Sx_TRISTATE_SHIFT);
+ mask_set_reg(iface, MASTER_GENx_PWR_MASK, 0);
+ }
+ mutex_lock(&ab5500_clk_mutex);
+ ab5500_clk_request--;
+ if (ab5500_clk_request == 0)
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0 << CLOCK_ENABLE_SHIFT);
+ mutex_unlock(&ab5500_clk_mutex);
+}
+
+static int ab5500_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ return 0;
+}
+
+static int ab5500_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ u8 iface = (codec_dai->id == 0) ? INTERFACE0 : INTERFACE1;
+ u8 val = 0;
+ dev_info(ab5500_dev, "%s called.\n", __func__);
+
+ switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
+ SND_SOC_DAIFMT_MASTER_MASK)) {
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+ val |= 1 << I2Sx_MODE_SHIFT;
+ mask_set_reg(iface, I2Sx_MODE_MASK, val);
+ break;
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
+ break;
+
+ default:
+ dev_warn(ab5500_dev, "AB5500_dai: unsupported DAI format "
+ "0x%x\n", fmt);
+ return -EINVAL;
+ }
+ if (codec_dai->playback_active && codec_dai->capture_active) {
+ if ((read_reg(iface) & I2Sx_MODE_MASK) == val)
+ return 0;
+ else {
+ dev_err(ab5500_dev,
+ "%s: DAI format set differently "
+ "by an existing stream.\n", __func__);
+ return -EINVAL;
+ }
+ }
+ mask_set_reg(iface, I2Sx_MODE_MASK, val);
+ return 0;
+}
+
+struct snd_soc_dai_driver ab5500_dai_drv[] = {
+ {
+ .name = "ab5500-codec-dai.0",
+ .id = 0,
+ .playback = {
+ .stream_name = "ab5500.0 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB5500_SUPPORTED_RATE,
+ .formats = AB5500_SUPPORTED_FMT,
+ },
+ .capture = {
+ .stream_name = "ab5500.0 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB5500_SUPPORTED_RATE,
+ .formats = AB5500_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .startup = ab5500_pcm_startup,
+ .prepare = ab5500_pcm_prepare,
+ .hw_params = ab5500_pcm_hw_params,
+ .shutdown = ab5500_pcm_shutdown,
+ .set_sysclk = ab5500_set_dai_sysclk,
+ .set_fmt = ab5500_set_dai_fmt,
+ }
+ },
+ .symmetric_rates = 1,
+ },
+ {
+ .name = "ab5500-codec-dai.1",
+ .id = 1,
+ .playback = {
+ .stream_name = "ab5500.1 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB5500_SUPPORTED_RATE,
+ .formats = AB5500_SUPPORTED_FMT,
+ },
+ .capture = {
+ .stream_name = "ab5500.1 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB5500_SUPPORTED_RATE,
+ .formats = AB5500_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .startup = ab5500_pcm_startup,
+ .prepare = ab5500_pcm_prepare,
+ .hw_params = ab5500_pcm_hw_params,
+ .shutdown = ab5500_pcm_shutdown,
+ .set_sysclk = ab5500_set_dai_sysclk,
+ .set_fmt = ab5500_set_dai_fmt,
+ }
+ },
+ .symmetric_rates = 1,
+ }
+};
+
+static int ab5500_codec_probe(struct snd_soc_codec *codec)
+{
+ int ret = ab5500_add_controls(codec);
+ if (ret < 0)
+ return ret;
+ ab5500_add_widgets(codec);
+ return 0;
+}
+
+static int ab5500_codec_remove(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_free(&codec->dapm);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ab5500_codec_suspend(struct snd_soc_codec *codec,
+ pm_message_t state)
+{
+ if (!ab5500_clk_request)
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0);
+ return 0;
+}
+
+static int ab5500_codec_resume(struct snd_soc_codec *codec)
+{
+ if (ab5500_clk_request)
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0xff);
+ return 0;
+}
+#else
+#define ab5500_codec_resume NULL
+#define ab5500_codec_suspend NULL
+#endif
+
+/**
+ This function is only called by the SOC framework to
+ set registers associated to the mixer controls.
+*/
+static int ab5500_codec_write_reg(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ if (reg < MIC_BIAS1 || reg > INTERFACE_SWAP)
+ return -EINVAL;
+ switch (reg) {
+ u8 diff, oldval;
+ case ANALOG_LOOP_PGA1:
+ case ANALOG_LOOP_PGA2: {
+ enum enum_widget apga = reg == ANALOG_LOOP_PGA1 ?
+ widget_apga1 : widget_apga2;
+
+ oldval = read_reg(reg);
+ diff = value ^ oldval;
+
+ /*
+ * The APGA is to be turned on/off. The power bit and the
+ * other bits in the same register won't be changed at the
+ * same time since they belong to different controls.
+ */
+ if (diff & (1 << APGAx_PWR_SHIFT)) {
+ power_widget_locked(value >> APGAx_PWR_SHIFT & 1,
+ apga);
+ } else if (diff & APGAx_MUX_MASK) {
+ enum enum_widget old_source =
+ apga_source_translate(oldval);
+ enum enum_widget new_source =
+ apga_source_translate(value);
+ update_widgets_link(UNLINK, old_source, apga,
+ reg, APGAx_MUX_MASK, 0);
+ update_widgets_link(LINK, new_source, apga,
+ reg, APGAx_MUX_MASK, value);
+ } else {
+ set_reg(reg, value);
+ }
+ break;
+ }
+
+ case AUXO3_ADDER:
+ case AUXO4_ADDER:
+ case SPKR2_ADDER:
+ case LINE1_ADDER:
+ case LINE2_ADDER: {
+ int i;
+ enum enum_widget widgets[] = {
+ widget_dac1, widget_dac2, widget_dac3,
+ widget_apga1, widget_apga2
+ };
+ oldval = read_reg(reg);
+ diff = value ^ oldval;
+ for (i = 0; diff; i++) {
+ if (!(diff & 1 << i))
+ continue;
+ diff ^= 1 << i;
+ update_widgets_link(value >> i & 1, widgets[i],
+ adder_sink_translate(reg),
+ reg, 1 << i, value);
+ }
+ break;
+ }
+ case AB5500_VIRTUAL_REG3:
+ oldval = read_reg(reg);
+ diff = value ^ oldval;
+ /*
+ * The following changes won't take place in the same call,
+ * since they are arranged into different mixer controls.
+ */
+
+ /* changed between the two amplifier modes */
+ if (count_ones(diff & SPKR1_MODE_MASK) == 2) {
+ set_reg(reg, value);
+ break;
+ }
+
+ if (diff & SPKR1_MODE_MASK) {
+ update_widgets_link(
+ UNLINK,
+ (oldval & SPKR1_MODE_MASK) == 0 ?
+ widget_pwm_spkr1 : widget_spkr1_adder,
+ widget_spkr1,
+ reg, SPKR1_MODE_MASK, value);
+ update_widgets_link(
+ LINK,
+ (value & SPKR1_MODE_MASK) == 0 ?
+ widget_pwm_spkr1 : widget_spkr1_adder,
+ widget_spkr1,
+ DUMMY_REG, 0, 0);
+
+ }
+ if (diff & SPKR2_MODE_MASK) {
+ update_widgets_link(
+ UNLINK,
+ (oldval & SPKR2_MODE_MASK) == 0 ?
+ widget_pwm_spkr2 : widget_spkr2_adder,
+ widget_spkr2,
+ reg, SPKR2_MODE_MASK, value);
+ update_widgets_link(
+ LINK,
+ (value & SPKR2_MODE_MASK) == 0 ?
+ widget_pwm_spkr2 : widget_spkr2_adder,
+ widget_spkr2,
+ DUMMY_REG, 0, 0);
+
+ }
+ break;
+
+ case AB5500_VIRTUAL_REG4:
+ /* configure PWMCTRL_SPKR1, PWMCTRL_SPKR2, etc. */
+ break;
+ default:
+ set_reg(reg, value);
+ }
+ return 0;
+}
+
+static unsigned int ab5500_codec_read_reg(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return read_reg(reg);
+}
+
+
+static struct snd_soc_codec_driver ab5500_codec_drv = {
+ .probe = ab5500_codec_probe,
+ .remove = ab5500_codec_remove,
+ .suspend = ab5500_codec_suspend,
+ .resume = ab5500_codec_resume,
+ .read = ab5500_codec_read_reg,
+ .write = ab5500_codec_write_reg,
+};
+EXPORT_SYMBOL_GPL(ab5500_codec_drv);
+
+static inline void init_playback_route(void)
+{
+ /* if0_dld_l -> rx1 -> dac1 -> auxo1 */
+ update_widgets_link(LINK, widget_if0_dld_l, widget_rx1, 0, 0, 0);
+ update_widgets_link(LINK, widget_rx1, widget_dac1, 0, 0, 0);
+ update_widgets_link(LINK, widget_dac1, widget_auxo1, 0, 0, 0);
+
+ /* if0_dld_r -> rx2 -> dac2 -> auxo2 */
+ update_widgets_link(LINK, widget_if0_dld_r, widget_rx2, 0, 0, 0);
+ update_widgets_link(LINK, widget_rx2, widget_dac2, 0, 0, 0);
+ update_widgets_link(LINK, widget_dac2, widget_auxo2, 0, 0, 0);
+
+ /* Earpiece */
+ update_widgets_link(LINK, widget_dac1, widget_ear, 0, 0, 0);
+
+ /* if1_dld_l -> rx3 -> dac3 -> spkr1 */
+ update_widgets_link(LINK, widget_if1_dld_l, widget_rx3, 0, 0, 0);
+ update_widgets_link(LINK, widget_rx3, widget_dac3, 0, 0, 0);
+ update_widgets_link(LINK, widget_dac3, widget_spkr1, 0, 0, 0);
+
+}
+
+static inline void init_capture_route(void)
+{
+ /* mic bias - > mic2 inputs */
+ update_widgets_link(LINK, widget_micbias1, widget_mic2p2, 0, 0, 0);
+ update_widgets_link(LINK, widget_micbias1, widget_mic2n2, 0, 0, 0);
+
+ /* mic2 inputs -> mic2 */
+ update_widgets_link(LINK, widget_mic2p2, widget_mic2, 0, 0, 0);
+ update_widgets_link(LINK, widget_mic2n2, widget_mic2, 0, 0, 0);
+
+ /* mic2 -> adc2 -> tx2 */
+ update_widgets_link(LINK, widget_mic2, widget_adc2, 0, 0, 0);
+ update_widgets_link(LINK, widget_adc2, widget_tx2, 0, 0, 0);
+
+ /* tx2 -> if0_uld_l & if0_uld_r */
+ update_widgets_link(LINK, widget_tx2, widget_if0_uld_l, 0, 0, 0);
+ update_widgets_link(LINK, widget_tx2, widget_if0_uld_r, 0, 0, 0);
+}
+
+static int create_regulators(void)
+{
+ int i, status = 0;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ for (i = 0; i < ARRAY_SIZE(reg_info); ++i)
+ reg_info[i].consumer = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(reg_info); ++i) {
+ reg_info[i].consumer = regulator_get(ab5500_dev,
+ reg_info[i].supply);
+ if (IS_ERR(reg_info[i].consumer)) {
+ status = PTR_ERR(reg_info[i].consumer);
+ pr_err("%s: ERROR: Failed to get regulator '%s' (ret = %d)!\n",
+ __func__, reg_info[i].supply, status);
+ reg_info[i].consumer = NULL;
+ goto err_get;
+ }
+ }
+
+ return 0;
+
+err_get:
+
+ for (i = 0; i < ARRAY_SIZE(reg_info); ++i) {
+ if (reg_info[i].consumer) {
+ regulator_put(reg_info[i].consumer);
+ reg_info[i].consumer = NULL;
+ }
+ }
+
+ return status;
+}
+
+static int __devinit ab5500_platform_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ u8 reg;
+ struct ab5500_codec_dai_data *codec_drvdata;
+ int status;
+
+ pr_info("%s invoked with pdev = %p.\n", __func__, pdev);
+ ab5500_dev = &pdev->dev;
+
+ status = create_regulators();
+ if (status < 0) {
+ pr_err("%s: ERROR: Failed to instantiate regulators (ret = %d)!\n",
+ __func__, status);
+ return status;
+ }
+ codec_drvdata = kzalloc(sizeof(struct ab5500_codec_dai_data),
+ GFP_KERNEL);
+ if (codec_drvdata == NULL)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, codec_drvdata);
+ ret = snd_soc_register_codec(ab5500_dev, &ab5500_codec_drv,
+ ab5500_dai_drv,
+ ARRAY_SIZE(ab5500_dai_drv));
+ if (ret < 0) {
+ dev_err(ab5500_dev, "%s: Failed to register codec. "
+ "Error %d.\n", __func__, ret);
+ snd_soc_unregister_codec(ab5500_dev);
+ kfree(codec_drvdata);
+ }
+ /* Initialize the codec registers */
+ for (reg = AB5500_FIRST_REG; reg <= AB5500_LAST_REG; reg++)
+ set_reg(reg, 0);
+
+ mask_set_reg(INTERFACE0, I2Sx_TRISTATE_MASK, 1 << I2Sx_TRISTATE_SHIFT);
+ mask_set_reg(INTERFACE1, I2Sx_TRISTATE_MASK, 1 << I2Sx_TRISTATE_SHIFT);
+
+ printk(KERN_ERR "Clock Setting ab5500\n");
+ init_playback_route();
+ init_capture_route();
+ memset(&pm_stack, 0, sizeof(pm_stack));
+ return ret;
+}
+
+static int __devexit ab5500_platform_remove(struct platform_device *pdev)
+{
+ pr_info("%s called.\n", __func__);
+ regulator_bulk_free(ARRAY_SIZE(reg_info), reg_info);
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0);
+ snd_soc_unregister_codec(ab5500_dev);
+ kfree(platform_get_drvdata(pdev));
+ ab5500_dev = NULL;
+ return 0;
+}
+
+static int ab5500_platform_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ return 0;
+}
+
+static int ab5500_platform_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver ab5500_platform_driver = {
+ .driver = {
+ .name = "ab5500-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ab5500_platform_probe,
+ .remove = ab5500_platform_remove,
+ .suspend = ab5500_platform_suspend,
+ .resume = ab5500_platform_resume,
+};
+
+static int __devinit ab5500_init(void)
+{
+ int ret;
+
+ pr_info("%s called.\n", __func__);
+
+ /* Register codec platform driver. */
+ ret = platform_driver_register(&ab5500_platform_driver);
+ if (ret) {
+ pr_err("%s: Error %d: Failed to register codec platform "
+ "driver.\n", __func__, ret);
+ }
+ return ret;
+}
+
+static void __devexit ab5500_exit(void)
+{
+ pr_info("%s called.\n", __func__);
+ platform_driver_unregister(&ab5500_platform_driver);
+}
+
+module_init(ab5500_init);
+module_exit(ab5500_exit);
+
+MODULE_DESCRIPTION("AB5500 Codec driver");
+MODULE_AUTHOR("Xie Xiaolei <xie.xiaolei@stericsson.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ab5500.h b/sound/soc/codecs/ab5500.h
new file mode 100644
index 00000000000..bb37798b8c7
--- /dev/null
+++ b/sound/soc/codecs/ab5500.h
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Register definitions for AB5500 codec
+ * Author: Xie Xiaolei <xie.xiaolei@etericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+#ifndef AB5500_CODEC_REGISTERS_H
+#define AB5500_CODEC_REGISTERS_H
+
+#define AB5500_SUPPORTED_RATE (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define AB5500_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+
+/* MIC BIAS */
+
+#define MIC_BIAS1 0x00
+#define MIC_BIAS2 0x01
+#define MBIAS2_OUT_V_MASK 0x04
+#define MBIAS2_OUT_V_SHIFT 2
+#define MBIASx_PWR_MASK 0x02
+#define MBIASx_PWR_SHIFT 1
+#define MBIASx_PDN_IMP_MASK 0x01
+#define MBIASx_PDN_IMP_SHIFT 0
+
+#define MIC_BIAS2_VAD 0x02
+#define MBIAS2_R_INT_MASK 0x01
+#define MBIAS2_R_INT_SHIFT 0
+
+/* MIC */
+#define MIC1_GAIN 0x03
+#define MIC2_GAIN 0x04
+#define MICx_GAIN_MASK 0xF0
+#define MICx_GAIN_SHIFT 4
+#define MICx_IN_IMP_MASK 0x0C
+#define MICx_IN_IMP_SHIFT 2
+#define MICx_PWR_MASK 0x01
+#define MICx_PWR_SHIFT 0
+
+#define MIC1_INPUT_SELECT 0x05
+#define MIC2_INPUT_SELECT 0x06
+#define MICxP1_SEL_MASK 0x80
+#define MICxP1_SEL_SHIFT 7
+#define MICxN1_SEL_MASK 0x40
+#define MICxN1_SEL_SHIFT 6
+#define MICxP2_SEL_MASK 0x20
+#define MICxP2_SEL_SHIFT 5
+#define MICxN2_SEL_MASK 0x10
+#define MICxN2_SEL_SHIFT 4
+#define LINEIN_SEL_MASK 0x03
+#define LINEIN_SEL_SHIFT 0
+
+#define MIC1_VMID_SELECT 0x07
+#define MIC2_VMID_SELECT 0x08
+#define VMIDx_ENABLE_MASK 0xC0
+#define VMIDx_ENABLE_SHIFT 6
+#define VMIDx_LINEIN1_N_MASK 0x20
+#define VMIDx_LINEIN1_N_SHIFT 5
+#define VMIDx_LINEIN2_N_MASK 0x10
+#define VMIDx_LINEIN2_N_SHIFT 4
+#define VMIDx_MICxP1_MASK 0x08
+#define VMIDx_MICxP1_SHIFT 3
+#define VMIDx_MICxP2_MASK 0x04
+#define VMIDx_MICxP2_SHIFT 2
+#define VMIDx_MICxN1_MASK 0x02
+#define VMIDx_MICxN1_SHIFT 1
+#define VMIDx_MICxN2_MASK 0x01
+#define VMIDx_MICxN2_SHIFT 0
+
+#define MIC2_TO_MIC1 0x09
+#define MIC2_TO_MIC1_MASK 0x03
+#define MIC2_TO_MIC1_SHIFT 0
+
+/* Analog Loop */
+#define ANALOG_LOOP_PGA1 0x0A
+#define ANALOG_LOOP_PGA2 0x0B
+#define APGAx_GAIN_MASK 0xF8
+#define APGAx_GAIN_SHIFT 3
+#define APGAx_PWR_MASK 0x04
+#define APGAx_PWR_SHIFT 2
+#define APGAx_MUX_MASK 0x03
+#define APGAx_MUX_SHIFT 0
+#define APGAx_MUX_MIC1_MASK 0x01
+#define APGAx_MUX_MIC1_SHIFT 0
+#define APGAx_MUX_MIC2_MASK 0x02
+#define APGAx_MUX_MIC2_SHIFT 1
+
+#define APGA_VMID_SELECT 0x0C
+#define VMID_APGA1_ENABLE_MASK 0xC0
+#define VMID_APGA1_ENABLE_SHIFT 6
+#define VMID_APGA1_LINEIN1_MASK 0x20
+#define VMID_APGA1_LINEIN1_SHIFT 5
+#define VMID_APGA2_ENABLE_MASK 0x0C
+#define VMID_APGA2_ENABLE_SHIFT 2
+#define VMID_APGA2_LINEIN2_MASK 0x02
+#define VMID_APGA2_LINEIN2_SHIFT 1
+
+/* Output Amplifiers */
+#define EAR_PWR 0x0D
+#define EAR_PWR_MODE_MASK 0xC0
+#define EAR_PWR_MODE_SHIFT 6
+#define EAR_PWR_VMID_MASK 0x30
+#define EAR_PWR_VMID_SHIFT 4
+#define EAR_PWR_MASK 0x01
+#define EAR_PWR_SHIFT 0
+
+#define EAR_GAIN 0x0E
+#define EAR_GAIN_MASK 0x1F
+#define EAR_GAIN_SHIFT 0
+
+#define AUXO1 0x0F
+#define AUXO2 0x10
+#define AUXO3 0x11
+#define AUXO4 0x12
+#define AUXOx_PWR_MASK 0x80
+#define AUXOx_PWR_SHIFT 7
+#define AUXOx_INV_MASK 0x40
+#define AUXOx_INV_SHIFT 6
+#define AUXOx_PULLDOWN_MASK 0x20
+#define AUXOx_PULLDOWN_SHIFT 5
+#define AUXOx_GAIN_MASK 0x0F
+#define AUXOx_GAIN_SHIFT 0
+
+#define AUXO12_PWR_MODE 0x13
+#define AUXO34_PWR_MODE 0x14
+#define AUXOxy_PWR_MODE_MASK 0x07
+#define AUXOxy_PWR_MODE_SHIFT 0
+
+#define NEG_CHARGE_PUMP 0x15
+#define NEG_CHARGE_PUMP_MODE_MASK 0x02
+#define NEG_CHARGE_PUMP_MODE_SHIFT 1
+#define NEG_CHARGE_PUMP_PWR_MASK 0x01
+#define NEG_CHARGE_PUMP_PWR_SHIFT 0
+
+#define ENV_THR 0x16
+#define ENV_THR_HIGH_MASK 0xF0
+#define ENV_THR_HIGH_SHIFT 4
+#define ENV_THR_LOW_MASK 0x0F
+#define ENV_THR_LOW_SHIFT 0
+
+#define ENV_DECAY_TIME 0x17
+#define ENV_DECAY_TIME_CP_LV_MASK 0x20
+#define ENV_DECAY_TIME_CP_LV_SHIFT 5
+#define ENV_DECAY_TIME_DET_CP_MASK 0x10
+#define ENV_DECAY_TIME_DET_CP_SHIFT 4
+#define ENV_DECAY_TIME_MASK 0x0F
+#define ENV_DECAY_TIME_SHIFT 0
+
+#define DC_CANCEL 0x18
+#define DC_CANCEL_SPKR2_MASK 0x10
+#define DC_CANCEL_SPKR2_SHIFT 4
+#define DC_CANCEL_SPKR1_MASK 0x08
+#define DC_CANCEL_SPKR1_SHIFT 3
+#define DC_CANCEL_AUXO34_MASK 0x04
+#define DC_CANCEL_AUXO34_SHIFT 2
+#define DC_CANCEL_AUXO12_MASK 0x02
+#define DC_CANCEL_AUXO12_SHIFT 1
+#define DC_CANCEL_OFFSET_CLOCK_MASK 0x01
+#define DC_CANCEL_OFFSET_CLOCK_SHIFT 0
+
+#define SPKR1 0x19
+#define SPKR2 0x1A
+#define SPKRx_PWR_MASK 0xC0
+#define SPKRx_PWR_SHIFT 6
+#define SPKRx_PWR_VBR_VALUE 0x40
+#define SPKRx_PWR_CLS_D_VALUE 0x80
+#define SPKRx_PWR_CLS_AB_VALUE 0xC0
+#define SPKR1_VMID_MASK 0x20
+#define SPKR1_VMID_SHIFT 5
+#define SPKRx_GAIN_MASK 0x1F
+#define SPKRx_GAIN_SHIFT 0
+
+#define SPKR_OVCR 0x1B
+#define SPKR_OVCR_PROT2_MASK 0x80
+#define SPKR_OVCR_PROT2_SHIFT 7
+#define SPKR_OVCR_TRIM2_MASK 0x70
+#define SPKR_OVCR_TRIM2_SHIFT 4
+#define SPKR_OVCR_PROT1_MASK 0x08
+#define SPKR_OVCR_PROT1_SHIFT 3
+#define SPKR_OVCR_TRIM1_MASK 0x07
+#define SPKR_OVCR_TRIM1_SHIFT 0
+
+#define PWMCTRL_SPKR1 0x1C
+#define PWMCTRL_SPKR2 0x1F
+#define PWMCTRL_SPKRx_N1_POL_MASK 0x80
+#define PWMCTRL_SPKRx_N1_POL_SHIFT 7
+#define PWMCTRL_SPKRx_P1_POL_MASK 0x40
+#define PWMCTRL_SPKRx_P1_POL_SHIFT 6
+#define PWMCTRL_SPKRx_MASK 0x04
+#define PWMCTRL_SPKRx_SHIFT 2
+#define PWMCTRL_SPKRxN_MASK 0x02
+#define PWMCTRL_SPKRxN_SHIFT 1
+#define PWMCTRL_SPKRxP_MASK 0x01
+#define PWMCTRL_SPKRxP_SHIFT 0
+
+#define PWM_SPKR1N 0x1D
+#define PWM_SPKR2N 0x20
+#define PWM_SPKR1P 0x1E
+#define PWM_SPKR2P 0x21
+#define PWM_SPKRxy_DUT_CYC_MASK 0xFF
+#define PWM_SPKRxy_DUT_CYC_SHIFT 0
+
+#define SPKR1_CLK_DIV 0x22
+#define SPKR2_CLK_DIV 0x23
+#define SPKRx_CLK_DIV_MASK 0x3F
+#define SPKRx_CLK_DIV_SHIFT 0
+
+#define LINE1 0x24
+#define LINE2 0x25
+#define LINEx_PWR_MASK 0x80
+#define LINEx_PWR_SHIFT 7
+#define LINEx_INV_MASK 0x40
+#define LINEx_INV_SHIFT 6
+#define LINEx_TO_USB_MASK 0x20
+#define LINEx_TO_USB_SHIFT 5
+#define LINEx_VMID_BUFF_MASK 0x10
+#define LINEx_VMID_BUFF_SHIFT 4
+#define LINEx_GAIN_MASK 0x0F
+#define LINEx_GAIN_SHIFT 0
+
+#define USB_AUDIO 0x26
+#define USB_AUDIO_MIC_MUX_MASK 0x03
+#define USB_AUDIO_MIC_MUX_SHIFT 0
+
+#define EAR_ADDER 0x28
+#define AUXO1_ADDER 0x29
+#define AUXO2_ADDER 0x2A
+#define AUXO3_ADDER 0x2B
+#define AUXO4_ADDER 0x2C
+#define SPKR1_ADDER 0x2D
+#define SPKR2_ADDER 0x2E
+#define LINE1_ADDER 0x2F
+#define LINE2_ADDER 0x30
+#define APGA2_TO_X_MASK 0x10
+#define APGA2_TO_X_SHIFT 4
+#define APGA1_TO_X_MASK 0x08
+#define APGA1_TO_X_SHIFT 3
+#define DAC3_TO_X_MASK 0x04
+#define DAC3_TO_X_SHIFT 2
+#define DAC2_TO_X_MASK 0x02
+#define DAC2_TO_X_SHIFT 1
+#define DAC1_TO_X_MASK 0x01
+#define DAC1_TO_X_SHIFT 0
+
+#define EAR_TO_MIC2 0x31
+#define SPKR1_TO_MIC2 0x32
+#define SPKR2_TO_MIC2 0x33
+#define EAR_TO_MIC2_MASK 0x01
+#define EAR_TO_MIC2_SHIFT 0
+
+#define ADC_LOW_PWR 0x35
+#define ADC_LOW_PWR_MASK 0x01
+#define ADC_LOW_PWR_SHIFT 0
+
+#define TX1 0x36
+#define TX2 0x37
+#define TXx_MUX_MASK 0x60
+#define TXx_MUX_SHIFT 5
+#define TXx_FS_MASK 0x10
+#define TXx_FS_SHIFT 4
+#define TXx_HP_FILTER_MASK 0x0C
+#define TXx_HP_FILTER_SHIFT 2
+#define TXx_PWR_MASK 0x02
+#define TXx_PWR_SHIFT 1
+#define ADCx_PWR_MASK 0x01
+#define ADCx_PWR_SHIFT 0
+
+#define RX1 0x38
+#define RX2 0x39
+#define RX3 0x3A
+#define RXx_DATA_MASK 0x70
+#define RXx_DATA_SHIFT 4
+#define RXx_PWR_MASK 0x08
+#define RXx_PWR_SHIFT 3
+#define DACx_PWR_MASK 0x04
+#define DACx_PWR_SHIFT 2
+#define DACx_PWR_MODE_MASK 0x03
+#define DACx_PWR_MODE_SHIFT 0
+
+#define TX_DPGA1 0x3B
+#define TX_DPGA2 0x3C
+#define TX_DPGAx_MASK 0x0F
+#define TX_DPGAx_SHIFT 0
+
+#define RX1_DPGA 0x3D
+#define RX2_DPGA 0x3E
+#define RX3_DPGA 0x3F
+#define RXx_DPGA_MASK 0x7F
+#define RXx_DPGA_SHIFT 0
+
+#define ST1_PGA 0x40
+#define ST2_PGA 0x41
+#define STx_HP_FILTER_MASK 0x60
+#define STx_HP_FILTER_SHIFT 6
+#define STx_MUX_MASK 0x10
+#define STx_MUX_SHIFT 4
+#define STx_PGA_MASK 0x0F
+#define STx_PGA_SHIFT 0
+
+#define CLOCK 0x42
+#define CLOCK_REF_SELECT_MASK 0x02
+#define CLOCK_REF_SELECT_SHIFT 1
+#define CLOCK_ENABLE_MASK 0x01
+#define CLOCK_ENABLE_SHIFT 0
+
+#define INTERFACE0 0x43
+#define INTERFACE1 0x45
+#define I2Sx_WORDLENGTH_MASK 0x40
+#define I2Sx_WORDLENGTH_SHIFT 6
+#define MASTER_GENx_PWR_MASK 0x20
+#define MASTER_GENx_PWR_SHIFT 5
+#define I2Sx_MODE_MASK 0x10
+#define I2Sx_MODE_SHIFT 4
+#define I2Sx_TRISTATE_MASK 0x08
+#define I2Sx_TRISTATE_SHIFT 3
+#define I2Sx_PULLDOWN_MASK 0x04
+#define I2Sx_PULLDOWN_SHIFT 2
+#define I2Sx_SR_MASK 0x03
+#define I2Sx_SR_SHIFT 0
+#define I2Sx_SR_8000Hz 0
+#define I2Sx_SR_16000Hz 1
+#define I2Sx_SR_44100Hz 2
+#define I2Sx_SR_48000Hz 3
+
+#define INTERFACE0_ULD 0x44
+#define INTERFACE1_ULD 0x46
+#define I2Sx_ULD_R_MASK 0x70
+#define I2Sx_ULD_R_SHIFT 4
+#define I2Sx_ULD_L_MASK 0x07
+#define I2Sx_ULD_L_SHIFT 0
+
+#define INTERFACE_SWAP 0x47
+#define IO_SWAP0_MASK 0x02
+#define IO_SWAP0_SHIFT 1
+#define IO_SWAP1_MASK 0x01
+#define IO_SWAP1_SHIFT 0
+
+#define AB5500_FIRST_REG MIC_BIAS1
+#define AB5500_LAST_REG INTERFACE_SWAP
+
+#define AB5500_VIRTUAL_REG1 (AB5500_LAST_REG + 1)
+#define IF0_DLD_L_PW_SHIFT 0
+#define IF0_DLD_R_PW_SHIFT 1
+#define IF0_ULD_L_PW_SHIFT 2
+#define IF0_ULD_R_PW_SHIFT 3
+#define IF1_DLD_L_PW_SHIFT 4
+#define IF1_DLD_R_PW_SHIFT 5
+#define IF1_ULD_L_PW_SHIFT 6
+#define IF1_ULD_R_PW_SHIFT 7
+
+#define AB5500_VIRTUAL_REG2 (AB5500_LAST_REG + 2)
+#define MIC1P1_PW_SHIFT 0
+#define MIC1N1_PW_SHIFT 1
+#define MIC1P2_PW_SHIFT 2
+#define MIC1N2_PW_SHIFT 3
+#define MIC2P1_PW_SHIFT 4
+#define MIC2N1_PW_SHIFT 5
+#define MIC2P2_PW_SHIFT 6
+#define MIC2N2_PW_SHIFT 7
+
+#define AB5500_VIRTUAL_REG3 (AB5500_LAST_REG + 3)
+#define SPKR1_MODE_MASK 0x03
+#define SPKR1_MODE_SHIFT 0
+#define SPKR1_MODE_VBR_VALUE 0
+#define SPKR1_MODE_CLS_D_VALUE 1
+#define SPKR1_MODE_CLS_AB_VALUE 2
+#define SPKR1_ADDER_PWR_SHIFT 2
+#define SPKR1_PWR_SHIFT 3
+#define SPKR2_MODE_MASK 0x10
+#define SPKR2_MODE_SHIFT 4
+#define SPKR2_MODE_VBR_VALUE 0
+#define SPKR2_MODE_CLS_D_VALUE 1
+#define SPKR2_ADDER_PWR_SHIFT 5
+#define SPKR2_PWR_SHIFT 6
+
+#define AB5500_VIRTUAL_REG4 (AB5500_LAST_REG + 4)
+#define PWM_SPKR1_PWR_SHIFT 0
+#define PWM_SPKR2_PWR_SHIFT 1
+#define PWM_SPKR1N_PWR_SHIFT 2
+#define PWM_SPKR1P_PWR_SHIFT 3
+#define PWM_SPKR2N_PWR_SHIFT 4
+#define PWM_SPKR2P_PWR_SHIFT 5
+
+#define AB5500_VIRTUAL_REG5 (AB5500_LAST_REG + 5)
+#define PWM_SPKR1N_SEL_SHIFT 0
+#define PWM_SPKR1P_SEL_SHIFT 1
+#define PWM_SPKR2N_SEL_SHIFT 2
+#define PWM_SPKR2P_SEL_SHIFT 3
+
+#define DUMMY_REG 0xff
+
+/* #define SPKR1_PWR_VBR_SHIFT 0 */
+/* #define SPKR1_PWR_CLS_D_SHIFT 1 */
+/* #define SPKR1_PWR_CLS_AB_SHIFT 2 */
+/* #define SPKR2_PWR_VBR_SHIFT 3 */
+/* #define SPKR2_PWR_CLS_D_SHIFT 4 */
+/* #define SPKR2_PWR_CLS_AB_SHIFT 5 */
+
+#endif
diff --git a/sound/soc/codecs/ab8500_audio.c b/sound/soc/codecs/ab8500_audio.c
new file mode 100644
index 00000000000..41899112f38
--- /dev/null
+++ b/sound/soc/codecs/ab8500_audio.c
@@ -0,0 +1,2956 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Mikko J. Lehto <mikko.lehto@symbio.com>,
+ * Mikko Sarmanne <mikko.sarmanne@symbio.com>,
+ * Jarmo K. Kuronen <jarmo.kuronen@symbio.com>,
+ * Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Kristoffer Karlsson <kristoffer.karlsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab5500.h>
+#include <linux/mfd/abx500/ab8500-sysctrl.h>
+#include "ab8500_audio.h"
+
+/* To convert register definition shifts to masks */
+#define BMASK(bsft) (1 << (bsft))
+
+/* Macrocell value definitions */
+#define CLK_32K_OUT2_DISABLE 0x01
+#define INACTIVE_RESET_AUDIO 0x02
+#define ENABLE_AUDIO_CLK_TO_AUDIO_BLK 0x10
+#define ENABLE_VINTCORE12_SUPPLY 0x04
+#define GPIO27_DIR_OUTPUT 0x04
+#define GPIO29_DIR_OUTPUT 0x10
+#define GPIO31_DIR_OUTPUT 0x40
+
+/* Macrocell register definitions */
+#define AB8500_CTRL3_REG 0x0200
+#define AB8500_GPIO_DIR4_REG 0x1013
+
+/* Nr of FIR/IIR-coeff banks in ANC-block */
+#define AB8500_NR_OF_ANC_COEFF_BANKS 2
+
+/* Minimum duration to keep ANC IIR Init bit high or
+low before proceeding with the configuration sequence */
+#define AB8500_ANC_SM_DELAY 2000
+
+/*
+ * AB8500 register cache & default register settings
+ */
+static const u8 ab8500_reg_cache[AB8500_CACHEREGNUM] = {
+ 0x00, /* REG_POWERUP (0x00) */
+ 0x00, /* REG_AUDSWRESET (0x01) */
+ 0x00, /* REG_ADPATHENA (0x02) */
+ 0x00, /* REG_DAPATHENA (0x03) */
+ 0x00, /* REG_ANACONF1 (0x04) */
+ 0x0F, /* REG_ANACONF2 (0x05) */
+ 0x00, /* REG_DIGMICCONF (0x06) */
+ 0x00, /* REG_ANACONF3 (0x07) */
+ 0x00, /* REG_ANACONF4 (0x08) */
+ 0x00, /* REG_DAPATHCONF (0x09) */
+ 0x40, /* REG_MUTECONF (0x0A) */
+ 0x01, /* REG_SHORTCIRCONF (0x0B) */
+ 0x01, /* REG_ANACONF5 (0x0C) */
+ 0x00, /* REG_ENVCPCONF (0x0D) */
+ 0x00, /* REG_SIGENVCONF (0x0E) */
+ 0x3F, /* REG_PWMGENCONF1 (0x0F) */
+ 0x32, /* REG_PWMGENCONF2 (0x10) */
+ 0x32, /* REG_PWMGENCONF3 (0x11) */
+ 0x32, /* REG_PWMGENCONF4 (0x12) */
+ 0x32, /* REG_PWMGENCONF5 (0x13) */
+ 0x0F, /* REG_ANAGAIN1 (0x14) */
+ 0x0F, /* REG_ANAGAIN2 (0x15) */
+ 0x22, /* REG_ANAGAIN3 (0x16) */
+ 0x55, /* REG_ANAGAIN4 (0x17) */
+ 0x13, /* REG_DIGLINHSLGAIN (0x18) */
+ 0x13, /* REG_DIGLINHSRGAIN (0x19) */
+ 0x00, /* REG_ADFILTCONF (0x1A) */
+ 0x00, /* REG_DIGIFCONF1 (0x1B) */
+ 0x02, /* REG_DIGIFCONF2 (0x1C) */
+ 0x00, /* REG_DIGIFCONF3 (0x1D) */
+ 0x02, /* REG_DIGIFCONF4 (0x1E) */
+ 0xCC, /* REG_ADSLOTSEL1 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL2 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL3 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL4 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL5 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL6 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL7 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL8 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL9 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL10 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL11 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL12 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL13 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL14 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL15 (0xCC) */
+ 0xCC, /* REG_ADSLOTSEL16 (0xCC) */
+ 0x00, /* REG_ADSLOTHIZCTRL1 (0x2F) */
+ 0x00, /* REG_ADSLOTHIZCTRL2 (0x30) */
+ 0x00, /* REG_ADSLOTHIZCTRL3 (0x31) */
+ 0x00, /* REG_ADSLOTHIZCTRL4 (0x32) */
+ 0x08, /* REG_DASLOTCONF1 (0x33) */
+ 0x08, /* REG_DASLOTCONF2 (0x34) */
+ 0x08, /* REG_DASLOTCONF3 (0x35) */
+ 0x08, /* REG_DASLOTCONF4 (0x36) */
+ 0x08, /* REG_DASLOTCONF5 (0x37) */
+ 0x08, /* REG_DASLOTCONF6 (0x38) */
+ 0x08, /* REG_DASLOTCONF7 (0x39) */
+ 0x08, /* REG_DASLOTCONF8 (0x3A) */
+ 0x00, /* REG_CLASSDCONF1 (0x3B) */
+ 0x00, /* REG_CLASSDCONF2 (0x3C) */
+ 0x84, /* REG_CLASSDCONF3 (0x3D) */
+ 0x00, /* REG_DMICFILTCONF (0x3E) */
+ 0xFE, /* REG_DIGMULTCONF1 (0x3F) */
+ 0xC0, /* REG_DIGMULTCONF2 (0x40) */
+ 0x3F, /* REG_ADDIGGAIN1 (0x41) */
+ 0x3F, /* REG_ADDIGGAIN2 (0x42) */
+ 0x1F, /* REG_ADDIGGAIN3 (0x43) */
+ 0x1F, /* REG_ADDIGGAIN4 (0x44) */
+ 0x3F, /* REG_ADDIGGAIN5 (0x45) */
+ 0x3F, /* REG_ADDIGGAIN6 (0x46) */
+ 0x1F, /* REG_DADIGGAIN1 (0x47) */
+ 0x1F, /* REG_DADIGGAIN2 (0x48) */
+ 0x3F, /* REG_DADIGGAIN3 (0x49) */
+ 0x3F, /* REG_DADIGGAIN4 (0x4A) */
+ 0x3F, /* REG_DADIGGAIN5 (0x4B) */
+ 0x3F, /* REG_DADIGGAIN6 (0x4C) */
+ 0x3F, /* REG_ADDIGLOOPGAIN1 (0x4D) */
+ 0x3F, /* REG_ADDIGLOOPGAIN2 (0x4E) */
+ 0x00, /* REG_HSLEARDIGGAIN (0x4F) */
+ 0x00, /* REG_HSRDIGGAIN (0x50) */
+ 0x1F, /* REG_SIDFIRGAIN1 (0x51) */
+ 0x1F, /* REG_SIDFIRGAIN2 (0x52) */
+ 0x00, /* REG_ANCCONF1 (0x53) */
+ 0x00, /* REG_ANCCONF2 (0x54) */
+ 0x00, /* REG_ANCCONF3 (0x55) */
+ 0x00, /* REG_ANCCONF4 (0x56) */
+ 0x00, /* REG_ANCCONF5 (0x57) */
+ 0x00, /* REG_ANCCONF6 (0x58) */
+ 0x00, /* REG_ANCCONF7 (0x59) */
+ 0x00, /* REG_ANCCONF8 (0x5A) */
+ 0x00, /* REG_ANCCONF9 (0x5B) */
+ 0x00, /* REG_ANCCONF10 (0x5C) */
+ 0x00, /* REG_ANCCONF11 (0x5D) - read only */
+ 0x00, /* REG_ANCCONF12 (0x5E) - read only */
+ 0x00, /* REG_ANCCONF13 (0x5F) - read only */
+ 0x00, /* REG_ANCCONF14 (0x60) - read only */
+ 0x00, /* REG_SIDFIRADR (0x61) */
+ 0x00, /* REG_SIDFIRCOEF1 (0x62) */
+ 0x00, /* REG_SIDFIRCOEF2 (0x63) */
+ 0x00, /* REG_SIDFIRCONF (0x64) */
+ 0x00, /* REG_AUDINTMASK1 (0x65) */
+ 0x00, /* REG_AUDINTSOURCE1 (0x66) - read only */
+ 0x00, /* REG_AUDINTMASK2 (0x67) */
+ 0x00, /* REG_AUDINTSOURCE2 (0x68) - read only */
+ 0x00, /* REG_FIFOCONF1 (0x69) */
+ 0x00, /* REG_FIFOCONF2 (0x6A) */
+ 0x00, /* REG_FIFOCONF3 (0x6B) */
+ 0x00, /* REG_FIFOCONF4 (0x6C) */
+ 0x00, /* REG_FIFOCONF5 (0x6D) */
+ 0x00, /* REG_FIFOCONF6 (0x6E) */
+ 0x02, /* REG_AUDREV (0x6F) - read only */
+};
+
+static struct snd_soc_codec *ab8500_codec;
+
+/* ADCM */
+static const u8 ADCM_ANACONF5_MASK = BMASK(REG_ANACONF5_ENCPHS);
+static const u8 ADCM_MUTECONF_MASK = BMASK(REG_MUTECONF_MUTHSL) |
+ BMASK(REG_MUTECONF_MUTHSR);
+static const u8 ADCM_ANACONF4_MASK = BMASK(REG_ANACONF4_ENHSL) |
+ BMASK(REG_ANACONF4_ENHSR);
+static unsigned int adcm_anaconf5, adcm_muteconf, adcm_anaconf4;
+static int adcm = AB8500_AUDIO_ADCM_NORMAL;
+
+/* Signed multi register array controls. */
+struct soc_smra_control {
+ unsigned int *reg;
+ const unsigned int rcount, count, invert;
+ long min, max;
+ const char **texts;
+ long *values;
+};
+
+/* Sidetone FIR-coeff cache */
+static long sid_fir_cache[REG_SID_FIR_COEFFS];
+
+/* ANC FIR- & IIR-coeff caches */
+static long anc_fir_cache[REG_ANC_FIR_COEFFS];
+static long anc_iir_cache[REG_ANC_IIR_COEFFS];
+
+/* Reads an arbitrary register from the ab8500 chip.
+*/
+static int ab8500_codec_read_reg(struct snd_soc_codec *codec, unsigned int bank,
+ unsigned int reg)
+{
+ u8 value;
+ int status = abx500_get_register_interruptible(
+ codec->dev, bank, reg, &value);
+
+ if (status < 0) {
+ pr_err("%s: Register (%02x:%02x) read failed (%d).\n",
+ __func__, (u8)bank, (u8)reg, status);
+ } else {
+ pr_debug("Read 0x%02x from register %02x:%02x\n",
+ (u8)value, (u8)bank, (u8)reg);
+ status = value;
+ }
+
+ return status;
+}
+
+/* Writes an arbitrary register to the ab8500 chip.
+ */
+static int ab8500_codec_write_reg(struct snd_soc_codec *codec, unsigned int bank,
+ unsigned int reg, unsigned int value)
+{
+ int status = abx500_set_register_interruptible(
+ codec->dev, bank, reg, value);
+
+ if (status < 0) {
+ pr_err("%s: Register (%02x:%02x) write failed (%d).\n",
+ __func__, (u8)bank, (u8)reg, status);
+ } else {
+ pr_debug("Wrote 0x%02x into register %02x:%02x\n",
+ (u8)value, (u8)bank, (u8)reg);
+ }
+
+ return status;
+}
+
+/* Reads an audio register from the cache or hardware.
+ */
+static unsigned int ab8500_codec_read_reg_audio(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *cache = codec->reg_cache;
+
+ if (reg == REG_SIDFIRCONF)
+ return ab8500_codec_read_reg(codec, AB8500_AUDIO, reg);
+
+ return cache[reg];
+}
+
+/* Writes an audio register to the hardware and cache.
+ */
+static int ab8500_codec_write_reg_audio(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ u8 *cache = codec->reg_cache;
+ int status = ab8500_codec_write_reg(codec, AB8500_AUDIO, reg, value);
+
+ if (status >= 0)
+ cache[reg] = value;
+
+ return status;
+}
+
+/*
+ * Updates an audio register.
+ *
+ * Returns 1 for change, 0 for no change, or negative error code.
+ */
+static inline int ab8500_codec_update_reg_audio(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int clr, unsigned int ins)
+{
+ unsigned int new, old;
+ int ret;
+
+ old = ab8500_codec_read_reg_audio(codec, reg);
+ new = (old & ~clr) | ins;
+ if (old == new)
+ return 0;
+
+ ret = ab8500_codec_write_reg_audio(codec, reg, new);
+
+ return (ret < 0) ? ret : 1;
+}
+
+/* Generic soc info for signed register controls. */
+int snd_soc_info_s(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_smra_control *smra =
+ (struct soc_smra_control *)kcontrol->private_value;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = smra->count;
+ uinfo->value.integer.min = smra->min;
+ uinfo->value.integer.max = smra->max;
+
+ return 0;
+}
+
+/* Generic soc get for signed multi register controls. */
+int snd_soc_get_smr(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_smra_control *smra =
+ (struct soc_smra_control *)kcontrol->private_value;
+ unsigned int *reg = smra->reg;
+ unsigned int rcount = smra->rcount;
+ long min = smra->min;
+ long max = smra->max;
+ unsigned int invert = smra->invert;
+ unsigned long mask = abs(min) | abs(max);
+ long value = 0;
+ int i, rvalue;
+
+ for (i = 0; i < rcount; i++) {
+ rvalue = snd_soc_read(codec, reg[i]) & REG_MASK_ALL;
+ value |= rvalue << (8 * (rcount - i - 1));
+ }
+ value &= mask;
+ if (min < 0 && value > max)
+ value |= ~mask;
+ if (invert)
+ value = ~value;
+ ucontrol->value.integer.value[0] = value;
+
+ return 0;
+}
+
+/* Generic soc put for signed multi register controls. */
+int snd_soc_put_smr(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_smra_control *smra =
+ (struct soc_smra_control *)kcontrol->private_value;
+ unsigned int *reg = smra->reg;
+ unsigned int rcount = smra->rcount;
+ long min = smra->min;
+ long max = smra->max;
+ unsigned int invert = smra->invert;
+ unsigned long mask = abs(min) | abs(max);
+ long value = ucontrol->value.integer.value[0];
+ int i, rvalue, err;
+
+ if (invert)
+ value = ~value;
+ if (value > max)
+ value = max;
+ else if (value < min)
+ value = min;
+ value &= mask;
+ for (i = 0; i < rcount; i++) {
+ rvalue = (value >> (8 * (rcount - i - 1))) & REG_MASK_ALL;
+ err = snd_soc_write(codec, reg[i], rvalue);
+ if (err < 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Generic soc get for signed array controls. */
+static int snd_soc_get_sa(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_smra_control *smra =
+ (struct soc_smra_control *)kcontrol->private_value;
+ long *values = smra->values;
+ unsigned int count = smra->count;
+ unsigned int idx;
+
+ for (idx = 0; idx < count; idx++)
+ ucontrol->value.integer.value[idx] = values[idx];
+
+ return 0;
+}
+
+/* Generic soc put for signed array controls. */
+static int snd_soc_put_sa(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_smra_control *smra =
+ (struct soc_smra_control *) kcontrol->private_value;
+ long *values = smra->values;
+ unsigned int count = smra->count;
+ long min = smra->min;
+ long max = smra->max;
+ unsigned int idx;
+ long value;
+
+ for (idx = 0; idx < count; idx++) {
+ value = ucontrol->value.integer.value[idx];
+ if (value > max)
+ value = max;
+ else if (value < min)
+ value = min;
+ values[idx] = value;
+ }
+
+ return 0;
+}
+
+/* Generic soc get for enum strobe controls. */
+int snd_soc_get_enum_strobe(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int reg = e->reg;
+ unsigned int bit = e->shift_l;
+ unsigned int invert = e->shift_r != 0;
+ unsigned int value = snd_soc_read(codec, reg) & BMASK(bit);
+
+ if (bit != 0 && value != 0)
+ value = value >> bit;
+ ucontrol->value.enumerated.item[0] = value ^ invert;
+
+ return 0;
+}
+
+/* Generic soc put for enum strobe controls. */
+int snd_soc_put_enum_strobe(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int reg = e->reg;
+ unsigned int bit = e->shift_l;
+ unsigned int invert = e->shift_r != 0;
+ unsigned int strobe = ucontrol->value.enumerated.item[0] != 0;
+ unsigned int clr_mask = (strobe ^ invert) ? REG_MASK_NONE : BMASK(bit);
+ unsigned int set_mask = (strobe ^ invert) ? BMASK(bit) : REG_MASK_NONE;
+
+ if (snd_soc_update_bits(codec, reg, clr_mask, set_mask) == 0)
+ return 0;
+ return snd_soc_update_bits(codec, reg, set_mask, clr_mask);
+}
+
+static const char * const enum_ena_dis[] = {"Enabled", "Disabled"};
+static const char * const enum_dis_ena[] = {"Disabled", "Enabled"};
+static const char * const enum_rdy_apl[] = {"Ready", "Apply"};
+
+/* Sidetone FIR-coefficients configuration sequence */
+static int sid_apply_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec;
+ unsigned int param, sidconf;
+ int ret = 0;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ if (ucontrol->value.integer.value[0] != 1) {
+ pr_err("%s: ERROR: This control supports 'Apply' only!\n",
+ __func__);
+ return ret;
+ }
+
+ codec = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&codec->mutex);
+
+ sidconf = snd_soc_read(codec, REG_SIDFIRCONF);
+ if (((sidconf & BMASK(REG_SIDFIRCONF_FIRSIDBUSY)) != 0)) {
+ if ((sidconf & BMASK(REG_SIDFIRCONF_ENFIRSIDS)) == 0) {
+ pr_err("%s: Sidetone busy while off. Resetting...\n",
+ __func__);
+ snd_soc_update_bits(codec, REG_SIDFIRADR,
+ REG_MASK_NONE, BMASK(REG_SIDFIRADR_FIRSIDSET));
+ snd_soc_update_bits(codec, REG_SIDFIRADR,
+ BMASK(REG_SIDFIRADR_FIRSIDSET), REG_MASK_NONE);
+ }
+ ret = -EBUSY;
+ goto out;
+ }
+
+ snd_soc_write(codec, REG_SIDFIRADR, REG_MASK_NONE);
+
+ for (param = 0; param < REG_SID_FIR_COEFFS; param++) {
+ snd_soc_write(codec, REG_SIDFIRCOEF1,
+ sid_fir_cache[param] >> 8 & REG_MASK_ALL);
+ snd_soc_write(codec, REG_SIDFIRCOEF2,
+ sid_fir_cache[param] & REG_MASK_ALL);
+ }
+
+ snd_soc_update_bits(codec, REG_SIDFIRADR,
+ REG_MASK_NONE, BMASK(REG_SIDFIRADR_FIRSIDSET));
+ snd_soc_update_bits(codec, REG_SIDFIRADR,
+ BMASK(REG_SIDFIRADR_FIRSIDSET), REG_MASK_NONE);
+
+ ret = 1;
+out:
+ mutex_unlock(&codec->mutex);
+
+ pr_debug("%s: Exit\n", __func__);
+
+ return ret;
+}
+
+/* Controls - DAPM */
+
+/* Inverted order - Ascending/Descending */
+enum control_inversion {
+ NORMAL = 0,
+ INVERT = 1
+};
+
+/* Headset */
+
+/* Headset Left - Enable/Disable */
+static const struct soc_enum enum_headset_left = SOC_ENUM_SINGLE(0, 0, 2, enum_ena_dis);
+static const struct snd_kcontrol_new dapm_headset_left_mux =
+ SOC_DAPM_ENUM_VIRT("Headset Left", enum_headset_left);
+
+/* Headsett Right - Enable/Disable */
+static const struct soc_enum enum_headset_right = SOC_ENUM_SINGLE(0, 0, 2, enum_ena_dis);
+static const struct snd_kcontrol_new dapm_headset_right_mux =
+ SOC_DAPM_ENUM_VIRT("Headset Right", enum_headset_right);
+
+/* Earpiece */
+
+/* Earpiece - Mute */
+static const struct soc_enum enum_ear = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_ear_mux =
+ SOC_DAPM_ENUM_VIRT("Earpiece", enum_ear);
+
+/* Earpiece source selector */
+static const char * const enum_ear_lineout_source[] = {"Headset Left", "IHF Left"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ear_lineout_source, REG_DMICFILTCONF,
+ REG_DMICFILTCONF_DA3TOEAR, enum_ear_lineout_source);
+static const struct snd_kcontrol_new dapm_ear_lineout_source[] = {
+ SOC_DAPM_ENUM("Earpiece or LineOut Mono Source", dapm_enum_ear_lineout_source),
+};
+
+/* LineOut */
+
+/* LineOut source selector */
+static const char * const enum_lineout_source[] = {"Mono Path", "Stereo Path"};
+static SOC_ENUM_DOUBLE_DECL(dapm_enum_lineout_source, REG_ANACONF5,
+ REG_ANACONF5_HSLDACTOLOL, REG_ANACONF5_HSRDACTOLOR, enum_lineout_source);
+static const struct snd_kcontrol_new dapm_lineout_source[] = {
+ SOC_DAPM_ENUM("LineOut Source", dapm_enum_lineout_source),
+};
+
+/* LineOut */
+
+/* LineOut Left - Enable/Disable */
+static const struct soc_enum enum_lineout_left = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_lineout_left_mux =
+ SOC_DAPM_ENUM_VIRT("LineOut Left", enum_lineout_left);
+
+/* LineOut Right - Enable/Disable */
+static const struct soc_enum enum_lineout_right = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_lineout_right_mux =
+ SOC_DAPM_ENUM_VIRT("LineOut Right", enum_lineout_right);
+
+/* LineOut/IHF - Select */
+static const char * const enum_ihf_or_lineout_select_sel[] = {"IHF", "LineOut"};
+static const struct soc_enum enum_ihf_or_lineout_select = SOC_ENUM_SINGLE(0, 0, 2, enum_ihf_or_lineout_select_sel);
+static const struct snd_kcontrol_new dapm_ihf_or_lineout_select_mux =
+ SOC_DAPM_ENUM_VIRT("IHF or LineOut Select", enum_ihf_or_lineout_select);
+
+
+/* IHF */
+
+/* IHF - Enable/Disable */
+static const struct soc_enum enum_ihf_left = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_ihf_left_mux =
+ SOC_DAPM_ENUM_VIRT("IHF Left", enum_ihf_left);
+
+static const struct soc_enum enum_ihf_right = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_ihf_right_mux =
+ SOC_DAPM_ENUM_VIRT("IHF Right", enum_ihf_right);
+
+/* IHF left - ANC selector */
+static const char * const enum_ihfx_sel[] = {"Audio Path", "ANC"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ihfl_sel, REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_HFLSEL, enum_ihfx_sel);
+static const struct snd_kcontrol_new dapm_ihfl_select[] = {
+ SOC_DAPM_ENUM("IHF Left Source", dapm_enum_ihfl_sel),
+};
+
+/* IHF right - ANC selector */
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ihfr_sel, REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_HFRSEL, enum_ihfx_sel);
+static const struct snd_kcontrol_new dapm_ihfr_select[] = {
+ SOC_DAPM_ENUM("IHF Right Source", dapm_enum_ihfr_sel),
+};
+
+/* Mic 1 */
+
+/* Mic 1 - Mute */
+static const struct soc_enum enum_mic1 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_mic1_mux =
+ SOC_DAPM_ENUM_VIRT("Mic 1", enum_mic1);
+
+/* Mic 1 - Mic 1A or 1B selector */
+static const char * const enum_mic1ab_sel[] = {"Mic 1A", "Mic 1B"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_mic1ab_sel, REG_ANACONF3,
+ REG_ANACONF3_MIC1SEL, enum_mic1ab_sel);
+static const struct snd_kcontrol_new dapm_mic1ab_select[] = {
+ SOC_DAPM_ENUM("Mic 1A or 1B Select", dapm_enum_mic1ab_sel),
+};
+
+/* Mic 1 - AD3 - Mic 1 or DMic 3 selector */
+static const char * const enum_ad3_sel[] = {"Mic 1", "DMic 3"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ad3_sel, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_AD3SEL, enum_ad3_sel);
+static const struct snd_kcontrol_new dapm_ad3_select[] = {
+ SOC_DAPM_ENUM("AD 3 Select", dapm_enum_ad3_sel),
+};
+
+/* Mic 1 - AD6 - Mic 1 or DMic 6 selector */
+static const char * const enum_ad6_sel[] = {"Mic 1", "DMic 6"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ad6_sel, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_AD6SEL, enum_ad6_sel);
+static const struct snd_kcontrol_new dapm_ad6_select[] = {
+ SOC_DAPM_ENUM("AD 6 Select", dapm_enum_ad6_sel),
+};
+
+/* Mic 2 */
+
+/* Mic 2 - Mute */
+static const struct soc_enum enum_mic2 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_mic2_mux =
+ SOC_DAPM_ENUM_VIRT("Mic 2", enum_mic2);
+
+/* Mic 2 - AD5 - Mic 2 or DMic 5 selector */
+static const char * const enum_ad5_sel[] = {"Mic 2", "DMic 5"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ad5_sel, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_AD5SEL, enum_ad5_sel);
+static const struct snd_kcontrol_new dapm_ad5_select[] = {
+ SOC_DAPM_ENUM("AD 5 Select", dapm_enum_ad5_sel),
+};
+
+/* LineIn */
+
+/* LineIn left - Mute */
+static const struct soc_enum enum_linl = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_linl_mux =
+ SOC_DAPM_ENUM_VIRT("LineIn Left", enum_linl);
+
+static const struct snd_kcontrol_new dapm_linl_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_ANACONF2, REG_ANACONF2_MUTLINL, 1, INVERT),
+};
+
+/* LineIn left - AD1 - LineIn Left or DMic 1 selector */
+static const char * const enum_ad1_sel[] = {"LineIn Left", "DMic 1"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ad1_sel, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_AD1SEL, enum_ad1_sel);
+static const struct snd_kcontrol_new dapm_ad1_select[] = {
+ SOC_DAPM_ENUM("AD 1 Select", dapm_enum_ad1_sel),
+};
+
+/* LineIn right - Mute */
+static const struct soc_enum enum_linr = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_linr_mux =
+ SOC_DAPM_ENUM_VIRT("LineIn Right", enum_linr);
+
+static const struct snd_kcontrol_new dapm_linr_mute[] = {
+ SOC_DAPM_SINGLE("Capture Switch", REG_ANACONF2, REG_ANACONF2_MUTLINR, 1, INVERT),
+};
+
+/* LineIn right - Mic 2 or LineIn Right selector */
+static const char * const enum_mic2lr_sel[] = {"Mic 2", "LineIn Right"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_mic2lr_sel, REG_ANACONF3,
+ REG_ANACONF3_LINRSEL, enum_mic2lr_sel);
+static const struct snd_kcontrol_new dapm_mic2lr_select[] = {
+ SOC_DAPM_ENUM("Mic 2 or LINR Select", dapm_enum_mic2lr_sel),
+};
+
+/* LineIn right - AD2 - LineIn Right or DMic2 selector */
+static const char * const enum_ad2_sel[] = {"LineIn Right", "DMic 2"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_ad2_sel, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_AD2SEL, enum_ad2_sel);
+static const struct snd_kcontrol_new dapm_ad2_select[] = {
+ SOC_DAPM_ENUM("AD 2 Select", dapm_enum_ad2_sel),
+};
+
+/* AD1 to DA1 (IHF Left) Switch */
+static const struct soc_enum enum_ad1_to_ihf_left = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_ad1_to_ihf_left_mux =
+ SOC_DAPM_ENUM_VIRT("AD1 to IHF Left", enum_ad1_to_ihf_left);
+
+/* AD2 to DA1 (IHF Left) Switch */
+static const struct soc_enum enum_ad2_to_ihf_right = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_ad2_to_ihf_right_mux =
+ SOC_DAPM_ENUM_VIRT("AD2 to IHF Right", enum_ad2_to_ihf_right);
+
+/* LineIn Left to Headset Left switch */
+static const struct soc_enum enum_linl_to_hs_left = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_linl_to_hs_left_mux =
+ SOC_DAPM_ENUM_VIRT("LineIn Left to Headset Left", enum_linl_to_hs_left);
+
+/* LineIn Right to Headset Right switch */
+static const struct soc_enum enum_linr_to_hs_right = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_linr_to_hs_right_mux =
+ SOC_DAPM_ENUM_VIRT("LineIn Right to Headset Right", enum_linr_to_hs_right);
+
+
+/* DMic */
+
+/* DMic 1 - Mute */
+static const struct soc_enum enum_dmic1 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_dmic1_mux =
+ SOC_DAPM_ENUM_VIRT("DMic 1", enum_dmic1);
+
+/* DMic 2 - Mute */
+static const struct soc_enum enum_dmic2 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_dmic2_mux =
+ SOC_DAPM_ENUM_VIRT("DMic 2", enum_dmic2);
+
+/* DMic 3 - Mute */
+static const struct soc_enum enum_dmic3 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_dmic3_mux =
+ SOC_DAPM_ENUM_VIRT("DMic 3", enum_dmic3);
+
+/* DMic 4 - Mute */
+static const struct soc_enum enum_dmic4 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_dmic4_mux =
+ SOC_DAPM_ENUM_VIRT("DMic 4", enum_dmic4);
+
+/* DMic 5 - Mute */
+static const struct soc_enum enum_dmic5 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_dmic5_mux =
+ SOC_DAPM_ENUM_VIRT("DMic 5", enum_dmic5);
+
+/* DMic 6 - Mute */
+static const struct soc_enum enum_dmic6 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_dmic6_mux =
+ SOC_DAPM_ENUM_VIRT("DMic 6", enum_dmic6);
+
+/* ANC */
+
+static const char * const enum_anc_in_sel[] = {"Mic 1 / DMic 6", "Mic 2 / DMic 5"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_anc_in_sel, REG_DMICFILTCONF,
+ REG_DMICFILTCONF_ANCINSEL, enum_anc_in_sel);
+static const struct snd_kcontrol_new dapm_anc_in_select[] = {
+ SOC_DAPM_ENUM("ANC Source", dapm_enum_anc_in_sel),
+};
+
+/* ANC - Enable/Disable */
+static SOC_ENUM_SINGLE_DECL(dapm_enum_anc_enable, REG_ANCCONF1,
+ REG_ANCCONF1_ENANC, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_anc_enable[] = {
+ SOC_DAPM_ENUM("ANC", dapm_enum_anc_enable),
+};
+
+/* ANC to Earpiece - Mute */
+static const struct snd_kcontrol_new dapm_anc_ear_mute[] = {
+ SOC_DAPM_SINGLE("Playback Switch", REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_ANCSEL, 1, NORMAL),
+};
+
+/* Sidetone left */
+
+/* Sidetone left - Input selector */
+static const char * const enum_stfir1_in_sel[] = {
+ "LineIn Left", "LineIn Right", "Mic 1", "Headset Left"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_stfir1_in_sel, REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_FIRSID1SEL, enum_stfir1_in_sel);
+static const struct snd_kcontrol_new dapm_stfir1_in_select[] = {
+ SOC_DAPM_ENUM("Sidetone Left Source", dapm_enum_stfir1_in_sel),
+};
+
+/* Sidetone right path */
+
+/* Sidetone right - Input selector */
+static const char * const enum_stfir2_in_sel[] = {
+ "LineIn Right", "Mic 1", "DMic 4", "Headset Right"};
+static SOC_ENUM_SINGLE_DECL(dapm_enum_stfir2_in_sel, REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_FIRSID2SEL, enum_stfir2_in_sel);
+static const struct snd_kcontrol_new dapm_stfir2_in_select[] = {
+ SOC_DAPM_ENUM("Sidetone Right Source", dapm_enum_stfir2_in_sel),
+};
+
+/* Vibra */
+
+/* Vibra 1 - Enable/Disable */
+static const struct soc_enum enum_vibra1 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_vibra1_mux =
+ SOC_DAPM_ENUM_VIRT("Vibra 1", enum_vibra1);
+
+/* Vibra 2 - Enable/Disable */
+static const struct soc_enum enum_vibra2 = SOC_ENUM_SINGLE(0, 0, 2, enum_dis_ena);
+static const struct snd_kcontrol_new dapm_vibra2_mux =
+ SOC_DAPM_ENUM_VIRT("Vibra 2", enum_vibra2);
+
+static const char * const enum_pwm2vibx[] = {"Audio Path", "PWM Generator"};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_pwm2vib1, REG_PWMGENCONF1,
+ REG_PWMGENCONF1_PWMTOVIB1, enum_pwm2vibx);
+
+static const struct snd_kcontrol_new dapm_pwm2vib1[] = {
+ SOC_DAPM_ENUM("Vibra 1 Controller", dapm_enum_pwm2vib1),
+};
+
+static SOC_ENUM_SINGLE_DECL(dapm_enum_pwm2vib2, REG_PWMGENCONF1,
+ REG_PWMGENCONF1_PWMTOVIB2, enum_pwm2vibx);
+
+static const struct snd_kcontrol_new dapm_pwm2vib2[] = {
+ SOC_DAPM_ENUM("Vibra 2 Controller", dapm_enum_pwm2vib2),
+};
+
+/* Event-handlers - DAPM */
+static int linein_enable_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ case SND_SOC_DAPM_POST_PMD:
+ msleep(LINEIN_RAMP_DELAY);
+ break;
+ }
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = {
+
+ /* DA/AD */
+
+ SND_SOC_DAPM_INPUT("ADC Input"),
+ SND_SOC_DAPM_ADC("ADC", "ab8500_0c", SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_DAC("DAC", "ab8500_0p", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_OUTPUT("DAC Output"),
+
+ SND_SOC_DAPM_AIF_IN("DA_IN1", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("DA_IN2", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("DA_IN3", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("DA_IN4", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("DA_IN5", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("DA_IN6", "ab8500_0p", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AD_OUT1", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AD_OUT2", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AD_OUT3", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AD_OUT4", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AD_OUT57", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AD_OUT68", "ab8500_0c", 0, SND_SOC_NOPM, 0, 0),
+
+ /* Headset path */
+
+ SND_SOC_DAPM_SUPPLY("Charge Pump", REG_ANACONF5, REG_ANACONF5_ENCPHS,
+ NORMAL, NULL, 0),
+
+ SND_SOC_DAPM_DAC("DA1 Enable", "ab8500_0p",
+ REG_DAPATHENA, REG_DAPATHENA_ENDA1, 0),
+ SND_SOC_DAPM_DAC("DA2 Enable", "ab8500_0p",
+ REG_DAPATHENA, REG_DAPATHENA_ENDA2, 0),
+
+ SND_SOC_DAPM_MUX("Headset Left",
+ SND_SOC_NOPM, 0, 0, &dapm_headset_left_mux),
+ SND_SOC_DAPM_MUX("Headset Right",
+ SND_SOC_NOPM, 0, 0, &dapm_headset_right_mux),
+
+ SND_SOC_DAPM_PGA("HSL Digital Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HSR Digital Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_DAC("HSL DAC", "ab8500_0p",
+ REG_DAPATHCONF, REG_DAPATHCONF_ENDACHSL, 0),
+ SND_SOC_DAPM_DAC("HSR DAC", "ab8500_0p",
+ REG_DAPATHCONF, REG_DAPATHCONF_ENDACHSR, 0),
+ SND_SOC_DAPM_MIXER("HSL DAC Mute", REG_MUTECONF, REG_MUTECONF_MUTDACHSL,
+ INVERT, NULL, 0),
+ SND_SOC_DAPM_MIXER("HSR DAC Mute", REG_MUTECONF, REG_MUTECONF_MUTDACHSR,
+ INVERT, NULL, 0),
+ SND_SOC_DAPM_DAC("HSL DAC Driver", "ab8500_0p",
+ REG_ANACONF3, REG_ANACONF3_ENDRVHSL, 0),
+ SND_SOC_DAPM_DAC("HSR DAC Driver", "ab8500_0p",
+ REG_ANACONF3, REG_ANACONF3_ENDRVHSR, 0),
+
+ SND_SOC_DAPM_MIXER("HSL Mute", REG_MUTECONF, REG_MUTECONF_MUTHSL,
+ INVERT, NULL, 0),
+ SND_SOC_DAPM_MIXER("HSR Mute", REG_MUTECONF, REG_MUTECONF_MUTHSR,
+ INVERT, NULL, 0),
+ SND_SOC_DAPM_MIXER("HSL Enable", REG_ANACONF4, REG_ANACONF4_ENHSL,
+ NORMAL, NULL, 0),
+ SND_SOC_DAPM_MIXER("HSR Enable", REG_ANACONF4, REG_ANACONF4_ENHSR,
+ NORMAL, NULL, 0),
+ SND_SOC_DAPM_PGA("HSL Gain", SND_SOC_NOPM, 0,
+ 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HSR Gain", SND_SOC_NOPM, 0,
+ 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("HSL"),
+ SND_SOC_DAPM_OUTPUT("HSR"),
+
+ /* LineOut path */
+
+ SND_SOC_DAPM_MUX("LineOut Source Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_lineout_source),
+
+ SND_SOC_DAPM_MIXER("LOL Enable", REG_ANACONF5,
+ REG_ANACONF5_ENLOL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("LOR Enable", REG_ANACONF5,
+ REG_ANACONF5_ENLOR, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("LineOut Left",
+ SND_SOC_NOPM, 0, 0, &dapm_lineout_left_mux),
+
+ SND_SOC_DAPM_MUX("LineOut Right",
+ SND_SOC_NOPM, 0, 0, &dapm_lineout_right_mux),
+
+ /* Earpiece path */
+
+ SND_SOC_DAPM_MUX("Earpiece or LineOut Mono Source",
+ SND_SOC_NOPM, 0, 0, dapm_ear_lineout_source),
+
+ SND_SOC_DAPM_MIXER("EAR DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACEAR, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("Earpiece", SND_SOC_NOPM, 0, 0, &dapm_ear_mux),
+
+ SND_SOC_DAPM_MIXER("EAR Mute", REG_MUTECONF,
+ REG_MUTECONF_MUTEAR, 1, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("EAR Enable", REG_ANACONF4,
+ REG_ANACONF4_ENEAR, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("EAR"),
+
+ /* Handsfree path */
+
+ SND_SOC_DAPM_MIXER("DA3 Channel Gain", REG_DAPATHENA,
+ REG_DAPATHENA_ENDA3, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DA4 Channel Gain", REG_DAPATHENA,
+ REG_DAPATHENA_ENDA4, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("IHF Left Source Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_ihfl_select),
+ SND_SOC_DAPM_MUX("IHF Right Source Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_ihfr_select),
+
+ SND_SOC_DAPM_MUX("IHF Left", SND_SOC_NOPM, 0, 0, &dapm_ihf_left_mux),
+ SND_SOC_DAPM_MUX("IHF Right", SND_SOC_NOPM, 0, 0, &dapm_ihf_right_mux),
+
+ SND_SOC_DAPM_MUX("IHF or LineOut Select", SND_SOC_NOPM,
+ 0, 0, &dapm_ihf_or_lineout_select_mux),
+
+ SND_SOC_DAPM_MIXER("IHFL DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACHFL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("IHFR DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACHFR, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("DA4 or ANC path to HfR", REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_DATOHFREN, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DA3 or ANC path to HfL", REG_DIGMULTCONF2,
+ REG_DIGMULTCONF2_DATOHFLEN, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("IHFL Enable", REG_ANACONF4,
+ REG_ANACONF4_ENHFL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("IHFR Enable", REG_ANACONF4,
+ REG_ANACONF4_ENHFR, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("IHFL"),
+ SND_SOC_DAPM_OUTPUT("IHFR"),
+
+ /* Vibrator path */
+
+ SND_SOC_DAPM_MUX("Vibra 1", SND_SOC_NOPM, 0, 0, &dapm_vibra1_mux),
+ SND_SOC_DAPM_MUX("Vibra 2", SND_SOC_NOPM, 0, 0, &dapm_vibra2_mux),
+ SND_SOC_DAPM_MIXER("DA5 Channel Gain", REG_DAPATHENA,
+ REG_DAPATHENA_ENDA5, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DA6 Channel Gain", REG_DAPATHENA,
+ REG_DAPATHENA_ENDA6, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("VIB1 DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACVIB1, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("VIB2 DAC", REG_DAPATHCONF,
+ REG_DAPATHCONF_ENDACVIB2, 0, NULL, 0),
+
+ SND_SOC_DAPM_INPUT("PWMGEN1"),
+ SND_SOC_DAPM_INPUT("PWMGEN2"),
+
+ SND_SOC_DAPM_MUX("Vibra 1 Controller Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_pwm2vib1),
+ SND_SOC_DAPM_MUX("Vibra 2 Controller Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_pwm2vib2),
+
+ SND_SOC_DAPM_MIXER("VIB1 Enable", REG_ANACONF4,
+ REG_ANACONF4_ENVIB1, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("VIB2 Enable", REG_ANACONF4,
+ REG_ANACONF4_ENVIB2, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("VIB1"),
+ SND_SOC_DAPM_OUTPUT("VIB2"),
+
+ /* LineIn & Microphone 2 path */
+
+ SND_SOC_DAPM_INPUT("LINL"),
+ SND_SOC_DAPM_INPUT("LINR"),
+ SND_SOC_DAPM_INPUT("MIC2 Input"),
+
+
+ SND_SOC_DAPM_MUX("LineIn Left", SND_SOC_NOPM, 0, 0, &dapm_linl_mux),
+ SND_SOC_DAPM_MIXER("LINL Mute", REG_ANACONF2,
+ REG_ANACONF2_MUTLINL, INVERT, NULL, 0),
+
+ SND_SOC_DAPM_MUX("LineIn Right", SND_SOC_NOPM, 0, 0, &dapm_linr_mux),
+ SND_SOC_DAPM_MIXER("LINR Mute", REG_ANACONF2,
+ REG_ANACONF2_MUTLINR, INVERT, NULL, 0),
+
+ SND_SOC_DAPM_MUX("Mic 2", SND_SOC_NOPM, 0, 0, &dapm_mic2_mux),
+ SND_SOC_DAPM_MIXER("MIC2 Mute", REG_ANACONF2,
+ REG_ANACONF2_MUTMIC2, INVERT, NULL, 0),
+
+ SND_SOC_DAPM_MIXER_E("LINL Enable", REG_ANACONF2,
+ REG_ANACONF2_ENLINL, 0, NULL, 0,
+ linein_enable_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_MIXER_E("LINR Enable", REG_ANACONF2,
+ REG_ANACONF2_ENLINR, 0, NULL, 0,
+ linein_enable_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_MIXER("MIC2 Enable", REG_ANACONF2,
+ REG_ANACONF2_ENMIC2, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("Mic 2 or LINR Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_mic2lr_select),
+
+ SND_SOC_DAPM_MIXER("LINL ADC", REG_ANACONF3,
+ REG_ANACONF3_ENADCLINL, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("LINR ADC", REG_ANACONF3,
+ REG_ANACONF3_ENADCLINR, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("AD 1 Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_ad1_select),
+ SND_SOC_DAPM_MUX("AD 2 Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_ad2_select),
+
+ SND_SOC_DAPM_MIXER("AD1 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("AD2 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("AD12 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD12, 0, NULL, 0),
+
+ /* Microphone 1 path */
+
+ SND_SOC_DAPM_INPUT("MIC1A Input"),
+ SND_SOC_DAPM_INPUT("MIC1B Input"),
+
+ SND_SOC_DAPM_MUX("Mic 1A or 1B Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_mic1ab_select),
+
+ SND_SOC_DAPM_MUX("Mic 1", SND_SOC_NOPM, 0, 0, &dapm_mic1_mux),
+
+ SND_SOC_DAPM_MIXER("MIC1 Mute", REG_ANACONF2,
+ REG_ANACONF2_MUTMIC1, INVERT, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("MIC1 Enable", REG_ANACONF2,
+ REG_ANACONF2_ENMIC1, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("MIC1 ADC", REG_ANACONF3,
+ REG_ANACONF3_ENADCMIC, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("AD 3 Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_ad3_select),
+
+ SND_SOC_DAPM_MIXER("AD3 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("AD3 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD34, 0, NULL, 0),
+
+ /* HD Capture path */
+
+ SND_SOC_DAPM_MUX("AD 5 Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_ad5_select),
+ SND_SOC_DAPM_MUX("AD 6 Select Capture Route",
+ SND_SOC_NOPM, 0, 0, dapm_ad6_select),
+
+ SND_SOC_DAPM_MIXER("AD5 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("AD6 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("AD57 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD5768, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("AD68 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD5768, 0, NULL, 0),
+
+ /* Digital Microphone path */
+
+ SND_SOC_DAPM_INPUT("DMIC Input"),
+
+ SND_SOC_DAPM_MUX("DMic 1", SND_SOC_NOPM, 0, 0, &dapm_dmic1_mux),
+ SND_SOC_DAPM_MUX("DMic 2", SND_SOC_NOPM, 0, 0, &dapm_dmic2_mux),
+ SND_SOC_DAPM_MUX("DMic 3", SND_SOC_NOPM, 0, 0, &dapm_dmic3_mux),
+ SND_SOC_DAPM_MUX("DMic 4", SND_SOC_NOPM, 0, 0, &dapm_dmic4_mux),
+ SND_SOC_DAPM_MUX("DMic 5", SND_SOC_NOPM, 0, 0, &dapm_dmic5_mux),
+ SND_SOC_DAPM_MUX("DMic 6", SND_SOC_NOPM, 0, 0, &dapm_dmic6_mux),
+
+ SND_SOC_DAPM_MIXER("DMIC1 Mute", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC1, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DMIC2 Mute", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC2, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DMIC3 Mute", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC3, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DMIC4 Mute", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC4, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DMIC5 Mute", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC5, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("DMIC6 Mute", REG_DIGMICCONF,
+ REG_DIGMICCONF_ENDMIC6, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("AD4 Channel Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("AD4 Enable", REG_ADPATHENA,
+ REG_ADPATHENA_ENAD34, 0, NULL, 0),
+
+ /* LineIn to Bypass path */
+
+ SND_SOC_DAPM_MIXER("LINL to HSL Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("LINR to HSR Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MUX("LineIn Left to Headset Left", SND_SOC_NOPM, 0, 0, &dapm_linl_to_hs_left_mux),
+ SND_SOC_DAPM_MUX("LineIn Right to Headset Right", SND_SOC_NOPM, 0, 0, &dapm_linr_to_hs_right_mux),
+
+ /* LineIn to Speaker */
+
+ SND_SOC_DAPM_MUX("AD1 to IHF Left", SND_SOC_NOPM, 0, 0, &dapm_ad1_to_ihf_left_mux),
+ SND_SOC_DAPM_MUX("AD2 to IHF Right", SND_SOC_NOPM, 0, 0, &dapm_ad2_to_ihf_right_mux),
+
+ /* Acoustical Noise Cancellation path */
+
+ SND_SOC_DAPM_MUX("ANC Source Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_anc_in_select),
+
+ SND_SOC_DAPM_MUX("ANC Playback Switch",
+ SND_SOC_NOPM, 0, 0, dapm_anc_enable),
+
+ SND_SOC_DAPM_SWITCH("ANC to Earpiece",
+ SND_SOC_NOPM, 0, 0, dapm_anc_ear_mute),
+
+ /* Sidetone Filter path */
+
+ SND_SOC_DAPM_MUX("Sidetone Left Source Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_stfir1_in_select),
+ SND_SOC_DAPM_MUX("Sidetone Right Source Playback Route",
+ SND_SOC_NOPM, 0, 0, dapm_stfir2_in_select),
+
+ SND_SOC_DAPM_MIXER("STFIR1 Control", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("STFIR2 Control", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("STFIR1 Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("STFIR2 Gain", SND_SOC_NOPM, 0, 0, NULL, 0),
+};
+
+/* DAPM-routes */
+
+static const struct snd_soc_dapm_route dapm_routes[] = {
+ /* AD/DA */
+ {"ADC", NULL, "ADC Input"},
+ {"DAC Output", NULL, "DAC"},
+
+ /* Powerup charge pump if DA1/2 is in use */
+ {"HSL Mute", NULL, "Charge Pump"},
+ {"HSR Mute", NULL, "Charge Pump"},
+
+ /* Headset path */
+
+ {"DA1 Enable", NULL, "DA_IN1"},
+ {"DA2 Enable", NULL, "DA_IN2"},
+
+ {"HSL Digital Gain", NULL, "DA1 Enable"},
+ {"HSR Digital Gain", NULL, "DA2 Enable"},
+
+ {"HSL DAC", NULL, "HSL Digital Gain"},
+ {"HSR DAC", NULL, "HSR Digital Gain"},
+
+ {"HSL DAC Mute", NULL, "HSL DAC"},
+ {"HSR DAC Mute", NULL, "HSR DAC"},
+
+ {"HSL DAC Driver", NULL, "HSL DAC Mute"},
+ {"HSR DAC Driver", NULL, "HSR DAC Mute"},
+
+ {"HSL Mute", NULL, "HSL DAC Driver"},
+ {"HSR Mute", NULL, "HSR DAC Driver"},
+
+ {"Headset Left", "Enabled", "HSL Mute"},
+ {"Headset Right", "Enabled", "HSR Mute"},
+
+ {"HSL Enable", NULL, "Headset Left"},
+ {"HSR Enable", NULL, "Headset Right"},
+
+ {"HSL Gain", NULL, "HSL Enable"},
+ {"HSR Gain", NULL, "HSR Enable"},
+
+ {"HSL", NULL, "HSL Gain"},
+ {"HSR", NULL, "HSR Gain"},
+
+ /* IHF or LineOut path */
+
+ {"DA3 Channel Gain", NULL, "DA_IN3"},
+ {"DA4 Channel Gain", NULL, "DA_IN4"},
+
+ {"IHF Left Source Playback Route", "Audio Path", "DA3 Channel Gain"},
+ {"IHF Right Source Playback Route", "Audio Path", "DA4 Channel Gain"},
+
+ {"DA3 or ANC path to HfL", NULL, "IHF Left Source Playback Route"},
+ {"DA4 or ANC path to HfR", NULL, "IHF Right Source Playback Route"},
+
+ /* IHF path */
+
+ {"IHF Left", "Enabled", "DA3 or ANC path to HfL"},
+ {"IHF Right", "Enabled", "DA4 or ANC path to HfR"},
+
+ {"IHFL DAC", NULL, "IHF Left"},
+ {"IHFR DAC", NULL, "IHF Right"},
+
+ {"IHFL Enable", NULL, "IHFL DAC"},
+ {"IHFR Enable", NULL, "IHFR DAC"},
+
+ {"IHF or LineOut Select", "IHF", "IHFL Enable"},
+ {"IHF or LineOut Select", "IHF", "IHFR Enable"},
+
+ /* Earpiece path */
+
+ {"Earpiece or LineOut Mono Source", "Headset Left", "HSL Digital Gain"},
+ {"Earpiece or LineOut Mono Source", "IHF Left", "DA3 or ANC path to HfL"},
+
+ {"EAR DAC", NULL, "Earpiece or LineOut Mono Source"},
+
+ {"Earpiece", "Enabled", "EAR DAC"},
+
+ {"EAR Mute", NULL, "Earpiece"},
+
+ {"EAR Enable", NULL, "EAR Mute"},
+
+ {"EAR", NULL, "EAR Enable"},
+
+ /* LineOut path stereo */
+
+ {"LineOut Source Playback Route", "Stereo Path", "HSL DAC Driver"},
+ {"LineOut Source Playback Route", "Stereo Path", "HSR DAC Driver"},
+
+ /* LineOut path mono */
+
+ {"LineOut Source Playback Route", "Mono Path", "EAR DAC"},
+
+ /* LineOut path */
+
+ {"LineOut Left", "Enabled", "LineOut Source Playback Route"},
+ {"LineOut Right", "Enabled", "LineOut Source Playback Route"},
+
+ {"LOL Enable", NULL, "LineOut Left"},
+ {"LOR Enable", NULL, "LineOut Right"},
+
+ {"IHF or LineOut Select", "LineOut", "LOL Enable"},
+ {"IHF or LineOut Select", "LineOut", "LOR Enable"},
+
+ /* IHF path */
+
+ {"IHFL", NULL, "IHF or LineOut Select"},
+ {"IHFR", NULL, "IHF or LineOut Select"},
+
+ /* Vibrator path */
+
+ {"DA5 Channel Gain", NULL, "DA_IN5"},
+ {"DA6 Channel Gain", NULL, "DA_IN6"},
+
+ {"VIB1 DAC", NULL, "DA5 Channel Gain"},
+ {"VIB2 DAC", NULL, "DA6 Channel Gain"},
+
+ {"Vibra 1 Controller Playback Route", "Audio Path", "VIB1 DAC"},
+ {"Vibra 2 Controller Playback Route", "Audio Path", "VIB2 DAC"},
+ {"Vibra 1 Controller Playback Route", "PWM Generator", "PWMGEN1"},
+ {"Vibra 2 Controller Playback Route", "PWM Generator", "PWMGEN2"},
+
+ {"Vibra 1", "Enabled", "Vibra 1 Controller Playback Route"},
+ {"Vibra 2", "Enabled", "Vibra 2 Controller Playback Route"},
+
+ {"VIB1 Enable", NULL, "Vibra 1"},
+ {"VIB2 Enable", NULL, "Vibra 2"},
+
+ {"VIB1", NULL, "VIB1 Enable"},
+ {"VIB2", NULL, "VIB2 Enable"},
+
+ /* LineIn & Microphone 2 path */
+
+ {"LineIn Left", "Enabled", "LINL"},
+ {"LineIn Right", "Enabled", "LINR"},
+
+ {"LINL Mute", NULL, "LineIn Left"},
+ {"LINR Mute", NULL, "LineIn Right"},
+
+ {"Mic 2", "Enabled", "MIC2 Input"},
+
+ {"MIC2 Mute", NULL, "Mic 2"},
+
+ {"LINL Enable", NULL, "LINL Mute"},
+ {"LINR Enable", NULL, "LINR Mute"},
+ {"MIC2 Enable", NULL, "MIC2 Mute"},
+
+ {"Mic 2 or LINR Select Capture Route", "LineIn Right", "LINR Enable"},
+ {"Mic 2 or LINR Select Capture Route", "Mic 2", "MIC2 Enable"},
+
+ {"LINL ADC", NULL, "LINL Enable"},
+ {"LINR ADC", NULL, "Mic 2 or LINR Select Capture Route"},
+
+ {"AD 1 Select Capture Route", "LineIn Left", "LINL ADC"},
+ {"AD 2 Select Capture Route", "LineIn Right", "LINR ADC"},
+
+ {"AD1 Channel Gain", NULL, "AD 1 Select Capture Route"},
+ {"AD2 Channel Gain", NULL, "AD 2 Select Capture Route"},
+
+ {"AD12 Enable", NULL, "AD1 Channel Gain"},
+ {"AD12 Enable", NULL, "AD2 Channel Gain"},
+
+ {"AD_OUT1", NULL, "AD12 Enable"},
+ {"AD_OUT2", NULL, "AD12 Enable"},
+
+ /* Microphone 1 path */
+
+ {"Mic 1A or 1B Select Capture Route", "Mic 1A", "MIC1A Input"},
+ {"Mic 1A or 1B Select Capture Route", "Mic 1B", "MIC1B Input"},
+
+ {"Mic 1", "Enabled", "Mic 1A or 1B Select Capture Route"},
+
+ {"MIC1 Mute", NULL, "Mic 1"},
+
+ {"MIC1 Enable", NULL, "MIC1 Mute"},
+
+ {"MIC1 ADC", NULL, "MIC1 Enable"},
+
+ {"AD 3 Select Capture Route", "Mic 1", "MIC1 ADC"},
+
+ {"AD3 Channel Gain", NULL, "AD 3 Select Capture Route"},
+
+ {"AD3 Enable", NULL, "AD3 Channel Gain"},
+
+ {"AD_OUT3", NULL, "AD3 Enable"},
+
+ /* HD Capture path */
+
+ {"AD 5 Select Capture Route", "Mic 2", "LINR ADC"},
+ {"AD 6 Select Capture Route", "Mic 1", "MIC1 ADC"},
+
+ {"AD5 Channel Gain", NULL, "AD 5 Select Capture Route"},
+ {"AD6 Channel Gain", NULL, "AD 6 Select Capture Route"},
+
+ {"AD57 Enable", NULL, "AD5 Channel Gain"},
+ {"AD68 Enable", NULL, "AD6 Channel Gain"},
+
+ {"AD_OUT57", NULL, "AD57 Enable"},
+ {"AD_OUT68", NULL, "AD68 Enable"},
+
+ /* Digital Microphone path */
+
+ {"DMic 1", "Enabled", "DMIC Input"},
+ {"DMic 2", "Enabled", "DMIC Input"},
+ {"DMic 3", "Enabled", "DMIC Input"},
+ {"DMic 4", "Enabled", "DMIC Input"},
+ {"DMic 5", "Enabled", "DMIC Input"},
+ {"DMic 6", "Enabled", "DMIC Input"},
+
+ {"DMIC1 Mute", NULL, "DMic 1"},
+ {"DMIC2 Mute", NULL, "DMic 2"},
+ {"DMIC3 Mute", NULL, "DMic 3"},
+ {"DMIC4 Mute", NULL, "DMic 4"},
+ {"DMIC5 Mute", NULL, "DMic 5"},
+ {"DMIC6 Mute", NULL, "DMic 6"},
+
+ {"AD 1 Select Capture Route", "DMic 1", "DMIC1 Mute"},
+ {"AD 2 Select Capture Route", "DMic 2", "DMIC2 Mute"},
+ {"AD 3 Select Capture Route", "DMic 3", "DMIC3 Mute"},
+ {"AD 5 Select Capture Route", "DMic 5", "DMIC5 Mute"},
+ {"AD 6 Select Capture Route", "DMic 6", "DMIC6 Mute"},
+
+ {"AD4 Channel Gain", NULL, "DMIC4 Mute"},
+
+ {"AD4 Enable", NULL, "AD4 Channel Gain"},
+
+ {"AD_OUT4", NULL, "AD4 Enable"},
+
+ /* LineIn to Headset Bypass path */
+
+ {"LINL to HSL Gain", NULL, "LINL Enable"},
+ {"LINR to HSR Gain", NULL, "LINR Enable"},
+
+ {"LineIn Left to Headset Left", "Enabled", "LINL to HSL Gain"},
+ {"LineIn Right to Headset Right", "Enabled", "LINR to HSR Gain"},
+
+ {"HSL DAC Driver", NULL, "LineIn Left to Headset Left"},
+ {"HSR DAC Driver", NULL, "LineIn Right to Headset Right"},
+
+ /* LineIn to Speaker path */
+
+ {"AD1 to IHF Left", "Enabled", "AD 1 Select Capture Route"},
+ {"AD2 to IHF Right", "Enabled", "AD 2 Select Capture Route"},
+
+ {"IHFL DAC", NULL, "AD1 to IHF Left"},
+ {"IHFR DAC", NULL, "AD2 to IHF Right"},
+
+ /* Acoustical Noise Cancellation path */
+
+ {"ANC Source Playback Route", "Mic 2 / DMic 5", "AD5 Channel Gain"},
+ {"ANC Source Playback Route", "Mic 1 / DMic 6", "AD6 Channel Gain"},
+
+ {"ANC Playback Switch", "Enabled", "ANC Source Playback Route"},
+
+ {"IHF Left Source Playback Route", "ANC", "ANC Playback Switch"},
+ {"IHF Right Source Playback Route", "ANC", "ANC Playback Switch"},
+ {"ANC to Earpiece", "Playback Switch", "ANC Playback Switch"},
+
+ {"HSL Digital Gain", NULL, "ANC to Earpiece"},
+
+ /* Sidetone Filter path */
+
+ {"Sidetone Left Source Playback Route", "LineIn Left", "AD12 Enable"},
+ {"Sidetone Left Source Playback Route", "LineIn Right", "AD12 Enable"},
+ {"Sidetone Left Source Playback Route", "Mic 1", "AD3 Enable"},
+ {"Sidetone Left Source Playback Route", "Headset Left", "DA_IN1"},
+ {"Sidetone Right Source Playback Route", "LineIn Right", "AD12 Enable"},
+ {"Sidetone Right Source Playback Route", "Mic 1", "AD3 Enable"},
+ {"Sidetone Right Source Playback Route", "DMic 4", "AD4 Enable"},
+ {"Sidetone Right Source Playback Route", "Headset Right", "DA_IN2"},
+
+ {"STFIR1 Control", NULL, "Sidetone Left Source Playback Route"},
+ {"STFIR2 Control", NULL, "Sidetone Right Source Playback Route"},
+
+ {"STFIR1 Gain", NULL, "STFIR1 Control"},
+ {"STFIR2 Gain", NULL, "STFIR2 Control"},
+
+ {"DA1 Enable", NULL, "STFIR1 Gain"},
+ {"DA2 Enable", NULL, "STFIR2 Gain"},
+};
+
+/* Controls - Non-DAPM ASoC */
+
+/* from -31 to 31 dB in 1 dB steps (mute instead of -32 dB) */
+static DECLARE_TLV_DB_SCALE(adx_dig_gain_tlv, -3200, 100, 1);
+
+/* from -62 to 0 dB in 1 dB steps (mute instead of -63 dB) */
+static DECLARE_TLV_DB_SCALE(dax_dig_gain_tlv, -6300, 100, 1);
+
+/* from 0 to 8 dB in 1 dB steps (mute instead of -1 dB) */
+static DECLARE_TLV_DB_SCALE(hs_ear_dig_gain_tlv, -100, 100, 1);
+
+/* from -30 to 0 dB in 1 dB steps (mute instead of -31 dB) */
+static DECLARE_TLV_DB_SCALE(stfir_dig_gain_tlv, -3100, 100, 1);
+
+/* from -32 to -20 dB in 4 dB steps / from -18 to 2 dB in 2 dB steps */
+static const unsigned int hs_gain_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 3, TLV_DB_SCALE_ITEM(-3200, 400, 0),
+ 4, 15, TLV_DB_SCALE_ITEM(-1800, 200, 0),
+};
+
+/* from 0 to 31 dB in 1 dB steps */
+static DECLARE_TLV_DB_SCALE(mic_gain_tlv, 0, 100, 0);
+
+/* from -10 to 20 dB in 2 dB steps */
+static DECLARE_TLV_DB_SCALE(lin_gain_tlv, -1000, 200, 0);
+
+/* from -36 to 0 dB in 2 dB steps (mute instead of -38 dB) */
+static DECLARE_TLV_DB_SCALE(lin2hs_gain_tlv, -3800, 200, 1);
+
+static SOC_ENUM_SINGLE_DECL(soc_enum_hshpen,
+ REG_ANACONF1, REG_ANACONF1_HSHPEN, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_hslowpow,
+ REG_ANACONF1, REG_ANACONF1_HSLOWPOW, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_daclowpow1,
+ REG_ANACONF1, REG_ANACONF1_DACLOWPOW1, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_daclowpow0,
+ REG_ANACONF1, REG_ANACONF1_DACLOWPOW0, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_eardaclowpow,
+ REG_ANACONF1, REG_ANACONF1_EARDACLOWPOW, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_eardrvlowpow,
+ REG_ANACONF1, REG_ANACONF1_EARDRVLOWPOW, enum_dis_ena);
+
+static const char * const enum_earselcm[] = {"0.95V", "1.10V", "1.27V", "1.58V"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_earselcm,
+ REG_ANACONF1, REG_ANACONF1_EARSELCM, enum_earselcm);
+
+static const char * const enum_hsfadspeed[] = {"2ms", "0.5ms", "10.6ms", "5ms"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_hsfadspeed,
+ REG_DIGMICCONF, REG_DIGMICCONF_HSFADSPEED, enum_hsfadspeed);
+
+static const char * const enum_envdetthre[] = {
+ "250mV", "300mV", "350mV", "400mV",
+ "450mV", "500mV", "550mV", "600mV",
+ "650mV", "700mV", "750mV", "800mV",
+ "850mV", "900mV", "950mV", "1.00V" };
+static SOC_ENUM_SINGLE_DECL(soc_enum_envdetcpen,
+ REG_SIGENVCONF, REG_SIGENVCONF_ENVDETCPEN, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_envdeththre,
+ REG_ENVCPCONF, REG_ENVCPCONF_ENVDETHTHRE, enum_envdetthre);
+static SOC_ENUM_SINGLE_DECL(soc_enum_envdetlthre,
+ REG_ENVCPCONF, REG_ENVCPCONF_ENVDETLTHRE, enum_envdetthre);
+
+static const char * const enum_envdettime[] = {
+ "26.6us", "53.2us", "106us", "213us",
+ "426us", "851us", "1.70ms", "3.40ms",
+ "6.81ms", "13.6ms", "27.2ms", "54.5ms",
+ "109ms", "218ms", "436ms", "872ms" };
+static SOC_ENUM_SINGLE_DECL(soc_enum_envdettime,
+ REG_SIGENVCONF, REG_SIGENVCONF_ENVDETTIME, enum_envdettime);
+
+static const char * const enum_ensemicx[] = {"Differential", "Single Ended"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_ensemic1,
+ REG_ANAGAIN1, REG_ANAGAINX_ENSEMICX, enum_ensemicx);
+static SOC_ENUM_SINGLE_DECL(soc_enum_ensemic2,
+ REG_ANAGAIN2, REG_ANAGAINX_ENSEMICX, enum_ensemicx);
+static SOC_ENUM_SINGLE_DECL(soc_enum_lowpowmic1,
+ REG_ANAGAIN1, REG_ANAGAINX_LOWPOWMICX, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_lowpowmic2,
+ REG_ANAGAIN2, REG_ANAGAINX_LOWPOWMICX, enum_dis_ena);
+
+static SOC_ENUM_DOUBLE_DECL(soc_enum_ad12nh, REG_ADFILTCONF,
+ REG_ADFILTCONF_AD1NH, REG_ADFILTCONF_AD2NH, enum_ena_dis);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_ad34nh, REG_ADFILTCONF,
+ REG_ADFILTCONF_AD3NH, REG_ADFILTCONF_AD4NH, enum_ena_dis);
+
+static const char * const enum_av_mode[] = {"Audio", "Voice"};
+static SOC_ENUM_DOUBLE_DECL(soc_enum_ad12voice, REG_ADFILTCONF,
+ REG_ADFILTCONF_AD1VOICE, REG_ADFILTCONF_AD2VOICE, enum_av_mode);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_ad34voice, REG_ADFILTCONF,
+ REG_ADFILTCONF_AD3VOICE, REG_ADFILTCONF_AD4VOICE, enum_av_mode);
+
+static SOC_ENUM_SINGLE_DECL(soc_enum_da12voice,
+ REG_DASLOTCONF1, REG_DASLOTCONF1_DA12VOICE, enum_av_mode);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da34voice,
+ REG_DASLOTCONF3, REG_DASLOTCONF3_DA34VOICE, enum_av_mode);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da56voice,
+ REG_DASLOTCONF5, REG_DASLOTCONF5_DA56VOICE, enum_av_mode);
+
+static SOC_ENUM_SINGLE_DECL(soc_enum_swapda12_34,
+ REG_DASLOTCONF1, REG_DASLOTCONF1_SWAPDA12_34, enum_dis_ena);
+
+static SOC_ENUM_DOUBLE_DECL(soc_enum_vib12swap, REG_CLASSDCONF1,
+ REG_CLASSDCONF1_VIB1SWAPEN, REG_CLASSDCONF1_VIB2SWAPEN, enum_dis_ena);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_hflrswap, REG_CLASSDCONF1,
+ REG_CLASSDCONF1_HFLSWAPEN, REG_CLASSDCONF1_HFRSWAPEN, enum_dis_ena);
+
+static SOC_ENUM_DOUBLE_DECL(soc_enum_fir01byp, REG_CLASSDCONF2,
+ REG_CLASSDCONF2_FIRBYP0, REG_CLASSDCONF2_FIRBYP1, enum_dis_ena);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_fir23byp, REG_CLASSDCONF2,
+ REG_CLASSDCONF2_FIRBYP2, REG_CLASSDCONF2_FIRBYP3, enum_dis_ena);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_highvol01, REG_CLASSDCONF2,
+ REG_CLASSDCONF2_HIGHVOLEN0, REG_CLASSDCONF2_HIGHVOLEN1, enum_dis_ena);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_highvol23, REG_CLASSDCONF2,
+ REG_CLASSDCONF2_HIGHVOLEN2, REG_CLASSDCONF2_HIGHVOLEN3, enum_dis_ena);
+
+static const char * const enum_sinc53[] = {"Sinc 5", "Sinc 3"};
+static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic12sinc, REG_DMICFILTCONF,
+ REG_DMICFILTCONF_DMIC1SINC3, REG_DMICFILTCONF_DMIC2SINC3, enum_sinc53);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic34sinc, REG_DMICFILTCONF,
+ REG_DMICFILTCONF_DMIC3SINC3, REG_DMICFILTCONF_DMIC4SINC3, enum_sinc53);
+static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic56sinc, REG_DMICFILTCONF,
+ REG_DMICFILTCONF_DMIC5SINC3, REG_DMICFILTCONF_DMIC6SINC3, enum_sinc53);
+
+static const char * const enum_da2hslr[] = {"Sidetone", "Audio Path"};
+static SOC_ENUM_DOUBLE_DECL(soc_enum_da2hslr, REG_DIGMULTCONF1,
+ REG_DIGMULTCONF1_DATOHSLEN, REG_DIGMULTCONF1_DATOHSREN, enum_da2hslr);
+
+static const char * const enum_sinc31[] = {"Sinc 3", "Sinc 1"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_hsesinc,
+ REG_HSLEARDIGGAIN, REG_HSLEARDIGGAIN_HSSINC1, enum_sinc31);
+
+static const char * const enum_fadespeed[] = {"1ms", "4ms", "8ms", "16ms"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_fadespeed,
+ REG_HSRDIGGAIN, REG_HSRDIGGAIN_FADESPEED, enum_fadespeed);
+
+/* Digital interface - Clocks */
+static SOC_ENUM_SINGLE_DECL(soc_enum_mastgen,
+ REG_DIGIFCONF1, REG_DIGIFCONF1_ENMASTGEN, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_fsbitclk0,
+ REG_DIGIFCONF1, REG_DIGIFCONF1_ENFSBITCLK0, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_fsbitclk1,
+ REG_DIGIFCONF1, REG_DIGIFCONF1_ENFSBITCLK1, enum_dis_ena);
+
+/* Digital interface - DA from slot mapping */
+static const char * const enum_da_from_slot_map[] = {"SLOT0",
+ "SLOT1",
+ "SLOT2",
+ "SLOT3",
+ "SLOT4",
+ "SLOT5",
+ "SLOT6",
+ "SLOT7",
+ "SLOT8",
+ "SLOT9",
+ "SLOT10",
+ "SLOT11",
+ "SLOT12",
+ "SLOT13",
+ "SLOT14",
+ "SLOT15",
+ "SLOT16",
+ "SLOT17",
+ "SLOT18",
+ "SLOT19",
+ "SLOT20",
+ "SLOT21",
+ "SLOT22",
+ "SLOT23",
+ "SLOT24",
+ "SLOT25",
+ "SLOT26",
+ "SLOT27",
+ "SLOT28",
+ "SLOT29",
+ "SLOT30",
+ "SLOT31"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_da1slotmap,
+ REG_DASLOTCONF1, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da2slotmap,
+ REG_DASLOTCONF2, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da3slotmap,
+ REG_DASLOTCONF3, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da4slotmap,
+ REG_DASLOTCONF4, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da5slotmap,
+ REG_DASLOTCONF5, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da6slotmap,
+ REG_DASLOTCONF6, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da7slotmap,
+ REG_DASLOTCONF7, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_da8slotmap,
+ REG_DASLOTCONF8, REG_DASLOTCONFX_SLTODAX_SHIFT, enum_da_from_slot_map);
+
+/* Digital interface - AD to slot mapping */
+static const char * const enum_ad_to_slot_map[] = {"AD_OUT1",
+ "AD_OUT2",
+ "AD_OUT3",
+ "AD_OUT4",
+ "AD_OUT5",
+ "AD_OUT6",
+ "AD_OUT7",
+ "AD_OUT8",
+ "zeroes",
+ "tristate"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot0map,
+ REG_ADSLOTSEL1, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot1map,
+ REG_ADSLOTSEL1, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot2map,
+ REG_ADSLOTSEL2, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot3map,
+ REG_ADSLOTSEL2, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot4map,
+ REG_ADSLOTSEL3, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot5map,
+ REG_ADSLOTSEL3, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot6map,
+ REG_ADSLOTSEL4, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot7map,
+ REG_ADSLOTSEL4, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot8map,
+ REG_ADSLOTSEL5, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot9map,
+ REG_ADSLOTSEL5, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot10map,
+ REG_ADSLOTSEL6, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot11map,
+ REG_ADSLOTSEL6, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot12map,
+ REG_ADSLOTSEL7, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot13map,
+ REG_ADSLOTSEL7, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot14map,
+ REG_ADSLOTSEL8, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot15map,
+ REG_ADSLOTSEL8, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot16map,
+ REG_ADSLOTSEL9, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot17map,
+ REG_ADSLOTSEL9, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot18map,
+ REG_ADSLOTSEL10, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot19map,
+ REG_ADSLOTSEL10, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot20map,
+ REG_ADSLOTSEL11, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot21map,
+ REG_ADSLOTSEL11, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot22map,
+ REG_ADSLOTSEL12, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot23map,
+ REG_ADSLOTSEL12, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot24map,
+ REG_ADSLOTSEL13, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot25map,
+ REG_ADSLOTSEL13, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot26map,
+ REG_ADSLOTSEL14, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot27map,
+ REG_ADSLOTSEL14, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot28map,
+ REG_ADSLOTSEL15, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot29map,
+ REG_ADSLOTSEL15, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot30map,
+ REG_ADSLOTSEL16, REG_ADSLOTSELX_EVEN_SHIFT, enum_ad_to_slot_map);
+static SOC_ENUM_SINGLE_DECL(soc_enum_adslot31map,
+ REG_ADSLOTSEL16, REG_ADSLOTSELX_ODD_SHIFT, enum_ad_to_slot_map);
+
+/* Digital interface - Digital loopback */
+static SOC_ENUM_SINGLE_DECL(soc_enum_ad1loop,
+ REG_DASLOTCONF1, REG_DASLOTCONF1_DAI7TOADO1, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_ad2loop,
+ REG_DASLOTCONF2, REG_DASLOTCONF2_DAI8TOADO2, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_ad3loop,
+ REG_DASLOTCONF3, REG_DASLOTCONF3_DAI7TOADO3, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_ad4loop,
+ REG_DASLOTCONF4, REG_DASLOTCONF4_DAI8TOADO4, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_ad5loop,
+ REG_DASLOTCONF5, REG_DASLOTCONF5_DAI7TOADO5, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_ad6loop,
+ REG_DASLOTCONF6, REG_DASLOTCONF6_DAI8TOADO6, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_ad7loop,
+ REG_DASLOTCONF7, REG_DASLOTCONF7_DAI8TOADO7, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_ad8loop,
+ REG_DASLOTCONF8, REG_DASLOTCONF8_DAI7TOADO8, enum_dis_ena);
+
+/* Digital interface - Burst mode */
+static SOC_ENUM_SINGLE_DECL(soc_enum_if0fifoen,
+ REG_DIGIFCONF3, REG_DIGIFCONF3_IF0BFIFOEN, enum_dis_ena);
+static const char * const enum_mask[] = {"Unmasked", "Masked"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomask,
+ REG_FIFOCONF1, REG_FIFOCONF1_BFIFOMASK, enum_mask);
+static const char * const enum_bitclk0[] = {"38_4_MHz", "19_2_MHz"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_bfifo19m2,
+ REG_FIFOCONF1, REG_FIFOCONF1_BFIFO19M2, enum_bitclk0);
+static const char * const enum_slavemaster[] = {"Slave", "Master"};
+static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast,
+ REG_FIFOCONF3, REG_FIFOCONF3_BFIFOMAST_SHIFT, enum_slavemaster);
+static SOC_ENUM_SINGLE_DECL(soc_enum_bfifoint,
+ REG_FIFOCONF3, REG_FIFOCONF3_BFIFORUN_SHIFT, enum_dis_ena);
+
+/* TODO: move to DAPM */
+static SOC_ENUM_SINGLE_DECL(soc_enum_enfirsids,
+ REG_SIDFIRCONF, REG_SIDFIRCONF_ENFIRSIDS, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_parlhf,
+ REG_CLASSDCONF1, REG_CLASSDCONF1_PARLHF, enum_dis_ena);
+static SOC_ENUM_SINGLE_DECL(soc_enum_parlvib,
+ REG_CLASSDCONF1, REG_CLASSDCONF1_PARLVIB, enum_dis_ena);
+static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_applysidetone, enum_rdy_apl);
+
+static struct snd_kcontrol_new ab8500_snd_controls[] = {
+ SOC_ENUM("Headset High Pass Playback Switch", soc_enum_hshpen),
+ SOC_ENUM("Headset Low Power Playback Switch", soc_enum_hslowpow),
+ SOC_ENUM("Headset DAC Low Power Playback Switch", soc_enum_daclowpow1),
+ SOC_ENUM("Headset DAC Drv Low Power Playback Switch",
+ soc_enum_daclowpow0),
+ SOC_ENUM("Earpiece DAC Low Power Playback Switch",
+ soc_enum_eardaclowpow),
+ SOC_ENUM("Earpiece DAC Drv Low Power Playback Switch",
+ soc_enum_eardrvlowpow),
+ SOC_ENUM("Earpiece Common Mode Playback Switch", soc_enum_earselcm),
+
+ SOC_ENUM("Headset Fade Speed Playback Switch", soc_enum_hsfadspeed),
+
+ SOC_ENUM("Charge Pump High Threshold For Low Voltage",
+ soc_enum_envdeththre),
+ SOC_ENUM("Charge Pump Low Threshold For Low Voltage",
+ soc_enum_envdetlthre),
+ SOC_ENUM("Charge Pump Envelope Detection", soc_enum_envdetcpen),
+ SOC_ENUM("Charge Pump Envelope Detection Decay Time",
+ soc_enum_envdettime),
+
+ SOC_ENUM("Mic 1 Type Capture Switch", soc_enum_ensemic1),
+ SOC_ENUM("Mic 2 Type Capture Switch", soc_enum_ensemic2),
+ SOC_ENUM("Mic 1 Low Power Capture Switch", soc_enum_lowpowmic1),
+ SOC_ENUM("Mic 2 Low Power Capture Switch", soc_enum_lowpowmic2),
+
+ SOC_ENUM("LineIn High Pass Capture Switch", soc_enum_ad12nh),
+ SOC_ENUM("Mic High Pass Capture Switch", soc_enum_ad34nh),
+ SOC_ENUM("LineIn Mode Capture Switch", soc_enum_ad12voice),
+ SOC_ENUM("Mic Mode Capture Switch", soc_enum_ad34voice),
+
+ SOC_ENUM("Headset Mode Playback Switch", soc_enum_da12voice),
+ SOC_ENUM("IHF Mode Playback Switch", soc_enum_da34voice),
+ SOC_ENUM("Vibra Mode Playback Switch", soc_enum_da56voice),
+
+ SOC_ENUM("IHF and Headset Swap Playback Switch", soc_enum_swapda12_34),
+
+ SOC_ENUM("IHF Low EMI Mode Playback Switch", soc_enum_hflrswap),
+ SOC_ENUM("Vibra Low EMI Mode Playback Switch", soc_enum_vib12swap),
+
+ SOC_ENUM("IHF FIR Bypass Playback Switch", soc_enum_fir01byp),
+ SOC_ENUM("Vibra FIR Bypass Playback Switch", soc_enum_fir23byp),
+
+ /* TODO: Cannot be changed on the fly with digital channel enabled. */
+ SOC_ENUM("IHF High Volume Playback Switch", soc_enum_highvol01),
+ SOC_ENUM("Vibra High Volume Playback Switch", soc_enum_highvol23),
+
+ SOC_SINGLE("ClassD High Pass Gain Playback Volume",
+ REG_CLASSDCONF3, REG_CLASSDCONF3_DITHHPGAIN,
+ REG_CLASSDCONF3_DITHHPGAIN_MAX, NORMAL),
+ SOC_SINGLE("ClassD White Gain Playback Volume",
+ REG_CLASSDCONF3, REG_CLASSDCONF3_DITHWGAIN,
+ REG_CLASSDCONF3_DITHWGAIN_MAX, NORMAL),
+
+ SOC_ENUM("LineIn Filter Capture Switch", soc_enum_dmic12sinc),
+ SOC_ENUM("Mic Filter Capture Switch", soc_enum_dmic34sinc),
+ SOC_ENUM("HD Mic Filter Capture Switch", soc_enum_dmic56sinc),
+
+ SOC_ENUM("Headset Source Playback Route", soc_enum_da2hslr),
+
+ /* TODO: Cannot be changed on the fly with digital channel enabled. */
+ SOC_ENUM("Headset Filter Playback Switch", soc_enum_hsesinc),
+
+ SOC_ENUM("Digital Gain Fade Speed Switch", soc_enum_fadespeed),
+
+ SOC_DOUBLE_R("Vibra PWM Duty Cycle N Playback Volume",
+ REG_PWMGENCONF3, REG_PWMGENCONF5,
+ REG_PWMGENCONFX_PWMVIBXDUTCYC,
+ REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX, NORMAL),
+ SOC_DOUBLE_R("Vibra PWM Duty Cycle P Playback Volume",
+ REG_PWMGENCONF2, REG_PWMGENCONF4,
+ REG_PWMGENCONFX_PWMVIBXDUTCYC,
+ REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX, NORMAL),
+
+ /* TODO: move to DAPM */
+ SOC_ENUM("Sidetone Playback Switch", soc_enum_enfirsids),
+ SOC_ENUM("IHF L and R Bridge Playback Route", soc_enum_parlhf),
+ SOC_ENUM("Vibra 1 and 2 Bridge Playback Route", soc_enum_parlvib),
+
+ /* Digital gains for AD side */
+
+ SOC_DOUBLE_R_TLV("LineIn Master Gain Capture Volume",
+ REG_ADDIGGAIN1, REG_ADDIGGAIN2,
+ 0, REG_ADDIGGAINX_ADXGAIN_MAX, INVERT, adx_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Mic Master Gain Capture Volume",
+ REG_ADDIGGAIN3, REG_ADDIGGAIN4,
+ 0, REG_ADDIGGAINX_ADXGAIN_MAX, INVERT, adx_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("HD Mic Master Gain Capture Volume",
+ REG_ADDIGGAIN5, REG_ADDIGGAIN6,
+ 0, REG_ADDIGGAINX_ADXGAIN_MAX, INVERT, adx_dig_gain_tlv),
+
+ /* Digital gains for DA side */
+
+ SOC_DOUBLE_R_TLV("Headset Master Gain Playback Volume",
+ REG_DADIGGAIN1, REG_DADIGGAIN2,
+ 0, REG_DADIGGAINX_DAXGAIN_MAX, INVERT, dax_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("IHF Master Gain Playback Volume",
+ REG_DADIGGAIN3, REG_DADIGGAIN4,
+ 0, REG_DADIGGAINX_DAXGAIN_MAX, INVERT, dax_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Vibra Master Gain Playback Volume",
+ REG_DADIGGAIN5, REG_DADIGGAIN6,
+ 0, REG_DADIGGAINX_DAXGAIN_MAX, INVERT, dax_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Analog Loopback Gain Playback Volume",
+ REG_ADDIGLOOPGAIN1, REG_ADDIGLOOPGAIN2,
+ 0, REG_ADDIGLOOPGAINX_ADXLBGAIN_MAX, INVERT, dax_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Headset Digital Gain Playback Volume",
+ REG_HSLEARDIGGAIN, REG_HSRDIGGAIN,
+ 0, REG_HSLEARDIGGAIN_HSLDGAIN_MAX, INVERT, hs_ear_dig_gain_tlv),
+ SOC_DOUBLE_R_TLV("Sidetone Digital Gain Playback Volume",
+ REG_SIDFIRGAIN1, REG_SIDFIRGAIN2,
+ 0, REG_SIDFIRGAINX_FIRSIDXGAIN_MAX, INVERT, stfir_dig_gain_tlv),
+
+ /* Analog gains */
+
+ SOC_DOUBLE_TLV("Headset Gain Playback Volume",
+ REG_ANAGAIN3,
+ REG_ANAGAIN3_HSLGAIN, REG_ANAGAIN3_HSRGAIN,
+ REG_ANAGAIN3_HSXGAIN_MAX, INVERT, hs_gain_tlv),
+ SOC_SINGLE_TLV("Mic 1 Capture Volume",
+ REG_ANAGAIN1,
+ REG_ANAGAINX_MICXGAIN,
+ REG_ANAGAINX_MICXGAIN_MAX, NORMAL, mic_gain_tlv),
+ SOC_SINGLE_TLV("Mic 2 Capture Volume",
+ REG_ANAGAIN2,
+ REG_ANAGAINX_MICXGAIN,
+ REG_ANAGAINX_MICXGAIN_MAX, NORMAL, mic_gain_tlv),
+ SOC_DOUBLE_TLV("LineIn Capture Volume",
+ REG_ANAGAIN4,
+ REG_ANAGAIN4_LINLGAIN, REG_ANAGAIN4_LINRGAIN,
+ REG_ANAGAIN4_LINXGAIN_MAX, NORMAL, lin_gain_tlv),
+ SOC_DOUBLE_R_TLV("LineIn to Headset Bypass Playback Volume",
+ REG_DIGLINHSLGAIN, REG_DIGLINHSRGAIN,
+ REG_DIGLINHSXGAIN_LINTOHSXGAIN,
+ REG_DIGLINHSXGAIN_LINTOHSXGAIN_MAX, INVERT, lin2hs_gain_tlv),
+
+ /* Digital interface - Clocks */
+ SOC_ENUM("Digital Interface Master Generator Switch", soc_enum_mastgen),
+ SOC_ENUM("Digital Interface 0 Bit-clock Switch", soc_enum_fsbitclk0),
+ SOC_ENUM("Digital Interface 1 Bit-clock Switch", soc_enum_fsbitclk1),
+
+ /* Digital interface - DA from slot mapping */
+ SOC_ENUM("Digital Interface DA 1 From Slot Map", soc_enum_da1slotmap),
+ SOC_ENUM("Digital Interface DA 2 From Slot Map", soc_enum_da2slotmap),
+ SOC_ENUM("Digital Interface DA 3 From Slot Map", soc_enum_da3slotmap),
+ SOC_ENUM("Digital Interface DA 4 From Slot Map", soc_enum_da4slotmap),
+ SOC_ENUM("Digital Interface DA 5 From Slot Map", soc_enum_da5slotmap),
+ SOC_ENUM("Digital Interface DA 6 From Slot Map", soc_enum_da6slotmap),
+ SOC_ENUM("Digital Interface DA 7 From Slot Map", soc_enum_da7slotmap),
+ SOC_ENUM("Digital Interface DA 8 From Slot Map", soc_enum_da8slotmap),
+
+ /* Digital interface - AD to slot mapping */
+ SOC_ENUM("Digital Interface AD To Slot 0 Map", soc_enum_adslot0map),
+ SOC_ENUM("Digital Interface AD To Slot 1 Map", soc_enum_adslot1map),
+ SOC_ENUM("Digital Interface AD To Slot 2 Map", soc_enum_adslot2map),
+ SOC_ENUM("Digital Interface AD To Slot 3 Map", soc_enum_adslot3map),
+ SOC_ENUM("Digital Interface AD To Slot 4 Map", soc_enum_adslot4map),
+ SOC_ENUM("Digital Interface AD To Slot 5 Map", soc_enum_adslot5map),
+ SOC_ENUM("Digital Interface AD To Slot 6 Map", soc_enum_adslot6map),
+ SOC_ENUM("Digital Interface AD To Slot 7 Map", soc_enum_adslot7map),
+ SOC_ENUM("Digital Interface AD To Slot 8 Map", soc_enum_adslot8map),
+ SOC_ENUM("Digital Interface AD To Slot 9 Map", soc_enum_adslot9map),
+ SOC_ENUM("Digital Interface AD To Slot 10 Map", soc_enum_adslot10map),
+ SOC_ENUM("Digital Interface AD To Slot 11 Map", soc_enum_adslot11map),
+ SOC_ENUM("Digital Interface AD To Slot 12 Map", soc_enum_adslot12map),
+ SOC_ENUM("Digital Interface AD To Slot 13 Map", soc_enum_adslot13map),
+ SOC_ENUM("Digital Interface AD To Slot 14 Map", soc_enum_adslot14map),
+ SOC_ENUM("Digital Interface AD To Slot 15 Map", soc_enum_adslot15map),
+ SOC_ENUM("Digital Interface AD To Slot 16 Map", soc_enum_adslot16map),
+ SOC_ENUM("Digital Interface AD To Slot 17 Map", soc_enum_adslot17map),
+ SOC_ENUM("Digital Interface AD To Slot 18 Map", soc_enum_adslot18map),
+ SOC_ENUM("Digital Interface AD To Slot 19 Map", soc_enum_adslot19map),
+ SOC_ENUM("Digital Interface AD To Slot 20 Map", soc_enum_adslot20map),
+ SOC_ENUM("Digital Interface AD To Slot 21 Map", soc_enum_adslot21map),
+ SOC_ENUM("Digital Interface AD To Slot 22 Map", soc_enum_adslot22map),
+ SOC_ENUM("Digital Interface AD To Slot 23 Map", soc_enum_adslot23map),
+ SOC_ENUM("Digital Interface AD To Slot 24 Map", soc_enum_adslot24map),
+ SOC_ENUM("Digital Interface AD To Slot 25 Map", soc_enum_adslot25map),
+ SOC_ENUM("Digital Interface AD To Slot 26 Map", soc_enum_adslot26map),
+ SOC_ENUM("Digital Interface AD To Slot 27 Map", soc_enum_adslot27map),
+ SOC_ENUM("Digital Interface AD To Slot 28 Map", soc_enum_adslot28map),
+ SOC_ENUM("Digital Interface AD To Slot 29 Map", soc_enum_adslot29map),
+ SOC_ENUM("Digital Interface AD To Slot 30 Map", soc_enum_adslot30map),
+ SOC_ENUM("Digital Interface AD To Slot 31 Map", soc_enum_adslot31map),
+
+ /* Digital interface - Loopback */
+ SOC_ENUM("Digital Interface AD 1 Loopback Switch", soc_enum_ad1loop),
+ SOC_ENUM("Digital Interface AD 2 Loopback Switch", soc_enum_ad2loop),
+ SOC_ENUM("Digital Interface AD 3 Loopback Switch", soc_enum_ad3loop),
+ SOC_ENUM("Digital Interface AD 4 Loopback Switch", soc_enum_ad4loop),
+ SOC_ENUM("Digital Interface AD 5 Loopback Switch", soc_enum_ad5loop),
+ SOC_ENUM("Digital Interface AD 6 Loopback Switch", soc_enum_ad6loop),
+ SOC_ENUM("Digital Interface AD 7 Loopback Switch", soc_enum_ad7loop),
+ SOC_ENUM("Digital Interface AD 8 Loopback Switch", soc_enum_ad8loop),
+
+ /* Digital interface - Burst FIFO */
+ SOC_ENUM("Digital Interface 0 FIFO Enable Switch", soc_enum_if0fifoen),
+ SOC_ENUM("Burst FIFO Mask", soc_enum_bfifomask),
+ SOC_ENUM("Burst FIFO Bit-clock Frequency", soc_enum_bfifo19m2),
+ SOC_SINGLE("Burst FIFO Threshold",
+ REG_FIFOCONF1,
+ REG_FIFOCONF1_BFIFOINT_SHIFT,
+ REG_FIFOCONF1_BFIFOINT_MAX,
+ NORMAL),
+ SOC_SINGLE("Burst FIFO Length",
+ REG_FIFOCONF2,
+ REG_FIFOCONF2_BFIFOTX_SHIFT,
+ REG_FIFOCONF2_BFIFOTX_MAX,
+ NORMAL),
+ SOC_SINGLE("Burst FIFO EOS Extra Slots",
+ REG_FIFOCONF3,
+ REG_FIFOCONF3_BFIFOEXSL_SHIFT,
+ REG_FIFOCONF3_BFIFOEXSL_MAX,
+ NORMAL),
+ SOC_SINGLE("Burst FIFO FS Extra Bit-clocks",
+ REG_FIFOCONF3,
+ REG_FIFOCONF3_PREBITCLK0_SHIFT,
+ REG_FIFOCONF3_PREBITCLK0_MAX,
+ NORMAL),
+ SOC_ENUM("Burst FIFO Interface Mode", soc_enum_bfifomast),
+ SOC_ENUM("Burst FIFO Interface Switch", soc_enum_bfifoint),
+ SOC_SINGLE("Burst FIFO Switch Frame Number",
+ REG_FIFOCONF4,
+ REG_FIFOCONF4_BFIFOFRAMSW_SHIFT,
+ REG_FIFOCONF4_BFIFOFRAMSW_MAX,
+ NORMAL),
+ SOC_SINGLE("Burst FIFO Wake Up Delay",
+ REG_FIFOCONF5,
+ REG_FIFOCONF5_BFIFOWAKEUP_SHIFT,
+ REG_FIFOCONF5_BFIFOWAKEUP_MAX,
+ NORMAL),
+ SOC_SINGLE("Burst FIFO Samples In FIFO",
+ REG_FIFOCONF6,
+ REG_FIFOCONF6_BFIFOSAMPLE_SHIFT,
+ REG_FIFOCONF6_BFIFOSAMPLE_MAX,
+ NORMAL),
+
+ /* ANC */
+ SOC_SINGLE_S1R("ANC Warp Delay Shift",
+ REG_ANCCONF2,
+ REG_ANCCONF2_VALUE_MIN,
+ REG_ANCCONF2_VALUE_MAX,
+ 0),
+ SOC_SINGLE_S1R("ANC FIR Output Shift",
+ REG_ANCCONF3,
+ REG_ANCCONF3_VALUE_MIN,
+ REG_ANCCONF3_VALUE_MAX,
+ 0),
+ SOC_SINGLE_S1R("ANC IIR Output Shift",
+ REG_ANCCONF4,
+ REG_ANCCONF4_VALUE_MIN,
+ REG_ANCCONF4_VALUE_MAX,
+ 0),
+ SOC_SINGLE_S2R("ANC Warp Delay",
+ REG_ANCCONF9, REG_ANCCONF10,
+ REG_ANC_WARP_DELAY_MIN,
+ REG_ANC_WARP_DELAY_MAX,
+ 0),
+ SOC_MULTIPLE_SA("ANC FIR Coefficients",
+ anc_fir_cache,
+ REG_ANC_FIR_COEFF_MIN,
+ REG_ANC_FIR_COEFF_MAX,
+ 0),
+ SOC_MULTIPLE_SA("ANC IIR Coefficients",
+ anc_iir_cache,
+ REG_ANC_IIR_COEFF_MIN,
+ REG_ANC_IIR_COEFF_MAX,
+ 0),
+
+ /* Sidetone */
+ SOC_MULTIPLE_SA("Sidetone FIR Coefficients",
+ sid_fir_cache,
+ REG_SID_FIR_COEFF_MIN,
+ REG_SID_FIR_COEFF_MAX,
+ 0),
+ SOC_ENUM_EXT("Sidetone FIR Apply Coefficients",
+ soc_enum_applysidetone,
+ snd_soc_get_enum_double,
+ sid_apply_control_put),
+};
+
+static int ab8500_codec_set_format_if1(struct snd_soc_codec *codec, unsigned int fmt)
+{
+ unsigned int clear_mask, set_mask;
+
+ /* Master or slave */
+
+ clear_mask = BMASK(REG_DIGIFCONF3_IF1MASTER);
+ set_mask = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & FRM master */
+ pr_debug("%s: IF1 Master-mode: AB8500 master.\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF3_IF1MASTER);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & FRM slave */
+ pr_debug("%s: IF1 Master-mode: AB8500 slave.\n", __func__);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & FRM master */
+ case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */
+ pr_err("%s: ERROR: The device is either a master or a slave.\n",
+ __func__);
+ default:
+ pr_err("%s: ERROR: Unsupported master mask 0x%x\n",
+ __func__,
+ fmt & SND_SOC_DAIFMT_MASTER_MASK);
+ return -EINVAL;
+ }
+
+ ab8500_codec_update_reg_audio(codec,
+ REG_DIGIFCONF3,
+ BMASK(REG_DIGIFCONF3_IF1MASTER),
+ BMASK(REG_DIGIFCONF3_IF1MASTER));
+
+ /* I2S or TDM */
+
+ clear_mask = BMASK(REG_DIGIFCONF4_FSYNC1P) |
+ BMASK(REG_DIGIFCONF4_BITCLK1P) |
+ BMASK(REG_DIGIFCONF4_IF1FORMAT1) |
+ BMASK(REG_DIGIFCONF4_IF1FORMAT0);
+ set_mask = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S: /* I2S mode */
+ pr_debug("%s: IF1 Protocol: I2S\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF4_IF1FORMAT1);
+ break;
+ case SND_SOC_DAIFMT_DSP_B: /* L data MSB during FRM LRC */
+ pr_debug("%s: IF1 Protocol: DSP B (TDM)\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF4_IF1FORMAT0);
+ break;
+ default:
+ pr_err("%s: ERROR: Unsupported format (0x%x)!\n",
+ __func__,
+ fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+ return -EINVAL;
+ }
+
+ ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF4, clear_mask, set_mask);
+
+ return 0;
+}
+
+static int ab8500_codec_set_word_length_if1(struct snd_soc_codec *codec, unsigned int wl)
+{
+ unsigned int clear_mask, set_mask;
+
+ clear_mask = BMASK(REG_DIGIFCONF4_IF1WL1) | BMASK(REG_DIGIFCONF4_IF1WL0);
+ set_mask = 0;
+
+ switch (wl) {
+ case 16:
+ break;
+ case 20:
+ set_mask |= BMASK(REG_DIGIFCONF4_IF1WL0);
+ break;
+ case 24:
+ set_mask |= BMASK(REG_DIGIFCONF4_IF1WL1);
+ break;
+ case 32:
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0WL1) |
+ BMASK(REG_DIGIFCONF2_IF0WL0);
+ break;
+ default:
+ pr_err("%s: Unsupporter word-length 0x%x\n", __func__, wl);
+ return -EINVAL;
+ }
+
+ pr_debug("%s: Word-length: %d bits.\n", __func__, wl);
+ ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF4, clear_mask, set_mask);
+
+ return 0;
+}
+
+static int ab8500_codec_set_bit_delay_if1(struct snd_soc_codec *codec, unsigned int delay)
+{
+ unsigned int clear_mask, set_mask;
+
+ clear_mask = BMASK(REG_DIGIFCONF4_IF1DEL);
+ set_mask = 0;
+
+ switch (delay) {
+ case 0:
+ break;
+ case 1:
+ set_mask |= BMASK(REG_DIGIFCONF4_IF1DEL);
+ break;
+ default:
+ pr_err("%s: ERROR: Unsupported bit-delay (0x%x)!\n", __func__, delay);
+ return -EINVAL;
+ }
+
+ pr_debug("%s: IF1 Bit-delay: %d bits.\n", __func__, delay);
+ ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF4, clear_mask, set_mask);
+
+ return 0;
+}
+
+/* Configures audio macrocell into the AB8500 Chip */
+static void ab8500_codec_configure_audio_macrocell(struct snd_soc_codec *codec)
+{
+ int data, ret;
+
+ ret = ab8500_sysctrl_write(AB8500_STW4500CTRL3,
+ AB8500_STW4500CTRL3_CLK32KOUT2DIS | AB8500_STW4500CTRL3_RESETAUDN,
+ AB8500_STW4500CTRL3_RESETAUDN);
+ if (ret < 0)
+ pr_err("%s: WARN: Unable to set reg STW4500CTRL3!\n", __func__);
+
+ data = ab8500_codec_read_reg(codec, AB8500_MISC, AB8500_GPIO_DIR4_REG);
+ data |= GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT | GPIO31_DIR_OUTPUT;
+ ab8500_codec_write_reg(codec, AB8500_MISC, AB8500_GPIO_DIR4_REG, data);
+}
+
+/* Extended interface for codec-driver */
+
+int ab8500_audio_power_control(bool power_on)
+{
+ int pwr_mask = BMASK(REG_POWERUP_POWERUP) | BMASK(REG_POWERUP_ENANA);
+
+ if (ab8500_codec == NULL) {
+ pr_err("%s: ERROR: AB8500 ASoC-driver not yet probed!\n", __func__);
+ return -EIO;
+ }
+
+ pr_debug("%s AB8500.", (power_on) ? "Enabling" : "Disabling");
+
+ return ab8500_codec_update_reg_audio(ab8500_codec, REG_POWERUP,
+ pwr_mask, (power_on) ? pwr_mask : REG_MASK_NONE);
+}
+
+void ab8500_audio_pwm_vibra(unsigned char speed_left_pos,
+ unsigned char speed_left_neg,
+ unsigned char speed_right_pos,
+ unsigned char speed_right_neg)
+{
+ unsigned int clear_mask, set_mask;
+ bool vibra_on;
+
+ if (ab8500_codec == NULL) {
+ pr_err("%s: ERROR: AB8500 ASoC-driver not yet probed!\n", __func__);
+ return;
+ }
+
+ vibra_on = speed_left_pos | speed_left_neg | speed_right_pos | speed_right_neg;
+ if (!vibra_on) {
+ speed_left_pos = 0;
+ speed_left_neg = 0;
+ speed_right_pos = 0;
+ speed_right_neg = 0;
+ }
+
+ pr_debug("%s: PWM-vibra (%d, %d, %d, %d).\n",
+ __func__,
+ speed_left_pos,
+ speed_left_neg,
+ speed_right_pos,
+ speed_right_neg);
+
+ set_mask = BMASK(REG_PWMGENCONF1_PWMTOVIB1) |
+ BMASK(REG_PWMGENCONF1_PWMTOVIB2) |
+ BMASK(REG_PWMGENCONF1_PWM1CTRL) |
+ BMASK(REG_PWMGENCONF1_PWM2CTRL) |
+ BMASK(REG_PWMGENCONF1_PWM1NCTRL) |
+ BMASK(REG_PWMGENCONF1_PWM1PCTRL) |
+ BMASK(REG_PWMGENCONF1_PWM2NCTRL) |
+ BMASK(REG_PWMGENCONF1_PWM2PCTRL);
+ ab8500_codec_update_reg_audio(ab8500_codec, REG_PWMGENCONF1, 0x00, set_mask);
+
+ if (speed_left_pos > REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX)
+ speed_left_pos = REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX;
+ ab8500_codec_update_reg_audio(ab8500_codec, REG_PWMGENCONF3, REG_MASK_ALL, speed_left_pos);
+
+ if (speed_left_neg > REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX)
+ speed_left_neg = REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX;
+ ab8500_codec_update_reg_audio(ab8500_codec, REG_PWMGENCONF2, REG_MASK_ALL, speed_left_neg);
+
+ if (speed_right_pos > REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX)
+ speed_right_pos = REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX;
+ ab8500_codec_update_reg_audio(ab8500_codec, REG_PWMGENCONF5, REG_MASK_ALL, speed_right_pos);
+
+ if (speed_right_neg > REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX)
+ speed_right_neg = REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX;
+ ab8500_codec_update_reg_audio(ab8500_codec, REG_PWMGENCONF4, REG_MASK_ALL, speed_right_neg);
+
+ if (vibra_on) {
+ clear_mask = 0;
+ set_mask = BMASK(REG_ANACONF4_ENVIB1) | BMASK(REG_ANACONF4_ENVIB2);
+ } else {
+ clear_mask = BMASK(REG_ANACONF4_ENVIB1) | BMASK(REG_ANACONF4_ENVIB2);
+ set_mask = 0;
+ };
+ ab8500_codec_update_reg_audio(ab8500_codec, REG_ANACONF4, clear_mask, set_mask);
+}
+
+int ab8500_audio_set_word_length(struct snd_soc_dai *dai, unsigned int wl)
+{
+ unsigned int clear_mask, set_mask;
+ struct snd_soc_codec *codec = dai->codec;
+
+ clear_mask = BMASK(REG_DIGIFCONF2_IF0WL0) | BMASK(REG_DIGIFCONF2_IF0WL1);
+ set_mask = 0;
+
+ switch (wl) {
+ case 16:
+ break;
+ case 20:
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0WL0);
+ break;
+ case 24:
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0WL1);
+ break;
+ case 32:
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0WL1) |
+ BMASK(REG_DIGIFCONF2_IF0WL0);
+ break;
+ default:
+ pr_err("%s: Unsupported word-length 0x%x\n", __func__, wl);
+ return -EINVAL;
+ }
+
+ pr_debug("%s: IF0 Word-length: %d bits.\n", __func__, wl);
+ ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF2, clear_mask, set_mask);
+
+ return 0;
+}
+
+int ab8500_audio_set_bit_delay(struct snd_soc_dai *dai, unsigned int delay)
+{
+ unsigned int clear_mask, set_mask;
+ struct snd_soc_codec *codec = dai->codec;
+
+ clear_mask = BMASK(REG_DIGIFCONF2_IF0DEL);
+ set_mask = 0;
+
+ switch (delay) {
+ case 0:
+ break;
+ case 1:
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0DEL);
+ break;
+ default:
+ pr_err("%s: ERROR: Unsupported bit-delay (0x%x)!\n", __func__, delay);
+ return -EINVAL;
+ }
+
+ pr_debug("%s: IF0 Bit-delay: %d bits.\n", __func__, delay);
+ ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF2, clear_mask, set_mask);
+
+ return 0;
+}
+
+int ab8500_audio_setup_if1(struct snd_soc_codec *codec,
+ unsigned int fmt,
+ unsigned int wl,
+ unsigned int delay)
+{
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ ret = ab8500_codec_set_format_if1(codec, fmt);
+ if (ret)
+ return -1;
+
+ ret = ab8500_codec_set_bit_delay_if1(codec, delay);
+ if (ret)
+ return -1;
+
+
+ ret = ab8500_codec_set_word_length_if1(codec, wl);
+ if (ret)
+ return -1;
+
+ return 0;
+}
+
+/* ANC FIR-coefficients configuration sequence */
+static void ab8500_audio_anc_fir(struct snd_soc_codec *codec,
+ unsigned int bank, unsigned int param)
+{
+ if (param == 0 && bank == 0)
+ snd_soc_update_bits(codec, REG_ANCCONF1,
+ REG_MASK_NONE, BMASK(REG_ANCCONF1_ANCFIRUPDATE));
+
+ snd_soc_write(codec, REG_ANCCONF5,
+ anc_fir_cache[param] >> 8 & REG_MASK_ALL);
+ snd_soc_write(codec, REG_ANCCONF6,
+ anc_fir_cache[param] & REG_MASK_ALL);
+
+ if (param == REG_ANC_FIR_COEFFS - 1 && bank == 1)
+ snd_soc_update_bits(codec, REG_ANCCONF1,
+ BMASK(REG_ANCCONF1_ANCFIRUPDATE), REG_MASK_NONE);
+}
+
+/* ANC IIR-coefficients configuration sequence */
+static void ab8500_audio_anc_iir(struct snd_soc_codec *codec,
+ unsigned int bank, unsigned int param)
+{
+ if (param == 0) {
+ if (bank == 0) {
+ snd_soc_update_bits(codec, REG_ANCCONF1,
+ REG_MASK_NONE, BMASK(REG_ANCCONF1_ANCIIRINIT));
+ usleep_range(AB8500_ANC_SM_DELAY, AB8500_ANC_SM_DELAY);
+ snd_soc_update_bits(codec, REG_ANCCONF1,
+ BMASK(REG_ANCCONF1_ANCIIRINIT), REG_MASK_NONE);
+ usleep_range(AB8500_ANC_SM_DELAY, AB8500_ANC_SM_DELAY);
+ } else {
+ snd_soc_update_bits(codec, REG_ANCCONF1,
+ REG_MASK_NONE, BMASK(REG_ANCCONF1_ANCIIRUPDATE));
+ }
+ } else if (param > 3) {
+ snd_soc_write(codec, REG_ANCCONF7, REG_MASK_NONE);
+ snd_soc_write(codec, REG_ANCCONF8,
+ anc_iir_cache[param] >> 16 & REG_MASK_ALL);
+ }
+
+ snd_soc_write(codec, REG_ANCCONF7,
+ anc_iir_cache[param] >> 8 & REG_MASK_ALL);
+ snd_soc_write(codec, REG_ANCCONF8,
+ anc_iir_cache[param] & REG_MASK_ALL);
+
+ if (param == REG_ANC_IIR_COEFFS - 1 && bank == 1)
+ snd_soc_update_bits(codec, REG_ANCCONF1,
+ BMASK(REG_ANCCONF1_ANCIIRUPDATE), REG_MASK_NONE);
+}
+
+/* ANC IIR-/FIR-coefficients configuration sequence */
+void ab8500_audio_anc_configure(struct snd_soc_codec *codec,
+ bool apply_fir, bool apply_iir)
+{
+ unsigned int bank, param;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ if (apply_fir)
+ snd_soc_update_bits(codec, REG_ANCCONF1,
+ BMASK(REG_ANCCONF1_ENANC), REG_MASK_NONE);
+
+ snd_soc_update_bits(codec, REG_ANCCONF1,
+ REG_MASK_NONE, BMASK(REG_ANCCONF1_ENANC));
+
+ if (apply_fir)
+ for (bank = 0; bank < AB8500_NR_OF_ANC_COEFF_BANKS; bank++)
+ for (param = 0; param < REG_ANC_FIR_COEFFS; param++)
+ ab8500_audio_anc_fir(codec, bank, param);
+
+ if (apply_iir)
+ for (bank = 0; bank < AB8500_NR_OF_ANC_COEFF_BANKS; bank++)
+ for (param = 0; param < REG_ANC_IIR_COEFFS; param++)
+ ab8500_audio_anc_iir(codec, bank, param);
+
+ pr_debug("%s: Exit.\n", __func__);
+}
+
+int ab8500_audio_set_adcm(enum ab8500_audio_adcm req_adcm)
+{
+ if (ab8500_codec == NULL) {
+ pr_err("%s: ERROR: AB8500 ASoC-driver not yet probed!\n",
+ __func__);
+ return -EIO;
+ }
+
+ if (adcm == req_adcm)
+ return 0;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ if (AB8500_AUDIO_ADCM_FORCE_UP == req_adcm ||
+ AB8500_AUDIO_ADCM_FORCE_DOWN == req_adcm) {
+
+ mutex_lock(&ab8500_codec->mutex);
+
+ adcm_anaconf5 = snd_soc_read(ab8500_codec, REG_ANACONF5);
+ adcm_muteconf = snd_soc_read(ab8500_codec, REG_MUTECONF);
+ adcm_anaconf4 = snd_soc_read(ab8500_codec, REG_ANACONF4);
+
+ if (AB8500_AUDIO_ADCM_FORCE_UP == req_adcm) {
+ snd_soc_update_bits(ab8500_codec, REG_ANACONF5,
+ REG_MASK_NONE, ADCM_ANACONF5_MASK);
+ snd_soc_update_bits(ab8500_codec, REG_MUTECONF,
+ REG_MASK_NONE, ADCM_MUTECONF_MASK);
+ snd_soc_update_bits(ab8500_codec, REG_ANACONF4,
+ REG_MASK_NONE, ADCM_ANACONF4_MASK);
+ } else {
+ snd_soc_update_bits(ab8500_codec, REG_ANACONF5,
+ ADCM_ANACONF5_MASK, REG_MASK_NONE);
+ }
+ } else if (AB8500_AUDIO_ADCM_NORMAL == req_adcm) {
+ if (AB8500_AUDIO_ADCM_FORCE_UP == adcm) {
+ snd_soc_update_bits(ab8500_codec, REG_ANACONF5,
+ ~adcm_anaconf5 & ADCM_ANACONF5_MASK,
+ adcm_anaconf5 & ADCM_ANACONF5_MASK);
+ snd_soc_update_bits(ab8500_codec, REG_MUTECONF,
+ ~adcm_muteconf & ADCM_MUTECONF_MASK,
+ adcm_muteconf & ADCM_MUTECONF_MASK);
+ snd_soc_update_bits(ab8500_codec, REG_ANACONF4,
+ ~adcm_anaconf4 & ADCM_ANACONF4_MASK,
+ adcm_anaconf4 & ADCM_ANACONF4_MASK);
+ } else {
+ snd_soc_update_bits(ab8500_codec, REG_ANACONF5,
+ ~adcm_anaconf5 & ADCM_ANACONF5_MASK,
+ adcm_anaconf5 & ADCM_ANACONF5_MASK);
+ }
+ }
+
+ adcm = req_adcm;
+
+ if (AB8500_AUDIO_ADCM_NORMAL == adcm)
+ mutex_unlock(&ab8500_codec->mutex);
+
+ pr_debug("%s: Exit.\n", __func__);
+
+ return 0;
+}
+
+static int ab8500_codec_add_widgets(struct snd_soc_codec *codec)
+{
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(&codec->dapm, ab8500_dapm_widgets,
+ ARRAY_SIZE(ab8500_dapm_widgets));
+ if (ret < 0) {
+ pr_err("%s: Failed to create DAPM controls (%d).\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&codec->dapm, dapm_routes, ARRAY_SIZE(dapm_routes));
+ if (ret < 0) {
+ pr_err("%s: Failed to add DAPM routes (%d).\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ab8500_codec_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+static int ab8500_codec_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+static int ab8500_codec_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+static void ab8500_codec_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ pr_debug("%s Enter.\n", __func__);
+}
+
+static int ab8500_codec_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ pr_err("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+/* Gates clocking according format mask */
+static int ab8500_codec_set_dai_clock_gate(struct snd_soc_codec *codec, unsigned int fmt)
+{
+ unsigned int clear_mask;
+ unsigned int set_mask;
+
+ clear_mask = BMASK(REG_DIGIFCONF1_ENMASTGEN) |
+ BMASK(REG_DIGIFCONF1_ENFSBITCLK0);
+
+ set_mask = BMASK(REG_DIGIFCONF1_ENMASTGEN);
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
+ case SND_SOC_DAIFMT_CONT: /* continuous clock */
+ pr_debug("%s: IF0 Clock is continuous.\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF1_ENFSBITCLK0);
+ break;
+ case SND_SOC_DAIFMT_GATED: /* clock is gated */
+ pr_debug("%s: IF0 Clock is gated.\n", __func__);
+ break;
+ default:
+ pr_err("%s: ERROR: Unsupported clock mask (0x%x)!\n",
+ __func__,
+ fmt & SND_SOC_DAIFMT_CLOCK_MASK);
+ return -EINVAL;
+ }
+
+ ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF1, clear_mask, set_mask);
+
+ return 0;
+}
+
+static int ab8500_codec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ unsigned int clear_mask;
+ unsigned int set_mask;
+ struct snd_soc_codec *codec = dai->codec;
+ int err;
+
+ pr_debug("%s: Enter (fmt = 0x%x)\n", __func__, fmt);
+
+ clear_mask = BMASK(REG_DIGIFCONF3_IF1DATOIF0AD) |
+ BMASK(REG_DIGIFCONF3_IF1CLKTOIF0CLK) |
+ BMASK(REG_DIGIFCONF3_IF0BFIFOEN) |
+ BMASK(REG_DIGIFCONF3_IF0MASTER);
+ set_mask = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & FRM master */
+ pr_debug("%s: IF0 Master-mode: AB8500 master.\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF3_IF0MASTER);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & FRM slave */
+ pr_debug("%s: IF0 Master-mode: AB8500 slave.\n", __func__);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & FRM master */
+ case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */
+ pr_err("%s: ERROR: The device is either a master or a slave.\n", __func__);
+ default:
+ pr_err("%s: ERROR: Unsupporter master mask 0x%x\n",
+ __func__,
+ (fmt & SND_SOC_DAIFMT_MASTER_MASK));
+ return -EINVAL;
+ break;
+ }
+
+ ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF3, clear_mask, set_mask);
+
+ /* Set clock gating */
+ err = ab8500_codec_set_dai_clock_gate(codec, fmt);
+ if (err) {
+ pr_err("%s: ERRROR: Failed to set clock gate (%d).\n", __func__, err);
+ return err;
+ }
+
+ /* Setting data transfer format */
+
+ clear_mask = BMASK(REG_DIGIFCONF2_IF0FORMAT0) |
+ BMASK(REG_DIGIFCONF2_IF0FORMAT1) |
+ BMASK(REG_DIGIFCONF2_FSYNC0P) |
+ BMASK(REG_DIGIFCONF2_BITCLK0P);
+ set_mask = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S: /* I2S mode */
+ pr_debug("%s: IF0 Protocol: I2S\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0FORMAT1);
+
+ /* 32 bit, 0 delay */
+ ab8500_audio_set_word_length(dai, 32);
+ ab8500_audio_set_bit_delay(dai, 0);
+
+ break;
+ case SND_SOC_DAIFMT_DSP_A: /* L data MSB after FRM LRC */
+ pr_debug("%s: IF0 Protocol: DSP A (TDM)\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0FORMAT0);
+ break;
+ case SND_SOC_DAIFMT_DSP_B: /* L data MSB during FRM LRC */
+ pr_debug("%s: IF0 Protocol: DSP B (TDM)\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF2_IF0FORMAT0);
+ break;
+ default:
+ pr_err("%s: ERROR: Unsupported format (0x%x)!\n",
+ __func__,
+ fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
+ pr_debug("%s: IF0: Normal bit clock, normal frame\n", __func__);
+ break;
+ case SND_SOC_DAIFMT_NB_IF: /* normal BCLK + inv FRM */
+ pr_debug("%s: IF0: Normal bit clock, inverted frame\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF2_FSYNC0P);
+ break;
+ case SND_SOC_DAIFMT_IB_NF: /* invert BCLK + nor FRM */
+ pr_debug("%s: IF0: Inverted bit clock, normal frame\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF2_BITCLK0P);
+ break;
+ case SND_SOC_DAIFMT_IB_IF: /* invert BCLK + FRM */
+ pr_debug("%s: IF0: Inverted bit clock, inverted frame\n", __func__);
+ set_mask |= BMASK(REG_DIGIFCONF2_FSYNC0P);
+ set_mask |= BMASK(REG_DIGIFCONF2_BITCLK0P);
+ break;
+ default:
+ pr_err("%s: ERROR: Unsupported INV mask 0x%x\n",
+ __func__,
+ (fmt & SND_SOC_DAIFMT_INV_MASK));
+ return -EINVAL;
+ break;
+ }
+
+ ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF2, clear_mask, set_mask);
+
+ return 0;
+}
+
+static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots, int slot_width)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ unsigned int set_mask, clear_mask, slots_active;
+
+ /* Only 16 bit slot width is supported at the moment in TDM mode */
+ if (slot_width != 16) {
+ pr_err("%s: ERROR: Unsupported slot_width %d.\n",
+ __func__, slot_width);
+ return -EINVAL;
+ }
+
+ /* Setup TDM clocking according to slot count */
+ pr_debug("%s: Slots, total: %d\n", __func__, slots);
+ clear_mask = BMASK(REG_DIGIFCONF1_IF0BITCLKOS0) |
+ BMASK(REG_DIGIFCONF1_IF0BITCLKOS1);
+ switch (slots) {
+ case 2:
+ set_mask = REG_MASK_NONE;
+ break;
+ case 4:
+ set_mask = BMASK(REG_DIGIFCONF1_IF0BITCLKOS0);
+ break;
+ case 8:
+ set_mask = BMASK(REG_DIGIFCONF1_IF0BITCLKOS1);
+ break;
+ case 16:
+ set_mask = BMASK(REG_DIGIFCONF1_IF0BITCLKOS0) |
+ BMASK(REG_DIGIFCONF1_IF0BITCLKOS1);
+ break;
+ default:
+ pr_err("%s: ERROR: Unsupported number of slots (%d)!\n", __func__, slots);
+ return -EINVAL;
+ }
+ ab8500_codec_update_reg_audio(codec, REG_DIGIFCONF1, clear_mask, set_mask);
+
+ /* Setup TDM DA according to active tx slots */
+ clear_mask = REG_DASLOTCONFX_SLTODAX_MASK;
+ slots_active = hweight32(tx_mask);
+ pr_debug("%s: Slots, active, TX: %d\n", __func__, slots_active);
+ switch (slots_active) {
+ case 0:
+ break;
+ case 1:
+ /* Slot 9 -> DA_IN1 & DA_IN3 */
+ ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF1, clear_mask, 9);
+ ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF3, clear_mask, 9);
+ break;
+ case 2:
+ /* Slot 9 -> DA_IN1 & DA_IN3, Slot 11 -> DA_IN2 & DA_IN4 */
+ ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF1, clear_mask, 9);
+ ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF3, clear_mask, 9);
+ ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF2, clear_mask, 11);
+ ab8500_codec_update_reg_audio(codec, REG_DASLOTCONF4, clear_mask, 11);
+
+ break;
+ case 8:
+ pr_debug("%s: In 8-channel mode DA-from-slot mapping is set manually.", __func__);
+ break;
+ default:
+ pr_err("%s: Unsupported number of active TX-slots (%d)!\n", __func__, slots_active);
+ return -EINVAL;
+ }
+
+ /* Setup TDM AD according to active RX-slots */
+ slots_active = hweight32(rx_mask);
+ pr_debug("%s: Slots, active, RX: %d\n", __func__, slots_active);
+ switch (slots_active) {
+ case 0:
+ break;
+ case 1:
+ /* AD_OUT3 -> slot 0 & 1 */
+ ab8500_codec_update_reg_audio(codec, REG_ADSLOTSEL1,
+ REG_MASK_ALL,
+ REG_ADSLOTSELX_AD_OUT3_TO_SLOT_EVEN |
+ REG_ADSLOTSELX_AD_OUT3_TO_SLOT_ODD);
+ break;
+ case 2:
+ /* AD_OUT3 -> slot 0, AD_OUT2 -> slot 1 */
+ ab8500_codec_update_reg_audio(codec, REG_ADSLOTSEL1,
+ REG_MASK_ALL,
+ REG_ADSLOTSELX_AD_OUT3_TO_SLOT_EVEN |
+ REG_ADSLOTSELX_AD_OUT2_TO_SLOT_ODD);
+ break;
+ case 8:
+ pr_debug("%s: In 8-channel mode AD-to-slot mapping is set manually.", __func__);
+ break;
+ default:
+ pr_err("%s: Unsupported number of active RX-slots (%d)!\n", __func__, slots_active);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct snd_soc_dai_driver ab8500_codec_dai[] = {
+ {
+ .name = "ab8500-codec-dai.0",
+ .id = 0,
+ .playback = {
+ .stream_name = "ab8500_0p",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = AB8500_SUPPORTED_RATE,
+ .formats = AB8500_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .startup = ab8500_codec_pcm_startup,
+ .prepare = ab8500_codec_pcm_prepare,
+ .hw_params = ab8500_codec_pcm_hw_params,
+ .shutdown = ab8500_codec_pcm_shutdown,
+ .set_sysclk = ab8500_codec_set_dai_sysclk,
+ .set_tdm_slot = ab8500_codec_set_dai_tdm_slot,
+ .set_fmt = ab8500_codec_set_dai_fmt,
+ }
+ },
+ .symmetric_rates = 1
+ },
+ {
+ .name = "ab8500-codec-dai.1",
+ .id = 1,
+ .capture = {
+ .stream_name = "ab8500_0c",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = AB8500_SUPPORTED_RATE,
+ .formats = AB8500_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .startup = ab8500_codec_pcm_startup,
+ .prepare = ab8500_codec_pcm_prepare,
+ .hw_params = ab8500_codec_pcm_hw_params,
+ .shutdown = ab8500_codec_pcm_shutdown,
+ .set_sysclk = ab8500_codec_set_dai_sysclk,
+ .set_tdm_slot = ab8500_codec_set_dai_tdm_slot,
+ .set_fmt = ab8500_codec_set_dai_fmt,
+ }
+ },
+ .symmetric_rates = 1
+ }
+};
+
+static int ab8500_codec_probe(struct snd_soc_codec *codec)
+{
+ int i, ret;
+ u8 *cache = codec->reg_cache;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ ab8500_codec_configure_audio_macrocell(codec);
+
+ for (i = REG_AUDREV; i >= REG_POWERUP; i--)
+ ab8500_codec_write_reg_audio(codec, i, cache[i]);
+
+ /* Add controls */
+ ret = snd_soc_add_codec_controls(codec, ab8500_snd_controls,
+ ARRAY_SIZE(ab8500_snd_controls));
+ if (ret < 0) {
+ pr_err("%s: failed to add soc controls (%d).\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* Add DAPM-widgets */
+ ret = ab8500_codec_add_widgets(codec);
+ if (ret < 0) {
+ pr_err("%s: Failed add widgets (%d).\n", __func__, ret);
+ return ret;
+ }
+
+ ab8500_codec = codec;
+
+ return ret;
+}
+
+static int ab8500_codec_remove(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_free(&codec->dapm);
+ ab8500_codec = NULL;
+
+ return 0;
+}
+
+static int ab8500_codec_suspend(struct snd_soc_codec *codec)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+static int ab8500_codec_resume(struct snd_soc_codec *codec)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+struct snd_soc_codec_driver ab8500_codec_driver = {
+ .probe = ab8500_codec_probe,
+ .remove = ab8500_codec_remove,
+ .suspend = ab8500_codec_suspend,
+ .resume = ab8500_codec_resume,
+ .read = ab8500_codec_read_reg_audio,
+ .write = ab8500_codec_write_reg_audio,
+ .reg_cache_size = ARRAY_SIZE(ab8500_reg_cache),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = ab8500_reg_cache,
+};
+
+static int __devinit ab8500_codec_driver_probe(struct platform_device *pdev)
+{
+ int err;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ pr_info("%s: Register codec.\n", __func__);
+ err = snd_soc_register_codec(&pdev->dev,
+ &ab8500_codec_driver,
+ ab8500_codec_dai,
+ ARRAY_SIZE(ab8500_codec_dai));
+
+ if (err < 0) {
+ pr_err("%s: Error: Failed to register codec (%d).\n",
+ __func__, err);
+ }
+
+ return err;
+}
+
+static int __devexit ab8500_codec_driver_remove(struct platform_device *pdev)
+{
+ pr_info("%s Enter.\n", __func__);
+
+ snd_soc_unregister_codec(&pdev->dev);
+
+ return 0;
+}
+
+static int ab8500_codec_driver_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+static int ab8500_codec_driver_resume(struct platform_device *pdev)
+{
+ pr_debug("%s Enter.\n", __func__);
+
+ return 0;
+}
+
+static struct platform_driver ab8500_codec_platform_driver = {
+ .driver = {
+ .name = "ab8500-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ab8500_codec_driver_probe,
+ .remove = __devexit_p(ab8500_codec_driver_remove),
+ .suspend = ab8500_codec_driver_suspend,
+ .resume = ab8500_codec_driver_resume,
+};
+
+static int __devinit ab8500_codec_platform_driver_init(void)
+{
+ int ret;
+
+ pr_info("%s: Enter.\n", __func__);
+
+ ret = platform_driver_register(&ab8500_codec_platform_driver);
+ if (ret != 0) {
+ pr_err("%s: Failed to register AB8500 platform driver (%d)!\n",
+ __func__, ret);
+ }
+
+ return ret;
+}
+
+static void __exit ab8500_codec_platform_driver_exit(void)
+{
+ pr_info("%s: Enter.\n", __func__);
+
+ platform_driver_unregister(&ab8500_codec_platform_driver);
+}
+
+module_init(ab8500_codec_platform_driver_init);
+module_exit(ab8500_codec_platform_driver_exit);
+
+MODULE_DESCRIPTION("AB8500 Codec driver");
+MODULE_ALIAS("platform:ab8500-codec");
+MODULE_AUTHOR("ST-Ericsson");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/ab8500_audio.h b/sound/soc/codecs/ab8500_audio.h
new file mode 100644
index 00000000000..0b3d9bc92fa
--- /dev/null
+++ b/sound/soc/codecs/ab8500_audio.h
@@ -0,0 +1,683 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Mikko J. Lehto <mikko.lehto@symbio.com>,
+ * Mikko Sarmanne <mikko.sarmanne@symbio.com>,
+ * Ola Lilja <ola.o.lilja@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#ifndef AB8500_CODEC_REGISTERS_H
+#define AB8500_CODEC_REGISTERS_H
+
+#define AB8500_SUPPORTED_RATE (SNDRV_PCM_RATE_48000)
+#define AB8500_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE)
+
+extern struct snd_soc_dai_driver ab8500_codec_dai[];
+extern struct snd_soc_codec_driver soc_codec_dev_ab8500;
+
+#define LINEIN_RAMP_DELAY 50
+
+/* Extended interface for codec-driver */
+
+int ab8500_audio_power_control(bool power_on);
+void ab8500_audio_pwm_vibra(unsigned char speed_left_pos,
+ unsigned char speed_left_neg,
+ unsigned char speed_right_pos,
+ unsigned char speed_right_neg);
+int ab8500_audio_set_word_length(struct snd_soc_dai *dai, unsigned int wl);
+int ab8500_audio_set_bit_delay(struct snd_soc_dai *dai, unsigned int delay);
+int ab8500_audio_setup_if1(struct snd_soc_codec *codec,
+ unsigned int fmt,
+ unsigned int wl,
+ unsigned int delay);
+
+void ab8500_audio_anc_configure(struct snd_soc_codec *codec,
+ bool apply_fir, bool apply_iir);
+
+enum ab8500_audio_adcm {
+ AB8500_AUDIO_ADCM_NORMAL,
+ AB8500_AUDIO_ADCM_FORCE_UP,
+ AB8500_AUDIO_ADCM_FORCE_DOWN
+};
+int ab8500_audio_set_adcm(enum ab8500_audio_adcm req_adcm);
+
+#define SOC_SINGLE_VALUE_S1R(xreg0, xcount, xmin, xmax, xinvert) \
+ ((unsigned long)&(struct soc_smra_control) \
+ { .reg = ((unsigned int[]){ xreg0 }), \
+ .rcount = 1, .count = xcount, \
+ .invert = xinvert, .min = xmin, .max = xmax})
+
+#define SOC_SINGLE_VALUE_S2R(xreg0, xreg1, xcount, xmin, xmax, xinvert) \
+ ((unsigned long)&(struct soc_smra_control) \
+ {.reg = ((unsigned int[]){ xreg0, xreg1 }), \
+ .rcount = 2, .count = xcount, \
+ .min = xmin, .max = xmax, .invert = xinvert})
+
+#define SOC_SINGLE_VALUE_S4R(xreg0, xreg1, xreg2, xreg3, \
+ xcount, xmin, xmax, xinvert) \
+ ((unsigned long)&(struct soc_smra_control) \
+ {.reg = ((unsigned int[]){ xreg0, xreg1, xreg2, xreg3 }), \
+ .rcount = 4, .count = xcount, \
+ .min = xmin, .max = xmax, .invert = xinvert})
+
+#define SOC_SINGLE_VALUE_S8R(xreg0, xreg1, xreg2, xreg3, \
+ xreg4, xreg5, xreg6, xreg7, xcount, xmin, xmax, xinvert) \
+ ((unsigned long)&(struct soc_smra_control) \
+ {.reg = ((unsigned int[]){ xreg0, xreg1, xreg2, xreg3, \
+ xreg4, xreg5, xreg6, xreg7 }), \
+ .rcount = 8, .count = xcount, \
+ .min = xmin, .max = xmax, .invert = xinvert})
+
+#define SOC_MULTIPLE_VALUE_SA(xvalues, xcount, xmin, xmax, xinvert) \
+ ((unsigned long)&(struct soc_smra_control) \
+ {.values = xvalues, .rcount = 1, .count = xcount, \
+ .min = xmin, .max = xmax, .invert = xinvert})
+
+#define SOC_ENUM_STROBE_DECL(name, xreg, xbit, xinvert, xtexts) \
+ struct soc_enum name = SOC_ENUM_DOUBLE(xreg, xbit, \
+ xinvert, 2, xtexts)
+
+/* Extended SOC macros */
+
+#define SOC_SINGLE_S1R(xname, reg0, min, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = snd_soc_info_s, .get = snd_soc_get_smr, .put = snd_soc_put_smr, \
+ .private_value = SOC_SINGLE_VALUE_S1R(reg0, 1, min, max, invert) }
+
+#define SOC_SINGLE_S2R(xname, reg0, reg1, min, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = snd_soc_info_s, .get = snd_soc_get_smr, .put = snd_soc_put_smr, \
+ .private_value = SOC_SINGLE_VALUE_S2R(reg0, reg1, 1, min, max, invert) }
+
+#define SOC_SINGLE_S4R(xname, reg0, reg1, reg2, reg3, min, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = snd_soc_info_s, .get = snd_soc_get_smr, .put = snd_soc_put_smr, \
+ .private_value = SOC_SINGLE_VALUE_S4R(reg0, reg1, reg2, reg3, \
+ 1, min, max, invert) }
+
+#define SOC_SINGLE_S8R(xname, reg0, reg1, reg2, reg3, \
+ reg4, reg5, reg6, reg7, min, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = snd_soc_info_s, .get = snd_soc_get_smr, .put = snd_soc_put_smr, \
+ .private_value = SOC_SINGLE_VALUE_S4R(reg0, reg1, reg2, reg3, \
+ reg4, reg5, reg6, reg7\
+ 1, min, max, invert) }
+
+#define SOC_MULTIPLE_SA(xname, values, min, max, invert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = snd_soc_info_s, .get = snd_soc_get_sa, .put = snd_soc_put_sa, \
+ .private_value = SOC_MULTIPLE_VALUE_SA(values, ARRAY_SIZE(values), \
+ min, max, invert) }
+
+#define SOC_ENUM_STROBE(xname, xenum) \
+ SOC_ENUM_EXT(xname, xenum, \
+ snd_soc_get_enum_strobe, \
+ snd_soc_put_enum_strobe)
+
+/* AB8500 audio bank (0x0d) register definitions */
+
+#define REG_POWERUP 0x00
+#define REG_AUDSWRESET 0x01
+#define REG_ADPATHENA 0x02
+#define REG_DAPATHENA 0x03
+#define REG_ANACONF1 0x04
+#define REG_ANACONF2 0x05
+#define REG_DIGMICCONF 0x06
+#define REG_ANACONF3 0x07
+#define REG_ANACONF4 0x08
+#define REG_DAPATHCONF 0x09
+#define REG_MUTECONF 0x0A
+#define REG_SHORTCIRCONF 0x0B
+#define REG_ANACONF5 0x0C
+#define REG_ENVCPCONF 0x0D
+#define REG_SIGENVCONF 0x0E
+#define REG_PWMGENCONF1 0x0F
+#define REG_PWMGENCONF2 0x10
+#define REG_PWMGENCONF3 0x11
+#define REG_PWMGENCONF4 0x12
+#define REG_PWMGENCONF5 0x13
+#define REG_ANAGAIN1 0x14
+#define REG_ANAGAIN2 0x15
+#define REG_ANAGAIN3 0x16
+#define REG_ANAGAIN4 0x17
+#define REG_DIGLINHSLGAIN 0x18
+#define REG_DIGLINHSRGAIN 0x19
+#define REG_ADFILTCONF 0x1A
+#define REG_DIGIFCONF1 0x1B
+#define REG_DIGIFCONF2 0x1C
+#define REG_DIGIFCONF3 0x1D
+#define REG_DIGIFCONF4 0x1E
+#define REG_ADSLOTSEL1 0x1F
+#define REG_ADSLOTSEL2 0x20
+#define REG_ADSLOTSEL3 0x21
+#define REG_ADSLOTSEL4 0x22
+#define REG_ADSLOTSEL5 0x23
+#define REG_ADSLOTSEL6 0x24
+#define REG_ADSLOTSEL7 0x25
+#define REG_ADSLOTSEL8 0x26
+#define REG_ADSLOTSEL9 0x27
+#define REG_ADSLOTSEL10 0x28
+#define REG_ADSLOTSEL11 0x29
+#define REG_ADSLOTSEL12 0x2A
+#define REG_ADSLOTSEL13 0x2B
+#define REG_ADSLOTSEL14 0x2C
+#define REG_ADSLOTSEL15 0x2D
+#define REG_ADSLOTSEL16 0x2E
+#define REG_ADSLOTHIZCTRL1 0x2F
+#define REG_ADSLOTHIZCTRL2 0x30
+#define REG_ADSLOTHIZCTRL3 0x31
+#define REG_ADSLOTHIZCTRL4 0x32
+#define REG_DASLOTCONF1 0x33
+#define REG_DASLOTCONF2 0x34
+#define REG_DASLOTCONF3 0x35
+#define REG_DASLOTCONF4 0x36
+#define REG_DASLOTCONF5 0x37
+#define REG_DASLOTCONF6 0x38
+#define REG_DASLOTCONF7 0x39
+#define REG_DASLOTCONF8 0x3A
+#define REG_CLASSDCONF1 0x3B
+#define REG_CLASSDCONF2 0x3C
+#define REG_CLASSDCONF3 0x3D
+#define REG_DMICFILTCONF 0x3E
+#define REG_DIGMULTCONF1 0x3F
+#define REG_DIGMULTCONF2 0x40
+#define REG_ADDIGGAIN1 0x41
+#define REG_ADDIGGAIN2 0x42
+#define REG_ADDIGGAIN3 0x43
+#define REG_ADDIGGAIN4 0x44
+#define REG_ADDIGGAIN5 0x45
+#define REG_ADDIGGAIN6 0x46
+#define REG_DADIGGAIN1 0x47
+#define REG_DADIGGAIN2 0x48
+#define REG_DADIGGAIN3 0x49
+#define REG_DADIGGAIN4 0x4A
+#define REG_DADIGGAIN5 0x4B
+#define REG_DADIGGAIN6 0x4C
+#define REG_ADDIGLOOPGAIN1 0x4D
+#define REG_ADDIGLOOPGAIN2 0x4E
+#define REG_HSLEARDIGGAIN 0x4F
+#define REG_HSRDIGGAIN 0x50
+#define REG_SIDFIRGAIN1 0x51
+#define REG_SIDFIRGAIN2 0x52
+#define REG_ANCCONF1 0x53
+#define REG_ANCCONF2 0x54
+#define REG_ANCCONF3 0x55
+#define REG_ANCCONF4 0x56
+#define REG_ANCCONF5 0x57
+#define REG_ANCCONF6 0x58
+#define REG_ANCCONF7 0x59
+#define REG_ANCCONF8 0x5A
+#define REG_ANCCONF9 0x5B
+#define REG_ANCCONF10 0x5C
+#define REG_ANCCONF11 0x5D
+#define REG_ANCCONF12 0x5E
+#define REG_ANCCONF13 0x5F
+#define REG_ANCCONF14 0x60
+#define REG_SIDFIRADR 0x61
+#define REG_SIDFIRCOEF1 0x62
+#define REG_SIDFIRCOEF2 0x63
+#define REG_SIDFIRCONF 0x64
+#define REG_AUDINTMASK1 0x65
+#define REG_AUDINTSOURCE1 0x66
+#define REG_AUDINTMASK2 0x67
+#define REG_AUDINTSOURCE2 0x68
+#define REG_FIFOCONF1 0x69
+#define REG_FIFOCONF2 0x6A
+#define REG_FIFOCONF3 0x6B
+#define REG_FIFOCONF4 0x6C
+#define REG_FIFOCONF5 0x6D
+#define REG_FIFOCONF6 0x6E
+#define REG_AUDREV 0x6F
+
+#define AB8500_FIRST_REG REG_POWERUP
+#define AB8500_LAST_REG REG_AUDREV
+#define AB8500_CACHEREGNUM (AB8500_LAST_REG + 1)
+
+
+#define REG_MASK_ALL 0xFF
+#define REG_MASK_NONE 0x00
+
+/* REG_POWERUP */
+#define REG_POWERUP_POWERUP 7
+#define REG_POWERUP_ENANA 3
+
+/* REG_AUDSWRESET */
+#define REG_AUDSWRESET_SWRESET 7
+
+/* REG_ADPATHENA */
+#define REG_ADPATHENA_ENAD12 7
+#define REG_ADPATHENA_ENAD34 5
+#define REG_ADPATHENA_ENAD5768 3
+
+/* REG_DAPATHENA */
+#define REG_DAPATHENA_ENDA1 7
+#define REG_DAPATHENA_ENDA2 6
+#define REG_DAPATHENA_ENDA3 5
+#define REG_DAPATHENA_ENDA4 4
+#define REG_DAPATHENA_ENDA5 3
+#define REG_DAPATHENA_ENDA6 2
+
+/* REG_ANACONF1 */
+#define REG_ANACONF1_HSLOWPOW 7
+#define REG_ANACONF1_DACLOWPOW1 6
+#define REG_ANACONF1_DACLOWPOW0 5
+#define REG_ANACONF1_EARDACLOWPOW 4
+#define REG_ANACONF1_EARSELCM 2
+#define REG_ANACONF1_HSHPEN 1
+#define REG_ANACONF1_EARDRVLOWPOW 0
+
+/* REG_ANACONF2 */
+#define REG_ANACONF2_ENMIC1 7
+#define REG_ANACONF2_ENMIC2 6
+#define REG_ANACONF2_ENLINL 5
+#define REG_ANACONF2_ENLINR 4
+#define REG_ANACONF2_MUTMIC1 3
+#define REG_ANACONF2_MUTMIC2 2
+#define REG_ANACONF2_MUTLINL 1
+#define REG_ANACONF2_MUTLINR 0
+
+/* REG_DIGMICCONF */
+#define REG_DIGMICCONF_ENDMIC1 7
+#define REG_DIGMICCONF_ENDMIC2 6
+#define REG_DIGMICCONF_ENDMIC3 5
+#define REG_DIGMICCONF_ENDMIC4 4
+#define REG_DIGMICCONF_ENDMIC5 3
+#define REG_DIGMICCONF_ENDMIC6 2
+#define REG_DIGMICCONF_HSFADSPEED 0
+
+/* REG_ANACONF3 */
+#define REG_ANACONF3_MIC1SEL 7
+#define REG_ANACONF3_LINRSEL 6
+#define REG_ANACONF3_ENDRVHSL 5
+#define REG_ANACONF3_ENDRVHSR 4
+#define REG_ANACONF3_ENADCMIC 2
+#define REG_ANACONF3_ENADCLINL 1
+#define REG_ANACONF3_ENADCLINR 0
+
+/* REG_ANACONF4 */
+#define REG_ANACONF4_DISPDVSS 7
+#define REG_ANACONF4_ENEAR 6
+#define REG_ANACONF4_ENHSL 5
+#define REG_ANACONF4_ENHSR 4
+#define REG_ANACONF4_ENHFL 3
+#define REG_ANACONF4_ENHFR 2
+#define REG_ANACONF4_ENVIB1 1
+#define REG_ANACONF4_ENVIB2 0
+
+/* REG_DAPATHCONF */
+#define REG_DAPATHCONF_ENDACEAR 6
+#define REG_DAPATHCONF_ENDACHSL 5
+#define REG_DAPATHCONF_ENDACHSR 4
+#define REG_DAPATHCONF_ENDACHFL 3
+#define REG_DAPATHCONF_ENDACHFR 2
+#define REG_DAPATHCONF_ENDACVIB1 1
+#define REG_DAPATHCONF_ENDACVIB2 0
+
+/* REG_MUTECONF */
+#define REG_MUTECONF_MUTEAR 6
+#define REG_MUTECONF_MUTHSL 5
+#define REG_MUTECONF_MUTHSR 4
+#define REG_MUTECONF_MUTDACEAR 2
+#define REG_MUTECONF_MUTDACHSL 1
+#define REG_MUTECONF_MUTDACHSR 0
+
+/* REG_SHORTCIRCONF */
+#define REG_SHORTCIRCONF_ENSHORTPWD 7
+#define REG_SHORTCIRCONF_EARSHORTDIS 6
+#define REG_SHORTCIRCONF_HSSHORTDIS 5
+#define REG_SHORTCIRCONF_HSPULLDEN 4
+#define REG_SHORTCIRCONF_HSOSCEN 2
+#define REG_SHORTCIRCONF_HSFADDIS 1
+#define REG_SHORTCIRCONF_HSZCDDIS 0 /* Zero cross should be disabled */
+
+/* REG_ANACONF5 */
+#define REG_ANACONF5_ENCPHS 7
+#define REG_ANACONF5_HSLDACTOLOL 5
+#define REG_ANACONF5_HSRDACTOLOR 4
+#define REG_ANACONF5_ENLOL 3
+#define REG_ANACONF5_ENLOR 2
+#define REG_ANACONF5_HSAUTOEN 0
+
+/* REG_ENVCPCONF */
+#define REG_ENVCPCONF_ENVDETHTHRE 4
+#define REG_ENVCPCONF_ENVDETLTHRE 0
+#define REG_ENVCPCONF_ENVDETHTHRE_MAX 0x0F
+#define REG_ENVCPCONF_ENVDETLTHRE_MAX 0x0F
+
+/* REG_SIGENVCONF */
+#define REG_SIGENVCONF_CPLVEN 5
+#define REG_SIGENVCONF_ENVDETCPEN 4
+#define REG_SIGENVCONF_ENVDETTIME 0
+#define REG_SIGENVCONF_ENVDETTIME_MAX 0x0F
+
+/* REG_PWMGENCONF1 */
+#define REG_PWMGENCONF1_PWMTOVIB1 7
+#define REG_PWMGENCONF1_PWMTOVIB2 6
+#define REG_PWMGENCONF1_PWM1CTRL 5
+#define REG_PWMGENCONF1_PWM2CTRL 4
+#define REG_PWMGENCONF1_PWM1NCTRL 3
+#define REG_PWMGENCONF1_PWM1PCTRL 2
+#define REG_PWMGENCONF1_PWM2NCTRL 1
+#define REG_PWMGENCONF1_PWM2PCTRL 0
+
+/* REG_PWMGENCONF2 */
+/* REG_PWMGENCONF3 */
+/* REG_PWMGENCONF4 */
+/* REG_PWMGENCONF5 */
+#define REG_PWMGENCONFX_PWMVIBXPOL 7
+#define REG_PWMGENCONFX_PWMVIBXDUTCYC 0
+#define REG_PWMGENCONFX_PWMVIBXDUTCYC_MAX 0x64
+
+/* REG_ANAGAIN1 */
+/* REG_ANAGAIN2 */
+#define REG_ANAGAINX_ENSEMICX 7
+#define REG_ANAGAINX_LOWPOWMICX 6
+#define REG_ANAGAINX_MICXGAIN 0
+#define REG_ANAGAINX_MICXGAIN_MAX 0x1F
+
+/* REG_ANAGAIN3 */
+#define REG_ANAGAIN3_HSLGAIN 4
+#define REG_ANAGAIN3_HSRGAIN 0
+#define REG_ANAGAIN3_HSXGAIN_MAX 0x0F
+
+/* REG_ANAGAIN4 */
+#define REG_ANAGAIN4_LINLGAIN 4
+#define REG_ANAGAIN4_LINRGAIN 0
+#define REG_ANAGAIN4_LINXGAIN_MAX 0x0F
+
+/* REG_DIGLINHSLGAIN */
+/* REG_DIGLINHSRGAIN */
+#define REG_DIGLINHSXGAIN_LINTOHSXGAIN 0
+#define REG_DIGLINHSXGAIN_LINTOHSXGAIN_MAX 0x13
+
+/* REG_ADFILTCONF */
+#define REG_ADFILTCONF_AD1NH 7
+#define REG_ADFILTCONF_AD2NH 6
+#define REG_ADFILTCONF_AD3NH 5
+#define REG_ADFILTCONF_AD4NH 4
+#define REG_ADFILTCONF_AD1VOICE 3
+#define REG_ADFILTCONF_AD2VOICE 2
+#define REG_ADFILTCONF_AD3VOICE 1
+#define REG_ADFILTCONF_AD4VOICE 0
+
+/* REG_DIGIFCONF1 */
+#define REG_DIGIFCONF1_ENMASTGEN 7
+#define REG_DIGIFCONF1_IF1BITCLKOS1 6
+#define REG_DIGIFCONF1_IF1BITCLKOS0 5
+#define REG_DIGIFCONF1_ENFSBITCLK1 4
+#define REG_DIGIFCONF1_IF0BITCLKOS1 2
+#define REG_DIGIFCONF1_IF0BITCLKOS0 1
+#define REG_DIGIFCONF1_ENFSBITCLK0 0
+
+/* REG_DIGIFCONF2 */
+#define REG_DIGIFCONF2_FSYNC0P 6
+#define REG_DIGIFCONF2_BITCLK0P 5
+#define REG_DIGIFCONF2_IF0DEL 4
+#define REG_DIGIFCONF2_IF0FORMAT1 3
+#define REG_DIGIFCONF2_IF0FORMAT0 2
+#define REG_DIGIFCONF2_IF0WL1 1
+#define REG_DIGIFCONF2_IF0WL0 0
+
+/* REG_DIGIFCONF3 */
+#define REG_DIGIFCONF3_IF0DATOIF1AD 7
+#define REG_DIGIFCONF3_IF0CLKTOIF1CLK 6
+#define REG_DIGIFCONF3_IF1MASTER 5
+#define REG_DIGIFCONF3_IF1DATOIF0AD 3
+#define REG_DIGIFCONF3_IF1CLKTOIF0CLK 2
+#define REG_DIGIFCONF3_IF0MASTER 1
+#define REG_DIGIFCONF3_IF0BFIFOEN 0
+
+/* REG_DIGIFCONF4 */
+#define REG_DIGIFCONF4_FSYNC1P 6
+#define REG_DIGIFCONF4_BITCLK1P 5
+#define REG_DIGIFCONF4_IF1DEL 4
+#define REG_DIGIFCONF4_IF1FORMAT1 3
+#define REG_DIGIFCONF4_IF1FORMAT0 2
+#define REG_DIGIFCONF4_IF1WL1 1
+#define REG_DIGIFCONF4_IF1WL0 0
+
+/* REG_ADSLOTSELX */
+#define REG_ADSLOTSELX_AD_OUT1_TO_SLOT_ODD 0x00
+#define REG_ADSLOTSELX_AD_OUT2_TO_SLOT_ODD 0x01
+#define REG_ADSLOTSELX_AD_OUT3_TO_SLOT_ODD 0x02
+#define REG_ADSLOTSELX_AD_OUT4_TO_SLOT_ODD 0x03
+#define REG_ADSLOTSELX_AD_OUT5_TO_SLOT_ODD 0x04
+#define REG_ADSLOTSELX_AD_OUT6_TO_SLOT_ODD 0x05
+#define REG_ADSLOTSELX_AD_OUT7_TO_SLOT_ODD 0x06
+#define REG_ADSLOTSELX_AD_OUT8_TO_SLOT_ODD 0x07
+#define REG_ADSLOTSELX_ZEROES_TO_SLOT_ODD 0x08
+#define REG_ADSLOTSELX_TRISTATE_TO_SLOT_ODD 0x0F
+#define REG_ADSLOTSELX_AD_OUT1_TO_SLOT_EVEN 0x00
+#define REG_ADSLOTSELX_AD_OUT2_TO_SLOT_EVEN 0x10
+#define REG_ADSLOTSELX_AD_OUT3_TO_SLOT_EVEN 0x20
+#define REG_ADSLOTSELX_AD_OUT4_TO_SLOT_EVEN 0x30
+#define REG_ADSLOTSELX_AD_OUT5_TO_SLOT_EVEN 0x40
+#define REG_ADSLOTSELX_AD_OUT6_TO_SLOT_EVEN 0x50
+#define REG_ADSLOTSELX_AD_OUT7_TO_SLOT_EVEN 0x60
+#define REG_ADSLOTSELX_AD_OUT8_TO_SLOT_EVEN 0x70
+#define REG_ADSLOTSELX_ZEROES_TO_SLOT_EVEN 0x80
+#define REG_ADSLOTSELX_TRISTATE_TO_SLOT_EVEN 0xF0
+#define REG_ADSLOTSELX_EVEN_SHIFT 0
+#define REG_ADSLOTSELX_ODD_SHIFT 4
+
+/* REG_ADSLOTHIZCTRL1 */
+/* REG_ADSLOTHIZCTRL2 */
+/* REG_ADSLOTHIZCTRL3 */
+/* REG_ADSLOTHIZCTRL4 */
+/* REG_DASLOTCONF1 */
+#define REG_DASLOTCONF1_DA12VOICE 7
+#define REG_DASLOTCONF1_SWAPDA12_34 6
+#define REG_DASLOTCONF1_DAI7TOADO1 5
+
+/* REG_DASLOTCONF2 */
+#define REG_DASLOTCONF2_DAI8TOADO2 5
+
+/* REG_DASLOTCONF3 */
+#define REG_DASLOTCONF3_DA34VOICE 7
+#define REG_DASLOTCONF3_DAI7TOADO3 5
+
+/* REG_DASLOTCONF4 */
+#define REG_DASLOTCONF4_DAI8TOADO4 5
+
+/* REG_DASLOTCONF5 */
+#define REG_DASLOTCONF5_DA56VOICE 7
+#define REG_DASLOTCONF5_DAI7TOADO5 5
+
+/* REG_DASLOTCONF6 */
+#define REG_DASLOTCONF6_DAI8TOADO6 5
+
+/* REG_DASLOTCONF7 */
+#define REG_DASLOTCONF7_DAI8TOADO7 5
+
+/* REG_DASLOTCONF8 */
+#define REG_DASLOTCONF8_DAI7TOADO8 5
+
+#define REG_DASLOTCONFX_SLTODAX_SHIFT 0
+#define REG_DASLOTCONFX_SLTODAX_MASK 0x1F
+
+/* REG_CLASSDCONF1 */
+#define REG_CLASSDCONF1_PARLHF 7
+#define REG_CLASSDCONF1_PARLVIB 6
+#define REG_CLASSDCONF1_VIB1SWAPEN 3
+#define REG_CLASSDCONF1_VIB2SWAPEN 2
+#define REG_CLASSDCONF1_HFLSWAPEN 1
+#define REG_CLASSDCONF1_HFRSWAPEN 0
+
+/* REG_CLASSDCONF2 */
+#define REG_CLASSDCONF2_FIRBYP3 7
+#define REG_CLASSDCONF2_FIRBYP2 6
+#define REG_CLASSDCONF2_FIRBYP1 5
+#define REG_CLASSDCONF2_FIRBYP0 4
+#define REG_CLASSDCONF2_HIGHVOLEN3 3
+#define REG_CLASSDCONF2_HIGHVOLEN2 2
+#define REG_CLASSDCONF2_HIGHVOLEN1 1
+#define REG_CLASSDCONF2_HIGHVOLEN0 0
+
+/* REG_CLASSDCONF3 */
+#define REG_CLASSDCONF3_DITHHPGAIN 4
+#define REG_CLASSDCONF3_DITHHPGAIN_MAX 0x0A
+#define REG_CLASSDCONF3_DITHWGAIN 0
+#define REG_CLASSDCONF3_DITHWGAIN_MAX 0x0A
+
+/* REG_DMICFILTCONF */
+#define REG_DMICFILTCONF_ANCINSEL 7
+#define REG_DMICFILTCONF_DA3TOEAR 6
+#define REG_DMICFILTCONF_DMIC1SINC3 5
+#define REG_DMICFILTCONF_DMIC2SINC3 4
+#define REG_DMICFILTCONF_DMIC3SINC3 3
+#define REG_DMICFILTCONF_DMIC4SINC3 2
+#define REG_DMICFILTCONF_DMIC5SINC3 1
+#define REG_DMICFILTCONF_DMIC6SINC3 0
+
+/* REG_DIGMULTCONF1 */
+#define REG_DIGMULTCONF1_DATOHSLEN 7
+#define REG_DIGMULTCONF1_DATOHSREN 6
+#define REG_DIGMULTCONF1_AD1SEL 5
+#define REG_DIGMULTCONF1_AD2SEL 4
+#define REG_DIGMULTCONF1_AD3SEL 3
+#define REG_DIGMULTCONF1_AD5SEL 2
+#define REG_DIGMULTCONF1_AD6SEL 1
+#define REG_DIGMULTCONF1_ANCSEL 0
+
+/* REG_DIGMULTCONF2 */
+#define REG_DIGMULTCONF2_DATOHFREN 7
+#define REG_DIGMULTCONF2_DATOHFLEN 6
+#define REG_DIGMULTCONF2_HFRSEL 5
+#define REG_DIGMULTCONF2_HFLSEL 4
+#define REG_DIGMULTCONF2_FIRSID1SEL 2
+#define REG_DIGMULTCONF2_FIRSID2SEL 0
+
+/* REG_ADDIGGAIN1 */
+/* REG_ADDIGGAIN2 */
+/* REG_ADDIGGAIN3 */
+/* REG_ADDIGGAIN4 */
+/* REG_ADDIGGAIN5 */
+/* REG_ADDIGGAIN6 */
+#define REG_ADDIGGAINX_FADEDISADX 6
+#define REG_ADDIGGAINX_ADXGAIN_MAX 0x3F
+
+/* REG_DADIGGAIN1 */
+/* REG_DADIGGAIN2 */
+/* REG_DADIGGAIN3 */
+/* REG_DADIGGAIN4 */
+/* REG_DADIGGAIN5 */
+/* REG_DADIGGAIN6 */
+#define REG_DADIGGAINX_FADEDISDAX 6
+#define REG_DADIGGAINX_DAXGAIN_MAX 0x3F
+
+/* REG_ADDIGLOOPGAIN1 */
+/* REG_ADDIGLOOPGAIN2 */
+#define REG_ADDIGLOOPGAINX_FADEDISADXL 6
+#define REG_ADDIGLOOPGAINX_ADXLBGAIN_MAX 0x3F
+
+/* REG_HSLEARDIGGAIN */
+#define REG_HSLEARDIGGAIN_HSSINC1 7
+#define REG_HSLEARDIGGAIN_FADEDISHSL 4
+#define REG_HSLEARDIGGAIN_HSLDGAIN_MAX 0x09
+
+/* REG_HSRDIGGAIN */
+#define REG_HSRDIGGAIN_FADESPEED 6
+#define REG_HSRDIGGAIN_FADEDISHSR 4
+#define REG_HSRDIGGAIN_HSRDGAIN_MAX 0x09
+
+/* REG_SIDFIRGAIN1 */
+/* REG_SIDFIRGAIN2 */
+#define REG_SIDFIRGAINX_FIRSIDXGAIN_MAX 0x1F
+
+/* REG_ANCCONF1 */
+#define REG_ANCCONF1_ANCIIRUPDATE 3
+#define REG_ANCCONF1_ENANC 2
+#define REG_ANCCONF1_ANCIIRINIT 1
+#define REG_ANCCONF1_ANCFIRUPDATE 0
+
+/* REG_ANCCONF2 */
+#define REG_ANCCONF2_VALUE_MIN -0x10
+#define REG_ANCCONF2_VALUE_MAX 0x0F
+/* REG_ANCCONF3 */
+#define REG_ANCCONF3_VALUE_MIN -0x10
+#define REG_ANCCONF3_VALUE_MAX 0x0F
+/* REG_ANCCONF4 */
+#define REG_ANCCONF4_VALUE_MIN -0x10
+#define REG_ANCCONF4_VALUE_MAX 0x0F
+/* REG_ANC_FIR_COEFFS */
+#define REG_ANC_FIR_COEFF_MIN -0x8000
+#define REG_ANC_FIR_COEFF_MAX 0x7FFF
+#define REG_ANC_FIR_COEFFS 0xF
+/* REG_ANC_IIR_COEFFS */
+#define REG_ANC_IIR_COEFF_MIN -0x800000
+#define REG_ANC_IIR_COEFF_MAX 0x7FFFFF
+#define REG_ANC_IIR_COEFFS 0x18
+/* REG_ANC_WARP_DELAY */
+#define REG_ANC_WARP_DELAY_MIN 0x0000
+#define REG_ANC_WARP_DELAY_MAX 0xFFFF
+/* REG_ANCCONF11 */
+/* REG_ANCCONF12 */
+/* REG_ANCCONF13 */
+/* REG_ANCCONF14 */
+
+/* REG_SIDFIRADR */
+#define REG_SIDFIRADR_FIRSIDSET 7
+#define REG_SIDFIRADR_ADDRESS_SHIFT 0
+#define REG_SIDFIRADR_ADDRESS_MAX 0x7F
+
+/* REG_SIDFIRCOEF1 */
+/* REG_SIDFIRCOEF2 */
+#define REG_SID_FIR_COEFF_MIN 0
+#define REG_SID_FIR_COEFF_MAX 0xFFFF
+#define REG_SID_FIR_COEFFS 128
+
+/* REG_SIDFIRCONF */
+#define REG_SIDFIRCONF_ENFIRSIDS 2
+#define REG_SIDFIRCONF_FIRSIDSTOIF1 1
+#define REG_SIDFIRCONF_FIRSIDBUSY 0
+
+/* REG_AUDINTMASK1 */
+/* REG_AUDINTSOURCE1 */
+/* REG_AUDINTMASK2 */
+/* REG_AUDINTSOURCE2 */
+
+/* REG_FIFOCONF1 */
+#define REG_FIFOCONF1_BFIFOMASK 0x80
+#define REG_FIFOCONF1_BFIFO19M2 0x40
+#define REG_FIFOCONF1_BFIFOINT_SHIFT 0
+#define REG_FIFOCONF1_BFIFOINT_MAX 0x3F
+
+/* REG_FIFOCONF2 */
+#define REG_FIFOCONF2_BFIFOTX_SHIFT 0
+#define REG_FIFOCONF2_BFIFOTX_MAX 0xFF
+
+/* REG_FIFOCONF3 */
+#define REG_FIFOCONF3_BFIFOEXSL_SHIFT 5
+#define REG_FIFOCONF3_BFIFOEXSL_MAX 0x5
+#define REG_FIFOCONF3_PREBITCLK0_SHIFT 2
+#define REG_FIFOCONF3_PREBITCLK0_MAX 0x7
+#define REG_FIFOCONF3_BFIFOMAST_SHIFT 1
+#define REG_FIFOCONF3_BFIFORUN_SHIFT 0
+
+/* REG_FIFOCONF4 */
+#define REG_FIFOCONF4_BFIFOFRAMSW_SHIFT 0
+#define REG_FIFOCONF4_BFIFOFRAMSW_MAX 0xFF
+
+/* REG_FIFOCONF5 */
+#define REG_FIFOCONF5_BFIFOWAKEUP_SHIFT 0
+#define REG_FIFOCONF5_BFIFOWAKEUP_MAX 0xFF
+
+/* REG_FIFOCONF6 */
+#define REG_FIFOCONF6_BFIFOSAMPLE_SHIFT 0
+#define REG_FIFOCONF6_BFIFOSAMPLE_MAX 0xFF
+
+/* REG_AUDREV */
+
+#endif
diff --git a/sound/soc/codecs/av8100_audio.c b/sound/soc/codecs/av8100_audio.c
new file mode 100644
index 00000000000..1f706a5f0f6
--- /dev/null
+++ b/sound/soc/codecs/av8100_audio.c
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <video/av8100.h>
+#include <video/hdmi.h>
+
+#include "av8100_audio.h"
+
+/* codec private data */
+struct av8100_codec_dai_data {
+ struct hdmi_audio_settings as;
+};
+
+static struct av8100_codec_dai_data *get_dai_data_codec(struct snd_soc_codec *codec,
+ int dai_id)
+{
+ struct av8100_codec_dai_data *dai_data = snd_soc_codec_get_drvdata(codec);
+ return &dai_data[dai_id];
+}
+
+static struct av8100_codec_dai_data *get_dai_data(struct snd_soc_dai *codec_dai)
+{
+ return get_dai_data_codec(codec_dai->codec, codec_dai->id);
+}
+
+/* Controls - Non-DAPM Non-ASoC */
+
+/* Coding Type */
+
+static const char *hdmi_coding_type_str[] = {"AV8100_CODEC_CT_REFER",
+ "AV8100_CODEC_CT_IEC60958_PCM",
+ "AV8100_CODEC_CT_AC3",
+ "AV8100_CODEC_CT_MPEG1",
+ "AV8100_CODEC_CT_MP3",
+ "AV8100_CODEC_CT_MPEG2",
+ "AV8100_CODEC_CT_AAC",
+ "AV8100_CODEC_CT_DTS",
+ "AV8100_CODEC_CT_ATRAC",
+ "AV8100_CODEC_CT_ONE_BIT_AUDIO",
+ "AV8100_CODEC_CT_DOLBY_DIGITAL",
+ "AV8100_CODEC_CT_DTS_HD",
+ "AV8100_CODEC_CT_MAT",
+ "AV8100_CODEC_CT_DST",
+ "AV8100_CODEC_CT_WMA_PRO"};
+
+enum hdmi_audio_coding_type audio_coding_type;
+
+static int hdmi_coding_type_control_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int items = ARRAY_SIZE(hdmi_coding_type_str);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = items;
+
+ if (uinfo->value.enumerated.item > items - 1)
+ uinfo->value.enumerated.item = items - 1;
+
+ strcpy(uinfo->value.enumerated.name,
+ hdmi_coding_type_str[uinfo->value.enumerated.item]);
+
+ return 0;
+}
+
+static int hdmi_coding_type_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = audio_coding_type;
+
+ return 0;
+}
+
+static int hdmi_coding_type_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int items = ARRAY_SIZE(hdmi_coding_type_str);
+
+ if (ucontrol->value.enumerated.item[0] > items - 1)
+ ucontrol->value.enumerated.item[0] = items - 1;
+
+ audio_coding_type = ucontrol->value.enumerated.item[0];
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new hdmi_coding_type_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Coding Type",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = hdmi_coding_type_control_info,
+ .get = hdmi_coding_type_control_get,
+ .put = hdmi_coding_type_control_put,
+};
+
+/* Extended interface for codec-driver */
+
+int av8100_audio_change_hdmi_audio_settings(struct snd_soc_dai *codec_dai,
+ struct hdmi_audio_settings *as)
+{
+ struct av8100_codec_dai_data *dai_data = get_dai_data(codec_dai);
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ dai_data->as.audio_channel_count = as->audio_channel_count;
+ dai_data->as.sampling_frequency = as->sampling_frequency;
+ dai_data->as.sample_size = as->sample_size;
+ dai_data->as.channel_allocation = as->channel_allocation;
+ dai_data->as.level_shift_value = as->level_shift_value;
+ dai_data->as.downmix_inhibit = as->downmix_inhibit;
+
+ return 0;
+}
+
+static int av8100_codec_powerup(void)
+{
+ struct av8100_status status;
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ status = av8100_status_get();
+ if (status.av8100_state < AV8100_OPMODE_STANDBY) {
+ pr_debug("%s: Powering up AV8100.", __func__);
+ ret = av8100_powerup();
+ if (ret != 0) {
+ pr_err("%s: Power up AV8100 failed "
+ "(av8100_powerup returned %d)!\n",
+ __func__,
+ ret);
+ return -EINVAL;
+ }
+ }
+ if (status.av8100_state < AV8100_OPMODE_INIT) {
+ ret = av8100_download_firmware(I2C_INTERFACE);
+ if (ret != 0) {
+ pr_err("%s: Download firmware failed "
+ "(av8100_download_firmware returned %d)!\n",
+ __func__,
+ ret);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int av8100_codec_setup_hdmi_format(void)
+{
+ union av8100_configuration config;
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ pr_debug("%s: hdmi_mode = AV8100_HDMI_ON.", __func__);
+ pr_debug("%s: hdmi_format = AV8100_HDMI.", __func__);
+ config.hdmi_format.hdmi_mode = AV8100_HDMI_ON;
+ config.hdmi_format.hdmi_format = AV8100_HDMI;
+ ret = av8100_conf_prep(AV8100_COMMAND_HDMI, &config);
+ if (ret != 0) {
+ pr_err("%s: Setting hdmi_format failed "
+ "(av8100_conf_prep returned %d)!\n",
+ __func__,
+ ret);
+ return -EINVAL;
+ }
+ ret = av8100_conf_w(AV8100_COMMAND_HDMI,
+ NULL,
+ NULL,
+ I2C_INTERFACE);
+ if (ret != 0) {
+ pr_err("%s: Setting hdmi_format failed "
+ "(av8100_conf_w returned %d)!\n",
+ __func__,
+ ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int av8100_codec_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ return 0;
+}
+
+static int av8100_codec_send_audio_infoframe(struct hdmi_audio_settings *as)
+{
+ union av8100_configuration config;
+ struct av8100_infoframes_format_cmd info_fr;
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ pr_debug("%s: HDMI-settings:\n", __func__);
+ pr_debug("%s: audio_coding_type = %d\n", __func__, audio_coding_type);
+ pr_debug("%s: audio_channel_count = %d\n", __func__, as->audio_channel_count);
+ pr_debug("%s: sampling_frequency = %d\n", __func__, as->sampling_frequency);
+ pr_debug("%s: sample_size = %d\n", __func__, as->sample_size);
+ pr_debug("%s: channel_allocation = %d\n", __func__, as->channel_allocation);
+ pr_debug("%s: level_shift_value = %d\n", __func__, as->level_shift_value);
+ pr_debug("%s: downmix_inhibit = %d\n", __func__, as->downmix_inhibit);
+
+ /* Prepare the infoframe from the hdmi_audio_settings struct */
+ pr_info("%s: Preparing audio info-frame.", __func__);
+ info_fr.type = 0x84;
+ info_fr.version = 0x01;
+ info_fr.length = 0x0a;
+ info_fr.data[0] = (audio_coding_type << 4) | as->audio_channel_count;
+ info_fr.data[1] = (as->sampling_frequency << 2) | as->sample_size;
+ info_fr.data[2] = 0;
+ info_fr.data[3] = as->channel_allocation;
+ info_fr.data[4] = ((int)as->downmix_inhibit << 7) |
+ (as->level_shift_value << 3);
+ info_fr.data[5] = 0;
+ info_fr.data[6] = 0;
+ info_fr.data[7] = 0;
+ info_fr.data[8] = 0;
+ info_fr.data[9] = 0;
+ info_fr.crc = 0x100 - (info_fr.type +
+ info_fr.version +
+ info_fr.length +
+ info_fr.data[0] +
+ info_fr.data[1] +
+ info_fr.data[3] +
+ info_fr.data[4]);
+ config.infoframes_format.type = info_fr.type;
+ config.infoframes_format.version = info_fr.version;
+ config.infoframes_format.crc = info_fr.crc;
+ config.infoframes_format.length = info_fr.length;
+ memcpy(&config.infoframes_format.data, info_fr.data, info_fr.length);
+
+ /* Send audio info-frame */
+ pr_info("%s: Sending audio info-frame.", __func__);
+ ret = av8100_conf_prep(AV8100_COMMAND_INFOFRAMES, &config);
+ if (ret != 0) {
+ pr_err("%s: Sending audio info-frame failed "
+ "(av8100_conf_prep returned %d)!\n",
+ __func__,
+ ret);
+ return -EINVAL;
+ }
+ ret = av8100_conf_w(AV8100_COMMAND_INFOFRAMES,
+ NULL,
+ NULL,
+ I2C_INTERFACE);
+ if (ret != 0) {
+ pr_err("%s: Sending audio info-frame failed "
+ "(av8100_conf_w returned %d)!\n",
+ __func__,
+ ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int av8100_codec_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params,
+ struct snd_soc_dai *codec_dai)
+{
+ struct av8100_codec_dai_data *dai_data = get_dai_data(codec_dai);
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ av8100_codec_send_audio_infoframe(&dai_data->as);
+
+ return 0;
+}
+
+static int av8100_codec_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *codec_dai)
+{
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ /* Startup AV8100 if it is not already started */
+ ret = av8100_codec_powerup();
+ if (ret != 0) {
+ pr_err("%s: Startup of AV8100 failed "
+ "(av8100_codec_powerupAV8100 returned %d)!\n",
+ __func__,
+ ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void av8100_codec_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *codec_dai)
+{
+ pr_debug("%s: Enter.\n", __func__);
+}
+
+static int av8100_codec_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id,
+ unsigned int freq, int dir)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ return 0;
+}
+
+static int av8100_codec_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ union av8100_configuration config;
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ /* Set the HDMI format of AV8100 */
+ ret = av8100_codec_setup_hdmi_format();
+ if (ret != 0)
+ return ret;
+
+ /* Set the audio input format of AV8100 */
+ config.audio_input_format.audio_input_if_format =
+ ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) ?
+ AV8100_AUDIO_TDM_MODE : AV8100_AUDIO_I2SDELAYED_MODE;
+ config.audio_input_format.audio_if_mode =
+ ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) ?
+ AV8100_AUDIO_MASTER : AV8100_AUDIO_SLAVE;
+ pr_info("%s: Setting audio_input_format "
+ "(if_format = %d, if_mode = %d).",
+ __func__,
+ config.audio_input_format.audio_input_if_format,
+ config.audio_input_format.audio_if_mode);
+ config.audio_input_format.i2s_input_nb = 1;
+ config.audio_input_format.sample_audio_freq = AV8100_AUDIO_FREQ_48KHZ;
+ config.audio_input_format.audio_word_lg = AV8100_AUDIO_16BITS;
+ config.audio_input_format.audio_format = AV8100_AUDIO_LPCM_MODE;
+ config.audio_input_format.audio_mute = AV8100_AUDIO_MUTE_DISABLE;
+ ret = av8100_conf_prep(AV8100_COMMAND_AUDIO_INPUT_FORMAT, &config);
+ if (ret != 0) {
+ pr_err("%s: Setting audio_input_format failed "
+ "(av8100_conf_prep returned %d)!\n",
+ __func__,
+ ret);
+ return -EINVAL;
+ }
+ ret = av8100_conf_w(AV8100_COMMAND_AUDIO_INPUT_FORMAT,
+ NULL,
+ NULL,
+ I2C_INTERFACE);
+ if (ret != 0) {
+ pr_err("%s: Setting audio_input_format failed "
+ "(av8100_conf_w returned %d)!\n",
+ __func__,
+ ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct snd_soc_dai_driver av8100_dai_driver = {
+ .name = "av8100-codec-dai",
+ .playback = {
+ .stream_name = "AV8100 Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = AV8100_SUPPORTED_RATE,
+ .formats = AV8100_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .prepare = av8100_codec_pcm_prepare,
+ .hw_params = av8100_codec_pcm_hw_params,
+ .startup = av8100_codec_pcm_startup,
+ .shutdown = av8100_codec_pcm_shutdown,
+ .set_sysclk = av8100_codec_set_dai_sysclk,
+ .set_fmt = av8100_codec_set_dai_fmt,
+ }
+ },
+};
+EXPORT_SYMBOL_GPL(av8100_dai_driver);
+
+static int av8100_codec_probe(struct snd_soc_codec *codec)
+{
+ pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name);
+
+ audio_coding_type = AV8100_CODEC_CT_REFER;
+
+ /* Add controls with events */
+ snd_ctl_add(codec->card->snd_card, snd_ctl_new1(&hdmi_coding_type_control, codec));
+
+ return 0;
+}
+
+static int av8100_codec_remove(struct snd_soc_codec *codec)
+{
+ pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name);
+
+ return 0;
+}
+
+static int av8100_codec_suspend(struct snd_soc_codec *codec)
+{
+ pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name);
+
+ return 0;
+}
+
+static int av8100_codec_resume(struct snd_soc_codec *codec)
+{
+ pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name);
+
+ return 0;
+}
+
+struct snd_soc_codec_driver av8100_codec_drv = {
+ .probe = av8100_codec_probe,
+ .remove = av8100_codec_remove,
+ .suspend = av8100_codec_suspend,
+ .resume = av8100_codec_resume
+};
+
+static __devinit int av8100_codec_drv_probe(struct platform_device *pdev)
+{
+ struct av8100_codec_dai_data *dai_data;
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ pr_info("%s: Init codec private data..\n", __func__);
+ dai_data = kzalloc(sizeof(struct av8100_codec_dai_data), GFP_KERNEL);
+ if (dai_data == NULL)
+ return -ENOMEM;
+
+ /* Setup hdmi_audio_settings default values */
+ dai_data[0].as.audio_channel_count = AV8100_CODEC_CC_2CH;
+ dai_data[0].as.sampling_frequency = AV8100_CODEC_SF_48KHZ;
+ dai_data[0].as.sample_size = AV8100_CODEC_SS_16BIT;
+ dai_data[0].as.channel_allocation = AV8100_CODEC_CA_FL_FR;
+ dai_data[0].as.level_shift_value = AV8100_CODEC_LSV_0DB;
+ dai_data[0].as.downmix_inhibit = false;
+
+ platform_set_drvdata(pdev, dai_data);
+
+ pr_info("%s: Register codec.\n", __func__);
+ ret = snd_soc_register_codec(&pdev->dev, &av8100_codec_drv, &av8100_dai_driver, 1);
+ if (ret < 0) {
+ pr_debug("%s: Error: Failed to register codec (ret = %d).\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit av8100_codec_drv_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ kfree(platform_get_drvdata(pdev));
+ return 0;
+}
+
+static const struct platform_device_id av8100_codec_platform_id[] = {
+ { "av8100-codec", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, av8100_codec_platform_id);
+
+static struct platform_driver av8100_codec_platform_driver = {
+ .driver = {
+ .name = "av8100-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = av8100_codec_drv_probe,
+ .remove = __devexit_p(av8100_codec_drv_remove),
+ .id_table = av8100_codec_platform_id,
+};
+
+static int __devinit av8100_codec_platform_drv_init(void)
+{
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ ret = platform_driver_register(&av8100_codec_platform_driver);
+ if (ret != 0) {
+ pr_err("Failed to register AV8100 platform driver (%d)!\n", ret);
+ }
+
+ return ret;
+}
+
+static void __exit av8100_codec_platform_drv_exit(void)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ platform_driver_unregister(&av8100_codec_platform_driver);
+}
+
+module_init(av8100_codec_platform_drv_init);
+module_exit(av8100_codec_platform_drv_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/av8100_audio.h b/sound/soc/codecs/av8100_audio.h
new file mode 100644
index 00000000000..4802e0d0242
--- /dev/null
+++ b/sound/soc/codecs/av8100_audio.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>,
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+#ifndef AV8100_AUDIO_CODEC_H
+#define AV8100_AUDIO_CODEC_H
+
+/* Supported sampling rates */
+#define AV8100_SUPPORTED_RATE (SNDRV_PCM_RATE_48000)
+
+/* Supported data formats */
+#define AV8100_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE)
+
+/* TDM-slot mask */
+#define AV8100_CODEC_MASK_MONO 0x0001
+#define AV8100_CODEC_MASK_STEREO 0x0005
+#define AV8100_CODEC_MASK_2DOT1 0x0015
+#define AV8100_CODEC_MASK_QUAD 0x0505
+#define AV8100_CODEC_MASK_5DOT0 0x0545
+#define AV8100_CODEC_MASK_5DOT1 0x0555
+#define AV8100_CODEC_MASK_7DOT0 0x5545
+#define AV8100_CODEC_MASK_7DOT1 0x5555
+
+enum hdmi_audio_coding_type {
+ AV8100_CODEC_CT_REFER,
+ AV8100_CODEC_CT_IEC60958_PCM,
+ AV8100_CODEC_CT_AC3,
+ AV8100_CODEC_CT_MPEG1,
+ AV8100_CODEC_CT_MP3,
+ AV8100_CODEC_CT_MPEG2,
+ AV8100_CODEC_CT_AAC,
+ AV8100_CODEC_CT_DTS,
+ AV8100_CODEC_CT_ATRAC,
+ AV8100_CODEC_CT_ONE_BIT_AUDIO,
+ AV8100_CODEC_CT_DOLBY_DIGITAL,
+ AV8100_CODEC_CT_DTS_HD,
+ AV8100_CODEC_CT_MAT,
+ AV8100_CODEC_CT_DST,
+ AV8100_CODEC_CT_WMA_PRO
+};
+
+enum hdmi_audio_channel_count {
+ AV8100_CODEC_CC_REFER,
+ AV8100_CODEC_CC_2CH,
+ AV8100_CODEC_CC_3CH,
+ AV8100_CODEC_CC_4CH,
+ AV8100_CODEC_CC_5CH,
+ AV8100_CODEC_CC_6CH,
+ AV8100_CODEC_CC_7CH,
+ AV8100_CODEC_CC_8CH
+};
+
+enum hdmi_sampling_frequency {
+ AV8100_CODEC_SF_REFER,
+ AV8100_CODEC_SF_32KHZ,
+ AV8100_CODEC_SF_44_1KHZ,
+ AV8100_CODEC_SF_48KHZ,
+ AV8100_CODEC_SF_88_2KHZ,
+ AV8100_CODEC_SF_96KHZ,
+ AV8100_CODEC_SF_176_4KHZ,
+ AV8100_CODEC_SF_192KHZ
+};
+
+enum hdmi_sample_size {
+ AV8100_CODEC_SS_REFER,
+ AV8100_CODEC_SS_16BIT,
+ AV8100_CODEC_SS_20BIT,
+ AV8100_CODEC_SS_24BIT
+};
+
+enum hdmi_speaker_placement {
+ AV8100_CODEC_SP_FL, /* Front Left */
+ AV8100_CODEC_SP_FC, /* Front Center */
+ AV8100_CODEC_SP_FR, /* Front Right */
+ AV8100_CODEC_SP_FLC, /* Front Left Center */
+ AV8100_CODEC_SP_FRC, /* Front Right Center */
+ AV8100_CODEC_SP_RL, /* Rear Left */
+ AV8100_CODEC_SP_RC, /* Rear Center */
+ AV8100_CODEC_SP_RR, /* Rear Right */
+ AV8100_CODEC_SP_RLC, /* Rear Left Center */
+ AV8100_CODEC_SP_RRC, /* Rear Right Center */
+ AV8100_CODEC_SP_LFE, /* Low Frequency Effekt */
+};
+
+enum hdmi_channel_allocation {
+ AV8100_CODEC_CA_FL_FR, /* 0x00, Stereo */
+ AV8100_CODEC_CA_FL_FR_LFE, /* 0x01, 2.1 */
+ AV8100_CODEC_CA_FL_FR_FC, /* 0x02*/
+ AV8100_CODEC_CA_FL_FR_LFE_FC, /* 0x03*/
+ AV8100_CODEC_CA_FL_FR_RC, /* 0x04*/
+ AV8100_CODEC_CA_FL_FR_LFE_RC, /* 0x05*/
+ AV8100_CODEC_CA_FL_FR_FC_RC, /* 0x06*/
+ AV8100_CODEC_CA_FL_FR_LFE_FC_RC, /* 0x07*/
+ AV8100_CODEC_CA_FL_FR_RL_RR, /* 0x08, Quad */
+ AV8100_CODEC_CA_FL_FR_LFE_RL_RR, /* 0x09*/
+ AV8100_CODEC_CA_FL_FR_FC_RL_RR, /* 0x0a, 5.0*/
+ AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR, /* 0x0b, 5.1*/
+ AV8100_CODEC_CA_FL_FR_RL_RR_RC, /* 0x0c*/
+ AV8100_CODEC_CA_FL_FR_LFE_RL_RR_RC, /* 0x0d*/
+ AV8100_CODEC_CA_FL_FR_RC_RL_RR_RC, /* 0x0e*/
+ AV8100_CODEC_CA_FL_FR_LFE_RC_RL_RR_RC, /* 0x0f*/
+ AV8100_CODEC_CA_FL_FR_RL_RR_RLC_RRC, /* 0x10*/
+ AV8100_CODEC_CA_FL_FR_LFE_RL_RR_RLC_RRC, /* 0x11*/
+ AV8100_CODEC_CA_FL_FR_FC_RL_RR_RLC_RRC, /* 0x12*/
+ AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR_RLC_RRC, /* 0x13*/
+ AV8100_CODEC_CA_FL_FR_FLC_FRC, /* 0x14*/
+ AV8100_CODEC_CA_FL_FR_LFE_FLC_FRC, /* 0x15*/
+ AV8100_CODEC_CA_FL_FR_FC_FLC_FRC, /* 0x16*/
+ AV8100_CODEC_CA_FL_FR_LFE_FC_FLC_FRC, /* 0x17*/
+ AV8100_CODEC_CA_FL_FR_RC_FLC_FRC, /* 0x18*/
+ AV8100_CODEC_CA_FL_FR_LFE_RC_FLC_FRC, /* 0x19*/
+ AV8100_CODEC_CA_FL_FR_FC_RC_FLC_FRC, /* 0x1a*/
+ AV8100_CODEC_CA_FL_FR_LFE_FR_FC_RC_FLC_FRC, /* 0x1b*/
+ AV8100_CODEC_CA_FL_FR_RL_RR_FLC_FRC, /* 0x1c*/
+ AV8100_CODEC_CA_FL_FR_LFE_RL_RR_FLC_FRC, /* 0x1d*/
+ AV8100_CODEC_CA_FL_FR_FC_RL_RR_FLC_FRC, /* 0x1e*/
+ AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR_FLC_FRC /* 0x1f, 7.1 */
+};
+
+enum hdmi_level_shift_value {
+ AV8100_CODEC_LSV_0DB,
+ AV8100_CODEC_LSV_1DB,
+ AV8100_CODEC_LSV_2DB,
+ AV8100_CODEC_LSV_3DB,
+ AV8100_CODEC_LSV_4DB,
+ AV8100_CODEC_LSV_5DB,
+ AV8100_CODEC_LSV_6DB,
+ AV8100_CODEC_LSV_7DB,
+ AV8100_CODEC_LSV_8DB,
+ AV8100_CODEC_LSV_9DB,
+ AV8100_CODEC_LSV_10DB,
+ AV8100_CODEC_LSV_11DB,
+ AV8100_CODEC_LSV_12DB,
+ AV8100_CODEC_LSV_13DB,
+ AV8100_CODEC_LSV_14DB,
+ AV8100_CODEC_LSV_15DB
+};
+
+struct hdmi_audio_settings {
+ enum hdmi_audio_channel_count audio_channel_count;
+ enum hdmi_sampling_frequency sampling_frequency;
+ enum hdmi_sample_size sample_size;
+ enum hdmi_channel_allocation channel_allocation;
+ enum hdmi_level_shift_value level_shift_value;
+ bool downmix_inhibit;
+};
+
+/* Extended interface for codec-driver */
+int av8100_audio_change_hdmi_audio_settings(struct snd_soc_dai *dai,
+ struct hdmi_audio_settings *as);
+
+#endif /* AV8100_AUDIO_CODEC_H */
+
+
+
diff --git a/sound/soc/codecs/cg29xx.c b/sound/soc/codecs/cg29xx.c
new file mode 100644
index 00000000000..5b62a01f384
--- /dev/null
+++ b/sound/soc/codecs/cg29xx.c
@@ -0,0 +1,772 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Roger Nilsson <roger.xr.nilsson@stericsson.com>,
+ * Ola Lilja <ola.o.lilja@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+ #include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/bitops.h>
+#include <../../../drivers/staging/cg2900/include/cg2900_audio.h>
+
+#include "cg29xx.h"
+
+#define CG29XX_NBR_OF_DAI 2
+#define CG29XX_SUPPORTED_RATE_PCM (SNDRV_PCM_RATE_8000 | \
+ SNDRV_PCM_RATE_16000)
+
+#define CG29XX_SUPPORTED_RATE (SNDRV_PCM_RATE_8000 | \
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define CG29XX_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE)
+
+enum cg29xx_dai_direction {
+ CG29XX_DAI_DIRECTION_TX,
+ CG29XX_DAI_DIRECTION_RX
+};
+
+static int cg29xx_dai_startup(
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai);
+
+static int cg29xx_dai_prepare(
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai);
+
+static int cg29xx_dai_hw_params(
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params,
+ struct snd_soc_dai *dai);
+
+static void cg29xx_dai_shutdown(
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai);
+
+static int cg29xx_set_dai_sysclk(
+ struct snd_soc_dai *codec_dai,
+ int clk_id,
+ unsigned int freq, int dir);
+
+static int cg29xx_set_dai_fmt(
+ struct snd_soc_dai *codec_dai,
+ unsigned int fmt);
+
+static int cg29xx_set_tdm_slot(
+ struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots,
+ int slot_width);
+
+static struct cg29xx_codec codec_private = {
+ .session = 0,
+};
+
+static struct snd_soc_dai_ops cg29xx_dai_driver_dai_ops = {
+ .startup = cg29xx_dai_startup,
+ .prepare = cg29xx_dai_prepare,
+ .hw_params = cg29xx_dai_hw_params,
+ .shutdown = cg29xx_dai_shutdown,
+ .set_sysclk = cg29xx_set_dai_sysclk,
+ .set_fmt = cg29xx_set_dai_fmt,
+ .set_tdm_slot = cg29xx_set_tdm_slot
+};
+
+struct snd_soc_dai_driver cg29xx_dai_driver[] = {
+ {
+ .name = "cg29xx-codec-dai.0",
+ .id = 0,
+ .playback = {
+ .stream_name = "CG29xx.0 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = CG29XX_SUPPORTED_RATE,
+ .formats = CG29XX_SUPPORTED_FMT,
+ },
+ .capture = {
+ .stream_name = "CG29xx.0 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = CG29XX_SUPPORTED_RATE,
+ .formats = CG29XX_SUPPORTED_FMT,
+ },
+ .ops = &cg29xx_dai_driver_dai_ops,
+ .symmetric_rates = 1,
+ },
+ {
+ .name = "cg29xx-codec-dai.1",
+ .id = 1,
+ .playback = {
+ .stream_name = "CG29xx.1 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = CG29XX_SUPPORTED_RATE_PCM,
+ .formats = CG29XX_SUPPORTED_FMT,
+ },
+ .capture = {
+ .stream_name = "CG29xx.1 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = CG29XX_SUPPORTED_RATE_PCM,
+ .formats = CG29XX_SUPPORTED_FMT,
+ },
+ .ops = &cg29xx_dai_driver_dai_ops,
+ .symmetric_rates = 1,
+ }
+};
+EXPORT_SYMBOL_GPL(cg29xx_dai_driver);
+
+static const char *enum_ifs_input_select[] = {
+ "BT_SCO", "FM_RX"
+};
+
+static const char *enum_ifs_output_select[] = {
+ "BT_SCO", "FM_TX"
+};
+
+/* If0 Input Select */
+static struct soc_enum if0_input_select =
+ SOC_ENUM_SINGLE(INTERFACE0_INPUT_SELECT, 0,
+ ARRAY_SIZE(enum_ifs_input_select),
+ enum_ifs_input_select);
+
+/* If1 Input Select */
+static struct soc_enum if1_input_select =
+ SOC_ENUM_SINGLE(INTERFACE1_INPUT_SELECT, 0,
+ ARRAY_SIZE(enum_ifs_input_select),
+ enum_ifs_input_select);
+
+/* If0 Output Select */
+static struct soc_enum if0_output_select =
+ SOC_ENUM_SINGLE(INTERFACE0_OUTPUT_SELECT, 0,
+ ARRAY_SIZE(enum_ifs_output_select),
+ enum_ifs_output_select);
+
+/* If1 Output Select */
+static struct soc_enum if1_output_select =
+ SOC_ENUM_SINGLE(INTERFACE1_OUTPUT_SELECT, 4,
+ ARRAY_SIZE(enum_ifs_output_select),
+ enum_ifs_output_select);
+
+static struct snd_kcontrol_new cg29xx_snd_controls[] = {
+ SOC_ENUM("If0 Input Select", if0_input_select),
+ SOC_ENUM("If1 Input Select", if1_input_select),
+ SOC_ENUM("If0 Output Select", if0_output_select),
+ SOC_ENUM("If1 Output Select", if1_output_select),
+};
+
+
+static struct cg29xx_codec_dai_data *get_dai_data_codec(struct snd_soc_codec *codec,
+ int dai_id)
+{
+ struct cg29xx_codec_dai_data *codec_drvdata = snd_soc_codec_get_drvdata(codec);
+ return &codec_drvdata[dai_id];
+}
+
+static struct cg29xx_codec_dai_data *get_dai_data(struct snd_soc_dai *codec_dai)
+{
+ return get_dai_data_codec(codec_dai->codec, codec_dai->id);
+}
+
+static int cg29xx_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id,
+ unsigned int freq, int dir)
+{
+ return 0;
+}
+
+static int cg29xx_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct cg29xx_codec_dai_data *dai_data = get_dai_data(codec_dai);
+ unsigned int prot;
+ unsigned int msel;
+ prot = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+ msel = fmt & SND_SOC_DAIFMT_MASTER_MASK;
+
+ switch (prot) {
+ case SND_SOC_DAIFMT_I2S:
+ if (dai_data->config.port != PORT_0_I2S) {
+ pr_err("cg29xx_dai: unsupported DAI format 0x%x\n",
+ fmt);
+ return -EINVAL;
+ }
+
+ if (msel == SND_SOC_DAIFMT_CBM_CFM)
+ dai_data->config.conf.i2s.mode = DAI_MODE_MASTER;
+ else
+ dai_data->config.conf.i2s.mode = DAI_MODE_SLAVE;
+ break;
+
+ case SND_SOC_DAIFMT_DSP_B:
+ if (dai_data->config.port != PORT_1_I2S_PCM ||
+ msel == SND_SOC_DAIFMT_CBM_CFM) {
+ pr_err("cg29xx_dai: unsupported DAI format 0x%x port=%d,msel=%d\n",
+ fmt, dai_data->config.port, msel);
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cg29xx_set_tdm_slot(struct snd_soc_dai *codec_dai,
+ unsigned int tx_mask,
+ unsigned int rx_mask,
+ int slots,
+ int slot_width)
+{
+ struct cg29xx_codec_dai_data *dai_data = get_dai_data(codec_dai);
+
+ if (dai_data->config.port != PORT_1_I2S_PCM)
+ return -EINVAL;
+
+ dai_data->config.conf.i2s_pcm.slot_0_used =
+ (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT0_SHIFT) ?
+ true : false;
+ dai_data->config.conf.i2s_pcm.slot_1_used =
+ (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT1_SHIFT) ?
+ true : false;
+ dai_data->config.conf.i2s_pcm.slot_2_used =
+ (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT2_SHIFT) ?
+ true : false;
+ dai_data->config.conf.i2s_pcm.slot_3_used =
+ (tx_mask | rx_mask) & (1<<CG29XX_DAI_SLOT3_SHIFT) ?
+ true : false;
+
+ dai_data->config.conf.i2s_pcm.slot_0_start = 0;
+ dai_data->config.conf.i2s_pcm.slot_1_start = slot_width;
+ dai_data->config.conf.i2s_pcm.slot_2_start = 2 * slot_width;
+ dai_data->config.conf.i2s_pcm.slot_3_start = 3 * slot_width;
+
+ return 0;
+}
+
+static int cg29xx_configure_endp(struct cg29xx_codec_dai_data *dai_data,
+ enum cg2900_audio_endpoint_id endpid)
+{
+ struct cg2900_endpoint_config config;
+ int err;
+ enum cg2900_dai_sample_rate dai_sr;
+ enum cg2900_endpoint_sample_rate endp_sr;
+
+ switch (dai_data->config.port) {
+ default:
+ case PORT_0_I2S:
+ dai_sr = dai_data->config.conf.i2s.sample_rate;
+ break;
+
+ case PORT_1_I2S_PCM:
+ dai_sr = dai_data->config.conf.i2s_pcm.sample_rate;
+ break;
+ }
+
+ switch (dai_sr) {
+ default:
+ case SAMPLE_RATE_8:
+ endp_sr = ENDPOINT_SAMPLE_RATE_8_KHZ;
+ break;
+ case SAMPLE_RATE_16:
+ endp_sr = ENDPOINT_SAMPLE_RATE_16_KHZ;
+ break;
+ case SAMPLE_RATE_44_1:
+ endp_sr = ENDPOINT_SAMPLE_RATE_44_1_KHZ;
+ break;
+ case SAMPLE_RATE_48:
+ endp_sr = ENDPOINT_SAMPLE_RATE_48_KHZ;
+ break;
+ }
+
+ config.endpoint_id = endpid;
+
+ switch (endpid) {
+ default:
+ case ENDPOINT_BT_SCO_INOUT:
+ config.config.sco.sample_rate = endp_sr;
+ break;
+
+ case ENDPOINT_FM_TX:
+ case ENDPOINT_FM_RX:
+ config.config.fm.sample_rate = endp_sr;
+ break;
+ }
+
+ err = cg2900_audio_config_endpoint(codec_private.session, &config);
+
+ return err;
+}
+
+static int cg29xx_stop_if(struct cg29xx_codec_dai_data *dai_data,
+ enum cg29xx_dai_direction direction)
+{
+ int err = 0;
+ unsigned int *stream;
+
+ if (direction == CG29XX_DAI_DIRECTION_TX)
+ stream = &dai_data->tx_active;
+ else
+ stream = &dai_data->rx_active;
+
+ if (*stream) {
+ err = cg2900_audio_stop_stream(
+ codec_private.session,
+ *stream);
+ if (!err) {
+ *stream = 0;
+ } else {
+ pr_err("asoc cg29xx - %s - Failed to stop stream on interface %d.\n",
+ __func__,
+ dai_data->config.port);
+ }
+ }
+
+ return err;
+}
+
+static int cg29xx_start_if(struct cg29xx_codec_dai_data *dai_data,
+ enum cg29xx_dai_direction direction)
+{
+ enum cg2900_audio_endpoint_id if_endpid;
+ enum cg2900_audio_endpoint_id endpid;
+ unsigned int *stream;
+ int err;
+
+ if (dai_data->config.port == PORT_0_I2S)
+ if_endpid = ENDPOINT_PORT_0_I2S;
+ else
+ if_endpid = ENDPOINT_PORT_1_I2S_PCM;
+
+ if (direction == CG29XX_DAI_DIRECTION_RX) {
+ switch (dai_data->output_select) {
+ default:
+ case 0:
+ endpid = ENDPOINT_BT_SCO_INOUT;
+ break;
+ case 1:
+ endpid = ENDPOINT_FM_TX;
+ }
+ stream = &dai_data->rx_active;
+ } else {
+ switch (dai_data->input_select) {
+ default:
+ case 0:
+ endpid = ENDPOINT_BT_SCO_INOUT;
+ break;
+ case 1:
+ endpid = ENDPOINT_FM_RX;
+ }
+
+ stream = &dai_data->tx_active;
+ }
+
+ if (*stream || (endpid == ENDPOINT_BT_SCO_INOUT)) {
+ pr_debug("asoc cg29xx - %s - The interface has already been started.\n",
+ __func__);
+ return 0;
+ }
+
+ pr_debug("asoc cg29xx - %s - direction: %d, if_id: %d endpid: %d\n",
+ __func__,
+ direction,
+ if_endpid,
+ endpid);
+
+ err = cg29xx_configure_endp(dai_data, endpid);
+
+ if (err) {
+ pr_err("asoc cg29xx - %s - Configure endpoint id: %d failed.\n",
+ __func__,
+ endpid);
+
+ return err;
+ }
+
+ err = cg2900_audio_start_stream(codec_private.session,
+ if_endpid,
+ endpid,
+ stream);
+
+ return err;
+}
+
+static int cg29xx_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int err = 0;
+
+ if (!codec_private.session)
+ err = cg2900_audio_open(&codec_private.session, NULL);
+
+ return err;
+}
+
+static int cg29xx_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *codec_dai)
+{
+ struct cg29xx_codec_dai_data *dai_data = get_dai_data(codec_dai);
+ int err = 0;
+ enum cg29xx_dai_direction direction;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ direction = CG29XX_DAI_DIRECTION_RX;
+ else
+ direction = CG29XX_DAI_DIRECTION_TX;
+
+ err = cg29xx_start_if(dai_data, direction);
+
+ return err;
+}
+
+static void cg29xx_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *codec_dai)
+{
+ struct cg29xx_codec_dai_data *dai_data = get_dai_data(codec_dai);
+ enum cg29xx_dai_direction direction;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ direction = CG29XX_DAI_DIRECTION_RX;
+ else
+ direction = CG29XX_DAI_DIRECTION_TX;
+
+ (void) cg29xx_stop_if(dai_data, direction);
+}
+
+static int cg29xx_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params,
+ struct snd_soc_dai *codec_dai)
+{
+ struct cg29xx_codec_dai_data *dai_data = get_dai_data(codec_dai);
+ enum cg2900_dai_fs_duration duration = SYNC_DURATION_32;
+ enum cg2900_dai_bit_clk bclk = BIT_CLK_512;
+ int sr;
+ int err = 0;
+ enum cg2900_dai_stream_ratio ratio = STREAM_RATIO_FM48_VOICE16;
+
+ pr_debug("cg29xx asoc - %s called. Port: %d.\n",
+ __func__,
+ dai_data->config.port);
+
+ switch (params_rate(hw_params)) {
+ case 8000:
+ sr = SAMPLE_RATE_8;
+ bclk = BIT_CLK_512;
+ duration = SYNC_DURATION_32;
+ ratio = STREAM_RATIO_FM48_VOICE8;
+ break;
+ case 16000:
+ sr = SAMPLE_RATE_16;
+ bclk = BIT_CLK_512;
+ duration = SYNC_DURATION_32;
+ ratio = STREAM_RATIO_FM48_VOICE16;
+ break;
+ case 44100:
+ sr = SAMPLE_RATE_44_1;
+ break;
+ case 48000:
+ sr = SAMPLE_RATE_48;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (dai_data->config.port == PORT_0_I2S) {
+ dai_data->config.conf.i2s.sample_rate = sr;
+ } else {
+ dai_data->config.conf.i2s_pcm.sample_rate = sr;
+ dai_data->config.conf.i2s_pcm.duration = duration;
+ dai_data->config.conf.i2s_pcm.clk = bclk;
+ dai_data->config.conf.i2s_pcm.ratio = ratio;
+ }
+
+ if (!(dai_data->tx_active | dai_data->rx_active) && dai_data->config.port != PORT_1_I2S_PCM) {
+ err = cg2900_audio_set_dai_config(
+ codec_private.session,
+ &dai_data->config);
+
+ pr_debug("asoc cg29xx: cg2900_audio_set_dai_config"
+ "on port %d completed with result: %d.\n",
+ dai_data->config.port,
+ err);
+ }
+
+ return err;
+}
+
+static unsigned int cg29xx_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct cg29xx_codec_dai_data *dai_data;
+
+ switch (reg) {
+ case INTERFACE0_INPUT_SELECT:
+ dai_data = get_dai_data_codec(codec, 0);
+ return dai_data->input_select;
+
+ case INTERFACE1_INPUT_SELECT:
+ dai_data = get_dai_data_codec(codec, 1);
+ return dai_data->input_select;
+
+ case INTERFACE0_OUTPUT_SELECT:
+ dai_data = get_dai_data_codec(codec, 0);
+ return dai_data->output_select;
+
+ case INTERFACE1_OUTPUT_SELECT:
+ dai_data = get_dai_data_codec(codec, 1);
+ return dai_data->output_select;
+
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int cg29xx_codec_write(struct snd_soc_codec *codec,
+ unsigned int reg,
+ unsigned int value)
+{
+ struct cg29xx_codec_dai_data *dai_data;
+ enum cg29xx_dai_direction direction;
+ bool restart_if = false;
+ int old_value;
+
+ switch (reg) {
+ case INTERFACE0_INPUT_SELECT:
+ dai_data = get_dai_data_codec(codec, 0);
+ direction = CG29XX_DAI_DIRECTION_TX;
+
+ old_value = dai_data->input_select;
+ dai_data->input_select = value;
+
+ if ((old_value ^ value) && dai_data->tx_active)
+ restart_if = true;
+ break;
+
+ case INTERFACE1_INPUT_SELECT:
+ dai_data = get_dai_data_codec(codec, 1);
+ direction = CG29XX_DAI_DIRECTION_TX;
+
+ old_value = dai_data->input_select;
+ dai_data->input_select = value;
+
+ if ((old_value ^ value) && dai_data->tx_active)
+ restart_if = true;
+ break;
+
+ case INTERFACE0_OUTPUT_SELECT:
+ dai_data = get_dai_data_codec(codec, 0);
+ direction = CG29XX_DAI_DIRECTION_RX;
+
+ old_value = dai_data->output_select;
+ dai_data->output_select = value;
+
+ if ((old_value ^ value) && dai_data->rx_active)
+ restart_if = true;
+ break;
+
+ case INTERFACE1_OUTPUT_SELECT:
+ dai_data = get_dai_data_codec(codec, 1);
+ direction = CG29XX_DAI_DIRECTION_RX;
+
+ old_value = dai_data->output_select;
+ dai_data->output_select = value;
+
+ if ((old_value ^ value) && dai_data->rx_active)
+ restart_if = true;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (restart_if) {
+ (void) cg29xx_stop_if(dai_data, direction);
+ (void) cg29xx_start_if(dai_data, direction);
+ }
+
+ return 0;
+}
+
+static int cg29xx_codec_probe(struct snd_soc_codec *codec)
+{
+ pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name);
+
+ snd_soc_add_codec_controls(
+ codec,
+ cg29xx_snd_controls,
+ ARRAY_SIZE(cg29xx_snd_controls));
+
+ return 0;
+}
+
+static int cg29xx_codec_remove(struct snd_soc_codec *codec)
+{
+ pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name);
+
+ return 0;
+}
+
+static int cg29xx_codec_suspend(struct snd_soc_codec *codec)
+{
+ pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name);
+
+ return 0;
+}
+
+static int cg29xx_codec_resume(struct snd_soc_codec *codec)
+{
+ pr_debug("%s: Enter (codec->name = %s).\n", __func__, codec->name);
+
+ return 0;
+}
+
+struct snd_soc_codec_driver cg29xx_codec_driver = {
+ .probe = cg29xx_codec_probe,
+ .remove = cg29xx_codec_remove,
+ .suspend = cg29xx_codec_suspend,
+ .resume = cg29xx_codec_resume,
+ .read = cg29xx_codec_read,
+ .write = cg29xx_codec_write,
+};
+
+static int __devinit cg29xx_codec_driver_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct cg29xx_codec_dai_data *dai_data;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ pr_info("%s: Init codec private data..\n", __func__);
+ dai_data = kzalloc(CG29XX_NBR_OF_DAI * sizeof(struct cg29xx_codec_dai_data),
+ GFP_KERNEL);
+ if (dai_data == NULL)
+ return -ENOMEM;
+
+ dai_data[0].tx_active = 0;
+ dai_data[0].rx_active = 0;
+ dai_data[0].input_select = 1;
+ dai_data[0].output_select = 1;
+ dai_data[0].config.port = PORT_0_I2S;
+ dai_data[0].config.conf.i2s.mode = DAI_MODE_SLAVE;
+ dai_data[0].config.conf.i2s.half_period = HALF_PER_DUR_16;
+ dai_data[0].config.conf.i2s.channel_sel = CHANNEL_SELECTION_BOTH;
+ dai_data[0].config.conf.i2s.sample_rate = SAMPLE_RATE_48;
+ dai_data[0].config.conf.i2s.word_width = WORD_WIDTH_32;
+ dai_data[1].tx_active = 0;
+ dai_data[1].rx_active = 0;
+ dai_data[1].input_select = 0;
+ dai_data[1].output_select = 0;
+ dai_data[1].config.port = PORT_1_I2S_PCM;
+ dai_data[1].config.conf.i2s_pcm.mode = DAI_MODE_SLAVE;
+ dai_data[1].config.conf.i2s_pcm.slot_0_dir = DAI_DIR_B_RX_A_TX;
+ dai_data[1].config.conf.i2s_pcm.slot_1_dir = DAI_DIR_B_TX_A_RX;
+ dai_data[1].config.conf.i2s_pcm.slot_2_dir = DAI_DIR_B_RX_A_TX;
+ dai_data[1].config.conf.i2s_pcm.slot_3_dir = DAI_DIR_B_RX_A_TX;
+ dai_data[1].config.conf.i2s_pcm.slot_0_used = true;
+ dai_data[1].config.conf.i2s_pcm.slot_1_used = false;
+ dai_data[1].config.conf.i2s_pcm.slot_2_used = false;
+ dai_data[1].config.conf.i2s_pcm.slot_3_used = false;
+ dai_data[1].config.conf.i2s_pcm.slot_0_start = 0;
+ dai_data[1].config.conf.i2s_pcm.slot_1_start = 16;
+ dai_data[1].config.conf.i2s_pcm.slot_2_start = 32;
+ dai_data[1].config.conf.i2s_pcm.slot_3_start = 48;
+ dai_data[1].config.conf.i2s_pcm.protocol = PORT_PROTOCOL_PCM;
+ dai_data[1].config.conf.i2s_pcm.ratio = STREAM_RATIO_FM48_VOICE16;
+ dai_data[1].config.conf.i2s_pcm.duration = SYNC_DURATION_32;
+ dai_data[1].config.conf.i2s_pcm.clk = BIT_CLK_512;
+ dai_data[1].config.conf.i2s_pcm.sample_rate = SAMPLE_RATE_16;
+
+ platform_set_drvdata(pdev, dai_data);
+
+ pr_info("%s: Register codec.\n", __func__);
+ ret = snd_soc_register_codec(&pdev->dev, &cg29xx_codec_driver, &cg29xx_dai_driver[0], 2);
+ if (ret < 0) {
+ pr_debug("%s: Error: Failed to register codec (ret = %d).\n",
+ __func__,
+ ret);
+ snd_soc_unregister_codec(&pdev->dev);
+ kfree(platform_get_drvdata(pdev));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit cg29xx_codec_driver_remove(struct platform_device *pdev)
+{
+ (void)cg2900_audio_close(&codec_private.session);
+
+ snd_soc_unregister_codec(&pdev->dev);
+ kfree(platform_get_drvdata(pdev));
+
+ return 0;
+}
+
+static int cg29xx_codec_driver_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+static int cg29xx_codec_driver_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver cg29xx_codec_platform_driver = {
+ .driver = {
+ .name = "cg29xx-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = cg29xx_codec_driver_probe,
+ .remove = __devexit_p(cg29xx_codec_driver_remove),
+ .suspend = cg29xx_codec_driver_suspend,
+ .resume = cg29xx_codec_driver_resume,
+};
+
+
+static int __devinit cg29xx_codec_platform_driver_init(void)
+{
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ ret = platform_driver_register(&cg29xx_codec_platform_driver);
+ if (ret != 0)
+ pr_err("Failed to register CG29xx platform driver (%d)!\n", ret);
+
+ return ret;
+}
+
+static void __exit cg29xx_codec_platform_driver_exit(void)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ platform_driver_unregister(&cg29xx_codec_platform_driver);
+}
+
+
+module_init(cg29xx_codec_platform_driver_init);
+module_exit(cg29xx_codec_platform_driver_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/cg29xx.h b/sound/soc/codecs/cg29xx.h
new file mode 100644
index 00000000000..fec52d7cdd7
--- /dev/null
+++ b/sound/soc/codecs/cg29xx.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Roger Nilsson roger.xr.nilsson@stericsson.com
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+#ifndef CG29XX_CODEC_H
+#define CG29XX_CODEC_H
+
+#include <../../../drivers/staging/cg2900/include/cg2900_audio.h>
+
+struct cg29xx_codec_dai_data {
+ struct mutex mutex;
+ unsigned int rx_active;
+ unsigned int tx_active;
+ int input_select;
+ int output_select;
+ struct cg2900_dai_config config;
+};
+
+struct cg29xx_codec{
+ unsigned int session;
+};
+
+#define CG29XX_DAI_SLOT0_SHIFT 0
+#define CG29XX_DAI_SLOT1_SHIFT 1
+#define CG29XX_DAI_SLOT2_SHIFT 2
+#define CG29XX_DAI_SLOT3_SHIFT 3
+
+#define INTERFACE0_INPUT_SELECT 0x00
+#define INTERFACE1_INPUT_SELECT 0x01
+#define INTERFACE0_OUTPUT_SELECT 0x02
+#define INTERFACE1_OUTPUT_SELECT 0x03
+
+#endif /* CG29XX_CODEC_H */
diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig
new file mode 100644
index 00000000000..f412c89d8df
--- /dev/null
+++ b/sound/soc/ux500/Kconfig
@@ -0,0 +1,67 @@
+#
+# Ux500 SoC audio configuration
+#
+
+menuconfig SND_SOC_UX500
+ bool "SoC Audio support for Ux500 platform"
+ depends on SND_SOC && STM_MSP_SPI
+ default n
+ help
+ Say Y if you want to add support for the Ux500 platform.
+
+choice
+ prompt "Platform 5500/8500"
+ depends on SND_SOC_UX500
+ default SND_SOC_U8500
+ config SND_SOC_U8500
+ bool "Platform - U8500"
+ config SND_SOC_U5500
+ bool "Platform - U5500"
+endchoice
+
+config SND_SOC_UX500_AB3550
+ bool "Codec - AB3550"
+ depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && AB3550_CORE
+ select SND_SOC_AB3550
+ default n
+ help
+ Say Y if you want to include the AB3550 codec.
+
+config SND_SOC_UX500_AB5500
+ bool "Codec - AB5500"
+ depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && AB5500_CORE
+ select SND_SOC_AB5500
+ default n
+ help
+ Say Y if you want to include the AB5500 codec.
+
+config SND_SOC_UX500_AB8500
+ bool "Codec - AB8500"
+ depends on SND_SOC_UX500 && UX500_SOC_DB8500 && AB8500_CORE && AB8500_GPADC
+ select SND_SOC_AB8500
+ default n
+ help
+ Say Y if you want to include AB8500 audio codec.
+
+config SND_SOC_UX500_CG29XX
+ bool "Codec - CG29xx"
+ depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && CG2900_AUDIO
+ select SND_SOC_CG29XX
+ default n
+ help
+ Say Y if you want to include CG29xx codec (Combo chip).
+
+config SND_SOC_UX500_AV8100
+ bool "Codec - AV8100"
+ depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && AV8100
+ select SND_SOC_AV8100
+ default n
+ help
+ Say Y if you want to include AV8100 codec (HDMI chip).
+
+config SND_SOC_UX500_DEBUG
+ bool "Activate Ux500 platform debug-mode (pr_debug)"
+ depends on SND_SOC_UX500
+ default n
+ help
+ Say Y if you want to add debug level prints for Ux500 code-files.
diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile
new file mode 100644
index 00000000000..262e44a2812
--- /dev/null
+++ b/sound/soc/ux500/Makefile
@@ -0,0 +1,46 @@
+# Ux500 Platform Support
+
+ifdef CONFIG_SND_SOC_UX500_DEBUG
+CFLAGS_u8500.o := -DDEBUG
+CFLAGS_ux500_pcm.o := -DDEBUG
+CFLAGS_ux500_msp_dai.o := -DDEBUG
+CFLAGS_ux500_ab3550.o := -DDEBUG
+CFLAGS_ux500_ab8500.o := -DDEBUG
+CFLAGS_ux500_av8100.o := -DDEBUG
+CFLAGS_ux500_cg29xx.o := -DDEBUG
+CFLAGS_ux500_msp_i2s.o := -DDEBUG
+endif
+
+ifdef CONFIG_UX500_SOC_DBX500
+snd-soc-ux500-platform-objs := ux500_pcm.o ux500_msp_dai.o ux500_msp_i2s.o
+obj-y += snd-soc-ux500-platform.o
+endif
+
+ifdef CONFIG_SND_SOC_UX500_AB8500
+snd-soc-ux500-machine-objs += ux500_ab8500.o
+endif
+
+ifdef CONFIG_SND_SOC_UX500_AV8100
+snd-soc-ux500-machine-objs += ux500_av8100.o
+endif
+
+ifdef CONFIG_SND_SOC_UX500_CG29XX
+snd-soc-ux500-machine-objs += ux500_cg29xx.o
+endif
+
+ifdef CONFIG_SND_SOC_UX500_AB5500
+snd-soc-ux500-machine-objs += ux500_ab5500.o
+endif
+
+obj-y += snd-soc-ux500-machine.o
+
+ifdef CONFIG_UX500_SOC_DB8500
+snd-soc-u8500-objs := u8500.o
+obj-y += snd-soc-u8500.o
+endif
+
+ifdef CONFIG_UX500_SOC_DB5500
+snd-soc-u5500-objs := u5500.o
+obj-y += snd-soc-u5500.o
+endif
+
diff --git a/sound/soc/ux500/u5500.c b/sound/soc/ux500/u5500.c
new file mode 100755
index 00000000000..6787daa9de5
--- /dev/null
+++ b/sound/soc/ux500/u5500.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Xie Xiaolei (xie.xiaolei@stericsson.com)
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <linux/io.h>
+#include <sound/soc.h>
+#include <asm/mach-types.h>
+
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+
+#include <linux/spi/spi.h>
+#include <sound/initval.h>
+
+#ifdef CONFIG_SND_SOC_UX500_AB5500
+#include "ux500_ab5500.h"
+#endif
+
+#ifdef CONFIG_SND_SOC_UX500_AV8100
+#include "ux500_av8100.h"
+#endif
+#ifdef CONFIG_SND_SOC_UX500_CG29XX
+#include "ux500_cg29xx.h"
+#endif
+static struct platform_device *u5500_platform_dev;
+/* Create dummy devices for platform drivers */
+
+static struct platform_device ux500_pcm = {
+ .name = "ux500-pcm",
+ .id = 0,
+ .dev = {
+ .platform_data = NULL,
+ },
+};
+
+#ifdef CONFIG_SND_SOC_UX500_AV8100
+static struct platform_device av8100_codec = {
+ .name = "av8100-codec",
+ .id = 0,
+ .dev = {
+ .platform_data = NULL,
+ },
+};
+#endif
+
+#ifdef CONFIG_SND_SOC_UX500_CG29XX
+static struct platform_device cg29xx_codec = {
+ .name = "cg29xx-codec",
+ .id = 0,
+ .dev = {
+ .platform_data = NULL,
+ },
+};
+#endif
+/* Define the whole U5500 soundcard, linking platform to the codec-drivers */
+struct snd_soc_dai_link u5500_dai_links[] = {
+ {
+ .name = "ab5500_0",
+ .stream_name = "ab5500_0",
+ .cpu_dai_name = "ux500-msp-i2s.0",
+ .codec_dai_name = "ab5500-codec-dai.0",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "ab5500-codec.0",
+ .init = ux500_ab5500_machine_codec_init,
+ .ops = (struct snd_soc_ops[]) {
+ {
+ .startup = ux500_ab5500_startup,
+ .shutdown = ux500_ab5500_shutdown,
+ .hw_params = ux500_ab5500_hw_params,
+ }
+ }
+ },
+ #ifdef CONFIG_SND_SOC_UX500_CG29XX
+ {
+ .name = "cg29xx_0",
+ .stream_name = "cg29xx_0",
+ .cpu_dai_name = "ux500-msp-i2s.1",
+ .codec_dai_name = "cg29xx-codec-dai.0",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "cg29xx-codec.0",
+ .init = NULL,
+ .ops = u5500_cg29xx_ops,
+ },
+ {
+ .name = "cg29xx_1",
+ .stream_name = "cg29xx_1",
+ .cpu_dai_name = "ux500-msp-i2s.0",
+ .codec_dai_name = "cg29xx-codec-dai.1",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "cg29xx-codec.0",
+ .init = NULL,
+ .ops = u5500_cg29xx_ops,
+ },
+ #endif
+ {
+ .name = "ab5500_1",
+ .stream_name = "ab5500_1",
+ .cpu_dai_name = "ux500-msp-i2s.1",
+ .codec_dai_name = "ab5500-codec-dai.1",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "ab5500-codec.0",
+ .init = ux500_ab5500_machine_codec_init,
+ .ops = (struct snd_soc_ops[]) {
+ {
+ .startup = ux500_ab5500_startup,
+ .shutdown = ux500_ab5500_shutdown,
+ .hw_params = ux500_ab5500_hw_params,
+ }
+ }
+ },
+ #ifdef CONFIG_SND_SOC_UX500_AV8100
+ {
+ .name = "hdmi",
+ .stream_name = "hdmi",
+ .cpu_dai_name = "ux500-msp-i2s.2",
+ .codec_dai_name = "av8100-codec-dai",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "av8100-codec.0",
+ .init = NULL,
+ .ops = ux500_av8100_ops,
+ },
+ #endif
+};
+
+static struct snd_soc_card u5500_drvdata = {
+ .name = "U5500-card",
+ .probe = NULL,
+ .dai_link = u5500_dai_links,
+ .num_links = ARRAY_SIZE(u5500_dai_links),
+};
+
+static int __init u5500_soc_init(void)
+{
+ int ret = 0;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ if (!machine_is_u5500())
+ return 0;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ #ifdef CONFIG_SND_SOC_UX500_AV8100
+ pr_debug("%s: Register device to generate a probe for AV8100 codec.\n",
+ __func__);
+ platform_device_register(&av8100_codec);
+ #endif
+
+ #ifdef CONFIG_SND_SOC_UX500_CG29XX
+ pr_debug("%s: Register device to generate a probe for CG29xx codec.\n",
+ __func__);
+ platform_device_register(&cg29xx_codec);
+ #endif
+ pr_debug("%s: Register device to generate a probe for Ux500-pcm platform.\n",
+ __func__);
+ platform_device_register(&ux500_pcm);
+
+ u5500_platform_dev = platform_device_alloc("soc-audio", -1);
+ if (!u5500_platform_dev)
+ return -ENOMEM;
+
+ platform_set_drvdata(u5500_platform_dev, &u5500_drvdata);
+ u5500_drvdata.dev = &u5500_platform_dev->dev;
+
+ ret = platform_device_add(u5500_platform_dev);
+ if (ret) {
+ pr_err("%s: Error: Failed to add platform device (%s).\n",
+ __func__,
+ u5500_drvdata.name);
+ platform_device_put(u5500_platform_dev);
+ }
+
+ return ret;
+}
+
+static void __exit u5500_soc_exit(void)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ platform_device_unregister(u5500_platform_dev);
+}
+
+module_init(u5500_soc_init);
+module_exit(u5500_soc_exit);
+
+MODULE_LICENSE("GPLv2");
diff --git a/sound/soc/ux500/u8500.c b/sound/soc/ux500/u8500.c
new file mode 100644
index 00000000000..516008fcc6a
--- /dev/null
+++ b/sound/soc/ux500/u8500.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja (ola.o.lilja@stericsson.com)
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/soc.h>
+#include <asm/mach-types.h>
+
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+
+#include <linux/spi/spi.h>
+#include <sound/initval.h>
+
+#ifdef CONFIG_SND_SOC_UX500_AB3550
+#include "ux500_ab3550.h"
+#endif
+
+#ifdef CONFIG_SND_SOC_UX500_AB8500
+#include <sound/ux500_ab8500.h>
+#endif
+
+#ifdef CONFIG_SND_SOC_UX500_AV8100
+#include "ux500_av8100.h"
+#endif
+
+#ifdef CONFIG_SND_SOC_UX500_CG29XX
+#include "ux500_cg29xx.h"
+#endif
+
+
+static struct platform_device *u8500_platform_dev;
+
+/* Create dummy devices for platform drivers */
+
+static struct platform_device ux500_pcm = {
+ .name = "ux500-pcm",
+ .id = 0,
+ .dev = {
+ .platform_data = NULL,
+ },
+};
+
+#ifdef CONFIG_SND_SOC_UX500_AV8100
+static struct platform_device av8100_codec = {
+ .name = "av8100-codec",
+ .id = 0,
+ .dev = {
+ .platform_data = NULL,
+ },
+};
+#endif
+
+#ifdef CONFIG_SND_SOC_UX500_CG29XX
+static struct platform_device cg29xx_codec = {
+ .name = "cg29xx-codec",
+ .id = 0,
+ .dev = {
+ .platform_data = NULL,
+ },
+};
+#endif
+
+/* Define the whole U8500 soundcard, linking platform to the codec-drivers */
+struct snd_soc_dai_link u8500_dai_links[] = {
+ #ifdef CONFIG_SND_SOC_UX500_AV8100
+ {
+ .name = "hdmi",
+ .stream_name = "hdmi",
+ .cpu_dai_name = "ux500-msp-i2s.2",
+ .codec_dai_name = "av8100-codec-dai",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "av8100-codec.0",
+ .init = NULL,
+ .ops = ux500_av8100_ops,
+ },
+ #endif
+ #ifdef CONFIG_SND_SOC_UX500_AB3550
+ {
+ .name = "ab3550_0",
+ .stream_name = "ab3550_0",
+ .cpu_dai_name = "ux500-msp-i2s.0",
+ .codec_dai_name = "ab3550-codec-dai.0",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "ab3550-codec.11",
+ .init = NULL,
+ .ops = ux500_ab3550_ops,
+ },
+ {
+ .name = "ab3550_1",
+ .stream_name = "ab3550_1",
+ .cpu_dai_name = "ux500-msp-i2s.1",
+ .codec_dai_name = "ab3550-codec-dai.1",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "ab3550-codec.11",
+ .init = NULL,
+ .ops = ux500_ab3550_ops,
+ },
+ #endif
+ #ifdef CONFIG_SND_SOC_UX500_AB8500
+ {
+ .name = "ab8500_0",
+ .stream_name = "ab8500_0",
+ .cpu_dai_name = "ux500-msp-i2s.1",
+ .codec_dai_name = "ab8500-codec-dai.0",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "ab8500-codec.0",
+ .init = ux500_ab8500_machine_codec_init,
+ .ops = ux500_ab8500_ops,
+ },
+ {
+ .name = "ab8500_1",
+ .stream_name = "ab8500_1",
+ .cpu_dai_name = "ux500-msp-i2s.3",
+ .codec_dai_name = "ab8500-codec-dai.1",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "ab8500-codec.0",
+ .init = NULL,
+ .ops = ux500_ab8500_ops,
+ },
+ #endif
+ #ifdef CONFIG_SND_SOC_UX500_CG29XX
+ {
+ .name = "cg29xx_0",
+ .stream_name = "cg29xx_0",
+ .cpu_dai_name = "ux500-msp-i2s.0",
+ .codec_dai_name = "cg29xx-codec-dai.1",
+ .platform_name = "ux500-pcm.0",
+ .codec_name = "cg29xx-codec.0",
+ .init = NULL,
+ .ops = ux500_cg29xx_ops,
+ },
+ #endif
+};
+
+static struct snd_soc_card u8500_drvdata = {
+ .name = "U8500-card",
+ .probe = NULL,
+ .dai_link = u8500_dai_links,
+ .num_links = ARRAY_SIZE(u8500_dai_links),
+};
+
+static int __init u8500_soc_init(void)
+{
+ int ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ if (machine_is_u5500())
+ return 0;
+
+ #ifdef CONFIG_SND_SOC_UX500_AV8100
+ pr_debug("%s: Register device to generate a probe for AV8100 codec.\n",
+ __func__);
+ platform_device_register(&av8100_codec);
+ #endif
+
+ #ifdef CONFIG_SND_SOC_UX500_CG29XX
+ pr_debug("%s: Register device to generate a probe for CG29xx codec.\n",
+ __func__);
+ platform_device_register(&cg29xx_codec);
+ #endif
+
+ #ifdef CONFIG_SND_SOC_UX500_AB8500
+ pr_debug("%s: Calling init-function for AB8500 machine driver.\n",
+ __func__);
+ ret = ux500_ab8500_soc_machine_drv_init();
+ if (ret)
+ pr_err("%s: ux500_ab8500_soc_machine_drv_init failed (%d).\n",
+ __func__, ret);
+ #endif
+
+ pr_debug("%s: Register device to generate a probe for Ux500-pcm platform.\n",
+ __func__);
+ platform_device_register(&ux500_pcm);
+
+ pr_debug("%s: Allocate platform device 'soc-audio'.\n",
+ __func__);
+ u8500_platform_dev = platform_device_alloc("soc-audio", -1);
+ if (!u8500_platform_dev)
+ return -ENOMEM;
+
+ pr_debug("%s: Card %s: num_links = %d\n",
+ __func__,
+ u8500_drvdata.name,
+ u8500_drvdata.num_links);
+ pr_debug("%s: Card %s: DAI-link 0: name = %s\n",
+ __func__,
+ u8500_drvdata.name,
+ u8500_drvdata.dai_link[0].name);
+ pr_debug("%s: Card %s: DAI-link 0: stream_name = %s\n",
+ __func__,
+ u8500_drvdata.name,
+ u8500_drvdata.dai_link[0].stream_name);
+
+ pr_debug("%s: Card %s: Set platform drvdata.\n",
+ __func__,
+ u8500_drvdata.name);
+ platform_set_drvdata(u8500_platform_dev, &u8500_drvdata);
+ u8500_drvdata.dev = &u8500_platform_dev->dev;
+
+ pr_debug("%s: Card %s: Add platform device.\n",
+ __func__,
+ u8500_drvdata.name);
+ ret = platform_device_add(u8500_platform_dev);
+ if (ret) {
+ pr_err("%s: Error: Failed to add platform device (%s).\n",
+ __func__,
+ u8500_drvdata.name);
+ platform_device_put(u8500_platform_dev);
+ }
+
+ return ret;
+}
+
+static void __exit u8500_soc_exit(void)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ #ifdef CONFIG_SND_SOC_UX500_AB8500
+ pr_debug("%s: Calling exit-function for AB8500 machine driver.\n",
+ __func__);
+ ux500_ab8500_soc_machine_drv_cleanup();
+ #endif
+
+ pr_debug("%s: Unregister platform device (%s).\n",
+ __func__,
+ u8500_drvdata.name);
+ platform_device_unregister(u8500_platform_dev);
+}
+
+module_init(u8500_soc_init);
+module_exit(u8500_soc_exit);
+
+MODULE_LICENSE("GPLv2");
diff --git a/sound/soc/ux500/ux500_ab3550.c b/sound/soc/ux500/ux500_ab3550.c
new file mode 100644
index 00000000000..7e144c0e4d2
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab3550.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja ola.o.lilja@stericsson.com,
+ * Roger Nilsson roger.xr.nilsson@stericsson.com
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <sound/soc.h>
+#include "../codecs/ab3550.h"
+
+static int ux500_ab3550_startup(struct snd_pcm_substream *substream)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ return 0;
+}
+
+static void ux500_ab3550_shutdown(struct snd_pcm_substream *substream)
+{
+ pr_debug("%s: Enter.\n", __func__);
+}
+
+static int ux500_ab3550_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret = 0;
+
+ int channels = params_channels(params);
+
+ pr_debug("%s: Enter.\n", __func__);
+ pr_debug("%s: substream->pcm->name = %s.\n", __func__, substream->pcm->name);
+ pr_debug("%s: substream->pcm->id = %s.\n", __func__, substream->pcm->id);
+ pr_debug("%s: substream->name = %s.\n", __func__, substream->name);
+ pr_debug("%s: substream->number = %d.\n", __func__, substream->number);
+ pr_debug("%s: channels = %d.\n", __func__, channels);
+ pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id);
+ pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id);
+
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0) {
+ pr_debug("%s: snd_soc_dai_set_fmt failed with %d.\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret < 0) {
+ pr_debug("%s: snd_soc_dai_set_fmt failed with %d.\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+struct snd_soc_ops ux500_ab3550_ops[] = {
+ {
+ .startup = ux500_ab3550_startup,
+ .shutdown = ux500_ab3550_shutdown,
+ .hw_params = ux500_ab3550_hw_params,
+ }
+};
diff --git a/sound/soc/ux500/ux500_ab3550.h b/sound/soc/ux500/ux500_ab3550.h
new file mode 100644
index 00000000000..53ea3902d36
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab3550.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#ifndef UX500_AB3550_H
+#define UX500_AB3550_H
+
+extern struct snd_soc_ops ux500_ab3550_ops[];
+
+#endif
diff --git a/sound/soc/ux500/ux500_ab5500.c b/sound/soc/ux500/ux500_ab5500.c
new file mode 100755
index 00000000000..3a1dab0a990
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab5500.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Ola Lilja ola.o.lilja@stericsson.com,
+ * Roger Nilsson roger.xr.nilsson@stericsson.com
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <sound/soc.h>
+#include <linux/clk.h>
+#include "../codecs/ab5500.h"
+#include "ux500_msp_dai.h"
+
+/* For a workwround purpose we enable sysclk
+ by default this will be changed later */
+static unsigned int sysclk_state = 1;/* Enabled */
+static struct clk *ux500_ab5500_sysclk;
+
+static int sysclk_input_select_control_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int sysclk_input_select_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = sysclk_state;
+ return 0;
+}
+
+static int sysclk_input_select_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ sysclk_state = ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static const struct snd_kcontrol_new sysclk_input_select_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Sysclk Input Select",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = sysclk_input_select_control_info,
+ .get = sysclk_input_select_control_get,
+ .put = sysclk_input_select_control_put
+};
+
+int ux500_ab5500_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ int ret = 0;
+
+ if (sysclk_state == 1) {
+ ret = clk_enable(ux500_ab5500_sysclk);
+ if (ret)
+ dev_err(codec->dev, "failed to enable clock %d\n", ret);
+ }
+
+ return ret;
+}
+
+void ux500_ab5500_shutdown(struct snd_pcm_substream *substream)
+{
+ pr_info("%s: Enter.\n", __func__);
+ if (sysclk_state == 1)
+ clk_disable(ux500_ab5500_sysclk);
+}
+
+int ux500_ab5500_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret = 0;
+
+ int channels = params_channels(params);
+
+ printk(KERN_DEBUG "%s: Enter.\n", __func__);
+ printk(KERN_DEBUG "%s: substream->pcm->name = %s.\n", __func__, substream->pcm->name);
+ printk(KERN_DEBUG "%s: substream->pcm->id = %s.\n", __func__, substream->pcm->id);
+ printk(KERN_DEBUG "%s: substream->name = %s.\n", __func__, substream->name);
+ printk(KERN_DEBUG "%s: substream->number = %d.\n", __func__, substream->number);
+ printk(KERN_DEBUG "%s: channels = %d.\n", __func__, channels);
+ printk(KERN_DEBUG "%s: DAI-index (Codec): %d\n", __func__, codec_dai->id);
+ printk(KERN_DEBUG "%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id);
+
+ ret = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_CBM_CFM |
+ SND_SOC_DAIFMT_NB_NF);
+ if (ret < 0)
+ return ret;
+ ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_1);
+
+ return ret;
+}
+
+int ux500_ab5500_machine_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ int ret = 0;
+
+ snd_ctl_add(codec->card->snd_card,
+ snd_ctl_new1(&sysclk_input_select_control, codec));
+
+ ux500_ab5500_sysclk = clk_get(codec->dev, "sysclk");
+ if (IS_ERR(ux500_ab5500_sysclk)) {
+ dev_err(codec->dev, "could not get sysclk %ld\n",
+ PTR_ERR(ux500_ab5500_sysclk));
+ ret = PTR_ERR(ux500_ab5500_sysclk);
+ }
+
+ return ret;
+}
diff --git a/sound/soc/ux500/ux500_ab5500.h b/sound/soc/ux500/ux500_ab5500.h
new file mode 100755
index 00000000000..8a9be4b98d0
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab5500.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Xie Xiaolei (xie.xiaolei@stericsson.com)
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#ifndef UX500_AB5500_H
+#define UX500_AB5500_H
+
+struct snd_soc_pcm_runtime;
+
+int ux500_ab5500_startup(struct snd_pcm_substream *substream);
+
+void ux500_ab5500_shutdown(struct snd_pcm_substream *substream);
+
+int ux500_ab5500_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params);
+
+int ux500_ab5500_machine_codec_init(struct snd_soc_pcm_runtime *runtime);
+
+#endif
diff --git a/sound/soc/ux500/ux500_ab8500.c b/sound/soc/ux500/ux500_ab8500.c
new file mode 100644
index 00000000000..b3819f88b10
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab8500.c
@@ -0,0 +1,1064 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Mikko J. Lehto <mikko.lehto@symbio.com>,
+ * Mikko Sarmanne <mikko.sarmanne@symbio.com>,
+ * Jarmo K. Kuronen <jarmo.kuronen@symbio.com>.
+ * Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Kristoffer Karlsson <kristoffer.karlsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+#include <mach/hardware.h>
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+#include "../codecs/ab8500_audio.h"
+
+#define TX_SLOT_MONO 0x0008
+#define TX_SLOT_STEREO 0x000a
+#define RX_SLOT_MONO 0x0001
+#define RX_SLOT_STEREO 0x0003
+#define TX_SLOT_8CH 0x00FF
+#define RX_SLOT_8CH 0x00FF
+
+#define DEF_TX_SLOTS TX_SLOT_STEREO
+#define DEF_RX_SLOTS RX_SLOT_MONO
+
+#define DRIVERMODE_NORMAL 0
+#define DRIVERMODE_CODEC_ONLY 1
+
+static struct snd_soc_jack jack;
+static bool vibra_on;
+
+/* Power-control */
+static DEFINE_MUTEX(power_lock);
+static int ab8500_power_count;
+
+/* ADCM-control */
+static DEFINE_MUTEX(adcm_lock);
+#define GPADC_MIN_DELTA_DELAY 500
+#define GPADC_MAX_DELTA_DELAY 1000
+#define GPADC_MAX_VOLT_DIFF 20
+#define GPADC_MAX_ITERATIONS 4
+
+/* Clocks */
+/* audioclk -> intclk -> sysclk/ulpclk */
+static int master_clock_sel;
+static struct clk *clk_ptr_audioclk;
+static struct clk *clk_ptr_intclk;
+static struct clk *clk_ptr_sysclk;
+static struct clk *clk_ptr_ulpclk;
+static struct clk *clk_ptr_gpio1;
+
+static const char * const enum_mclk[] = {
+ "SYSCLK",
+ "ULPCLK"
+};
+static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk);
+
+/* ANC States */
+static const char * const enum_anc_state[] = {
+ "Unconfigured",
+ "Apply FIR and IIR",
+ "FIR and IIR are configured",
+ "Apply FIR",
+ "FIR is configured",
+ "Apply IIR",
+ "IIR is configured",
+ "Error"
+};
+static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_ancstate, enum_anc_state);
+enum anc_state {
+ ANC_UNCONFIGURED = 0,
+ ANC_APPLY_FIR_IIR = 1,
+ ANC_FIR_IIR_CONFIGURED = 2,
+ ANC_APPLY_FIR = 3,
+ ANC_FIR_CONFIGURED = 4,
+ ANC_APPLY_IIR = 5,
+ ANC_IIR_CONFIGURED = 6,
+ ANC_ERROR = 7
+};
+static enum anc_state anc_status = ANC_UNCONFIGURED;
+
+/* Regulators */
+enum regulator_idx {
+ REGULATOR_AUDIO,
+ REGULATOR_DMIC,
+ REGULATOR_AMIC1,
+ REGULATOR_AMIC2
+};
+static struct regulator_bulk_data reg_info[4] = {
+ { .consumer = NULL, .supply = "v-audio" },
+ { .consumer = NULL, .supply = "v-dmic" },
+ { .consumer = NULL, .supply = "v-amic1" },
+ { .consumer = NULL, .supply = "v-amic2" }
+};
+static bool reg_enabled[4] = {
+ false,
+ false,
+ false,
+ false
+};
+static int reg_claim[4];
+enum amic_idx { AMIC_1A, AMIC_1B, AMIC_2 };
+struct amic_conf {
+ enum regulator_idx reg_id;
+ bool enabled;
+ char *name;
+};
+static struct amic_conf amic_info[3] = {
+ { REGULATOR_AMIC1, false, "amic1a" },
+ { REGULATOR_AMIC1, false, "amic1b" },
+ { REGULATOR_AMIC2, false, "amic2" }
+};
+static DEFINE_MUTEX(amic_conf_lock);
+
+static const char *enum_amic_reg_conf[2] = { "v-amic1", "v-amic2" };
+static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_amicconf, enum_amic_reg_conf);
+
+/* Slot configuration */
+static unsigned int tx_slots = DEF_TX_SLOTS;
+static unsigned int rx_slots = DEF_RX_SLOTS;
+
+/* Regulators */
+
+static int enable_regulator(enum regulator_idx idx)
+{
+ int ret;
+
+ if (reg_info[idx].consumer == NULL) {
+ pr_err("%s: Failure to enable regulator '%s'\n",
+ __func__, reg_info[idx].supply);
+ return -EIO;
+ }
+
+ if (reg_enabled[idx])
+ return 0;
+
+ ret = regulator_enable(reg_info[idx].consumer);
+ if (ret != 0) {
+ pr_err("%s: Failure to enable regulator '%s' (ret = %d)\n",
+ __func__, reg_info[idx].supply, ret);
+ return -EIO;
+ };
+
+ reg_enabled[idx] = true;
+ pr_debug("%s: Enabled regulator '%s', status: %d, %d, %d, %d\n",
+ __func__,
+ reg_info[idx].supply,
+ (int)reg_enabled[0],
+ (int)reg_enabled[1],
+ (int)reg_enabled[2],
+ (int)reg_enabled[3]);
+ return 0;
+}
+
+static void disable_regulator(enum regulator_idx idx)
+{
+ if (reg_info[idx].consumer == NULL) {
+ pr_err("%s: Failure to disable regulator '%s'\n",
+ __func__, reg_info[idx].supply);
+ return;
+ }
+
+ if (!reg_enabled[idx])
+ return;
+
+ regulator_disable(reg_info[idx].consumer);
+
+ reg_enabled[idx] = false;
+ pr_debug("%s: Disabled regulator '%s', status: %d, %d, %d, %d\n",
+ __func__,
+ reg_info[idx].supply,
+ (int)reg_enabled[0],
+ (int)reg_enabled[1],
+ (int)reg_enabled[2],
+ (int)reg_enabled[3]);
+}
+
+static int create_regulators(void)
+{
+ int i, status = 0;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ for (i = 0; i < ARRAY_SIZE(reg_info); ++i)
+ reg_info[i].consumer = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(reg_info); ++i) {
+ reg_info[i].consumer = regulator_get(NULL, reg_info[i].supply);
+ if (IS_ERR(reg_info[i].consumer)) {
+ status = PTR_ERR(reg_info[i].consumer);
+ pr_err("%s: ERROR: Failed to get regulator '%s' (ret = %d)!\n",
+ __func__, reg_info[i].supply, status);
+ reg_info[i].consumer = NULL;
+ goto err_get;
+ }
+ }
+
+ return 0;
+
+err_get:
+
+ for (i = 0; i < ARRAY_SIZE(reg_info); ++i) {
+ if (reg_info[i].consumer) {
+ regulator_put(reg_info[i].consumer);
+ reg_info[i].consumer = NULL;
+ }
+ }
+
+ return status;
+}
+
+static int claim_amic_regulator(enum amic_idx amic_id)
+{
+ enum regulator_idx reg_id = amic_info[amic_id].reg_id;
+ int ret = 0;
+
+ reg_claim[reg_id]++;
+ if (reg_claim[reg_id] > 1)
+ goto cleanup;
+
+ ret = enable_regulator(reg_id);
+ if (ret < 0) {
+ pr_err("%s: Failed to claim %s for %s (ret = %d)!",
+ __func__, reg_info[reg_id].supply,
+ amic_info[amic_id].name, ret);
+ reg_claim[reg_id]--;
+ }
+
+cleanup:
+ amic_info[amic_id].enabled = (ret == 0);
+
+ return ret;
+}
+
+static void release_amic_regulator(enum amic_idx amic_id)
+{
+ enum regulator_idx reg_id = amic_info[amic_id].reg_id;
+
+ reg_claim[reg_id]--;
+ if (reg_claim[reg_id] <= 0) {
+ disable_regulator(reg_id);
+ reg_claim[reg_id] = 0;
+ }
+
+ amic_info[amic_id].enabled = false;
+}
+
+/* Power/clock control */
+
+static int ux500_ab8500_power_control_inc(void)
+{
+ int ret = 0;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ mutex_lock(&power_lock);
+
+ ab8500_power_count++;
+ pr_debug("%s: ab8500_power_count changed from %d to %d",
+ __func__,
+ ab8500_power_count-1,
+ ab8500_power_count);
+
+ if (ab8500_power_count == 1) {
+ /* Turn on audio-regulator */
+ ret = enable_regulator(REGULATOR_AUDIO);
+ if (ret < 0)
+ goto out;
+
+ /* Enable audio-clock */
+ ret = clk_set_parent(clk_ptr_intclk,
+ (master_clock_sel == 0) ? clk_ptr_sysclk : clk_ptr_ulpclk);
+ if (ret != 0) {
+ pr_err("%s: ERROR: Setting master-clock to %s failed (ret = %d)!",
+ __func__,
+ (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK",
+ ret);
+ ret = -EIO;
+ goto clk_err;
+ }
+ pr_debug("%s: Enabling master-clock (%s).",
+ __func__,
+ (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK");
+ ret = clk_enable(clk_ptr_audioclk);
+ if (ret != 0) {
+ pr_err("%s: ERROR: clk_enable failed (ret = %d)!", __func__, ret);
+ ret = -EIO;
+ ab8500_power_count = 0;
+ goto clk_err;
+ }
+
+ /* Power on audio-parts of AB8500 */
+ ret = ab8500_audio_power_control(true);
+ }
+
+ goto out;
+
+clk_err:
+ disable_regulator(REGULATOR_AUDIO);
+
+out:
+ mutex_unlock(&power_lock);
+
+ pr_debug("%s: Exit.\n", __func__);
+
+ return ret;
+}
+
+static void ux500_ab8500_power_control_dec(void)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ mutex_lock(&power_lock);
+
+ ab8500_power_count--;
+
+ pr_debug("%s: ab8500_power_count changed from %d to %d",
+ __func__,
+ ab8500_power_count+1,
+ ab8500_power_count);
+
+ if (ab8500_power_count == 0) {
+ /* Power off audio-parts of AB8500 */
+ ab8500_audio_power_control(false);
+
+ /* Disable audio-clock */
+ pr_debug("%s: Disabling master-clock (%s).",
+ __func__,
+ (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK");
+ clk_disable(clk_ptr_audioclk);
+
+ /* Turn off audio-regulator */
+ disable_regulator(REGULATOR_AUDIO);
+ }
+
+ mutex_unlock(&power_lock);
+
+ pr_debug("%s: Exit.\n", __func__);
+}
+
+/* Controls - Non-DAPM Non-ASoC */
+
+static int mclk_input_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = master_clock_sel;
+
+ return 0;
+}
+
+static int mclk_input_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned int val;
+
+ val = (ucontrol->value.enumerated.item[0] != 0);
+ if (master_clock_sel == val)
+ return 0;
+
+ master_clock_sel = val;
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new mclk_input_control = \
+ SOC_ENUM_EXT("Master Clock Select", soc_enum_mclk,
+ mclk_input_control_get, mclk_input_control_put);
+
+static int anc_status_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&codec->mutex);
+ ucontrol->value.integer.value[0] = anc_status;
+ mutex_unlock(&codec->mutex);
+
+ return 0;
+}
+
+static int anc_status_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec;
+ bool apply_fir, apply_iir;
+ int req, ret;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ req = ucontrol->value.integer.value[0];
+ if (req != ANC_APPLY_FIR_IIR && req != ANC_APPLY_FIR &&
+ req != ANC_APPLY_IIR) {
+ pr_err("%s: ERROR: Unsupported status to set '%s'!\n",
+ __func__, enum_anc_state[req]);
+ return -EINVAL;
+ }
+ apply_fir = req == ANC_APPLY_FIR || req == ANC_APPLY_FIR_IIR;
+ apply_iir = req == ANC_APPLY_IIR || req == ANC_APPLY_FIR_IIR;
+
+ codec = snd_kcontrol_chip(kcontrol);
+
+ ret = ux500_ab8500_power_control_inc();
+ if (ret < 0) {
+ pr_err("%s: ERROR: Failed to enable power (ret = %d)!\n",
+ __func__, ret);
+ goto cleanup;
+ }
+
+ mutex_lock(&codec->mutex);
+
+ ab8500_audio_anc_configure(codec, apply_fir, apply_iir);
+
+ if (apply_fir) {
+ if (anc_status == ANC_IIR_CONFIGURED)
+ anc_status = ANC_FIR_IIR_CONFIGURED;
+ else if (anc_status != ANC_FIR_IIR_CONFIGURED)
+ anc_status = ANC_FIR_CONFIGURED;
+ }
+ if (apply_iir) {
+ if (anc_status == ANC_FIR_CONFIGURED)
+ anc_status = ANC_FIR_IIR_CONFIGURED;
+ else if (anc_status != ANC_FIR_IIR_CONFIGURED)
+ anc_status = ANC_IIR_CONFIGURED;
+ }
+
+ mutex_unlock(&codec->mutex);
+
+ ux500_ab8500_power_control_dec();
+
+cleanup:
+ if (ret < 0)
+ pr_err("%s: Unable to configure ANC! (ret = %d)\n",
+ __func__, ret);
+
+ pr_debug("%s: Exit.\n", __func__);
+
+ return (ret < 0) ? ret : 1;
+}
+
+static const struct snd_kcontrol_new anc_status_control = \
+ SOC_ENUM_EXT("ANC Status", soc_enum_ancstate,
+ anc_status_control_get, anc_status_control_put);
+
+static int amic_reg_control_get(struct snd_ctl_elem_value *ucontrol,
+ enum amic_idx amic_id)
+{
+ ucontrol->value.integer.value[0] =
+ (amic_info[amic_id].reg_id == REGULATOR_AMIC2);
+
+ return 0;
+}
+
+static int amic_reg_control_put(struct snd_ctl_elem_value *ucontrol,
+ enum amic_idx amic_id)
+{
+ enum regulator_idx old_reg_id, new_reg_id;
+ int ret = 0;
+
+ if (ucontrol->value.integer.value[0] == 0)
+ new_reg_id = REGULATOR_AMIC1;
+ else
+ new_reg_id = REGULATOR_AMIC2;
+
+ mutex_lock(&amic_conf_lock);
+
+ old_reg_id = amic_info[amic_id].reg_id;
+ if (old_reg_id == new_reg_id)
+ goto cleanup;
+
+ if (!amic_info[amic_id].enabled) {
+ amic_info[amic_id].reg_id = new_reg_id;
+ goto cleanup;
+ }
+
+ release_amic_regulator(amic_id);
+ amic_info[amic_id].reg_id = new_reg_id;
+ ret = claim_amic_regulator(amic_id);
+
+cleanup:
+ mutex_unlock(&amic_conf_lock);
+
+ return (ret < 0) ? 0 : 1;
+}
+
+static int amic1a_reg_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return amic_reg_control_get(ucontrol, AMIC_1A);
+}
+
+static int amic1a_reg_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return amic_reg_control_put(ucontrol, AMIC_1A);
+}
+
+static int amic1b_reg_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return amic_reg_control_get(ucontrol, AMIC_1B);
+}
+
+static int amic1b_reg_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return amic_reg_control_put(ucontrol, AMIC_1B);
+}
+
+static int amic2_reg_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return amic_reg_control_get(ucontrol, AMIC_2);
+}
+
+static int amic2_reg_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return amic_reg_control_put(ucontrol, AMIC_2);
+}
+
+static const struct snd_kcontrol_new mic1a_regulator_control = \
+ SOC_ENUM_EXT("Mic 1A Regulator", soc_enum_amicconf,
+ amic1a_reg_control_get, amic1a_reg_control_put);
+static const struct snd_kcontrol_new mic1b_regulator_control = \
+ SOC_ENUM_EXT("Mic 1B Regulator", soc_enum_amicconf,
+ amic1b_reg_control_get, amic1b_reg_control_put);
+static const struct snd_kcontrol_new mic2_regulator_control = \
+ SOC_ENUM_EXT("Mic 2 Regulator", soc_enum_amicconf,
+ amic2_reg_control_get, amic2_reg_control_put);
+
+/* DAPM-events */
+
+static int dapm_audioreg_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ ux500_ab8500_power_control_inc();
+ else
+ ux500_ab8500_power_control_dec();
+
+ return 0;
+}
+
+static int dapm_amicreg_event(enum amic_idx amic_id, int event)
+{
+ int ret = 0;
+
+ mutex_lock(&amic_conf_lock);
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ ret = claim_amic_regulator(amic_id);
+ else if (amic_info[amic_id].enabled)
+ release_amic_regulator(amic_id);
+
+ mutex_unlock(&amic_conf_lock);
+
+ return ret;
+}
+
+static int dapm_amic1areg_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ return dapm_amicreg_event(AMIC_1A, event);
+}
+
+static int dapm_amic1breg_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ return dapm_amicreg_event(AMIC_1B, event);
+}
+
+static int dapm_amic2reg_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ return dapm_amicreg_event(AMIC_2, event);
+}
+
+static int dapm_dmicreg_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int ret = 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ ret = enable_regulator(REGULATOR_DMIC);
+ else
+ disable_regulator(REGULATOR_DMIC);
+
+ return ret;
+}
+
+/* DAPM-widgets */
+
+static const struct snd_soc_dapm_widget ux500_ab8500_dapm_widgets[] = {
+ SND_SOC_DAPM_SUPPLY("AUDIO Regulator",
+ SND_SOC_NOPM, 0, 0, dapm_audioreg_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("AMIC1A Regulator",
+ SND_SOC_NOPM, 0, 0, dapm_amic1areg_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("AMIC1B Regulator",
+ SND_SOC_NOPM, 0, 0, dapm_amic1breg_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("AMIC2 Regulator",
+ SND_SOC_NOPM, 0, 0, dapm_amic2reg_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("DMIC Regulator",
+ SND_SOC_NOPM, 0, 0, dapm_dmicreg_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+};
+
+/* DAPM-routes */
+
+static const struct snd_soc_dapm_route ux500_ab8500_dapm_intercon[] = {
+
+ /* Power AB8500 audio-block when AD/DA is active */
+ {"DAC", NULL, "AUDIO Regulator"},
+ {"ADC", NULL, "AUDIO Regulator"},
+
+ /* Power AB8500 audio-block when LineIn is active */
+ {"LINL Enable", NULL, "AUDIO Regulator"},
+ {"LINR Enable", NULL, "AUDIO Regulator"},
+
+ /* Power configured regulator when an analog mic is enabled */
+ {"MIC1A Input", NULL, "AMIC1A Regulator"},
+ {"MIC1B Input", NULL, "AMIC1B Regulator"},
+ {"MIC2 Input", NULL, "AMIC2 Regulator"},
+
+ /* Power DMIC-regulator when any digital mic is enabled */
+ {"DMic 1", NULL, "DMIC Regulator"},
+ {"DMic 2", NULL, "DMIC Regulator"},
+ {"DMic 3", NULL, "DMIC Regulator"},
+ {"DMic 4", NULL, "DMIC Regulator"},
+ {"DMic 5", NULL, "DMIC Regulator"},
+ {"DMic 6", NULL, "DMIC Regulator"},
+};
+
+static int add_widgets(struct snd_soc_codec *codec)
+{
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(&codec->dapm,
+ ux500_ab8500_dapm_widgets,
+ ARRAY_SIZE(ux500_ab8500_dapm_widgets));
+ if (ret < 0) {
+ pr_err("%s: Failed to create DAPM controls (%d).\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(&codec->dapm,
+ ux500_ab8500_dapm_intercon,
+ ARRAY_SIZE(ux500_ab8500_dapm_intercon));
+ if (ret < 0) {
+ pr_err("%s: Failed to add DAPM routes (%d).\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* ASoC */
+
+int ux500_ab8500_startup(struct snd_pcm_substream *substream)
+{
+ int ret = 0;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ /* Enable gpio.1-clock (needed by DSP in burst mode) */
+ ret = clk_enable(clk_ptr_gpio1);
+ if (ret) {
+ pr_err("%s: ERROR: clk_enable(gpio.1) failed (ret = %d)!", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+void ux500_ab8500_shutdown(struct snd_pcm_substream *substream)
+{
+ pr_debug("%s: Enter\n", __func__);
+
+ /* Reset slots configuration to default(s) */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tx_slots = DEF_TX_SLOTS;
+ else
+ rx_slots = DEF_RX_SLOTS;
+
+ clk_disable(clk_ptr_gpio1);
+}
+
+int ux500_ab8500_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int fmt, fmt_if1;
+ int channels, ret = 0, slots, slot_width, driver_mode;
+ bool streamIsPlayback;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ pr_debug("%s: substream->pcm->name = %s\n"
+ "substream->pcm->id = %s.\n"
+ "substream->name = %s.\n"
+ "substream->number = %d.\n",
+ __func__,
+ substream->pcm->name,
+ substream->pcm->id,
+ substream->name,
+ substream->number);
+
+ channels = params_channels(params);
+
+ /* Setup codec depending on driver-mode */
+ driver_mode = (channels == 8) ?
+ DRIVERMODE_CODEC_ONLY : DRIVERMODE_NORMAL;
+ pr_debug("%s: Driver-mode: %s.\n",
+ __func__,
+ (driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY");
+
+ ab8500_audio_set_bit_delay(codec_dai, 1);
+
+ if (driver_mode == DRIVERMODE_NORMAL) {
+ ab8500_audio_set_word_length(codec_dai, 16);
+ fmt = SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_CBM_CFM |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CONT;
+ } else {
+ ab8500_audio_set_word_length(codec_dai, 20);
+ fmt = SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_CBM_CFM |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_GATED;
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret < 0) {
+ pr_err("%s: ERROR: snd_soc_dai_set_fmt failed for codec_dai (ret = %d)!\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0) {
+ pr_err("%s: ERROR: snd_soc_dai_set_fmt for cpu_dai (ret = %d)!\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_1);
+
+ /* Setup TDM-slots */
+
+ streamIsPlayback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+ switch (channels) {
+ case 1:
+ slots = 16;
+ slot_width = 16;
+ tx_slots = (streamIsPlayback) ? TX_SLOT_MONO : 0;
+ rx_slots = (streamIsPlayback) ? 0 : RX_SLOT_MONO;
+ break;
+ case 2:
+ slots = 16;
+ slot_width = 16;
+ tx_slots = (streamIsPlayback) ? TX_SLOT_STEREO : 0;
+ rx_slots = (streamIsPlayback) ? 0 : RX_SLOT_STEREO;
+ break;
+ case 8:
+ slots = 16;
+ slot_width = 16;
+ tx_slots = (streamIsPlayback) ? TX_SLOT_8CH : 0;
+ rx_slots = (streamIsPlayback) ? 0 : RX_SLOT_8CH;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pr_debug("%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n",
+ __func__, tx_slots, rx_slots);
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots, slot_width);
+ if (ret)
+ return ret;
+
+ pr_debug("%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n",
+ __func__, tx_slots, rx_slots);
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots, slot_width);
+ if (ret)
+ return ret;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ pr_debug("%s: Setup IF1 for FM-radio.\n", __func__);
+ fmt_if1 = SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_I2S;
+ ret = ab8500_audio_setup_if1(codec_dai->codec, fmt_if1, 16, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+struct snd_soc_ops ux500_ab8500_ops[] = {
+ {
+ .hw_params = ux500_ab8500_hw_params,
+ .startup = ux500_ab8500_startup,
+ .shutdown = ux500_ab8500_shutdown,
+ }
+};
+
+int ux500_ab8500_machine_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ int ret;
+
+ pr_debug("%s Enter.\n", __func__);
+
+ ret = snd_soc_jack_new(codec,
+ "AB8500 Hs Status",
+ SND_JACK_HEADPHONE |
+ SND_JACK_MICROPHONE |
+ SND_JACK_HEADSET |
+ SND_JACK_LINEOUT |
+ SND_JACK_MECHANICAL |
+ SND_JACK_VIDEOOUT,
+ &jack);
+ if (ret < 0) {
+ pr_err("%s: ERROR: Failed to create Jack (ret = %d)!\n", __func__, ret);
+ return ret;
+ }
+
+ /* Add controls */
+ snd_ctl_add(codec->card->snd_card, snd_ctl_new1(
+ &mclk_input_control, codec));
+ snd_ctl_add(codec->card->snd_card, snd_ctl_new1(
+ &anc_status_control, codec));
+ snd_ctl_add(codec->card->snd_card, snd_ctl_new1(
+ &mic1a_regulator_control, codec));
+ snd_ctl_add(codec->card->snd_card, snd_ctl_new1(
+ &mic1b_regulator_control, codec));
+ snd_ctl_add(codec->card->snd_card, snd_ctl_new1(
+ &mic2_regulator_control, codec));
+
+ /* Get references to clock-nodes */
+ clk_ptr_sysclk = NULL;
+ clk_ptr_ulpclk = NULL;
+ clk_ptr_intclk = NULL;
+ clk_ptr_audioclk = NULL;
+ clk_ptr_gpio1 = NULL;
+ clk_ptr_sysclk = clk_get(codec->dev, "sysclk");
+ if (IS_ERR(clk_ptr_sysclk)) {
+ pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT);
+ return -EFAULT;
+ }
+ clk_ptr_ulpclk = clk_get(codec->dev, "ulpclk");
+ if (IS_ERR(clk_ptr_ulpclk)) {
+ pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT);
+ return -EFAULT;
+ }
+ clk_ptr_intclk = clk_get(codec->dev, "intclk");
+ if (IS_ERR(clk_ptr_intclk)) {
+ pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT);
+ return -EFAULT;
+ }
+ clk_ptr_audioclk = clk_get(codec->dev, "audioclk");
+ if (IS_ERR(clk_ptr_audioclk)) {
+ pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT);
+ return -EFAULT;
+ }
+ clk_ptr_gpio1 = clk_get_sys("gpio.1", NULL);
+ if (IS_ERR(clk_ptr_gpio1)) {
+ pr_err("ERROR: clk_get_sys(gpio.1) failed (ret = %d)!", -EFAULT);
+ return -EFAULT;
+ }
+
+ /* Set intclk default parent to ulpclk */
+ ret = clk_set_parent(clk_ptr_intclk, clk_ptr_ulpclk);
+ if (ret) {
+ pr_err("%s: ERROR: Setting intclk parent to ulpclk failed (ret = %d)!",
+ __func__,
+ ret);
+ return -EFAULT;
+ }
+
+ master_clock_sel = 1;
+
+ ab8500_power_count = 0;
+
+ reg_claim[REGULATOR_AMIC1] = 0;
+ reg_claim[REGULATOR_AMIC2] = 0;
+
+ /* Add DAPM-widgets */
+ ret = add_widgets(codec);
+ if (ret < 0) {
+ pr_err("%s: Failed add widgets (%d).\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int ux500_ab8500_soc_machine_drv_init(void)
+{
+ int status = 0;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ status = create_regulators();
+ if (status < 0) {
+ pr_err("%s: ERROR: Failed to instantiate regulators (ret = %d)!\n",
+ __func__, status);
+ return status;
+ }
+
+ vibra_on = false;
+
+ return 0;
+}
+
+void ux500_ab8500_soc_machine_drv_cleanup(void)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ regulator_bulk_free(ARRAY_SIZE(reg_info), reg_info);
+
+ if (clk_ptr_sysclk != NULL)
+ clk_put(clk_ptr_sysclk);
+ if (clk_ptr_ulpclk != NULL)
+ clk_put(clk_ptr_ulpclk);
+ if (clk_ptr_intclk != NULL)
+ clk_put(clk_ptr_intclk);
+ if (clk_ptr_audioclk != NULL)
+ clk_put(clk_ptr_audioclk);
+ if (clk_ptr_gpio1 != NULL)
+ clk_put(clk_ptr_gpio1);
+}
+
+/*
+ * Measures a relative stable voltage from spec. input on spec channel
+ */
+static int gpadc_convert_stable(struct ab8500_gpadc *gpadc,
+ u8 channel, int *value)
+{
+ int i = GPADC_MAX_ITERATIONS;
+ int mv1, mv2, dmv;
+
+ mv1 = ab8500_gpadc_convert(gpadc, channel);
+ do {
+ i--;
+ usleep_range(GPADC_MIN_DELTA_DELAY, GPADC_MAX_DELTA_DELAY);
+ mv2 = ab8500_gpadc_convert(gpadc, channel);
+ dmv = abs(mv2 - mv1);
+ mv1 = mv2;
+ } while (i > 0 && dmv > GPADC_MAX_VOLT_DIFF);
+
+ if (mv1 < 0 || dmv > GPADC_MAX_VOLT_DIFF)
+ return -EIO;
+
+ *value = mv1;
+
+ return 0;
+}
+
+/* Extended interface */
+
+void ux500_ab8500_audio_pwm_vibra(unsigned char speed_left_pos,
+ unsigned char speed_left_neg,
+ unsigned char speed_right_pos,
+ unsigned char speed_right_neg)
+{
+ bool vibra_on_new;
+
+ vibra_on_new = speed_left_pos | speed_left_neg | speed_right_pos | speed_right_neg;
+ if ((!vibra_on_new) && (vibra_on)) {
+ pr_debug("%s: PWM-vibra off.\n", __func__);
+ vibra_on = false;
+
+ ux500_ab8500_power_control_dec();
+ }
+
+ if ((vibra_on_new) && (!vibra_on)) {
+ pr_debug("%s: PWM-vibra on.\n", __func__);
+ vibra_on = true;
+
+ ux500_ab8500_power_control_inc();
+ }
+
+ ab8500_audio_pwm_vibra(speed_left_pos,
+ speed_left_neg,
+ speed_right_pos,
+ speed_right_neg);
+}
+
+int ux500_ab8500_audio_gpadc_measure(struct ab8500_gpadc *gpadc,
+ u8 channel, bool mode, int *value)
+{
+ int ret = 0;
+ int adcm = (mode) ?
+ AB8500_AUDIO_ADCM_FORCE_UP :
+ AB8500_AUDIO_ADCM_FORCE_DOWN;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ mutex_lock(&adcm_lock);
+
+ ret = ux500_ab8500_power_control_inc();
+ if (ret < 0) {
+ pr_err("%s: ERROR: Failed to enable power (ret = %d)!\n",
+ __func__, ret);
+ goto power_failure;
+ }
+
+ ret = ab8500_audio_set_adcm(adcm);
+ if (ret < 0) {
+ pr_err("%s: ERROR: Failed to force adcm %s (ret = %d)!\n",
+ __func__, (mode) ? "UP" : "DOWN", ret);
+ goto adcm_failure;
+ }
+
+ ret = gpadc_convert_stable(gpadc, channel, value);
+ ret |= ab8500_audio_set_adcm(AB8500_AUDIO_ADCM_NORMAL);
+
+adcm_failure:
+ ux500_ab8500_power_control_dec();
+
+power_failure:
+ mutex_unlock(&adcm_lock);
+
+ pr_debug("%s: Exit.\n", __func__);
+
+ return ret;
+}
+
+void ux500_ab8500_jack_report(int value)
+{
+ if (jack.jack)
+ snd_soc_jack_report(&jack, value, 0xFF);
+}
+EXPORT_SYMBOL_GPL(ux500_ab8500_jack_report);
+
diff --git a/sound/soc/ux500/ux500_av8100.c b/sound/soc/ux500/ux500_av8100.c
new file mode 100644
index 00000000000..4ccc7531f4b
--- /dev/null
+++ b/sound/soc/ux500/ux500_av8100.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <sound/soc.h>
+#include "../codecs/av8100_audio.h"
+#include "ux500_av8100.h"
+#include "ux500_msp_dai.h"
+
+static const char *stream_str(struct snd_pcm_substream *substream)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return "Playback";
+ else
+ return "Capture";
+}
+
+static int ux500_av8100_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int channels = params_channels(params);
+ unsigned int tx_mask, fmt;
+ enum hdmi_channel_allocation hdmi_ca;
+ enum hdmi_audio_channel_count hdmi_cc;
+ struct hdmi_audio_settings as;
+ int ret;
+
+ pr_debug("%s: Enter (%s).\n", __func__, stream_str(substream));
+ pr_debug("%s: substream->pcm->name = %s.\n", __func__, substream->pcm->name);
+ pr_debug("%s: substream->pcm->id = %s.\n", __func__, substream->pcm->id);
+ pr_debug("%s: substream->name = %s.\n", __func__, substream->name);
+ pr_debug("%s: substream->number = %d.\n", __func__, substream->number);
+ pr_debug("%s: channels = %d.\n", __func__, channels);
+ pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id);
+ pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id);
+
+ switch (channels) {
+ case 1:
+ hdmi_cc = AV8100_CODEC_CC_2CH;
+ hdmi_ca = AV8100_CODEC_CA_FL_FR; /* Stereo-setup */
+ tx_mask = AV8100_CODEC_MASK_MONO;
+ break;
+ case 2:
+ hdmi_cc = AV8100_CODEC_CC_2CH;
+ hdmi_ca = AV8100_CODEC_CA_FL_FR; /* Stereo */
+ tx_mask = AV8100_CODEC_MASK_STEREO;
+ break;
+ case 3:
+ hdmi_cc = AV8100_CODEC_CC_6CH;
+ hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1-setup */
+ tx_mask = AV8100_CODEC_MASK_2DOT1;
+ break;
+ case 4:
+ hdmi_cc = AV8100_CODEC_CC_6CH;
+ hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1-setup */
+ tx_mask = AV8100_CODEC_MASK_QUAD;
+ break;
+ case 5:
+ hdmi_cc = AV8100_CODEC_CC_6CH;
+ hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1-setup */
+ tx_mask = AV8100_CODEC_MASK_5DOT0;
+ break;
+ case 6:
+ hdmi_cc = AV8100_CODEC_CC_6CH;
+ hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1 */
+ tx_mask = AV8100_CODEC_MASK_5DOT1;
+ break;
+ case 7:
+ hdmi_cc = AV8100_CODEC_CC_8CH;
+ hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR_RLC_RRC; /* 7.1 */
+ tx_mask = AV8100_CODEC_MASK_7DOT0;
+ break;
+ case 8:
+ hdmi_cc = AV8100_CODEC_CC_8CH;
+ hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR_RLC_RRC; /* 7.1 */
+ tx_mask = AV8100_CODEC_MASK_7DOT1;
+ break;
+ default:
+ pr_err("%s: Unsupported number of channels (channels = %d)!\n",
+ __func__,
+ channels);
+ return -EINVAL;
+ }
+
+ /* Change HDMI audio-settings for codec-DAI. */
+ pr_debug("%s: Change HDMI audio-settings for codec-DAI.\n", __func__);
+ as.audio_channel_count = hdmi_cc;
+ as.sampling_frequency = AV8100_CODEC_SF_REFER;
+ as.sample_size = AV8100_CODEC_SS_REFER;
+ as.channel_allocation = hdmi_ca;
+ as.level_shift_value = AV8100_CODEC_LSV_0DB;
+ as.downmix_inhibit = false;
+ ret = av8100_audio_change_hdmi_audio_settings(codec_dai, &as);
+ if (ret < 0) {
+ pr_err("%s: Unable to change HDMI audio-settings for codec-DAI "
+ "(av8100_codec_change_hdmi_audio_settings returned %d)!\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ /* Set format for codec-DAI */
+ fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM;
+ pr_debug("%s: Setting format for codec-DAI (fmt = %d).\n",
+ __func__,
+ fmt);
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret < 0) {
+ pr_err("%s: Unable to set format for codec-DAI "
+ "(snd_soc_dai_set_tdm_slot returned %d)!\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ /* Set TDM-slot for CPU-DAI */
+ pr_debug("%s: Setting TDM-slot for codec-DAI (tx_mask = %d).\n",
+ __func__,
+ tx_mask);
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_mask, 0, 16, 16);
+ if (ret < 0) {
+ pr_err("%s: Unable to set TDM-slot for codec-DAI "
+ "(snd_soc_dai_set_tdm_slot returned %d)!\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ /* Set format for CPU-DAI */
+ fmt = SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_CBM_CFM |
+ SND_SOC_DAIFMT_NB_IF;
+ pr_debug("%s: Setting DAI-format for Ux500-platform (fmt = %d).\n",
+ __func__,
+ fmt);
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0) {
+ pr_err("%s: Unable to set DAI-format for Ux500-platform "
+ "(snd_soc_dai_set_fmt returned %d).\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_1);
+
+ return ret;
+}
+
+struct snd_soc_ops ux500_av8100_ops[] = {
+ {
+ .hw_params = ux500_av8100_hw_params,
+ }
+};
+
diff --git a/sound/soc/ux500/ux500_av8100.h b/sound/soc/ux500/ux500_av8100.h
new file mode 100644
index 00000000000..b107b2e1be7
--- /dev/null
+++ b/sound/soc/ux500/ux500_av8100.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#ifndef UX500_AV8100_H
+#define UX500_AV8100_H
+
+extern struct snd_soc_ops ux500_av8100_ops[];
+
+#endif
diff --git a/sound/soc/ux500/ux500_cg29xx.c b/sound/soc/ux500/ux500_cg29xx.c
new file mode 100644
index 00000000000..456262c6c0b
--- /dev/null
+++ b/sound/soc/ux500/ux500_cg29xx.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Roger Nilsson <roger.xr.nilsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <sound/soc.h>
+#include "../codecs/cg29xx.h"
+#include "ux500_msp_dai.h"
+
+#define UX500_CG29XX_MSP_CLOCK_FREQ 18900000
+#define U5500_CG29XX_MSP_CLOCK_FREQ 13000000
+#define UX500_CG29XX_DAI_SLOT_WIDTH 16
+#define UX500_CG29XX_DAI_SLOTS 2
+#define UX500_CG29XX_DAI_ACTIVE_SLOTS 0x02
+
+int ux500_cg29xx_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int channels = params_channels(params);
+ int err;
+
+ pr_debug("%s: Enter.\n", __func__);
+ pr_debug("%s: substream->pcm->name = %s.\n", __func__, substream->pcm->name);
+ pr_debug("%s: substream->pcm->id = %s.\n", __func__, substream->pcm->id);
+ pr_debug("%s: substream->name = %s.\n", __func__, substream->name);
+ pr_debug("%s: substream->number = %d.\n", __func__, substream->number);
+ pr_debug("%s: channels = %d.\n", __func__, channels);
+ pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id);
+ pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id);
+
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS);
+
+ if (err) {
+ pr_err("%s: snd_soc_dai_set_fmt(codec) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_tdm_slot(codec_dai,
+ 1 << CG29XX_DAI_SLOT0_SHIFT,
+ 1 << CG29XX_DAI_SLOT0_SHIFT,
+ UX500_CG29XX_DAI_SLOTS,
+ UX500_CG29XX_DAI_SLOT_WIDTH);
+
+ if (err) {
+ pr_err("%s: cg29xx_set_tdm_slot(codec_dai) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS |
+ SND_SOC_DAIFMT_NB_NF);
+
+ if (err) {
+ pr_err("%s: snd_soc_dai_set_fmt(cpu_dai) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_sysclk(cpu_dai,
+ UX500_MSP_MASTER_CLOCK,
+ UX500_CG29XX_MSP_CLOCK_FREQ,
+ 0);
+
+ if (err) {
+ pr_err("%s: snd_soc_dai_set_sysclk(cpu_dai) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_tdm_slot(cpu_dai,
+ UX500_CG29XX_DAI_ACTIVE_SLOTS,
+ UX500_CG29XX_DAI_ACTIVE_SLOTS,
+ UX500_CG29XX_DAI_SLOTS,
+ UX500_CG29XX_DAI_SLOT_WIDTH);
+
+ if (err) {
+ pr_err("%s: cg29xx_set_tdm_slot(cpu_dai) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+out_err:
+ return err;
+}
+
+int u5500_cg29xx_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int channels = params_channels(params);
+ int err;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int dai_id = codec_dai->id;
+ struct cg29xx_codec_dai_data *codec_drvdata =
+ snd_soc_codec_get_drvdata(codec);
+ struct cg29xx_codec_dai_data *dai_data = &codec_drvdata[dai_id];
+
+ pr_debug("%s: Enter.\n", __func__);
+ pr_debug("%s: substream->pcm->name=%s\n",
+ __func__, substream->pcm->name);
+ pr_debug("%s: substream->pcm->id = %s\n", __func__, substream->pcm->id);
+ pr_debug("%s: substream->name = %s.\n", __func__, substream->name);
+ pr_debug("%s: substream->number = %d.\n", __func__, substream->number);
+ pr_debug("%s: channels = %d.\n", __func__, channels);
+ pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id);
+ pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id);
+
+ if (dai_data->config.port == PORT_0_I2S) {
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
+ if (err) {
+ pr_err("%s: snd_soc_dai_set_fmt (codec) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
+
+ if (err) {
+ pr_err("%s: snd_soc_dai_set_sysclk(cpu_dai) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+ } else {
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS);
+ if (err) {
+ pr_err("%s: snd_soc_dai_set_fmt(codec) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_tdm_slot(codec_dai,
+ 1 << CG29XX_DAI_SLOT0_SHIFT,
+ 1 << CG29XX_DAI_SLOT0_SHIFT,
+ UX500_CG29XX_DAI_SLOTS,
+ UX500_CG29XX_DAI_SLOT_WIDTH);
+
+ if (err) {
+ pr_err("%s: cg29xx_set_tdm_slot(codec_dai) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_DSP_B |
+ SND_SOC_DAIFMT_CBS_CFS |
+ SND_SOC_DAIFMT_NB_NF);
+
+ if (err) {
+ pr_err("%s: snd_soc_dai_set_fmt(cpu_dai) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_sysclk(cpu_dai,
+ UX500_MSP_MASTER_CLOCK,
+ U5500_CG29XX_MSP_CLOCK_FREQ,
+ 0);
+
+ if (err) {
+ pr_err("%s: snd_soc_dai_set_sysclk(cpu_dai) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_tdm_slot(cpu_dai,
+ UX500_CG29XX_DAI_ACTIVE_SLOTS,
+ UX500_CG29XX_DAI_ACTIVE_SLOTS,
+ UX500_CG29XX_DAI_SLOTS,
+ UX500_CG29XX_DAI_SLOT_WIDTH);
+
+ if (err) {
+ pr_err("%s: cg29xx_set_tdm_slot(cpu_dai) failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ }
+ ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_0);
+out_err:
+ return err;
+}
+
+struct snd_soc_ops ux500_cg29xx_ops[] = {
+ {
+ .hw_params = ux500_cg29xx_hw_params,
+ }
+};
+
+struct snd_soc_ops u5500_cg29xx_ops[] = {
+ {
+ .hw_params = u5500_cg29xx_hw_params,
+ }
+};
+
diff --git a/sound/soc/ux500/ux500_cg29xx.h b/sound/soc/ux500/ux500_cg29xx.h
new file mode 100644
index 00000000000..33736bca0cd
--- /dev/null
+++ b/sound/soc/ux500/ux500_cg29xx.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#ifndef UX500_CG29XX_H
+#define UX500_CG29XX_H
+
+extern struct snd_soc_ops ux500_cg29xx_ops[];
+extern struct snd_soc_ops u5500_cg29xx_ops[];
+
+#endif
diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c
new file mode 100644
index 00000000000..5eabb0629d7
--- /dev/null
+++ b/sound/soc/ux500/ux500_msp_dai.c
@@ -0,0 +1,947 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Roger Nilsson <roger.xr.nilsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+
+#include <mach/hardware.h>
+#include <mach/msp.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include "ux500_msp_i2s.h"
+#include "ux500_msp_dai.h"
+#include "ux500_pcm.h"
+
+static struct ux500_platform_drvdata platform_drvdata[UX500_NBR_OF_DAI] = {
+ {
+ .msp_i2s_drvdata = NULL,
+ .fmt = 0,
+ .slots = 1,
+ .tx_mask = 0x01,
+ .rx_mask = 0x01,
+ .slot_width = 16,
+ .playback_active = false,
+ .capture_active = false,
+ .configured = 0,
+ .data_delay = MSP_DELAY_0,
+ .master_clk = UX500_MSP_INTERNAL_CLOCK_FREQ,
+ },
+ {
+ .msp_i2s_drvdata = NULL,
+ .fmt = 0,
+ .slots = 1,
+ .tx_mask = 0x01,
+ .rx_mask = 0x01,
+ .slot_width = 16,
+ .playback_active = false,
+ .capture_active = false,
+ .configured = 0,
+ .data_delay = MSP_DELAY_0,
+ .master_clk = UX500_MSP1_INTERNAL_CLOCK_FREQ,
+ },
+ {
+ .msp_i2s_drvdata = NULL,
+ .fmt = 0,
+ .slots = 1,
+ .tx_mask = 0x01,
+ .rx_mask = 0x01,
+ .slot_width = 16,
+ .playback_active = false,
+ .capture_active = false,
+ .configured = 0,
+ .data_delay = MSP_DELAY_0,
+ .master_clk = UX500_MSP_INTERNAL_CLOCK_FREQ,
+ },
+ {
+ .msp_i2s_drvdata = NULL,
+ .fmt = 0,
+ .slots = 1,
+ .tx_mask = 0x01,
+ .rx_mask = 0x01,
+ .slot_width = 16,
+ .playback_active = false,
+ .capture_active = false,
+ .configured = 0,
+ .data_delay = MSP_DELAY_0,
+ .master_clk = UX500_MSP1_INTERNAL_CLOCK_FREQ,
+ },
+};
+
+static const char *stream_str(struct snd_pcm_substream *substream)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return "Playback";
+ else
+ return "Capture";
+}
+
+static int ux500_msp_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id];
+ bool mode_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+
+ pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream));
+
+ if ((mode_playback && drvdata->playback_active) ||
+ (!mode_playback && drvdata->capture_active)) {
+ pr_err("%s: Error: MSP %d (%s): Stream already active.\n",
+ __func__,
+ dai->id,
+ stream_str(substream));
+ return -EBUSY;
+ }
+
+ if (mode_playback)
+ drvdata->playback_active = true;
+ else
+ drvdata->capture_active = true;
+
+ return 0;
+}
+
+static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id];
+ bool mode_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+
+ pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream));
+
+ if (drvdata == NULL)
+ return;
+
+ if (ux500_msp_i2s_close(drvdata->msp_i2s_drvdata,
+ mode_playback ? DISABLE_TRANSMIT : DISABLE_RECEIVE)) {
+ pr_err("%s: Error: MSP %d (%s): Unable to close i2s.\n",
+ __func__,
+ dai->id,
+ stream_str(substream));
+ }
+
+ if (mode_playback) {
+ drvdata->playback_active = false;
+ drvdata->configured &= ~PLAYBACK_CONFIGURED;
+ } else {
+ drvdata->capture_active = false;
+ drvdata->configured &= ~CAPTURE_CONFIGURED;
+ }
+}
+
+static void ux500_msp_dai_setup_multichannel(struct ux500_platform_drvdata *private,
+ struct msp_config *msp_config)
+{
+ struct msp_multichannel_config *multi = &msp_config->multichannel_config;
+
+ if (private->slots > 1) {
+ msp_config->multichannel_configured = 1;
+
+ multi->tx_multichannel_enable = true;
+ multi->rx_multichannel_enable = true;
+ multi->rx_comparison_enable_mode = MSP_COMPARISON_DISABLED;
+
+ multi->tx_channel_0_enable = private->tx_mask;
+ multi->tx_channel_1_enable = 0;
+ multi->tx_channel_2_enable = 0;
+ multi->tx_channel_3_enable = 0;
+
+ multi->rx_channel_0_enable = private->rx_mask;
+ multi->rx_channel_1_enable = 0;
+ multi->rx_channel_2_enable = 0;
+ multi->rx_channel_3_enable = 0;
+
+ pr_debug("%s: Multichannel enabled."
+ "Slots: %d TX: %u RX: %u\n",
+ __func__,
+ private->slots,
+ multi->tx_channel_0_enable,
+ multi->rx_channel_0_enable);
+ }
+}
+
+static void ux500_msp_dai_setup_frameper(struct ux500_platform_drvdata *private,
+ unsigned int rate,
+ struct msp_protocol_desc *prot_desc)
+{
+ switch (private->slots) {
+ default:
+ case 1:
+ switch (rate) {
+ case 8000:
+ prot_desc->frame_period =
+ FRAME_PER_SINGLE_SLOT_8_KHZ;
+ break;
+ case 16000:
+ prot_desc->frame_period =
+ FRAME_PER_SINGLE_SLOT_16_KHZ;
+ break;
+ case 44100:
+ prot_desc->frame_period =
+ FRAME_PER_SINGLE_SLOT_44_1_KHZ;
+ break;
+ case 48000:
+ default:
+ prot_desc->frame_period =
+ FRAME_PER_SINGLE_SLOT_48_KHZ;
+ break;
+ }
+ break;
+
+ case 2:
+ prot_desc->frame_period = FRAME_PER_2_SLOTS;
+ break;
+
+ case 8:
+ prot_desc->frame_period =
+ FRAME_PER_8_SLOTS;
+ break;
+
+ case 16:
+ prot_desc->frame_period =
+ FRAME_PER_16_SLOTS;
+ break;
+ }
+
+ prot_desc->total_clocks_for_one_frame =
+ prot_desc->frame_period+1;
+
+ pr_debug("%s: Total clocks per frame: %u\n",
+ __func__,
+ prot_desc->total_clocks_for_one_frame);
+}
+
+static void ux500_msp_dai_setup_framing_pcm(struct ux500_platform_drvdata *private,
+ unsigned int rate,
+ struct msp_protocol_desc *prot_desc)
+{
+ u32 frame_length = MSP_FRAME_LENGTH_1;
+ prot_desc->frame_width = 0;
+
+ switch (private->slots) {
+ default:
+ case 1:
+ frame_length = MSP_FRAME_LENGTH_1;
+ break;
+
+ case 2:
+ frame_length = MSP_FRAME_LENGTH_2;
+ break;
+
+ case 8:
+ frame_length = MSP_FRAME_LENGTH_8;
+ break;
+
+ case 16:
+ frame_length = MSP_FRAME_LENGTH_16;
+ break;
+ }
+
+ prot_desc->tx_frame_length_1 = frame_length;
+ prot_desc->rx_frame_length_1 = frame_length;
+ prot_desc->tx_frame_length_2 = frame_length;
+ prot_desc->rx_frame_length_2 = frame_length;
+
+ prot_desc->tx_element_length_1 = MSP_ELEM_LENGTH_16;
+ prot_desc->rx_element_length_1 = MSP_ELEM_LENGTH_16;
+ prot_desc->tx_element_length_2 = MSP_ELEM_LENGTH_16;
+ prot_desc->rx_element_length_2 = MSP_ELEM_LENGTH_16;
+
+ ux500_msp_dai_setup_frameper(private, rate, prot_desc);
+}
+
+static void ux500_msp_dai_setup_clocking(unsigned int fmt,
+ struct msp_config *msp_config)
+{
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ default:
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+
+ case SND_SOC_DAIFMT_NB_IF:
+ msp_config->tx_frame_sync_pol ^= 1 << TFSPOL_SHIFT;
+ msp_config->rx_frame_sync_pol ^= 1 << RFSPOL_SHIFT;
+ break;
+ }
+
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) {
+ pr_debug("%s: Codec is MASTER.\n",
+ __func__);
+ msp_config->iodelay = 0x20;
+ msp_config->rx_frame_sync_sel = 0;
+ msp_config->tx_frame_sync_sel = 1 << TFSSEL_SHIFT;
+ msp_config->tx_clock_sel = 0;
+ msp_config->rx_clock_sel = 0;
+ msp_config->srg_clock_sel = 0x2 << SCKSEL_SHIFT;
+
+ } else {
+ pr_debug("%s: Codec is SLAVE.\n",
+ __func__);
+
+ msp_config->tx_clock_sel = TX_CLK_SEL_SRG;
+ msp_config->tx_frame_sync_sel = TX_SYNC_SRG_PROG;
+ msp_config->rx_clock_sel = RX_CLK_SEL_SRG;
+ msp_config->rx_frame_sync_sel = RX_SYNC_SRG;
+ msp_config->srg_clock_sel = 1 << SCKSEL_SHIFT;
+ }
+}
+
+static void ux500_msp_dai_compile_prot_desc_pcm(unsigned int fmt,
+ struct msp_protocol_desc *prot_desc)
+{
+ prot_desc->rx_phase_mode = MSP_SINGLE_PHASE;
+ prot_desc->tx_phase_mode = MSP_SINGLE_PHASE;
+ prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE;
+ prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE;
+ prot_desc->rx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ prot_desc->tx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ prot_desc->tx_frame_sync_pol = MSP_FRAME_SYNC_POL(MSP_FRAME_SYNC_POL_ACTIVE_HIGH);
+ prot_desc->rx_frame_sync_pol = MSP_FRAME_SYNC_POL_ACTIVE_HIGH << RFSPOL_SHIFT;
+
+ if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) {
+ pr_debug("%s: DSP_A.\n",
+ __func__);
+ prot_desc->rx_clock_pol = MSP_RISING_EDGE;
+ prot_desc->tx_clock_pol = MSP_FALLING_EDGE;
+ } else {
+ pr_debug("%s: DSP_B.\n",
+ __func__);
+ prot_desc->rx_clock_pol = MSP_FALLING_EDGE;
+ prot_desc->tx_clock_pol = MSP_RISING_EDGE;
+ }
+
+ prot_desc->rx_half_word_swap = MSP_HWS_NO_SWAP;
+ prot_desc->tx_half_word_swap = MSP_HWS_NO_SWAP;
+ prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR;
+ prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR;
+ prot_desc->spi_clk_mode = MSP_SPI_CLOCK_MODE_NON_SPI;
+ prot_desc->spi_burst_mode = MSP_SPI_BURST_MODE_DISABLE;
+ prot_desc->frame_sync_ignore = MSP_FRAME_SYNC_IGNORE;
+}
+
+static void ux500_msp_dai_compile_prot_desc_i2s(struct msp_protocol_desc *prot_desc)
+{
+ prot_desc->rx_phase_mode = MSP_DUAL_PHASE;
+ prot_desc->tx_phase_mode = MSP_DUAL_PHASE;
+ prot_desc->rx_phase2_start_mode =
+ MSP_PHASE2_START_MODE_FRAME_SYNC;
+ prot_desc->tx_phase2_start_mode =
+ MSP_PHASE2_START_MODE_FRAME_SYNC;
+ prot_desc->rx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ prot_desc->tx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ prot_desc->tx_frame_sync_pol = MSP_FRAME_SYNC_POL(MSP_FRAME_SYNC_POL_ACTIVE_LOW);
+ prot_desc->rx_frame_sync_pol = MSP_FRAME_SYNC_POL_ACTIVE_LOW << RFSPOL_SHIFT;
+
+ prot_desc->rx_frame_length_1 = MSP_FRAME_LENGTH_1;
+ prot_desc->rx_frame_length_2 = MSP_FRAME_LENGTH_1;
+ prot_desc->tx_frame_length_1 = MSP_FRAME_LENGTH_1;
+ prot_desc->tx_frame_length_2 = MSP_FRAME_LENGTH_1;
+ prot_desc->rx_element_length_1 = MSP_ELEM_LENGTH_16;
+ prot_desc->rx_element_length_2 = MSP_ELEM_LENGTH_16;
+ prot_desc->tx_element_length_1 = MSP_ELEM_LENGTH_16;
+ prot_desc->tx_element_length_2 = MSP_ELEM_LENGTH_16;
+
+ prot_desc->rx_clock_pol = MSP_RISING_EDGE;
+ prot_desc->tx_clock_pol = MSP_FALLING_EDGE;
+
+ prot_desc->tx_half_word_swap = MSP_HWS_NO_SWAP;
+ prot_desc->rx_half_word_swap = MSP_HWS_NO_SWAP;
+ prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR;
+ prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR;
+ prot_desc->spi_clk_mode = MSP_SPI_CLOCK_MODE_NON_SPI;
+ prot_desc->spi_burst_mode = MSP_SPI_BURST_MODE_DISABLE;
+ prot_desc->frame_sync_ignore = MSP_FRAME_SYNC_IGNORE;
+}
+
+static void ux500_msp_dai_compile_msp_config(struct snd_pcm_substream *substream,
+ struct ux500_platform_drvdata *private,
+ unsigned int rate,
+ struct msp_config *msp_config)
+{
+ struct msp_protocol_desc *prot_desc = &msp_config->protocol_desc;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int fmt = private->fmt;
+
+ memset(msp_config, 0, sizeof(*msp_config));
+
+ msp_config->input_clock_freq = private->master_clk;
+
+ msp_config->tx_fifo_config = TX_FIFO_ENABLE;
+ msp_config->rx_fifo_config = RX_FIFO_ENABLE;
+ msp_config->spi_clk_mode = SPI_CLK_MODE_NORMAL;
+ msp_config->spi_burst_mode = 0;
+ msp_config->def_elem_len = 1;
+ msp_config->direction =
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ MSP_TRANSMIT_MODE : MSP_RECEIVE_MODE;
+ msp_config->data_size = MSP_DATA_BITS_32;
+ msp_config->work_mode = MSP_DMA_MODE;
+ msp_config->frame_freq = rate;
+
+ pr_debug("%s: input_clock_freq = %u, frame_freq = %u.\n",
+ __func__, msp_config->input_clock_freq, msp_config->frame_freq);
+ /* To avoid division by zero in I2S-driver (i2s_setup) */
+ prot_desc->total_clocks_for_one_frame = 1;
+
+ prot_desc->rx_data_delay = private->data_delay;
+ prot_desc->tx_data_delay = private->data_delay;
+
+ pr_debug("%s: rate: %u channels: %d.\n",
+ __func__,
+ rate,
+ runtime->channels);
+ switch (fmt &
+ (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+ pr_debug("%s: SND_SOC_DAIFMT_I2S.\n",
+ __func__);
+
+ msp_config->default_protocol_desc = 1;
+ msp_config->protocol = MSP_I2S_PROTOCOL;
+ break;
+
+ default:
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
+ pr_debug("%s: SND_SOC_DAIFMT_I2S.\n",
+ __func__);
+
+ msp_config->data_size = MSP_DATA_BITS_16;
+ msp_config->protocol = MSP_I2S_PROTOCOL;
+
+ ux500_msp_dai_compile_prot_desc_i2s(prot_desc);
+ break;
+
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM:
+ pr_debug("%s: PCM format.\n",
+ __func__);
+ msp_config->data_size = MSP_DATA_BITS_16;
+ msp_config->protocol = MSP_PCM_PROTOCOL;
+
+ ux500_msp_dai_compile_prot_desc_pcm(fmt, prot_desc);
+ ux500_msp_dai_setup_multichannel(private, msp_config);
+ ux500_msp_dai_setup_framing_pcm(private, rate, prot_desc);
+ break;
+ }
+
+ ux500_msp_dai_setup_clocking(fmt, msp_config);
+}
+
+static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+ struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id];
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct msp_config msp_config;
+ bool mode_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+ u8 configflag = mode_playback ? PLAYBACK_CONFIGURED : CAPTURE_CONFIGURED;
+
+ pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream));
+
+ if (configflag & drvdata->configured) {
+
+ ret = ux500_msp_i2s_close(
+ drvdata->msp_i2s_drvdata,
+ mode_playback ? DISABLE_TRANSMIT : DISABLE_RECEIVE);
+
+ if (ret) {
+ pr_err("%s: Error: MSP %d (%s): Unable to close i2s.\n",
+ __func__,
+ dai->id,
+ stream_str(substream));
+ goto cleanup;
+ }
+
+ drvdata->configured &= ~configflag;
+ }
+
+ pr_debug("%s: Setup dai (Rate: %u).\n", __func__, runtime->rate);
+ ux500_msp_dai_compile_msp_config(substream,
+ drvdata,
+ runtime->rate,
+ &msp_config);
+
+ ret = ux500_msp_i2s_open(drvdata->msp_i2s_drvdata, &msp_config);
+ if (ret < 0) {
+ pr_err("%s: Error: msp_setup failed (ret = %d)!\n", __func__, ret);
+ goto cleanup;
+ }
+
+ drvdata->configured |= configflag;
+
+cleanup:
+ return ret;
+}
+
+static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ unsigned int mask, slots_active;
+ struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id];
+
+ pr_debug("%s: MSP %d (%s): Enter.\n",
+ __func__,
+ dai->id,
+ stream_str(substream));
+
+ switch (drvdata->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ if (params_channels(params) != 2) {
+ pr_err("%s: Error: I2S requires channels = 2 "
+ "(channels = %d)!\n",
+ __func__,
+ params_channels(params));
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ case SND_SOC_DAIFMT_DSP_A:
+
+ mask = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ drvdata->tx_mask :
+ drvdata->rx_mask;
+
+ slots_active = hweight32(mask);
+
+ pr_debug("TDM slots active: %d", slots_active);
+
+ if (params_channels(params) != slots_active) {
+ pr_err("%s: Error: PCM TDM format requires channels "
+ "to match active slots "
+ "(channels = %d, active slots = %d)!\n",
+ __func__,
+ params_channels(params),
+ slots_active);
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay)
+{
+ struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id];
+
+ pr_debug("%s: MSP %d: Enter.\n", __func__, dai->id);
+
+ switch (delay) {
+ case MSP_DELAY_0:
+ case MSP_DELAY_1:
+ case MSP_DELAY_2:
+ case MSP_DELAY_3:
+ break;
+ default:
+ goto unsupported_delay;
+ }
+
+ drvdata->data_delay = delay;
+ return 0;
+
+unsupported_delay:
+ pr_err("%s: MSP %d: Error: Unsupported DAI delay (%d)!\n",
+ __func__,
+ dai->id,
+ delay);
+ return -EINVAL;
+}
+
+static int ux500_msp_dai_set_dai_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id];
+
+ pr_debug("%s: MSP %d: Enter.\n", __func__, dai->id);
+
+ switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
+ break;
+
+ default:
+ goto unsupported_format;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ case SND_SOC_DAIFMT_NB_IF:
+ case SND_SOC_DAIFMT_IB_IF:
+ break;
+
+ default:
+ goto unsupported_format;
+ }
+
+ drvdata->fmt = fmt;
+ return 0;
+
+unsupported_format:
+ pr_err("%s: MSP %d: Error: Unsupported DAI format (0x%x)!\n",
+ __func__,
+ dai->id,
+ fmt);
+ return -EINVAL;
+}
+
+static int ux500_msp_dai_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask,
+ unsigned int rx_mask,
+ int slots,
+ int slot_width)
+{
+ struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id];
+ unsigned int cap;
+
+ if (!(slots == 1 || slots == 2 || slots == 8 || slots == 16)) {
+ pr_err("%s: Error: Unsupported slots (%d)! "
+ "Supported values are 1/2/8/16.\n",
+ __func__,
+ slots);
+ return -EINVAL;
+ }
+ drvdata->slots = slots;
+
+ if (!(slot_width == 16)) {
+ pr_err("%s: Error: Unsupported slots_width (%d)!. "
+ "Supported value is 16.\n",
+ __func__,
+ slot_width);
+ return -EINVAL;
+ }
+ drvdata->slot_width = slot_width;
+
+ switch (slots) {
+ default:
+ case 1:
+ cap = 0x01;
+ break;
+ case 2:
+ cap = 0x03;
+ break;
+ case 8:
+ cap = 0xFF;
+ break;
+ case 16:
+ cap = 0xFFFF;
+ break;
+ }
+
+ drvdata->tx_mask = tx_mask & cap;
+ drvdata->rx_mask = rx_mask & cap;
+
+ return 0;
+}
+
+static int ux500_msp_dai_set_dai_sysclk(struct snd_soc_dai *dai,
+ int clk_id,
+ unsigned int freq,
+ int dir)
+{
+ struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id];
+
+ pr_debug("%s: MSP %d: Enter. Clk id: %d, freq: %u.\n",
+ __func__,
+ dai->id,
+ clk_id,
+ freq);
+
+ switch (clk_id) {
+ case UX500_MSP_MASTER_CLOCK:
+ drvdata->master_clk = freq;
+ break;
+
+ default:
+ pr_err("%s: MSP %d: Invalid clkid: %d.\n",
+ __func__,
+ dai->id,
+ clk_id);
+ }
+
+ return 0;
+}
+
+static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream,
+ int cmd,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+ struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id];
+
+ pr_debug("%s: MSP %d (%s): Enter (msp->id = %d, cmd = %d).\n",
+ __func__,
+ dai->id,
+ stream_str(substream),
+ (int)drvdata->msp_i2s_drvdata->id,
+ cmd);
+
+ ret = ux500_msp_i2s_trigger(drvdata->msp_i2s_drvdata,
+ cmd,
+ substream->stream);
+
+ return ret;
+}
+
+static int
+ux500_msp_dai_probe(struct snd_soc_dai *dai)
+{
+ struct ux500_msp_i2s_drvdata *drvdata = snd_soc_dai_get_drvdata(dai);
+ struct ux500_platform_drvdata *private = &platform_drvdata[dai->id];
+
+ drvdata->playback_dma_data.dma_cfg = drvdata->msp->dma_cfg_tx;
+ drvdata->capture_dma_data.dma_cfg = drvdata->msp->dma_cfg_rx;
+
+ dai->playback_dma_data = &drvdata->playback_dma_data;
+ dai->capture_dma_data = &drvdata->capture_dma_data;
+
+ drvdata->playback_dma_data.data_size = private->slot_width;
+ drvdata->capture_dma_data.data_size = private->slot_width;
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = {
+ {
+ .name = "ux500-msp-i2s.0",
+ .probe = ux500_msp_dai_probe,
+ .id = 0,
+ .suspend = NULL,
+ .resume = NULL,
+ .playback = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .set_sysclk = ux500_msp_dai_set_dai_sysclk,
+ .set_fmt = ux500_msp_dai_set_dai_fmt,
+ .set_tdm_slot = ux500_msp_dai_set_tdm_slot,
+ .startup = ux500_msp_dai_startup,
+ .shutdown = ux500_msp_dai_shutdown,
+ .prepare = ux500_msp_dai_prepare,
+ .trigger = ux500_msp_dai_trigger,
+ .hw_params = ux500_msp_dai_hw_params,
+ }
+ },
+ },
+ {
+ .name = "ux500-msp-i2s.1",
+ .probe = ux500_msp_dai_probe,
+ .id = 1,
+ .suspend = NULL,
+ .resume = NULL,
+ .playback = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .set_sysclk = ux500_msp_dai_set_dai_sysclk,
+ .set_fmt = ux500_msp_dai_set_dai_fmt,
+ .set_tdm_slot = ux500_msp_dai_set_tdm_slot,
+ .startup = ux500_msp_dai_startup,
+ .shutdown = ux500_msp_dai_shutdown,
+ .prepare = ux500_msp_dai_prepare,
+ .trigger = ux500_msp_dai_trigger,
+ .hw_params = ux500_msp_dai_hw_params,
+ }
+ },
+ },
+ {
+ .name = "ux500-msp-i2s.2",
+ .id = 2,
+ .probe = ux500_msp_dai_probe,
+ .suspend = NULL,
+ .resume = NULL,
+ .playback = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .set_sysclk = ux500_msp_dai_set_dai_sysclk,
+ .set_fmt = ux500_msp_dai_set_dai_fmt,
+ .set_tdm_slot = ux500_msp_dai_set_tdm_slot,
+ .startup = ux500_msp_dai_startup,
+ .shutdown = ux500_msp_dai_shutdown,
+ .prepare = ux500_msp_dai_prepare,
+ .trigger = ux500_msp_dai_trigger,
+ .hw_params = ux500_msp_dai_hw_params,
+ }
+ },
+ },
+ {
+ .name = "ux500-msp-i2s.3",
+ .probe = ux500_msp_dai_probe,
+ .id = 3,
+ .suspend = NULL,
+ .resume = NULL,
+ .playback = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .set_sysclk = ux500_msp_dai_set_dai_sysclk,
+ .set_fmt = ux500_msp_dai_set_dai_fmt,
+ .set_tdm_slot = ux500_msp_dai_set_tdm_slot,
+ .startup = ux500_msp_dai_startup,
+ .shutdown = ux500_msp_dai_shutdown,
+ .prepare = ux500_msp_dai_prepare,
+ .trigger = ux500_msp_dai_trigger,
+ .hw_params = ux500_msp_dai_hw_params,
+ }
+ },
+ },
+};
+EXPORT_SYMBOL(ux500_msp_dai_drv);
+
+static int ux500_msp_drv_probe(struct platform_device *pdev)
+{
+ struct ux500_msp_i2s_drvdata *msp_i2s_drvdata;
+ struct ux500_platform_drvdata *drvdata;
+ struct msp_i2s_platform_data *platform_data;
+ int id;
+ int ret = 0;
+
+ pr_err("%s: Enter (pdev->name = %s).\n", __func__, pdev->name);
+
+ platform_data = (struct msp_i2s_platform_data *)pdev->dev.platform_data;
+ msp_i2s_drvdata = ux500_msp_i2s_init(pdev, platform_data);
+ if (!msp_i2s_drvdata) {
+ pr_err("%s: ERROR: ux500_msp_i2s_init failed!", __func__);
+ return -EINVAL;
+ }
+
+ id = msp_i2s_drvdata->id;
+ drvdata = &platform_drvdata[id];
+ drvdata->msp_i2s_drvdata = msp_i2s_drvdata;
+
+ pr_info("%s: Registering ux500-msp-dai SoC CPU-DAI.\n", __func__);
+ ret = snd_soc_register_dai(&pdev->dev, &ux500_msp_dai_drv[id]);
+ if (ret < 0) {
+ pr_err("Error: %s: Failed to register MSP %d.\n", __func__, id);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int ux500_msp_drv_remove(struct platform_device *pdev)
+{
+ struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev);
+ struct ux500_platform_drvdata *drvdata = &platform_drvdata[msp_i2s_drvdata->id];
+
+ pr_info("%s: Unregister ux500-msp-dai ASoC CPU-DAI.\n", __func__);
+ snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(ux500_msp_dai_drv));
+
+ ux500_msp_i2s_exit(msp_i2s_drvdata);
+ drvdata->msp_i2s_drvdata = NULL;
+
+ return 0;
+}
+
+int ux500_msp_drv_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev);
+
+ pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name);
+
+ return ux500_msp_i2s_suspend(msp_i2s_drvdata);
+}
+
+int ux500_msp_drv_resume(struct platform_device *pdev)
+{
+ struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev);
+
+ pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name);
+
+ return ux500_msp_i2s_resume(msp_i2s_drvdata);
+}
+
+static struct platform_driver msp_i2s_driver = {
+ .driver = {
+ .name = "ux500-msp-i2s",
+ .owner = THIS_MODULE,
+ },
+ .probe = ux500_msp_drv_probe,
+ .remove = ux500_msp_drv_remove,
+ .suspend = ux500_msp_drv_suspend,
+ .resume = ux500_msp_drv_resume,
+};
+
+static int __init ux500_msp_init(void)
+{
+ pr_info("%s: Register ux500-msp-dai platform driver.\n", __func__);
+ return platform_driver_register(&msp_i2s_driver);
+}
+
+static void __exit ux500_msp_exit(void)
+{
+ pr_info("%s: Unregister ux500-msp-dai platform driver.\n", __func__);
+ platform_driver_unregister(&msp_i2s_driver);
+}
+
+module_init(ux500_msp_init);
+module_exit(ux500_msp_exit);
+
+MODULE_LICENSE("GPLv2");
diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h
new file mode 100644
index 00000000000..f5bda875214
--- /dev/null
+++ b/sound/soc/ux500/ux500_msp_dai.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Roger Nilsson <roger.xr.nilsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#ifndef UX500_msp_dai_H
+#define UX500_msp_dai_H
+
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <mach/msp.h>
+
+#define UX500_NBR_OF_DAI 4
+
+#define UX500_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define UX500_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE)
+
+#define FRAME_PER_SINGLE_SLOT_8_KHZ 31
+#define FRAME_PER_SINGLE_SLOT_16_KHZ 124
+#define FRAME_PER_SINGLE_SLOT_44_1_KHZ 63
+#define FRAME_PER_SINGLE_SLOT_48_KHZ 49
+#define FRAME_PER_2_SLOTS 31
+#define FRAME_PER_8_SLOTS 138
+#define FRAME_PER_16_SLOTS 277
+
+#ifndef CONFIG_SND_SOC_UX500_AB5500
+#define UX500_MSP_INTERNAL_CLOCK_FREQ 40000000
+#define UX500_MSP1_INTERNAL_CLOCK_FREQ UX500_MSP_INTERNAL_CLOCK_FREQ
+#else
+#define UX500_MSP_INTERNAL_CLOCK_FREQ 13000000
+#define UX500_MSP1_INTERNAL_CLOCK_FREQ (UX500_MSP_INTERNAL_CLOCK_FREQ * 2)
+#endif
+
+#define UX500_MSP_MIN_CHANNELS 1
+#define UX500_MSP_MAX_CHANNELS 8
+
+#define PLAYBACK_CONFIGURED 1
+#define CAPTURE_CONFIGURED 2
+
+enum ux500_msp_clock_id {
+ UX500_MSP_MASTER_CLOCK,
+};
+
+struct ux500_platform_drvdata {
+ struct ux500_msp_i2s_drvdata *msp_i2s_drvdata;
+ unsigned int fmt;
+ unsigned int tx_mask;
+ unsigned int rx_mask;
+ int slots;
+ int slot_width;
+ bool playback_active;
+ bool capture_active;
+ u8 configured;
+ int data_delay;
+ unsigned int master_clk;
+};
+
+extern struct snd_soc_dai ux500_msp_dai[UX500_NBR_OF_DAI];
+int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay);
+
+#endif
diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c
new file mode 100644
index 00000000000..71d40e16e2c
--- /dev/null
+++ b/sound/soc/ux500/ux500_msp_i2s.c
@@ -0,0 +1,837 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Roger Nilsson <roger.xr.nilsson@stericsson.com>,
+ * Sandeep Kaushik <sandeep.kaushik@st.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mfd/dbx500-prcmu.h>
+
+#include <mach/hardware.h>
+#include <mach/msp.h>
+
+#include <sound/soc.h>
+
+#include "ux500_msp_i2s.h"
+
+static int
+ux500_msp_i2s_enable(struct msp *msp, struct msp_config *config);
+
+ /* Protocol desciptors */
+static const struct msp_protocol_desc prot_descs[] = {
+ I2S_PROTOCOL_DESC,
+ PCM_PROTOCOL_DESC,
+ PCM_COMPAND_PROTOCOL_DESC,
+ AC97_PROTOCOL_DESC,
+ SPI_MASTER_PROTOCOL_DESC,
+ SPI_SLAVE_PROTOCOL_DESC,
+};
+
+static void ux500_msp_i2s_set_prot_desc_tx(struct msp *msp,
+ struct msp_protocol_desc *protocol_desc,
+ enum msp_data_size data_size)
+{
+ u32 temp_reg = 0;
+
+ temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->tx_phase_mode);
+ temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->tx_phase2_start_mode);
+ temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->tx_frame_length_1);
+ temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->tx_frame_length_2);
+ if (msp->def_elem_len) {
+ temp_reg |= MSP_P1_ELEM_LEN_BITS(protocol_desc->tx_element_length_1);
+ temp_reg |= MSP_P2_ELEM_LEN_BITS(protocol_desc->tx_element_length_2);
+ if (protocol_desc->tx_element_length_1 ==
+ protocol_desc->tx_element_length_2) {
+ msp->actual_data_size = protocol_desc->tx_element_length_1;
+ } else {
+ msp->actual_data_size = data_size;
+ }
+ } else {
+ temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size);
+ temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size);
+ msp->actual_data_size = data_size;
+ }
+ temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->tx_data_delay);
+ temp_reg |= MSP_SET_ENDIANNES_BIT(protocol_desc->tx_bit_transfer_format);
+ temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->tx_frame_sync_pol);
+ temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->tx_half_word_swap);
+ temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->compression_mode);
+ temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore);
+
+ writel(temp_reg, msp->registers + MSP_TCF);
+}
+
+static void ux500_msp_i2s_set_prot_desc_rx(struct msp *msp,
+ struct msp_protocol_desc *protocol_desc,
+ enum msp_data_size data_size)
+{
+ u32 temp_reg = 0;
+
+ temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->rx_phase_mode);
+ temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->rx_phase2_start_mode);
+ temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->rx_frame_length_1);
+ temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->rx_frame_length_2);
+ if (msp->def_elem_len) {
+ temp_reg |= MSP_P1_ELEM_LEN_BITS(protocol_desc->rx_element_length_1);
+ temp_reg |= MSP_P2_ELEM_LEN_BITS(protocol_desc->rx_element_length_2);
+ if (protocol_desc->rx_element_length_1 ==
+ protocol_desc->rx_element_length_2) {
+ msp->actual_data_size = protocol_desc->rx_element_length_1;
+ } else {
+ msp->actual_data_size = data_size;
+ }
+ } else {
+ temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size);
+ temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size);
+ msp->actual_data_size = data_size;
+ }
+
+ temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->rx_data_delay);
+ temp_reg |= MSP_SET_ENDIANNES_BIT(protocol_desc->rx_bit_transfer_format);
+ temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->rx_frame_sync_pol);
+ temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->rx_half_word_swap);
+ temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->expansion_mode);
+ temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore);
+
+ writel(temp_reg, msp->registers + MSP_RCF);
+
+}
+
+static int ux500_msp_i2s_configure_protocol(struct msp *msp,
+ struct msp_config *config)
+{
+ int direction;
+ struct msp_protocol_desc *protocol_desc;
+ enum msp_data_size data_size;
+ u32 temp_reg = 0;
+
+ data_size = config->data_size;
+ msp->def_elem_len = config->def_elem_len;
+ direction = config->direction;
+ if (config->default_protocol_desc == 1) {
+ if (config->protocol >= MSP_INVALID_PROTOCOL) {
+ pr_err("%s: ERROR: Invalid protocol!\n", __func__);
+ return -EINVAL;
+ }
+ protocol_desc =
+ (struct msp_protocol_desc *)&prot_descs[config->protocol];
+ } else {
+ protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc;
+ }
+
+ if (data_size < MSP_DATA_BITS_DEFAULT || data_size > MSP_DATA_BITS_32) {
+ pr_err("%s: ERROR: Invalid data-size requested (data_size = %d)!\n",
+ __func__, data_size);
+ return -EINVAL;
+ }
+
+ switch (direction) {
+ case MSP_TRANSMIT_MODE:
+ ux500_msp_i2s_set_prot_desc_tx(msp, protocol_desc, data_size);
+ break;
+ case MSP_RECEIVE_MODE:
+ ux500_msp_i2s_set_prot_desc_rx(msp, protocol_desc, data_size);
+ break;
+ case MSP_BOTH_T_R_MODE:
+ ux500_msp_i2s_set_prot_desc_tx(msp, protocol_desc, data_size);
+ ux500_msp_i2s_set_prot_desc_rx(msp, protocol_desc, data_size);
+ break;
+ default:
+ pr_err("%s: ERROR: Invalid direction requested (direction = %d)!\n",
+ __func__, direction);
+ return -EINVAL;
+ }
+
+ /* The below code is needed for both Rx and Tx path. Can't separate them. */
+ temp_reg = readl(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING;
+ temp_reg |= MSP_TX_CLKPOL_BIT(~protocol_desc->tx_clock_pol);
+ writel(temp_reg, msp->registers + MSP_GCR);
+ temp_reg = readl(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING;
+ temp_reg |= MSP_RX_CLKPOL_BIT(protocol_desc->rx_clock_pol);
+ writel(temp_reg, msp->registers + MSP_GCR);
+
+ return 0;
+}
+
+static int ux500_msp_i2s_configure_clock(struct msp *msp, struct msp_config *config)
+{
+ u32 reg_val_GCR;
+ u32 frame_per = 0;
+ u32 sck_div = 0;
+ u32 frame_width = 0;
+ u32 temp_reg = 0;
+ u32 bit_clock = 0;
+ struct msp_protocol_desc *protocol_desc = NULL;
+
+ reg_val_GCR = readl(msp->registers + MSP_GCR);
+ writel(reg_val_GCR & ~SRG_ENABLE, msp->registers + MSP_GCR);
+
+ if (config->default_protocol_desc)
+ protocol_desc =
+ (struct msp_protocol_desc *)&prot_descs[config->protocol];
+ else
+ protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc;
+
+ switch (config->protocol) {
+ case MSP_PCM_PROTOCOL:
+ case MSP_PCM_COMPAND_PROTOCOL:
+ frame_width = protocol_desc->frame_width;
+ sck_div = config->input_clock_freq / (config->frame_freq *
+ (protocol_desc->total_clocks_for_one_frame));
+ frame_per = protocol_desc->frame_period;
+ break;
+ case MSP_I2S_PROTOCOL:
+ frame_width = protocol_desc->frame_width;
+ sck_div = config->input_clock_freq / (config->frame_freq *
+ (protocol_desc->total_clocks_for_one_frame));
+ frame_per = protocol_desc->frame_period;
+ break;
+ case MSP_AC97_PROTOCOL:
+ /* Not supported */
+ pr_err("%s: ERROR: AC97 protocol not supported!\n", __func__);
+ return -ENOSYS;
+ default:
+ pr_err("%s: ERROR: Unknown protocol (%d)!\n",
+ __func__,
+ config->protocol);
+ return -EINVAL;
+ }
+
+ temp_reg = (sck_div - 1) & SCK_DIV_MASK;
+ temp_reg |= FRAME_WIDTH_BITS(frame_width);
+ temp_reg |= FRAME_PERIOD_BITS(frame_per);
+ writel(temp_reg, msp->registers + MSP_SRG);
+
+ bit_clock = (config->input_clock_freq)/(sck_div + 1);
+ /* If the bit clock is higher than 19.2MHz, Vape should be run in 100% OPP
+ * Only consider OPP 100% when bit-clock is used, i.e. MSP master mode
+ */
+ if ((bit_clock > 19200000) && ((config->tx_clock_sel != 0) || (config->rx_clock_sel != 0))) {
+ prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500-msp-i2s", 100);
+ msp->vape_opp_constraint = 1;
+ } else {
+ prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500-msp-i2s", 50);
+ msp->vape_opp_constraint = 0;
+ }
+
+ /* Enable clock */
+ udelay(100);
+ reg_val_GCR = readl(msp->registers + MSP_GCR);
+ writel(reg_val_GCR | SRG_ENABLE, msp->registers + MSP_GCR);
+ udelay(100);
+
+ return 0;
+}
+
+static int ux500_msp_i2s_configure_multichannel(struct msp *msp, struct msp_config *config)
+{
+ struct msp_protocol_desc *protocol_desc;
+ struct msp_multichannel_config *mcfg;
+ u32 reg_val_MCR;
+
+ if (config->default_protocol_desc == 1) {
+ if (config->protocol >= MSP_INVALID_PROTOCOL) {
+ pr_err("%s: ERROR: Invalid protocol (%d)!\n",
+ __func__,
+ config->protocol);
+ return -EINVAL;
+ }
+ protocol_desc = (struct msp_protocol_desc *)
+ &prot_descs[config->protocol];
+ } else {
+ protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc;
+ }
+
+ mcfg = &config->multichannel_config;
+ if (mcfg->tx_multichannel_enable) {
+ if (protocol_desc->tx_phase_mode == MSP_SINGLE_PHASE) {
+ reg_val_MCR = readl(msp->registers + MSP_MCR);
+ writel(reg_val_MCR |
+ (mcfg->tx_multichannel_enable ? 1 << TMCEN_BIT : 0),
+ msp->registers + MSP_MCR);
+ writel(mcfg->tx_channel_0_enable,
+ msp->registers + MSP_TCE0);
+ writel(mcfg->tx_channel_1_enable,
+ msp->registers + MSP_TCE1);
+ writel(mcfg->tx_channel_2_enable,
+ msp->registers + MSP_TCE2);
+ writel(mcfg->tx_channel_3_enable,
+ msp->registers + MSP_TCE3);
+ } else {
+ pr_err("%s: ERROR: Only single-phase supported (TX-mode: %d)!\n",
+ __func__, protocol_desc->tx_phase_mode);
+ return -EINVAL;
+ }
+ }
+ if (mcfg->rx_multichannel_enable) {
+ if (protocol_desc->rx_phase_mode == MSP_SINGLE_PHASE) {
+ reg_val_MCR = readl(msp->registers + MSP_MCR);
+ writel(reg_val_MCR |
+ (mcfg->rx_multichannel_enable ? 1 << RMCEN_BIT : 0),
+ msp->registers + MSP_MCR);
+ writel(mcfg->rx_channel_0_enable,
+ msp->registers + MSP_RCE0);
+ writel(mcfg->rx_channel_1_enable,
+ msp->registers + MSP_RCE1);
+ writel(mcfg->rx_channel_2_enable,
+ msp->registers + MSP_RCE2);
+ writel(mcfg->rx_channel_3_enable,
+ msp->registers + MSP_RCE3);
+ } else {
+ pr_err("%s: ERROR: Only single-phase supported (RX-mode: %d)!\n",
+ __func__, protocol_desc->rx_phase_mode);
+ return -EINVAL;
+ }
+ if (mcfg->rx_comparison_enable_mode) {
+ reg_val_MCR = readl(msp->registers + MSP_MCR);
+ writel(reg_val_MCR |
+ (mcfg->rx_comparison_enable_mode << RCMPM_BIT),
+ msp->registers + MSP_MCR);
+
+ writel(mcfg->comparison_mask,
+ msp->registers + MSP_RCM);
+ writel(mcfg->comparison_value,
+ msp->registers + MSP_RCV);
+
+ }
+ }
+
+ return 0;
+}
+
+static int ux500_msp_i2s_enable(struct msp *msp, struct msp_config *config)
+{
+ int status = 0;
+ u32 reg_val_DMACR, reg_val_GCR;
+
+ if (config->work_mode != MSP_DMA_MODE) {
+ pr_err("%s: ERROR: Only DMA-mode is supported (msp->work_mode = %d)\n",
+ __func__,
+ msp->work_mode);
+ return -EINVAL;
+ }
+ msp->work_mode = config->work_mode;
+
+ /* Check msp state whether in RUN or CONFIGURED Mode */
+ if (msp->msp_state == MSP_STATE_IDLE) {
+ if (msp->plat_init) {
+ status = msp->plat_init();
+ if (status) {
+ pr_err("%s: ERROR: Failed to init MSP (%d)!\n",
+ __func__,
+ status);
+ return status;
+ }
+ }
+ }
+
+ /* Configure msp with protocol dependent settings */
+ ux500_msp_i2s_configure_protocol(msp, config);
+ ux500_msp_i2s_configure_clock(msp, config);
+ if (config->multichannel_configured == 1) {
+ status = ux500_msp_i2s_configure_multichannel(msp, config);
+ if (status)
+ pr_warn("%s: WARN: ux500_msp_i2s_configure_multichannel failed (%d)!\n",
+ __func__, status);
+ }
+
+ /* Make sure the correct DMA-directions are configured */
+ if ((config->direction == MSP_RECEIVE_MODE) ||
+ (config->direction == MSP_BOTH_T_R_MODE))
+ if (!msp->dma_cfg_rx) {
+ pr_err("%s: ERROR: MSP RX-mode is not configured!", __func__);
+ return -EINVAL;
+ }
+ if ((config->direction == MSP_TRANSMIT_MODE) ||
+ (config->direction == MSP_BOTH_T_R_MODE))
+ if (!msp->dma_cfg_tx) {
+ pr_err("%s: ERROR: MSP TX-mode is not configured!", __func__);
+ return -EINVAL;
+ }
+
+ reg_val_DMACR = readl(msp->registers + MSP_DMACR);
+ switch (config->direction) {
+ case MSP_TRANSMIT_MODE:
+ writel(reg_val_DMACR | TX_DMA_ENABLE,
+ msp->registers + MSP_DMACR);
+
+ break;
+ case MSP_RECEIVE_MODE:
+ writel(reg_val_DMACR | RX_DMA_ENABLE,
+ msp->registers + MSP_DMACR);
+ break;
+ case MSP_BOTH_T_R_MODE:
+ writel(reg_val_DMACR | RX_DMA_ENABLE | TX_DMA_ENABLE,
+ msp->registers + MSP_DMACR);
+ break;
+ default:
+ pr_err("%s: ERROR: Illegal MSP direction (config->direction = %d)!",
+ __func__,
+ config->direction);
+ if (msp->plat_exit)
+ msp->plat_exit();
+ return -EINVAL;
+ }
+ writel(config->iodelay, msp->registers + MSP_IODLY);
+
+ /* Enable frame generation logic */
+ reg_val_GCR = readl(msp->registers + MSP_GCR);
+ writel(reg_val_GCR | FRAME_GEN_ENABLE, msp->registers + MSP_GCR);
+
+ return status;
+}
+
+static void flush_fifo_rx(struct msp *msp)
+{
+ u32 reg_val_DR, reg_val_GCR, reg_val_FLR;
+ u32 limit = 32;
+
+ reg_val_GCR = readl(msp->registers + MSP_GCR);
+ writel(reg_val_GCR | RX_ENABLE, msp->registers + MSP_GCR);
+
+ reg_val_FLR = readl(msp->registers + MSP_FLR);
+ while (!(reg_val_FLR & RX_FIFO_EMPTY) && limit--) {
+ reg_val_DR = readl(msp->registers + MSP_DR);
+ reg_val_FLR = readl(msp->registers + MSP_FLR);
+ }
+
+ writel(reg_val_GCR, msp->registers + MSP_GCR);
+}
+
+static void flush_fifo_tx(struct msp *msp)
+{
+ u32 reg_val_TSTDR, reg_val_GCR, reg_val_FLR;
+ u32 limit = 32;
+
+ reg_val_GCR = readl(msp->registers + MSP_GCR);
+ writel(reg_val_GCR | TX_ENABLE, msp->registers + MSP_GCR);
+ writel(MSP_ITCR_ITEN | MSP_ITCR_TESTFIFO, msp->registers + MSP_ITCR);
+
+ reg_val_FLR = readl(msp->registers + MSP_FLR);
+ while (!(reg_val_FLR & TX_FIFO_EMPTY) && limit--) {
+ reg_val_TSTDR = readl(msp->registers + MSP_TSTDR);
+ reg_val_FLR = readl(msp->registers + MSP_FLR);
+ }
+ writel(0x0, msp->registers + MSP_ITCR);
+ writel(reg_val_GCR, msp->registers + MSP_GCR);
+}
+
+int ux500_msp_i2s_open(struct ux500_msp_i2s_drvdata *drvdata, struct msp_config *msp_config)
+{
+ struct msp *msp = drvdata->msp;
+ u32 old_reg, new_reg, mask;
+ int res;
+
+ if (in_interrupt()) {
+ pr_err("%s: ERROR: Open called in interrupt context!\n", __func__);
+ return -1;
+ }
+
+ /* Two simultanous configuring msp is avoidable */
+ down(&msp->lock);
+
+ /* Don't enable regulator if its msp1 or msp3 */
+ if (!(msp->reg_enabled) && msp->id != MSP_1_I2S_CONTROLLER
+ && msp->id != MSP_3_I2S_CONTROLLER) {
+ res = regulator_enable(drvdata->reg_vape);
+ if (res != 0) {
+ pr_err("%s: Failed to enable regulator!\n", __func__);
+ up(&msp->lock);
+ return res;
+ }
+ msp->reg_enabled = 1;
+ }
+
+ switch (msp->users) {
+ case 0:
+ clk_enable(msp->clk);
+ msp->direction = msp_config->direction;
+ break;
+ case 1:
+ if (msp->direction == MSP_BOTH_T_R_MODE ||
+ msp_config->direction == msp->direction ||
+ msp_config->direction == MSP_BOTH_T_R_MODE) {
+ pr_warn("%s: WARN: MSP is in use (direction = %d)!\n",
+ __func__, msp_config->direction);
+ up(&msp->lock);
+ return -EBUSY;
+ }
+ msp->direction = MSP_BOTH_T_R_MODE;
+ break;
+ default:
+ pr_warn("%s: MSP in use in (both directions)!\n", __func__);
+ up(&msp->lock);
+ return -EBUSY;
+ }
+ msp->users++;
+
+ /* First do the global config register */
+ mask =
+ RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FRAME_SYNC_MASK |
+ TX_FRAME_SYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK |
+ RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK |
+ LOOPBACK_MASK | TX_EXTRA_DELAY_MASK;
+
+ new_reg = (msp_config->tx_clock_sel | msp_config->rx_clock_sel |
+ msp_config->rx_frame_sync_pol | msp_config->tx_frame_sync_pol |
+ msp_config->rx_frame_sync_sel | msp_config->tx_frame_sync_sel |
+ msp_config->rx_fifo_config | msp_config->tx_fifo_config |
+ msp_config->srg_clock_sel | msp_config->loopback_enable |
+ msp_config->tx_data_enable);
+
+ old_reg = readl(msp->registers + MSP_GCR);
+ old_reg &= ~mask;
+ new_reg |= old_reg;
+ writel(new_reg, msp->registers + MSP_GCR);
+
+ if (ux500_msp_i2s_enable(msp, msp_config) != 0) {
+ pr_err("%s: ERROR: ux500_msp_i2s_enable failed!\n", __func__);
+ return -EBUSY;
+ }
+ if (msp_config->loopback_enable & 0x80)
+ msp->loopback_enable = 1;
+
+ /* Flush FIFOs */
+ flush_fifo_tx(msp);
+ flush_fifo_rx(msp);
+
+ msp->msp_state = MSP_STATE_CONFIGURED;
+ up(&msp->lock);
+ return 0;
+}
+
+static void ux500_msp_i2s_disable_rx(struct msp *msp)
+{
+ u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC;
+
+ reg_val_GCR = readl(msp->registers + MSP_GCR);
+ writel(reg_val_GCR & ~RX_ENABLE, msp->registers + MSP_GCR);
+ reg_val_DMACR = readl(msp->registers + MSP_DMACR);
+ writel(reg_val_DMACR & ~RX_DMA_ENABLE, msp->registers + MSP_DMACR);
+ reg_val_IMSC = readl(msp->registers + MSP_IMSC);
+ writel(reg_val_IMSC &
+ ~(RECEIVE_SERVICE_INT | RECEIVE_OVERRUN_ERROR_INT),
+ msp->registers + MSP_IMSC);
+}
+
+static void ux500_msp_i2s_disable_tx(struct msp *msp)
+{
+ u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC;
+
+ reg_val_GCR = readl(msp->registers + MSP_GCR);
+ writel(reg_val_GCR & ~TX_ENABLE, msp->registers + MSP_GCR);
+ reg_val_DMACR = readl(msp->registers + MSP_DMACR);
+ writel(reg_val_DMACR & ~TX_DMA_ENABLE, msp->registers + MSP_DMACR);
+ reg_val_IMSC = readl(msp->registers + MSP_IMSC);
+ writel(reg_val_IMSC &
+ ~(TRANSMIT_SERVICE_INT | TRANSMIT_UNDERRUN_ERR_INT),
+ msp->registers + MSP_IMSC);
+}
+
+static int ux500_msp_i2s_disable(struct msp *msp, int direction, enum i2s_flag flag)
+{
+ u32 reg_val_GCR;
+ int status = 0;
+
+ reg_val_GCR = readl(msp->registers + MSP_GCR);
+
+ if (flag == DISABLE_TRANSMIT)
+ ux500_msp_i2s_disable_tx(msp);
+ else if (flag == DISABLE_RECEIVE)
+ ux500_msp_i2s_disable_rx(msp);
+ else {
+ reg_val_GCR = readl(msp->registers + MSP_GCR);
+ writel(reg_val_GCR | LOOPBACK_MASK,
+ msp->registers + MSP_GCR);
+
+ /* Flush TX-FIFO */
+ flush_fifo_tx(msp);
+
+ /* Disable TX-channel */
+ writel((readl(msp->registers + MSP_GCR) &
+ (~TX_ENABLE)), msp->registers + MSP_GCR);
+
+ /* Flush RX-FIFO */
+ flush_fifo_rx(msp);
+
+ /* Disable Loopback and Receive channel */
+ writel((readl(msp->registers + MSP_GCR) &
+ (~(RX_ENABLE | LOOPBACK_MASK))),
+ msp->registers + MSP_GCR);
+
+ ux500_msp_i2s_disable_tx(msp);
+ ux500_msp_i2s_disable_rx(msp);
+
+ }
+
+ /* disable sample rate and frame generators */
+ if (flag == DISABLE_ALL) {
+ msp->msp_state = MSP_STATE_IDLE;
+ writel((readl(msp->registers + MSP_GCR) &
+ (~(FRAME_GEN_ENABLE | SRG_ENABLE))),
+ msp->registers + MSP_GCR);
+ if (msp->plat_exit)
+ status = msp->plat_exit();
+ if (status)
+ pr_warn("%s: WARN: ux500_msp_i2s_exit failed (%d)!\n",
+ __func__, status);
+ writel(0, msp->registers + MSP_GCR);
+ writel(0, msp->registers + MSP_TCF);
+ writel(0, msp->registers + MSP_RCF);
+ writel(0, msp->registers + MSP_DMACR);
+ writel(0, msp->registers + MSP_SRG);
+ writel(0, msp->registers + MSP_MCR);
+ writel(0, msp->registers + MSP_RCM);
+ writel(0, msp->registers + MSP_RCV);
+ writel(0, msp->registers + MSP_TCE0);
+ writel(0, msp->registers + MSP_TCE1);
+ writel(0, msp->registers + MSP_TCE2);
+ writel(0, msp->registers + MSP_TCE3);
+ writel(0, msp->registers + MSP_RCE0);
+ writel(0, msp->registers + MSP_RCE1);
+ writel(0, msp->registers + MSP_RCE2);
+ writel(0, msp->registers + MSP_RCE3);
+ }
+
+ return status;
+}
+
+int
+ux500_msp_i2s_trigger(
+ struct ux500_msp_i2s_drvdata *drvdata,
+ int cmd,
+ int direction)
+{
+ struct msp *msp = drvdata->msp;
+ u32 reg_val_GCR, enable_bit;
+
+ if (msp->msp_state == MSP_STATE_IDLE) {
+ pr_err("%s: ERROR: MSP is not configured!\n", __func__);
+ return -EINVAL;
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ enable_bit = TX_ENABLE;
+ } else {
+ enable_bit = RX_ENABLE;
+ }
+ reg_val_GCR = readl(msp->registers + MSP_GCR);
+ writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ ux500_msp_i2s_disable_tx(msp);
+ } else {
+ ux500_msp_i2s_disable_rx(msp);
+ }
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+
+ return 0;
+}
+
+int ux500_msp_i2s_close(struct ux500_msp_i2s_drvdata *drvdata, enum i2s_flag flag)
+{
+ struct msp *msp = drvdata->msp;
+ int status = 0;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ down(&msp->lock);
+
+ if (msp->users == 0) {
+ pr_err("%s: ERROR: MSP already closed!\n", __func__);
+ status = -EINVAL;
+ goto end;
+ }
+ pr_debug("%s: msp->users = %d, flag = %d\n", __func__, msp->users, flag);
+
+ /* We need to call it twice for DISABLE_ALL*/
+ msp->users = flag == DISABLE_ALL ? 0 : msp->users - 1;
+ if (msp->users)
+ status = ux500_msp_i2s_disable(msp, MSP_BOTH_T_R_MODE, flag);
+ else {
+ status = ux500_msp_i2s_disable(msp, MSP_BOTH_T_R_MODE, DISABLE_ALL);
+ clk_disable(msp->clk);
+ if (msp->reg_enabled) {
+ status = regulator_disable(drvdata->reg_vape);
+ msp->reg_enabled = 0;
+ }
+ if (status != 0) {
+ pr_err("%s: ERROR: Failed to disable regulator (%d)!\n",
+ __func__, status);
+ clk_enable(msp->clk);
+ goto end;
+ }
+ }
+ if (status)
+ goto end;
+ if (msp->users)
+ msp->direction = flag == DISABLE_TRANSMIT ?
+ MSP_RECEIVE_MODE : MSP_TRANSMIT_MODE;
+
+ if (msp->vape_opp_constraint == 1) {
+ prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s", 50);
+ msp->vape_opp_constraint = 0;
+ }
+end:
+ up(&msp->lock);
+ return status;
+
+}
+
+struct ux500_msp_i2s_drvdata *ux500_msp_i2s_init(struct platform_device *pdev,
+ struct msp_i2s_platform_data *platform_data)
+{
+ struct ux500_msp_i2s_drvdata *msp_i2s_drvdata;
+ int irq;
+ struct resource *res = NULL;
+ struct i2s_controller *i2s_cont;
+ struct msp *msp;
+
+ pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name);
+
+ msp_i2s_drvdata = kzalloc(sizeof(struct ux500_msp_i2s_drvdata), GFP_KERNEL);
+ msp_i2s_drvdata->msp = kzalloc(sizeof(struct msp), GFP_KERNEL);
+ msp = msp_i2s_drvdata->msp;
+
+ msp->id = platform_data->id;
+ msp_i2s_drvdata->id = msp->id;
+ pr_debug("msp_i2s_drvdata->id = %d\n", msp_i2s_drvdata->id);
+
+ msp->plat_init = platform_data->msp_i2s_init;
+ msp->plat_exit = platform_data->msp_i2s_exit;
+ msp->dma_cfg_rx = platform_data->msp_i2s_dma_rx;
+ msp->dma_cfg_tx = platform_data->msp_i2s_dma_tx;
+
+ sema_init(&msp->lock, 1);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ pr_err("%s: ERROR: Unable to get resource!\n", __func__);
+ goto free_msp;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ goto free_msp;
+ msp->irq = irq;
+
+ msp->registers = ioremap(res->start, (res->end - res->start + 1));
+ if (msp->registers == NULL)
+ goto free_msp;
+
+ msp_i2s_drvdata->reg_vape = regulator_get(NULL, "v-ape");
+ if (IS_ERR(msp_i2s_drvdata->reg_vape)) {
+ pr_err("%s: ERROR: Failed to get Vape supply (%d)!\n",
+ __func__, (int)PTR_ERR(msp_i2s_drvdata->reg_vape));
+ goto free_irq;
+ }
+ dev_set_drvdata(&pdev->dev, msp_i2s_drvdata);
+
+ prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name,
+ PRCMU_QOS_DEFAULT_VALUE);
+ msp->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(msp->clk)) {
+ pr_err("%s: ERROR: clk_get failed (%d)!\n",
+ __func__, (int)PTR_ERR(msp->clk));
+ goto free_irq;
+ }
+
+ msp->msp_state = MSP_STATE_IDLE;
+ msp->loopback_enable = 0;
+
+ /* I2S Controller is allocated and added in I2S controller class. */
+ i2s_cont = kzalloc(sizeof(*i2s_cont), GFP_KERNEL);
+ if (!i2s_cont) {
+ pr_err("%s: ERROR: Failed to allocate struct i2s_cont (kzalloc)!\n",
+ __func__);
+ goto del_clk;
+ }
+ i2s_cont->dev.parent = &pdev->dev;
+ i2s_cont->data = (void *)msp;
+ i2s_cont->id = (s16)msp->id;
+ snprintf(i2s_cont->name,
+ sizeof(i2s_cont->name),
+ "ux500-msp-i2s.%04x",
+ msp->id);
+ pr_debug("I2S device-name :%s\n", i2s_cont->name);
+ msp->i2s_cont = i2s_cont;
+
+ return msp_i2s_drvdata;
+
+del_clk:
+ clk_put(msp->clk);
+free_irq:
+ iounmap(msp->registers);
+free_msp:
+ kfree(msp);
+ return NULL;
+}
+
+int ux500_msp_i2s_exit(struct ux500_msp_i2s_drvdata *drvdata)
+{
+ struct msp *msp = drvdata->msp;
+ int status = 0;
+
+ pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id);
+
+ device_unregister(&msp->i2s_cont->dev);
+ clk_put(msp->clk);
+ iounmap(msp->registers);
+ prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s");
+ regulator_put(drvdata->reg_vape);
+ kfree(msp);
+
+ return status;
+}
+
+int ux500_msp_i2s_suspend(struct ux500_msp_i2s_drvdata *drvdata)
+{
+ struct msp *msp = drvdata->msp;
+
+ pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id);
+
+ down(&msp->lock);
+ if (msp->users > 0) {
+ up(&msp->lock);
+ return -EBUSY;
+ }
+ up(&msp->lock);
+
+ return 0;
+}
+
+int ux500_msp_i2s_resume(struct ux500_msp_i2s_drvdata *drvdata)
+{
+ pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id);
+
+ return 0;
+}
+
+MODULE_LICENSE("GPLv2");
diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h
new file mode 100644
index 00000000000..a3a2338a8b1
--- /dev/null
+++ b/sound/soc/ux500/ux500_msp_i2s.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>,
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+
+#ifndef UX500_MSP_I2S_H
+#define UX500_MSP_I2S_H
+
+#include <linux/platform_device.h>
+#include <mach/msp.h>
+#include "ux500_pcm.h"
+
+struct ux500_msp_i2s_drvdata {
+ int id;
+ struct msp *msp;
+ struct regulator *reg_vape;
+ struct ux500_pcm_dma_params playback_dma_data;
+ struct ux500_pcm_dma_params capture_dma_data;
+};
+
+struct ux500_msp_i2s_drvdata *ux500_msp_i2s_init(struct platform_device *pdev,
+ struct msp_i2s_platform_data *platform_data);
+int ux500_msp_i2s_exit(struct ux500_msp_i2s_drvdata *drvdata);
+int ux500_msp_i2s_open(struct ux500_msp_i2s_drvdata *drvdata, struct msp_config *msp_config);
+int ux500_msp_i2s_close(struct ux500_msp_i2s_drvdata *drvdata, enum i2s_flag flag);
+int ux500_msp_i2s_trigger(struct ux500_msp_i2s_drvdata *drvdata, int cmd, int direction);
+int ux500_msp_i2s_suspend(struct ux500_msp_i2s_drvdata *drvdata);
+int ux500_msp_i2s_resume(struct ux500_msp_i2s_drvdata *drvdata);
+
+#endif
+
diff --git a/sound/soc/ux500/ux500_pcm.c b/sound/soc/ux500/ux500_pcm.c
new file mode 100644
index 00000000000..6b6680b878c
--- /dev/null
+++ b/sound/soc/ux500/ux500_pcm.c
@@ -0,0 +1,571 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Roger Nilsson <roger.xr.nilsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <asm/page.h>
+
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <plat/ste_dma40.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "ux500_pcm.h"
+
+static struct snd_pcm_hardware ux500_pcm_hw_playback = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_PAUSE,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_U16_BE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = UX500_PLATFORM_MIN_RATE_PLAYBACK,
+ .rate_max = UX500_PLATFORM_MAX_RATE_PLAYBACK,
+ .channels_min = UX500_PLATFORM_MIN_CHANNELS,
+ .channels_max = UX500_PLATFORM_MAX_CHANNELS,
+ .buffer_bytes_max = UX500_PLATFORM_BUFFER_BYTES_MAX,
+ .period_bytes_min = UX500_PLATFORM_PERIODS_BYTES_MIN,
+ .period_bytes_max = UX500_PLATFORM_PERIODS_BYTES_MAX,
+ .periods_min = UX500_PLATFORM_PERIODS_MIN,
+ .periods_max = UX500_PLATFORM_PERIODS_MAX,
+};
+
+static struct snd_pcm_hardware ux500_pcm_hw_capture = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_PAUSE,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_U16_BE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = UX500_PLATFORM_MIN_RATE_CAPTURE,
+ .rate_max = UX500_PLATFORM_MAX_RATE_CAPTURE,
+ .channels_min = UX500_PLATFORM_MIN_CHANNELS,
+ .channels_max = UX500_PLATFORM_MAX_CHANNELS,
+ .buffer_bytes_max = UX500_PLATFORM_BUFFER_BYTES_MAX,
+ .period_bytes_min = UX500_PLATFORM_PERIODS_BYTES_MIN,
+ .period_bytes_max = UX500_PLATFORM_PERIODS_BYTES_MAX,
+ .periods_min = UX500_PLATFORM_PERIODS_MIN,
+ .periods_max = UX500_PLATFORM_PERIODS_MAX,
+};
+
+static const char *stream_str(struct snd_pcm_substream *substream)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return "Playback";
+ else
+ return "Capture";
+}
+
+static void
+ux500_pcm_dma_eot_handler(void *data)
+{
+ struct snd_pcm_substream *substream = data;
+ struct snd_pcm_runtime *runtime;
+ struct ux500_pcm_private *private;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *dai = rtd->cpu_dai;
+
+ pr_debug("%s: MSP %d (%s): Enter.\n", __func__,
+ dai->id,
+ stream_str(substream));
+
+ if (substream) {
+ runtime = substream->runtime;
+ private = substream->runtime->private_data;
+
+ /* calc the offset in the circular buffer */
+ private->offset += frames_to_bytes(runtime,
+ runtime->period_size);
+ private->offset %= frames_to_bytes(runtime,
+ runtime->period_size) * runtime->periods;
+
+ snd_pcm_period_elapsed(substream);
+ }
+}
+
+static int
+ux500_pcm_dma_start(
+ struct snd_pcm_substream *substream,
+ dma_addr_t dma_addr,
+ int period_cnt,
+ size_t period_len,
+ int dai_idx,
+ int stream_id)
+{
+ dma_cookie_t status_submit;
+ int direction;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *dai = rtd->cpu_dai;
+ struct ux500_pcm_private *private = runtime->private_data;
+ struct dma_async_tx_descriptor *cdesc;
+ struct ux500_pcm_dma_params *dma_params;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ dma_params = snd_soc_dai_get_dma_data(dai, substream);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ direction = DMA_TO_DEVICE;
+ else
+ direction = DMA_FROM_DEVICE;
+
+ cdesc = private->pipeid->device->device_prep_dma_cyclic(
+ private->pipeid,
+ dma_addr,
+ period_cnt * period_len,
+ period_len,
+ direction,
+ NULL);
+
+ if (IS_ERR(cdesc)) {
+ pr_err("%s: ERROR: device_prep_dma_cyclic failed (%ld)!\n",
+ __func__,
+ PTR_ERR(cdesc));
+ return -EINVAL;
+ }
+
+ cdesc->callback = ux500_pcm_dma_eot_handler;
+ cdesc->callback_param = substream;
+
+ status_submit = dmaengine_submit(cdesc);
+
+ if (dma_submit_error(status_submit)) {
+ pr_err("%s: ERROR: dmaengine_submit failed!\n", __func__);
+ return -EINVAL;
+ }
+
+ dma_async_issue_pending(private->pipeid);
+
+ return 0;
+}
+
+static void
+ux500_pcm_dma_stop(struct work_struct *work)
+{
+ struct ux500_pcm_private *private = container_of(work,
+ struct ux500_pcm_private, ws_stop);
+
+ if (private->pipeid != NULL) {
+ dmaengine_terminate_all(private->pipeid);
+ dma_release_channel(private->pipeid);
+ private->pipeid = NULL;
+ }
+}
+
+static void
+ux500_pcm_dma_hw_free(
+ struct device *dev,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dma_buffer *buf = runtime->dma_buffer_p;
+
+ if (runtime->dma_area == NULL)
+ return;
+
+ if (buf != &substream->dma_buffer) {
+ dma_free_coherent(
+ buf->dev.dev,
+ buf->bytes,
+ buf->area,
+ buf->addr);
+ kfree(runtime->dma_buffer_p);
+ }
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+}
+
+static int ux500_pcm_open(struct snd_pcm_substream *substream)
+{
+ int stream_id = substream->pstr->stream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ux500_pcm_private *private;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *dai = rtd->cpu_dai;
+ int ret;
+
+ pr_debug("%s: MSP %d (%s): Enter.\n", __func__,
+ dai->id,
+ stream_str(substream));
+
+ pr_debug("%s: Set runtime hwparams.\n", __func__);
+ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_set_runtime_hwparams(substream, &ux500_pcm_hw_playback);
+ else
+ snd_soc_set_runtime_hwparams(substream, &ux500_pcm_hw_capture);
+
+ /* ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(
+ runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+
+ if (ret < 0) {
+ pr_err("%s: Error: snd_pcm_hw_constraints failed (%d)\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ pr_debug("%s: Init runtime private data.\n", __func__);
+ private = kzalloc(sizeof(struct ux500_pcm_private), GFP_KERNEL);
+ if (private == NULL)
+ return -ENOMEM;
+ private->msp_id = dai->id;
+ private->wq = alloc_ordered_workqueue("ux500/pcm", 0);
+ INIT_WORK(&private->ws_stop, ux500_pcm_dma_stop);
+
+ runtime->private_data = private;
+
+ pr_debug("%s: Set hw-struct for %s.\n",
+ __func__,
+ stream_str(substream));
+
+ runtime->hw = (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ?
+ ux500_pcm_hw_playback : ux500_pcm_hw_capture;
+
+ return 0;
+}
+
+static int ux500_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct ux500_pcm_private *private = substream->runtime->private_data;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ if (private->pipeid != NULL) {
+ dma_release_channel(private->pipeid);
+ private->pipeid = NULL;
+ }
+
+ destroy_workqueue(private->wq);
+ kfree(private);
+
+ return 0;
+}
+
+static int ux500_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dma_buffer *buf = runtime->dma_buffer_p;
+ int ret = 0;
+ int size;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ size = params_buffer_bytes(hw_params);
+
+ if (buf) {
+ if (buf->bytes >= size)
+ goto out;
+ ux500_pcm_dma_hw_free(NULL, substream);
+ }
+
+ if (substream->dma_buffer.area != NULL &&
+ substream->dma_buffer.bytes >= size) {
+ buf = &substream->dma_buffer;
+ } else {
+ buf = kmalloc(sizeof(struct snd_dma_buffer), GFP_KERNEL);
+ if (!buf)
+ goto nomem;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = NULL;
+ buf->area = dma_alloc_coherent(
+ NULL,
+ size,
+ &buf->addr,
+ GFP_KERNEL);
+ buf->bytes = size;
+ buf->private_data = NULL;
+
+ if (!buf->area)
+ goto free;
+ }
+ snd_pcm_set_runtime_buffer(substream, buf);
+ ret = 1;
+ out:
+ runtime->dma_bytes = size;
+ return ret;
+
+ free:
+ kfree(buf);
+ nomem:
+ return -ENOMEM;
+}
+
+static int
+ux500_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ pr_debug("%s: Enter\n", __func__);
+
+ ux500_pcm_dma_hw_free(NULL, substream);
+
+ return 0;
+}
+
+static int
+ux500_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct stedma40_chan_cfg *dma_cfg;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *dai = rtd->cpu_dai;
+ struct ux500_pcm_private *private = runtime->private_data;
+ struct ux500_pcm_dma_params *dma_params;
+ dma_cap_mask_t mask;
+ u16 per_data_width, mem_data_width;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ if (private->pipeid != NULL) {
+ dma_release_channel(private->pipeid);
+ private->pipeid = NULL;
+ }
+
+ dma_params = snd_soc_dai_get_dma_data(dai, substream);
+
+ mem_data_width = STEDMA40_HALFWORD_WIDTH;
+
+ switch (dma_params->data_size) {
+ case 32:
+ per_data_width = STEDMA40_WORD_WIDTH;
+ break;
+ case 16:
+ per_data_width = STEDMA40_HALFWORD_WIDTH;
+ break;
+ case 8:
+ per_data_width = STEDMA40_BYTE_WIDTH;
+ break;
+ default:
+ per_data_width = STEDMA40_WORD_WIDTH;
+ pr_warn("%s: Unknown data-size (%d)! Assuming 32 bits.\n",
+ __func__, dma_params->data_size);
+ }
+
+ dma_cfg = dma_params->dma_cfg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_cfg->src_info.data_width = mem_data_width;
+ dma_cfg->dst_info.data_width = per_data_width;
+ } else {
+ dma_cfg->src_info.data_width = per_data_width;
+ dma_cfg->dst_info.data_width = mem_data_width;
+ }
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ private->pipeid = dma_request_channel(mask, stedma40_filter, dma_cfg);
+
+ return 0;
+}
+
+static int
+ux500_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int ret = 0;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ux500_pcm_private *private = runtime->private_data;
+ int stream_id = substream->pstr->stream;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ pr_debug("%s: START/PAUSE-RELEASE\n", __func__);
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN) {
+ pr_debug("XRUN occurred\n");
+ return 0;
+ }
+
+ private->offset = 0;
+ ret = ux500_pcm_dma_start(
+ substream,
+ runtime->dma_addr,
+ runtime->periods,
+ frames_to_bytes(runtime, runtime->period_size),
+ private->msp_id,
+ stream_id);
+ if (ret) {
+ pr_err("%s: Failed to configure I2S!\n", __func__);
+ return -EINVAL;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__);
+ queue_work(private->wq, &private->ws_stop);
+ break;
+
+ default:
+ pr_err("%s: Invalid command in pcm trigger\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t
+ux500_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ux500_pcm_private *private = runtime->private_data;
+
+ pr_debug("%s: dma_offset %d frame %ld\n", __func__, private->offset,
+ bytes_to_frames(substream->runtime, private->offset));
+
+ return bytes_to_frames(substream->runtime, private->offset);
+}
+
+static int
+ux500_pcm_mmap(
+ struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ pr_debug("%s: Enter.\n", __func__);
+
+ return dma_mmap_coherent(
+ NULL,
+ vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops ux500_pcm_ops = {
+ .open = ux500_pcm_open,
+ .close = ux500_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = ux500_pcm_hw_params,
+ .hw_free = ux500_pcm_hw_free,
+ .prepare = ux500_pcm_prepare,
+ .trigger = ux500_pcm_trigger,
+ .pointer = ux500_pcm_pointer,
+ .mmap = ux500_pcm_mmap
+};
+
+int ux500_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm *pcm = rtd->pcm;
+
+ pr_debug("%s: pcm = %d\n", __func__, (int)pcm);
+
+ pcm->info_flags = 0;
+ strcpy(pcm->name, "UX500_PCM");
+
+ pr_debug("%s: pcm->name = %s.\n", __func__, pcm->name);
+
+ return 0;
+}
+
+static void ux500_pcm_free(struct snd_pcm *pcm)
+{
+ pr_debug("%s: Enter\n", __func__);
+}
+
+static int ux500_pcm_suspend(struct snd_soc_dai *dai)
+{
+ pr_debug("%s: Enter\n", __func__);
+
+ return 0;
+}
+
+static int ux500_pcm_resume(struct snd_soc_dai *dai)
+{
+ pr_debug("%s: Enter\n", __func__);
+
+ return 0;
+}
+
+struct snd_soc_platform_driver ux500_pcm_soc_drv = {
+ .ops = &ux500_pcm_ops,
+ .pcm_new = ux500_pcm_new,
+ .pcm_free = ux500_pcm_free,
+ .suspend = ux500_pcm_suspend,
+ .resume = ux500_pcm_resume,
+};
+EXPORT_SYMBOL(ux500_pcm_soc_drv);
+
+static int __devexit ux500_pcm_drv_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ pr_info("%s: Register ux500-pcm SoC platform driver.\n", __func__);
+ ret = snd_soc_register_platform(&pdev->dev, &ux500_pcm_soc_drv);
+ if (ret < 0) {
+ pr_err("%s: Error: Failed to register "
+ "ux500-pcm SoC platform driver (%d)!\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devinit ux500_pcm_drv_remove(struct platform_device *pdev)
+{
+ pr_info("%s: Unregister ux500-pcm SoC platform driver.\n", __func__);
+ snd_soc_unregister_platform(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver ux500_pcm_driver = {
+ .driver = {
+ .name = "ux500-pcm",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = ux500_pcm_drv_probe,
+ .remove = __devexit_p(ux500_pcm_drv_remove),
+};
+
+static int __init ux500_pcm_drv_init(void)
+{
+ pr_debug("%s: Register ux500-pcm platform driver.\n", __func__);
+
+ return platform_driver_register(&ux500_pcm_driver);
+}
+
+static void __exit ux500_pcm_drv_exit(void)
+{
+ pr_debug("%s: Unregister ux500-pcm platform driver.\n", __func__);
+
+ platform_driver_unregister(&ux500_pcm_driver);
+}
+
+module_init(ux500_pcm_drv_init);
+module_exit(ux500_pcm_drv_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ux500/ux500_pcm.h b/sound/soc/ux500/ux500_pcm.h
new file mode 100644
index 00000000000..3d1ea4f8395
--- /dev/null
+++ b/sound/soc/ux500/ux500_pcm.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Roger Nilsson <roger.xr.nilsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+#ifndef UX500_PCM_H
+#define UX500_PCM_H
+
+#include <asm/page.h>
+#include <linux/workqueue.h>
+
+#define UX500_PLATFORM_MIN_RATE_PLAYBACK 8000
+#define UX500_PLATFORM_MAX_RATE_PLAYBACK 48000
+#define UX500_PLATFORM_MIN_RATE_CAPTURE 8000
+#define UX500_PLATFORM_MAX_RATE_CAPTURE 48000
+
+#define UX500_PLATFORM_MIN_CHANNELS 1
+#define UX500_PLATFORM_MAX_CHANNELS 8
+
+#define UX500_PLATFORM_PERIODS_BYTES_MIN 128
+#define UX500_PLATFORM_PERIODS_BYTES_MAX (64 * PAGE_SIZE)
+#define UX500_PLATFORM_PERIODS_MIN 2
+#define UX500_PLATFORM_PERIODS_MAX 48
+#define UX500_PLATFORM_BUFFER_BYTES_MAX (2048 * PAGE_SIZE)
+
+extern struct snd_soc_platform ux500_soc_platform;
+
+struct ux500_pcm_private {
+ struct dma_chan *pipeid;
+ struct workqueue_struct *wq;
+ struct work_struct ws_stop;
+ int msp_id;
+ int stream_id;
+ unsigned int offset;
+};
+
+struct ux500_pcm_dma_params {
+ unsigned int data_size;
+ struct stedma40_chan_cfg *dma_cfg;
+};
+
+#endif
diff --git a/sound/usb/card.c b/sound/usb/card.c
index 4a7be7b9833..e667491baee 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -445,6 +445,7 @@ snd_usb_audio_probe(struct usb_device *dev,
struct usb_host_interface *alts;
int ifnum;
u32 id;
+ struct usb_device *snd_dev;
alts = &intf->altsetting[0];
ifnum = get_iface_desc(alts)->bInterfaceNumber;
@@ -527,6 +528,9 @@ snd_usb_audio_probe(struct usb_device *dev,
usb_chip[chip->index] = chip;
chip->num_interfaces++;
chip->probing = 0;
+ snd_dev = interface_to_usbdev(intf);
+ usb_enable_autosuspend(snd_dev);
+
mutex_unlock(&register_mutex);
return chip;