diff options
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 */ |