diff options
author | Philippe Langlais <philippe.langlais@linaro.org> | 2012-03-19 09:23:36 +0100 |
---|---|---|
committer | Philippe Langlais <philippe.langlais@linaro.org> | 2012-03-19 09:23:36 +0100 |
commit | 4765c76dde923871890a8ec6d3a1f0bbaf81a5fa (patch) | |
tree | 1ee3a78149f81942b84e9dd3b97ddc8f721d61b5 | |
parent | e5a77921253944a39c22d7d0d7825b6bd8603be1 (diff) | |
parent | 839b93f2048bd572d8ea1e2737181ce33e282376 (diff) |
Merge topic branch 'audio' into integration-linux-ux500-3.3
49 files changed, 21334 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..7a5a23baf87 --- /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(void) +{ + db8500_add_msp0_i2s(&msp0_platform_data); + db8500_add_msp1_i2s(&msp1_platform_data); + db8500_add_msp2_i2s(&msp2_platform_data); + db8500_add_msp3_i2s(&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..6f42cca48c6 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/msp.h @@ -0,0 +1,1023 @@ +/* + * 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 + * @handler: callback handler in case of interrupt or dma. + * @tx_callback_data: Callback data for transmit. + * @rx_callback_data: Callback data for receive. + * + * 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; + void (*handler) (void *data); + void *tx_callback_data; + void *rx_callback_data; + +}; + +/*** 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 + * @tx_pipeid: TX DMA channel + * @rx_pipeid: RX DMA channel + * @msp_state: Current state of msp. + * @read: Function pointer for read, u8_msp_read,u16_msp_read,u32_msp_read. + * @write: Function pointer for write, u8_msp_write,u16_msp_write,u32_msp_write. + * @transfer: Function pointer for type of transfer i.e dma,polling or interrupt + * @xfer_data: MSP's transfer data structure. Contains info about current xfer. + * @plat_init: MSP's initialization function. + * @plat_exit: MSP's Exit function. + * @notify_timer: Timer used in Polling mode to prevent hang. + * @polling_flag: Flag used in error handling while polling. + * @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 + * @infinite: true if an infinite transfer has been configured + * + * 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; + struct dma_chan *tx_pipeid; + struct dma_chan *rx_pipeid; + enum msp_state msp_state; + void (*read) (struct trans_data *xfer_data); + void (*write) (struct trans_data *xfer_data); + int (*transfer) (struct msp *msp, struct i2s_message *message); + struct trans_data xfer_data; + int (*plat_init) (void); + int (*plat_exit) (void); + struct timer_list notify_timer; + int polling_flag; + 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; + bool infinite; +}; + +/** + * 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 8293658e7cf..9c467a272f1 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -107,6 +107,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 61c3261c388..56f8e794cfb 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -58,4 +58,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..1cc9a74585c --- /dev/null +++ b/include/sound/ux500_ab8500_ext.h @@ -0,0 +1,22 @@ +/* + * 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); + +#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 3420bd3da5d..cfadbb6a879 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 35e662d270e..e2054fa16e4 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. @@ -45,6 +45,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 9ea8ac827ad..c5d3966cf03 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -22,3 +22,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 7c205e77d83..efc806a205e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -105,7 +105,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 @@ -421,6 +426,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 de8078178f8..c8b8fd3f8c5 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 @@ -100,6 +105,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 @@ -117,6 +125,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 @@ -201,3 +211,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..49b9c1cc178 --- /dev/null +++ b/sound/soc/codecs/ab5500.c @@ -0,0 +1,1805 @@ +/* + * 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) +{ + dev_info(ab5500_dev, "%s called.\n", __func__); + u8 value = (dai->id == 1) ? INTERFACE1 : INTERFACE0; + + /* 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..d51e3a7a544 --- /dev/null +++ b/sound/soc/codecs/ab8500_audio.c @@ -0,0 +1,2960 @@ +/* + * 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 +#define GPIO35_DIR_OUTPUT 0x04 + +/* Macrocell register definitions */ +#define AB8500_CTRL3_REG 0x0200 +#define AB8500_GPIO_DIR4_REG 0x1013 +#define AB8500_GPIO_DIR5_REG 0x1014 +#define AB8500_GPIO_OUT5_REG 0x1024 + +/* Nr of FIR/IIR-coeff banks in ANC-block */ +#define AB8500_NR_OF_ANC_COEFF_BANKS 2 + +/* Macros to simplify implementation of register write sequences and error handling */ +#define AB8500_SET_BIT_LOCKED(xreg, xbit, xerr, xerr_hdl) { \ + xerr = ab8500_codec_update_reg_audio_locked(ab8500_codec, \ + xreg, REG_MASK_NONE, BMASK(xbit)); \ + if (xerr < 0) \ + goto xerr_hdl; } +#define AB8500_CLEAR_BIT_LOCKED(xreg, xbit, xerr, xerr_hdl) { \ + xerr = ab8500_codec_update_reg_audio_locked(ab8500_codec, \ + xreg, BMASK(xbit), REG_MASK_NONE); \ + if (xerr < 0) \ + goto xerr_hdl; } +#define AB8500_WRITE(xreg, xvalue, xerr, xerr_hdl) { \ + xerr = ab8500_codec_write_reg_audio(ab8500_codec, xreg, xvalue); \ + if (xerr < 0) \ + goto xerr_hdl; } + +/* + * 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) */ + 0x00, /* 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; +}; + +/* ANC FIR- & IIR-coeff caches */ +static long anc_fir_cache[REG_ANC_FIR_COEFFS]; +static long anc_iir_cache[REG_ANC_IIR_COEFFS]; + +/* ANC states */ +enum anc_states { + ANC_UNCONFIGURED = 0, + ANC_CONFIGURE_FIR_IIR = 1, + ANC_FIR_IIR_CONFIGURED = 2, + ANC_CONFIGURE_FIR = 3, + ANC_FIR_CONFIGURED = 4, + ANC_CONFIGURE_IIR = 5, + ANC_IIR_CONFIGURED = 6, + ANC_ERROR = 7 +}; +static int ab8500_anc_status = ANC_UNCONFIGURED; + +/* ANC configuration lock */ +static DEFINE_MUTEX(ab8500_anc_conf_lock); + +/* 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. + */ +static unsigned int ab8500_codec_read_reg_audio(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + return cache[reg]; +} + +/* Reads an audio register from the hardware. + */ +static int ab8500_codec_read_reg_audio_nocache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + int value = ab8500_codec_read_reg(codec, AB8500_AUDIO, reg); + + if (value >= 0) + cache[reg] = value; + + return value; +} + +/* 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; +} + +/* Dumps all audio registers. + */ +static inline void ab8500_codec_dump_all_reg(struct snd_soc_codec *codec) +{ + int i; + + pr_debug("%s Enter.\n", __func__); + + for (i = AB8500_FIRST_REG; i <= AB8500_LAST_REG; i++) + ab8500_codec_read_reg_audio_nocache(codec, i); +} + +/* + * 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; +} + +/* + * Updates an audio register, and takes the codec mutex. + * + * Returns 1 for change, 0 for no change, or negative error code. + */ +static int ab8500_codec_update_reg_audio_locked(struct snd_soc_codec *codec, + unsigned int reg, unsigned int clr, unsigned int ins) +{ + int ret; + + mutex_lock(&codec->mutex); + ret = ab8500_codec_update_reg_audio(codec, reg, clr, ins); + mutex_unlock(&codec->mutex); + + return ret; +} + +/* 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 clear_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, clear_mask, set_mask) == 0) + return 0; + return snd_soc_update_bits(codec, reg, set_mask, clear_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"}; + +/* 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_dis_ena); +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_dis_ena); +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 snd_kcontrol_new dapm_ear_mute[] = { + SOC_DAPM_SINGLE("Playback Switch", REG_MUTECONF, REG_MUTECONF_MUTEAR, 1, INVERT), +}; + +/* 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 snd_kcontrol_new dapm_mic1_mute[] = { + SOC_DAPM_SINGLE("Capture Switch", REG_ANACONF2, REG_ANACONF2_MUTMIC1, 1, INVERT), +}; + +/* 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 snd_kcontrol_new dapm_mic2_mute[] = { + SOC_DAPM_SINGLE("Capture Switch", REG_ANACONF2, REG_ANACONF2_MUTMIC2, 1, INVERT), +}; + +/* 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 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 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), +}; + +/* DMic */ + +/* DMic 1 - Mute */ +static const struct snd_kcontrol_new dapm_dmic1_mute[] = { + SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC1, 1, NORMAL), +}; + +/* DMic 2 - Mute */ +static const struct snd_kcontrol_new dapm_dmic2_mute[] = { + SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC2, 1, NORMAL), +}; + +/* DMic 3 - Mute */ +static const struct snd_kcontrol_new dapm_dmic3_mute[] = { + SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC3, 1, NORMAL), +}; + +/* DMic 4 - Mute */ +static const struct snd_kcontrol_new dapm_dmic4_mute[] = { + SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC4, 1, NORMAL), +}; + +/* DMic 5 - Mute */ +static const struct snd_kcontrol_new dapm_dmic5_mute[] = { + SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC5, 1, NORMAL), +}; + +/* DMic 6 - Mute */ +static const struct snd_kcontrol_new dapm_dmic6_mute[] = { + SOC_DAPM_SINGLE("Capture Switch", REG_DIGMICCONF, + REG_DIGMICCONF_ENDMIC6, 1, NORMAL), +}; + +/* 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), +}; + +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_SWITCH("Earpiece", SND_SOC_NOPM, 0, 0, dapm_ear_mute), + + 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_SWITCH("LineIn Left", SND_SOC_NOPM, 0, 0, dapm_linl_mute), + SND_SOC_DAPM_SWITCH("LineIn Right", SND_SOC_NOPM, 0, 0, dapm_linr_mute), + SND_SOC_DAPM_SWITCH("Mic 2", SND_SOC_NOPM, 0, 0, dapm_mic2_mute), + + SND_SOC_DAPM_MIXER("LINL Enable", REG_ANACONF2, + REG_ANACONF2_ENLINL, 0, NULL, 0), + SND_SOC_DAPM_MIXER("LINR Enable", REG_ANACONF2, + REG_ANACONF2_ENLINR, 0, NULL, 0), + 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_SWITCH("Mic 1", SND_SOC_NOPM, 0, 0, dapm_mic1_mute), + + 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_SWITCH("DMic 1", SND_SOC_NOPM, 0, 0, dapm_dmic1_mute), + SND_SOC_DAPM_SWITCH("DMic 2", SND_SOC_NOPM, 0, 0, dapm_dmic2_mute), + SND_SOC_DAPM_SWITCH("DMic 3", SND_SOC_NOPM, 0, 0, dapm_dmic3_mute), + SND_SOC_DAPM_SWITCH("DMic 4", SND_SOC_NOPM, 0, 0, dapm_dmic4_mute), + SND_SOC_DAPM_SWITCH("DMic 5", SND_SOC_NOPM, 0, 0, dapm_dmic5_mute), + SND_SOC_DAPM_SWITCH("DMic 6", SND_SOC_NOPM, 0, 0, dapm_dmic6_mute), + + 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 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), + + /* 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 */ + {"DA_IN1", NULL, "Charge Pump"}, + {"DA_IN2", 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", "Playback Switch", "EAR DAC"}, + + {"EAR Enable", NULL, "Earpiece"}, + + {"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", "Capture Switch", "LINL"}, + {"LineIn Right", "Capture Switch", "LINR"}, + {"Mic 2", "Capture Switch", "MIC2 Input"}, + + {"LINL Enable", NULL, "LineIn Left"}, + {"LINR Enable", NULL, "LineIn Right"}, + {"MIC2 Enable", NULL, "Mic 2"}, + + {"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", "Capture Switch", "Mic 1A or 1B Select Capture Route"}, + + {"MIC1 Enable", NULL, "Mic 1"}, + + {"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", "Capture Switch", "DMIC Input"}, + {"DMic 2", "Capture Switch", "DMIC Input"}, + {"DMic 3", "Capture Switch", "DMIC Input"}, + {"DMic 4", "Capture Switch", "DMIC Input"}, + {"DMic 5", "Capture Switch", "DMIC Input"}, + {"DMic 6", "Capture Switch", "DMIC Input"}, + + {"AD 1 Select Capture Route", "DMic 1", "DMic 1"}, + {"AD 2 Select Capture Route", "DMic 2", "DMic 2"}, + {"AD 3 Select Capture Route", "DMic 3", "DMic 3"}, + {"AD 5 Select Capture Route", "DMic 5", "DMic 5"}, + {"AD 6 Select Capture Route", "DMic 6", "DMic 6"}, + + {"AD4 Channel Gain", NULL, "DMic 4"}, + + {"AD4 Enable", NULL, "AD4 Channel Gain"}, + + {"AD_OUT4", NULL, "AD4 Enable"}, + + /* LineIn Bypass path */ + + {"LINL to HSL Gain", NULL, "LINL Enable"}, + {"LINR to HSR Gain", NULL, "LINR Enable"}, + + {"HSL DAC Driver", NULL, "LINL to HSL Gain"}, + {"HSR DAC Driver", NULL, "LINR to HSR Gain"}, + + /* 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[] = {"19_2_MHz", "38_4_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_STROBE_DECL(soc_enum_applysidetone, + REG_SIDFIRADR, REG_SIDFIRADR_FIRSIDSET, NORMAL, 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), + + /* Sidetone */ + SOC_SINGLE("Sidetone FIR Coefficient Index", + REG_SIDFIRADR, + REG_SIDFIRADR_ADDRESS_SHIFT, + REG_SIDFIRADR_ADDRESS_MAX, + NORMAL), + SOC_SINGLE_S2R("Sidetone FIR Coefficient Value", + REG_SIDFIRCOEF1, REG_SIDFIRCOEF2, + REG_SIDFIRCOEFX_VALUE_SHIFT, + REG_SIDFIRCOEFX_VALUE_MAX, + NORMAL), + SOC_ENUM_STROBE("Sidetone FIR Apply Coefficients", + soc_enum_applysidetone), + + /* ANC */ + SOC_SINGLE_S1R("ANC Warp Delay Shift", + REG_ANCCONF2, + REG_ANCCONF2_VALUE_MIN, + REG_ANCCONF2_VALUE_MAX, + NORMAL), + SOC_SINGLE_S1R("ANC FIR Output Shift", + REG_ANCCONF3, + REG_ANCCONF3_VALUE_MIN, + REG_ANCCONF3_VALUE_MAX, + NORMAL), + SOC_SINGLE_S1R("ANC IIR Output Shift", + REG_ANCCONF4, + REG_ANCCONF4_VALUE_MIN, + REG_ANCCONF4_VALUE_MAX, + NORMAL), + SOC_SINGLE_S2R("ANC Warp Delay", + REG_ANCCONF9, REG_ANCCONF10, + REG_ANC_WARP_DELAY_MIN, + REG_ANC_WARP_DELAY_MAX, + NORMAL), + SOC_MULTIPLE_SA("ANC FIR Coefficients", + anc_fir_cache, + REG_ANC_FIR_COEFF_MIN, + REG_ANC_FIR_COEFF_MAX, + NORMAL), + SOC_MULTIPLE_SA("ANC IIR Coefficients", + anc_iir_cache, + REG_ANC_IIR_COEFF_MIN, + REG_ANC_IIR_COEFF_MAX, + NORMAL), +}; + +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 block current configuration status */ +unsigned int ab8500_audio_anc_status(void) +{ + return ab8500_anc_status; +} + +/* ANC IIR-/FIR-coefficients configuration sequence */ +int ab8500_audio_anc_configure(unsigned int req_state) +{ + bool configure_fir = req_state == ANC_CONFIGURE_FIR || + req_state == ANC_CONFIGURE_FIR_IIR; + bool configure_iir = req_state == ANC_CONFIGURE_IIR || + req_state == ANC_CONFIGURE_FIR_IIR; + unsigned int bank, param; + int ret; + + if (req_state == ANC_UNCONFIGURED || + req_state == ANC_FIR_IIR_CONFIGURED || + req_state == ANC_FIR_CONFIGURED || + req_state == ANC_IIR_CONFIGURED || + req_state == ANC_ERROR) + return -EINVAL; + + mutex_lock(&ab8500_anc_conf_lock); + + if (configure_fir) + AB8500_CLEAR_BIT_LOCKED(REG_ANCCONF1, REG_ANCCONF1_ENANC, ret, cleanup) + + AB8500_SET_BIT_LOCKED(REG_ANCCONF1, REG_ANCCONF1_ENANC, ret, cleanup) + + if (configure_fir) { + for (bank = 0; bank < AB8500_NR_OF_ANC_COEFF_BANKS; bank++) { + for (param = 0; param < REG_ANC_FIR_COEFFS; param++) { + if (param == 0 && bank == 0) + AB8500_SET_BIT_LOCKED(REG_ANCCONF1, + REG_ANCCONF1_ANCFIRUPDATE, ret, cleanup) + + AB8500_WRITE(REG_ANCCONF5, + anc_fir_cache[param] >> 8 & REG_MASK_ALL, + ret, cleanup) + AB8500_WRITE(REG_ANCCONF6, + anc_fir_cache[param] & REG_MASK_ALL, + ret, cleanup) + + if (param == REG_ANC_FIR_COEFFS - 1 && bank == 1) + AB8500_CLEAR_BIT_LOCKED(REG_ANCCONF1, + REG_ANCCONF1_ANCFIRUPDATE, ret, cleanup) + } + } + if (ab8500_anc_status == ANC_IIR_CONFIGURED) + ab8500_anc_status = ANC_FIR_IIR_CONFIGURED; + else if (ab8500_anc_status != ANC_FIR_IIR_CONFIGURED) + ab8500_anc_status = ANC_FIR_CONFIGURED; + } + + if (configure_iir) { + for (bank = 0; bank < AB8500_NR_OF_ANC_COEFF_BANKS; bank++) { + for (param = 0; param < REG_ANC_IIR_COEFFS; param++) { + if (param == 0) { + if (bank == 0) { + AB8500_SET_BIT_LOCKED(REG_ANCCONF1, + REG_ANCCONF1_ANCIIRINIT, + ret, cleanup) + udelay(2000); + AB8500_CLEAR_BIT_LOCKED(REG_ANCCONF1, + REG_ANCCONF1_ANCIIRINIT, + ret, cleanup) + udelay(2000); + } else { + AB8500_SET_BIT_LOCKED(REG_ANCCONF1, + REG_ANCCONF1_ANCIIRUPDATE, + ret, cleanup) + } + } else if (param > 3) { + AB8500_WRITE(REG_ANCCONF7, + REG_MASK_NONE, ret, cleanup) + AB8500_WRITE(REG_ANCCONF8, + anc_iir_cache[param] >> 16 & REG_MASK_ALL, + ret, cleanup) + } + + AB8500_WRITE(REG_ANCCONF7, + anc_iir_cache[param] >> 8 & REG_MASK_ALL, + ret, cleanup) + AB8500_WRITE(REG_ANCCONF8, + anc_iir_cache[param] & REG_MASK_ALL, + ret, cleanup) + + if (param == REG_ANC_IIR_COEFFS - 1 && bank == 1) + AB8500_CLEAR_BIT_LOCKED(REG_ANCCONF1, + REG_ANCCONF1_ANCIIRUPDATE, + ret, cleanup) + } + } + if (ab8500_anc_status == ANC_FIR_CONFIGURED) + ab8500_anc_status = ANC_FIR_IIR_CONFIGURED; + else if (ab8500_anc_status != ANC_FIR_IIR_CONFIGURED) + ab8500_anc_status = ANC_IIR_CONFIGURED; + } + + mutex_unlock(&ab8500_anc_conf_lock); + + return 0; + +cleanup: + ret |= ab8500_codec_update_reg_audio_locked(ab8500_codec, + REG_ANCCONF1, + BMASK(REG_ANCCONF1_ENANC) | + BMASK(REG_ANCCONF1_ANCIIRINIT) | + BMASK(REG_ANCCONF1_ANCIIRUPDATE) | + BMASK(REG_ANCCONF1_ANCFIRUPDATE), + REG_MASK_NONE); + + ab8500_anc_status = ANC_ERROR; + + mutex_unlock(&ab8500_anc_conf_lock); + + return ret; +} + +bool ab8500_audio_dapm_path_active(enum ab8500_audio_dapm_path dapm_path) +{ + int reg, reg_mask; + + switch (dapm_path) { + case AB8500_AUDIO_DAPM_PATH_DMIC: + reg = ab8500_codec_read_reg_audio(ab8500_codec, REG_DIGMICCONF); + reg_mask = BMASK(REG_DIGMICCONF_ENDMIC1) | + BMASK(REG_DIGMICCONF_ENDMIC2) | + BMASK(REG_DIGMICCONF_ENDMIC3) | + BMASK(REG_DIGMICCONF_ENDMIC4) | + BMASK(REG_DIGMICCONF_ENDMIC5) | + BMASK(REG_DIGMICCONF_ENDMIC6); + return reg & reg_mask; + + case AB8500_AUDIO_DAPM_PATH_AMIC1: + reg = ab8500_codec_read_reg_audio(ab8500_codec, REG_ANACONF2); + reg_mask = BMASK(REG_ANACONF2_MUTMIC1); + return !(reg & reg_mask); + + case AB8500_AUDIO_DAPM_PATH_AMIC2: + reg = ab8500_codec_read_reg_audio(ab8500_codec, REG_ANACONF2); + reg_mask = BMASK(REG_ANACONF2_MUTMIC2); + return !(reg & reg_mask); + + default: + return false; + } +} + +int ab8500_audio_set_adcm(enum ab8500_audio_adcm req_adcm) +{ + int ret = 0; + + if (ab8500_codec == NULL) { + pr_err("%s: ERROR: AB8500 ASoC-driver not yet probed!\n", __func__); + return -EIO; + } + + if (adcm == req_adcm) + return ret; + + if (AB8500_AUDIO_ADCM_FORCE_UP == req_adcm || + AB8500_AUDIO_ADCM_FORCE_DOWN == req_adcm) { + + mutex_lock(&ab8500_codec->mutex); + + adcm_anaconf5 = ab8500_codec_read_reg_audio(ab8500_codec, REG_ANACONF5); + adcm_muteconf = ab8500_codec_read_reg_audio(ab8500_codec, REG_MUTECONF); + adcm_anaconf4 = ab8500_codec_read_reg_audio(ab8500_codec, REG_ANACONF4); + + if (AB8500_AUDIO_ADCM_FORCE_UP == req_adcm) { + ret |= ab8500_codec_update_reg_audio(ab8500_codec, + REG_ANACONF5, REG_MASK_NONE, ADCM_ANACONF5_MASK); + if (ret < 0) + goto cleanup; + ret |= ab8500_codec_update_reg_audio(ab8500_codec, + REG_MUTECONF, REG_MASK_NONE, ADCM_MUTECONF_MASK); + if (ret < 0) + goto cleanup; + ret |= ab8500_codec_update_reg_audio(ab8500_codec, + REG_ANACONF4, REG_MASK_NONE, ADCM_ANACONF4_MASK); + if (ret < 0) + goto cleanup; + } else { + ret |= ab8500_codec_update_reg_audio(ab8500_codec, + REG_ANACONF5, ADCM_ANACONF5_MASK, REG_MASK_NONE); + if (ret < 0) + goto cleanup; + } + } else if (AB8500_AUDIO_ADCM_NORMAL == req_adcm) { + + if (AB8500_AUDIO_ADCM_FORCE_UP == adcm) { + ret |= ab8500_codec_update_reg_audio(ab8500_codec, + REG_ANACONF5, ~adcm_anaconf5 & ADCM_ANACONF5_MASK, + adcm_anaconf5 & ADCM_ANACONF5_MASK); + if (ret < 0) + goto cleanup; + ret |= ab8500_codec_update_reg_audio(ab8500_codec, + REG_MUTECONF, ~adcm_muteconf & ADCM_MUTECONF_MASK, + adcm_muteconf & ADCM_MUTECONF_MASK); + if (ret < 0) + goto cleanup; + ret |= ab8500_codec_update_reg_audio(ab8500_codec, + REG_ANACONF4, ~adcm_anaconf4 & ADCM_ANACONF4_MASK, + adcm_anaconf4 & ADCM_ANACONF4_MASK); + if (ret < 0) + goto cleanup; + } else { + ret |= ab8500_codec_update_reg_audio(ab8500_codec, + REG_ANACONF5, ~adcm_anaconf5 & ADCM_ANACONF5_MASK, + adcm_anaconf5 & ADCM_ANACONF5_MASK); + if (ret < 0) + goto cleanup; + } + } + +cleanup: + adcm = (ret < 0) ? AB8500_AUDIO_ADCM_NORMAL : req_adcm; + + if (AB8500_AUDIO_ADCM_NORMAL == adcm) + mutex_unlock(&ab8500_codec->mutex); + + return ret; +} + +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__); + + /* Clear interrupt status registers by reading them. */ + ab8500_codec_read_reg_audio(dai->codec, REG_AUDINTSOURCE1); + ab8500_codec_read_reg_audio(dai->codec, REG_AUDINTSOURCE2); + + return 0; +} + +static void ab8500_codec_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + pr_debug("%s Enter.\n", __func__); + + ab8500_codec_dump_all_reg(dai->codec); +} + +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_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, + pm_message_t state) +{ + 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..bfc1c01e5fc --- /dev/null +++ b/sound/soc/codecs/ab8500_audio.h @@ -0,0 +1,676 @@ +/* + * 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; + +/* Extended interface for codec-driver */ + +int ab8500_audio_power_control(bool power_on); +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); +unsigned int ab8500_audio_anc_status(void); +int ab8500_audio_anc_configure(unsigned int req_state); + +enum ab8500_audio_dapm_path { + AB8500_AUDIO_DAPM_PATH_DMIC, + AB8500_AUDIO_DAPM_PATH_AMIC1, + AB8500_AUDIO_DAPM_PATH_AMIC2 +}; +bool ab8500_audio_dapm_path_active(enum ab8500_audio_dapm_path dapm_path); + +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 */ + +/* 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_SIDFIRCOEFX_VALUE_SHIFT 0 +#define REG_SIDFIRCOEFX_VALUE_MAX 0xFFFF + +/* 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..8716827c17e --- /dev/null +++ b/sound/soc/codecs/av8100_audio.c @@ -0,0 +1,526 @@ +/* + * 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 = 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_IEC60958_PCM; + + /* 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, pm_message_t state) +{ + 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..109387fa2f9 --- /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_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, pm_message_t state) +{ + 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..452564f3e5c --- /dev/null +++ b/sound/soc/ux500/ux500_ab8500.c @@ -0,0 +1,966 @@ +/* + * 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 <sound/soc-dapm.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; + +/* 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", + "Configure FIR+IIR", + "FIR+IIR Configured", + "Configure FIR", + "FIR Configured", + "Configure IIR", + "IIR Configured", + "Error" +}; +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_ancstate, enum_anc_state); + +/* 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; + + 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); + + return ret; +} + +static void ux500_ab8500_power_control_dec(void) +{ + 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); +} + +/* 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) +{ + ucontrol->value.integer.value[0] = ab8500_audio_anc_status(); + + return 0; +} + +static int anc_status_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int req_state = ucontrol->value.integer.value[0]; + + int ret = ux500_ab8500_power_control_inc(); + if (ret < 0) + goto cleanup; + + ret = ab8500_audio_anc_configure(req_state); + + ux500_ab8500_power_control_dec(); + +cleanup: + if (ret < 0) + pr_err("%s: Unable to configure ANC! (ret = %d)\n", + __func__, ret); + + return (ret < 0) ? 0 : 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 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; + } + + 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 */ + +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; + + 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); + + 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..3e8f29b14b3 --- /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_48KHZ; + as.sample_size = AV8100_CODEC_SS_16BIT; + 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..2af5bf14193 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -0,0 +1,1007 @@ +/* + * 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, + }, +}; + +bool ux500_msp_dai_i2s_get_underrun_status(int dai_idx) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai_idx]; + int status = ux500_msp_i2s_hw_status(drvdata->msp_i2s_drvdata); + return (bool)(status & TRANSMIT_UNDERRUN_ERR_INT); +} + +dma_addr_t ux500_msp_dai_i2s_get_pointer(int dai_idx, int stream_id) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai_idx]; + return ux500_msp_i2s_get_pointer(drvdata->msp_i2s_drvdata, + (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ? + I2S_DIRECTION_TX : + I2S_DIRECTION_RX); +} + +int ux500_msp_dai_i2s_configure_sg(dma_addr_t dma_addr, + int period_cnt, + size_t period_len, + int dai_idx, + int stream_id) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai_idx]; + struct i2s_message message; + int ret = 0; + bool playback_req_valid = + (drvdata->playback_active && + stream_id == SNDRV_PCM_STREAM_PLAYBACK); + bool capture_req_valid = + (drvdata->capture_active && + stream_id == SNDRV_PCM_STREAM_CAPTURE); + + pr_debug("%s: Enter (MSP Index: %u, period-cnt: %u, period-len: %u).\n", + __func__, + dai_idx, + period_cnt, + period_len); + + if (!playback_req_valid && !capture_req_valid) { + pr_err("%s: The I2S controller is not available." + "MSP index:%d\n", + __func__, + dai_idx); + return ret; + } + + message.i2s_direction = (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ? + I2S_DIRECTION_TX : + I2S_DIRECTION_RX; + message.buf_addr = dma_addr; + message.buf_len = period_cnt * period_len; + message.period_len = period_len; + + ret = ux500_msp_i2s_transfer(drvdata->msp_i2s_drvdata, &message); + if (ret < 0) { + pr_err("%s: Error: i2s_transfer failed. MSP index: %d\n", + __func__, + dai_idx); + } + + return ret; +} + +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 (mode_playback) + drvdata->playback_active = false; + else + drvdata->capture_active = false; + + 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->configured &= ~PLAYBACK_CONFIGURED; + else + 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->handler = ux500_pcm_dma_eot_handler; + msp_config->tx_callback_data = + substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + substream : NULL; + msp_config->rx_callback_data = + substream->stream == SNDRV_PCM_STREAM_CAPTURE ? + substream : NULL; + 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); + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream)); + + /* If already configured -> not errors reported */ + if (mode_playback) { + if ((drvdata->configured & PLAYBACK_CONFIGURED) && + (drvdata->playback_active)) + goto cleanup; + } else { + if ((drvdata->configured & CAPTURE_CONFIGURED) && + (drvdata->capture_active)) + goto cleanup; + } + + 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 |= mode_playback ? + PLAYBACK_CONFIGURED : CAPTURE_CONFIGURED; + +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); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = 0; + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = 0; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + ret = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = 0; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = { + { + .name = "ux500-msp-i2s.0", + .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", + .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, + .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", + .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..c44894526f2 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -0,0 +1,83 @@ +/* + * 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]; + +bool ux500_msp_dai_i2s_get_underrun_status(int dai_idx); +dma_addr_t ux500_msp_dai_i2s_get_pointer(int dai_idx, int stream_id); +int ux500_msp_dai_i2s_configure_sg(dma_addr_t dma_addr, + int perod_cnt, + size_t period_len, + int dai_idx, + int stream_id); +int ux500_msp_dai_i2s_send_data(void *data, size_t bytes, int dai_idx); +int ux500_msp_dai_i2s_receive_data(void *data, size_t bytes, int dai_idx); + +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..360405a7c09 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -0,0 +1,1014 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Ola Lilja <ola.o.lilja@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 "ux500_msp_i2s.h" + + /* 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; +} + +void ux500_msp_i2s_configure_dma(struct msp *msp, struct msp_config *config) +{ + struct stedma40_chan_cfg *rx_dma_info = msp->dma_cfg_rx; + struct stedma40_chan_cfg *tx_dma_info = msp->dma_cfg_tx; + dma_cap_mask_t mask; + u16 word_width; + bool rx_active, tx_active; + + if (msp->tx_pipeid != NULL) { + dma_release_channel(msp->tx_pipeid); + msp->tx_pipeid = NULL; + } + + switch (config->data_size) { + case MSP_DATA_BITS_32: + word_width = STEDMA40_WORD_WIDTH; + break; + case MSP_DATA_BITS_16: + word_width = STEDMA40_HALFWORD_WIDTH; + break; + case MSP_DATA_BITS_8: + word_width = STEDMA40_BYTE_WIDTH; + break; + default: + word_width = STEDMA40_WORD_WIDTH; + pr_warn("%s: Unknown data-size (%d)! Assuming 32 bits.\n", + __func__, config->data_size); + } + + rx_active = (config->direction == MSP_RECEIVE_MODE || + config->direction == MSP_BOTH_T_R_MODE); + tx_active = (config->direction == MSP_TRANSMIT_MODE || + config->direction == MSP_BOTH_T_R_MODE); + + if (rx_active) { + rx_dma_info->src_info.data_width = word_width; + rx_dma_info->dst_info.data_width = word_width; + } + if (tx_active) { + tx_dma_info->src_info.data_width = word_width; + tx_dma_info->dst_info.data_width = word_width; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + if (rx_active) + msp->rx_pipeid = dma_request_channel(mask, stedma40_filter, rx_dma_info); + + if (tx_active) + msp->tx_pipeid = dma_request_channel(mask, stedma40_filter, tx_dma_info); +} + +static int ux500_msp_i2s_dma_xfer(struct msp *msp, struct i2s_message *msg) +{ + dma_cookie_t status_submit; + int direction, enable_bit; + u32 reg_val_GCR; + struct dma_chan *pipeid; + struct dma_async_tx_descriptor *cdesc; + + if (msg->i2s_direction == I2S_DIRECTION_TX) { + direction = DMA_TO_DEVICE; + pipeid = msp->tx_pipeid; + enable_bit = TX_ENABLE; + pr_debug("%s: Direction: TX\n", __func__); + } else { + direction = DMA_FROM_DEVICE; + pipeid = msp->rx_pipeid; + enable_bit = RX_ENABLE; + pr_debug("%s: Direction: RX\n", __func__); + } + + pr_debug("%s: msg->buf_addr = %p\n", __func__, (void *)msg->buf_addr); + pr_debug("%s: buf_len = %d\n", __func__, msg->buf_len); + pr_debug("%s: perios_len = %d\n", __func__, msg->period_len); + + /* setup the cyclic description */ + cdesc = pipeid->device->device_prep_dma_cyclic(pipeid, + msg->buf_addr, + msg->buf_len, + msg->period_len, + direction); + if (IS_ERR(cdesc)) { + pr_err("%s: ERROR: device_prep_dma_cyclic failed (%ld)!\n", + __func__, + PTR_ERR(cdesc)); + return -EINVAL; + } + + /* Submit to the dma */ + if (msg->i2s_direction == I2S_DIRECTION_TX) { + cdesc->callback = msp->xfer_data.tx_handler; + cdesc->callback_param = msp->xfer_data.tx_callback_data; + } else { + cdesc->callback = msp->xfer_data.rx_handler; + cdesc->callback_param = msp->xfer_data.rx_callback_data; + } + status_submit = dmaengine_submit(cdesc); + if (dma_submit_error(status_submit)) { + pr_err("%s: ERROR: dmaengine_submit failed!\n", __func__); + return -EINVAL; + } + + /* Start the dma */ + dma_async_issue_pending(pipeid); + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR); + + 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); + + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.tx_callback_data = config->tx_callback_data; + + break; + case MSP_RECEIVE_MODE: + writel(reg_val_DMACR | RX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.rx_callback_data = config->rx_callback_data; + + break; + case MSP_BOTH_T_R_MODE: + writel(reg_val_DMACR | RX_DMA_ENABLE | TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.tx_callback_data = config->tx_callback_data; + msp->xfer_data.rx_callback_data = config->rx_callback_data; + + break; + default: + pr_err("%s: ERROR: Illegal MSP direction (config->direction = %d)!", + __func__, + config->direction); + if (msp->plat_exit) + msp->plat_exit(); + return -EINVAL; + } + ux500_msp_i2s_configure_dma(msp, config); + + msp->transfer = ux500_msp_i2s_dma_xfer; + + 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 func_notify_timer(unsigned long data) +{ + struct msp *msp = (struct msp *)data; + if (msp->polling_flag) { + msp->msp_io_error = 1; + pr_err("%s: ERROR: Polling timeout!\n", __func__); + del_timer(&msp->notify_timer); + } +} + +int ux500_msp_i2s_transfer(struct ux500_msp_i2s_drvdata *drvdata, struct i2s_message *message) +{ + struct msp *msp = drvdata->msp; + int status = 0; + + if (!message || (msp->msp_state == MSP_STATE_IDLE)) { + pr_err("%s: ERROR: i2s_message == NULL!\n", __func__); + return -EINVAL; + } + if (msp->msp_state == MSP_STATE_IDLE) { + pr_err("%s: ERROR: MSP in idle-state!\n", __func__); + return -EPERM; + } + + msp->msp_state = MSP_STATE_RUN; + if (msp->transfer) + status = msp->transfer(msp, message); + + if (msp->msp_state == MSP_STATE_RUN) + msp->msp_state = MSP_STATE_CONFIGURED; + + return status; +} + +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); + msp->xfer_data.message.rxbytes = 0; + msp->xfer_data.message.rx_offset = 0; + msp->xfer_data.message.rxdata = NULL; + msp->read = NULL; +} + +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); + msp->xfer_data.message.txbytes = 0; + msp->xfer_data.message.tx_offset = 0; + msp->xfer_data.message.txdata = NULL; + msp->write = NULL; +} + +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 (!(reg_val_GCR & (TX_ENABLE | RX_ENABLE))) + return 0; + + if (flag == DISABLE_ALL || flag == DISABLE_TRANSMIT) { + if (msp->tx_pipeid != NULL) { + dmaengine_terminate_all(msp->tx_pipeid); + dma_release_channel(msp->tx_pipeid); + msp->tx_pipeid = NULL; + } + } + if ((flag == DISABLE_ALL || flag == DISABLE_RECEIVE)) { + if (msp->rx_pipeid != NULL) { + dmaengine_terminate_all(msp->rx_pipeid); + dma_release_channel(msp->rx_pipeid); + msp->rx_pipeid = NULL; + } + } + + 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); + memset(&msp->xfer_data, 0, sizeof(struct trans_data)); + if (msp->plat_exit) + status = msp->plat_exit(); + if (status) + pr_warn("%s: WARN: ux500_msp_i2s_exit failed (%d)!\n", + __func__, status); + msp->transfer = NULL; + 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_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; + +} + +int ux500_msp_i2s_hw_status(struct ux500_msp_i2s_drvdata *drvdata) +{ + struct msp *msp = drvdata->msp; + int status; + + pr_debug("%s: Enter.\n", __func__); + + status = readl(msp->registers + MSP_RIS) & 0xee; + if (status) + writel(status, msp->registers + MSP_ICR); + + return status; +} + +dma_addr_t ux500_msp_i2s_get_pointer(struct ux500_msp_i2s_drvdata *drvdata, + enum i2s_direction_t i2s_direction) +{ + struct msp *msp = drvdata->msp; + + pr_debug("%s: Enter.\n", __func__); + + return (i2s_direction == I2S_DIRECTION_TX) ? + stedma40_get_src_addr(msp->tx_pipeid) : + stedma40_get_dst_addr(msp->rx_pipeid); +} + +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, 50); + 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; + } + + init_timer(&msp->notify_timer); + msp->notify_timer.expires = jiffies + msecs_to_jiffies(1000); + msp->notify_timer.function = func_notify_timer; + msp->notify_timer.data = (unsigned long)msp; + + msp->rx_pipeid = NULL; + msp->tx_pipeid = NULL; + msp->read = NULL; + msp->write = NULL; + msp->transfer = NULL; + 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_timer; + } + 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_timer: + del_timer_sync(&msp->notify_timer); + 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); + del_timer_sync(&msp->notify_timer); + 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..db88d0ca5de --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -0,0 +1,41 @@ +/* + * 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> + +struct ux500_msp_i2s_drvdata { + int id; + struct msp *msp; + struct regulator *reg_vape; +}; + +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_transfer(struct ux500_msp_i2s_drvdata *drvdata, struct i2s_message *message); +int ux500_msp_i2s_hw_status(struct ux500_msp_i2s_drvdata *drvdata); +dma_addr_t ux500_msp_i2s_get_pointer(struct ux500_msp_i2s_drvdata *drvdata, + enum i2s_direction_t i2s_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..29b3f5e0ffb --- /dev/null +++ b/sound/soc/ux500/ux500_pcm.c @@ -0,0 +1,430 @@ +/* + * 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 <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "ux500_pcm.h" +#include "ux500_msp_dai.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_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); +} + +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; + + if (ux500_msp_dai_i2s_get_underrun_status(private->msp_id)) { + private->no_of_underruns++; + pr_debug("%s: Nr of underruns (%d)\n", __func__, + private->no_of_underruns); + } + + /* 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); + } +} +EXPORT_SYMBOL(ux500_pcm_dma_eot_handler); + +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; + 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__); + + 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) +{ + pr_debug("%s: Enter\n", __func__); + return 0; +} + +static int ux500_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret; + 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->no_of_underruns = 0; + private->offset = 0; + ret = ux500_msp_dai_i2s_configure_sg(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_PAUSE_PUSH: + break; + + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__); + pr_debug("%s: no_of_underruns = %u\n", + __func__, + private->no_of_underruns); + break; + + default: + pr_err("%s: Invalid command in pcm trigger\n", + __func__); + return -EINVAL; + } + + return 0; +} + +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..50f46615275 --- /dev/null +++ b/sound/soc/ux500/ux500_pcm.h @@ -0,0 +1,44 @@ +/* + * 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 <mach/msp.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 { + int msp_id; + int stream_id; + unsigned int no_of_underruns; + unsigned int offset; +}; + +void ux500_pcm_dma_eot_handler(void *data); + +#endif |