mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 12:11:40 +00:00
d0e806b0cc
During the migration of Soundwire runtime stream allocation from
the Qualcomm Soundwire controller to SoC's soundcard drivers the sdm845
soundcard was forgotten.
At this point any playback attempt or audio daemon startup, for instance
on sdm845-db845c (Qualcomm RB3 board), will result in stream pointer
NULL dereference:
Unable to handle kernel NULL pointer dereference at virtual
address 0000000000000020
Mem abort info:
ESR = 0x0000000096000004
EC = 0x25: DABT (current EL), IL = 32 bits
SET = 0, FnV = 0
EA = 0, S1PTW = 0
FSC = 0x04: level 0 translation fault
Data abort info:
ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000
CM = 0, WnR = 0, TnD = 0, TagAccess = 0
GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
user pgtable: 4k pages, 48-bit VAs, pgdp=0000000101ecf000
[0000000000000020] pgd=0000000000000000, p4d=0000000000000000
Internal error: Oops: 0000000096000004 [#1] PREEMPT SMP
Modules linked in: ...
CPU: 5 UID: 0 PID: 1198 Comm: aplay
Not tainted 6.12.0-rc2-qcomlt-arm64-00059-g9d78f315a362-dirty #18
Hardware name: Thundercomm Dragonboard 845c (DT)
pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
pc : sdw_stream_add_slave+0x44/0x380 [soundwire_bus]
lr : sdw_stream_add_slave+0x44/0x380 [soundwire_bus]
sp : ffff80008a2035c0
x29: ffff80008a2035c0 x28: ffff80008a203978 x27: 0000000000000000
x26: 00000000000000c0 x25: 0000000000000000 x24: ffff1676025f4800
x23: ffff167600ff1cb8 x22: ffff167600ff1c98 x21: 0000000000000003
x20: ffff167607316000 x19: ffff167604e64e80 x18: 0000000000000000
x17: 0000000000000000 x16: ffffcec265074160 x15: 0000000000000000
x14: 0000000000000000 x13: 0000000000000000 x12: 0000000000000000
x11: 0000000000000000 x10: 0000000000000000 x9 : 0000000000000000
x8 : 0000000000000000 x7 : 0000000000000000 x6 : ffff167600ff1cec
x5 : ffffcec22cfa2010 x4 : 0000000000000000 x3 : 0000000000000003
x2 : ffff167613f836c0 x1 : 0000000000000000 x0 : ffff16761feb60b8
Call trace:
sdw_stream_add_slave+0x44/0x380 [soundwire_bus]
wsa881x_hw_params+0x68/0x80 [snd_soc_wsa881x]
snd_soc_dai_hw_params+0x3c/0xa4
__soc_pcm_hw_params+0x230/0x660
dpcm_be_dai_hw_params+0x1d0/0x3f8
dpcm_fe_dai_hw_params+0x98/0x268
snd_pcm_hw_params+0x124/0x460
snd_pcm_common_ioctl+0x998/0x16e8
snd_pcm_ioctl+0x34/0x58
__arm64_sys_ioctl+0xac/0xf8
invoke_syscall+0x48/0x104
el0_svc_common.constprop.0+0x40/0xe0
do_el0_svc+0x1c/0x28
el0_svc+0x34/0xe0
el0t_64_sync_handler+0x120/0x12c
el0t_64_sync+0x190/0x194
Code: aa0403fb f9418400 9100e000 9400102f (f8420f22)
---[ end trace 0000000000000000 ]---
0000000000006108 <sdw_stream_add_slave>:
6108: d503233f paciasp
610c: a9b97bfd stp x29, x30, [sp, #-112]!
6110: 910003fd mov x29, sp
6114: a90153f3 stp x19, x20, [sp, #16]
6118: a9025bf5 stp x21, x22, [sp, #32]
611c: aa0103f6 mov x22, x1
6120: 2a0303f5 mov w21, w3
6124: a90363f7 stp x23, x24, [sp, #48]
6128: aa0003f8 mov x24, x0
612c: aa0203f7 mov x23, x2
6130: a9046bf9 stp x25, x26, [sp, #64]
6134: aa0403f9 mov x25, x4 <-- x4 copied to x25
6138: a90573fb stp x27, x28, [sp, #80]
613c: aa0403fb mov x27, x4
6140: f9418400 ldr x0, [x0, #776]
6144: 9100e000 add x0, x0, #0x38
6148: 94000000 bl 0 <mutex_lock>
614c: f8420f22 ldr x2, [x25, #32]! <-- offset 0x44
^^^
This is 0x6108 + offset 0x44 from the beginning of sdw_stream_add_slave()
where data abort happens.
wsa881x_hw_params() is called with stream = NULL and passes it further
in register x4 (5th argument) to sdw_stream_add_slave() without any checks.
Value from x4 is copied to x25 and finally it aborts on trying to load
a value from address in x25 plus offset 32 (in dec) which corresponds
to master_list member in struct sdw_stream_runtime:
struct sdw_stream_runtime {
const char * name; /* 0 8 */
struct sdw_stream_params params; /* 8 12 */
enum sdw_stream_state state; /* 20 4 */
enum sdw_stream_type type; /* 24 4 */
/* XXX 4 bytes hole, try to pack */
here-> struct list_head master_list; /* 32 16 */
int m_rt_count; /* 48 4 */
/* size: 56, cachelines: 1, members: 6 */
/* sum members: 48, holes: 1, sum holes: 4 */
/* padding: 4 */
/* last cacheline: 56 bytes */
Fix this by adding required calls to qcom_snd_sdw_startup() and
sdw_release_stream() to startup and shutdown routines which restores
the previous correct behaviour when ->set_stream() method is called to
set a valid stream runtime pointer on playback startup.
Reproduced and then fix was tested on db845c RB3 board.
Reported-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Cc: stable@vger.kernel.org
Fixes: 15c7fab0e0
("ASoC: qcom: Move Soundwire runtime stream alloc to soundcards")
Cc: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Cc: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Cc: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Cc: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Alexey Klimov <alexey.klimov@linaro.org>
Tested-by: Steev Klimaszewski <steev@kali.org> # Lenovo Yoga C630
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Reviewed-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://patch.msgid.link/20241009213922.999355-1-alexey.klimov@linaro.org
Signed-off-by: Mark Brown <broonie@kernel.org>
634 lines
17 KiB
C
634 lines
17 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#include <dt-bindings/sound/qcom,q6afe.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/jack.h>
|
|
#include <sound/soc.h>
|
|
#include <linux/soundwire/sdw.h>
|
|
#include <uapi/linux/input-event-codes.h>
|
|
#include "common.h"
|
|
#include "qdsp6/q6afe.h"
|
|
#include "sdw.h"
|
|
#include "../codecs/rt5663.h"
|
|
|
|
#define DRIVER_NAME "sdm845"
|
|
#define DEFAULT_SAMPLE_RATE_48K 48000
|
|
#define DEFAULT_MCLK_RATE 24576000
|
|
#define TDM_BCLK_RATE 6144000
|
|
#define MI2S_BCLK_RATE 1536000
|
|
#define LEFT_SPK_TDM_TX_MASK 0x30
|
|
#define RIGHT_SPK_TDM_TX_MASK 0xC0
|
|
#define SPK_TDM_RX_MASK 0x03
|
|
#define NUM_TDM_SLOTS 8
|
|
#define SLIM_MAX_TX_PORTS 16
|
|
#define SLIM_MAX_RX_PORTS 13
|
|
#define WCD934X_DEFAULT_MCLK_RATE 9600000
|
|
|
|
struct sdm845_snd_data {
|
|
struct snd_soc_jack jack;
|
|
bool jack_setup;
|
|
bool slim_port_setup;
|
|
bool stream_prepared[AFE_PORT_MAX];
|
|
struct snd_soc_card *card;
|
|
uint32_t pri_mi2s_clk_count;
|
|
uint32_t sec_mi2s_clk_count;
|
|
uint32_t quat_tdm_clk_count;
|
|
struct sdw_stream_runtime *sruntime[AFE_PORT_MAX];
|
|
};
|
|
|
|
static struct snd_soc_jack_pin sdm845_jack_pins[] = {
|
|
{
|
|
.pin = "Headphone Jack",
|
|
.mask = SND_JACK_HEADPHONE,
|
|
},
|
|
{
|
|
.pin = "Headset Mic",
|
|
.mask = SND_JACK_MICROPHONE,
|
|
},
|
|
};
|
|
|
|
static unsigned int tdm_slot_offset[8] = {0, 4, 8, 12, 16, 20, 24, 28};
|
|
|
|
static int sdm845_slim_snd_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
|
struct snd_soc_dai *codec_dai;
|
|
struct sdm845_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card);
|
|
u32 rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS];
|
|
struct sdw_stream_runtime *sruntime;
|
|
u32 rx_ch_cnt = 0, tx_ch_cnt = 0;
|
|
int ret = 0, i;
|
|
|
|
for_each_rtd_codec_dais(rtd, i, codec_dai) {
|
|
sruntime = snd_soc_dai_get_stream(codec_dai,
|
|
substream->stream);
|
|
if (sruntime != ERR_PTR(-ENOTSUPP))
|
|
pdata->sruntime[cpu_dai->id] = sruntime;
|
|
|
|
ret = snd_soc_dai_get_channel_map(codec_dai,
|
|
&tx_ch_cnt, tx_ch, &rx_ch_cnt, rx_ch);
|
|
|
|
if (ret != 0 && ret != -ENOTSUPP) {
|
|
pr_err("failed to get codec chan map, err:%d\n", ret);
|
|
return ret;
|
|
} else if (ret == -ENOTSUPP) {
|
|
/* Ignore unsupported */
|
|
continue;
|
|
}
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL,
|
|
rx_ch_cnt, rx_ch);
|
|
else
|
|
ret = snd_soc_dai_set_channel_map(cpu_dai, tx_ch_cnt,
|
|
tx_ch, 0, NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
|
struct snd_soc_dai *codec_dai;
|
|
int ret = 0, j;
|
|
int channels, slot_width;
|
|
|
|
switch (params_format(params)) {
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
slot_width = 16;
|
|
break;
|
|
default:
|
|
dev_err(rtd->dev, "%s: invalid param format 0x%x\n",
|
|
__func__, params_format(params));
|
|
return -EINVAL;
|
|
}
|
|
|
|
channels = params_channels(params);
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0x3,
|
|
8, slot_width);
|
|
if (ret < 0) {
|
|
dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n",
|
|
__func__, ret);
|
|
goto end;
|
|
}
|
|
|
|
ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL,
|
|
channels, tdm_slot_offset);
|
|
if (ret < 0) {
|
|
dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n",
|
|
__func__, ret);
|
|
goto end;
|
|
}
|
|
} else {
|
|
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xf, 0,
|
|
8, slot_width);
|
|
if (ret < 0) {
|
|
dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n",
|
|
__func__, ret);
|
|
goto end;
|
|
}
|
|
|
|
ret = snd_soc_dai_set_channel_map(cpu_dai, channels,
|
|
tdm_slot_offset, 0, NULL);
|
|
if (ret < 0) {
|
|
dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n",
|
|
__func__, ret);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
for_each_rtd_codec_dais(rtd, j, codec_dai) {
|
|
|
|
if (!strcmp(codec_dai->component->name_prefix, "Left")) {
|
|
ret = snd_soc_dai_set_tdm_slot(
|
|
codec_dai, LEFT_SPK_TDM_TX_MASK,
|
|
SPK_TDM_RX_MASK, NUM_TDM_SLOTS,
|
|
slot_width);
|
|
if (ret < 0) {
|
|
dev_err(rtd->dev,
|
|
"DEV0 TDM slot err:%d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (!strcmp(codec_dai->component->name_prefix, "Right")) {
|
|
ret = snd_soc_dai_set_tdm_slot(
|
|
codec_dai, RIGHT_SPK_TDM_TX_MASK,
|
|
SPK_TDM_RX_MASK, NUM_TDM_SLOTS,
|
|
slot_width);
|
|
if (ret < 0) {
|
|
dev_err(rtd->dev,
|
|
"DEV1 TDM slot err:%d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
end:
|
|
return ret;
|
|
}
|
|
|
|
static int sdm845_snd_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
|
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
|
int ret = 0;
|
|
|
|
switch (cpu_dai->id) {
|
|
case PRIMARY_MI2S_RX:
|
|
case PRIMARY_MI2S_TX:
|
|
/*
|
|
* Use ASRC for internal clocks, as PLL rate isn't multiple
|
|
* of BCLK.
|
|
*/
|
|
rt5663_sel_asrc_clk_src(
|
|
codec_dai->component,
|
|
RT5663_DA_STEREO_FILTER | RT5663_AD_STEREO_FILTER,
|
|
RT5663_CLK_SEL_I2S1_ASRC);
|
|
ret = snd_soc_dai_set_sysclk(
|
|
codec_dai, RT5663_SCLK_S_MCLK, DEFAULT_MCLK_RATE,
|
|
SND_SOC_CLOCK_IN);
|
|
if (ret < 0)
|
|
dev_err(rtd->dev,
|
|
"snd_soc_dai_set_sysclk err = %d\n", ret);
|
|
break;
|
|
case QUATERNARY_TDM_RX_0:
|
|
case QUATERNARY_TDM_TX_0:
|
|
ret = sdm845_tdm_snd_hw_params(substream, params);
|
|
break;
|
|
case SLIMBUS_0_RX...SLIMBUS_6_TX:
|
|
ret = sdm845_slim_snd_hw_params(substream, params);
|
|
break;
|
|
case QUATERNARY_MI2S_RX:
|
|
break;
|
|
default:
|
|
pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void sdm845_jack_free(struct snd_jack *jack)
|
|
{
|
|
struct snd_soc_component *component = jack->private_data;
|
|
|
|
snd_soc_component_set_jack(component, NULL, NULL);
|
|
}
|
|
|
|
static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct snd_soc_card *card = rtd->card;
|
|
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
|
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
|
struct sdm845_snd_data *pdata = snd_soc_card_get_drvdata(card);
|
|
struct snd_soc_dai_link *link = rtd->dai_link;
|
|
struct snd_jack *jack;
|
|
/*
|
|
* Codec SLIMBUS configuration
|
|
* RX1, RX2, RX3, RX4, RX5, RX6, RX7, RX8, RX9, RX10, RX11, RX12, RX13
|
|
* TX1, TX2, TX3, TX4, TX5, TX6, TX7, TX8, TX9, TX10, TX11, TX12, TX13
|
|
* TX14, TX15, TX16
|
|
*/
|
|
unsigned int rx_ch[SLIM_MAX_RX_PORTS] = {144, 145, 146, 147, 148, 149,
|
|
150, 151, 152, 153, 154, 155, 156};
|
|
unsigned int tx_ch[SLIM_MAX_TX_PORTS] = {128, 129, 130, 131, 132, 133,
|
|
134, 135, 136, 137, 138, 139,
|
|
140, 141, 142, 143};
|
|
int rval, i;
|
|
|
|
|
|
if (!pdata->jack_setup) {
|
|
rval = snd_soc_card_jack_new_pins(card, "Headset Jack",
|
|
SND_JACK_HEADSET |
|
|
SND_JACK_HEADPHONE |
|
|
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
|
|
SND_JACK_BTN_2 | SND_JACK_BTN_3,
|
|
&pdata->jack,
|
|
sdm845_jack_pins,
|
|
ARRAY_SIZE(sdm845_jack_pins));
|
|
|
|
if (rval < 0) {
|
|
dev_err(card->dev, "Unable to add Headphone Jack\n");
|
|
return rval;
|
|
}
|
|
|
|
jack = pdata->jack.jack;
|
|
|
|
snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
|
|
snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
|
|
snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
|
|
snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
|
|
pdata->jack_setup = true;
|
|
}
|
|
|
|
switch (cpu_dai->id) {
|
|
case PRIMARY_MI2S_RX:
|
|
jack = pdata->jack.jack;
|
|
component = codec_dai->component;
|
|
|
|
jack->private_data = component;
|
|
jack->private_free = sdm845_jack_free;
|
|
rval = snd_soc_component_set_jack(component,
|
|
&pdata->jack, NULL);
|
|
if (rval != 0 && rval != -ENOTSUPP) {
|
|
dev_warn(card->dev, "Failed to set jack: %d\n", rval);
|
|
return rval;
|
|
}
|
|
break;
|
|
case SLIMBUS_0_RX...SLIMBUS_6_TX:
|
|
/* setting up wcd multiple times for slim port is redundant */
|
|
if (pdata->slim_port_setup || !link->no_pcm)
|
|
return 0;
|
|
|
|
for_each_rtd_codec_dais(rtd, i, codec_dai) {
|
|
rval = snd_soc_dai_set_channel_map(codec_dai,
|
|
ARRAY_SIZE(tx_ch),
|
|
tx_ch,
|
|
ARRAY_SIZE(rx_ch),
|
|
rx_ch);
|
|
if (rval != 0 && rval != -ENOTSUPP)
|
|
return rval;
|
|
|
|
snd_soc_dai_set_sysclk(codec_dai, 0,
|
|
WCD934X_DEFAULT_MCLK_RATE,
|
|
SNDRV_PCM_STREAM_PLAYBACK);
|
|
|
|
rval = snd_soc_component_set_jack(codec_dai->component,
|
|
&pdata->jack, NULL);
|
|
if (rval != 0 && rval != -ENOTSUPP) {
|
|
dev_warn(card->dev, "Failed to set jack: %d\n", rval);
|
|
return rval;
|
|
}
|
|
}
|
|
|
|
pdata->slim_port_setup = true;
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int sdm845_snd_startup(struct snd_pcm_substream *substream)
|
|
{
|
|
unsigned int fmt = SND_SOC_DAIFMT_BP_FP;
|
|
unsigned int codec_dai_fmt = SND_SOC_DAIFMT_BC_FC;
|
|
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
struct snd_soc_card *card = rtd->card;
|
|
struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
|
|
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
|
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
|
int j;
|
|
int ret;
|
|
|
|
switch (cpu_dai->id) {
|
|
case PRIMARY_MI2S_RX:
|
|
case PRIMARY_MI2S_TX:
|
|
codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF;
|
|
if (++(data->pri_mi2s_clk_count) == 1) {
|
|
snd_soc_dai_set_sysclk(cpu_dai,
|
|
Q6AFE_LPASS_CLK_ID_MCLK_1,
|
|
DEFAULT_MCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
|
|
snd_soc_dai_set_sysclk(cpu_dai,
|
|
Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT,
|
|
MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
|
|
}
|
|
snd_soc_dai_set_fmt(cpu_dai, fmt);
|
|
snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
|
|
break;
|
|
|
|
case SECONDARY_MI2S_TX:
|
|
codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S;
|
|
if (++(data->sec_mi2s_clk_count) == 1) {
|
|
snd_soc_dai_set_sysclk(cpu_dai,
|
|
Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT,
|
|
MI2S_BCLK_RATE, SNDRV_PCM_STREAM_CAPTURE);
|
|
}
|
|
snd_soc_dai_set_fmt(cpu_dai, fmt);
|
|
snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
|
|
break;
|
|
case QUATERNARY_MI2S_RX:
|
|
snd_soc_dai_set_sysclk(cpu_dai,
|
|
Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT,
|
|
MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
|
|
snd_soc_dai_set_fmt(cpu_dai, fmt);
|
|
|
|
|
|
break;
|
|
|
|
case QUATERNARY_TDM_RX_0:
|
|
case QUATERNARY_TDM_TX_0:
|
|
if (++(data->quat_tdm_clk_count) == 1) {
|
|
snd_soc_dai_set_sysclk(cpu_dai,
|
|
Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT,
|
|
TDM_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
|
|
}
|
|
|
|
codec_dai_fmt |= SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_B;
|
|
|
|
for_each_rtd_codec_dais(rtd, j, codec_dai) {
|
|
|
|
if (!strcmp(codec_dai->component->name_prefix,
|
|
"Left")) {
|
|
ret = snd_soc_dai_set_fmt(
|
|
codec_dai, codec_dai_fmt);
|
|
if (ret < 0) {
|
|
dev_err(rtd->dev,
|
|
"Left TDM fmt err:%d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (!strcmp(codec_dai->component->name_prefix,
|
|
"Right")) {
|
|
ret = snd_soc_dai_set_fmt(
|
|
codec_dai, codec_dai_fmt);
|
|
if (ret < 0) {
|
|
dev_err(rtd->dev,
|
|
"Right TDM slot err:%d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case SLIMBUS_0_RX...SLIMBUS_6_TX:
|
|
break;
|
|
|
|
default:
|
|
pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
|
|
break;
|
|
}
|
|
return qcom_snd_sdw_startup(substream);
|
|
}
|
|
|
|
static void sdm845_snd_shutdown(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
struct snd_soc_card *card = rtd->card;
|
|
struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
|
|
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
|
struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
|
|
|
|
switch (cpu_dai->id) {
|
|
case PRIMARY_MI2S_RX:
|
|
case PRIMARY_MI2S_TX:
|
|
if (--(data->pri_mi2s_clk_count) == 0) {
|
|
snd_soc_dai_set_sysclk(cpu_dai,
|
|
Q6AFE_LPASS_CLK_ID_MCLK_1,
|
|
0, SNDRV_PCM_STREAM_PLAYBACK);
|
|
snd_soc_dai_set_sysclk(cpu_dai,
|
|
Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT,
|
|
0, SNDRV_PCM_STREAM_PLAYBACK);
|
|
}
|
|
break;
|
|
|
|
case SECONDARY_MI2S_TX:
|
|
if (--(data->sec_mi2s_clk_count) == 0) {
|
|
snd_soc_dai_set_sysclk(cpu_dai,
|
|
Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT,
|
|
0, SNDRV_PCM_STREAM_CAPTURE);
|
|
}
|
|
break;
|
|
|
|
case QUATERNARY_TDM_RX_0:
|
|
case QUATERNARY_TDM_TX_0:
|
|
if (--(data->quat_tdm_clk_count) == 0) {
|
|
snd_soc_dai_set_sysclk(cpu_dai,
|
|
Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT,
|
|
0, SNDRV_PCM_STREAM_PLAYBACK);
|
|
}
|
|
break;
|
|
case SLIMBUS_0_RX...SLIMBUS_6_TX:
|
|
case QUATERNARY_MI2S_RX:
|
|
break;
|
|
|
|
default:
|
|
pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
|
|
break;
|
|
}
|
|
|
|
data->sruntime[cpu_dai->id] = NULL;
|
|
sdw_release_stream(sruntime);
|
|
}
|
|
|
|
static int sdm845_snd_prepare(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
struct sdm845_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
|
|
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
|
struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
|
|
int ret;
|
|
|
|
if (!sruntime)
|
|
return 0;
|
|
|
|
if (data->stream_prepared[cpu_dai->id]) {
|
|
sdw_disable_stream(sruntime);
|
|
sdw_deprepare_stream(sruntime);
|
|
data->stream_prepared[cpu_dai->id] = false;
|
|
}
|
|
|
|
ret = sdw_prepare_stream(sruntime);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/**
|
|
* NOTE: there is a strict hw requirement about the ordering of port
|
|
* enables and actual WSA881x PA enable. PA enable should only happen
|
|
* after soundwire ports are enabled if not DC on the line is
|
|
* accumulated resulting in Click/Pop Noise
|
|
* PA enable/mute are handled as part of codec DAPM and digital mute.
|
|
*/
|
|
|
|
ret = sdw_enable_stream(sruntime);
|
|
if (ret) {
|
|
sdw_deprepare_stream(sruntime);
|
|
return ret;
|
|
}
|
|
data->stream_prepared[cpu_dai->id] = true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sdm845_snd_hw_free(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
struct sdm845_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
|
|
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
|
struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id];
|
|
|
|
if (sruntime && data->stream_prepared[cpu_dai->id]) {
|
|
sdw_disable_stream(sruntime);
|
|
sdw_deprepare_stream(sruntime);
|
|
data->stream_prepared[cpu_dai->id] = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_ops sdm845_be_ops = {
|
|
.hw_params = sdm845_snd_hw_params,
|
|
.hw_free = sdm845_snd_hw_free,
|
|
.prepare = sdm845_snd_prepare,
|
|
.startup = sdm845_snd_startup,
|
|
.shutdown = sdm845_snd_shutdown,
|
|
};
|
|
|
|
static int sdm845_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_interval *rate = hw_param_interval(params,
|
|
SNDRV_PCM_HW_PARAM_RATE);
|
|
struct snd_interval *channels = hw_param_interval(params,
|
|
SNDRV_PCM_HW_PARAM_CHANNELS);
|
|
struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
|
|
|
rate->min = rate->max = DEFAULT_SAMPLE_RATE_48K;
|
|
channels->min = channels->max = 2;
|
|
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_dapm_widget sdm845_snd_widgets[] = {
|
|
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
|
SND_SOC_DAPM_MIC("Headset Mic", NULL),
|
|
SND_SOC_DAPM_SPK("Left Spk", NULL),
|
|
SND_SOC_DAPM_SPK("Right Spk", NULL),
|
|
SND_SOC_DAPM_MIC("Int Mic", NULL),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new sdm845_snd_controls[] = {
|
|
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
|
|
SOC_DAPM_PIN_SWITCH("Headset Mic"),
|
|
};
|
|
|
|
static void sdm845_add_ops(struct snd_soc_card *card)
|
|
{
|
|
struct snd_soc_dai_link *link;
|
|
int i;
|
|
|
|
for_each_card_prelinks(card, i, link) {
|
|
if (link->no_pcm == 1) {
|
|
link->ops = &sdm845_be_ops;
|
|
link->be_hw_params_fixup = sdm845_be_hw_params_fixup;
|
|
}
|
|
link->init = sdm845_dai_init;
|
|
}
|
|
}
|
|
|
|
static int sdm845_snd_platform_probe(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_card *card;
|
|
struct sdm845_snd_data *data;
|
|
struct device *dev = &pdev->dev;
|
|
int ret;
|
|
|
|
card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
|
|
if (!card)
|
|
return -ENOMEM;
|
|
|
|
/* Allocate the private data */
|
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
card->driver_name = DRIVER_NAME;
|
|
card->dapm_widgets = sdm845_snd_widgets;
|
|
card->num_dapm_widgets = ARRAY_SIZE(sdm845_snd_widgets);
|
|
card->controls = sdm845_snd_controls;
|
|
card->num_controls = ARRAY_SIZE(sdm845_snd_controls);
|
|
card->dev = dev;
|
|
card->owner = THIS_MODULE;
|
|
dev_set_drvdata(dev, card);
|
|
ret = qcom_snd_parse_of(card);
|
|
if (ret)
|
|
return ret;
|
|
|
|
data->card = card;
|
|
snd_soc_card_set_drvdata(card, data);
|
|
|
|
sdm845_add_ops(card);
|
|
return devm_snd_soc_register_card(dev, card);
|
|
}
|
|
|
|
static const struct of_device_id sdm845_snd_device_id[] = {
|
|
{ .compatible = "qcom,sdm845-sndcard" },
|
|
/* Do not grow the list for compatible devices */
|
|
{ .compatible = "qcom,db845c-sndcard" },
|
|
{ .compatible = "lenovo,yoga-c630-sndcard" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sdm845_snd_device_id);
|
|
|
|
static struct platform_driver sdm845_snd_driver = {
|
|
.probe = sdm845_snd_platform_probe,
|
|
.driver = {
|
|
.name = "msm-snd-sdm845",
|
|
.of_match_table = sdm845_snd_device_id,
|
|
},
|
|
};
|
|
module_platform_driver(sdm845_snd_driver);
|
|
|
|
MODULE_DESCRIPTION("sdm845 ASoC Machine Driver");
|
|
MODULE_LICENSE("GPL");
|