mirror of
https://github.com/torvalds/linux.git
synced 2024-12-21 02:21:36 +00:00
33899b1985
We no longer have a means to differentiate between MSP devices at probe time, mainly because we don't really have to. So rather than have an over- sized static data structure in place, where the only difference between devices is the ID and name (which are unused), we'll just create one succinct, statically assigned and shared one instead. Signed-off-by: Lee Jones <lee.jones@linaro.org> Signed-off-by: Mark Brown <broonie@linaro.org>
868 lines
22 KiB
C
868 lines
22 KiB
C
/*
|
|
* Copyright (C) ST-Ericsson SA 2012
|
|
*
|
|
* Author: Ola Lilja <ola.o.lilja@stericsson.com>,
|
|
* Roger Nilsson <roger.xr.nilsson@stericsson.com>
|
|
* for ST-Ericsson.
|
|
*
|
|
* License terms:
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as published
|
|
* by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/of.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/mfd/dbx500-prcmu.h>
|
|
#include <linux/platform_data/asoc-ux500-msp.h>
|
|
|
|
#include <sound/soc.h>
|
|
#include <sound/soc-dai.h>
|
|
#include <sound/dmaengine_pcm.h>
|
|
|
|
#include "ux500_msp_i2s.h"
|
|
#include "ux500_msp_dai.h"
|
|
#include "ux500_pcm.h"
|
|
|
|
static int setup_pcm_multichan(struct snd_soc_dai *dai,
|
|
struct ux500_msp_config *msp_config)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
struct msp_multichannel_config *multi =
|
|
&msp_config->multichannel_config;
|
|
|
|
if (drvdata->slots > 1) {
|
|
msp_config->multichannel_configured = 1;
|
|
|
|
multi->tx_multichannel_enable = true;
|
|
multi->rx_multichannel_enable = true;
|
|
multi->rx_comparison_enable_mode = MSP_COMPARISON_DISABLED;
|
|
|
|
multi->tx_channel_0_enable = drvdata->tx_mask;
|
|
multi->tx_channel_1_enable = 0;
|
|
multi->tx_channel_2_enable = 0;
|
|
multi->tx_channel_3_enable = 0;
|
|
|
|
multi->rx_channel_0_enable = drvdata->rx_mask;
|
|
multi->rx_channel_1_enable = 0;
|
|
multi->rx_channel_2_enable = 0;
|
|
multi->rx_channel_3_enable = 0;
|
|
|
|
dev_dbg(dai->dev,
|
|
"%s: Multichannel enabled. Slots: %d, TX: %u, RX: %u\n",
|
|
__func__, drvdata->slots, multi->tx_channel_0_enable,
|
|
multi->rx_channel_0_enable);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int setup_frameper(struct snd_soc_dai *dai, unsigned int rate,
|
|
struct msp_protdesc *prot_desc)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
|
|
switch (drvdata->slots) {
|
|
case 1:
|
|
switch (rate) {
|
|
case 8000:
|
|
prot_desc->frame_period =
|
|
FRAME_PER_SINGLE_SLOT_8_KHZ;
|
|
break;
|
|
|
|
case 16000:
|
|
prot_desc->frame_period =
|
|
FRAME_PER_SINGLE_SLOT_16_KHZ;
|
|
break;
|
|
|
|
case 44100:
|
|
prot_desc->frame_period =
|
|
FRAME_PER_SINGLE_SLOT_44_1_KHZ;
|
|
break;
|
|
|
|
case 48000:
|
|
prot_desc->frame_period =
|
|
FRAME_PER_SINGLE_SLOT_48_KHZ;
|
|
break;
|
|
|
|
default:
|
|
dev_err(dai->dev,
|
|
"%s: Error: Unsupported sample-rate (freq = %d)!\n",
|
|
__func__, rate);
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
prot_desc->frame_period = FRAME_PER_2_SLOTS;
|
|
break;
|
|
|
|
case 8:
|
|
prot_desc->frame_period = FRAME_PER_8_SLOTS;
|
|
break;
|
|
|
|
case 16:
|
|
prot_desc->frame_period = FRAME_PER_16_SLOTS;
|
|
break;
|
|
default:
|
|
dev_err(dai->dev,
|
|
"%s: Error: Unsupported slot-count (slots = %d)!\n",
|
|
__func__, drvdata->slots);
|
|
return -EINVAL;
|
|
}
|
|
|
|
prot_desc->clocks_per_frame =
|
|
prot_desc->frame_period+1;
|
|
|
|
dev_dbg(dai->dev, "%s: Clocks per frame: %u\n",
|
|
__func__,
|
|
prot_desc->clocks_per_frame);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int setup_pcm_framing(struct snd_soc_dai *dai, unsigned int rate,
|
|
struct msp_protdesc *prot_desc)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
|
|
u32 frame_length = MSP_FRAME_LEN_1;
|
|
prot_desc->frame_width = 0;
|
|
|
|
switch (drvdata->slots) {
|
|
case 1:
|
|
frame_length = MSP_FRAME_LEN_1;
|
|
break;
|
|
|
|
case 2:
|
|
frame_length = MSP_FRAME_LEN_2;
|
|
break;
|
|
|
|
case 8:
|
|
frame_length = MSP_FRAME_LEN_8;
|
|
break;
|
|
|
|
case 16:
|
|
frame_length = MSP_FRAME_LEN_16;
|
|
break;
|
|
default:
|
|
dev_err(dai->dev,
|
|
"%s: Error: Unsupported slot-count (slots = %d)!\n",
|
|
__func__, drvdata->slots);
|
|
return -EINVAL;
|
|
}
|
|
|
|
prot_desc->tx_frame_len_1 = frame_length;
|
|
prot_desc->rx_frame_len_1 = frame_length;
|
|
prot_desc->tx_frame_len_2 = frame_length;
|
|
prot_desc->rx_frame_len_2 = frame_length;
|
|
|
|
prot_desc->tx_elem_len_1 = MSP_ELEM_LEN_16;
|
|
prot_desc->rx_elem_len_1 = MSP_ELEM_LEN_16;
|
|
prot_desc->tx_elem_len_2 = MSP_ELEM_LEN_16;
|
|
prot_desc->rx_elem_len_2 = MSP_ELEM_LEN_16;
|
|
|
|
return setup_frameper(dai, rate, prot_desc);
|
|
}
|
|
|
|
static int setup_clocking(struct snd_soc_dai *dai,
|
|
unsigned int fmt,
|
|
struct ux500_msp_config *msp_config)
|
|
{
|
|
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_NB_IF:
|
|
msp_config->tx_fsync_pol ^= 1 << TFSPOL_SHIFT;
|
|
msp_config->rx_fsync_pol ^= 1 << RFSPOL_SHIFT;
|
|
|
|
break;
|
|
|
|
default:
|
|
dev_err(dai->dev,
|
|
"%s: Error: Unsopported inversion (fmt = 0x%x)!\n",
|
|
__func__, fmt);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
dev_dbg(dai->dev, "%s: Codec is master.\n", __func__);
|
|
|
|
msp_config->iodelay = 0x20;
|
|
msp_config->rx_fsync_sel = 0;
|
|
msp_config->tx_fsync_sel = 1 << TFSSEL_SHIFT;
|
|
msp_config->tx_clk_sel = 0;
|
|
msp_config->rx_clk_sel = 0;
|
|
msp_config->srg_clk_sel = 0x2 << SCKSEL_SHIFT;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
dev_dbg(dai->dev, "%s: Codec is slave.\n", __func__);
|
|
|
|
msp_config->tx_clk_sel = TX_CLK_SEL_SRG;
|
|
msp_config->tx_fsync_sel = TX_SYNC_SRG_PROG;
|
|
msp_config->rx_clk_sel = RX_CLK_SEL_SRG;
|
|
msp_config->rx_fsync_sel = RX_SYNC_SRG;
|
|
msp_config->srg_clk_sel = 1 << SCKSEL_SHIFT;
|
|
|
|
break;
|
|
|
|
default:
|
|
dev_err(dai->dev, "%s: Error: Unsopported master (fmt = 0x%x)!\n",
|
|
__func__, fmt);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int setup_pcm_protdesc(struct snd_soc_dai *dai,
|
|
unsigned int fmt,
|
|
struct msp_protdesc *prot_desc)
|
|
{
|
|
prot_desc->rx_phase_mode = MSP_SINGLE_PHASE;
|
|
prot_desc->tx_phase_mode = MSP_SINGLE_PHASE;
|
|
prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE;
|
|
prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE;
|
|
prot_desc->rx_byte_order = MSP_BTF_MS_BIT_FIRST;
|
|
prot_desc->tx_byte_order = MSP_BTF_MS_BIT_FIRST;
|
|
prot_desc->tx_fsync_pol = MSP_FSYNC_POL(MSP_FSYNC_POL_ACT_HI);
|
|
prot_desc->rx_fsync_pol = MSP_FSYNC_POL_ACT_HI << RFSPOL_SHIFT;
|
|
|
|
if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) {
|
|
dev_dbg(dai->dev, "%s: DSP_A.\n", __func__);
|
|
prot_desc->rx_clk_pol = MSP_RISING_EDGE;
|
|
prot_desc->tx_clk_pol = MSP_FALLING_EDGE;
|
|
|
|
prot_desc->rx_data_delay = MSP_DELAY_1;
|
|
prot_desc->tx_data_delay = MSP_DELAY_1;
|
|
} else {
|
|
dev_dbg(dai->dev, "%s: DSP_B.\n", __func__);
|
|
prot_desc->rx_clk_pol = MSP_FALLING_EDGE;
|
|
prot_desc->tx_clk_pol = MSP_RISING_EDGE;
|
|
|
|
prot_desc->rx_data_delay = MSP_DELAY_0;
|
|
prot_desc->tx_data_delay = MSP_DELAY_0;
|
|
}
|
|
|
|
prot_desc->rx_half_word_swap = MSP_SWAP_NONE;
|
|
prot_desc->tx_half_word_swap = MSP_SWAP_NONE;
|
|
prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR;
|
|
prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR;
|
|
prot_desc->frame_sync_ignore = MSP_FSYNC_IGNORE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int setup_i2s_protdesc(struct msp_protdesc *prot_desc)
|
|
{
|
|
prot_desc->rx_phase_mode = MSP_DUAL_PHASE;
|
|
prot_desc->tx_phase_mode = MSP_DUAL_PHASE;
|
|
prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_FSYNC;
|
|
prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_FSYNC;
|
|
prot_desc->rx_byte_order = MSP_BTF_MS_BIT_FIRST;
|
|
prot_desc->tx_byte_order = MSP_BTF_MS_BIT_FIRST;
|
|
prot_desc->tx_fsync_pol = MSP_FSYNC_POL(MSP_FSYNC_POL_ACT_LO);
|
|
prot_desc->rx_fsync_pol = MSP_FSYNC_POL_ACT_LO << RFSPOL_SHIFT;
|
|
|
|
prot_desc->rx_frame_len_1 = MSP_FRAME_LEN_1;
|
|
prot_desc->rx_frame_len_2 = MSP_FRAME_LEN_1;
|
|
prot_desc->tx_frame_len_1 = MSP_FRAME_LEN_1;
|
|
prot_desc->tx_frame_len_2 = MSP_FRAME_LEN_1;
|
|
prot_desc->rx_elem_len_1 = MSP_ELEM_LEN_16;
|
|
prot_desc->rx_elem_len_2 = MSP_ELEM_LEN_16;
|
|
prot_desc->tx_elem_len_1 = MSP_ELEM_LEN_16;
|
|
prot_desc->tx_elem_len_2 = MSP_ELEM_LEN_16;
|
|
|
|
prot_desc->rx_clk_pol = MSP_RISING_EDGE;
|
|
prot_desc->tx_clk_pol = MSP_FALLING_EDGE;
|
|
|
|
prot_desc->rx_data_delay = MSP_DELAY_0;
|
|
prot_desc->tx_data_delay = MSP_DELAY_0;
|
|
|
|
prot_desc->tx_half_word_swap = MSP_SWAP_NONE;
|
|
prot_desc->rx_half_word_swap = MSP_SWAP_NONE;
|
|
prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR;
|
|
prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR;
|
|
prot_desc->frame_sync_ignore = MSP_FSYNC_IGNORE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int setup_msp_config(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai,
|
|
struct ux500_msp_config *msp_config)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
struct msp_protdesc *prot_desc = &msp_config->protdesc;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
unsigned int fmt = drvdata->fmt;
|
|
int ret;
|
|
|
|
memset(msp_config, 0, sizeof(*msp_config));
|
|
|
|
msp_config->f_inputclk = drvdata->master_clk;
|
|
|
|
msp_config->tx_fifo_config = TX_FIFO_ENABLE;
|
|
msp_config->rx_fifo_config = RX_FIFO_ENABLE;
|
|
msp_config->def_elem_len = 1;
|
|
msp_config->direction = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
|
MSP_DIR_TX : MSP_DIR_RX;
|
|
msp_config->data_size = MSP_DATA_BITS_32;
|
|
msp_config->frame_freq = runtime->rate;
|
|
|
|
dev_dbg(dai->dev, "%s: f_inputclk = %u, frame_freq = %u.\n",
|
|
__func__, msp_config->f_inputclk, msp_config->frame_freq);
|
|
/* To avoid division by zero */
|
|
prot_desc->clocks_per_frame = 1;
|
|
|
|
dev_dbg(dai->dev, "%s: rate: %u, channels: %d.\n", __func__,
|
|
runtime->rate, runtime->channels);
|
|
switch (fmt &
|
|
(SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
|
|
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
|
|
dev_dbg(dai->dev, "%s: SND_SOC_DAIFMT_I2S.\n", __func__);
|
|
|
|
msp_config->default_protdesc = 1;
|
|
msp_config->protocol = MSP_I2S_PROTOCOL;
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
|
|
dev_dbg(dai->dev, "%s: SND_SOC_DAIFMT_I2S.\n", __func__);
|
|
|
|
msp_config->data_size = MSP_DATA_BITS_16;
|
|
msp_config->protocol = MSP_I2S_PROTOCOL;
|
|
|
|
ret = setup_i2s_protdesc(prot_desc);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
|
|
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
|
|
case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS:
|
|
case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM:
|
|
dev_dbg(dai->dev, "%s: PCM format.\n", __func__);
|
|
|
|
msp_config->data_size = MSP_DATA_BITS_16;
|
|
msp_config->protocol = MSP_PCM_PROTOCOL;
|
|
|
|
ret = setup_pcm_protdesc(dai, fmt, prot_desc);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = setup_pcm_multichan(dai, msp_config);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = setup_pcm_framing(dai, runtime->rate, prot_desc);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
break;
|
|
|
|
default:
|
|
dev_err(dai->dev, "%s: Error: Unsopported format (%d)!\n",
|
|
__func__, fmt);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return setup_clocking(dai, fmt, msp_config);
|
|
}
|
|
|
|
static int ux500_msp_dai_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
int ret = 0;
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
|
|
dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id,
|
|
snd_pcm_stream_str(substream));
|
|
|
|
/* Enable regulator */
|
|
ret = regulator_enable(drvdata->reg_vape);
|
|
if (ret != 0) {
|
|
dev_err(drvdata->msp->dev,
|
|
"%s: Failed to enable regulator!\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
/* Prepare and enable clocks */
|
|
dev_dbg(dai->dev, "%s: Enabling MSP-clocks.\n", __func__);
|
|
ret = clk_prepare_enable(drvdata->pclk);
|
|
if (ret) {
|
|
dev_err(drvdata->msp->dev,
|
|
"%s: Failed to prepare/enable pclk!\n", __func__);
|
|
goto err_pclk;
|
|
}
|
|
|
|
ret = clk_prepare_enable(drvdata->clk);
|
|
if (ret) {
|
|
dev_err(drvdata->msp->dev,
|
|
"%s: Failed to prepare/enable clk!\n", __func__);
|
|
goto err_clk;
|
|
}
|
|
|
|
return ret;
|
|
err_clk:
|
|
clk_disable_unprepare(drvdata->pclk);
|
|
err_pclk:
|
|
regulator_disable(drvdata->reg_vape);
|
|
return ret;
|
|
}
|
|
|
|
static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
int ret;
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
|
|
|
|
dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id,
|
|
snd_pcm_stream_str(substream));
|
|
|
|
if (drvdata->vape_opp_constraint == 1) {
|
|
prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP,
|
|
"ux500_msp_i2s", 50);
|
|
drvdata->vape_opp_constraint = 0;
|
|
}
|
|
|
|
if (ux500_msp_i2s_close(drvdata->msp,
|
|
is_playback ? MSP_DIR_TX : MSP_DIR_RX)) {
|
|
dev_err(dai->dev,
|
|
"%s: Error: MSP %d (%s): Unable to close i2s.\n",
|
|
__func__, dai->id, snd_pcm_stream_str(substream));
|
|
}
|
|
|
|
/* Disable and unprepare clocks */
|
|
clk_disable_unprepare(drvdata->clk);
|
|
clk_disable_unprepare(drvdata->pclk);
|
|
|
|
/* Disable regulator */
|
|
ret = regulator_disable(drvdata->reg_vape);
|
|
if (ret < 0)
|
|
dev_err(dai->dev,
|
|
"%s: ERROR: Failed to disable regulator (%d)!\n",
|
|
__func__, ret);
|
|
}
|
|
|
|
static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
int ret = 0;
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ux500_msp_config msp_config;
|
|
|
|
dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (rate = %d).\n", __func__,
|
|
dai->id, snd_pcm_stream_str(substream), runtime->rate);
|
|
|
|
setup_msp_config(substream, dai, &msp_config);
|
|
|
|
ret = ux500_msp_i2s_open(drvdata->msp, &msp_config);
|
|
if (ret < 0) {
|
|
dev_err(dai->dev, "%s: Error: msp_setup failed (ret = %d)!\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Set OPP-level */
|
|
if ((drvdata->fmt & SND_SOC_DAIFMT_MASTER_MASK) &&
|
|
(drvdata->msp->f_bitclk > 19200000)) {
|
|
/* If the bit-clock is higher than 19.2MHz, Vape should be
|
|
* run in 100% OPP. Only when bit-clock is used (MSP master) */
|
|
prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP,
|
|
"ux500-msp-i2s", 100);
|
|
drvdata->vape_opp_constraint = 1;
|
|
} else {
|
|
prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP,
|
|
"ux500-msp-i2s", 50);
|
|
drvdata->vape_opp_constraint = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
unsigned int mask, slots_active;
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
|
|
dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n",
|
|
__func__, dai->id, snd_pcm_stream_str(substream));
|
|
|
|
switch (drvdata->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_I2S:
|
|
snd_pcm_hw_constraint_minmax(runtime,
|
|
SNDRV_PCM_HW_PARAM_CHANNELS,
|
|
1, 2);
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_DSP_B:
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
mask = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
|
|
drvdata->tx_mask :
|
|
drvdata->rx_mask;
|
|
|
|
slots_active = hweight32(mask);
|
|
dev_dbg(dai->dev, "TDM-slots active: %d", slots_active);
|
|
|
|
snd_pcm_hw_constraint_minmax(runtime,
|
|
SNDRV_PCM_HW_PARAM_CHANNELS,
|
|
slots_active, slots_active);
|
|
break;
|
|
|
|
default:
|
|
dev_err(dai->dev,
|
|
"%s: Error: Unsupported protocol (fmt = 0x%x)!\n",
|
|
__func__, drvdata->fmt);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ux500_msp_dai_set_dai_fmt(struct snd_soc_dai *dai,
|
|
unsigned int fmt)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
|
|
dev_dbg(dai->dev, "%s: MSP %d: Enter.\n", __func__, dai->id);
|
|
|
|
switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
|
|
SND_SOC_DAIFMT_MASTER_MASK)) {
|
|
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
|
|
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
|
|
case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS:
|
|
case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM:
|
|
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
|
|
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
|
|
break;
|
|
|
|
default:
|
|
dev_err(dai->dev,
|
|
"%s: Error: Unsupported protocol/master (fmt = 0x%x)!\n",
|
|
__func__, drvdata->fmt);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
case SND_SOC_DAIFMT_NB_IF:
|
|
case SND_SOC_DAIFMT_IB_IF:
|
|
break;
|
|
|
|
default:
|
|
dev_err(dai->dev,
|
|
"%s: Error: Unsupported inversion (fmt = 0x%x)!\n",
|
|
__func__, drvdata->fmt);
|
|
return -EINVAL;
|
|
}
|
|
|
|
drvdata->fmt = fmt;
|
|
return 0;
|
|
}
|
|
|
|
static int ux500_msp_dai_set_tdm_slot(struct snd_soc_dai *dai,
|
|
unsigned int tx_mask,
|
|
unsigned int rx_mask,
|
|
int slots, int slot_width)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
unsigned int cap;
|
|
|
|
switch (slots) {
|
|
case 1:
|
|
cap = 0x01;
|
|
break;
|
|
case 2:
|
|
cap = 0x03;
|
|
break;
|
|
case 8:
|
|
cap = 0xFF;
|
|
break;
|
|
case 16:
|
|
cap = 0xFFFF;
|
|
break;
|
|
default:
|
|
dev_err(dai->dev, "%s: Error: Unsupported slot-count (%d)!\n",
|
|
__func__, slots);
|
|
return -EINVAL;
|
|
}
|
|
drvdata->slots = slots;
|
|
|
|
if (!(slot_width == 16)) {
|
|
dev_err(dai->dev, "%s: Error: Unsupported slot-width (%d)!\n",
|
|
__func__, slot_width);
|
|
return -EINVAL;
|
|
}
|
|
drvdata->slot_width = slot_width;
|
|
|
|
drvdata->tx_mask = tx_mask & cap;
|
|
drvdata->rx_mask = rx_mask & cap;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ux500_msp_dai_set_dai_sysclk(struct snd_soc_dai *dai,
|
|
int clk_id, unsigned int freq, int dir)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
|
|
dev_dbg(dai->dev, "%s: MSP %d: Enter. clk-id: %d, freq: %u.\n",
|
|
__func__, dai->id, clk_id, freq);
|
|
|
|
switch (clk_id) {
|
|
case UX500_MSP_MASTER_CLOCK:
|
|
drvdata->master_clk = freq;
|
|
break;
|
|
|
|
default:
|
|
dev_err(dai->dev, "%s: MSP %d: Invalid clk-id (%d)!\n",
|
|
__func__, dai->id, clk_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream,
|
|
int cmd, struct snd_soc_dai *dai)
|
|
{
|
|
int ret = 0;
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
|
|
dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (msp->id = %d, cmd = %d).\n",
|
|
__func__, dai->id, snd_pcm_stream_str(substream),
|
|
(int)drvdata->msp->id, cmd);
|
|
|
|
ret = ux500_msp_i2s_trigger(drvdata->msp, cmd, substream->stream);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ux500_msp_dai_of_probe(struct snd_soc_dai *dai)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
struct snd_dmaengine_dai_dma_data *playback_dma_data;
|
|
struct snd_dmaengine_dai_dma_data *capture_dma_data;
|
|
|
|
playback_dma_data = devm_kzalloc(dai->dev,
|
|
sizeof(*playback_dma_data),
|
|
GFP_KERNEL);
|
|
if (!playback_dma_data)
|
|
return -ENOMEM;
|
|
|
|
capture_dma_data = devm_kzalloc(dai->dev,
|
|
sizeof(*capture_dma_data),
|
|
GFP_KERNEL);
|
|
if (!capture_dma_data)
|
|
return -ENOMEM;
|
|
|
|
playback_dma_data->addr = drvdata->msp->playback_dma_data.tx_rx_addr;
|
|
capture_dma_data->addr = drvdata->msp->capture_dma_data.tx_rx_addr;
|
|
|
|
playback_dma_data->maxburst = 4;
|
|
capture_dma_data->maxburst = 4;
|
|
|
|
snd_soc_dai_init_dma_data(dai, playback_dma_data, capture_dma_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ux500_msp_dai_probe(struct snd_soc_dai *dai)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
|
|
struct msp_i2s_platform_data *pdata = dai->dev->platform_data;
|
|
int ret;
|
|
|
|
if (!pdata) {
|
|
ret = ux500_msp_dai_of_probe(dai);
|
|
return ret;
|
|
}
|
|
|
|
drvdata->msp->playback_dma_data.data_size = drvdata->slot_width;
|
|
drvdata->msp->capture_dma_data.data_size = drvdata->slot_width;
|
|
|
|
snd_soc_dai_init_dma_data(dai,
|
|
&drvdata->msp->playback_dma_data,
|
|
&drvdata->msp->capture_dma_data);
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_soc_dai_ops ux500_msp_dai_ops[] = {
|
|
{
|
|
.set_sysclk = ux500_msp_dai_set_dai_sysclk,
|
|
.set_fmt = ux500_msp_dai_set_dai_fmt,
|
|
.set_tdm_slot = ux500_msp_dai_set_tdm_slot,
|
|
.startup = ux500_msp_dai_startup,
|
|
.shutdown = ux500_msp_dai_shutdown,
|
|
.prepare = ux500_msp_dai_prepare,
|
|
.trigger = ux500_msp_dai_trigger,
|
|
.hw_params = ux500_msp_dai_hw_params,
|
|
}
|
|
};
|
|
|
|
static struct snd_soc_dai_driver ux500_msp_dai_drv = {
|
|
.probe = ux500_msp_dai_probe,
|
|
.suspend = NULL,
|
|
.resume = NULL,
|
|
.playback.channels_min = UX500_MSP_MIN_CHANNELS,
|
|
.playback.channels_max = UX500_MSP_MAX_CHANNELS,
|
|
.playback.rates = UX500_I2S_RATES,
|
|
.playback.formats = UX500_I2S_FORMATS,
|
|
.capture.channels_min = UX500_MSP_MIN_CHANNELS,
|
|
.capture.channels_max = UX500_MSP_MAX_CHANNELS,
|
|
.capture.rates = UX500_I2S_RATES,
|
|
.capture.formats = UX500_I2S_FORMATS,
|
|
.ops = ux500_msp_dai_ops,
|
|
};
|
|
|
|
static const struct snd_soc_component_driver ux500_msp_component = {
|
|
.name = "ux500-msp",
|
|
};
|
|
|
|
|
|
static int ux500_msp_drv_probe(struct platform_device *pdev)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata;
|
|
struct msp_i2s_platform_data *pdata = pdev->dev.platform_data;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int ret = 0;
|
|
|
|
if (!pdata && !np) {
|
|
dev_err(&pdev->dev, "No platform data or Device Tree found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
drvdata = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct ux500_msp_i2s_drvdata),
|
|
GFP_KERNEL);
|
|
if (!drvdata)
|
|
return -ENOMEM;
|
|
|
|
drvdata->fmt = 0;
|
|
drvdata->slots = 1;
|
|
drvdata->tx_mask = 0x01;
|
|
drvdata->rx_mask = 0x01;
|
|
drvdata->slot_width = 16;
|
|
drvdata->master_clk = MSP_INPUT_FREQ_APB;
|
|
|
|
drvdata->reg_vape = devm_regulator_get(&pdev->dev, "v-ape");
|
|
if (IS_ERR(drvdata->reg_vape)) {
|
|
ret = (int)PTR_ERR(drvdata->reg_vape);
|
|
dev_err(&pdev->dev,
|
|
"%s: ERROR: Failed to get Vape supply (%d)!\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name, 50);
|
|
|
|
drvdata->pclk = clk_get(&pdev->dev, "apb_pclk");
|
|
if (IS_ERR(drvdata->pclk)) {
|
|
ret = (int)PTR_ERR(drvdata->pclk);
|
|
dev_err(&pdev->dev, "%s: ERROR: clk_get of pclk failed (%d)!\n",
|
|
__func__, ret);
|
|
goto err_pclk;
|
|
}
|
|
|
|
drvdata->clk = clk_get(&pdev->dev, NULL);
|
|
if (IS_ERR(drvdata->clk)) {
|
|
ret = (int)PTR_ERR(drvdata->clk);
|
|
dev_err(&pdev->dev, "%s: ERROR: clk_get failed (%d)!\n",
|
|
__func__, ret);
|
|
goto err_clk;
|
|
}
|
|
|
|
ret = ux500_msp_i2s_init_msp(pdev, &drvdata->msp,
|
|
pdev->dev.platform_data);
|
|
if (!drvdata->msp) {
|
|
dev_err(&pdev->dev,
|
|
"%s: ERROR: Failed to init MSP-struct (%d)!",
|
|
__func__, ret);
|
|
goto err_init_msp;
|
|
}
|
|
dev_set_drvdata(&pdev->dev, drvdata);
|
|
|
|
ret = snd_soc_register_component(&pdev->dev, &ux500_msp_component,
|
|
&ux500_msp_dai_drv, 1);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Error: %s: Failed to register MSP%d!\n",
|
|
__func__, drvdata->msp->id);
|
|
goto err_init_msp;
|
|
}
|
|
|
|
ret = ux500_pcm_register_platform(pdev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev,
|
|
"Error: %s: Failed to register PCM platform device!\n",
|
|
__func__);
|
|
goto err_reg_plat;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_reg_plat:
|
|
snd_soc_unregister_component(&pdev->dev);
|
|
err_init_msp:
|
|
clk_put(drvdata->clk);
|
|
err_clk:
|
|
clk_put(drvdata->pclk);
|
|
err_pclk:
|
|
devm_regulator_put(drvdata->reg_vape);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ux500_msp_drv_remove(struct platform_device *pdev)
|
|
{
|
|
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
|
|
|
|
ux500_pcm_unregister_platform(pdev);
|
|
|
|
snd_soc_unregister_component(&pdev->dev);
|
|
|
|
devm_regulator_put(drvdata->reg_vape);
|
|
prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s");
|
|
|
|
clk_put(drvdata->clk);
|
|
clk_put(drvdata->pclk);
|
|
|
|
ux500_msp_i2s_cleanup_msp(pdev, drvdata->msp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id ux500_msp_i2s_match[] = {
|
|
{ .compatible = "stericsson,ux500-msp-i2s", },
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver msp_i2s_driver = {
|
|
.driver = {
|
|
.name = "ux500-msp-i2s",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = ux500_msp_i2s_match,
|
|
},
|
|
.probe = ux500_msp_drv_probe,
|
|
.remove = ux500_msp_drv_remove,
|
|
};
|
|
module_platform_driver(msp_i2s_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|