From 480bd22405eb840a9d9e5a276b0f8b029c30c183 Mon Sep 17 00:00:00 2001 From: Pratyush Meduri Date: Tue, 30 Jun 2026 14:28:13 +0530 Subject: [PATCH 1/3] audioreach-driver: add AUD_INTF PRM clocks Shikra AudioReach device trees consume AUD_INTF IBIT/EBIT clocks through q6prmcc for MI2S/TDM bit clock voting. Add the missing PRM clock IDs and provider entries so those clock references can resolve and be voted by kernel clients. Signed-off-by: Pratyush Meduri --- audioreach-driver/q6prm_audioreach.h | 64 ++++++++++++++++++++++ audioreach-driver/q6prm_audioreach_clock.c | 28 ++++++++++ 2 files changed, 92 insertions(+) diff --git a/audioreach-driver/q6prm_audioreach.h b/audioreach-driver/q6prm_audioreach.h index 51fb155..846f759 100644 --- a/audioreach-driver/q6prm_audioreach.h +++ b/audioreach-driver/q6prm_audioreach.h @@ -97,11 +97,75 @@ /* Clock ID for RX CORE MCLK2 2X MCLK */ #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF0_IBIT 0x500 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF0_EBIT 0x501 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF1_IBIT 0x502 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF1_EBIT 0x503 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF2_IBIT 0x504 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF2_EBIT 0x505 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF3_IBIT 0x506 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF3_EBIT 0x507 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF4_IBIT 0x508 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF4_EBIT 0x509 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF5_IBIT 0x50A +#define Q6PRM_LPASS_CLK_ID_AUD_INTF5_EBIT 0x50B +#define Q6PRM_LPASS_CLK_ID_AUD_INTF6_IBIT 0x50C +#define Q6PRM_LPASS_CLK_ID_AUD_INTF6_EBIT 0x50D +#define Q6PRM_LPASS_CLK_ID_AUD_INTF7_IBIT 0x50E +#define Q6PRM_LPASS_CLK_ID_AUD_INTF7_EBIT 0x50F +#define Q6PRM_LPASS_CLK_ID_AUD_INTF8_IBIT 0x510 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF8_EBIT 0x511 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF9_IBIT 0x512 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF9_EBIT 0x513 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF10_IBIT 0x514 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF10_EBIT 0x515 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF11_IBIT 0x516 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF11_EBIT 0x517 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF12_IBIT 0x518 +#define Q6PRM_LPASS_CLK_ID_AUD_INTF12_EBIT 0x519 +#define Q6PRM_LPASS_CLK_ID_AUD_VA_INTF0_IBIT 0x550 +#define Q6PRM_LPASS_CLK_ID_AUD_VA_INTF0_EBIT 0x551 + +#ifndef LPASS_CLK_ID_AUD_INTF0_IBIT +#define LPASS_CLK_ID_AUD_INTF0_IBIT 71 +#define LPASS_CLK_ID_AUD_INTF0_EBIT 72 +#define LPASS_CLK_ID_AUD_INTF1_IBIT 73 +#define LPASS_CLK_ID_AUD_INTF1_EBIT 74 +#define LPASS_CLK_ID_AUD_INTF2_IBIT 75 +#define LPASS_CLK_ID_AUD_INTF2_EBIT 76 +#define LPASS_CLK_ID_AUD_INTF3_IBIT 77 +#define LPASS_CLK_ID_AUD_INTF3_EBIT 78 +#define LPASS_CLK_ID_AUD_INTF4_IBIT 79 +#define LPASS_CLK_ID_AUD_INTF4_EBIT 80 +#define LPASS_CLK_ID_AUD_INTF5_IBIT 81 +#define LPASS_CLK_ID_AUD_INTF5_EBIT 82 +#define LPASS_CLK_ID_AUD_INTF6_IBIT 83 +#define LPASS_CLK_ID_AUD_INTF6_EBIT 84 +#define LPASS_CLK_ID_AUD_INTF7_IBIT 85 +#define LPASS_CLK_ID_AUD_INTF7_EBIT 86 +#define LPASS_CLK_ID_AUD_INTF8_IBIT 87 +#define LPASS_CLK_ID_AUD_INTF8_EBIT 88 +#define LPASS_CLK_ID_AUD_INTF9_IBIT 89 +#define LPASS_CLK_ID_AUD_INTF9_EBIT 90 +#define LPASS_CLK_ID_AUD_INTF10_IBIT 91 +#define LPASS_CLK_ID_AUD_INTF10_EBIT 92 +#define LPASS_CLK_ID_AUD_INTF11_IBIT 93 +#define LPASS_CLK_ID_AUD_INTF11_EBIT 94 +#define LPASS_CLK_ID_AUD_INTF12_IBIT 95 +#define LPASS_CLK_ID_AUD_INTF12_EBIT 96 +#define LPASS_CLK_ID_AUD_VA_INTF0_IBIT 97 +#define LPASS_CLK_ID_AUD_VA_INTF0_EBIT 98 +#endif + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 #define Q6PRM_HW_CORE_ID_LPASS 1 #define Q6PRM_HW_CORE_ID_DCODEC 2 +#define LPAIF_MI2S_MCLK 1 +#define LPAIF_MI2S_BCLK 2 +#define LPAIF_MI2S_ECLK 3 + struct audio_hw_clk_cfg { uint32_t clock_id; uint32_t clock_freq; diff --git a/audioreach-driver/q6prm_audioreach_clock.c b/audioreach-driver/q6prm_audioreach_clock.c index 1ac5e01..87e60bb 100644 --- a/audioreach-driver/q6prm_audioreach_clock.c +++ b/audioreach-driver/q6prm_audioreach_clock.c @@ -65,6 +65,34 @@ static const struct q6dsp_clk_init q6prm_clks[] = { Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF0_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF0_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF1_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF1_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF2_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF2_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF3_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF3_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF4_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF4_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF5_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF5_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF6_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF6_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF7_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF7_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF8_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF8_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF9_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF9_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF10_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF10_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF11_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF11_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF12_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_INTF12_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_VA_INTF0_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_AUD_VA_INTF0_EBIT), Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, "LPASS_HW_MACRO"), Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, From d9c2238f15738160c88ceb733126e1fcbf8eedc0 Mon Sep 17 00:00:00 2001 From: Pratyush Meduri Date: Tue, 30 Jun 2026 14:28:13 +0530 Subject: [PATCH 2/3] audioreach-driver: use GPR device destination domain Route APM/SPF/PRM GPR packets to the domain advertised by the GPR device instead of always forcing ADSP. This keeps the path compatible with non-default AudioReach DSP domains while preserving ADSP as the fallback. Signed-off-by: Pratyush Meduri --- audioreach-driver/q6apm_audio_pkt.c | 17 +++++++++++------ audioreach-driver/q6prm_audioreach.c | 20 ++++++++++++-------- audioreach-driver/q6prm_audioreach.h | 7 +++++++ dsp/spf-core.c | 7 ++++++- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/audioreach-driver/q6apm_audio_pkt.c b/audioreach-driver/q6apm_audio_pkt.c index 768e8aa..b308ac1 100644 --- a/audioreach-driver/q6apm_audio_pkt.c +++ b/audioreach-driver/q6apm_audio_pkt.c @@ -181,7 +181,8 @@ static int q6apm_audio_send_cmd(struct q6apm_audio_pkt *apm, struct gpr_pkt *pkt } static void *__q6apm_audio_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token, - uint32_t src_port, uint32_t dest_port, bool has_cmd_hdr) + uint32_t src_port, uint32_t dest_port, + uint16_t dest_domain, bool has_cmd_hdr) { struct gpr_pkt *pkt; void *p; @@ -201,7 +202,7 @@ static void *__q6apm_audio_alloc_pkt(int payload_size, uint32_t opcode, uint32_t pkt->hdr.dest_port = dest_port; pkt->hdr.src_port = src_port; - pkt->hdr.dest_domain = GPR_DOMAIN_ID_ADSP; + pkt->hdr.dest_domain = dest_domain ?: GPR_DOMAIN_ID_ADSP; pkt->hdr.src_domain = GPR_DOMAIN_ID_APPS; pkt->hdr.token = token; pkt->hdr.opcode = opcode; @@ -217,17 +218,21 @@ static void *__q6apm_audio_alloc_pkt(int payload_size, uint32_t opcode, uint32_t return pkt; } -static void *q6apm_audio_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t token) +static void *q6apm_audio_alloc_apm_cmd_pkt(struct q6apm_audio_pkt *apm, + int pkt_size, uint32_t opcode, + uint32_t token) { return __q6apm_audio_alloc_pkt(pkt_size, opcode, token, GPR_APM_MODULE_IID, - APM_MODULE_INSTANCE_ID, true); + APM_MODULE_INSTANCE_ID, + audioreach_gpr_dest_domain(apm ? apm->adev : NULL), + true); } static int q6apm_audio_get_apm_state(struct q6apm_audio_pkt *apm) { struct gpr_pkt *pkt; - pkt = q6apm_audio_alloc_apm_cmd_pkt(0, APM_CMD_GET_SPF_STATE, 0); + pkt = q6apm_audio_alloc_apm_cmd_pkt(apm, 0, APM_CMD_GET_SPF_STATE, 0); if (IS_ERR(pkt)) return PTR_ERR(pkt); @@ -250,7 +255,7 @@ static void q6apm_audio_close_all(void) { struct gpr_pkt *pkt; - pkt = q6apm_audio_alloc_apm_cmd_pkt(0, APM_CMD_CLOSE_ALL, 0); + pkt = q6apm_audio_alloc_apm_cmd_pkt(g_apm, 0, APM_CMD_CLOSE_ALL, 0); if (IS_ERR(pkt)) return; diff --git a/audioreach-driver/q6prm_audioreach.c b/audioreach-driver/q6prm_audioreach.c index d06326e..57961da 100644 --- a/audioreach-driver/q6prm_audioreach.c +++ b/audioreach-driver/q6prm_audioreach.c @@ -106,7 +106,8 @@ static int q6prm_audioreach_send_cmd_sync(struct q6prm *prm, struct gpr_pkt *pkt } static void *__q6prm_audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token, - uint32_t src_port, uint32_t dest_port, bool has_cmd_hdr) + uint32_t src_port, uint32_t dest_port, + uint16_t dest_domain, bool has_cmd_hdr) { struct gpr_pkt *pkt; void *p; @@ -126,7 +127,7 @@ static void *__q6prm_audioreach_alloc_pkt(int payload_size, uint32_t opcode, uin pkt->hdr.dest_port = dest_port; pkt->hdr.src_port = src_port; - pkt->hdr.dest_domain = GPR_DOMAIN_ID_ADSP; + pkt->hdr.dest_domain = dest_domain ?: GPR_DOMAIN_ID_ADSP; pkt->hdr.src_domain = GPR_DOMAIN_ID_APPS; pkt->hdr.token = token; pkt->hdr.opcode = opcode; @@ -142,10 +143,12 @@ static void *__q6prm_audioreach_alloc_pkt(int payload_size, uint32_t opcode, uin return pkt; } -static void *q6prm_audioreach_alloc_cmd_pkt(int payload_size, uint32_t opcode, uint32_t token, - uint32_t src_port, uint32_t dest_port) +static void *q6prm_audioreach_alloc_cmd_pkt(gpr_device_t *gdev, int payload_size, + uint32_t opcode, uint32_t token, uint32_t src_port, + uint32_t dest_port) { - return __q6prm_audioreach_alloc_pkt(payload_size, opcode, token, src_port, dest_port, true); + return __q6prm_audioreach_alloc_pkt(payload_size, opcode, token, src_port, + dest_port, audioreach_gpr_dest_domain(gdev), true); } static int q6prm_audioreach_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool enable) @@ -166,7 +169,8 @@ static int q6prm_audioreach_set_hw_core_req(struct device *dev, uint32_t hw_bloc rsp_opcode = PRM_CMD_RSP_RELEASE_HW_RSC; } - pkt = q6prm_audioreach_alloc_cmd_pkt(sizeof(*req), opcode, 0, gdev->svc.id, GPR_PRM_MODULE_IID); + pkt = q6prm_audioreach_alloc_cmd_pkt(gdev, sizeof(*req), opcode, 0, + gdev->svc.id, GPR_PRM_MODULE_IID); if (IS_ERR(pkt)) return PTR_ERR(pkt); @@ -214,7 +218,7 @@ static int q6prm_audioreach_request_lpass_clock(struct device *dev, int clk_id, struct gpr_pkt *pkt; int rc; - pkt = q6prm_audioreach_alloc_cmd_pkt(sizeof(*req), PRM_CMD_REQUEST_HW_RSC, 0, gdev->svc.id, + pkt = q6prm_audioreach_alloc_cmd_pkt(gdev, sizeof(*req), PRM_CMD_REQUEST_HW_RSC, 0, gdev->svc.id, GPR_PRM_MODULE_IID); if (IS_ERR(pkt)) return PTR_ERR(pkt); @@ -251,7 +255,7 @@ static int q6prm_audioreach_release_lpass_clock(struct device *dev, int clk_id, struct gpr_pkt *pkt; int rc; - pkt = q6prm_audioreach_alloc_cmd_pkt(sizeof(*rel), PRM_CMD_RELEASE_HW_RSC, 0, gdev->svc.id, + pkt = q6prm_audioreach_alloc_cmd_pkt(gdev, sizeof(*rel), PRM_CMD_RELEASE_HW_RSC, 0, gdev->svc.id, GPR_PRM_MODULE_IID); if (IS_ERR(pkt)) return PTR_ERR(pkt); diff --git a/audioreach-driver/q6prm_audioreach.h b/audioreach-driver/q6prm_audioreach.h index 846f759..ffd5841 100644 --- a/audioreach-driver/q6prm_audioreach.h +++ b/audioreach-driver/q6prm_audioreach.h @@ -4,7 +4,9 @@ #ifndef __Q6PRM_AUDIOREACH_H__ #define __Q6PRM_AUDIOREACH_H__ +#include #include +#include /* Clock ID for Primary I2S IBIT */ #define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 @@ -195,6 +197,11 @@ struct q6dsp_clk_desc { int (*lpass_unvote_clk)(struct device *dev, uint32_t hid, uint32_t h); }; +static inline u16 audioreach_gpr_dest_domain(gpr_device_t *gdev) +{ + return gdev && gdev->domain_id ? gdev->domain_id : GPR_DOMAIN_ID_ADSP; +} + int q6dsp_clock_dev_probe(struct platform_device *pdev); int q6prm_audioreach_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, diff --git a/dsp/spf-core.c b/dsp/spf-core.c index 28ad3c1..68c1811 100644 --- a/dsp/spf-core.c +++ b/dsp/spf-core.c @@ -36,6 +36,11 @@ struct spf_core_private { struct work_struct add_chld_dev_work; }; +static inline u16 spf_core_gpr_dest_domain(gpr_device_t *gdev) +{ + return gdev && gdev->domain_id ? gdev->domain_id : GPR_DOMAIN_ID_ADSP; +} + static struct spf_core_private *spf_core_priv; struct apm_cmd_rsp_get_spf_status_t @@ -93,7 +98,7 @@ static bool __spf_core_is_apm_ready(struct spf_core *core) pkt.hdr.opcode = APM_CMD_GET_SPF_STATE; pkt.hdr.dest_port = APM_MODULE_INSTANCE_ID; pkt.hdr.src_port = adev->svc_id; //1 - pkt.hdr.dest_domain = GPR_DOMAIN_ID_ADSP; + pkt.hdr.dest_domain = spf_core_gpr_dest_domain(adev); pkt.hdr.src_domain = GPR_DOMAIN_ID_APPS; pkt.hdr.opcode = APM_CMD_GET_SPF_STATE; From 342e2481f2805b12b861fb54b7451946d996df61 Mon Sep 17 00:00:00 2001 From: Pratyush Meduri Date: Tue, 30 Jun 2026 14:28:14 +0530 Subject: [PATCH 3/3] audioreach-driver: add Shikra sound card support Add Shikra CQM/CQS/IQS support to the common AudioReach machine driver without changing the existing platform match flow. Select card data only for Shikra compatibles or Shikra model names, so other targets keep the existing widgets and controls. Parse AudioReach DPCM platform nodes from device tree, skip SoundWire stream setup for non-SoundWire MI2S/TDM backends, and add the Shikra-specific codec/sysclk setup needed by the IQS and CQS links. Add optional mclk/bclk/eclk handling to q6apm LPASS dummy DAIs so machine-driver sysclk requests become kernel clock votes through q6prmcc. Signed-off-by: Pratyush Meduri --- audioreach-driver/audioreach_common.c | 181 ++++++++++++++++++++- audioreach-driver/q6apm_lpass_dummy_dais.c | 154 ++++++++++++++++++ 2 files changed, 333 insertions(+), 2 deletions(-) diff --git a/audioreach-driver/audioreach_common.c b/audioreach-driver/audioreach_common.c index 523b8bd..2fb9f32 100644 --- a/audioreach-driver/audioreach_common.c +++ b/audioreach-driver/audioreach_common.c @@ -4,10 +4,13 @@ #include #include +#include #include +#include #include #include #include +#include #include #include #include @@ -16,6 +19,8 @@ #define AFE_PORT_MAX 137 #define NAME_SIZE 32 +#define SHIKRA_IQS_MCLK_RATE 12288000 +#define SHIKRA_CQS_BCLK_RATE 3072000 struct qcs6490_snd_data { bool stream_prepared[AFE_PORT_MAX]; @@ -36,6 +41,34 @@ struct qcom_snd_common_data { struct qcom_snd_dailink_data *link_data; }; +static bool qcs6490_snd_model_is(struct snd_soc_card *card, const char *model) +{ + const char *card_model; + + if (of_property_read_string(card->dev->of_node, "model", &card_model)) + return false; + + return !strcmp(card_model, model); +} + +static bool qcs6490_snd_is_shikra_cqm(struct snd_soc_card *card) +{ + return of_device_is_compatible(card->dev->of_node, "qcom,shikra-cqm-sndcard") || + qcs6490_snd_model_is(card, "shikra-cqm-evk"); +} + +static bool qcs6490_snd_is_shikra_cqs(struct snd_soc_card *card) +{ + return of_device_is_compatible(card->dev->of_node, "qcom,shikra-cqs-sndcard") || + qcs6490_snd_model_is(card, "shikra-cqs-evk"); +} + +static bool qcs6490_snd_is_shikra_iqs(struct snd_soc_card *card) +{ + return of_device_is_compatible(card->dev->of_node, "qcom,shikra-iqs-sndcard") || + qcs6490_snd_model_is(card, "shikra-iqs-evk"); +} + static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Mic Jack", NULL), @@ -49,6 +82,24 @@ static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { SND_SOC_DAPM_SPK("DP7 Jack", NULL), }; +static struct snd_soc_dapm_widget shikra_cqm_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_widget shikra_iqs_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_kcontrol_new shikra_iqs_controls[] = { + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; static struct snd_soc_jack_pin qcs6490_headset_jack_pins[] = { /* Headset */ @@ -62,6 +113,23 @@ static struct snd_soc_jack_pin qcs6490_headset_jack_pins[] = { }, }; +static bool qcs6490_snd_is_sdw_dai(int dai_id) +{ + switch (dai_id) { + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + case RX_CODEC_DMA_RX_0: + case RX_CODEC_DMA_RX_1: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: + case TX_CODEC_DMA_TX_3: + return true; + default: + return false; + } +} + static int qcs6490_snd_sdw_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); @@ -72,6 +140,9 @@ static int qcs6490_snd_sdw_startup(struct snd_pcm_substream *substream) u32 rx_ch_cnt = 0, tx_ch_cnt = 0; int ret, i, j; + if (!qcs6490_snd_is_sdw_dai(cpu_dai->id)) + return 0; + sruntime = sdw_alloc_stream(cpu_dai->name, SDW_STREAM_PCM); if (!sruntime) return -ENOMEM; @@ -260,8 +331,12 @@ static void qcs6490_snd_shutdown(struct snd_pcm_substream *substream) break; } + if (!qcs6490_snd_is_sdw_dai(cpu_dai->id)) + return; + pdata->sruntime[cpu_dai->id] = NULL; - sdw_release_stream(sruntime); + if (sruntime) + sdw_release_stream(sruntime); } static int qcs6490_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd, @@ -480,6 +555,7 @@ static int qcs6490_snd_parse_of(struct snd_soc_card *card) } cpu = of_get_child_by_name(np, "cpu"); + platform = of_get_child_by_name(np, "platform"); codec = of_get_child_by_name(np, "codec"); if (!cpu) { @@ -496,7 +572,18 @@ static int qcs6490_snd_parse_of(struct snd_soc_card *card) } link->id = args.args[0]; - link->platforms->of_node = link->cpus->of_node; + if (platform) { + link->platforms->of_node = of_parse_phandle(platform, + "sound-dai", 0); + if (!link->platforms->of_node) { + dev_err(card->dev, "%s: platform dai not found\n", + link->name); + ret = -EINVAL; + goto err; + } + } else { + link->platforms->of_node = link->cpus->of_node; + } if (codec) { ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); @@ -585,6 +672,9 @@ static int qcs6490_snd_init(struct snd_soc_pcm_runtime *rtd) if (dp_jack) return qcs6490_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); + if (qcs6490_snd_is_shikra_cqs(card) || qcs6490_snd_is_shikra_iqs(card)) + return 0; + return qcs6490_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); } @@ -615,12 +705,81 @@ static int qcs6490_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, return 0; } +static int qcs6490_snd_set_codec_sysclk(struct snd_soc_pcm_runtime *rtd, + unsigned int freq) +{ + struct snd_soc_dai *codec_dai; + int ret; + int i; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, freq, + SND_SOC_CLOCK_IN); + if (ret) + return ret; + } + + return 0; +} + +static int qcs6490_snd_set_iqs_codec_fmt(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai; + unsigned int fmt = SND_SOC_DAIFMT_CBP_CFP | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S; + int ret; + int i; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) + return ret; + } + + return 0; +} + static int qcs6490_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 qcs6490_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); + int bclk_freq; + int ret; + + if (qcs6490_snd_is_shikra_iqs(rtd->card) && + (cpu_dai->id == PRIMARY_MI2S_RX || cpu_dai->id == PRIMARY_MI2S_TX)) { + ret = qcs6490_snd_set_iqs_codec_fmt(rtd); + if (ret) + return ret; + + ret = qcs6490_snd_set_codec_sysclk(rtd, SHIKRA_IQS_MCLK_RATE); + if (ret) + return ret; + + bclk_freq = snd_soc_params_to_bclk(params); + if (bclk_freq <= 0) + return -EINVAL; + + ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, + bclk_freq, SND_SOC_CLOCK_IN); + if (ret) + return ret; + } + + if (qcs6490_snd_is_shikra_cqs(rtd->card) && + cpu_dai->id == SECONDARY_TDM_RX_0) { + ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, + SHIKRA_CQS_BCLK_RATE, + SND_SOC_CLOCK_IN); + if (ret) + return ret; + + ret = qcs6490_snd_set_codec_sysclk(rtd, SHIKRA_CQS_BCLK_RATE); + if (ret) + return ret; + } return qcs6490_snd_sdw_hw_params(substream, params, &pdata->sruntime[cpu_dai->id]); } @@ -667,6 +826,19 @@ static void qcs6490_add_be_ops(struct snd_soc_card *card) } } +static void qcs6490_add_shikra_card_data(struct snd_soc_card *card) +{ + if (qcs6490_snd_is_shikra_cqm(card) || qcs6490_snd_is_shikra_cqs(card)) { + card->dapm_widgets = shikra_cqm_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(shikra_cqm_dapm_widgets); + } else if (qcs6490_snd_is_shikra_iqs(card)) { + card->dapm_widgets = shikra_iqs_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(shikra_iqs_dapm_widgets); + card->controls = shikra_iqs_controls; + card->num_controls = ARRAY_SIZE(shikra_iqs_controls); + } +} + static int qcs6490_platform_probe(struct platform_device *pdev) { struct snd_soc_card *card; @@ -686,6 +858,7 @@ static int qcs6490_platform_probe(struct platform_device *pdev) card->dev = dev; dev_set_drvdata(dev, card); snd_soc_card_set_drvdata(card, data); + qcs6490_add_shikra_card_data(card); ret = qcs6490_snd_parse_of(card); if (ret) return ret; @@ -699,6 +872,10 @@ static const struct of_device_id snd_qcs6490_dt_match[] = { {.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"}, {.compatible = "qcom,qcs615-sndcard", "qcs615"}, {.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"}, + {.compatible = "qcom,shikra-sndcard", "shikra"}, + {.compatible = "qcom,shikra-cqm-sndcard", "shikra"}, + {.compatible = "qcom,shikra-cqs-sndcard", "shikra"}, + {.compatible = "qcom,shikra-iqs-sndcard", "shikra"}, {.compatible = "qcom,qcs8275-sndcard", "qcs8275"}, {.compatible = "qcom,qcs8300-sndcard", "qcs8300"}, {.compatible = "qcom,qcs9075-sndcard", "qcs9075"}, diff --git a/audioreach-driver/q6apm_lpass_dummy_dais.c b/audioreach-driver/q6apm_lpass_dummy_dais.c index 0bd7292..424a885 100644 --- a/audioreach-driver/q6apm_lpass_dummy_dais.c +++ b/audioreach-driver/q6apm_lpass_dummy_dais.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,15 @@ #define APM_PORT_MAX 127 #define AUDIOREACH_BE_PCM_BASE 16 +struct q6apm_dai_priv_data { + struct clk *mclk; + struct clk *bclk; + struct clk *eclk; + bool mclk_enabled; + bool bclk_enabled; + bool eclk_enabled; +}; + #define Q6AFE_TDM_PB_DAI(pre, num, did) { \ .playback = { \ .stream_name = pre" TDM"#num" Playback", \ @@ -613,6 +623,7 @@ struct q6apm_lpass_dai_data { struct q6apm_graph *graph[APM_PORT_MAX]; bool is_port_started[APM_PORT_MAX]; struct audioreach_module_config module_config[APM_PORT_MAX]; + struct q6apm_dai_priv_data priv[APM_PORT_MAX]; }; static const struct snd_pcm_hardware q6apm_dummy_dma_hardware = { @@ -692,16 +703,121 @@ static int q6apm_lpass_dai_dummy_startup(struct snd_pcm_substream *substream, return 0; } +static void q6apm_lpass_dai_dummy_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6apm_dai_priv_data *priv; + + if (dai->id >= APM_PORT_MAX) + return; + + priv = &dai_data->priv[dai->id]; + + if (priv->mclk_enabled) { + clk_disable_unprepare(priv->mclk); + priv->mclk_enabled = false; + } + + if (priv->bclk_enabled) { + clk_disable_unprepare(priv->bclk); + priv->bclk_enabled = false; + } + + if (priv->eclk_enabled) { + clk_disable_unprepare(priv->eclk); + priv->eclk_enabled = false; + } +} + +static int q6apm_lpass_dai_dummy_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, + int dir) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6apm_dai_priv_data *priv; + struct clk *sysclk; + bool *enabled; + int ret; + + if (dai->id >= APM_PORT_MAX) + return -EINVAL; + + priv = &dai_data->priv[dai->id]; + + switch (clk_id) { + case LPAIF_MI2S_MCLK: + sysclk = priv->mclk; + enabled = &priv->mclk_enabled; + break; + case LPAIF_MI2S_BCLK: + sysclk = priv->bclk; + enabled = &priv->bclk_enabled; + break; + case LPAIF_MI2S_ECLK: + sysclk = priv->eclk; + enabled = &priv->eclk_enabled; + break; + default: + return 0; + } + + if (!sysclk) + return 0; + + if (!freq) { + if (*enabled) { + clk_disable_unprepare(sysclk); + *enabled = false; + } + return 0; + } + + ret = clk_set_rate(sysclk, freq); + if (ret) + return ret; + + if (*enabled) + return 0; + + ret = clk_prepare_enable(sysclk); + if (ret) + return ret; + + *enabled = true; + + return 0; +} + +static int q6apm_lpass_dai_get_clk(struct device *dev, struct device_node *np, + const char *name, struct clk **clk) +{ + *clk = devm_get_clk_from_child(dev, np, name); + if (IS_ERR(*clk)) { + if (PTR_ERR(*clk) == -EPROBE_DEFER) + return dev_err_probe(dev, PTR_ERR(*clk), + "unable to get %s\n", name); + + *clk = NULL; + } + + return 0; +} + static const struct snd_soc_dai_ops q6dummy_ops = { .startup = q6apm_lpass_dai_dummy_startup, }; static const struct snd_soc_dai_ops q6i2sdummy_ops = { .startup = q6apm_lpass_dai_dummy_startup, + .shutdown = q6apm_lpass_dai_dummy_shutdown, + .set_sysclk = q6apm_lpass_dai_dummy_set_sysclk, }; static const struct snd_soc_dai_ops q6tdmdummy_ops = { .startup = q6apm_lpass_dai_dummy_startup, + .shutdown = q6apm_lpass_dai_dummy_shutdown, + .set_sysclk = q6apm_lpass_dai_dummy_set_sysclk, }; static const struct snd_soc_component_driver q6apm_lpass_dummy_dai_component = { @@ -718,12 +834,46 @@ static int q6apm_lpass_dummy_dai_dev_probe(struct platform_device *pdev) struct q6apm_lpass_dai_data *dai_data; struct snd_soc_dai_driver *dais; struct device *dev = &pdev->dev; + struct device_node *np; int num_dais; + int ret; dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); if (!dai_data) return -ENOMEM; + for_each_available_child_of_node(dev->of_node, np) { + struct q6apm_dai_priv_data *priv; + u32 id; + + ret = of_property_read_u32(np, "reg", &id); + if (ret || id >= APM_PORT_MAX) + continue; + + switch (id) { + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + break; + default: + continue; + } + + priv = &dai_data->priv[id]; + + ret = q6apm_lpass_dai_get_clk(dev, np, "mclk", &priv->mclk); + if (ret) + goto err_put_np; + + ret = q6apm_lpass_dai_get_clk(dev, np, "bclk", &priv->bclk); + if (ret) + goto err_put_np; + + ret = q6apm_lpass_dai_get_clk(dev, np, "eclk", &priv->eclk); + if (ret) + goto err_put_np; + } + dev_set_drvdata(dev, dai_data); memset(&cfg, 0, sizeof(cfg)); @@ -739,6 +889,10 @@ static int q6apm_lpass_dummy_dai_dev_probe(struct platform_device *pdev) dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); return devm_snd_soc_register_component(dev, q6apm_lpass_component, dais, num_dais); + +err_put_np: + of_node_put(np); + return ret; } #ifdef CONFIG_OF