aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sound/pci/Kconfig13
-rw-r--r--sound/pci/intel_mid_i2s/Makefile4
-rw-r--r--sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_interface.c529
-rw-r--r--sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_interface.h98
-rw-r--r--sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_settings.h104
-rw-r--r--sound/pci/intel_mid_i2s/intel_alsa_ssp_snd_card.c616
-rw-r--r--sound/pci/intel_mid_i2s/intel_alsa_ssp_snd_card.h166
7 files changed, 1530 insertions, 0 deletions
diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig
index 0b0a5245a89..19afa0acf0d 100644
--- a/sound/pci/Kconfig
+++ b/sound/pci/Kconfig
@@ -871,4 +871,17 @@ config SND_INTEL_MID_I2S
will be called intel_mid_i2s.ko
If unsure, say N here.
+config SND_INTEL_ALSA_SSP
+ tristate "ALSA SSP SND Card Driver"
+ depends on EXPERIMENTAL && SND_INTEL_MID_I2S
+ help
+ Say Y here to include support for ALSA SSP soundcard.
+ To compile this driver as a module, choose M here: the module
+ will be called snd-alsa-ssp.ko
+ This is a sound card driver for TI LNW1273 BT/FM Board
+ This ALSA driver offers:
+ 1 BT device with 1 capture and 1 playback substreams
+ 1 FM device with 1 capture substream
+
+
endif # SND_PCI
diff --git a/sound/pci/intel_mid_i2s/Makefile b/sound/pci/intel_mid_i2s/Makefile
index 7a24d98d045..7436fe4b32b 100644
--- a/sound/pci/intel_mid_i2s/Makefile
+++ b/sound/pci/intel_mid_i2s/Makefile
@@ -16,3 +16,7 @@
obj-$(CONFIG_SND_INTEL_MID_I2S) += intel_mid_i2s.o
+obj-$(CONFIG_SND_INTEL_ALSA_SSP) += snd-alsa-ssp.o
+
+snd-alsa-ssp-objs := intel_alsa_ssp_snd_card.o intel_alsa_ssp_hw_interface.o
+
diff --git a/sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_interface.c b/sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_interface.c
new file mode 100644
index 00000000000..a66bc438caa
--- /dev/null
+++ b/sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_interface.c
@@ -0,0 +1,529 @@
+/*
+ * intel_alsa_ssp_hw_interface.c - Intel Sound card driver for SSP
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Authors: Selma Bensaid <selma.bensaid@intel.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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <sound/pcm.h>
+
+#include "intel_alsa_ssp_hw_interface.h"
+#include "intel_alsa_ssp_hw_settings.h"
+
+/*****************************************/
+/* Global Variables */
+/*****************************************/
+static struct intel_alsa_stream_status s_stream_status;
+
+void intel_alsa_reset_ssp_status(void)
+{
+ s_stream_status.ssp_handle = NULL;
+ s_stream_status.stream_info = 0;
+ spin_lock_init(&s_stream_status.lock);
+}
+
+/*
+ * intel_alsa_ssp_open - This function opens the requested stream
+ * The Intel I2S driver is opened only if all streams has been closed
+ *
+ * Input parameters
+ * @str_info : pointer to stream structure
+ * Output parameters
+ * @ret_val : status, 0 ==> OK
+ */
+static int intel_alsa_ssp_open(struct intel_alsa_ssp_stream_info *str_info)
+{
+ int device_id, status;
+ bool open_ssp = false;
+
+ WARN(!str_info, "ALSA_SSP: NULL str_info\n");
+ if (!str_info)
+ return -EINVAL;
+
+ device_id = str_info->device_id;
+
+ str_info->dma_slot.period_req_index = 0;
+ str_info->dma_slot.period_cb_index = 0;
+
+ /*
+ * One Open Callback is called by sub-stream:
+ * 1 for BT Playback
+ * 1 for BT Capture
+ * 1 for FM Capture
+ * The FM and BT are exclusive, the 2 devices cannot be opened in
+ * parallel
+ */
+
+ pr_debug("ALSA_SSP: call intel_mid_i2s_open for Device ID = %d\n",
+ device_id);
+
+ /*
+ * Algorithm that detects conflicting call of open /close
+ * The Open Call back is called for each device substream
+ * BT Capture, BT Playback and FM Capture
+ * However the Intel MID SSP provide a unique interface
+ * to open/configure and to close the SSP
+ * The aim of this function is to properly handle the concurrent
+ * Open/Close Callbacks
+ * The s_stream_status.stream_info is a bit field that indicated
+ * the stream status open or close
+ * BT Capture substream index = 0
+ * BT Play substream index = 1
+ * FM Capture substream index = 2
+ */
+ spin_lock(&s_stream_status.lock);
+ if (s_stream_status.stream_info == 0) {
+ open_ssp = true;
+ } else if ((((str_info->stream_index == 0)) &&
+ (test_bit(1, &s_stream_status.stream_info))) ||
+ ((str_info->stream_index == 1) &&
+ (test_bit(0, &s_stream_status.stream_info)))) {
+ /*
+ * do nothing be cause already Open by sibling substream
+ */
+ pr_debug("ALSA SSP: Open DO NOTHING\n");
+
+ } else {
+ spin_unlock(&s_stream_status.lock);
+ WARN(1, "ALSA SSP: Open unsupported Config\n");
+ return -EBUSY;
+ }
+ set_bit(str_info->stream_index, &s_stream_status.stream_info);
+ spin_unlock(&s_stream_status.lock);
+
+ /*
+ * the Open SSP is performed out of lock
+ */
+ if (open_ssp == true) {
+ s_stream_status.ssp_handle = intel_mid_i2s_open(SSP_USAGE_BLUETOOTH_FM,
+ &a_alsa_ssp_stream_settings[device_id]);
+ /* Set the Write Callback */
+ status = intel_mid_i2s_set_wr_cb(s_stream_status.ssp_handle,
+ intel_alsa_ssp_dma_playback_complete);
+ if (status)
+ return -EINVAL;
+ /* Set the Default Read Callback */
+ status = intel_mid_i2s_set_rd_cb(s_stream_status.ssp_handle,
+ intel_alsa_ssp_dma_capture_complete);
+ if (status)
+ return -EINVAL;
+ }
+ return 0;
+} /* intel_alsa_ssp_open */
+
+/*
+ * intel_alsa_ssp_close - This function closes the requested stream
+ * The Intel I2S driver is closed only if all streams are closed
+ *
+ * Input parameters
+ * @str_info : pointer to stream structure
+ * Output parameters
+ * @ret_val : status
+ */
+static int intel_alsa_ssp_close(struct intel_alsa_ssp_stream_info
+ *str_info)
+{
+ unsigned int device_id;
+ bool close_ssp = false;
+
+ WARN(!str_info, "ALSA_SSP: NULL str_info\n");
+ if (!str_info)
+ return -EINVAL;
+
+ WARN(!s_stream_status.ssp_handle, "ALSA_SSP: ERROR, "
+ "trying to close a stream however "
+ "s_stream_status.ssp_handle is NULL\n");
+ if (!s_stream_status.ssp_handle)
+ return -EINVAL;
+
+ device_id = str_info->device_id;
+
+ /* Algorithm that detects conflicting call of open /close*/
+ spin_lock(&s_stream_status.lock);
+
+ if (s_stream_status.stream_info == 0) {
+ spin_unlock(&s_stream_status.lock);
+ WARN(1, "ALSA SSP: Close before Open\n");
+ return -EBUSY;
+ }
+ clear_bit(str_info->stream_index, &s_stream_status.stream_info);
+
+ if (s_stream_status.stream_info == 0)
+ close_ssp = true;
+
+ spin_unlock(&s_stream_status.lock);
+
+ /*
+ * the Close SSP is performed out of lock
+ */
+ if (close_ssp) {
+ intel_mid_i2s_close(s_stream_status.ssp_handle);
+ s_stream_status.ssp_handle = NULL;
+ }
+
+ return 0;
+} /* intel_alsa_ssp_close */
+
+/*
+ * intel_alsa_ssp_control - Set Control params
+ *
+ * Input parameters
+ * @command : command to execute
+ * @value: pointer to a structure
+ * Output parameters
+ * @ret_val : status
+ */
+int intel_alsa_ssp_control(int command, struct intel_alsa_ssp_stream_info
+ *str_info)
+{
+ int retval = 0;
+
+ switch (command) {
+ case INTEL_ALSA_SSP_CTRL_SND_OPEN:
+ retval = intel_alsa_ssp_open(str_info);
+ break;
+ /*
+ * SND_PAUSE & SND_RESUME not supported in this version
+ */
+ case INTEL_ALSA_SSP_CTRL_SND_PAUSE:
+ case INTEL_ALSA_SSP_CTRL_SND_RESUME:
+ break;
+
+ case INTEL_ALSA_SSP_CTRL_SND_CLOSE:
+ intel_alsa_ssp_close(str_info);
+ break;
+
+ default:
+ /* Illegal case */
+ WARN(1, "ALSA_SSP: intel_alsa_ssp_control Error: Bad Control ID\n");
+ return -EINVAL;
+ break;
+ }
+ return retval;
+} /* intel_alsa_ssp_control */
+
+/*
+ * intel_alsa_ssp_dma_playback_req - This function programs a write request
+ * to the Intel I2S driver
+ *
+ * Input parameters
+ * @str_info : pointer to stream structure
+ * Output parameters
+ * @ret_val : status
+ */
+static int intel_alsa_ssp_dma_playback_req(struct intel_alsa_ssp_stream_info
+ *str_info)
+{
+ u32 *tx_addr;
+ int tx_length;
+ struct intel_alsa_ssp_dma_buf *sb_tx;
+
+ WARN(!s_stream_status.ssp_handle, "ALSA_SSP: ERROR, "
+ "trying to play a stream however "
+ "s_stream_status.ssp_handle is NULL\n");
+ if (!s_stream_status.ssp_handle)
+ return -EINVAL;
+
+ sb_tx = &(str_info->dma_slot);
+ tx_length = sb_tx->length;
+ tx_addr = (u32 *)(sb_tx->addr + tx_length * sb_tx->period_req_index);
+ pr_debug("ALSA_SSP: DMA PLAYBACK ADDRESS = 0x%p\n", tx_addr);
+
+ if (intel_mid_i2s_wr_req(s_stream_status.ssp_handle, tx_addr,
+ tx_length, str_info) == 0) {
+ intel_mid_i2s_enable_ssp(s_stream_status.ssp_handle);
+
+ if (test_and_set_bit(INTEL_ALSA_SSP_STREAM_RUNNING,
+ &str_info->stream_status)) {
+ WARN_ON("ALSA SSP: ERROR previous requested not handled\n");
+ return -EBUSY;
+ }
+
+ if (++(sb_tx->period_req_index) >= sb_tx->period_index_max)
+ sb_tx->period_req_index = 0;
+ return 0;
+ } else {
+ WARN(1, "ALSA_SSP: intel_mid_i2s_wr_req returns ERROR\n");
+ return -EINVAL;
+ }
+} /* intel_alsa_ssp_dma_playback_req */
+
+/*
+ * intel_alsa_ssp_dma_capture_req - This function programs a read request
+ * to the Intel I2S driver
+ *
+ * Input parameters
+ * @str_info : pointer to stream structure
+ * Output parameters
+ * @ret_val : status
+ */
+static int intel_alsa_ssp_dma_capture_req(struct intel_alsa_ssp_stream_info
+ *str_info)
+{
+ u32 *rx_addr;
+ int rx_length;
+ struct intel_alsa_ssp_dma_buf *sb_rx;
+
+
+ WARN(!s_stream_status.ssp_handle, "ALSA_SSP: ERROR, "
+ "trying to play a stream however "
+ "s_stream_status.ssp_handle is NULL\n");
+ if (!s_stream_status.ssp_handle)
+ return -EINVAL;
+
+ sb_rx = &(str_info->dma_slot);
+
+ rx_length = sb_rx->length;
+ rx_addr = (u32 *) (sb_rx->addr + rx_length * sb_rx->period_req_index);
+
+ pr_debug("ALSA_SSP: DMA CAPTURE ADDRESS = 0x%p\n", rx_addr);
+
+ if (intel_mid_i2s_rd_req(s_stream_status.ssp_handle, rx_addr, rx_length,
+ str_info) == 0) {
+
+ intel_mid_i2s_enable_ssp(s_stream_status.ssp_handle);
+
+ if (test_and_set_bit(INTEL_ALSA_SSP_STREAM_RUNNING,
+ &str_info->stream_status)) {
+ WARN_ON("ALSA SSP: ERROR previous requested not handled\n");
+ return -EBUSY;
+ }
+
+ if (++(sb_rx->period_req_index) >= sb_rx->period_index_max)
+ sb_rx->period_req_index = 0;
+ return 0;
+ } else {
+ WARN(1, "ALSA_SSP: intel_mid_i2s_rd_req returns ERROR\n");
+ return -EINVAL;
+ }
+} /* intel_alsa_ssp_dma_capture_req */
+
+/*
+ * intel_alsa_ssp_transfer_data - send data buffers
+ *
+ * Input parameters
+ * @str_info : pointer to stream structure
+ * Output parameters
+ * @ret_val : status
+ */
+int intel_alsa_ssp_transfer_data(struct intel_alsa_ssp_stream_info
+ *str_info)
+{
+ int status = 0;
+ WARN(!str_info, "ALSA SSP: ERROT NULL str_info\n");
+ if (!str_info)
+ return -EBUSY;
+
+ switch (str_info->stream_dir) {
+ case INTEL_ALSA_SSP_PLAYBACK:
+ status = intel_alsa_ssp_dma_playback_req(str_info);
+ break;
+
+ case INTEL_ALSA_SSP_CAPTURE:
+ status = intel_alsa_ssp_dma_capture_req(str_info);
+ break;
+
+ default:
+ WARN(1, "ALSA_SSP: FCT %s Bad stream_dir: %d\n",
+ __func__, str_info->stream_dir);
+ return -EINVAL;
+ break;
+ }
+ return status;
+} /* intel_alsa_ssp_transfer_data */
+
+/*
+ * intel_alsa_ssp_dma_playback_complete - End of playback callback
+ * called in DMA Complete Tasklet context
+ * This Callback has in charge of re-programming a new write request to
+ * Intel MID I2S Driver if the stream has not been Closed.
+ * It calls also the snd_pcm_period_elapsed if the stream is not
+ * PAUSED or SUSPENDED to inform ALSA Kernel that the Ring Buffer
+ * period has been sent properly
+ *
+ * Input parameters
+ * @param : pointer to a structure
+ * Output parameters
+ * @ret_val : status
+ */
+int intel_alsa_ssp_dma_playback_complete(void *param)
+{
+ struct intel_alsa_ssp_stream_info *str_info;
+ struct intel_alsa_ssp_dma_buf *sb_tx;
+ bool call_back = false;
+ bool reset_index = false;
+
+ WARN(!param, "ALSA SSP: ERROR param NULL\n");
+ if (!param)
+ return -EBUSY;
+ str_info = param;
+ sb_tx = &(str_info->dma_slot);
+
+ if (test_and_clear_bit(INTEL_ALSA_SSP_STREAM_RUNNING,
+ &str_info->stream_status)) {
+ if (test_and_clear_bit(INTEL_ALSA_SSP_STREAM_DROPPED,
+ &str_info->stream_status)) {
+ if (test_bit(INTEL_ALSA_SSP_STREAM_STARTED,
+ &str_info->stream_status)) {
+ /*
+ * the stream has been dropped and restarted
+ * before the callback occurs
+ * in this case the we have to reprogram the
+ * requests to SSP driver and reset the stream's
+ * indexes
+ */
+ call_back = true;
+ reset_index = true;
+ } else
+ call_back = false;
+ } else {
+ if (test_bit(INTEL_ALSA_SSP_STREAM_STARTED,
+ &str_info->stream_status)) {
+ /*
+ * the stream is on going
+ */
+ call_back = true;
+ } else {
+ WARN(1, "ALSA_SSP: FCT %s spurious playback DMA complete 1 ?!\n",
+ __func__);
+ return -EBUSY;
+ }
+ }
+ } else {
+ WARN(1, "ALSA_SSP: FCT %s spurious playback DMA complete 2 ?!\n",
+ __func__);
+ return -EBUSY;
+ }
+
+ if (call_back == true) {
+ pr_debug("ALSA_SSP: playback (REQ=%d,CB=%d): PLAYBACK_DMA_REQ_COMPLETE\n",
+ sb_tx->period_req_index, sb_tx->period_cb_index);
+
+ if (reset_index) {
+ sb_tx->period_cb_index = 0;
+ sb_tx->period_req_index = 0;
+ } else if (++(sb_tx->period_cb_index) >= sb_tx->period_index_max)
+ sb_tx->period_cb_index = 0;
+
+ /*
+ * Launch the next Playback request if no CLOSE
+ * has been requested
+ */
+ intel_alsa_ssp_dma_playback_req(str_info);
+
+ /*
+ * Call the snd_pcm_period_elapsed to inform ALSA kernel
+ * that a ring buffer period has been played
+ */
+ snd_pcm_period_elapsed(str_info->substream);
+
+ }
+ return 0;
+} /* intel_alsa_ssp_dma_playback_complete */
+
+/*
+ * intel_alsa_ssp_dma_capture_complete - End of capture callback
+ * called in DMA Complete Tasklet context
+ * This Callback has in charge of re-programming a new read request to
+ * Intel MID I2S Driver if the stream has not been Closed.
+ * It calls also the snd_pcm_period_elapsed if the stream is not
+ * PAUSED or SUSPENDED to inform ALSA Kernel that the Ring Buffer
+ * period has been received properly
+ *
+ * Input parameters
+ * @param : pointer to a structure
+ * Output parameters
+ * @ret_val : status
+ */
+int intel_alsa_ssp_dma_capture_complete(void *param)
+{
+ struct intel_alsa_ssp_dma_buf *sb_rx;
+ struct intel_alsa_ssp_stream_info *str_info;
+ bool call_back = false;
+ bool reset_index = false;
+
+ WARN(!param, "ALSA SSP: ERROR param NULL\n");
+ if (!param)
+ return -EBUSY;
+
+ str_info = param;
+ sb_rx = &(str_info->dma_slot);
+
+ if (test_and_clear_bit(INTEL_ALSA_SSP_STREAM_RUNNING,
+ &str_info->stream_status)) {
+ if (test_and_clear_bit(INTEL_ALSA_SSP_STREAM_DROPPED,
+ &str_info->stream_status)) {
+ if (test_bit(INTEL_ALSA_SSP_STREAM_STARTED,
+ &str_info->stream_status)) {
+ /*
+ * the stream has been dropped and restarted
+ * before the callback occurs
+ * in this case the we have to reprogram the
+ * requests to SSP driver and reset the
+ * stream's indexes
+ */
+ call_back = true;
+ reset_index = true;
+ } else
+ call_back = false;
+ } else {
+ if (test_bit(INTEL_ALSA_SSP_STREAM_STARTED,
+ &str_info->stream_status)) {
+ /*
+ * the stream is on going
+ */
+ call_back = true;
+ } else {
+ WARN(1, "ALSA_SSP: FCT %s spurious playback "
+ "DMA complete 1 ?!\n",
+ __func__);
+ return -EBUSY;
+ }
+ }
+ } else {
+ WARN(1, "ALSA_SSP: FCT %s spurious playback DMA "
+ "complete 2 ?!\n",
+ __func__);
+ return -EBUSY;
+ }
+
+ if (call_back == true) {
+ pr_debug("ALSA_SSP: playback (REQ=%d,CB=%d): PLAYBACK_DMA_REQ_COMPLETE\n",
+ sb_rx->period_req_index, sb_rx->period_cb_index);
+
+ if (reset_index) {
+ sb_rx->period_cb_index = 0;
+ sb_rx->period_req_index = 0;
+ } else if (++(sb_rx->period_cb_index) >= sb_rx->period_index_max)
+ sb_rx->period_cb_index = 0;
+
+ /*
+ * Launch the next Playback request if no CLOSE
+ * has been requested
+ */
+ intel_alsa_ssp_dma_capture_req(str_info);
+ /*
+ * Call the snd_pcm_period_elapsed to inform ALSA
+ * kernel that a ring
+ * buffer period has been played
+ */
+ snd_pcm_period_elapsed(str_info->substream);
+
+ }
+ return 0;
+} /* intel_alsa_ssp_dma_capture_complete */
diff --git a/sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_interface.h b/sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_interface.h
new file mode 100644
index 00000000000..dfc6f91366c
--- /dev/null
+++ b/sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_interface.h
@@ -0,0 +1,98 @@
+/*
+ * intel_alsa_ssp_hw_interface.h
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Authors: Selma Bensaid <selma.bensaid@intel.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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef _INTEL_ALSA_SSP_HW_INTERFACE_H
+#define _INTEL_ALSA_SSP_HW_INTERFACE_H
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <sound/core.h>
+
+
+#define INTEL_ALSA_SSP_PLAYBACK 0
+#define INTEL_ALSA_SSP_CAPTURE 1
+
+#define INTEL_ALSA_SSP_SND_CARD_MAX_DEVICES 2
+
+#define INTEL_ALSA_BT_DEVICE_ID 0
+#define INTEL_ALSA_FM_DEVICE_ID 1
+
+enum intel_alsa_ssp_stream_status {
+ INTEL_ALSA_SSP_STREAM_INIT = 0,
+ INTEL_ALSA_SSP_STREAM_STARTED,
+ INTEL_ALSA_SSP_STREAM_RUNNING,
+ INTEL_ALSA_SSP_STREAM_PAUSED,
+ INTEL_ALSA_SSP_STREAM_DROPPED,
+};
+
+enum intel_alsa_ssp_control_id {
+ INTEL_ALSA_SSP_CTRL_SND_OPEN = 0x1000,
+ INTEL_ALSA_SSP_CTRL_SND_PAUSE = 0x1001,
+ INTEL_ALSA_SSP_CTRL_SND_RESUME = 0x1002,
+ INTEL_ALSA_SSP_CTRL_SND_CLOSE = 0x1003,
+};
+
+/**
+ * @period_req_index: ALSA index ring buffer updated by the DMA transfer
+ * request goes from 0 .. (period_size -1)
+ * @period_cb_index: ALSA index ring buffer updated by the DMA transfer
+ * callback goes from 0 .. (period_size -1)
+ * @period_index_max : ALSA ring Buffer number of periods
+ * @addr: Virtual address of the DMA transfer
+ * @length: length in bytes of the DMA transfer
+ * @dma_running: Status of DMA transfer
+ */
+struct intel_alsa_ssp_dma_buf {
+ u32 period_req_index;
+ u32 period_cb_index;
+ u32 period_index_max;
+ u8 *addr;
+ int length;
+};
+
+struct intel_alsa_ssp_stream_info {
+ struct intel_alsa_ssp_dma_buf dma_slot;
+ struct snd_pcm_substream *substream;
+ ssize_t dbg_cum_bytes;
+ unsigned long stream_status;
+ unsigned int device_id;
+ unsigned int stream_dir;
+ unsigned int device_offset;
+ unsigned int stream_index;
+};
+
+struct intel_alsa_stream_status {
+ void *ssp_handle;
+ spinlock_t lock;
+ unsigned long stream_info;
+
+};
+int intel_alsa_ssp_control(int command, struct intel_alsa_ssp_stream_info *pl_str_info);
+int intel_alsa_ssp_transfer_data(struct intel_alsa_ssp_stream_info *str_info);
+int intel_alsa_ssp_dma_capture_complete(void *param);
+int intel_alsa_ssp_dma_playback_complete(void *param);
+void intel_alsa_reset_ssp_status(void);
+
+#endif /* _INTEL_ALSA_SSP_HW_INTERFACE_H */
diff --git a/sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_settings.h b/sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_settings.h
new file mode 100644
index 00000000000..10e8fd8bb49
--- /dev/null
+++ b/sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_settings.h
@@ -0,0 +1,104 @@
+/*
+ * intel_alsa_ssp_hw_private.h
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Authors: Selma Bensaid <selma.bensaid@intel.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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef _LINUX_INTEL_ALSA_SSP_HW_PRIVATE_H
+#define _LINUX_INTEL_ALSA_SSP_HW_PRIVATE_H
+
+#include <sound/core.h>
+#include <linux/intel_mid_i2s_if.h>
+
+
+const struct intel_mid_i2s_settings a_alsa_ssp_stream_settings[INTEL_ALSA_SSP_SND_CARD_MAX_DEVICES] = {
+ {
+ .mode = SSP_IN_NETWORK_MODE,
+ .rx_fifo_interrupt = SSP_RX_FIFO_OVER_INT_ENABLE,
+ .tx_fifo_interrupt = SSP_TX_FIFO_UNDER_INT_ENABLE,
+ .frame_format = PSP_FORMAT,
+ .master_mode_clk_selection = SSP_MASTER_CLOCK_UNDEFINED,
+ .frame_rate_divider_control = 1,
+ .master_mode_serial_clock_rate = 0xFFFF,
+ .data_size = 16,
+ .tx_tristate_phase = TXD_TRISTATE_LAST_PHASE_OFF,
+ .tx_tristate_enable = TXD_TRISTATE_OFF,
+ .slave_clk_free_running_status = SLAVE_SSPCLK_ON_DURING_TRANSFER_ONLY,
+ .sspslclk_direction = SSPSCLK_SLAVE_MODE,
+ .sspsfrm_direction = SSPSFRM_SLAVE_MODE,
+ .ssp_duplex_mode = RX_AND_TX_MODE,
+ .ssp_trailing_byte_mode = SSP_TRAILING_BYTE_HDL_BY_IA,
+ .ssp_tx_dma = SSP_TX_DMA_ENABLE,
+ .ssp_rx_dma = SSP_RX_DMA_ENABLE,
+ .ssp_rx_timeout_interrupt_status = SSP_RX_TIMEOUT_INT_DISABLE,
+ .ssp_trailing_byte_interrupt_status = SSP_TRAILING_BYTE_INT_ENABLE,
+ .ssp_loopback_mode_status = SSP_LOOPBACK_OFF,
+ .ssp_rx_fifo_threshold = 8,
+ .ssp_tx_fifo_threshold = 7,
+ .ssp_frmsync_timing_bit = NEXT_FRMS_ASS_AFTER_END_OF_T4,
+ .ssp_frmsync_pol_bit = SSP_FRMS_ACTIVE_HIGH,
+ .ssp_end_transfer_state = SSP_END_DATA_TRANSFER_STATE_LOW,
+ .ssp_serial_clk_mode = SSP_CLK_MODE_1,
+ .ssp_psp_T1 = 0,
+ .ssp_psp_T2 = 0,
+ .ssp_psp_T4 = 0,
+ .ssp_psp_T5 = 0,
+ .ssp_psp_T6 = 1,
+ .ssp_active_tx_slots_map = 0x01,
+ .ssp_active_rx_slots_map = 0x01
+ },
+ {
+ .mode = SSP_IN_NETWORK_MODE,
+ .rx_fifo_interrupt = SSP_RX_FIFO_OVER_INT_ENABLE,
+ .tx_fifo_interrupt = SSP_TX_FIFO_UNDER_INT_ENABLE,
+ .frame_format = PSP_FORMAT,
+ .master_mode_clk_selection = SSP_MASTER_CLOCK_UNDEFINED,
+ .frame_rate_divider_control = 1,
+ .master_mode_serial_clock_rate = 0xFFFF,
+ .data_size = 32,
+ .tx_tristate_phase = TXD_TRISTATE_LAST_PHASE_OFF,
+ .tx_tristate_enable = TXD_TRISTATE_OFF,
+ .slave_clk_free_running_status = SLAVE_SSPCLK_ON_DURING_TRANSFER_ONLY,
+ .sspslclk_direction = SSPSCLK_SLAVE_MODE,
+ .sspsfrm_direction = SSPSFRM_SLAVE_MODE,
+ .ssp_duplex_mode = RX_AND_TX_MODE,
+ .ssp_trailing_byte_mode = SSP_TRAILING_BYTE_HDL_BY_IA,
+ .ssp_tx_dma = SSP_TX_DMA_ENABLE,
+ .ssp_rx_dma = SSP_RX_DMA_ENABLE,
+ .ssp_rx_timeout_interrupt_status = SSP_RX_TIMEOUT_INT_DISABLE,
+ .ssp_trailing_byte_interrupt_status = SSP_TRAILING_BYTE_INT_ENABLE,
+ .ssp_loopback_mode_status = SSP_LOOPBACK_OFF,
+ .ssp_rx_fifo_threshold = 8,
+ .ssp_tx_fifo_threshold = 7,
+ .ssp_frmsync_timing_bit = NEXT_FRMS_ASS_WITH_LSB_PREVIOUS_FRM,
+ .ssp_frmsync_pol_bit = SSP_FRMS_ACTIVE_HIGH ,
+ .ssp_end_transfer_state = SSP_END_DATA_TRANSFER_STATE_LOW,
+ .ssp_serial_clk_mode = SSP_CLK_MODE_1,
+ .ssp_psp_T1 = 0,
+ .ssp_psp_T2 = 0,
+ .ssp_psp_T4 = 0,
+ .ssp_psp_T5 = 0,
+ .ssp_psp_T6 = 1,
+ .ssp_active_tx_slots_map = 0x00,
+ .ssp_active_rx_slots_map = 0x01
+ }
+};
+
+#endif /* _LINUX_INTEL_ALSA_SSP_HW_PRIVATE_H */
diff --git a/sound/pci/intel_mid_i2s/intel_alsa_ssp_snd_card.c b/sound/pci/intel_mid_i2s/intel_alsa_ssp_snd_card.c
new file mode 100644
index 00000000000..5c540090804
--- /dev/null
+++ b/sound/pci/intel_mid_i2s/intel_alsa_ssp_snd_card.c
@@ -0,0 +1,616 @@
+/*
+ * intel_alsa_ssp_snd_card.c - Intel Sound card driver for SSP
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Authors: Selma Bensaid <selma.bensaid@intel.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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * ALSA driver for SSP sound card
+ * This Driver provides a sound card with 2 PCM devices:
+ * - a BT device which offer a capture and a playback stream
+ * - an FM device which offer a capture stream
+ * The ALSA SSP Driver interfaces with the Intel MID SSP Driver to
+ * send and receive the PCM samples between the Penwell SSP peripheral
+ * and the BT/PCM chipset
+ *
+ */
+
+
+#include "intel_alsa_ssp_snd_card.h"
+
+MODULE_AUTHOR("Selma Bensaid <selma.bensaid@intel.com>");
+MODULE_DESCRIPTION("Intel ALSA SSP Sound Card Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{Intel,INTEL_ALSA_SSP_SND_CARD");
+MODULE_VERSION(INTEL_ALSA_SSP_DRIVER_VERSION);
+
+
+static struct intel_alsa_ssp_card_info *p_alsa_ssp_snd_card;
+
+/* Index of sound card will be allocated by the kernel */
+static int v_intel_alsa_ssp_card_index = SNDRV_DEFAULT_IDX1;
+static char *p_intel_alsa_ssp_card_id = SNDRV_DEFAULT_STR1;
+
+module_param(v_intel_alsa_ssp_card_index, int, 0444);
+MODULE_PARM_DESC(v_intel_alsa_ssp_card_index,
+ "Index value for ALSA SSP PCM soundcard.");
+module_param(p_intel_alsa_ssp_card_id, charp, 0444);
+MODULE_PARM_DESC(p_intel_alsa_ssp_card_id,
+ "ID string ALSA SSP PCM soundcard.");
+
+
+/*
+ * snd_i2s_alsa_open- to set runtime parameters during stream start
+ * This function is called by ALSA framework when stream is started
+ *
+ * Input parameters
+ * @substream: substream for which the function is called
+ *
+ * Output parameters
+ * @ret_val : status, 0 ==> OK
+ *
+ */
+int snd_i2s_alsa_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *pl_stream_runtime;
+ struct intel_alsa_ssp_stream_info *pl_stream_info;
+ int ret_value, i;
+ unsigned int device;
+
+ WARN(!substream, "ALSA_SSP: ERROR NULL substream\n");
+ if (!substream)
+ return -EINVAL;
+
+ WARN(!substream->runtime, "ALSA_SSP: ERROR NULL substream->runtime\n");
+ if (!substream->runtime)
+ return -EINVAL;
+ pl_stream_runtime = substream->runtime;
+
+ WARN(!substream->pcm, "ALSA_SSP: ERROR NULL substream->pcm\n");
+ if (!substream->pcm)
+ return -EINVAL;
+
+ device = substream->pcm->device;
+
+ pr_debug("ALSA_SSP: FCT %s snd pcm device %d\n", __func__,
+ substream->pcm->device);
+
+ /* set the runtime hw parameter with local snd_pcm_hardware struct */
+ pl_stream_runtime->hw = *(s_dev_info[device].stream_hw_param);
+
+ /*
+ * setup the internal data structure stream pointers based on it being
+ * playback or capture stream
+ */
+ pl_stream_info = kzalloc(sizeof(*pl_stream_info), GFP_KERNEL);
+
+ if (!pl_stream_info)
+ return -ENOMEM;
+
+ pl_stream_info->device_id = device;
+ pl_stream_info->stream_status = 0;
+
+ pl_stream_info->stream_index = 0;
+ for (i = 0; i < device; i++)
+ pl_stream_info->stream_index += s_dev_info[i].nb_capt_stream +
+ s_dev_info[i].nb_play_stream;
+
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ pl_stream_info->stream_dir = INTEL_ALSA_SSP_PLAYBACK;
+ pl_stream_info->stream_index++;
+ } else
+ pl_stream_info->stream_dir = INTEL_ALSA_SSP_CAPTURE;
+
+
+ /* Initialize SSP1 driver */
+ /* Store the Stream information */
+ pl_stream_runtime->private_data = pl_stream_info;
+
+ ret_value = intel_alsa_ssp_control(INTEL_ALSA_SSP_CTRL_SND_OPEN,
+ pl_stream_info);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ p_alsa_ssp_snd_card->playback_cnt++;
+ else
+ p_alsa_ssp_snd_card->capture_cnt++;
+
+ return snd_pcm_hw_constraint_integer(pl_stream_runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+} /* intel_alsa_ssp_close */
+
+/*
+ * snd_i2s_alsa_close- to free parameters when stream is stopped
+ * This function is called by ALSA framework when stream is stopped
+ *
+ * Input parameters
+ * @substream: substream for which the function is called
+ *
+ * Output parameters
+ * @ret_val : status, 0 ==> OK
+ *
+ */
+int snd_i2s_alsa_close(struct snd_pcm_substream *substream)
+{
+ struct intel_alsa_ssp_stream_info *str_info;
+ int ret_val = 0;
+
+ WARN(!substream, "ALSA_SSP: ERROR NULL substream\n");
+ if (!substream)
+ return -EINVAL;
+
+ WARN(!substream->runtime, "ALSA_SSP: ERROR NULL substream->runtime\n");
+ if (!substream->runtime)
+ return -EINVAL;
+ str_info = substream->runtime->private_data;
+
+ if (str_info) {
+ /* SST API to actually stop/free the stream */
+ ret_val = intel_alsa_ssp_control(INTEL_ALSA_SSP_CTRL_SND_CLOSE,
+ str_info);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ p_alsa_ssp_snd_card->playback_cnt--;
+ else
+ p_alsa_ssp_snd_card->capture_cnt--;
+ } else {
+ WARN(1, "ALSA_SSP: ERROR p_stream_info is NULL\n");
+ return -EINVAL;
+ }
+ pr_debug("ALSA SSP CLOSE Stream Direction = %d\n",
+ str_info->stream_dir);
+
+ pr_debug("ALSA SSP CLOSE: Playback cnt = %d Capture cnt = %d\n",
+ p_alsa_ssp_snd_card->playback_cnt,
+ p_alsa_ssp_snd_card->capture_cnt);
+
+ kfree(substream->runtime->private_data);
+
+ return ret_val;
+} /* snd_i2s_alsa_close */
+
+/*
+ * snd_i2s_alsa_pcm_trigger- stream activities are handled here
+ * This function is called whenever a stream activity is invoked
+ * The Trigger function is called in an atomic context
+ *
+ * Input parameters
+ * @substream:substream for which the stream function is called
+ * @cmd:the stream command thats requested from upper layer
+ *
+ * Output parameters
+ * @ret_val : status, 0 ==> OK
+ *
+ */
+int snd_i2s_alsa_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int ret_val = 0;
+ struct intel_alsa_ssp_stream_info *str_info;
+ struct snd_pcm_runtime *pl_runtime;
+ struct intel_alsa_ssp_dma_buf *pl_dma_buf;
+ bool trigger_start = true;
+
+ WARN(!substream, "ALSA_SSP: ERROR NULL substream\n");
+ if (!substream)
+ return -EINVAL;
+
+ WARN(!substream->runtime, "ALSA_SSP: ERROR NULL substream->runtime\n");
+ if (!substream->runtime)
+ return -EINVAL;
+ pl_runtime = substream->runtime;
+
+ WARN(!pl_runtime->private_data, "ALSA_SSP: ERROR NULL pl_runtime->private_data\n");
+ if (!pl_runtime->private_data)
+ return -EINVAL;
+
+ str_info = pl_runtime->private_data;
+
+ pl_dma_buf = &(str_info->dma_slot);
+ pr_debug("ALSA_SSP: snd_i2s_alsa_pcm_trigger CMD = 0x%04X\n", cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ pr_debug("ALSA_SSP: SNDRV_PCM_TRIGGER_START for device %d\n",
+ str_info->device_id);
+ pr_debug("ALSA_SSP: Trigger Start period_size =%ld\n",
+ pl_runtime->period_size);
+
+ str_info->substream = substream;
+
+ if (!test_and_set_bit(INTEL_ALSA_SSP_STREAM_STARTED,
+ &str_info->stream_status)) {
+ if (test_bit(INTEL_ALSA_SSP_STREAM_DROPPED,
+ &str_info->stream_status)) {
+ pr_debug("ALSA SSP: Do not restart the trigger,"
+ "stream running already\n");
+ trigger_start = false;
+ } else
+ trigger_start = true;
+ } else {
+ WARN(1, "ALSA SSP: ERROR 2 consecutive TRIGGER_START\n");
+ return -EBUSY;
+ }
+
+ /* Store the substream locally */
+ if (trigger_start) {
+ pl_dma_buf->length = frames_to_bytes(pl_runtime,
+ pl_runtime->period_size);
+ pl_dma_buf->addr = pl_runtime->dma_area;
+ pl_dma_buf->period_index_max = pl_runtime->periods;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ str_info->stream_dir = INTEL_ALSA_SSP_PLAYBACK;
+ ret_val = intel_alsa_ssp_transfer_data(str_info);
+ } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ str_info->stream_dir = INTEL_ALSA_SSP_CAPTURE;
+ ret_val = intel_alsa_ssp_transfer_data(str_info);
+ } else {
+ WARN(1, "ALSA_SSP: SNDRV_PCM_TRIGGER_START Bad Stream: %d\n",
+ substream->stream);
+ return -EINVAL;
+ }
+ }
+ str_info->dbg_cum_bytes += frames_to_bytes(substream->runtime,
+ substream->runtime->period_size);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ pr_debug("ALSA_SSP: SNDRV_PCM_TRIGGER_STOP\n");
+
+ if (test_and_clear_bit(INTEL_ALSA_SSP_STREAM_STARTED,
+ &str_info->stream_status))
+ set_bit(INTEL_ALSA_SSP_STREAM_DROPPED, &str_info->stream_status);
+ else {
+ WARN(1, "ALSA SSP: trigger START/STOP mismatch\n");
+ return -EBUSY;
+ }
+ break;
+
+ default:
+ WARN(1, "ALSA_SSP: snd_i2s_alsa_pcm_trigger Bad Command\n");
+ return -EINVAL;
+ break;
+ }
+ return ret_val;
+}
+
+/**
+ * snd_i2s_alsa_hw_params - Allocate memory for Ring Buffer according
+ * to hw_params.
+ * It's called in a non-atomic context
+ *
+ * * Input parameters
+ * @substream:substream for which the stream function is called
+ * @hw_params: stream command thats requested from upper layer
+ *
+ * Output parameters
+ * @ret_val : status, 0 ==> OK
+ *
+ */
+int snd_i2s_alsa_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int ret_val;
+
+ WARN(!substream, "ALSA_SSP: ERROR NULL substream\n");
+ if (!substream)
+ return -EINVAL;
+
+ /*
+ * Allocates the DMA buffer for the substream
+ * This callback could be called several time
+ * snd_pcm_lib_malloc_pages allows to avoid memory leak
+ * as it release already allocated memory when already allocated
+ */
+ ret_val = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+
+ memset(substream->runtime->dma_area, 0, params_buffer_bytes(hw_params));
+
+ return ret_val;
+}
+
+/**
+ * snd_i2s_alsa_hw_free - Called to release ressources allocated by
+ * snd_i2s_alsa_hw_params
+ * It's called in a non-atomic context
+ *
+ * * Input parameters
+ * @substream:substream for which the stream function is called
+ *
+ * Output parameters
+ * @ret_val : status, 0 ==> OK
+ *
+ */
+int snd_i2s_alsa_hw_free(struct snd_pcm_substream *substream)
+{
+ WARN(!substream, "ALSA_SSP: ERROR NULL substream\n");
+ if (!substream)
+ return -EINVAL;
+
+ return snd_pcm_lib_free_pages(substream);
+}
+
+/*
+ * snd_i2s_alsa_pcm_pointer- to send the current buffer pointer
+ * processed by HW
+ * This function is called by ALSA framework to get the current HW buffer ptr
+ * to check the Ring Buffer Status
+ *
+ * Input parameters
+ * @substream: pointer to the substream for which the function is called
+ *
+ * Output parameters
+ * @pcm_pointer: indicates the number of samples played
+ *
+ */
+snd_pcm_uframes_t snd_i2s_alsa_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct intel_alsa_ssp_stream_info *str_info;
+ struct intel_alsa_ssp_dma_buf *dma_info;
+ unsigned long pcm_pointer = 0;
+
+ WARN(!substream, "ALSA_SSP: ERROR NULL substream\n");
+ if (!substream)
+ return -EINVAL;
+
+ WARN(!substream->runtime, "ALSA_SSP: ERROR NULL substream->runtime\n");
+ if (!substream->runtime)
+ return -EINVAL;
+ str_info = substream->runtime->private_data;
+
+ WARN(!str_info, "ALSA_SSP: ERROR NULL str_info\n");
+ if (!str_info)
+ return -EINVAL;
+ dma_info = &(str_info->dma_slot);
+
+ if (str_info->stream_dir == INTEL_ALSA_SSP_PLAYBACK)
+ pcm_pointer = (unsigned long) (dma_info->period_cb_index
+ * substream->runtime->period_size);
+ else if (str_info->stream_dir == INTEL_ALSA_SSP_CAPTURE)
+ pcm_pointer = (unsigned long) (dma_info->period_cb_index
+ * substream->runtime->period_size);
+
+ pr_debug("ALSA_SSP: Frame bits:: %d period_size :: %d periods :: %d\n",
+ (int) substream->runtime->frame_bits,
+ (int) substream->runtime->period_size,
+ (int) substream->runtime->periods);
+
+ pr_debug("ALSA_SSP: snd_i2s_alsa_pcm_pointer returns %ld\n",
+ pcm_pointer);
+
+ return pcm_pointer;
+}
+
+/*
+ * snd_i2s_alsa_pcm_prepare- internal preparation before starting a stream
+ * This function is called when a stream is started for internal preparation
+ *
+ * Input parameters
+ * @substream: substream for which the function is called
+ * Output parameters
+ * NA
+ */
+int snd_i2s_alsa_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+/**
+ *
+ * Card/Device Functions
+ *
+ */
+
+/*
+ * intel_alsa_create_pcm_device : to setup pcm for the card
+ *
+ * Input parameters
+ * @card : pointer to the sound card structure
+ * @p_alsa_ssp_snd_card : pointer to internal context
+ *
+ * Output parameters
+ * @ret_val : status, 0 ==> OK
+ *
+ */
+static int intel_alsa_create_pcm_device(struct snd_card *card,
+ struct intel_alsa_ssp_card_info *p_alsa_ssp_snd_card)
+{
+ struct snd_pcm *pl_pcm;
+ int i, ret_val = 0;
+
+ WARN(!card, "ALSA_SSP: ERROR NULL card\n");
+ if (!card)
+ return -EINVAL;
+
+ WARN(!p_alsa_ssp_snd_card, "ALSA_SSP: ERROR NULL p_alsa_ssp_snd_card\n");
+ if (!p_alsa_ssp_snd_card)
+ return -EINVAL;
+
+ /*
+ * The alsa_ssp driver handles provide 2 PCM devices :
+ * device 0 ==> BT with 1 capture sub-stream + 1 play sub-stream
+ * device 1 ==> FM with 1 capture sub-stream + 0 play sub-stream
+ * These 2 devices are exclusive
+ */
+ for (i = 0; i < INTEL_ALSA_SSP_SND_CARD_MAX_DEVICES; i++) {
+
+ ret_val = snd_pcm_new(card, s_dev_info[i].dev_name, i,
+ s_dev_info[i].nb_play_stream,
+ s_dev_info[i].nb_capt_stream,
+ &pl_pcm);
+
+ if (ret_val)
+ return ret_val;
+
+ /* setup the ops for playback and capture streams */
+ snd_pcm_set_ops(pl_pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &s_alsa_ssp_playback_ops);
+ snd_pcm_set_ops(pl_pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &s_alsa_ssp_capture_ops);
+
+ /* setup private data which can be retrieved when required */
+ pl_pcm->private_data = p_alsa_ssp_snd_card;
+ pl_pcm->info_flags = 0;
+
+ strncpy(pl_pcm->name, card->shortname, strlen(card->shortname));
+
+ /*
+ * allocate DMA pages for ALSA stream operations pre-allocation
+ * to all substreams of the given pcm for the specified DMA type
+ */
+ snd_pcm_lib_preallocate_pages_for_all(pl_pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data
+ (GFP_KERNEL),
+ s_dev_info[i].stream_hw_param->buffer_bytes_max,
+ s_dev_info[i].stream_hw_param->buffer_bytes_max);
+ }
+
+ return ret_val;
+}
+
+/**
+ * intel_alsa_create_snd_card : function which creates sound card and PCM
+ * device
+ *
+ * Input parameters
+ * NA
+ *
+ * Output parameters
+ * @ret_val : status, 0 ==> OK
+ *
+ */
+int intel_alsa_create_snd_card(void)
+{
+ struct snd_card *card;
+ int ret_val;
+
+ /*
+ * Allocate the global structure of the Driver p_alsa_ssp_snd_card
+ */
+ p_alsa_ssp_snd_card = kzalloc(sizeof(*p_alsa_ssp_snd_card), GFP_KERNEL);
+
+ if (!p_alsa_ssp_snd_card) {
+ WARN(1, "ALSA SSP ERROR: malloc fail\n");
+ return -ENOMEM;
+ }
+
+ /*
+ * create a card instance with ALSA framework
+ */
+ ret_val = snd_card_create(v_intel_alsa_ssp_card_index,
+ p_intel_alsa_ssp_card_id, THIS_MODULE, 0, &card);
+
+ if (ret_val) {
+ pr_debug("ALSA SSP ERROR: snd_card_create fail\n");
+ goto free_allocs;
+ }
+
+ p_alsa_ssp_snd_card->card = card;
+ p_alsa_ssp_snd_card->card_id = p_intel_alsa_ssp_card_id;
+ p_alsa_ssp_snd_card->playback_cnt = p_alsa_ssp_snd_card->capture_cnt = 0;
+
+ strncpy(card->driver, INTEL_ALSA_SSP_CARD_NAME,
+ strlen(INTEL_ALSA_SSP_CARD_NAME));
+
+ strncpy(card->shortname, INTEL_ALSA_SSP_CARD_NAME,
+ strlen(INTEL_ALSA_SSP_CARD_NAME));
+
+ ret_val = intel_alsa_create_pcm_device(card, p_alsa_ssp_snd_card);
+ if (ret_val) {
+ pr_err("ALSA SSP ERROR: failed to allocate p_alsa_ssp_snd_card\n");
+ goto free_allocs;
+ }
+
+ card->private_data = &p_alsa_ssp_snd_card;
+
+ ret_val = snd_card_register(card);
+
+ if (ret_val) {
+ pr_err("ALSA SSP: snd_card_register failed\n");
+ goto free_allocs;
+ }
+
+ /*
+ * Reset Internal parameters
+ */
+ intel_alsa_reset_ssp_status();
+
+ return ret_val;
+
+free_allocs:
+ snd_card_free(card);
+ kfree(p_alsa_ssp_snd_card);
+ WARN(1, "ALSA SSP ERROR: Intel ALSA SSP Card Creation failed\n");
+ return ret_val;
+} /* intel_alsa_create_snd_card */
+
+/**
+ * intel_alsa_remove_snd_card : function which removes the sound card and PCM
+ * devices to the SSP driver Interface
+ * This function is registered for exit and it's called when the device is
+ * uninitialized
+ *
+ * Input parameters
+ * NA
+ * Output parameters
+ * @ret_val : status, 0 ==> OK
+ */
+int intel_alsa_remove_snd_card(void)
+{
+ int ret_val;
+
+ if (p_alsa_ssp_snd_card) {
+ pr_debug("ALSA_SSP: p_alsa_ssp_snd_card: 0x%08X\n",
+ (u32) p_alsa_ssp_snd_card);
+ snd_card_free(p_alsa_ssp_snd_card->card);
+ kfree(p_alsa_ssp_snd_card);
+ ret_val = 0;
+ } else {
+ WARN(1, "ALSA_SSP: p_alsa_ssp_snd_card is NULL\n");
+ ret_val = -EINVAL;
+ }
+ return ret_val;
+
+} /* intel_alsa_remove_snd_card */
+
+static int __init intel_alsa_ssp_init(void)
+{
+ int status;
+ pr_info("Intel ALSA SSP Driver\n Version %s\n",
+ INTEL_ALSA_SSP_DRIVER_VERSION);
+ status = intel_alsa_create_snd_card();
+ return status;
+
+}
+
+static void __exit intel_alsa_ssp_exit(void)
+{
+ int status;
+
+ status = intel_alsa_remove_snd_card();
+ pr_info("Intel ALSA SSP Driver Remove with status = %d\n", status);
+
+}
+
+module_init(intel_alsa_ssp_init);
+module_exit(intel_alsa_ssp_exit);
diff --git a/sound/pci/intel_mid_i2s/intel_alsa_ssp_snd_card.h b/sound/pci/intel_mid_i2s/intel_alsa_ssp_snd_card.h
new file mode 100644
index 00000000000..f0e3439b261
--- /dev/null
+++ b/sound/pci/intel_mid_i2s/intel_alsa_ssp_snd_card.h
@@ -0,0 +1,166 @@
+/*
+ * intel_alsa_ssp_snd_card.h
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Authors: Selma Bensaid <selma.bensaid@intel.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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#ifndef _LINUX_INTEL_ALSA_SSP_SND_CARD_H
+#define _LINUX_INTEL_ALSA_SSP_SND_CARD_H
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/sched.h>
+#include <sound/initval.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+
+#include "intel_alsa_ssp_hw_interface.h"
+
+
+
+int snd_i2s_alsa_open(struct snd_pcm_substream *substream);
+int snd_i2s_alsa_close(struct snd_pcm_substream *substream);
+int snd_i2s_alsa_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params);
+int snd_i2s_alsa_pcm_prepare(struct snd_pcm_substream *substream);
+int snd_i2s_alsa_pcm_trigger(struct snd_pcm_substream *substream, int cmd);
+int snd_i2s_alsa_hw_free(struct snd_pcm_substream *substream);
+snd_pcm_uframes_t snd_i2s_alsa_pcm_pointer(struct snd_pcm_substream *substream);
+int snd_i2s_alsa_pcm_prepare(struct snd_pcm_substream *substream);
+
+/*
+ * Defines
+ */
+#define INTEL_ALSA_SSP_DRIVER_VERSION "1.0.3"
+#define INTEL_ALSA_SSP_CARD_NAME "IntelALSASSP"
+
+
+/*
+ * Structures Definition
+ */
+struct intel_alsa_ssp_card_info {
+ struct snd_card *card;
+ char *card_id;
+ int playback_cnt;
+ int capture_cnt;
+ int card_index;
+};
+
+struct intel_alsa_ssp_dev_info {
+ char dev_name[32];
+ int nb_play_stream;
+ int nb_capt_stream;
+ struct snd_pcm_hardware *stream_hw_param;
+};
+
+/*
+ * Global Variables
+ */
+
+/* Data path functionalities */
+struct snd_pcm_hardware BT_alsa_hw_param = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_DOUBLE |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_SYNC_START),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+ .rates = (SNDRV_PCM_RATE_8000),
+ .rate_min = 8000,
+ .rate_max = 8000,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = (320*1024),
+ .period_bytes_min = 32,
+ .period_bytes_max = (320*1024),
+ .periods_min = 2,
+ .periods_max = (1024*2),
+ .fifo_size = 0,
+};
+
+struct snd_pcm_hardware FM_alsa_hw_param = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_DOUBLE |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_SYNC_START),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE),
+ .rates = (SNDRV_PCM_RATE_48000),
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (640*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (640*1024),
+ .periods_min = 2,
+ .periods_max = (1024*2),
+ .fifo_size = 0,
+};
+
+struct snd_pcm_ops s_alsa_ssp_playback_ops = {
+ .open = snd_i2s_alsa_open,
+ .close = snd_i2s_alsa_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_i2s_alsa_hw_params,
+ .hw_free = snd_i2s_alsa_hw_free,
+ .trigger = snd_i2s_alsa_pcm_trigger,
+ .pointer = snd_i2s_alsa_pcm_pointer,
+ .prepare = snd_i2s_alsa_pcm_prepare,
+};
+
+struct snd_pcm_ops s_alsa_ssp_capture_ops = {
+ .open = snd_i2s_alsa_open,
+ .close = snd_i2s_alsa_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_i2s_alsa_hw_params,
+ .hw_free = snd_i2s_alsa_hw_free,
+ .trigger = snd_i2s_alsa_pcm_trigger,
+ .pointer = snd_i2s_alsa_pcm_pointer,
+ .prepare = snd_i2s_alsa_pcm_prepare,
+};
+
+struct intel_alsa_ssp_dev_info s_dev_info[INTEL_ALSA_SSP_SND_CARD_MAX_DEVICES] = {
+ {
+ .dev_name = "BT_DEVICE",
+ .nb_play_stream = 1,
+ .nb_capt_stream = 1,
+ .stream_hw_param = &BT_alsa_hw_param,
+
+ },
+ {
+ .dev_name = "FM_DEVICE",
+ .nb_play_stream = 0,
+ .nb_capt_stream = 1,
+ .stream_hw_param = &FM_alsa_hw_param,
+
+ }
+};
+
+#endif /* _LINUX_INTEL_ALSA_SSP_SND_CARD_H */