blob: 3cd09d2e18a905311aa769ff613c3c4fe3678c5e [file] [log] [blame]
/*
* mid_ssp.c - ASoC CPU DAI driver for
*
* Copyright (C) 2011-12 Intel Corp
* Author: 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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*
*/
#define FORMAT(fmt) "%s: " fmt, __func__
#define pr_fmt(fmt) KBUILD_MODNAME ": " FORMAT(fmt)
#include <linux/module.h>
#include "mid_ssp.h"
/*
* Default I2S configuration
*/
/*
* TO BE DONE: use mixer to make it more flexible
*/
const struct intel_mid_i2s_settings ssp_platform_i2s_config = {
.master_mode_clk_selection = SSP_MASTER_CLOCK_UNDEFINED,
.master_mode_standard_freq = 0xFFFF,
.tx_tristate_phase = TXD_TRISTATE_LAST_PHASE_OFF,
.slave_clk_free_running_status =
SLAVE_SSPCLK_ON_DURING_TRANSFER_ONLY,
.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,
.rx_fifo_interrupt = SSP_RX_FIFO_OVER_INT_ENABLE,
.tx_fifo_interrupt = SSP_TX_FIFO_UNDER_INT_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 = MID_SSP_RX_FIFO_THRESHOLD,
.ssp_tx_fifo_threshold = MID_SSP_TX_FIFO_THRESHOLD,
.ssp_frmsync_pol_bit = SSP_FRMS_ACTIVE_HIGH,
.ssp_end_transfer_state =
SSP_END_DATA_TRANSFER_STATE_LOW,
.ssp_psp_T1 = 0,
.ssp_psp_T2 = 0,
.ssp_psp_T4 = 0,
.ssp_psp_T5 = 0,
.ssp_psp_T6 = 1,
};
/*
* SSP DAI Internal functions
*/
/**
* ssp_dma_req - This function programs a write or read request
* to the Intel I2S driver
*
* @param substream Pointer to stream structure
* return ret_val Status
*/
static int ssp_dma_req(struct snd_pcm_substream *substream)
{
struct intel_alsa_ssp_stream_info *str_info;
struct intel_ssp_config *ssp_config;
struct snd_pcm_runtime *pl_runtime;
int ret;
#ifdef _LLI_ENABLED_
struct intel_mid_i2s_lli *sg_table = NULL;
int i;
#else
u32 *dma_addr;
#endif /* _LLI_ENABLED_ */
WARN(!substream, "SSP DAI: "
"ERROR NULL substream\n");
if (!substream)
return -EINVAL;
pl_runtime = substream->runtime;
str_info = pl_runtime->private_data;
WARN(!str_info, "SSP DAI: "
"ERROR NULL str_info\n");
if (!str_info)
return -EINVAL;
ssp_config = str_info->ssp_config;
WARN(!ssp_config, "SSP DAI: "
"ERROR NULL ssp_config\n");
if (!ssp_config)
return -EINVAL;
WARN(!ssp_config->i2s_handle, "SSP DAI: "
"ERROR, trying to play a stream however "
"ssp_config->i2s_handle is NULL\n");
if (!ssp_config->i2s_handle)
return -EINVAL;
#ifdef _LLI_ENABLED_
if (!test_bit(INTEL_ALSA_SSP_STREAM_STARTED,
&str_info->stream_status)) {
pr_err("%s: Stream has been stopped before SSP DMA request has been taken into account",
__func__);
return 0;
}
/* Will be executed once until next DAI shutdown */
if (!test_bit(INTEL_ALSA_SSP_STREAM_INIT,
&str_info->stream_status)) {
str_info->length = frames_to_bytes(pl_runtime,
pl_runtime->period_size);
str_info->addr = substream->runtime->dma_area;
sg_table = kzalloc(sizeof(struct intel_mid_i2s_lli) *
pl_runtime->periods,
GFP_KERNEL);
if (sg_table == NULL) {
pr_err("sg_table allocation failed!");
return -EINVAL;
}
for (i = 0; i < pl_runtime->periods; i++) {
sg_table[i].addr = (u32 *) (str_info->addr +
str_info->length * i);
sg_table[i].leng = (u32) str_info->length;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ret = intel_mid_i2s_lli_wr_req(ssp_config->i2s_handle,
sg_table,
pl_runtime->periods,
I2S_CIRCULAR_MODE,
substream);
else
ret = intel_mid_i2s_lli_rd_req(ssp_config->i2s_handle,
sg_table,
pl_runtime->periods,
I2S_CIRCULAR_MODE,
substream);
kfree(sg_table);
if (ret != 0) {
pr_err("SSP DAI: %s request error",
(substream->stream ==
SNDRV_PCM_STREAM_PLAYBACK) ?
"write" : "read");
}
set_bit(INTEL_ALSA_SSP_STREAM_INIT, &str_info->stream_status);
intel_mid_i2s_command(ssp_config->i2s_handle,
SSP_CMD_ENABLE_SSP, NULL);
}
/* Executed at each TRIGGER_START */
intel_mid_i2s_command(ssp_config->i2s_handle,
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
SSP_CMD_ENABLE_DMA_TX_INTR :
SSP_CMD_ENABLE_DMA_RX_INTR, NULL);
return 0;
#else
str_info->length = frames_to_bytes(pl_runtime, pl_runtime->period_size);
str_info->addr = substream->runtime->dma_area;
pr_debug("SSP DAI: FCT %s substream->runtime->dma_area = %p",
__func__, substream->runtime->dma_area);
dma_addr = (u32 *)(str_info->addr + str_info->length
* str_info->period_req_index);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ret = intel_mid_i2s_wr_req(ssp_config->i2s_handle, dma_addr,
str_info->length, substream);
else
ret = intel_mid_i2s_rd_req(ssp_config->i2s_handle, dma_addr,
str_info->length, substream);
if (ret == 0) {
intel_mid_i2s_command(ssp_config->i2s_handle,
SSP_CMD_ENABLE_SSP, NULL);
if (test_and_set_bit(INTEL_ALSA_SSP_STREAM_RUNNING,
&str_info->stream_status)) {
pr_err("SSP DAI: ERROR previous request not handled\n");
return -EBUSY;
}
if (++(str_info->period_req_index) >= pl_runtime->periods)
str_info->period_req_index = 0;
return 0;
} else {
pr_err("SSP DAI: FCT %s read/write req ERROR\n", __func__);
return -EINVAL;
}
#endif /* _LLI_ENABLED_ */
} /* ssp_dma_req */
/**
* ssp_dma_complete - End of capture or playback callback
* called in DMA Complete Tasklet context
* This Callback has in charge of re-programming a new read or 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 or received properly
*
* @param param Pointer to a user data
* return status
*/
static int ssp_dma_complete(void *param)
{
struct snd_pcm_substream *substream;
struct intel_alsa_ssp_stream_info *str_info;
struct snd_pcm_runtime *pl_runtime;
#ifndef _LLI_ENABLED_
bool call_back = false;
bool reset_index = false;
#endif /* _LLI_ENABLED_ */
substream = (struct snd_pcm_substream *)param;
pl_runtime = substream->runtime;
str_info = substream->runtime->private_data;
WARN(!str_info, "SSP DAI: ERROR NULL str_info\n");
if (str_info == NULL)
return -EINVAL;
#ifdef _LLI_ENABLED_
if (!test_bit(INTEL_ALSA_SSP_STREAM_INIT,
&str_info->stream_status)) {
pr_err("Stream already not initialized");
return 0;
}
if (++(str_info->period_cb_index) >= pl_runtime->periods)
str_info->period_cb_index = 0;
if (test_bit(INTEL_ALSA_SSP_STREAM_STARTED, &str_info->stream_status))
snd_pcm_period_elapsed(substream);
else
pr_debug("No call to snd_period_elapsed, stream is not started");
#else
if (test_and_clear_bit(INTEL_ALSA_SSP_STREAM_RUNNING,
&str_info->stream_status)) {
bool dropped = test_and_clear_bit(INTEL_ALSA_SSP_STREAM_DROPPED,
&str_info->stream_status);
bool started = test_bit(INTEL_ALSA_SSP_STREAM_STARTED,
&str_info->stream_status);
if (started) {
/*
* Whatever dropped or not,
* the stream is on going
*/
call_back = true;
}
if (started && dropped) {
/*
* 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
*/
reset_index = true;
}
if (!started && !dropped) {
pr_err("SSP DAI: FCT %s neither started nor dropped",
__func__);
return -EBUSY;
}
} else {
pr_err("SSP DAI: FCT %s called while not running ", __func__);
return -EBUSY;
}
if (call_back == true) {
pr_debug("SSP DAI: playback/capture (REQ=%d,CB=%d): DMA_REQ_COMPLETE\n",
str_info->period_req_index,
str_info->period_cb_index);
if (reset_index) {
str_info->period_cb_index = 0;
str_info->period_req_index = 0;
} else if (++(str_info->period_cb_index) >= pl_runtime->periods)
str_info->period_cb_index = 0;
/*
* Launch the next Capture/Playback request if
* no CLOSE has been requested
*/
ssp_dma_req(substream);
/*
* Call the snd_pcm_period_elapsed to inform ALSA kernel
* that a ringbuffer period has been played
*/
snd_pcm_period_elapsed(substream);
}
#endif /* _LLI_ENABLED_ */
return 0;
} /* ssp_dma_complete */
/**
* intel_mid_ssp_transfer_data - send data buffers
*
* @param work Pointer to stream structure
* return void
*/
void intel_mid_ssp_transfer_data(struct work_struct *work)
{
struct intel_alsa_ssp_stream_info *str_info;
struct snd_pcm_substream *substream;
BUG_ON(!work);
str_info = container_of(work, struct intel_alsa_ssp_stream_info,
ssp_ws);
BUG_ON(!str_info);
substream = str_info->substream;
BUG_ON(!substream);
ssp_dma_req(substream);
} /* intel_mid_ssp_transfer_data */
/*
* SSP PLATFORM
*/
/*
* SSP Platform functions
*/
static int ssp_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime)
{
int retval = 0;
struct snd_soc_dai *dai;
struct snd_pcm *pcm;
pr_debug("SSP DAI: FCT %s enters\n",
__func__);
/*
* Do pre-allocation to all substreams of the given pcm for the
* specified DMA type.
*
*/
dai = soc_runtime->cpu_dai;
pcm = soc_runtime->pcm;
if (dai->driver->playback.channels_min ||
dai->driver->capture.channels_min) {
retval = snd_pcm_lib_preallocate_pages_for_all(pcm,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
SSP_MIN_BUFFER, SSP_MAX_BUFFER);
if (retval) {
pr_err("DMA buffer allocation fail\n");
return retval;
}
}
return retval;
} /* ssp_platform_pcm_new */
static void ssp_platform_pcm_free(struct snd_pcm *pcm)
{
pr_debug("SSP DAI: FCT %s enter\n",
__func__);
/*
* release all pre-allocated buffers on the pcm
*
*/
snd_pcm_lib_preallocate_free_for_all(pcm);
} /* ssp_platform_pcm_free */
/**
* ssp_platform_hw_params - Allocate memory for Ring Buffer according
* to hw_params.
* It's called in a non-atomic context
*
* @param substream Substream for which the stream function is called
* @param hw_params Stream command thats requested from upper layer
* return status 0 ==> OK
*
*/
static int ssp_platform_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
int ret_val;
/*
* 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));
if (ret_val < 0)
return ret_val;
memset(substream->runtime->dma_area, 0, params_buffer_bytes(hw_params));
return 0;
} /* ssp_platform_hw_params */
/*
* ssp_platform_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
*
* @param substream Pointer to the substream for which the function
* is called
*
* return pcm_pointer Indicates the number of samples played
*
*/
static
snd_pcm_uframes_t ssp_platform_pointer(struct snd_pcm_substream *substream)
{
struct intel_alsa_ssp_stream_info *str_info;
unsigned long pcm_pointer = 0;
str_info = substream->runtime->private_data;
WARN(!str_info, "SSP DAI: ERROR NULL str_info\n");
if (!str_info)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
pcm_pointer = (unsigned long) (str_info->period_cb_index
* substream->runtime->period_size);
else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
pcm_pointer = (unsigned long) (str_info->period_cb_index
* substream->runtime->period_size);
pr_debug("SSP DAI: FCT %s Frame bits = %d, period_size = %d, periods = %d\n",
__func__,
(int) substream->runtime->frame_bits,
(int) substream->runtime->period_size,
(int) substream->runtime->periods);
pr_debug("SSP DAI: FCT %s returns %ld\n",
__func__, pcm_pointer);
return pcm_pointer;
} /* ssp_platform_pointer */
static struct snd_pcm_ops ssp_platform_ops = {
.open = NULL,
.close = NULL,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = ssp_platform_hw_params,
.hw_free = NULL,
.prepare = NULL,
.trigger = NULL,
.pointer = ssp_platform_pointer,
};
struct snd_soc_platform_driver soc_ssp_platform_drv = {
.ops = &ssp_platform_ops,
.probe = NULL,
.pcm_new = ssp_platform_pcm_new,
.pcm_free = ssp_platform_pcm_free,
};
/*
* SND SOC DAI OPs
*/
static int ssp_probe(struct snd_soc_dai *cpu_dai)
{
struct intel_ssp_config *ssp_config;
pr_info("SSP DAI: FCT %s enters for CPU_DAI %d\n",
__func__, cpu_dai->id);
ssp_config = kzalloc(sizeof(struct intel_ssp_config), GFP_KERNEL);
if (ssp_config == NULL) {
pr_err("Unable to allocate ssp_config\n");
return -ENOMEM;
}
#ifndef _LLI_ENABLED_
ssp_config->intel_mid_dma_alloc = false;
#endif /* _LLI_ENABLED_ */
ssp_config->ssp_dai_tx_allocated = false;
ssp_config->ssp_dai_rx_allocated = false;
ssp_config->i2s_settings = ssp_platform_i2s_config;
pr_info("SSP DAI: FCT %s ssp_config %p\n",
__func__, ssp_config);
cpu_dai->playback_dma_data = cpu_dai->capture_dma_data = ssp_config;
return 0;
} /* ssp_probe */
static int ssp_remove(struct snd_soc_dai *cpu_dai)
{
struct intel_ssp_config *ssp_config;
WARN(!cpu_dai, "SSP DAI: "
"ERROR NULL cpu_dai\n");
if (!cpu_dai)
return -EINVAL;
ssp_config = cpu_dai->playback_dma_data;
kfree(ssp_config);
return 0;
} /* ssp_remove */
static int ssp_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct intel_ssp_config *ssp_config;
struct snd_pcm_runtime *pl_runtime;
struct intel_alsa_ssp_stream_info *str_info;
struct intel_ssp_info *ssp_info;
struct snd_soc_dai_driver *cpudai_drv = cpu_dai->driver;
unsigned int device;
int ret = 0;
WARN(!cpu_dai->driver, "SSP DAI: "
"FCT %s ERROR NULL cpu_dai->driver\n",
__func__);
if (!cpu_dai->driver)
return -EINVAL;
pr_info("SSP DAI: FCT %s enters for DAI Id = %d\n",
__func__, cpu_dai->driver->id);
ssp_info = dev_get_drvdata(cpu_dai->dev);
WARN(!ssp_info, "SSP DAI: ERROR NULL ssp_info\n");
if (!ssp_info)
return -EINVAL;
pl_runtime = substream->runtime;
ssp_config = snd_soc_dai_get_dma_data(cpu_dai, substream);
WARN(!ssp_config, "SSP DAI: "
"FCT %s ERROR NULL ssp_config\n",
__func__);
if (!ssp_config)
return -EINVAL;
device = cpu_dai->driver->id;
/*
* setup the internal data structure stream pointers based on it being
* playback or capture stream
*/
str_info = kzalloc(sizeof(*str_info), GFP_KERNEL);
if (!str_info) {
pr_err("SSP DAI: str_info alloc failure\n");
return -EINVAL;
}
str_info->substream = substream;
str_info->ssp_config = ssp_config;
str_info->stream_status = 0;
INIT_WORK(&str_info->ssp_ws, intel_mid_ssp_transfer_data);
/*
* Initialize SSPx [x=0,1] driver
* Store the Stream information
*/
pl_runtime->private_data = str_info;
pr_debug("SSP DAI: FCT %s enters cpu_dai->card->name = %s\n",
__func__, cpu_dai->card->name);
if (!cpu_dai->active) {
if (!strcmp(cpudai_drv->name, SSP_BT_DAI_NAME)) {
ssp_config->i2s_handle =
intel_mid_i2s_open(SSP_USAGE_BLUETOOTH_FM);
pr_debug("opening the CPU_DAI for "\
"SSP_USAGE_BLUETOOTH_FM, i2s_handle = %p\n",
ssp_config->i2s_handle);
} else if (!strcmp(cpudai_drv->name, SSP_MODEM_DAI_NAME)) {
ssp_config->i2s_handle =
intel_mid_i2s_open(SSP_USAGE_MODEM);
pr_debug("opening the CPU_DAI for "\
"SSP_USAGE_MODEM, i2s_handle = %p\n",
ssp_config->i2s_handle);
} else {
pr_err("non Valid SOC CARD\n");
return -EINVAL;
}
/* Set the Write Callback */
ret = intel_mid_i2s_set_wr_cb(ssp_config->i2s_handle,
ssp_dma_complete);
if (ret)
return ret;
/* Set the Default Read Callback */
ret = intel_mid_i2s_set_rd_cb(ssp_config->i2s_handle,
ssp_dma_complete);
if (ret)
return ret;
} else {
/*
* do nothing because already Open by sibling substream
*/
pr_debug("SSP DAI: FCT %s Open DO NOTHING\n",
__func__);
}
return 0;
} /* ssp_dai_startup */
static void ssp_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct intel_ssp_config *ssp_config;
struct intel_alsa_ssp_stream_info *str_info;
struct snd_pcm_runtime *runtime;
struct intel_ssp_info *ssp_info;
ssp_config = snd_soc_dai_get_dma_data(cpu_dai, substream);
BUG_ON(!ssp_config);
runtime = substream->runtime;
BUG_ON(!runtime->private_data);
str_info = runtime->private_data;
BUG_ON(!str_info);
ssp_info = dev_get_drvdata(cpu_dai->dev);
BUG_ON(!ssp_info);
/* Cancel pending work */
cancel_work_sync(&str_info->ssp_ws);
switch (substream->stream) {
case SNDRV_PCM_STREAM_PLAYBACK:
/*
* Only Free Tx channel if no playback streams are active
* Shutdown can be called right after a startup if something
* failed (as a concurrency issue
* so this case can happen
*/
if ((!cpu_dai->playback_active) &&
(ssp_config->i2s_settings.ssp_active_tx_slots_map)) {
intel_mid_i2s_command(ssp_config->i2s_handle,
SSP_CMD_FREE_TX, NULL);
pr_debug("SSP DAI: FCT %s TX DMA Channel released\n",
__func__);
}
ssp_config->ssp_dai_tx_allocated = false;
break;
case SNDRV_PCM_STREAM_CAPTURE:
/*
* Only Free Rx channel if no capture streams are active
*/
if ((!cpu_dai->capture_active) &&
(ssp_config->i2s_settings.ssp_active_rx_slots_map)) {
intel_mid_i2s_command(ssp_config->i2s_handle,
SSP_CMD_FREE_RX, NULL);
pr_debug("SSP DAI: FCT %s RX DMA Channel released\n",
__func__);
}
ssp_config->ssp_dai_rx_allocated = false;
break;
default:
pr_err("SSP DAI: FCT %s Bad stream_dir: %d\n",
__func__, substream->stream);
break;
}
#ifdef _LLI_ENABLED_
clear_bit(INTEL_ALSA_SSP_STREAM_INIT, &str_info->stream_status);
#endif /* _LLI_ENABLED_ */
kfree(str_info);
if (!cpu_dai->active) {
pr_info("SSP DAI: FCT %s closing I2S\n",
__func__);
/*
* Close the Intel MID I2S connection
*/
intel_mid_i2s_close(ssp_config->i2s_handle);
ssp_config->i2s_handle = NULL;
#ifndef _LLI_ENABLED_
ssp_config->intel_mid_dma_alloc = false;
#endif /* _LLI_ENABLED_ */
}
} /* ssp_dai_shutdown */
static int ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct intel_ssp_config *ssp_config;
struct intel_mid_i2s_settings *i2s_config;
ssp_config = cpu_dai->playback_dma_data;
WARN(!ssp_config, "SSP DAI: FCT %s ssp_config=NULL\n",
__func__);
if (!ssp_config)
return -EINVAL;
pr_debug("SSP DAI: FCT %s fmt = %d\n",
__func__, fmt);
i2s_config = &(ssp_config->i2s_settings);
/*
* SSP CLK Direction
* SSP FRMSYNC Direction
*/
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
i2s_config->sspslclk_direction = SSPSCLK_MASTER_MODE;
i2s_config->sspsfrm_direction = SSPSCLK_MASTER_MODE;
/*
* Mandatory to be able to perform only RX without TX
* in SSP CLK Master Mode
*
*/
i2s_config->ssp_duplex_mode = RX_WITHOUT_TX_MODE;
break;
case SND_SOC_DAIFMT_CBM_CFS:
i2s_config->sspslclk_direction = SSPSCLK_MASTER_MODE;
i2s_config->sspsfrm_direction = SSPSCLK_SLAVE_MODE;
/*
* Mandatory to be able to perform only RX without TX
* in SSP CLK Master Mode
*
*/
i2s_config->ssp_duplex_mode = RX_WITHOUT_TX_MODE;
break;
case SND_SOC_DAIFMT_CBS_CFS:
i2s_config->sspslclk_direction = SSPSCLK_SLAVE_MODE;
i2s_config->sspsfrm_direction = SSPSCLK_SLAVE_MODE;
i2s_config->ssp_duplex_mode = RX_AND_TX_MODE;
break;
case SND_SOC_DAIFMT_CBS_CFM:
i2s_config->sspslclk_direction = SSPSCLK_SLAVE_MODE;
i2s_config->sspsfrm_direction = SSPSCLK_MASTER_MODE;
i2s_config->ssp_duplex_mode = RX_AND_TX_MODE;
break;
default:
pr_err("SSP DAI: %s Bad DAI CLK/FS Mode=%d\n",
__func__,
(fmt & SND_SOC_DAIFMT_MASTER_MASK));
return -EINVAL;
}
/*
* SSP Sgnal Inversion Mode
* Use clock gating bitfield for
* Serial bit-rate Clock Mode
*/
switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
case SSP_DAI_SCMODE_0:
i2s_config->ssp_serial_clk_mode = SSP_CLK_MODE_0;
break;
case SSP_DAI_SCMODE_1:
i2s_config->ssp_serial_clk_mode = SSP_CLK_MODE_1;
break;
case SSP_DAI_SCMODE_2:
i2s_config->ssp_serial_clk_mode = SSP_CLK_MODE_2;
break;
case SSP_DAI_SCMODE_3:
i2s_config->ssp_serial_clk_mode = SSP_CLK_MODE_3;
break;
default:
pr_err("SSP DAI: %s Bad DAI Signal Inversion Mode=%d\n",
__func__,
(fmt & SND_SOC_DAIFMT_INV_MASK));
return -EINVAL;
}
/*
* SSP FS Inversion Mode
*/
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
case SND_SOC_DAIFMT_IB_NF:
i2s_config->ssp_frmsync_pol_bit = SSP_FRMS_ACTIVE_HIGH;
break;
case SND_SOC_DAIFMT_NB_IF:
case SND_SOC_DAIFMT_IB_IF:
i2s_config->ssp_frmsync_pol_bit = SSP_FRMS_ACTIVE_LOW;
break;
default:
pr_err("SSP DAI: %s Bad DAI FS Inversion Mode=%d\n",
__func__,
(fmt & SND_SOC_DAIFMT_INV_MASK));
return -EINVAL;
}
/*
* SSP Format Mode
*/
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
i2s_config->frame_format = PSP_FORMAT;
break;
default:
pr_err("SSP DAI: %s Bad DAI format Mode=%d\n",
__func__,
(fmt & SND_SOC_DAIFMT_FORMAT_MASK));
return -EINVAL;
}
return 0;
} /* ssp_set_dai_fmt */
static int ssp_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
{
struct intel_ssp_config *ssp_config;
struct intel_mid_i2s_settings *i2s_config;
ssp_config = cpu_dai->playback_dma_data;
WARN(!ssp_config, "SSP DAI: FCT %s ssp_config=NULL\n",
__func__);
if (!ssp_config)
return -EINVAL;
i2s_config = &(ssp_config->i2s_settings);
i2s_config->frame_rate_divider_control = slots;
i2s_config->data_size = slot_width;
i2s_config->mode = SSP_IN_NETWORK_MODE;
i2s_config->ssp_active_tx_slots_map = tx_mask;
i2s_config->ssp_active_rx_slots_map = rx_mask;
pr_debug("i2s_config->frame_rate_divider_control = %d\n",
i2s_config->frame_rate_divider_control);
pr_debug("i2s_config->data_size = %d\n",
i2s_config->data_size);
pr_debug("i2s_config->mode = %d\n",
i2s_config->mode);
pr_debug("i2s_config->ssp_active_tx_slots_map = %d\n",
i2s_config->ssp_active_tx_slots_map);
pr_debug("i2s_config->ssp_active_rx_slots_map = %d\n",
i2s_config->ssp_active_rx_slots_map);
return 0;
}
static int ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
struct intel_ssp_config *ssp_config;
struct intel_mid_i2s_settings *i2s_config;
ssp_config = cpu_dai->playback_dma_data;
BUG_ON(!ssp_config);
i2s_config = &(ssp_config->i2s_settings);
pr_debug("SSP DAI: FCT %s clk_id = %d\n",
__func__, clk_id);
switch (clk_id) {
case SSP_CLK_ONCHIP:
i2s_config->master_mode_clk_selection = SSP_ONCHIP_CLOCK;
break;
case SSP_CLK_NET:
i2s_config->master_mode_clk_selection = SSP_NETWORK_CLOCK;
break;
case SSP_CLK_EXT:
i2s_config->master_mode_clk_selection = SSP_EXTERNAL_CLOCK;
break;
case SSP_CLK_AUDIO:
i2s_config->master_mode_clk_selection = SSP_ONCHIP_AUDIO_CLOCK;
break;
default:
i2s_config->master_mode_standard_freq =
SSP_MASTER_CLOCK_UNDEFINED;
pr_err("SSP DAI: %s Bad clk_id=%d\n",
__func__,
clk_id);
return -EINVAL;
}
pr_debug("SSP DAI:FCT %s freq = %d\n",
__func__, freq);
switch (freq) {
case 8000:
i2s_config->master_mode_standard_freq = SSP_FRM_FREQ_8_000;
i2s_config->ssp_psp_T1 = 0;
i2s_config->ssp_psp_T2 = 1;
i2s_config->ssp_psp_T4 = 0;
i2s_config->ssp_psp_T5 = 0;
i2s_config->ssp_psp_T6 = 1;
break;
case 11025:
i2s_config->master_mode_standard_freq = SSP_FRM_FREQ_11_025;
pr_err("SSP DAI: %s Bad freq_out=%d\n",
__func__,
freq);
return -EINVAL;
case 16000:
i2s_config->master_mode_standard_freq = SSP_FRM_FREQ_16_000;
i2s_config->ssp_psp_T1 = 6;
i2s_config->ssp_psp_T2 = 2;
i2s_config->ssp_psp_T4 = 0;
i2s_config->ssp_psp_T5 = 14;
i2s_config->ssp_psp_T6 = 16;
break;
case 22050:
i2s_config->master_mode_standard_freq = SSP_FRM_FREQ_22_050;
pr_err("SSP DAI: %s Bad freq_out=%d\n",
__func__,
freq);
return -EINVAL;
case 44100:
i2s_config->master_mode_standard_freq = SSP_FRM_FREQ_44_100;
pr_err("SSP DAI: %s Bad freq_out=%d\n",
__func__,
freq);
return -EINVAL;
case 48000:
i2s_config->master_mode_standard_freq = SSP_FRM_FREQ_48_000;
i2s_config->ssp_psp_T1 = 6;
i2s_config->ssp_psp_T2 = 2;
i2s_config->ssp_psp_T4 = 0;
i2s_config->ssp_psp_T5 = 14;
i2s_config->ssp_psp_T6 = 16;
break;
default:
i2s_config->master_mode_standard_freq = SSP_FRM_FREQ_UNDEFINED;
pr_err("SSP DAI: %s Bad freq_out=%d\n",
__func__,
freq);
return -EINVAL;
}
return 0;
}
static int ssp_set_dai_tristate(struct snd_soc_dai *cpu_dai,
int tristate)
{
struct intel_ssp_config *ssp_config;
struct intel_mid_i2s_settings *i2s_config;
ssp_config = cpu_dai->playback_dma_data;
BUG_ON(!ssp_config);
i2s_config = &(ssp_config->i2s_settings);
if (IS_TRISTATE_ENABLED(tristate))
i2s_config->tx_tristate_enable = TXD_TRISTATE_ON;
else
i2s_config->tx_tristate_enable = TXD_TRISTATE_OFF;
if (IS_NEXT_FRMS_ASSERTED_WITH_LSB_PREVIOUS_FRM(tristate))
i2s_config->ssp_frmsync_timing_bit =
NEXT_FRMS_ASS_WITH_LSB_PREVIOUS_FRM;
else
i2s_config->ssp_frmsync_timing_bit =
NEXT_FRMS_ASS_AFTER_END_OF_T4;
pr_debug("FCT %s tristate %x\n", __func__, tristate);
return 0;
}
/**
* ssp_dai_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
*
* @param substream Substream for which the stream function is called
* @param cmd The stream command thats requested from upper layer
* return status 0 ==> OK
*
*/
static int ssp_dai_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *cpu_dai)
{
int ret_val = 0;
struct intel_alsa_ssp_stream_info *str_info;
struct snd_pcm_runtime *pl_runtime;
struct intel_ssp_info *ssp_info;
#ifdef _LLI_ENABLED_
struct intel_ssp_config *ssp_config;
#endif /* _LLI_ENABLED_ */
bool trigger_start = true;
int stream = 0;
pr_debug("SSP DAI: FCT %s enters\n",
__func__);
stream = substream->stream;
pl_runtime = substream->runtime;
WARN(!pl_runtime->private_data, "SSP DAI: ERROR "
"NULL pl_runtime->private_data\n");
if (!pl_runtime->private_data)
return -EINVAL;
WARN(!cpu_dai, "SSP DAI: ERROR NULL cpu_dai\n");
if (!cpu_dai)
return -EINVAL;
WARN(!cpu_dai->dev, "SSP DAI: ERROR NULL cpu_dai->dev\n");
if (!cpu_dai->dev)
return -EINVAL;
ssp_info = dev_get_drvdata(cpu_dai->dev);
WARN(!ssp_info->ssp_dai_wq, "SSP DAI: ERROR NULL ssp_dai_wq\n");
if (!ssp_info->ssp_dai_wq)
return -EINVAL;
str_info = pl_runtime->private_data;
#ifdef _LLI_ENABLED_
ssp_config = str_info->ssp_config;
WARN(!ssp_config, "SSP DAI: ERROR NULL ssp_config\n");
if (!ssp_config)
return -EINVAL;
#endif /* _LLI_ENABLED_ */
pr_debug("SSP DAI: FCT %s CMD = 0x%04X\n",
__func__, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (!test_and_set_bit(INTEL_ALSA_SSP_STREAM_STARTED,
&str_info->stream_status)) {
#ifndef _LLI_ENABLED_
if (test_bit(INTEL_ALSA_SSP_STREAM_DROPPED,
&str_info->stream_status)) {
pr_debug("SSP DAI: FCT %s do not restart the trigger stream running already\n",
__func__);
trigger_start = false;
} else
#endif /* _LLI_ENABLED_ */
trigger_start = true;
} else {
pr_err("SSP DAI: ERROR 2 consecutive TRIGGER_START\n");
return -EBUSY;
}
/* Store the substream locally */
if (trigger_start) {
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
pr_debug("SSP DAI: queue Playback Work\n");
queue_work(ssp_info->ssp_dai_wq,
&str_info->ssp_ws);
} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
pr_debug("SSP DAI: queue Capture Work\n");
queue_work(ssp_info->ssp_dai_wq,
&str_info->ssp_ws);
} else {
pr_err("SSP DAI: SNDRV_PCM_TRIGGER_START Bad Stream: %d\n",
substream->stream);
return -EINVAL;
}
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
if (test_and_clear_bit(INTEL_ALSA_SSP_STREAM_STARTED,
&str_info->stream_status)) {
#ifdef _LLI_ENABLED_
intel_mid_i2s_command(ssp_config->i2s_handle,
(stream == SNDRV_PCM_STREAM_PLAYBACK) ?
SSP_CMD_DISABLE_DMA_TX_INTR :
SSP_CMD_DISABLE_DMA_RX_INTR,
NULL);
#else
set_bit(INTEL_ALSA_SSP_STREAM_DROPPED,
&str_info->stream_status);
#endif /* _LLI_ENABLED_ */
} else {
pr_err("SSP DAI: trigger START/STOP mismatch\n");
return -EBUSY;
}
break;
default:
pr_err("SSP DAI: snd_i2s_alsa_pcm_trigger Bad Command\n");
return -EINVAL;
}
return ret_val;
} /* ssp_dai_trigger */
/**
* ssp_dai_hw_params - Allocate memory for Ring Buffer according
* to hw_params.
* It's called in a non-atomic context
*
* @param substream Substream for which the stream function is called
* @param hw_params Stream command thats requested from upper layer
* @param cpu_dai Pointer to the CPU DAI that is used
* return status 0 ==> OK
*
*/
static int ssp_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params,
struct snd_soc_dai *cpu_dai)
{
return 0;
}
static int ssp_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct intel_ssp_config *ssp_config;
struct intel_ssp_info *ssp_info;
pr_debug("SSP DAI: FCT %s enters\n",
__func__);
WARN(!cpu_dai, "SSP DAI: ERROR NULL cpu_dai\n");
if (!cpu_dai)
return -EINVAL;
ssp_info = dev_get_drvdata(cpu_dai->dev);
WARN(!ssp_info, "SSP DAI: ERROR NULL ssp_info\n");
if (!ssp_info)
return -EINVAL;
ssp_config = snd_soc_dai_get_dma_data(cpu_dai, substream);
pr_debug("SSP DAI: FCT %s ssp_dai_tx_allocated %d "\
"ssp_dai_rx_allocated %d\n",
__func__,
ssp_config->ssp_dai_tx_allocated,
ssp_config->ssp_dai_rx_allocated);
/*
* The set HW Config is only once for a CPU DAI
*/
if (!ssp_config->ssp_dai_tx_allocated &&
!ssp_config->ssp_dai_rx_allocated) {
intel_mid_i2s_command(ssp_config->i2s_handle,
SSP_CMD_SET_HW_CONFIG,
&(ssp_config->i2s_settings));
}
switch (substream->stream) {
case SNDRV_PCM_STREAM_PLAYBACK:
if (!ssp_config->ssp_dai_tx_allocated) {
if (intel_mid_i2s_command(ssp_config->i2s_handle,
SSP_CMD_ALLOC_TX, NULL)) {
pr_err("can not alloc TX DMA Channel\n");
return -EBUSY;
}
ssp_config->ssp_dai_tx_allocated = true;
}
break;
case SNDRV_PCM_STREAM_CAPTURE:
if (!ssp_config->ssp_dai_rx_allocated) {
if (intel_mid_i2s_command(ssp_config->i2s_handle,
SSP_CMD_ALLOC_RX, NULL)) {
pr_err("can not alloc RX DMA Channel\n");
return -EBUSY;
}
ssp_config->ssp_dai_rx_allocated = true;
}
break;
default:
pr_err("SSP DAI: FCT %s Bad stream_dir: %d\n",
__func__, substream->stream);
return -EINVAL;
}
#ifndef _LLI_ENABLED_
ssp_config->intel_mid_dma_alloc = true;
#endif /* _LLI_ENABLED_ */
pr_debug("SSP DAI: FCT %s leaves\n",
__func__);
return 0;
}
/* BT/FM */
static struct snd_soc_dai_ops ssp_dai_ops = {
.startup = ssp_dai_startup,
.shutdown = ssp_dai_shutdown,
.trigger = ssp_dai_trigger,
.hw_params = ssp_dai_hw_params,
.prepare = ssp_dai_prepare,
.set_sysclk = ssp_set_dai_sysclk,
.set_pll = NULL,
.set_fmt = ssp_set_dai_fmt,
.set_tdm_slot = ssp_set_dai_tdm_slot,
.set_tristate = ssp_set_dai_tristate,
};
#define SSP_SUPPORTED_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
#define SSP_SUPPORTED_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_U16_LE | \
SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_U8)
struct snd_soc_dai_driver intel_ssp_platform_dai[] = {
{
.name = SSP_MODEM_DAI_NAME,
.id = 0,
.playback = {
.channels_min = 1,
.channels_max = 8,
.rates = SSP_SUPPORTED_RATES,
.formats = SSP_SUPPORTED_FORMATS,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
.rates = SSP_SUPPORTED_RATES,
.formats = SSP_SUPPORTED_FORMATS,
},
.ops = &ssp_dai_ops,
.probe = ssp_probe,
.remove = ssp_remove,
},
{
.name = SSP_BT_DAI_NAME,
.id = 1,
.playback = {
.channels_min = 1,
.channels_max = 8,
.rates = SSP_SUPPORTED_RATES,
.formats = SSP_SUPPORTED_FORMATS,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
.rates = SSP_SUPPORTED_RATES,
.formats = SSP_SUPPORTED_FORMATS,
},
.ops = &ssp_dai_ops,
.probe = ssp_probe,
.remove = ssp_remove,
},
};
static const struct snd_soc_component_driver ssp_component = {
.name = "ssp",
};
static int ssp_dai_probe(struct platform_device *pdev)
{
int ret;
struct intel_ssp_info *ssp_info;
pr_info("SSP DAI: FCT %s enters\n",
__func__);
ssp_info = kzalloc(sizeof(struct intel_ssp_info), GFP_KERNEL);
if (ssp_info == NULL) {
pr_err("Unable to allocate ssp_info\n");
return -ENOMEM;
}
pr_info("ssp_info address %p", ssp_info);
ret = snd_soc_register_platform(&pdev->dev,
&soc_ssp_platform_drv);
if (ret) {
pr_err("registering SSP PLATFORM failed\n");
snd_soc_unregister_component(&pdev->dev);
kfree(ssp_info);
return -EBUSY;
}
ret = snd_soc_register_component(&pdev->dev, &ssp_component,
intel_ssp_platform_dai,
ARRAY_SIZE(intel_ssp_platform_dai));
if (ret) {
pr_err("registering cpu DAIs failed\n");
snd_soc_unregister_component(&pdev->dev);
kfree(ssp_info);
return -EBUSY;
}
ssp_info->ssp_dai_wq = create_workqueue("ssp_transfer_data");
if (!ssp_info->ssp_dai_wq) {
pr_err("work queue failed\n");
snd_soc_unregister_component(&pdev->dev);
kfree(ssp_info);
return -ENOMEM;
}
platform_set_drvdata(pdev, ssp_info);
pr_info("SSP DAI: FCT %s leaves %d\n",
__func__, ret);
return ret;
}
static int ssp_dai_remove(struct platform_device *pdev)
{
struct intel_ssp_info *ssp_info = platform_get_drvdata(pdev);
pr_debug("SSP DAI: FCT %s enters\n",
__func__);
if (ssp_info == NULL) {
pr_err("Unable to allocate ssp_info\n");
return -ENOMEM;
}
pr_info("ssp_info address %p", ssp_info);
flush_workqueue(ssp_info->ssp_dai_wq);
destroy_workqueue(ssp_info->ssp_dai_wq);
platform_set_drvdata(pdev, NULL);
snd_soc_unregister_component(&pdev->dev);
snd_soc_unregister_platform(&pdev->dev);
pr_debug("SSP DAI: FCT %s leaves\n",
__func__);
return 0;
}
static struct platform_driver intel_ssp_dai_driver = {
.driver = {
.name = "mid-ssp-dai",
.owner = THIS_MODULE,
},
.probe = ssp_dai_probe,
.remove = ssp_dai_remove,
};
static int __init ssp_soc_dai_init(void)
{
pr_info("SSP DAI: FCT %s called\n",
__func__);
return platform_driver_register(&intel_ssp_dai_driver);
}
module_init(ssp_soc_dai_init);
static void __exit ssp_soc_dai_exit(void)
{
pr_debug("SSP DAI: FCT %s called\n",
__func__);
platform_driver_unregister(&intel_ssp_dai_driver);
}
module_exit(ssp_soc_dai_exit);
MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver");
MODULE_AUTHOR("Selma Bensaid");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:ssp-cpu-dai");