forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
444 lines
11 KiB
444 lines
11 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Copyright (C) ST-Ericsson SA 2012 |
|
* |
|
* Author: Ola Lilja <[email protected]>, |
|
* Kristoffer Karlsson <[email protected]> |
|
* for ST-Ericsson. |
|
* |
|
* License terms: |
|
*/ |
|
|
|
#include <linux/module.h> |
|
#include <linux/device.h> |
|
#include <linux/io.h> |
|
#include <linux/clk.h> |
|
#include <linux/mutex.h> |
|
|
|
#include <sound/soc.h> |
|
#include <sound/soc-dapm.h> |
|
#include <sound/pcm.h> |
|
#include <sound/pcm_params.h> |
|
|
|
#include "ux500_pcm.h" |
|
#include "ux500_msp_dai.h" |
|
#include "mop500_ab8500.h" |
|
#include "../codecs/ab8500-codec.h" |
|
|
|
#define TX_SLOT_MONO 0x0008 |
|
#define TX_SLOT_STEREO 0x000a |
|
#define RX_SLOT_MONO 0x0001 |
|
#define RX_SLOT_STEREO 0x0003 |
|
#define TX_SLOT_8CH 0x00FF |
|
#define RX_SLOT_8CH 0x00FF |
|
|
|
#define DEF_TX_SLOTS TX_SLOT_STEREO |
|
#define DEF_RX_SLOTS RX_SLOT_MONO |
|
|
|
#define DRIVERMODE_NORMAL 0 |
|
#define DRIVERMODE_CODEC_ONLY 1 |
|
|
|
/* Slot configuration */ |
|
static unsigned int tx_slots = DEF_TX_SLOTS; |
|
static unsigned int rx_slots = DEF_RX_SLOTS; |
|
|
|
/* Configuration consistency parameters */ |
|
static DEFINE_MUTEX(mop500_ab8500_params_lock); |
|
static unsigned long mop500_ab8500_usage; |
|
static int mop500_ab8500_rate; |
|
static int mop500_ab8500_channels; |
|
|
|
/* Clocks */ |
|
static const char * const enum_mclk[] = { |
|
"SYSCLK", |
|
"ULPCLK" |
|
}; |
|
enum mclk { |
|
MCLK_SYSCLK, |
|
MCLK_ULPCLK, |
|
}; |
|
|
|
static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk); |
|
|
|
/* Private data for machine-part MOP500<->AB8500 */ |
|
struct mop500_ab8500_drvdata { |
|
/* Clocks */ |
|
enum mclk mclk_sel; |
|
struct clk *clk_ptr_intclk; |
|
struct clk *clk_ptr_sysclk; |
|
struct clk *clk_ptr_ulpclk; |
|
}; |
|
|
|
static inline const char *get_mclk_str(enum mclk mclk_sel) |
|
{ |
|
switch (mclk_sel) { |
|
case MCLK_SYSCLK: |
|
return "SYSCLK"; |
|
case MCLK_ULPCLK: |
|
return "ULPCLK"; |
|
default: |
|
return "Unknown"; |
|
} |
|
} |
|
|
|
static int mop500_ab8500_set_mclk(struct device *dev, |
|
struct mop500_ab8500_drvdata *drvdata) |
|
{ |
|
int status; |
|
struct clk *clk_ptr; |
|
|
|
if (IS_ERR(drvdata->clk_ptr_intclk)) { |
|
dev_err(dev, |
|
"%s: ERROR: intclk not initialized!\n", __func__); |
|
return -EIO; |
|
} |
|
|
|
switch (drvdata->mclk_sel) { |
|
case MCLK_SYSCLK: |
|
clk_ptr = drvdata->clk_ptr_sysclk; |
|
break; |
|
case MCLK_ULPCLK: |
|
clk_ptr = drvdata->clk_ptr_ulpclk; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
if (IS_ERR(clk_ptr)) { |
|
dev_err(dev, "%s: ERROR: %s not initialized!\n", __func__, |
|
get_mclk_str(drvdata->mclk_sel)); |
|
return -EIO; |
|
} |
|
|
|
status = clk_set_parent(drvdata->clk_ptr_intclk, clk_ptr); |
|
if (status) |
|
dev_err(dev, |
|
"%s: ERROR: Setting intclk parent to %s failed (ret = %d)!", |
|
__func__, get_mclk_str(drvdata->mclk_sel), status); |
|
else |
|
dev_dbg(dev, |
|
"%s: intclk parent changed to %s.\n", |
|
__func__, get_mclk_str(drvdata->mclk_sel)); |
|
|
|
return status; |
|
} |
|
|
|
/* |
|
* Control-events |
|
*/ |
|
|
|
static int mclk_input_control_get(struct snd_kcontrol *kcontrol, |
|
struct snd_ctl_elem_value *ucontrol) |
|
{ |
|
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); |
|
struct mop500_ab8500_drvdata *drvdata = |
|
snd_soc_card_get_drvdata(card); |
|
|
|
ucontrol->value.enumerated.item[0] = drvdata->mclk_sel; |
|
|
|
return 0; |
|
} |
|
|
|
static int mclk_input_control_put(struct snd_kcontrol *kcontrol, |
|
struct snd_ctl_elem_value *ucontrol) |
|
{ |
|
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); |
|
struct mop500_ab8500_drvdata *drvdata = |
|
snd_soc_card_get_drvdata(card); |
|
unsigned int val = ucontrol->value.enumerated.item[0]; |
|
|
|
if (val > (unsigned int)MCLK_ULPCLK) |
|
return -EINVAL; |
|
if (drvdata->mclk_sel == val) |
|
return 0; |
|
|
|
drvdata->mclk_sel = val; |
|
|
|
return 1; |
|
} |
|
|
|
/* |
|
* Controls |
|
*/ |
|
|
|
static struct snd_kcontrol_new mop500_ab8500_ctrls[] = { |
|
SOC_ENUM_EXT("Master Clock Select", |
|
soc_enum_mclk, |
|
mclk_input_control_get, mclk_input_control_put), |
|
SOC_DAPM_PIN_SWITCH("Headset Left"), |
|
SOC_DAPM_PIN_SWITCH("Headset Right"), |
|
SOC_DAPM_PIN_SWITCH("Earpiece"), |
|
SOC_DAPM_PIN_SWITCH("Speaker Left"), |
|
SOC_DAPM_PIN_SWITCH("Speaker Right"), |
|
SOC_DAPM_PIN_SWITCH("LineOut Left"), |
|
SOC_DAPM_PIN_SWITCH("LineOut Right"), |
|
SOC_DAPM_PIN_SWITCH("Vibra 1"), |
|
SOC_DAPM_PIN_SWITCH("Vibra 2"), |
|
SOC_DAPM_PIN_SWITCH("Mic 1"), |
|
SOC_DAPM_PIN_SWITCH("Mic 2"), |
|
SOC_DAPM_PIN_SWITCH("LineIn Left"), |
|
SOC_DAPM_PIN_SWITCH("LineIn Right"), |
|
SOC_DAPM_PIN_SWITCH("DMic 1"), |
|
SOC_DAPM_PIN_SWITCH("DMic 2"), |
|
SOC_DAPM_PIN_SWITCH("DMic 3"), |
|
SOC_DAPM_PIN_SWITCH("DMic 4"), |
|
SOC_DAPM_PIN_SWITCH("DMic 5"), |
|
SOC_DAPM_PIN_SWITCH("DMic 6"), |
|
}; |
|
|
|
/* ASoC */ |
|
|
|
static int mop500_ab8500_startup(struct snd_pcm_substream *substream) |
|
{ |
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
|
|
|
/* Set audio-clock source */ |
|
return mop500_ab8500_set_mclk(rtd->card->dev, |
|
snd_soc_card_get_drvdata(rtd->card)); |
|
} |
|
|
|
static void mop500_ab8500_shutdown(struct snd_pcm_substream *substream) |
|
{ |
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
|
struct device *dev = rtd->card->dev; |
|
|
|
dev_dbg(dev, "%s: Enter\n", __func__); |
|
|
|
/* Reset slots configuration to default(s) */ |
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
|
tx_slots = DEF_TX_SLOTS; |
|
else |
|
rx_slots = DEF_RX_SLOTS; |
|
} |
|
|
|
static int mop500_ab8500_hw_params(struct snd_pcm_substream *substream, |
|
struct snd_pcm_hw_params *params) |
|
{ |
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
|
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); |
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); |
|
struct device *dev = rtd->card->dev; |
|
unsigned int fmt; |
|
int channels, ret = 0, driver_mode, slots; |
|
unsigned int sw_codec, sw_cpu; |
|
bool is_playback; |
|
|
|
dev_dbg(dev, "%s: Enter\n", __func__); |
|
|
|
dev_dbg(dev, "%s: substream->pcm->name = %s\n" |
|
"substream->pcm->id = %s.\n" |
|
"substream->name = %s.\n" |
|
"substream->number = %d.\n", |
|
__func__, |
|
substream->pcm->name, |
|
substream->pcm->id, |
|
substream->name, |
|
substream->number); |
|
|
|
/* Ensure configuration consistency between DAIs */ |
|
mutex_lock(&mop500_ab8500_params_lock); |
|
if (mop500_ab8500_usage) { |
|
if (mop500_ab8500_rate != params_rate(params) || |
|
mop500_ab8500_channels != params_channels(params)) { |
|
mutex_unlock(&mop500_ab8500_params_lock); |
|
return -EBUSY; |
|
} |
|
} else { |
|
mop500_ab8500_rate = params_rate(params); |
|
mop500_ab8500_channels = params_channels(params); |
|
} |
|
__set_bit(cpu_dai->id, &mop500_ab8500_usage); |
|
mutex_unlock(&mop500_ab8500_params_lock); |
|
|
|
channels = params_channels(params); |
|
|
|
switch (params_format(params)) { |
|
case SNDRV_PCM_FORMAT_S32_LE: |
|
sw_cpu = 32; |
|
break; |
|
|
|
case SNDRV_PCM_FORMAT_S16_LE: |
|
sw_cpu = 16; |
|
break; |
|
|
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
/* Setup codec depending on driver-mode */ |
|
if (channels == 8) |
|
driver_mode = DRIVERMODE_CODEC_ONLY; |
|
else |
|
driver_mode = DRIVERMODE_NORMAL; |
|
dev_dbg(dev, "%s: Driver-mode: %s.\n", __func__, |
|
(driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY"); |
|
|
|
/* Setup format */ |
|
|
|
if (driver_mode == DRIVERMODE_NORMAL) { |
|
fmt = SND_SOC_DAIFMT_DSP_A | |
|
SND_SOC_DAIFMT_CBM_CFM | |
|
SND_SOC_DAIFMT_NB_NF | |
|
SND_SOC_DAIFMT_CONT; |
|
} else { |
|
fmt = SND_SOC_DAIFMT_DSP_A | |
|
SND_SOC_DAIFMT_CBM_CFM | |
|
SND_SOC_DAIFMT_NB_NF | |
|
SND_SOC_DAIFMT_GATED; |
|
} |
|
|
|
ret = snd_soc_runtime_set_dai_fmt(rtd, fmt); |
|
if (ret) |
|
return ret; |
|
|
|
/* Setup TDM-slots */ |
|
|
|
is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); |
|
switch (channels) { |
|
case 1: |
|
slots = 16; |
|
tx_slots = (is_playback) ? TX_SLOT_MONO : 0; |
|
rx_slots = (is_playback) ? 0 : RX_SLOT_MONO; |
|
break; |
|
case 2: |
|
slots = 16; |
|
tx_slots = (is_playback) ? TX_SLOT_STEREO : 0; |
|
rx_slots = (is_playback) ? 0 : RX_SLOT_STEREO; |
|
break; |
|
case 8: |
|
slots = 16; |
|
tx_slots = (is_playback) ? TX_SLOT_8CH : 0; |
|
rx_slots = (is_playback) ? 0 : RX_SLOT_8CH; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
if (driver_mode == DRIVERMODE_NORMAL) |
|
sw_codec = sw_cpu; |
|
else |
|
sw_codec = 20; |
|
|
|
dev_dbg(dev, "%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, |
|
tx_slots, rx_slots); |
|
ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots, |
|
sw_cpu); |
|
if (ret) |
|
return ret; |
|
|
|
dev_dbg(dev, "%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, |
|
tx_slots, rx_slots); |
|
ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots, |
|
sw_codec); |
|
if (ret) |
|
return ret; |
|
|
|
return 0; |
|
} |
|
|
|
static int mop500_ab8500_hw_free(struct snd_pcm_substream *substream) |
|
{ |
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); |
|
|
|
mutex_lock(&mop500_ab8500_params_lock); |
|
__clear_bit(cpu_dai->id, &mop500_ab8500_usage); |
|
mutex_unlock(&mop500_ab8500_params_lock); |
|
|
|
return 0; |
|
} |
|
|
|
struct snd_soc_ops mop500_ab8500_ops[] = { |
|
{ |
|
.hw_params = mop500_ab8500_hw_params, |
|
.hw_free = mop500_ab8500_hw_free, |
|
.startup = mop500_ab8500_startup, |
|
.shutdown = mop500_ab8500_shutdown, |
|
} |
|
}; |
|
|
|
int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd) |
|
{ |
|
struct snd_soc_dapm_context *dapm = &rtd->card->dapm; |
|
struct device *dev = rtd->card->dev; |
|
struct mop500_ab8500_drvdata *drvdata; |
|
int ret; |
|
|
|
dev_dbg(dev, "%s Enter.\n", __func__); |
|
|
|
/* Create driver private-data struct */ |
|
drvdata = devm_kzalloc(dev, sizeof(struct mop500_ab8500_drvdata), |
|
GFP_KERNEL); |
|
|
|
if (!drvdata) |
|
return -ENOMEM; |
|
|
|
snd_soc_card_set_drvdata(rtd->card, drvdata); |
|
|
|
/* Setup clocks */ |
|
|
|
drvdata->clk_ptr_sysclk = clk_get(dev, "sysclk"); |
|
if (IS_ERR(drvdata->clk_ptr_sysclk)) |
|
dev_warn(dev, "%s: WARNING: clk_get failed for 'sysclk'!\n", |
|
__func__); |
|
drvdata->clk_ptr_ulpclk = clk_get(dev, "ulpclk"); |
|
if (IS_ERR(drvdata->clk_ptr_ulpclk)) |
|
dev_warn(dev, "%s: WARNING: clk_get failed for 'ulpclk'!\n", |
|
__func__); |
|
drvdata->clk_ptr_intclk = clk_get(dev, "intclk"); |
|
if (IS_ERR(drvdata->clk_ptr_intclk)) |
|
dev_warn(dev, "%s: WARNING: clk_get failed for 'intclk'!\n", |
|
__func__); |
|
|
|
/* Set intclk default parent to ulpclk */ |
|
drvdata->mclk_sel = MCLK_ULPCLK; |
|
ret = mop500_ab8500_set_mclk(dev, drvdata); |
|
if (ret < 0) |
|
dev_warn(dev, "%s: WARNING: mop500_ab8500_set_mclk!\n", |
|
__func__); |
|
|
|
drvdata->mclk_sel = MCLK_ULPCLK; |
|
|
|
/* Add controls */ |
|
ret = snd_soc_add_card_controls(rtd->card, mop500_ab8500_ctrls, |
|
ARRAY_SIZE(mop500_ab8500_ctrls)); |
|
if (ret < 0) { |
|
pr_err("%s: Failed to add machine-controls (%d)!\n", |
|
__func__, ret); |
|
return ret; |
|
} |
|
|
|
ret = snd_soc_dapm_disable_pin(dapm, "Earpiece"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Left"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Right"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Left"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Right"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 1"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 2"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "Mic 1"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "Mic 2"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Left"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Right"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "DMic 1"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "DMic 2"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "DMic 3"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "DMic 4"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "DMic 5"); |
|
ret |= snd_soc_dapm_disable_pin(dapm, "DMic 6"); |
|
|
|
return ret; |
|
} |
|
|
|
void mop500_ab8500_remove(struct snd_soc_card *card) |
|
{ |
|
struct mop500_ab8500_drvdata *drvdata = snd_soc_card_get_drvdata(card); |
|
|
|
if (drvdata->clk_ptr_sysclk != NULL) |
|
clk_put(drvdata->clk_ptr_sysclk); |
|
if (drvdata->clk_ptr_ulpclk != NULL) |
|
clk_put(drvdata->clk_ptr_ulpclk); |
|
if (drvdata->clk_ptr_intclk != NULL) |
|
clk_put(drvdata->clk_ptr_intclk); |
|
|
|
snd_soc_card_set_drvdata(card, drvdata); |
|
}
|
|
|