diff options
author | Selma Bensaid <selma.bensaid@intel.com> | 2010-12-09 10:37:44 +0000 |
---|---|---|
committer | Alan Cox <alan@linux.intel.com> | 2010-12-09 10:37:44 +0000 |
commit | f42e19b29cc9c529746eb7e0a579b179786d205d (patch) | |
tree | 9fabdd500af5fc789f0b23ec57d25be841649fd7 /sound/pci | |
parent | 3912d7e37d67374fcb77c1612ed486bad4dd9993 (diff) | |
download | mrst-s0i3-test-f42e19b29cc9c529746eb7e0a579b179786d205d.tar.gz mrst-s0i3-test-f42e19b29cc9c529746eb7e0a579b179786d205d.tar.xz mrst-s0i3-test-f42e19b29cc9c529746eb7e0a579b179786d205d.zip |
ALSA SSP Driver: ALSA sound card for TI WL1273 Chip (BT/FM/WLAN)
This sound card handles 2 PCM devices
- BT PCM device which support 1 capture substream and 1 playback
substream (8KHz, mono, 16 bits/sample)
- FM device which support 1 capture substream only (the FM TX is not
supported) (48KHz, stereo, 16bits/sample)
These 2 PCM devices are exclusive (i.e FM and BT substreams
cannot run in parallel)
The ALSA SSP driver interfaces with Intel MID I2S driver to
configure the SSP peripheral according to PCM device's settings
and to send/receive PCM samples.
Signed-off-by: Selma Bensaid <selma.bensaid@intel.com>
Signed-off-by: Alan Cox <alan@linux.intel.com>
Diffstat (limited to 'sound/pci')
-rw-r--r-- | sound/pci/Kconfig | 13 | ||||
-rw-r--r-- | sound/pci/intel_mid_i2s/Makefile | 4 | ||||
-rw-r--r-- | sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_interface.c | 529 | ||||
-rw-r--r-- | sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_interface.h | 98 | ||||
-rw-r--r-- | sound/pci/intel_mid_i2s/intel_alsa_ssp_hw_settings.h | 104 | ||||
-rw-r--r-- | sound/pci/intel_mid_i2s/intel_alsa_ssp_snd_card.c | 616 | ||||
-rw-r--r-- | sound/pci/intel_mid_i2s/intel_alsa_ssp_snd_card.h | 166 |
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 */ |