diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index 2c59501c33c511..6875c1515512bd 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -29,6 +29,9 @@ properties: - qcom,sm8650-sndcard - qcom,sm8750-sndcard - const: qcom,sm8450-sndcard + - items: + - const: microsoft,denali-sndcard + - const: qcom,x1e80100-sndcard - enum: - dell,xps13-9345-sndcard - fairphone,fp4-sndcard diff --git a/Documentation/devicetree/bindings/sound/qcom,wsa8840.yaml b/Documentation/devicetree/bindings/sound/qcom,wsa8840.yaml index 866c5e780fb0aa..9744cea5ee42b8 100644 --- a/Documentation/devicetree/bindings/sound/qcom,wsa8840.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,wsa8840.yaml @@ -40,8 +40,39 @@ properties: minItems: 6 maxItems: 6 + qcom,enable-cps: + type: boolean + description: Enable the dedicated CPS SoundWire source DAI on data port 6. + + qcom,cps-offset1: + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 255 + description: | + Slave-only SoundWire DP6 OffsetCtrl1 override for CPS. This does not + change the physical master-port schedule selected by qcom,port-mapping. + + qcom,visense-channel-mask: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 255 + description: | + Native WSA data-port-5 channel-enable mask for the VISENSE protection + sidechain. If absent, the codec default is used. + + qcom,speaker-load-ohms: + enum: [4, 8] + description: | + Nominal speaker load impedance in ohms. A 4-ohm load selects the + matching PA gain and PBR thresholds when the amplifier reports a 2S + VPHX supply. If absent, the existing 8-ohm defaults are retained. + '#sound-dai-cells': - const: 0 + enum: [0, 1] + description: | + Zero selects the legacy single speaker-playback DAI. With one cell, + DAI 0 is speaker playback, DAI 1 is the render-coupled VISENSE + protection-feedback sidechain, and DAI 2 is the dedicated CPS feedback + sidechain when qcom,enable-cps is present. vdd-1p8-supply: true vdd-io-supply: true @@ -75,7 +106,7 @@ examples: pinctrl-names = "default"; pinctrl-0 = <&spkr_2_sd_n_active>; powerdown-gpios = <&lpass_tlmm 18 GPIO_ACTIVE_LOW>; - #sound-dai-cells = <0>; + #sound-dai-cells = <1>; sound-name-prefix = "SpkrRight"; vdd-1p8-supply = <&vreg_l15b_1p8>; vdd-io-supply = <&vreg_l3g_1p2>; diff --git a/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi b/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi index 0886eb11095d60..8452469efae27d 100644 --- a/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi +++ b/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi @@ -307,7 +307,8 @@ }; sound { - compatible = "qcom,x1e80100-sndcard"; + compatible = "microsoft,denali-sndcard", + "qcom,x1e80100-sndcard"; model = "X1E80100-Microsoft-Surface-Pro-11"; audio-routing = "SpkrLeft IN", "WSA WSA_SPK1 OUT", "SpkrRight IN", "WSA WSA_SPK2 OUT", @@ -318,7 +319,7 @@ link-name = "WSA Playback"; codec { - sound-dai = <&left_spkr>, <&right_spkr>, + sound-dai = <&left_spkr 0>, <&right_spkr 0>, <&swr0 0>, <&lpass_wsamacro 0>; }; @@ -331,6 +332,51 @@ }; }; + wsa-vi-dai-link { + link-name = "WSA VI Protection"; + + /* + * One two-channel Windows-equivalent feedback transport: + * amplifier VISENSE ports 10/11 -> WSA macro VI -> + * SoundWire DIN0 -> AudioReach WSA TX0. + */ + codec { + sound-dai = <&left_spkr 1>, <&right_spkr 1>, + <&swr0 9>, <&lpass_wsamacro 2>; + }; + + cpu { + sound-dai = <&q6apmbedai WSA_CODEC_DMA_TX_0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + wsa-cps-dai-link { + link-name = "WSA CPS Protection"; + + /* + * Dedicated CPS sidechain: both WSA8845 DP6 sources share + * physical SoundWire master port 13. swr0 DAI 10 is only + * the independent ASoC/SoundWire stream handle; qcom,port-mapping + * below remains authoritative for the physical master ports. + */ + codec { + sound-dai = <&left_spkr 2>, <&right_spkr 2>, + <&swr0 10>, <&lpass_wsamacro 4>; + }; + + cpu { + sound-dai = <&q6apmbedai WSA_CODEC_DMA_TX_1>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + va-dai-link { link-name = "VA Capture"; @@ -1075,11 +1121,15 @@ compatible = "sdw20217020400"; reg = <0 0>; reset-gpios = <&lpass_tlmm 12 GPIO_ACTIVE_LOW>; - #sound-dai-cells = <0>; + #sound-dai-cells = <1>; sound-name-prefix = "SpkrLeft"; vdd-1p8-supply = <&vreg_l15b_1p8>; vdd-io-supply = <&vreg_l12b_1p2>; qcom,port-mapping = <1 2 3 7 10 13>; + qcom,speaker-load-ohms = <4>; + qcom,visense-channel-mask = <3>; + qcom,enable-cps; + qcom,cps-offset1 = <0>; }; /* WSA8845, Right Speaker */ @@ -1087,11 +1137,15 @@ compatible = "sdw20217020400"; reg = <0 1>; reset-gpios = <&lpass_tlmm 12 GPIO_ACTIVE_LOW>; - #sound-dai-cells = <0>; + #sound-dai-cells = <1>; sound-name-prefix = "SpkrRight"; vdd-1p8-supply = <&vreg_l15b_1p8>; vdd-io-supply = <&vreg_l12b_1p2>; qcom,port-mapping = <4 5 6 7 11 13>; + qcom,speaker-load-ohms = <4>; + qcom,visense-channel-mask = <3>; + qcom,enable-cps; + qcom,cps-offset1 = <25>; }; }; diff --git a/drivers/soc/qcom/apr.c b/drivers/soc/qcom/apr.c index ea7f83916d8d8d..f348be37cc2c80 100644 --- a/drivers/soc/qcom/apr.c +++ b/drivers/soc/qcom/apr.c @@ -74,15 +74,35 @@ int apr_send_pkt(struct apr_device *adev, struct apr_pkt *pkt) } EXPORT_SYMBOL_GPL(apr_send_pkt); -void gpr_free_port(gpr_port_t *port) +static void pkt_router_svc_put(struct pkt_router_svc *svc) +{ + if (refcount_dec_and_test(&svc->callback_refs)) + complete(&svc->callbacks_drained); +} + +static void pkt_router_svc_remove(struct pkt_router_svc *svc) { - struct packet_router *gpr = port->pr; + struct packet_router *pr = svc->pr; unsigned long flags; - spin_lock_irqsave(&gpr->svcs_lock, flags); - idr_remove(&gpr->svcs_idr, port->id); - spin_unlock_irqrestore(&gpr->svcs_lock, flags); + spin_lock_irqsave(&pr->svcs_lock, flags); + idr_remove(&pr->svcs_idr, svc->id); + spin_unlock_irqrestore(&pr->svcs_lock, flags); + + if (!refcount_dec_and_test(&svc->callback_refs)) + wait_for_completion(&svc->callbacks_drained); +} +/** + * gpr_free_port() - unregister a dynamic GPR port after callbacks drain + * @port: dynamic port returned by gpr_alloc_port() + * + * Context: process context, and never from @port's own callback. + */ +void gpr_free_port(gpr_port_t *port) +{ + might_sleep(); + pkt_router_svc_remove(port); kfree(port); } EXPORT_SYMBOL_GPL(gpr_free_port); @@ -105,6 +125,8 @@ gpr_port_t *gpr_alloc_port(struct apr_device *gdev, struct device *dev, svc->priv = priv; svc->dev = dev; spin_lock_init(&svc->lock); + refcount_set(&svc->callback_refs, 1); + init_completion(&svc->callbacks_drained); spin_lock(&pr->svcs_lock); id = idr_alloc_cyclic(&pr->svcs_idr, svc, GPR_DYNAMIC_PORT_START, @@ -191,7 +213,7 @@ static int apr_do_rx_callback(struct packet_router *apr, struct apr_rx_buf *abuf { uint16_t hdr_size, msg_type, ver, svc_id; struct pkt_router_svc *svc; - struct apr_device *adev; + struct apr_device *adev = NULL; struct apr_driver *adrv = NULL; struct apr_resp_pkt resp; struct apr_hdr *hdr; @@ -232,6 +254,8 @@ static int apr_do_rx_callback(struct packet_router *apr, struct apr_rx_buf *abuf svc_id = hdr->dest_svc; spin_lock_irqsave(&apr->svcs_lock, flags); svc = idr_find(&apr->svcs_idr, svc_id); + if (svc && !refcount_inc_not_zero(&svc->callback_refs)) + svc = NULL; if (svc && svc->dev->driver) { adev = svc_to_apr_device(svc); adrv = to_apr_driver(adev->dev.driver); @@ -239,6 +263,8 @@ static int apr_do_rx_callback(struct packet_router *apr, struct apr_rx_buf *abuf spin_unlock_irqrestore(&apr->svcs_lock, flags); if (!adrv || !adev) { + if (svc) + pkt_router_svc_put(svc); dev_err(apr->dev, "APR: service is not registered (%d)\n", svc_id); return -EINVAL; @@ -255,6 +281,7 @@ static int apr_do_rx_callback(struct packet_router *apr, struct apr_rx_buf *abuf resp.payload = buf + hdr_size; adrv->callback(adev, &resp); + pkt_router_svc_put(svc); return 0; } @@ -298,6 +325,8 @@ static int gpr_do_rx_callback(struct packet_router *gpr, struct apr_rx_buf *abuf spin_lock_irqsave(&gpr->svcs_lock, flags); svc = idr_find(&gpr->svcs_idr, hdr->dest_port); + if (svc && !refcount_inc_not_zero(&svc->callback_refs)) + svc = NULL; spin_unlock_irqrestore(&gpr->svcs_lock, flags); if (!svc) { @@ -308,6 +337,7 @@ static int gpr_do_rx_callback(struct packet_router *gpr, struct apr_rx_buf *abuf if (svc->callback) svc->callback(&resp, svc->priv, 0); + pkt_router_svc_put(svc); return 0; } @@ -378,13 +408,10 @@ static void apr_device_remove(struct device *dev) { struct apr_device *adev = to_apr_device(dev); struct apr_driver *adrv = to_apr_driver(dev->driver); - struct packet_router *apr = dev_get_drvdata(adev->dev.parent); + pkt_router_svc_remove(&adev->svc); if (adrv->remove) adrv->remove(adev); - spin_lock(&apr->svcs_lock); - idr_remove(&apr->svcs_idr, adev->svc.id); - spin_unlock(&apr->svcs_lock); } static int apr_uevent(const struct device *dev, struct kobj_uevent_env *env) @@ -428,6 +455,8 @@ static int apr_add_device(struct device *dev, struct device_node *np, svc->priv = adev; svc->dev = dev; spin_lock_init(&svc->lock); + refcount_set(&svc->callback_refs, 1); + init_completion(&svc->callbacks_drained); adev->domain_id = domain_id; @@ -458,7 +487,7 @@ static int apr_add_device(struct device *dev, struct device_node *np, spin_unlock(&apr->svcs_lock); if (ret < 0) { dev_err(dev, "idr_alloc failed: %d\n", ret); - goto out; + goto free_adev; } /* Protection domain is optional, it does not exist on older platforms */ @@ -466,7 +495,7 @@ static int apr_add_device(struct device *dev, struct device_node *np, 1, &adev->service_path); if (ret < 0 && ret != -EINVAL) { dev_err(dev, "Failed to read second value of qcom,protection-domain\n"); - goto out; + goto remove_svc; } dev_info(dev, "Adding APR/GPR dev: %s\n", dev_name(&adev->dev)); @@ -474,10 +503,16 @@ static int apr_add_device(struct device *dev, struct device_node *np, ret = device_register(&adev->dev); if (ret) { dev_err(dev, "device_register failed: %d\n", ret); + pkt_router_svc_remove(svc); put_device(&adev->dev); } -out: + return ret; + +remove_svc: + pkt_router_svc_remove(svc); +free_adev: + kfree(adev); return ret; } diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 8115c64dd48e2f..bc3f862878a4b2 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -117,6 +117,8 @@ int sdw_find_col_index(int col); * @ch_mask: Channel mask * @transport_params: Transport parameters * @port_params: Port parameters + * @transport_params_override_mask: Bitmask of per-slave transport overrides + * @transport_params_override: Per-slave transport override values * @port_node: List node for Master or Slave port_list * @lane: Which lane is used * @@ -128,6 +130,8 @@ struct sdw_port_runtime { int ch_mask; struct sdw_transport_params transport_params; struct sdw_port_params port_params; + unsigned int transport_params_override_mask; + struct sdw_transport_params transport_params_override; struct list_head port_node; unsigned int lane; }; diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 3d8f5a81eff195..826affd615c0f0 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -1217,10 +1217,37 @@ static void qcom_swrm_stream_free_ports(struct qcom_swrm_ctrl *ctrl, mutex_unlock(&ctrl->port_lock); } +#define SWRM_DENALI_VI_DAI 9 +#define SWRM_DENALI_CPS_DAI 10 + +static bool qcom_swrm_is_denali_feedback_dai(struct qcom_swrm_ctrl *ctrl, int dai_id) +{ + return of_machine_is_compatible("microsoft,denali") && + ctrl->bus.controller_id == MASTER_ID_WSA && + (dai_id == SWRM_DENALI_VI_DAI || + dai_id == SWRM_DENALI_CPS_DAI); +} + +static bool qcom_swrm_merge_port(struct sdw_port_config *pconfig, int nports, + int port, struct sdw_port_runtime *p_rt) +{ + int i; + + for (i = 0; i < nports; i++) { + if (pconfig[i].num != port) + continue; + + pconfig[i].ch_mask |= p_rt->ch_mask; + return true; + } + + return false; +} + static int qcom_swrm_stream_alloc_ports(struct qcom_swrm_ctrl *ctrl, struct sdw_stream_runtime *stream, - struct snd_pcm_hw_params *params, - int direction) + struct snd_pcm_hw_params *params, + int direction, int dai_id) { struct sdw_stream_config sconfig; struct sdw_master_runtime *m_rt; @@ -1235,7 +1262,15 @@ static int qcom_swrm_stream_alloc_ports(struct qcom_swrm_ctrl *ctrl, if (!pconfig) return -ENOMEM; - if (direction == SNDRV_PCM_STREAM_CAPTURE) + /* + * Direction is a property of the controller data port. A speaker VI + * sidechain is intentionally exposed as a companion playback BE so + * DPCM starts it with render, while the physical DIN port still carries + * data from the SoundWire slaves into the master. + */ + if (qcom_swrm_is_denali_feedback_dai(ctrl, dai_id)) + sconfig.direction = SDW_DATA_DIR_TX; + else if (direction == SNDRV_PCM_STREAM_CAPTURE) sconfig.direction = SDW_DATA_DIR_TX; else sconfig.direction = SDW_DATA_DIR_RX; @@ -1275,6 +1310,12 @@ static int qcom_swrm_stream_alloc_ports(struct qcom_swrm_ctrl *ctrl, dev_err(ctrl->dev, "All ports busy\n"); return -EBUSY; } + + /* Both Denali amplifiers use one master CPS port. */ + if (qcom_swrm_is_denali_feedback_dai(ctrl, dai_id) && + qcom_swrm_merge_port(pconfig, nports, pn, p_rt)) + continue; + set_bit(pn, port_mask); pconfig[nports].num = pn; pconfig[nports].ch_mask = p_rt->ch_mask; @@ -1298,7 +1339,7 @@ static int qcom_swrm_hw_params(struct snd_pcm_substream *substream, int ret; ret = qcom_swrm_stream_alloc_ports(ctrl, sruntime, params, - substream->stream); + substream->stream, dai->id); if (ret) qcom_swrm_stream_free_ports(ctrl, sruntime); @@ -1404,6 +1445,20 @@ static int qcom_swrm_register_dais(struct qcom_swrm_ctrl *ctrl) stream->rates = SNDRV_PCM_RATE_48000; stream->formats = SNDRV_PCM_FMTBIT_S16_LE; + if (qcom_swrm_is_denali_feedback_dai(ctrl, i)) { + dais[i].playback = (struct snd_soc_pcm_stream) { + .channels_min = 1, + .channels_max = 1, + .rates = i == SWRM_DENALI_VI_DAI ? + SNDRV_PCM_RATE_8000 : SNDRV_PCM_RATE_24000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }; + dais[i].playback.stream_name = + devm_kasprintf(dev, GFP_KERNEL, "SoundWire Feedback%d", i); + if (!dais[i].playback.stream_name) + return -ENOMEM; + } + dais[i].ops = &qcom_swrm_pdm_dai_ops; dais[i].id = i; } diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 5d20e95a1e23eb..b00d3354f40c40 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -62,6 +62,65 @@ int sdw_find_row_index(int row) } EXPORT_SYMBOL(sdw_find_row_index); +static int sdw_program_simple_ext(struct sdw_bus *bus, struct sdw_slave *slave, + struct sdw_transport_params *t_params, + u32 registers) +{ + u32 blockctrl3, samplectrl2, hctrl, offsetctrl2; + u16 wbuf; + int ret; + + if (bus->params.next_bank) { + blockctrl3 = SDW_DPN_BLOCKCTRL3_B1(t_params->port_num); + samplectrl2 = SDW_DPN_SAMPLECTRL2_B1(t_params->port_num); + hctrl = SDW_DPN_HCTRL_B1(t_params->port_num); + offsetctrl2 = SDW_DPN_OFFSETCTRL2_B1(t_params->port_num); + } else { + blockctrl3 = SDW_DPN_BLOCKCTRL3_B0(t_params->port_num); + samplectrl2 = SDW_DPN_SAMPLECTRL2_B0(t_params->port_num); + hctrl = SDW_DPN_HCTRL_B0(t_params->port_num); + offsetctrl2 = SDW_DPN_OFFSETCTRL2_B0(t_params->port_num); + } + + if (registers & SDW_DPN_SIMPLE_TRANSPORT_BLOCKCTRL3) { + ret = sdw_write_no_pm(slave, blockctrl3, t_params->blk_pkg_mode); + if (ret < 0) + return ret; + } + + if (registers & SDW_DPN_SIMPLE_TRANSPORT_SAMPLECTRL2) { + wbuf = FIELD_GET(SDW_DPN_SAMPLECTRL_HIGH, + t_params->sample_interval - 1); + ret = sdw_write_no_pm(slave, samplectrl2, wbuf); + if (ret < 0) + return ret; + } + + if (registers & SDW_DPN_SIMPLE_TRANSPORT_HCTRL) { + wbuf = FIELD_PREP(SDW_DPN_HCTRL_HSTART, t_params->hstart); + wbuf |= FIELD_PREP(SDW_DPN_HCTRL_HSTOP, t_params->hstop); + ret = sdw_write_no_pm(slave, hctrl, wbuf); + if (ret < 0) + return ret; + } + + if (registers & SDW_DPN_SIMPLE_TRANSPORT_OFFSETCTRL2) { + ret = sdw_write_no_pm(slave, offsetctrl2, t_params->offset2); + if (ret < 0) + return ret; + } + + return 0; +} + +static void sdw_apply_slave_transport_overrides(struct sdw_port_runtime *p_rt) +{ + if (p_rt->transport_params_override_mask & + SDW_PORT_CONFIG_OVERRIDE_OFFSET1) + p_rt->transport_params.offset1 = + p_rt->transport_params_override.offset1; +} + static int _sdw_program_slave_port_params(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_transport_params *t_params, @@ -137,6 +196,7 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus, struct sdw_slave_prop *slave_prop = &s_rt->slave->prop; u32 addr1, addr2, addr3, addr4, addr5, addr6; enum sdw_dpn_type port_type; + u32 simple_transport_registers = 0; bool read_only_wordlength; int ret; u8 wbuf; @@ -144,6 +204,12 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus, if (s_rt->slave->is_mockup_device) return 0; + /* Bus compute_params() establishes the common master schedule. Apply + * explicitly requested slave-only deltas immediately before the slave is + * programmed so master transport state remains shared. + */ + sdw_apply_slave_transport_overrides(p_rt); + if (t_params->port_num) { struct sdw_dpn_prop *dpn_prop; @@ -154,6 +220,7 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus, read_only_wordlength = dpn_prop->read_only_wordlength; port_type = dpn_prop->type; + simple_transport_registers = dpn_prop->simple_transport_registers; } else { read_only_wordlength = false; port_type = SDW_DPN_FULL; @@ -239,7 +306,14 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus, } } - if (port_type != SDW_DPN_SIMPLE) { + if (port_type == SDW_DPN_SIMPLE && simple_transport_registers) { + ret = sdw_program_simple_ext(bus, s_rt->slave, t_params, + simple_transport_registers); + if (ret < 0) + dev_err(&s_rt->slave->dev, + "Extended SIMPLE transport reg write failed for port: %d\n", + t_params->port_num); + } else if (port_type != SDW_DPN_SIMPLE) { ret = _sdw_program_slave_port_params(bus, s_rt->slave, t_params, port_type); if (ret < 0) @@ -983,6 +1057,10 @@ static int sdw_port_config(struct sdw_port_runtime *p_rt, { p_rt->ch_mask = port_config[port_index].ch_mask; p_rt->num = port_config[port_index].num; + p_rt->transport_params_override_mask = + port_config[port_index].transport_params_override_mask; + p_rt->transport_params_override = + port_config[port_index].transport_params_override; /* * TODO: Check port capabilities for requested configuration diff --git a/include/linux/soc/qcom/apr.h b/include/linux/soc/qcom/apr.h index 909e84f84e0cfa..15c18100494294 100644 --- a/include/linux/soc/qcom/apr.h +++ b/include/linux/soc/qcom/apr.h @@ -3,6 +3,8 @@ #ifndef __QCOM_APR_H_ #define __QCOM_APR_H_ +#include +#include #include #include #include @@ -129,6 +131,8 @@ struct pkt_router_svc { gpr_port_cb callback; struct packet_router *pr; spinlock_t lock; + refcount_t callback_refs; + struct completion callbacks_drained; int id; void *priv; }; diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 79dd44922fbcae..b3c74c876b83fa 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -305,7 +305,14 @@ struct sdw_dp0_prop { * @read_only_wordlength: Read Only wordlength field in DPN_BlockCtrl1 register * @simple_ch_prep_sm: If the port supports simplified channel prepare state * machine + * @simple_transport_registers: Optional banked transport registers implemented + * by a SIMPLE port in addition to the mandatory SIMPLE register set. */ +#define SDW_DPN_SIMPLE_TRANSPORT_BLOCKCTRL3 BIT(0) +#define SDW_DPN_SIMPLE_TRANSPORT_SAMPLECTRL2 BIT(1) +#define SDW_DPN_SIMPLE_TRANSPORT_HCTRL BIT(2) +#define SDW_DPN_SIMPLE_TRANSPORT_OFFSETCTRL2 BIT(3) + struct sdw_dpn_prop { u32 num; u32 max_word; @@ -330,6 +337,7 @@ struct sdw_dpn_prop { bool block_pack_mode; bool read_only_wordlength; bool simple_ch_prep_sm; + u32 simple_transport_registers; }; /** @@ -890,10 +898,18 @@ void sdw_show_ping_status(struct sdw_bus *bus, bool sync_delay); * * @num: Port number * @ch_mask: channels mask for port + * @transport_params_override_mask: Bitmask selecting slave transport fields + * which override values computed by the bus master. Master port parameters are + * never affected by these per-slave overrides. + * @transport_params_override: Values for the selected transport fields. */ +#define SDW_PORT_CONFIG_OVERRIDE_OFFSET1 BIT(0) + struct sdw_port_config { unsigned int num; unsigned int ch_mask; + unsigned int transport_params_override_mask; + struct sdw_transport_params transport_params_override; }; /** diff --git a/include/uapi/sound/snd_ar_tokens.h b/include/uapi/sound/snd_ar_tokens.h index 6b8102eaa121f1..748733c77c4f9c 100644 --- a/include/uapi/sound/snd_ar_tokens.h +++ b/include/uapi/sound/snd_ar_tokens.h @@ -96,6 +96,10 @@ enum ar_event_types { * APM_PROC_DOMAIN_ID_SDSP = 4, * APM_PROC_DOMAIN_ID_CDSP = 5 * + * %AR_TKN_U32_CONTAINER_PARENT_ID: Parent container instance ID. + * + * %AR_TKN_U32_CONTAINER_HEAP_ID: Container heap ID. + * * %AR_TKN_U32_MODULE_ID: Module ID * * %AR_TKN_U32_MODULE_INSTANCE_ID: Module Instance ID. @@ -112,6 +116,14 @@ enum ar_event_types { * * %AR_TKN_U32_MODULE_DST_IN_PORT_ID: Destination module input port ID * + * %AR_TKN_U32_MODULE_SPEAKER_PROTECTION_BYPASS: + * Leave speaker-protection modules + * in their default disabled state. + * + * %AR_TKN_U32_MODULE_INTEGRATED_BACKEND_ID: + * Backend DAI ID represented inside + * the same graph as the frontend. + * * %AR_TKN_U32_MODULE_HW_IF_IDX: Interface index types for I2S/LPAIF * * %AR_TKN_U32_MODULE_HW_IF_TYPE: Interface type @@ -186,6 +198,8 @@ enum ar_event_types { #define AR_TKN_U32_CONTAINER_STACK_SIZE 102 #define AR_TKN_U32_CONTAINER_GRAPH_POS 103 #define AR_TKN_U32_CONTAINER_PROC_DOMAIN 104 +#define AR_TKN_U32_CONTAINER_PARENT_ID 105 +#define AR_TKN_U32_CONTAINER_HEAP_ID 106 /* Module Tokens */ #define AR_TKN_U32_MODULE_ID 200 @@ -239,8 +253,24 @@ enum ar_event_types { #define AR_TKN_U32_MODULE_LOG_CODE 259 #define AR_TKN_U32_MODULE_LOG_TAP_POINT_ID 260 #define AR_TKN_U32_MODULE_LOG_MODE 261 +#define AR_TKN_U32_MODULE_SPEAKER_PROTECTION_BYPASS 262 +#define AR_TKN_U32_MODULE_INTEGRATED_BACKEND_ID 263 #define SND_SOC_AR_TPLG_MODULE_CFG_TYPE 0x01001006 +#define SND_SOC_AR_TPLG_CTRL_LINK_CFG_TYPE 0x08001061 + +/* Ordered private-data classes emitted by the protected FullIO topology. */ +#define SND_SOC_AR_TPLG_GRAPH_CAL_CFG_TYPE 0x53503101 +#define SND_SOC_AR_TPLG_RENDER_EP_CFG_TYPE 0x53503102 +#define SND_SOC_AR_TPLG_SP_TAG_CFG_TYPE 0x53503103 +#define SND_SOC_AR_TPLG_SPVI_TAG_CFG_TYPE 0x53503104 +#define SND_SOC_AR_TPLG_VI_EP_CFG_TYPE 0x53503105 +#define SND_SOC_AR_TPLG_PROTECTION_DYNAMIC_CFG_TYPE 0x53503106 +#define SND_SOC_AR_TPLG_VOLUME_GAIN_CFG_TYPE 0x53503107 +#define SND_SOC_AR_TPLG_VOLUME_FILTER_CFG_TYPE 0x53503108 +#define SND_SOC_AR_TPLG_VOLUME_MUTE_CFG_TYPE 0x53503109 +#define SND_SOC_AR_TPLG_CHANNEL_MIXER_CFG_TYPE 0x5350310a + struct audioreach_module_priv_data { __le32 size; /* size in bytes of the array, including all elements */ __le32 type; /* SND_SOC_AR_TPLG_MODULE_CFG_TYPE */ diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c index cb457f3edf7d59..4d0430a2917e53 100644 --- a/sound/soc/codecs/lpass-wsa-macro.c +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +77,7 @@ #define CDC_WSA_TX2_SPKR_PROT_PATH_CFG0 (0x0288) #define CDC_WSA_TX3_SPKR_PROT_PATH_CTL (0x02A4) #define CDC_WSA_TX3_SPKR_PROT_PATH_CFG0 (0x02A8) +#define WSA_MACRO_PROTECTION_PA_COUNT 2 #define CDC_WSA_INTR_CTRL_CFG (0x0340) #define CDC_WSA_INTR_CTRL_CLR_COMMIT (0x0344) #define CDC_WSA_INTR_CTRL_PIN1_MASK0 (0x0360) @@ -374,6 +377,7 @@ enum { WSA_MACRO_AIF_MIX1_PB, WSA_MACRO_AIF_VI, WSA_MACRO_AIF_ECHO, + WSA_MACRO_AIF_CPS, WSA_MACRO_MAX_DAIS, }; @@ -414,6 +418,11 @@ struct wsa_macro { u32 pcm_rate_vi; int is_softclip_on[WSA_MACRO_SOFTCLIP_MAX]; int softclip_clk_users[WSA_MACRO_SOFTCLIP_MAX]; + bool protected_feedback; + /* Protects protection_pa_users and protection_clocks_enabled. */ + struct mutex protection_lock; + unsigned int protection_pa_users; + bool protection_clocks_enabled; struct regmap *regmap; struct clk *mclk; struct clk *npl; @@ -424,6 +433,8 @@ struct wsa_macro { }; #define to_wsa_macro(_hw) container_of(_hw, struct wsa_macro, hw) +static const struct snd_soc_component_driver wsa_macro_component_drv; + static const struct wsa_reg_layout wsa_codec_v2_1 = { .rx_intx_1_mix_inp0_sel_mask = GENMASK(2, 0), .rx_intx_1_mix_inp1_sel_mask = GENMASK(5, 3), @@ -1278,6 +1289,15 @@ static int wsa_macro_hw_params(struct snd_pcm_substream *substream, struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); int ret; + if (wsa->protected_feedback && dai->id == WSA_MACRO_AIF_VI && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + wsa->pcm_rate_vi = params_rate(params); + return 0; + } + + if (wsa->protected_feedback && dai->id == WSA_MACRO_AIF_CPS) + return 0; + switch (substream->stream) { case SNDRV_PCM_STREAM_PLAYBACK: ret = wsa_macro_set_interpolator_rate(dai, params_rate(params)); @@ -1312,6 +1332,10 @@ static int wsa_macro_get_channel_map(const struct snd_soc_dai *dai, *tx_slot = wsa->active_ch_mask[dai->id]; *tx_num = wsa->active_ch_cnt[dai->id]; break; + case WSA_MACRO_AIF_CPS: + *tx_num = 2; + *tx_slot = GENMASK(1, 0); + break; case WSA_MACRO_AIF1_PB: case WSA_MACRO_AIF_MIX1_PB: for_each_set_bit(temp, &wsa->active_ch_mask[dai->id], @@ -1494,6 +1518,63 @@ static void wsa_macro_enable_disable_vi_sense(struct snd_soc_component *componen } } +static void wsa_macro_set_protection_clocks(struct snd_soc_component *component, + bool enable) +{ + wsa_macro_enable_disable_vi_sense(component, enable, + CDC_WSA_TX0_SPKR_PROT_PATH_CTL, + CDC_WSA_TX1_SPKR_PROT_PATH_CTL, 0); + wsa_macro_enable_disable_vi_sense(component, enable, + CDC_WSA_TX2_SPKR_PROT_PATH_CTL, + CDC_WSA_TX3_SPKR_PROT_PATH_CTL, 0); +} + +bool wsa_macro_protection_pa_event(struct snd_soc_component *source, + bool enable) +{ + struct snd_soc_component *component; + struct wsa_macro *wsa; + + if (!source || !source->card) + return false; + + for_each_card_components(source->card, component) { + if (component->driver != &wsa_macro_component_drv) + continue; + + wsa = snd_soc_component_get_drvdata(component); + if (!wsa->protected_feedback) + continue; + + guard(mutex)(&wsa->protection_lock); + + if (enable) { + if (wsa->protection_pa_users < WSA_MACRO_PROTECTION_PA_COUNT) + wsa->protection_pa_users++; + + if (wsa->protection_pa_users == WSA_MACRO_PROTECTION_PA_COUNT && + !wsa->protection_clocks_enabled) { + wsa_macro_set_protection_clocks(component, true); + wsa->protection_clocks_enabled = true; + } + } else { + if (wsa->protection_pa_users == WSA_MACRO_PROTECTION_PA_COUNT && + wsa->protection_clocks_enabled) { + wsa_macro_set_protection_clocks(component, false); + wsa->protection_clocks_enabled = false; + } + + if (wsa->protection_pa_users) + wsa->protection_pa_users--; + } + + return true; + } + + return false; +} +EXPORT_SYMBOL_GPL(wsa_macro_protection_pa_event); + static void wsa_macro_enable_disable_vi_feedback(struct snd_soc_component *component, bool enable, u32 rate) { @@ -2388,6 +2469,25 @@ static const struct snd_soc_dapm_widget wsa_macro_dapm_widgets[] = { SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), }; +static const struct snd_soc_dapm_widget wsa_macro_denali_widgets[] = { + SND_SOC_DAPM_AIF_IN_E("WSA AIF_VI Protection", + "WSA_AIF_VI Protection", 0, + SND_SOC_NOPM, WSA_MACRO_AIF_VI, 0, + wsa_macro_enable_vi_feedback, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN("WSA AIF_CPS Protection", + "WSA_AIF_CPS Protection", 0, + SND_SOC_NOPM, WSA_MACRO_AIF_CPS, 0), + SND_SOC_DAPM_INPUT("CPSINPUT_WSA"), +}; + +static const struct snd_soc_dapm_route wsa_macro_denali_routes[] = { + { "WSA AIF_VI Protection", NULL, "WSA_AIF_VI Mixer" }, + { "WSA AIF_VI Protection", NULL, "WSA_MCLK" }, + { "WSA AIF_CPS Protection", NULL, "CPSINPUT_WSA" }, + { "WSA AIF_CPS Protection", NULL, "WSA_MCLK" }, +}; + static const struct snd_soc_dapm_widget wsa_macro_dapm_widgets_v2_1[] = { SND_SOC_DAPM_MUX("WSA_RX0 INP0", SND_SOC_NOPM, 0, 0, &rx0_prim_inp0_mux_v2_1), SND_SOC_DAPM_MUX("WSA_RX0 INP1", SND_SOC_NOPM, 0, 0, &rx0_prim_inp1_mux_v2_1), @@ -2562,6 +2662,7 @@ static int wsa_macro_component_probe(struct snd_soc_component *comp) struct wsa_macro *wsa = snd_soc_component_get_drvdata(comp); const struct snd_soc_dapm_widget *widgets; unsigned int num_widgets; + int ret; snd_soc_component_init_regmap(comp, wsa->regmap); @@ -2599,7 +2700,17 @@ static int wsa_macro_component_probe(struct snd_soc_component *comp) return -EINVAL; } - return snd_soc_dapm_new_controls(dapm, widgets, num_widgets); + ret = snd_soc_dapm_new_controls(dapm, widgets, num_widgets); + if (ret || !wsa->protected_feedback) + return ret; + + ret = snd_soc_dapm_new_controls(dapm, wsa_macro_denali_widgets, + ARRAY_SIZE(wsa_macro_denali_widgets)); + if (ret) + return ret; + + return snd_soc_dapm_add_routes(dapm, wsa_macro_denali_routes, + ARRAY_SIZE(wsa_macro_denali_routes)); } static int swclk_gate_enable(struct clk_hw *hw) @@ -2679,9 +2790,11 @@ static const struct snd_soc_component_driver wsa_macro_component_drv = { static int wsa_macro_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct snd_soc_dai_driver *dais = wsa_macro_dai; struct wsa_macro *wsa; kernel_ulong_t flags; void __iomem *base; + int num_dais = ARRAY_SIZE(wsa_macro_dai); int ret, def_count; flags = (kernel_ulong_t)device_get_match_data(dev); @@ -2690,6 +2803,8 @@ static int wsa_macro_probe(struct platform_device *pdev) if (!wsa) return -ENOMEM; + mutex_init(&wsa->protection_lock); + wsa->macro = devm_clk_get_optional(dev, "macro"); if (IS_ERR(wsa->macro)) return dev_err_probe(dev, PTR_ERR(wsa->macro), "unable to get macro clock\n"); @@ -2771,6 +2886,39 @@ static int wsa_macro_probe(struct platform_device *pdev) dev_set_drvdata(dev, wsa); wsa->dev = dev; + wsa->protected_feedback = + of_machine_is_compatible("microsoft,denali"); + if (wsa->protected_feedback) { + dais = devm_kcalloc(dev, num_dais + 1, sizeof(*dais), GFP_KERNEL); + if (!dais) + return -ENOMEM; + + memcpy(dais, wsa_macro_dai, sizeof(wsa_macro_dai)); + dais[WSA_MACRO_AIF_VI].playback = (struct snd_soc_pcm_stream) { + .stream_name = "WSA_AIF_VI Protection", + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_max = 8000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 4, + }; + dais[num_dais] = (struct snd_soc_dai_driver) { + .name = "wsa_macro_cps", + .id = WSA_MACRO_AIF_CPS, + .playback = { + .stream_name = "WSA_AIF_CPS Protection", + .rates = SNDRV_PCM_RATE_24000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_max = 24000, + .rate_min = 24000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &wsa_macro_dai_ops, + }; + num_dais++; + } /* set MCLK and NPL rates */ clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); @@ -2807,8 +2955,7 @@ static int wsa_macro_probe(struct platform_device *pdev) CDC_WSA_SWR_RST_EN_MASK, CDC_WSA_SWR_RST_DISABLE); ret = devm_snd_soc_register_component(dev, &wsa_macro_component_drv, - wsa_macro_dai, - ARRAY_SIZE(wsa_macro_dai)); + dais, num_dais); if (ret) goto err_rpm_put; diff --git a/sound/soc/codecs/lpass-wsa-macro.h b/sound/soc/codecs/lpass-wsa-macro.h index d3d62b3f6500c9..d88b92350c7d0d 100644 --- a/sound/soc/codecs/lpass-wsa-macro.h +++ b/sound/soc/codecs/lpass-wsa-macro.h @@ -3,6 +3,10 @@ #ifndef __LPASS_WSA_MACRO_H__ #define __LPASS_WSA_MACRO_H__ +#include + +struct snd_soc_component; + /* * Selects compander and smart boost settings * for a given speaker mode @@ -14,4 +18,15 @@ enum { int wsa_macro_set_spkr_mode(struct snd_soc_component *component, int mode); +#if IS_REACHABLE(CONFIG_SND_SOC_LPASS_WSA_MACRO) +bool wsa_macro_protection_pa_event(struct snd_soc_component *source, + bool enable); +#else +static inline bool +wsa_macro_protection_pa_event(struct snd_soc_component *source, bool enable) +{ + return false; +} +#endif + #endif /* __LPASS_WSA_MACRO_H__ */ diff --git a/sound/soc/codecs/wsa884x.c b/sound/soc/codecs/wsa884x.c index 6c6b497657d0c8..7241c1d6765090 100644 --- a/sound/soc/codecs/wsa884x.c +++ b/sound/soc/codecs/wsa884x.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,8 @@ #include #include +#include "lpass-wsa-macro.h" + #define WSA884X_BASE 0x3000 #define WSA884X_ANA_BG_TSADC_BASE (WSA884X_BASE + 0x0001) #define WSA884X_BG_CTRL (WSA884X_ANA_BG_TSADC_BASE + 0x00) @@ -155,6 +158,7 @@ #define WSA884X_BBM_CTL (WSA884X_ANA_SPK_TOP_BASE + 0x28) #define WSA884X_TOP_MISC1 (WSA884X_ANA_SPK_TOP_BASE + 0x29) #define WSA884X_DAC_VCM_CTRL_REG7 (WSA884X_ANA_SPK_TOP_BASE + 0x2a) +#define WSA884X_DAC_VCM_CTRL_REG7_FINAL_OVERRIDE_MASK 0x02 #define WSA884X_TOP_BIAS_REG5 (WSA884X_ANA_SPK_TOP_BASE + 0x2b) #define WSA884X_DRV_LF_MISC_CTL2 (WSA884X_ANA_SPK_TOP_BASE + 0x2c) #define WSA884X_SPK_TOP_SPARE_TUNE_2 (WSA884X_ANA_SPK_TOP_BASE + 0x2d) @@ -297,6 +301,7 @@ #define WSA884X_PA_FSM_TIMER1 (WSA884X_DIG_CTRL0_BASE + 0x34) #define WSA884X_PA_FSM_STA0 (WSA884X_DIG_CTRL0_BASE + 0x35) #define WSA884X_PA_FSM_STA1 (WSA884X_DIG_CTRL0_BASE + 0x36) +#define WSA884X_PA_ERROR_MASK GENMASK(4, 0) #define WSA884X_PA_FSM_ERR_CTL (WSA884X_DIG_CTRL0_BASE + 0x37) #define WSA884X_PA_FSM_ERR_COND0 (WSA884X_DIG_CTRL0_BASE + 0x38) #define WSA884X_PA_FSM_ERR_COND1 (WSA884X_DIG_CTRL0_BASE + 0x39) @@ -437,7 +442,9 @@ #define WSA884X_CLSH_CTL_0_INPUT_EN_SHIFT 1 #define WSA884X_CLSH_CTL_0_CLSH_EN_SHIFT 0 #define WSA884X_CLSH_CTL_1 (WSA884X_DIG_CTRL0_BASE + 0xd1) +#define WSA884X_CLSH_CTL_1_SLR_MAX_MASK GENMASK(7, 4) #define WSA884X_CLSH_V_HD_PA (WSA884X_DIG_CTRL0_BASE + 0xd2) +#define WSA884X_CLSH_V_HD_PA_MASK GENMASK(4, 0) #define WSA884X_CLSH_V_PA_MIN (WSA884X_DIG_CTRL0_BASE + 0xd3) #define WSA884X_CLSH_OVRD_VAL (WSA884X_DIG_CTRL0_BASE + 0xd4) #define WSA884X_CLSH_HARD_MAX (WSA884X_DIG_CTRL0_BASE + 0xd5) @@ -731,8 +738,11 @@ struct wsa884x_priv { struct regulator_bulk_data supplies[WSA884X_SUPPLIES_NUM]; struct sdw_slave *slave; struct sdw_stream_config sconfig; - struct sdw_stream_runtime *sruntime; + struct sdw_stream_runtime *sruntime[3]; struct sdw_port_config port_config[WSA884X_MAX_SWR_PORTS]; + u8 cps_offset1; + u8 visense_channel_mask; + bool protected_feedback; struct gpio_desc *sd_n; struct reset_control *sd_reset; bool port_prepared[WSA884X_MAX_SWR_PORTS]; @@ -747,6 +757,16 @@ struct wsa884x_priv { struct mutex sp_lock; unsigned int temperature; bool pa_on; + unsigned int supply_config; + unsigned int speaker_load_ohms; + bool protection_pa_reported; +}; + +enum wsa884x_supply_config { + WSA884X_SUPPLY_EXT_ABOVE_3S, + WSA884X_SUPPLY_1S, + WSA884X_SUPPLY_2S, + WSA884X_SUPPLY_3S, }; enum { @@ -872,6 +892,29 @@ static struct sdw_dpn_prop wsa884x_sink_dpn_prop[WSA884X_MAX_SWR_PORTS] = { } }; +static struct sdw_dpn_prop wsa884x_source_dpn_prop[] = { + { + .num = WSA884X_PORT_VISENSE + 1, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 1, + .simple_ch_prep_sm = true, + .read_only_wordlength = true, + }, + { + .num = WSA884X_PORT_CPS + 1, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 2, + .simple_ch_prep_sm = true, + .read_only_wordlength = false, + .simple_transport_registers = + SDW_DPN_SIMPLE_TRANSPORT_BLOCKCTRL3 | + SDW_DPN_SIMPLE_TRANSPORT_SAMPLECTRL2 | + SDW_DPN_SIMPLE_TRANSPORT_HCTRL, + }, +}; + static const struct sdw_port_config wsa884x_pconfig[WSA884X_MAX_SWR_PORTS] = { [WSA884X_PORT_DAC] = { .num = WSA884X_PORT_DAC + 1, @@ -1457,11 +1500,26 @@ static const struct reg_sequence wsa884x_reg_init[] = { { WSA884X_OTP_REG_40, FIELD_PREP_CONST(WSA884X_OTP_REG_40_ISENSE_RESCAL_MASK, 0x8) }, }; +static bool wsa884x_uses_2s_4ohm_profile(struct wsa884x_priv *wsa884x) +{ + return wsa884x->speaker_load_ohms == 4 && + wsa884x->supply_config == WSA884X_SUPPLY_2S; +} + static void wsa884x_set_gain_parameters(struct wsa884x_priv *wsa884x) { struct regmap *regmap = wsa884x->regmap; unsigned int min_gain, igain, vgain, comp_offset; + if (wsa884x_uses_2s_4ohm_profile(wsa884x) && + wsa884x->dev_mode == WSA884X_SPEAKER) { + comp_offset = COMP_OFFSET0; + min_gain = G_0_DB; + igain = ISENSE_6_DB; + vgain = VSENSE_M21_DB; + goto apply; + } + /* * Downstream sets gain parameters customized per boards per use-case. * Choose here some sane values matching knowon users, like QRD8550 @@ -1484,6 +1542,7 @@ static void wsa884x_set_gain_parameters(struct wsa884x_priv *wsa884x) vgain = VSENSE_M24_DB; } +apply: regmap_update_bits(regmap, WSA884X_ISENSE2, WSA884X_ISENSE2_ISENSE_GAIN_CTL_MASK, FIELD_PREP(WSA884X_ISENSE2_ISENSE_GAIN_CTL_MASK, igain)); @@ -1509,18 +1568,121 @@ static void wsa884x_set_gain_parameters(struct wsa884x_priv *wsa884x) } } +static void wsa884x_apply_2s_4ohm_profile(struct wsa884x_priv *wsa884x) +{ + static const struct reg_sequence pbr_thresholds[] = { + { WSA884X_CLSH_VTH1, WSA884X_VTH_TO_REG(808) }, + { WSA884X_CLSH_VTH2, WSA884X_VTH_TO_REG(839) }, + { WSA884X_CLSH_VTH3, WSA884X_VTH_TO_REG(894) }, + { WSA884X_CLSH_VTH4, WSA884X_VTH_TO_REG(925) }, + { WSA884X_CLSH_VTH5, WSA884X_VTH_TO_REG(973) }, + { WSA884X_CLSH_VTH6, WSA884X_VTH_TO_REG(996) }, + { WSA884X_CLSH_VTH7, WSA884X_VTH_TO_REG(1051) }, + { WSA884X_CLSH_VTH8, WSA884X_VTH_TO_REG(1114) }, + { WSA884X_CLSH_VTH9, WSA884X_VTH_TO_REG(1184) }, + { WSA884X_CLSH_VTH10, WSA884X_VTH_TO_REG(1255) }, + { WSA884X_CLSH_VTH11, WSA884X_VTH_TO_REG(1318) }, + { WSA884X_CLSH_VTH12, WSA884X_VTH_TO_REG(1467) }, + { WSA884X_CLSH_VTH13, WSA884X_VTH_TO_REG(1616) }, + { WSA884X_CLSH_VTH14, WSA884X_VTH_TO_REG(1788) }, + { WSA884X_CLSH_VTH15, WSA884X_VTH_TO_REG(2000) }, + }; + struct regmap *regmap = wsa884x->regmap; + + if (wsa884x->speaker_load_ohms != 4) + return; + + if (!wsa884x_uses_2s_4ohm_profile(wsa884x)) { + /* + * A SoundWire detach leaves the cached profile values behind. Put + * every fixed 2S override back on the upstream defaults when a + * subsequent status read does not confirm 2S. wsa884x_reg_init + * restores the PBR thresholds before this function is called. + */ + regmap_write(regmap, WSA884X_OCP_CTL, 0xc6); + regmap_update_bits(regmap, WSA884X_CLSH_CTL_1, + WSA884X_CLSH_CTL_1_SLR_MAX_MASK, + FIELD_PREP(WSA884X_CLSH_CTL_1_SLR_MAX_MASK, 0x8)); + regmap_update_bits(regmap, WSA884X_CLSH_V_HD_PA, + WSA884X_CLSH_V_HD_PA_MASK, + FIELD_PREP(WSA884X_CLSH_V_HD_PA_MASK, 0xc)); + regmap_write(regmap, WSA884X_DAC_VCM_CTRL_REG2, 0x00); + regmap_write(regmap, WSA884X_DAC_VCM_CTRL_REG3, 0x00); + regmap_write(regmap, WSA884X_DAC_VCM_CTRL_REG4, 0x00); + regmap_write(regmap, WSA884X_DAC_VCM_CTRL_REG5, 0x00); + regmap_write(regmap, WSA884X_DAC_VCM_CTRL_REG6, 0x00); + regmap_update_bits(regmap, WSA884X_DAC_VCM_CTRL_REG7, + WSA884X_DAC_VCM_CTRL_REG7_FINAL_OVERRIDE_MASK, 0); + regmap_write(regmap, WSA884X_UVLO_PROG, 0x99); + regmap_write(regmap, WSA884X_PA_FSM_TIMER0, 0x80); + regmap_write(regmap, WSA884X_UVLO_DEGLITCH_CTL, 0x05); + regmap_write(regmap, WSA884X_UVLO_PROG1, 0x70); + regmap_update_bits(regmap, WSA884X_TOP_CTRL1, + WSA884X_TOP_CTRL1_OCP_LOWVBAT_ITH_EN_MASK, + WSA884X_TOP_CTRL1_OCP_LOWVBAT_ITH_EN_MASK); + return; + } + + regmap_write(regmap, WSA884X_OCP_CTL, 0xf6); + regmap_multi_reg_write(regmap, pbr_thresholds, + ARRAY_SIZE(pbr_thresholds)); + regmap_update_bits(regmap, WSA884X_CLSH_CTL_1, + WSA884X_CLSH_CTL_1_SLR_MAX_MASK, + FIELD_PREP(WSA884X_CLSH_CTL_1_SLR_MAX_MASK, 0x2)); + regmap_update_bits(regmap, WSA884X_CLSH_V_HD_PA, + WSA884X_CLSH_V_HD_PA_MASK, + FIELD_PREP(WSA884X_CLSH_V_HD_PA_MASK, 0x13)); + regmap_write(regmap, WSA884X_DAC_VCM_CTRL_REG2, 0x06); + regmap_write(regmap, WSA884X_DAC_VCM_CTRL_REG3, 0x14); + regmap_write(regmap, WSA884X_DAC_VCM_CTRL_REG4, 0x19); + regmap_write(regmap, WSA884X_DAC_VCM_CTRL_REG5, 0x1b); + regmap_write(regmap, WSA884X_DAC_VCM_CTRL_REG6, 0x1c); + regmap_update_bits(regmap, WSA884X_DAC_VCM_CTRL_REG7, + WSA884X_DAC_VCM_CTRL_REG7_FINAL_OVERRIDE_MASK, + WSA884X_DAC_VCM_CTRL_REG7_FINAL_OVERRIDE_MASK); + regmap_write(regmap, WSA884X_UVLO_PROG, 0x77); + regmap_write(regmap, WSA884X_PA_FSM_TIMER0, 0xc0); + regmap_write(regmap, WSA884X_UVLO_DEGLITCH_CTL, 0x1d); + regmap_write(regmap, WSA884X_UVLO_PROG1, 0x40); + regmap_update_bits(regmap, WSA884X_TOP_CTRL1, + WSA884X_TOP_CTRL1_OCP_LOWVBAT_ITH_EN_MASK, 0); +} + static void wsa884x_init(struct wsa884x_priv *wsa884x) { unsigned int wo_ctl_0; + unsigned int supply_config; unsigned int variant = 0; if (!regmap_read(wsa884x->regmap, WSA884X_OTP_REG_0, &variant)) variant = variant & WSA884X_OTP_REG_0_ID_MASK; + /* Never reuse a prior attach's supply status if this read fails. */ + wsa884x->supply_config = WSA884X_SUPPLY_1S; + if (wsa884x->speaker_load_ohms == 4) { + if (regmap_read(wsa884x->regmap, WSA884X_VPHX_SYS_EN_STATUS, + &supply_config)) { + dev_warn(wsa884x->dev, + "cannot read VPHX supply configuration; retaining defaults\n"); + } else { + wsa884x->supply_config = supply_config; + if (supply_config == WSA884X_SUPPLY_2S) + dev_dbg(wsa884x->dev, + "selected 2S 4-ohm speaker profile\n"); + else + dev_warn(wsa884x->dev, + "4-ohm profile requires 2S VPHX; retaining defaults\n"); + } + } + regmap_multi_reg_write(wsa884x->regmap, wsa884x_reg_init, ARRAY_SIZE(wsa884x_reg_init)); + wsa884x_apply_2s_4ohm_profile(wsa884x); wo_ctl_0 = 0xc; + if (wsa884x_uses_2s_4ohm_profile(wsa884x)) + wo_ctl_0 |= FIELD_PREP(WSA884X_ANA_WO_CTL_0_VPHX_SYS_EN_MASK, + WSA884X_SUPPLY_2S); wo_ctl_0 |= FIELD_PREP(WSA884X_ANA_WO_CTL_0_DAC_CM_CLAMP_EN_MASK, WSA884X_ANA_WO_CTL_0_DAC_CM_CLAMP_EN_MODE_SPEAKER); /* Assume that compander is enabled by default unless it is haptics sku */ @@ -1654,6 +1816,16 @@ static int wsa884x_codec_probe(struct snd_soc_component *comp) return 0; } +static void wsa884x_codec_remove(struct snd_soc_component *comp) +{ + struct wsa884x_priv *wsa884x = snd_soc_component_get_drvdata(comp); + + if (wsa884x->protection_pa_reported) { + wsa_macro_protection_pa_event(comp, false); + wsa884x->protection_pa_reported = false; + } +} + static void wsa884x_spkr_post_pmu(struct snd_soc_component *component, struct wsa884x_priv *wsa884x) { @@ -1677,7 +1849,8 @@ static void wsa884x_spkr_post_pmu(struct snd_soc_component *component, if (wsa884x->port_enable[WSA884X_PORT_PBR]) { curr_ovrd_en = 0x0; - curr_limit = 0x15; + curr_limit = wsa884x_uses_2s_4ohm_profile(wsa884x) ? + 0x11 : 0x15; } else { curr_ovrd_en = 0x1; if (wsa884x->dev_mode == WSA884X_RECEIVER) @@ -1760,6 +1933,7 @@ static const struct snd_soc_dapm_route wsa884x_audio_map[] = { static const struct snd_soc_component_driver wsa884x_component_drv = { .name = "WSA884x", .probe = wsa884x_codec_probe, + .remove = wsa884x_codec_remove, .controls = wsa884x_snd_controls, .num_controls = ARRAY_SIZE(wsa884x_snd_controls), .dapm_widgets = wsa884x_dapm_widgets, @@ -1773,22 +1947,77 @@ static int wsa884x_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct wsa884x_priv *wsa884x = dev_get_drvdata(dai->dev); + struct sdw_port_config port_config[WSA884X_MAX_SWR_PORTS]; + struct sdw_stream_config sconfig = { + .ch_count = 1, + .bps = 1, + .type = SDW_STREAM_PDM, + }; + int active_ports = 0; int i; - wsa884x->active_ports = 0; + if (dai->id && !wsa884x_uses_2s_4ohm_profile(wsa884x)) + return -ENODEV; + + if (!wsa884x->protected_feedback) { + wsa884x->active_ports = 0; + for (i = 0; i < WSA884X_MAX_SWR_PORTS; i++) { + if (!wsa884x->port_enable[i]) + continue; + + wsa884x->port_config[wsa884x->active_ports] = + wsa884x_pconfig[i]; + wsa884x->active_ports++; + } + + wsa884x->sconfig.frame_rate = params_rate(params); + + return sdw_stream_add_slave(wsa884x->slave, &wsa884x->sconfig, + wsa884x->port_config, + wsa884x->active_ports, + wsa884x->sruntime[0]); + } + for (i = 0; i < WSA884X_MAX_SWR_PORTS; i++) { if (!wsa884x->port_enable[i]) continue; - wsa884x->port_config[wsa884x->active_ports] = wsa884x_pconfig[i]; - wsa884x->active_ports++; + if (dai->id == 0) { + if (i != WSA884X_PORT_DAC && + i != WSA884X_PORT_COMP && + i != WSA884X_PORT_BOOST) + continue; + } else if (dai->id == 1) { + if (i != WSA884X_PORT_VISENSE) + continue; + } else if (dai->id == 2) { + if (i != WSA884X_PORT_CPS) + continue; + } else { + continue; + } + + port_config[active_ports] = wsa884x_pconfig[i]; + if (i == WSA884X_PORT_VISENSE) + port_config[active_ports].ch_mask = + wsa884x->visense_channel_mask; + if (i == WSA884X_PORT_CPS) { + port_config[active_ports].transport_params_override_mask = + SDW_PORT_CONFIG_OVERRIDE_OFFSET1; + port_config[active_ports].transport_params_override.offset1 = + wsa884x->cps_offset1; + } + active_ports++; } - wsa884x->sconfig.frame_rate = params_rate(params); + if (!active_ports) + return -ENODEV; + + sconfig.frame_rate = params_rate(params); + sconfig.direction = dai->id == 0 ? SDW_DATA_DIR_RX : SDW_DATA_DIR_TX; - return sdw_stream_add_slave(wsa884x->slave, &wsa884x->sconfig, - wsa884x->port_config, wsa884x->active_ports, - wsa884x->sruntime); + return sdw_stream_add_slave(wsa884x->slave, &sconfig, port_config, + active_ports, wsa884x->sruntime[dai->id]); } static int wsa884x_hw_free(struct snd_pcm_substream *substream, @@ -1796,30 +2025,80 @@ static int wsa884x_hw_free(struct snd_pcm_substream *substream, { struct wsa884x_priv *wsa884x = dev_get_drvdata(dai->dev); - sdw_stream_remove_slave(wsa884x->slave, wsa884x->sruntime); + sdw_stream_remove_slave(wsa884x->slave, wsa884x->sruntime[dai->id]); return 0; } +static void wsa884x_disable_pa(struct snd_soc_component *component) +{ + snd_soc_component_write_field(component, WSA884X_DRE_CTL_1, + WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK, 0x0); + snd_soc_component_write_field(component, WSA884X_PA_FSM_EN, + WSA884X_PA_FSM_EN_GLOBAL_PA_EN_MASK, 0x0); +} + static int wsa884x_mute_stream(struct snd_soc_dai *dai, int mute, int stream) { struct snd_soc_component *component = dai->component; + struct wsa884x_priv *wsa884x = snd_soc_component_get_drvdata(component); + unsigned int pa_status = 0; + int ret; + + if (dai->id != 0) + return 0; if (mute) { - snd_soc_component_write_field(component, WSA884X_DRE_CTL_1, - WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK, - 0x0); - snd_soc_component_write_field(component, WSA884X_PA_FSM_EN, - WSA884X_PA_FSM_EN_GLOBAL_PA_EN_MASK, - 0x0); + if (wsa884x->protection_pa_reported) { + wsa_macro_protection_pa_event(component, false); + wsa884x->protection_pa_reported = false; + } + + wsa884x_disable_pa(component); } else { - snd_soc_component_write_field(component, WSA884X_DRE_CTL_1, - WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK, - 0x1); - snd_soc_component_write_field(component, WSA884X_PA_FSM_EN, - WSA884X_PA_FSM_EN_GLOBAL_PA_EN_MASK, - 0x1); + ret = snd_soc_component_write_field(component, WSA884X_DRE_CTL_1, + WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK, + 0x1); + if (ret < 0) { + wsa884x_disable_pa(component); + return ret; + } + + ret = snd_soc_component_write_field(component, WSA884X_PA_FSM_EN, + WSA884X_PA_FSM_EN_GLOBAL_PA_EN_MASK, + 0x1); + if (ret < 0) { + wsa884x_disable_pa(component); + return ret; + } + + if (!wsa884x->protection_pa_reported && + wsa884x_uses_2s_4ohm_profile(wsa884x) && + wsa884x->protected_feedback) { + usleep_range(1000, 1100); + ret = regmap_read(wsa884x->regmap, WSA884X_PA_FSM_STA1, + &pa_status); + if (ret || (pa_status & WSA884X_PA_ERROR_MASK)) { + wsa884x_disable_pa(component); + if (ret) + dev_err(wsa884x->dev, + "cannot read protected PA status: %pe\n", + ERR_PTR(ret)); + else + dev_err(wsa884x->dev, + "protected PA start failed: status %#x\n", + pa_status); + return ret ?: -EIO; + } + + if (!wsa_macro_protection_pa_event(component, true)) { + wsa884x_disable_pa(component); + return -ENODEV; + } + + wsa884x->protection_pa_reported = true; + } } return 0; @@ -1830,7 +2109,7 @@ static int wsa884x_set_stream(struct snd_soc_dai *dai, { struct wsa884x_priv *wsa884x = dev_get_drvdata(dai->dev); - wsa884x->sruntime = stream; + wsa884x->sruntime[dai->id] = stream; return 0; } @@ -1846,6 +2125,7 @@ static const struct snd_soc_dai_ops wsa884x_dai_ops = { static struct snd_soc_dai_driver wsa884x_dais[] = { { .name = "SPKR", + .id = 0, .playback = { .stream_name = "SPKR Playback", .rates = WSA884X_RATES | WSA884X_FRAC_RATES, @@ -1857,6 +2137,34 @@ static struct snd_soc_dai_driver wsa884x_dais[] = { }, .ops = &wsa884x_dai_ops, }, + { + .name = "SPKR_VI", + .id = 1, + .playback = { + .stream_name = "SPKR VI Protection", + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &wsa884x_dai_ops, + }, + { + .name = "SPKR_CPS", + .id = 2, + .playback = { + .stream_name = "SPKR CPS Protection", + .rates = SNDRV_PCM_RATE_24000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_min = 24000, + .rate_max = 24000, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &wsa884x_dai_ops, + }, }; static int wsa884x_get_temp(struct wsa884x_priv *wsa884x, long *temp) @@ -2046,6 +2354,9 @@ static int wsa884x_probe(struct sdw_slave *pdev, struct device *dev = &pdev->dev; struct wsa884x_priv *wsa884x; unsigned int i; + u32 cps_offset1; + u32 speaker_load_ohms; + u32 visense_channel_mask; int ret; wsa884x = devm_kzalloc(dev, sizeof(*wsa884x), GFP_KERNEL); @@ -2079,10 +2390,42 @@ static int wsa884x_probe(struct sdw_slave *pdev, wsa884x->slave = pdev; wsa884x->dev = dev; wsa884x->dev_mode = WSA884X_SPEAKER; + wsa884x->speaker_load_ohms = 8; + wsa884x->supply_config = WSA884X_SUPPLY_1S; + if (!of_property_read_u32(dev->of_node, "qcom,speaker-load-ohms", + &speaker_load_ohms)) { + if (speaker_load_ohms != 4 && speaker_load_ohms != 8) + return dev_err_probe(dev, -EINVAL, + "invalid qcom,speaker-load-ohms\n"); + wsa884x->speaker_load_ohms = speaker_load_ohms; + } wsa884x->sconfig.ch_count = 1; wsa884x->sconfig.bps = 1; wsa884x->sconfig.direction = SDW_DATA_DIR_RX; wsa884x->sconfig.type = SDW_STREAM_PDM; + wsa884x->visense_channel_mask = + wsa884x_pconfig[WSA884X_PORT_VISENSE].ch_mask; + wsa884x->protected_feedback = + of_property_read_bool(dev->of_node, "qcom,enable-cps"); + if (wsa884x->protected_feedback) { + ret = of_property_read_u32(dev->of_node, + "qcom,visense-channel-mask", + &visense_channel_mask); + if (!ret) { + if (!visense_channel_mask || visense_channel_mask > U8_MAX) + return dev_err_probe(dev, -EINVAL, + "invalid qcom,visense-channel-mask\n"); + wsa884x->visense_channel_mask = visense_channel_mask; + } + + ret = of_property_read_u32(dev->of_node, "qcom,cps-offset1", + &cps_offset1); + if (ret || cps_offset1 > U8_MAX) + return dev_err_probe(dev, ret ?: -ERANGE, + "invalid qcom,cps-offset1\n"); + wsa884x->cps_offset1 = cps_offset1; + wsa884x->port_enable[WSA884X_PORT_CPS] = true; + } /* * Port map index starts with 0, however the data port for this codec @@ -2092,7 +2435,17 @@ static int wsa884x_probe(struct sdw_slave *pdev, WSA884X_MAX_SWR_PORTS)) dev_dbg(dev, "Static Port mapping not specified\n"); - pdev->prop.sink_ports = GENMASK(WSA884X_MAX_SWR_PORTS - 1, 0); + if (wsa884x->protected_feedback) { + pdev->prop.sink_ports = BIT(WSA884X_PORT_DAC + 1) | + BIT(WSA884X_PORT_COMP + 1) | + BIT(WSA884X_PORT_BOOST + 1) | + BIT(WSA884X_PORT_PBR + 1); + pdev->prop.source_ports = BIT(WSA884X_PORT_VISENSE + 1) | + BIT(WSA884X_PORT_CPS + 1); + pdev->prop.src_dpn_prop = wsa884x_source_dpn_prop; + } else { + pdev->prop.sink_ports = GENMASK(WSA884X_MAX_SWR_PORTS - 1, 0); + } pdev->prop.simple_clk_stop_capable = true; pdev->prop.sink_dpn_prop = wsa884x_sink_dpn_prop; pdev->prop.scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; @@ -2131,7 +2484,8 @@ static int wsa884x_probe(struct sdw_slave *pdev, return devm_snd_soc_register_component(dev, &wsa884x_component_drv, wsa884x_dais, - ARRAY_SIZE(wsa884x_dais)); + wsa884x->protected_feedback ? + ARRAY_SIZE(wsa884x_dais) : 1); } static int wsa884x_runtime_suspend(struct device *dev) diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index e6e9eb2e85aa1f..e609d115d9931e 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,10 @@ #include "q6apm.h" #include "audioreach.h" +DEFINE_FREE(q6apm_graph_user, struct q6apm_graph *, q6apm_graph_user_put(_T)) + +#define AUDIOREACH_DSP_EUNSUPPORTED 3 + /* SubGraph Config */ struct apm_sub_graph_data { struct apm_sub_graph_cfg sub_graph_cfg; @@ -55,14 +60,20 @@ struct apm_container_obj { struct apm_cont_prop_id_domain domain; } __packed; +struct apm_container_extended_obj { + struct apm_container_obj common; + struct apm_prop_data parent_data; + struct apm_cont_prop_id_parent_container parent; + struct apm_prop_data heap_data; + struct apm_cont_prop_id_headp_id heap; +} __packed; + struct apm_container_params { struct apm_module_param_data param_data; uint32_t num_containers; - struct apm_container_obj cont_obj[]; + u8 cont_obj[]; } __packed; -#define APM_CONTAINER_PSIZE(p, n) ALIGN(struct_size(p, cont_obj, n), 8) - /* Module List config */ struct apm_mod_list_obj { /* Modules list cfg */ @@ -109,6 +120,15 @@ struct apm_mod_conn_list_params { #define APM_MOD_CONN_PSIZE(p, n) ALIGN(struct_size(p, conn_obj, n), 8) +struct apm_mod_ctrl_link_list_params { + struct apm_module_param_data param_data; + u32 num_ctrl_links; + u8 ctrl_link_cfg[]; +} __packed; + +#define APM_MOD_CTRL_LINK_PSIZE(p, n) \ + ALIGN(struct_size(p, ctrl_link_cfg, n), 8) + struct apm_graph_open_params { struct apm_cmd_header *cmd_header; struct apm_sub_graph_params *sg_data; @@ -116,6 +136,7 @@ struct apm_graph_open_params { struct apm_module_list_params *mod_list_data; struct apm_prop_list_params *mod_prop_data; struct apm_mod_conn_list_params *mod_conn_list_data; + struct apm_mod_ctrl_link_list_params *mod_ctrl_link_data; } __packed; struct apm_pcm_module_media_fmt_cmd { @@ -292,6 +313,32 @@ void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t token } EXPORT_SYMBOL_GPL(audioreach_alloc_apm_cmd_pkt); +static int audioreach_size_add(size_t *total, size_t value) +{ + return check_add_overflow(*total, value, total) ? -EOVERFLOW : 0; +} + +static int audioreach_flex_size(size_t base, size_t count, size_t element, + size_t *size) +{ + size_t elements; + + if (check_mul_overflow(count, element, &elements) || + check_add_overflow(base, elements, size)) + return -EOVERFLOW; + + return 0; +} + +static int audioreach_align_size(size_t *size) +{ + if (*size > SIZE_MAX - 7) + return -EOVERFLOW; + + *size = ALIGN(*size, 8); + return 0; +} + void audioreach_set_default_channel_mapping(u8 *ch_map, int num_channels) { if (num_channels == 1) { @@ -308,13 +355,21 @@ void audioreach_set_default_channel_mapping(u8 *ch_map, int num_channels) } EXPORT_SYMBOL_GPL(audioreach_set_default_channel_mapping); -static void apm_populate_container_config(struct apm_container_obj *cfg, +static size_t apm_container_obj_size(const struct audioreach_container *cont) +{ + return cont->has_extended_properties ? + sizeof(struct apm_container_extended_obj) : + sizeof(struct apm_container_obj); +} + +static void apm_populate_container_config(void *data, const struct audioreach_container *cont) { + struct apm_container_obj *cfg = data; /* Container Config */ cfg->container_cfg.container_id = cont->container_id; - cfg->container_cfg.num_prop = 4; + cfg->container_cfg.num_prop = cont->has_extended_properties ? 6 : 4; /* Capability list */ cfg->cap_data.prop_id = APM_CONTAINER_PROP_ID_CAPABILITY_LIST; @@ -336,6 +391,19 @@ static void apm_populate_container_config(struct apm_container_obj *cfg, cfg->domain_data.prop_id = APM_CONTAINER_PROP_ID_PROC_DOMAIN; cfg->domain_data.prop_size = sizeof(struct apm_cont_prop_id_domain); cfg->domain.proc_domain = cont->proc_domain; + + if (cont->has_extended_properties) { + struct apm_container_extended_obj *ext = data; + + ext->parent_data.prop_id = + APM_CONTAINER_PROP_ID_PARENT_CONTAINER_ID; + ext->parent_data.prop_size = sizeof(ext->parent); + ext->parent.parent_container_id = cont->parent_container_id; + + ext->heap_data.prop_id = APM_CONTAINER_PROP_ID_HEAP_ID; + ext->heap_data.prop_size = sizeof(ext->heap); + ext->heap.heap_id = cont->heap_id; + } } static void apm_populate_sub_graph_config(struct apm_sub_graph_data *cfg, @@ -398,23 +466,26 @@ static void audioreach_populate_graph(struct q6apm *apm, { struct apm_mod_conn_list_params *mc_data = open->mod_conn_list_data; struct apm_module_list_params *ml_data = open->mod_list_data; + struct apm_mod_ctrl_link_list_params *cl_data = open->mod_ctrl_link_data; + u8 *ctrl_link_cfg = cl_data ? cl_data->ctrl_link_cfg : NULL; struct apm_prop_list_params *mp_data = open->mod_prop_data; struct apm_container_params *c_data = open->cont_data; struct apm_sub_graph_params *sg_data = open->sg_data; - int ncontainer = 0, nmodule = 0, nconn = 0; + int nmodule = 0, nconn = 0; struct apm_mod_prop_obj *module_prop_obj; struct audioreach_container *container; struct apm_module_conn_obj *conn_obj; struct audioreach_module *module; struct audioreach_sub_graph *sg; - struct apm_container_obj *cobj; + u8 *cobj = c_data->cont_obj; struct apm_mod_list_obj *mlobj; int i = 0; mlobj = &ml_data->mod_list_obj[0]; - if (info->dst_mod_inst_id && info->src_mod_inst_id) { + if (!info->internal_vmixer_connection && + info->dst_mod_inst_id && info->src_mod_inst_id) { conn_obj = &mc_data->conn_obj[nconn]; conn_obj->src_mod_inst_id = info->src_mod_inst_id; conn_obj->src_mod_op_port_id = info->src_mod_op_port_id; @@ -429,8 +500,6 @@ static void audioreach_populate_graph(struct q6apm *apm, apm_populate_sub_graph_config(sg_cfg, sg); list_for_each_entry(container, &sg->container_list, node) { - cobj = &c_data->cont_obj[ncontainer]; - apm_populate_container_config(cobj, container); apm_populate_module_list_obj(mlobj, container, sg->sub_graph_id); @@ -440,6 +509,16 @@ static void audioreach_populate_graph(struct q6apm *apm, module_prop_obj = &mp_data->mod_prop_obj[nmodule++]; apm_populate_module_prop_obj(module_prop_obj, module); + if (module->ctrl_link_data) { + u32 size = le32_to_cpu(module->ctrl_link_data->size); + + memcpy(ctrl_link_cfg, + (u8 *)module->ctrl_link_data->data + + sizeof(u32), + size - sizeof(u32)); + ctrl_link_cfg += size - sizeof(u32); + } + if (!module->max_op_port) continue; @@ -459,8 +538,7 @@ static void audioreach_populate_graph(struct q6apm *apm, } mlobj = (void *) mlobj + APM_MOD_LIST_OBJ_PSIZE(mlobj, container->num_modules); - - ncontainer++; + cobj += apm_container_obj_size(container); } } } @@ -468,58 +546,115 @@ static void audioreach_populate_graph(struct q6apm *apm, void *audioreach_alloc_graph_pkt(struct q6apm *apm, const struct audioreach_graph_info *info) { - int payload_size, sg_sz, cont_sz, ml_sz, mp_sz, mc_sz; + size_t payload_size = 0, sg_sz, cont_sz, ml_sz = 0; + size_t mp_sz, mc_sz, cl_sz = 0, object_size; struct apm_module_param_data *param_data; - struct apm_container_params *cont_params; struct audioreach_container *container; struct apm_sub_graph_params *sg_params; struct apm_mod_conn_list_params *mcon; struct apm_graph_open_params params; struct apm_prop_list_params *mprop; + struct apm_mod_ctrl_link_list_params *mctrl; struct audioreach_module *module; struct audioreach_sub_graph *sgs; struct apm_mod_list_obj *mlobj; const struct list_head *sg_list; - int num_connections = 0; - int num_containers = 0; - int num_sub_graphs = 0; - int num_modules = 0; - int num_modules_list; + size_t num_connections = 0; + size_t num_containers = 0; + size_t num_sub_graphs = 0; + size_t num_ctrl_links = 0; + size_t ctrl_link_cfg_size = 0; + size_t num_modules = 0; + size_t num_modules_list; struct gpr_pkt *pkt; void *p; + int ret; sg_list = &info->sg_list; - ml_sz = 0; + cont_sz = sizeof(struct apm_container_params); /* add FE-BE connections */ - if (info->dst_mod_inst_id && info->src_mod_inst_id) - num_connections++; + if (!info->internal_vmixer_connection && + info->dst_mod_inst_id && info->src_mod_inst_id) + num_connections = 1; list_for_each_entry(sgs, sg_list, node) { - num_sub_graphs++; + if (check_add_overflow(num_sub_graphs, 1UL, &num_sub_graphs)) + return ERR_PTR(-EOVERFLOW); list_for_each_entry(container, &sgs->container_list, node) { - num_containers++; - num_modules += container->num_modules; - ml_sz = ml_sz + sizeof(struct apm_module_list_params) + - APM_MOD_LIST_OBJ_PSIZE(mlobj, container->num_modules); + if (check_add_overflow(num_containers, 1UL, + &num_containers) || + check_add_overflow(num_modules, + (size_t)container->num_modules, + &num_modules) || + audioreach_size_add(&cont_sz, + apm_container_obj_size(container))) + return ERR_PTR(-EOVERFLOW); + + ret = audioreach_flex_size(sizeof(*mlobj), + container->num_modules, + sizeof(mlobj->mod_cfg[0]), + &object_size); + if (ret || + audioreach_size_add(&ml_sz, + sizeof(struct apm_module_list_params)) || + audioreach_size_add(&ml_sz, object_size)) + return ERR_PTR(-EOVERFLOW); list_for_each_entry(module, &container->modules_list, node) { - num_connections += module->num_connections; + if (check_add_overflow(num_connections, + (size_t)module->num_connections, + &num_connections)) + return ERR_PTR(-EOVERFLOW); + if (module->ctrl_link_data) { + u32 size = le32_to_cpu(module->ctrl_link_data->size); + u32 links; + + links = get_unaligned_le32(module->ctrl_link_data->data); + if (check_add_overflow(num_ctrl_links, + (size_t)links, + &num_ctrl_links) || + audioreach_size_add(&ctrl_link_cfg_size, + size - sizeof(u32))) + return ERR_PTR(-EOVERFLOW); + } } } } - num_modules_list = num_containers; - sg_sz = APM_SUB_GRAPH_PSIZE(sg_params, num_sub_graphs); - cont_sz = APM_CONTAINER_PSIZE(cont_params, num_containers); - - ml_sz = ALIGN(ml_sz, 8); - - mp_sz = APM_MOD_PROP_PSIZE(mprop, num_modules); - mc_sz = APM_MOD_CONN_PSIZE(mcon, num_connections); + if (num_sub_graphs > U32_MAX || num_containers > U32_MAX || + num_modules > U32_MAX || num_connections > U32_MAX || + num_ctrl_links > U32_MAX) + return ERR_PTR(-EOVERFLOW); - payload_size = sg_sz + cont_sz + ml_sz + mp_sz + mc_sz; - pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_GRAPH_OPEN, 0); + num_modules_list = num_containers; + ret = audioreach_flex_size(sizeof(*sg_params), num_sub_graphs, + sizeof(sg_params->sg_cfg[0]), &sg_sz); + ret = ret ?: audioreach_flex_size(sizeof(*mprop), num_modules, + sizeof(mprop->mod_prop_obj[0]), &mp_sz); + ret = ret ?: audioreach_flex_size(sizeof(*mcon), num_connections, + sizeof(mcon->conn_obj[0]), &mc_sz); + if (num_ctrl_links) + ret = ret ?: audioreach_flex_size(sizeof(*mctrl), + ctrl_link_cfg_size, 1, &cl_sz); + ret = ret ?: audioreach_align_size(&sg_sz); + ret = ret ?: audioreach_align_size(&cont_sz); + ret = ret ?: audioreach_align_size(&ml_sz); + ret = ret ?: audioreach_align_size(&mp_sz); + ret = ret ?: audioreach_align_size(&mc_sz); + if (num_ctrl_links) + ret = ret ?: audioreach_align_size(&cl_sz); + if (ret || audioreach_size_add(&payload_size, sg_sz) || + audioreach_size_add(&payload_size, cont_sz) || + audioreach_size_add(&payload_size, ml_sz) || + audioreach_size_add(&payload_size, mp_sz) || + audioreach_size_add(&payload_size, mc_sz) || + audioreach_size_add(&payload_size, cl_sz) || + payload_size > INT_MAX - GPR_HDR_SIZE - APM_CMD_HDR_SIZE) + return ERR_PTR(-EOVERFLOW); + + pkt = audioreach_alloc_apm_cmd_pkt((int)payload_size, + APM_CMD_GRAPH_OPEN, 0); if (IS_ERR(pkt)) return pkt; @@ -570,12 +705,505 @@ void *audioreach_alloc_graph_pkt(struct q6apm *apm, params.mod_conn_list_data->num_connections = num_connections; p += mc_sz; + if (num_ctrl_links) { + params.mod_ctrl_link_data = p; + param_data = ¶ms.mod_ctrl_link_data->param_data; + param_data->module_instance_id = APM_MODULE_INSTANCE_ID; + param_data->param_id = APM_PARAM_ID_MODULE_CTRL_LINK_CFG; + param_data->param_size = sizeof(u32) + ctrl_link_cfg_size; + params.mod_ctrl_link_data->num_ctrl_links = num_ctrl_links; + } else { + params.mod_ctrl_link_data = NULL; + } + audioreach_populate_graph(apm, info, ¶ms, sg_list, num_sub_graphs); return pkt; } EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt); +static struct audioreach_module_priv_data * +audioreach_module_data(const struct audioreach_module *module, u32 type) +{ + switch (type) { + case SND_SOC_AR_TPLG_GRAPH_CAL_CFG_TYPE: + return module->graph_cal_data; + case SND_SOC_AR_TPLG_RENDER_EP_CFG_TYPE: + return module->render_ep_data; + case SND_SOC_AR_TPLG_SP_TAG_CFG_TYPE: + return module->sp_tag_data; + case SND_SOC_AR_TPLG_SPVI_TAG_CFG_TYPE: + return module->spvi_tag_data; + case SND_SOC_AR_TPLG_VI_EP_CFG_TYPE: + return module->vi_ep_data; + case SND_SOC_AR_TPLG_PROTECTION_DYNAMIC_CFG_TYPE: + return module->protection_dynamic_data; + case SND_SOC_AR_TPLG_VOLUME_GAIN_CFG_TYPE: + return module->volume_gain_data; + case SND_SOC_AR_TPLG_VOLUME_FILTER_CFG_TYPE: + return module->volume_filter_data; + case SND_SOC_AR_TPLG_VOLUME_MUTE_CFG_TYPE: + return module->volume_mute_data; + case SND_SOC_AR_TPLG_CHANNEL_MIXER_CFG_TYPE: + return module->channel_mixer_data; + default: + return NULL; + } +} + +const struct audioreach_module_priv_data * +audioreach_graph_find_data(const struct audioreach_graph_info *info, u32 type) +{ + const struct audioreach_module_priv_data *found = NULL; + struct audioreach_container *container; + struct audioreach_sub_graph *sg; + struct audioreach_module *module; + + list_for_each_entry(sg, &info->sg_list, node) { + list_for_each_entry(container, &sg->container_list, node) { + list_for_each_entry(module, &container->modules_list, node) { + struct audioreach_module_priv_data *candidate; + + candidate = audioreach_module_data(module, type); + if (!candidate) + continue; + if (found) + return ERR_PTR(-EEXIST); + found = candidate; + } + } + } + + return found; +} + +const struct audioreach_module * +audioreach_graph_find_module(const struct audioreach_graph_info *info, u32 mid) +{ + const struct audioreach_module *found = NULL; + struct audioreach_container *container; + struct audioreach_sub_graph *sg; + struct audioreach_module *module; + + list_for_each_entry(sg, &info->sg_list, node) { + list_for_each_entry(container, &sg->container_list, node) { + list_for_each_entry(module, &container->modules_list, node) { + if (module->module_id != mid) + continue; + if (found) + return ERR_PTR(-EEXIST); + found = module; + } + } + } + + return found; +} + +static bool +audioreach_graph_has_iid(const struct audioreach_graph_info *info, u32 iid) +{ + struct audioreach_container *container; + struct audioreach_sub_graph *sg; + struct audioreach_module *module; + + list_for_each_entry(sg, &info->sg_list, node) + list_for_each_entry(container, &sg->container_list, node) + list_for_each_entry(module, &container->modules_list, node) + if (module->instance_id == iid) + return true; + + return false; +} + +static int audioreach_validate_stage(const struct audioreach_graph_info *info, + const struct audioreach_module_priv_data *stage) +{ + const u8 *cursor = (const u8 *)stage->data; + size_t remaining = le32_to_cpu(stage->size); + + if (!remaining || !IS_ALIGNED(remaining, 8)) + return -EINVAL; + + while (remaining) { + size_t frame_size; + u32 param_size; + + if (remaining < sizeof(struct apm_module_param_data)) + return -EINVAL; + + param_size = get_unaligned_le32(cursor + 2 * sizeof(u32)); + if (check_add_overflow(sizeof(struct apm_module_param_data), + (size_t)param_size, &frame_size)) + return -EOVERFLOW; + frame_size = ALIGN(frame_size, 8); + if (frame_size > remaining) + return -EINVAL; + if (!audioreach_graph_has_iid(info, + get_unaligned_le32(cursor))) + return -ENOENT; + + cursor += frame_size; + remaining -= frame_size; + } + + return 0; +} + +int audioreach_graph_protection_profile(const struct audioreach_graph_info *info) +{ + static const u32 stage_types[] = { + SND_SOC_AR_TPLG_GRAPH_CAL_CFG_TYPE, + SND_SOC_AR_TPLG_RENDER_EP_CFG_TYPE, + SND_SOC_AR_TPLG_SP_TAG_CFG_TYPE, + SND_SOC_AR_TPLG_SPVI_TAG_CFG_TYPE, + SND_SOC_AR_TPLG_VI_EP_CFG_TYPE, + SND_SOC_AR_TPLG_PROTECTION_DYNAMIC_CFG_TYPE, + SND_SOC_AR_TPLG_VOLUME_GAIN_CFG_TYPE, + SND_SOC_AR_TPLG_VOLUME_FILTER_CFG_TYPE, + SND_SOC_AR_TPLG_VOLUME_MUTE_CFG_TYPE, + SND_SOC_AR_TPLG_CHANNEL_MIXER_CFG_TYPE, + }; + struct audioreach_container *container; + const struct audioreach_module_priv_data *data; + const struct audioreach_module *sp, *spvi; + struct audioreach_sub_graph *sg; + struct audioreach_module *module; + bool declared = false; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(stage_types); i++) { + data = audioreach_graph_find_data(info, stage_types[i]); + if (IS_ERR(data)) + return PTR_ERR(data); + if (data) + declared = true; + } + list_for_each_entry(sg, &info->sg_list, node) + list_for_each_entry(container, &sg->container_list, node) + list_for_each_entry(module, &container->modules_list, node) + if (module->speaker_protection_bypass || + module->integrated_backend_id) + declared = true; + if (!declared) + return 0; + + for (i = 0; i < ARRAY_SIZE(stage_types); i++) { + data = audioreach_graph_find_data(info, stage_types[i]); + if (IS_ERR(data)) + return PTR_ERR(data); + if (!data || !le32_to_cpu(data->size) || + !IS_ALIGNED(le32_to_cpu(data->size), 8)) + return -EINVAL; + /* The graph-calibration aggregate is opaque and may carry sentinels. */ + if (i) { + ret = audioreach_validate_stage(info, data); + if (ret) + return ret; + } + } + + sp = audioreach_graph_find_module(info, MODULE_ID_SPEAKER_PROTECTION); + spvi = audioreach_graph_find_module(info, + MODULE_ID_SPEAKER_PROTECTION_VI); + if (IS_ERR(sp) || IS_ERR(spvi)) + return -EEXIST; + if (!sp || !spvi || !sp->speaker_protection_bypass || + !spvi->speaker_protection_bypass) + return -EINVAL; + + return 1; +} + +int audioreach_graph_protection_oob_size(const struct audioreach_graph_info *info, + size_t *size) +{ + static const u32 oob_types[] = { + SND_SOC_AR_TPLG_GRAPH_CAL_CFG_TYPE, + SND_SOC_AR_TPLG_RENDER_EP_CFG_TYPE, + SND_SOC_AR_TPLG_SP_TAG_CFG_TYPE, + SND_SOC_AR_TPLG_SPVI_TAG_CFG_TYPE, + SND_SOC_AR_TPLG_VI_EP_CFG_TYPE, + SND_SOC_AR_TPLG_VOLUME_FILTER_CFG_TYPE, + SND_SOC_AR_TPLG_CHANNEL_MIXER_CFG_TYPE, + }; + const struct audioreach_module_priv_data *data; + size_t max_size = 0; + int i, ret; + + ret = audioreach_graph_protection_profile(info); + if (ret <= 0) { + *size = 0; + return ret; + } + + for (i = 0; i < ARRAY_SIZE(oob_types); i++) { + data = audioreach_graph_find_data(info, oob_types[i]); + if (IS_ERR(data)) + return PTR_ERR(data); + if (data) + max_size = max(max_size, + (size_t)le32_to_cpu(data->size)); + } + + *size = max_size; + return 0; +} + +int audioreach_send_protected_graph_calibration(struct audioreach_graph *graph) +{ + const struct audioreach_module_priv_data *graph_cal; + int ret; + + ret = audioreach_graph_protection_profile(graph->info); + if (ret <= 0) + return ret < 0 ? ret : 0; + + graph_cal = audioreach_graph_find_data(graph->info, + SND_SOC_AR_TPLG_GRAPH_CAL_CFG_TYPE); + ret = q6apm_send_oob_config(graph, graph_cal->data, + le32_to_cpu(graph_cal->size)); + if (ret == -EOPNOTSUPP) { + /* Qualcomm calibration aggregates may contain query-only records. */ + dev_warn(graph->apm->dev, + "graph calibration returned AR_EUNSUPPORTED; continuing\n"); + ret = 0; + } + + return ret; +} + +static size_t audioreach_frame_size(const u8 *frame) +{ + return ALIGN(sizeof(struct apm_module_param_data) + + get_unaligned_le32(frame + 2 * sizeof(u32)), 8); +} + +static int audioreach_send_inband_frame(struct q6apm_graph *graph, + const u8 *frame, size_t frame_size) +{ + struct gpr_pkt *pkt __free(kfree) = NULL; + void *payload; + + pkt = audioreach_alloc_cmd_pkt(frame_size, APM_CMD_SET_CFG, 0, graph->port->id, + get_unaligned_le32(frame)); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + payload = (u8 *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + memcpy(payload, frame, frame_size); + + return audioreach_graph_send_cmd_sync(graph, pkt, 0); +} + +static int audioreach_send_inband_stage(struct q6apm_graph *graph, + const struct audioreach_module_priv_data *stage, + size_t offset) +{ + const u8 *cursor = (const u8 *)stage->data + offset; + size_t remaining = le32_to_cpu(stage->size) - offset; + int ret; + + while (remaining) { + size_t frame_size = audioreach_frame_size(cursor); + + ret = audioreach_send_inband_frame(graph, cursor, frame_size); + if (ret) + return ret; + cursor += frame_size; + remaining -= frame_size; + } + + return 0; +} + +static int audioreach_graph_module_enable(struct q6apm_graph *graph, + const struct audioreach_module *module, + bool enable) +{ + struct gpr_pkt *pkt __free(kfree) = NULL; + struct apm_module_param_data *param; + u32 *value; + + pkt = audioreach_alloc_cmd_pkt(APM_MODULE_PARAM_DATA_SIZE + sizeof(*value), + APM_CMD_SET_CFG, 0, graph->port->id, + module->instance_id); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + param = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + param->module_instance_id = module->instance_id; + param->param_id = PARAM_ID_MODULE_ENABLE; + param->param_size = sizeof(*value); + value = (void *)param + APM_MODULE_PARAM_DATA_SIZE; + *value = enable; + + return audioreach_graph_send_cmd_sync(graph, pkt, 0); +} + +static int audioreach_protection_enable(struct q6apm_graph *graph, + bool enable) +{ + const struct audioreach_module *first, *second; + const struct audioreach_module *sp, *spvi; + int ret; + + sp = audioreach_graph_find_module(graph->info, + MODULE_ID_SPEAKER_PROTECTION); + spvi = audioreach_graph_find_module(graph->info, + MODULE_ID_SPEAKER_PROTECTION_VI); + if (IS_ERR_OR_NULL(sp) || IS_ERR_OR_NULL(spvi)) + return -ENODEV; + + /* Bring feedback up first; tear render down first. */ + first = enable ? spvi : sp; + second = enable ? sp : spvi; + ret = audioreach_graph_module_enable(graph, first, enable); + if (ret) + return ret; + + return audioreach_graph_module_enable(graph, second, enable); +} + +static int audioreach_send_oob_stage(struct q6apm_graph *graph, u32 type) +{ + const struct audioreach_module_priv_data *stage; + + stage = audioreach_graph_find_data(graph->info, type); + if (IS_ERR_OR_NULL(stage)) + return stage ? PTR_ERR(stage) : -ENODATA; + + return q6apm_send_graph_oob_config(graph, stage->data, + le32_to_cpu(stage->size)); +} + +int audioreach_configure_protection(struct q6apm_graph *graph) +{ + const struct audioreach_module_priv_data *dynamic, *gain, *mute; + struct audioreach_graph *ar_graph = graph->ar_graph; + size_t first_size; + int bypass_ret; + int ret = 0; + + mutex_lock(&ar_graph->protection_lock); + if (ar_graph->protection_faulted) { + ret = -EIO; + goto unlock; + } + if (ar_graph->protection_configured || + ar_graph->protection_bypass_confirmed) + goto unlock; + if (!ar_graph->protection_available || + READ_ONCE(ar_graph->oob_transfer_uncertain)) { + ret = audioreach_protection_enable(graph, false); + if (ret) { + ar_graph->protection_faulted = true; + dev_err(graph->dev, + "protected graph calibration is unavailable; bypass failed (%d)\n", + ret); + } else { + ar_graph->protection_bypass_confirmed = true; + dev_warn(graph->dev, + "protected graph calibration is unavailable; using bypass\n"); + } + goto unlock; + } + + if (!ar_graph->protection_vi_ready || + !ar_graph->protection_cps_ready) { + ret = audioreach_protection_enable(graph, false); + if (ret) { + ar_graph->protection_faulted = true; + dev_err(graph->dev, + "protected speaker feedback is incomplete; bypass failed (%d)\n", + ret); + } else { + ar_graph->protection_bypass_confirmed = true; + dev_warn(graph->dev, + "protected speaker feedback is incomplete; using bypass\n"); + } + goto unlock; + } + + dynamic = audioreach_graph_find_data(graph->info, + SND_SOC_AR_TPLG_PROTECTION_DYNAMIC_CFG_TYPE); + gain = audioreach_graph_find_data(graph->info, + SND_SOC_AR_TPLG_VOLUME_GAIN_CFG_TYPE); + mute = audioreach_graph_find_data(graph->info, + SND_SOC_AR_TPLG_VOLUME_MUTE_CFG_TYPE); + if (IS_ERR_OR_NULL(dynamic) || IS_ERR_OR_NULL(gain) || + IS_ERR_OR_NULL(mute)) { + ret = -ENODATA; + goto bypass; + } + + first_size = audioreach_frame_size((const u8 *)dynamic->data); + ret = audioreach_send_inband_frame(graph, + (const u8 *)dynamic->data, + first_size); + if (ret) + goto bypass; + ret = audioreach_send_oob_stage(graph, + SND_SOC_AR_TPLG_SP_TAG_CFG_TYPE); + if (ret) + goto bypass; + ret = audioreach_send_inband_stage(graph, dynamic, first_size); + if (ret) + goto bypass; + ret = audioreach_send_oob_stage(graph, + SND_SOC_AR_TPLG_SPVI_TAG_CFG_TYPE); + if (ret) + goto bypass; + ret = audioreach_send_oob_stage(graph, + SND_SOC_AR_TPLG_RENDER_EP_CFG_TYPE); + if (ret) + goto bypass; + ret = audioreach_send_oob_stage(graph, + SND_SOC_AR_TPLG_VI_EP_CFG_TYPE); + if (ret) + goto bypass; + ret = audioreach_protection_enable(graph, true); + if (ret) + goto bypass; + ret = audioreach_send_inband_stage(graph, gain, 0); + if (ret) + goto bypass; + ret = audioreach_send_oob_stage(graph, + SND_SOC_AR_TPLG_VOLUME_FILTER_CFG_TYPE); + if (ret) + goto bypass; + ret = audioreach_send_inband_stage(graph, mute, 0); + if (ret) + goto bypass; + ret = audioreach_send_oob_stage(graph, + SND_SOC_AR_TPLG_CHANNEL_MIXER_CFG_TYPE); + if (ret) + goto bypass; + + ar_graph->protection_configured = true; + ar_graph->protection_bypass_confirmed = false; + goto unlock; + +bypass: + bypass_ret = audioreach_protection_enable(graph, false); + if (!bypass_ret) { + ar_graph->protection_bypass_confirmed = true; + dev_err(graph->dev, + "protected speaker setup failed (%d); using bypass\n", ret); + ret = 0; + } else { + ar_graph->protection_faulted = true; + dev_err(graph->dev, + "protected speaker setup failed (%d), bypass failed (%d)\n", + ret, bypass_ret); + ret = bypass_ret; + } + +unlock: + mutex_unlock(&ar_graph->protection_lock); + return ret; +} + int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, @@ -610,7 +1238,8 @@ int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, rc = -ETIMEDOUT; } else if (result->status > 0) { dev_err(dev, "DSP returned error[%x] %x\n", hdr->opcode, result->status); - rc = -EINVAL; + rc = result->status == AUDIOREACH_DSP_EUNSUPPORTED ? + -EOPNOTSUPP : -EINVAL; } else { /* DSP successfully finished the command */ rc = 0; @@ -622,12 +1251,83 @@ int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, } EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync); -int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, struct gpr_pkt *pkt, uint32_t rsp_opcode) { + struct gpr_hdr *hdr = &pkt->hdr; + u32 result_status; + bool aborted; + int ret; + + if (!q6apm_graph_user_get(graph)) + return -ESHUTDOWN; + + mutex_lock(&graph->cmd_lock); + if (!++graph->cmd_token) + graph->cmd_token++; + hdr->token = graph->cmd_token; + + spin_lock(&graph->lifecycle_lock); + if (graph->dying || graph->detached) { + spin_unlock(&graph->lifecycle_lock); + ret = -ESHUTDOWN; + goto unlock; + } + spin_lock(&graph->result_lock); + graph->result.opcode = 0; + graph->result.status = 0; + graph->result_token = U32_MAX; + graph->pending_opcode = hdr->opcode; + graph->pending_rsp_opcode = rsp_opcode; + graph->pending_token = hdr->token; + graph->cmd_pending = true; + spin_unlock(&graph->result_lock); + spin_unlock(&graph->lifecycle_lock); + + ret = gpr_send_port_pkt(graph->port, pkt); + if (ret >= 0) + ret = wait_event_timeout(graph->cmd_wait, + !READ_ONCE(graph->cmd_pending) || + (((READ_ONCE(graph->result.opcode) == + hdr->opcode) || + (rsp_opcode && + READ_ONCE(graph->result.opcode) == + rsp_opcode)) && + READ_ONCE(graph->result_token) == + hdr->token), 5 * HZ); + + spin_lock(&graph->result_lock); + aborted = !graph->cmd_pending || READ_ONCE(graph->dying); + if (!ret && graph->result_token == hdr->token && + (graph->result.opcode == hdr->opcode || + (rsp_opcode && graph->result.opcode == rsp_opcode))) + ret = 1; + graph->cmd_pending = false; + result_status = graph->result.status; + spin_unlock(&graph->result_lock); + + if (aborted) { + ret = -ESHUTDOWN; + goto unlock; + } + if (ret < 0) + goto unlock; + if (!ret) { + dev_err(graph->dev, "CMD timeout for [%x] opcode\n", hdr->opcode); + ret = -ETIMEDOUT; + } else if (result_status > 0) { + dev_err(graph->dev, "DSP returned error[%x] %x\n", + hdr->opcode, result_status); + ret = result_status == AUDIOREACH_DSP_EUNSUPPORTED ? + -EOPNOTSUPP : -EINVAL; + } else { + ret = 0; + } - return audioreach_send_cmd_sync(graph->dev, NULL, &graph->result, &graph->lock, - graph->port, &graph->cmd_wait, pkt, rsp_opcode); +unlock: + mutex_unlock(&graph->cmd_lock); + q6apm_graph_user_put(graph); + return ret; } EXPORT_SYMBOL_GPL(audioreach_graph_send_cmd_sync); @@ -843,7 +1543,9 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, int i; void *p; - struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); + struct gpr_pkt *pkt __free(kfree) = + audioreach_alloc_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0, graph->port->id, + module->instance_id); if (IS_ERR(pkt)) return PTR_ERR(pkt); @@ -863,7 +1565,7 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, for (i = 0; i < num_channels; i++) media_format->channel_mapping[i] = cfg->channel_map[i]; - return q6apm_send_cmd_sync(graph->apm, pkt, 0); + return audioreach_graph_send_cmd_sync(graph, pkt, 0); } static int audioreach_set_compr_media_format(struct media_format *media_fmt_hdr, @@ -952,15 +1654,21 @@ static int audioreach_set_compr_media_format(struct media_format *media_fmt_hdr, int audioreach_compr_set_param(struct q6apm_graph *graph, const struct audioreach_module_config *mcfg) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct media_format *header; int rc; void *p; - int iid = graph->shm_iid; + int iid; int payload_size = sizeof(struct apm_sh_module_media_fmt_cmd); + struct gpr_pkt *pkt __free(kfree) = NULL; - struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_cmd_pkt(payload_size, - DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT, - 0, graph->port->id, iid); + if (!active) + return -ESHUTDOWN; + iid = graph->shm_iid; + pkt = audioreach_alloc_cmd_pkt(payload_size, + DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT, + 0, graph->port->id, iid); if (IS_ERR(pkt)) return -ENOMEM; @@ -1089,7 +1797,8 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, payload_size = APM_PCM_MODULE_FMT_CMD_PSIZE(num_channels); struct gpr_pkt *pkt __free(kfree) = - audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); + audioreach_alloc_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0, graph->port->id, + module->instance_id); if (IS_ERR(pkt)) return PTR_ERR(pkt); @@ -1115,7 +1824,7 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, media_cfg->bits_per_sample = mcfg->bit_width; memcpy(media_cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); - return q6apm_send_cmd_sync(graph->apm, pkt, 0); + return audioreach_graph_send_cmd_sync(graph, pkt, 0); } int audioreach_shmem_register_event(struct q6apm_graph *graph, int bytes, int num_levels) @@ -1400,6 +2109,10 @@ int audioreach_set_media_format(struct q6apm_graph *graph, rc = audioreach_gapless_set_media_format(graph, module, cfg); break; case MODULE_ID_SPEAKER_PROTECTION: + if (module->speaker_protection_bypass) { + rc = 0; + break; + } rc = audioreach_speaker_protection(graph, module, PARAM_ID_SP_OP_MODE_NORMAL); if (!rc) @@ -1407,6 +2120,10 @@ int audioreach_set_media_format(struct q6apm_graph *graph, break; case MODULE_ID_SPEAKER_PROTECTION_VI: + if (module->speaker_protection_bypass) { + rc = 0; + break; + } rc = audioreach_speaker_protection_vi(graph, module, cfg); if (!rc) rc = audioreach_module_enable(graph, module, true); @@ -1478,10 +2195,18 @@ EXPORT_SYMBOL_GPL(audioreach_setup_push_pull); int audioreach_shared_memory_send_eos(struct q6apm_graph *graph) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct data_cmd_wr_sh_mem_ep_eos *eos; - int iid = graph->shm_iid; - struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_cmd_pkt(sizeof(*eos), - DATA_CMD_WR_SH_MEM_EP_EOS, 0, graph->port->id, iid); + struct gpr_pkt *pkt __free(kfree) = NULL; + int iid; + + if (!active) + return -ESHUTDOWN; + iid = graph->shm_iid; + pkt = audioreach_alloc_cmd_pkt(sizeof(*eos), + DATA_CMD_WR_SH_MEM_EP_EOS, 0, + graph->port->id, iid); if (IS_ERR(pkt)) return PTR_ERR(pkt); diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 62a2fd79bbcb93..1c641d17102954 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -8,6 +8,7 @@ #include struct q6apm; struct q6apm_graph; +struct audioreach_graph; /* Module IDs */ #define MODULE_ID_WR_SHARED_MEM_EP 0x07001000 @@ -157,6 +158,7 @@ struct apm_module_prop_cfg { } __packed; #define APM_PARAM_ID_MODULE_CONN 0x08001004 +#define APM_PARAM_ID_MODULE_CTRL_LINK_CFG 0x08001061 struct apm_param_id_module_conn { uint32_t num_connections; @@ -821,6 +823,7 @@ struct audioreach_graph_info { uint32_t src_mod_op_port_id; uint32_t dst_mod_inst_id; uint32_t dst_mod_ip_port_id; + bool internal_vmixer_connection; }; struct audioreach_sub_graph { @@ -841,6 +844,9 @@ struct audioreach_container { uint32_t graph_pos; uint32_t stack_size; uint32_t proc_domain; + u32 parent_container_id; + u32 heap_id; + bool has_extended_properties; struct list_head node; uint32_t num_modules; @@ -887,12 +893,25 @@ struct audioreach_module { uint32_t log_code; uint32_t log_tap_point_id; uint32_t log_mode; + bool speaker_protection_bypass; + u32 integrated_backend_id; /* bookkeeping */ struct list_head node; struct audioreach_container *container; struct snd_soc_dapm_widget *widget; struct audioreach_module_priv_data *data; + struct audioreach_module_priv_data *ctrl_link_data; + struct audioreach_module_priv_data *graph_cal_data; + struct audioreach_module_priv_data *render_ep_data; + struct audioreach_module_priv_data *sp_tag_data; + struct audioreach_module_priv_data *spvi_tag_data; + struct audioreach_module_priv_data *vi_ep_data; + struct audioreach_module_priv_data *protection_dynamic_data; + struct audioreach_module_priv_data *volume_gain_data; + struct audioreach_module_priv_data *volume_filter_data; + struct audioreach_module_priv_data *volume_mute_data; + struct audioreach_module_priv_data *channel_mixer_data; }; struct audioreach_module_config { @@ -925,6 +944,15 @@ void *audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t dest_port); void *audioreach_alloc_graph_pkt(struct q6apm *apm, const struct audioreach_graph_info *info); +const struct audioreach_module_priv_data * +audioreach_graph_find_data(const struct audioreach_graph_info *info, u32 type); +const struct audioreach_module * +audioreach_graph_find_module(const struct audioreach_graph_info *info, u32 mid); +int audioreach_graph_protection_profile(const struct audioreach_graph_info *info); +int audioreach_graph_protection_oob_size(const struct audioreach_graph_info *info, + size_t *size); +int audioreach_send_protected_graph_calibration(struct audioreach_graph *graph); +int audioreach_configure_protection(struct q6apm_graph *graph); /* Topology specific */ int audioreach_tplg_init(struct snd_soc_component *component); @@ -933,7 +961,7 @@ void audioreach_graph_free_buf(struct q6apm_graph *graph); int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, const struct gpr_pkt *pkt, uint32_t rsp_opcode); -int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, struct gpr_pkt *pkt, uint32_t rsp_opcode); int audioreach_set_media_format(struct q6apm_graph *graph, const struct audioreach_module *module, diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index bf1f872a09f45a..82b2cd249040f3 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -2,11 +2,13 @@ // Copyright (c) 2021, Linaro Limited #include +#include #include #include #include #include #include +#include #include #include #include @@ -55,6 +57,8 @@ enum stream_state { Q6APM_STREAM_IDLE = 0, Q6APM_STREAM_STOPPED, Q6APM_STREAM_RUNNING, + Q6APM_STREAM_UNCERTAIN, + Q6APM_STREAM_QUARANTINED, }; struct q6apm_dai_rtd { @@ -80,13 +84,66 @@ struct q6apm_dai_rtd { enum stream_state state; struct q6apm_graph *graph; spinlock_t lock; + /* Serialize callback admission against terminal DMA quarantine. */ + spinlock_t callback_lock; + atomic_t callback_users; + wait_queue_head_t callback_wait; + bool callbacks_quarantined; bool notify_on_drain; }; struct q6apm_dai_data { long long sid; + DECLARE_BITMAP(protected_graphs, APM_PORT_MAX); }; +static bool q6apm_denali_rtd(const struct snd_soc_pcm_runtime *rtd) +{ + return rtd && rtd->card && + rtd->card->dev->of_node && + of_device_is_compatible(rtd->card->dev->of_node, + "microsoft,denali-sndcard"); +} + +static bool q6apm_denali_card(struct snd_pcm_substream *substream) +{ + return q6apm_denali_rtd(snd_soc_substream_to_rtd(substream)); +} + +static bool q6apm_denali_protection_runtime(struct snd_pcm_substream *substream, + struct q6apm_graph *graph) +{ + return q6apm_denali_card(substream) && + q6apm_graph_has_protection(graph); +} + +static bool q6apm_denali_protected_graph(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd, + unsigned int graph_id) +{ + return q6apm_denali_rtd(rtd) && graph_id < APM_PORT_MAX && + q6apm_graph_id_has_protection(component->dev, graph_id); +} + +static void q6apm_latch_protected_graph(struct snd_soc_component *component, + unsigned int graph_id) +{ + struct q6apm_dai_data *pdata = snd_soc_component_get_drvdata(component); + + if (pdata && graph_id < APM_PORT_MAX) + set_bit(graph_id, pdata->protected_graphs); +} + +static bool q6apm_protected_graph_latched(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd, + unsigned int graph_id) +{ + struct q6apm_dai_data *pdata = snd_soc_component_get_drvdata(component); + + return pdata && q6apm_denali_rtd(rtd) && graph_id < APM_PORT_MAX && + test_bit(graph_id, pdata->protected_graphs); +} + static const struct snd_pcm_hardware q6apm_dai_hardware_capture = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | @@ -127,10 +184,36 @@ static const struct snd_pcm_hardware q6apm_dai_hardware_playback = { .fifo_size = 0, }; +static bool q6apm_dai_callback_get(struct q6apm_dai_rtd *prtd) +{ + unsigned long flags; + bool acquired = false; + + spin_lock_irqsave(&prtd->callback_lock, flags); + if (!prtd->callbacks_quarantined) { + atomic_inc(&prtd->callback_users); + acquired = true; + } + spin_unlock_irqrestore(&prtd->callback_lock, flags); + + return acquired; +} + +static void q6apm_dai_callback_put(struct q6apm_dai_rtd *prtd) +{ + if (atomic_dec_and_test(&prtd->callback_users)) + wake_up(&prtd->callback_wait); +} + static void event_handler(uint32_t opcode, uint32_t token, void *payload, void *priv) { struct q6apm_dai_rtd *prtd = priv; - struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_substream *substream; + + if (!q6apm_dai_callback_get(prtd)) + return; + + substream = prtd->substream; switch (opcode) { case APM_CLIENT_EVENT_WATERMARK_EVENT: @@ -152,6 +235,8 @@ static void event_handler(uint32_t opcode, uint32_t token, void *payload, void * default: break; } + + q6apm_dai_callback_put(prtd); } static void event_handler_compr(uint32_t opcode, uint32_t token, @@ -219,6 +304,7 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, struct audioreach_module_config cfg; struct device *dev = component->dev; struct q6apm_dai_data *pdata; + bool protected; int ret; pdata = snd_soc_component_get_drvdata(component); @@ -229,6 +315,9 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, dev_err(dev, "%s: private data null or audio client freed\n", __func__); return -EINVAL; } + if (prtd->state == Q6APM_STREAM_QUARANTINED) + return -EIO; + protected = q6apm_denali_protection_runtime(substream, prtd->graph); cfg.direction = substream->stream; cfg.sample_rate = runtime->rate; @@ -238,7 +327,15 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, audioreach_set_default_channel_mapping(cfg.channel_map, runtime->channels); if (prtd->state) { /* clear the previous setup if any */ - q6apm_graph_stop(prtd->graph); + if (!protected || prtd->state == Q6APM_STREAM_RUNNING || + prtd->state == Q6APM_STREAM_UNCERTAIN) { + ret = q6apm_graph_stop(prtd->graph); + if (ret) { + dev_err(dev, "Failed to stop Graph %d\n", ret); + return ret; + } + prtd->state = Q6APM_STREAM_STOPPED; + } q6apm_free_fragments(prtd->graph, substream->stream); } @@ -285,6 +382,14 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, return ret; } + if (protected) { + ret = q6apm_graph_configure_protection(prtd->graph); + if (ret < 0) { + dev_err(dev, "Failed to configure protected graph %d\n", ret); + return ret; + } + } + ret = q6apm_graph_prepare(prtd->graph); if (ret) { dev_err(dev, "Failed to prepare Graph %d\n", ret); @@ -293,6 +398,8 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, ret = q6apm_graph_start(prtd->graph); if (ret) { + if (q6apm_graph_execution_uncertain(prtd->graph)) + prtd->state = Q6APM_STREAM_UNCERTAIN; dev_err(dev, "Failed to Start Graph %d\n", ret); return ret; } @@ -340,8 +447,12 @@ static int q6apm_dai_trigger(struct snd_soc_component *component, { struct snd_pcm_runtime *runtime = substream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; + bool protected; int ret = 0; + protected = q6apm_denali_protection_runtime(substream, prtd->graph); + if (prtd->state == Q6APM_STREAM_QUARANTINED) + return -EIO; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: @@ -349,6 +460,13 @@ static int q6apm_dai_trigger(struct snd_soc_component *component, break; case SNDRV_PCM_TRIGGER_STOP: /* TODO support be handled via SoftPause Module */ + if (protected && + (prtd->state == Q6APM_STREAM_RUNNING || + prtd->state == Q6APM_STREAM_UNCERTAIN)) { + ret = q6apm_graph_stop(prtd->graph); + if (ret) + break; + } prtd->state = Q6APM_STREAM_STOPPED; prtd->queue_ptr = 0; prtd->last_pos_index = 0; @@ -364,6 +482,11 @@ static int q6apm_dai_trigger(struct snd_soc_component *component, return ret; } +static void q6apm_dai_quarantine_buffer(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct q6apm_dai_rtd *prtd, + int reason); + static int q6apm_dai_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) { @@ -373,7 +496,7 @@ static int q6apm_dai_open(struct snd_soc_component *component, struct device *dev = component->dev; struct q6apm_dai_data *pdata; struct q6apm_dai_rtd *prtd; - int graph_id, ret; + int close_ret, graph_id, ret; graph_id = cpu_dai->driver->id; @@ -388,8 +511,13 @@ static int q6apm_dai_open(struct snd_soc_component *component, return -ENOMEM; spin_lock_init(&prtd->lock); + spin_lock_init(&prtd->callback_lock); + atomic_set(&prtd->callback_users, 0); + init_waitqueue_head(&prtd->callback_wait); prtd->substream = substream; - prtd->graph = q6apm_graph_open(dev, event_handler, prtd, graph_id, substream->stream); + prtd->graph = q6apm_graph_open(dev, event_handler, prtd, + graph_id, substream->stream, + q6apm_denali_card(substream)); if (IS_ERR(prtd->graph)) { dev_err(dev, "%s: Could not allocate memory\n", __func__); ret = PTR_ERR(prtd->graph); @@ -447,29 +575,109 @@ static int q6apm_dai_open(struct snd_soc_component *component, return 0; err: + if (!IS_ERR_OR_NULL(prtd->graph)) { + close_ret = q6apm_graph_close(prtd->graph); + if (close_ret) { + q6apm_dai_quarantine_buffer(component, substream, prtd, + close_ret); + return ret; + } + } kfree(prtd); return ret; } +static void q6apm_dai_quarantine_buffer(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct q6apm_dai_rtd *prtd, int reason) +{ + unsigned long flags; + + if (prtd->state == Q6APM_STREAM_QUARANTINED) + return; + + spin_lock_irqsave(&prtd->callback_lock, flags); + prtd->callbacks_quarantined = true; + spin_unlock_irqrestore(&prtd->callback_lock, flags); + wait_event(prtd->callback_wait, + !atomic_read(&prtd->callback_users)); + + q6apm_graph_quarantine_dma(prtd->graph); + snd_pcm_set_runtime_buffer(substream, NULL); + /* DSP ownership is uncertain, so ALSA must never reclaim mapped pages. */ + if (substream->dma_buffer.area && substream->dma_buffer.dev.dev) + get_device(substream->dma_buffer.dev.dev); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + substream->dma_buffer.bytes = 0; + prtd->state = Q6APM_STREAM_QUARANTINED; + dev_err(component->dev, + "retaining protected client and DMA buffer after unconfirmed stop (%d)\n", + reason); +} + +static int q6apm_dai_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct q6apm_dai_rtd *prtd = substream->runtime->private_data; + bool protected; + int ret; + + if (!prtd) + return 0; + protected = q6apm_denali_protection_runtime(substream, prtd->graph); + if (prtd->state == Q6APM_STREAM_QUARANTINED) + return -EIO; + if (prtd->state != Q6APM_STREAM_UNCERTAIN && + (!protected || prtd->state != Q6APM_STREAM_RUNNING)) + return 0; + + ret = q6apm_graph_stop(prtd->graph); + if (ret) { + q6apm_dai_quarantine_buffer(component, substream, prtd, ret); + return ret; + } + + prtd->state = Q6APM_STREAM_STOPPED; + return 0; +} + static int q6apm_dai_close(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; + bool protected; + int close_ret; + int ret = 0; - if (prtd->state) { + protected = q6apm_denali_protection_runtime(substream, prtd->graph); + if (prtd->state == Q6APM_STREAM_QUARANTINED) { + ret = -EIO; + } else if (prtd->state) { /* only stop graph that is started */ - q6apm_graph_stop(prtd->graph); - q6apm_free_fragments(prtd->graph, substream->stream); + if (!protected || prtd->state == Q6APM_STREAM_RUNNING || + prtd->state == Q6APM_STREAM_UNCERTAIN) { + ret = q6apm_graph_stop(prtd->graph); + } + if (!ret) + q6apm_free_fragments(prtd->graph, substream->stream); + } + if (ret && q6apm_graph_execution_uncertain(prtd->graph)) + q6apm_dai_quarantine_buffer(component, substream, prtd, ret); + + close_ret = q6apm_graph_close(prtd->graph); + if (close_ret && prtd->state != Q6APM_STREAM_QUARANTINED) + q6apm_dai_quarantine_buffer(component, substream, prtd, + close_ret); + if (prtd->state != Q6APM_STREAM_QUARANTINED) { + prtd->graph = NULL; + kfree(prtd); } - - q6apm_graph_close(prtd->graph); - prtd->graph = NULL; - kfree(prtd); runtime->private_data = NULL; - return 0; + return ret ?: close_ret; } static snd_pcm_uframes_t q6apm_dai_pointer(struct snd_soc_component *component, @@ -531,7 +739,8 @@ static int q6apm_dai_hw_params(struct snd_soc_component *component, static int q6apm_dai_memory_map(struct snd_soc_component *component, struct snd_pcm_substream *substream, - int graph_id, bool is_push_pull) + int graph_id, bool is_push_pull, + bool protected, bool *retain_buffer) { struct q6apm_dai_data *pdata; struct device *dev = component->dev; @@ -550,8 +759,12 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, phys = substream->dma_buffer.addr | (pdata->sid << 32); ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, BUFFER_BYTES_MAX); - if (ret < 0) - dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret); + if (ret < 0) { + *retain_buffer = protected && + (ret == -ETIMEDOUT || ret == -ESHUTDOWN); + dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret); + return ret; + } if (is_push_pull) { if (pdata->sid < 0) @@ -560,8 +773,15 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, phys = (substream->dma_buffer.addr + BUFFER_BYTES_MAX) | (pdata->sid << 32); ret = q6apm_map_pos_buffer(dev, graph_id, phys, POS_BUFFER_BYTES); - if (ret < 0) - dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret); + if (ret < 0) { + int unmap_ret; + + dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", + ret); + unmap_ret = q6apm_unmap_memory_fixed_region(dev, graph_id); + *retain_buffer = protected && + (ret == -ETIMEDOUT || unmap_ret); + } } else { } @@ -569,6 +789,26 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, return ret; } +static void q6apm_dai_retain_dma_buffer(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + int reason) +{ + if (!substream->dma_buffer.area) + return; + + if (substream->runtime) + snd_pcm_set_runtime_buffer(substream, NULL); + /* Retain until reboot or a future, proven DSP-reset reclamation hook. */ + if (substream->dma_buffer.dev.dev) + get_device(substream->dma_buffer.dev.dev); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + substream->dma_buffer.bytes = 0; + dev_err(component->dev, + "retaining DMA buffer after uncertain DSP mapping (%d)\n", + reason); +} + static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); @@ -581,6 +821,8 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc int size = BUFFER_BYTES_MAX + PAGE_SIZE; int graph_id, ret; bool is_push_pull; + bool protected; + bool retain_buffer = false; struct snd_pcm_substream *substream = NULL; graph_id = cpu_dai->driver->id; @@ -593,6 +835,10 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc if (substream) { + protected = q6apm_denali_protected_graph(component, rtd, + graph_id); + if (protected) + q6apm_latch_protected_graph(component, graph_id); is_push_pull = q6apm_is_graph_in_push_pull_mode_from_id(component->dev, graph_id, substream->stream); @@ -603,34 +849,64 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc if (ret) return ret; - ret = q6apm_dai_memory_map(component, substream, graph_id, is_push_pull); - if (ret) + ret = q6apm_dai_memory_map(component, substream, + graph_id, is_push_pull, + protected, &retain_buffer); + if (ret) { + if (retain_buffer) + q6apm_dai_retain_dma_buffer(component, + substream, ret); return ret; + } } return 0; } -static void q6apm_dai_memory_unmap(struct snd_soc_component *component, - struct snd_pcm_substream *substream) +static int q6apm_dai_memory_unmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *soc_prtd; struct snd_soc_dai *cpu_dai; int graph_id; + int ret; soc_prtd = snd_soc_substream_to_rtd(substream); if (!soc_prtd) - return; + return 0; cpu_dai = snd_soc_rtd_to_cpu(soc_prtd, 0); if (!cpu_dai) - return; + return 0; graph_id = cpu_dai->driver->id; - q6apm_unmap_memory_fixed_region(component->dev, graph_id); + if (q6apm_graph_dma_quarantined(component->dev, graph_id)) + return -EBUSY; + + ret = q6apm_unmap_memory_fixed_region(component->dev, graph_id); + if (ret) + return ret; if (q6apm_is_graph_in_push_pull_mode_from_id(component->dev, graph_id, substream->stream)) - q6apm_unmap_pos_buffer(component->dev, graph_id); + return q6apm_unmap_pos_buffer(component->dev, graph_id); + + return 0; +} + +static bool q6apm_dai_protected_substream(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai; + + if (!rtd) + return false; + cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + if (!cpu_dai) + return false; + + return q6apm_protected_graph_latched(component, rtd, + cpu_dai->driver->id); } static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm) @@ -638,12 +914,20 @@ static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_p struct snd_pcm_substream *substream; substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; - if (substream) - q6apm_dai_memory_unmap(component, substream); + if (substream) { + int ret = q6apm_dai_memory_unmap(component, substream); + + if (ret && q6apm_dai_protected_substream(component, substream)) + q6apm_dai_retain_dma_buffer(component, substream, ret); + } substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; - if (substream) - q6apm_dai_memory_unmap(component, substream); + if (substream) { + int ret = q6apm_dai_memory_unmap(component, substream); + + if (ret && q6apm_dai_protected_substream(component, substream)) + q6apm_dai_retain_dma_buffer(component, substream, ret); + } } static int q6apm_dai_compr_open(struct snd_soc_component *component, @@ -669,7 +953,7 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, prtd->cstream = stream; prtd->graph = q6apm_graph_open(dev, event_handler_compr, prtd, graph_id, - SNDRV_PCM_STREAM_PLAYBACK); + SNDRV_PCM_STREAM_PLAYBACK, false); if (IS_ERR(prtd->graph)) { ret = PTR_ERR(prtd->graph); kfree(prtd); @@ -680,8 +964,14 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, runtime->dma_bytes = BUFFER_BYTES_MAX; size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE * COMPR_PLAYBACK_MAX_NUM_FRAGMENTS; ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, &prtd->dma_buffer); - if (ret) + if (ret) { + if (!q6apm_graph_close(prtd->graph)) { + prtd->graph = NULL; + runtime->private_data = NULL; + kfree(prtd); + } return ret; + } if (pdata->sid < 0) prtd->phys = prtd->dma_buffer.addr; @@ -1009,6 +1299,7 @@ static const struct snd_soc_component_driver q6apm_fe_dai_component = { .pcm_new = q6apm_dai_pcm_new, .pcm_free = q6apm_dai_pcm_free, .hw_params = q6apm_dai_hw_params, + .hw_free = q6apm_dai_hw_free, .pointer = q6apm_dai_pointer, .trigger = q6apm_dai_trigger, .ack = q6apm_dai_ack, @@ -1017,27 +1308,62 @@ static const struct snd_soc_component_driver q6apm_fe_dai_component = { .remove_order = SND_SOC_COMP_ORDER_EARLY, }; +static void q6apm_dai_release_dma_dev(void *data) +{ + struct device *dev = data; + struct q6apm *apm = dev_get_drvdata(dev->parent); + + if (apm) { + mutex_lock(&apm->lock); + if (apm->dma_dev == dev) + apm->dma_dev = NULL; + mutex_unlock(&apm->lock); + } + put_device(dev); +} + static int q6apm_dai_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; struct q6apm_dai_data *pdata; + struct q6apm *apm; struct of_phandle_args args; int rc; + apm = dev_get_drvdata(dev->parent); + if (!apm) + return -EPROBE_DEFER; + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); - if (rc < 0) + if (rc < 0) { pdata->sid = -1; - else + } else { pdata->sid = args.args[0] & SID_MASK_DEFAULT; + of_node_put(args.np); + } dev_set_drvdata(dev, pdata); + rc = 0; + mutex_lock(&apm->lock); + if (apm->dma_dev) + rc = -EBUSY; + else + apm->dma_dev = get_device(dev); + mutex_unlock(&apm->lock); + if (rc) + return rc; + + rc = devm_add_action_or_reset(dev, q6apm_dai_release_dma_dev, dev); + if (rc) + return rc; - return devm_snd_soc_register_component(dev, &q6apm_fe_dai_component, NULL, 0); + return devm_snd_soc_register_component(dev, &q6apm_fe_dai_component, + NULL, 0); } #ifdef CONFIG_OF diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index 006b283484d9e9..45701ac1a7f7e5 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,28 @@ struct q6apm_lpass_dai_data { struct audioreach_module_config module_config[APM_PORT_MAX]; }; +static bool q6apm_lpass_protection_runtime(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + + return rtd && rtd->card && rtd->card->dev->of_node && + of_device_is_compatible(rtd->card->dev->of_node, + "microsoft,denali-sndcard"); +} + +static int q6apm_lpass_graph_id(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + + if (!rtd || !rtd->card || !rtd->card->dev->of_node || + !of_device_is_compatible(rtd->card->dev->of_node, + "microsoft,denali-sndcard")) + return dai->id; + + return q6apm_graph_id_for_backend(dai->dev, dai->id); +} + static int q6dma_set_channel_map(struct snd_soc_dai *dai, unsigned int tx_num, const unsigned int *tx_ch_mask, @@ -148,13 +171,26 @@ static void q6apm_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct if (dai_data->is_port_started[dai->id]) { rc = q6apm_graph_stop(dai_data->graph[dai->id]); - dai_data->is_port_started[dai->id] = false; - if (rc < 0) + if (rc < 0) { + if (rc == -ESHUTDOWN && + !q6apm_graph_close(dai_data->graph[dai->id])) { + dai_data->graph[dai->id] = NULL; + dai_data->is_port_started[dai->id] = false; + return; + } dev_err(dai->dev, "failed to stop APM port (%d)\n", rc); + return; + } + dai_data->is_port_started[dai->id] = false; } if (dai_data->graph[dai->id]) { - q6apm_graph_close(dai_data->graph[dai->id]); + rc = q6apm_graph_close(dai_data->graph[dai->id]); + if (rc) { + dev_err(dai->dev, + "retaining uncertain APM port (%d)\n", rc); + return; + } dai_data->graph[dai->id] = NULL; } } @@ -171,10 +207,24 @@ static int q6apm_lpass_dai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (!dai_data->is_port_started[dai->id]) { ret = q6apm_graph_start(dai_data->graph[dai->id]); - if (ret < 0) + if (ret < 0) { + if (q6apm_graph_execution_uncertain(dai_data->graph[dai->id])) + dai_data->is_port_started[dai->id] = true; dev_err(dai->dev, "Failed to start APM port %d\n", dai->id); - else + } else { dai_data->is_port_started[dai->id] = true; + } + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (dai_data->is_port_started[dai->id] && + q6apm_graph_has_protection(dai_data->graph[dai->id])) { + ret = q6apm_graph_stop(dai_data->graph[dai->id]); + if (ret < 0) + dev_err(dai->dev, "Failed to stop APM port %d\n", + dai->id); + else + dai_data->is_port_started[dai->id] = false; } break; default: @@ -189,13 +239,19 @@ static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct s struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; struct q6apm_graph *graph; - int graph_id = dai->id; + int graph_id = q6apm_lpass_graph_id(substream, dai); int rc; + if (graph_id < 0) + return graph_id; + if (dai_data->is_port_started[dai->id]) { - q6apm_graph_stop(dai_data->graph[dai->id]); + rc = q6apm_graph_stop(dai_data->graph[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "failed to stop APM port (%d)\n", rc); + return rc; + } dai_data->is_port_started[dai->id] = false; - } /** @@ -203,14 +259,18 @@ static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct s * graph, so sequence for playback and capture will be different */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && dai_data->graph[dai->id] == NULL) { - graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id, substream->stream); + graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id, + substream->stream, + q6apm_lpass_protection_runtime(substream)); if (IS_ERR(graph)) { dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id); rc = PTR_ERR(graph); return rc; } - dai_data->graph[graph_id] = graph; + dai_data->graph[dai->id] = graph; } + if (q6apm_graph_has_protection(dai_data->graph[dai->id])) + return 0; cfg->direction = substream->stream; rc = q6apm_graph_media_format_pcm(dai_data->graph[dai->id], cfg); @@ -227,8 +287,11 @@ static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct s return 0; err: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - q6apm_graph_close(dai_data->graph[dai->id]); - dai_data->graph[dai->id] = NULL; + int close_ret; + + close_ret = q6apm_graph_close(dai_data->graph[dai->id]); + if (!close_ret) + dai_data->graph[dai->id] = NULL; } return rc; } @@ -237,15 +300,20 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s { struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); struct q6apm_graph *graph; - int graph_id = dai->id; + int graph_id = q6apm_lpass_graph_id(substream, dai); + + if (graph_id < 0) + return graph_id; - if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id, substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE || graph_id != dai->id) { + graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id, + substream->stream, + q6apm_lpass_protection_runtime(substream)); if (IS_ERR(graph)) { dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id); return PTR_ERR(graph); } - dai_data->graph[graph_id] = graph; + dai_data->graph[dai->id] = graph; } return 0; @@ -316,6 +384,8 @@ static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) cfg.q6dma_ops = &q6dma_ops; cfg.q6hdmi_ops = &q6hdmi_ops; dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); + if (IS_ERR(dais)) + return PTR_ERR(dais); return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais); } diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 641d6d24322990..81fe13a8d25593 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -17,8 +18,14 @@ #include #include "audioreach.h" #include "q6apm.h" +#include "q6dsp-errno.h" /* Graph Management */ +#define APM_MMAP_TOKEN_MAP_TYPE_OOB BIT(17) +#define APM_MMAP_TOKEN_OOB_SEQ_SHIFT 18 +#define APM_MMAP_TOKEN_OOB_SEQ_MASK GENMASK(31, 18) +#define Q6APM_DSP_SID_MASK GENMASK(3, 0) + struct apm_graph_mgmt_cmd { struct apm_module_param_data param_data; uint32_t num_sub_graphs; @@ -28,68 +35,528 @@ struct apm_graph_mgmt_cmd { #define APM_GRAPH_MGMT_PSIZE(p, n) ALIGN(struct_size(p, sub_graph_id_list, n), 8) static struct q6apm *g_apm; +static DEFINE_MUTEX(g_apm_lock); +static atomic_t q6apm_mmap_token_seq = ATOMIC_INIT(0); + +static int audioreach_graph_mgmt_cmd(struct audioreach_graph *graph, + u32 opcode); + +bool q6apm_graph_user_get(struct q6apm_graph *graph) +{ + unsigned long flags; + bool acquired = false; + + if (!graph) + return false; + + spin_lock_irqsave(&graph->lifecycle_lock, flags); + if (!graph->dying && !graph->detached) { + graph->active_users++; + acquired = true; + } + spin_unlock_irqrestore(&graph->lifecycle_lock, flags); + + return acquired; +} + +void q6apm_graph_user_put(struct q6apm_graph *graph) +{ + unsigned long flags; + bool wake = false; + + if (!graph) + return; + + spin_lock_irqsave(&graph->lifecycle_lock, flags); + if (WARN_ON(!graph->active_users)) { + spin_unlock_irqrestore(&graph->lifecycle_lock, flags); + return; + } + wake = --graph->active_users == 0; + spin_unlock_irqrestore(&graph->lifecycle_lock, flags); + + if (wake) + wake_up(&graph->users_wait); +} + +DEFINE_FREE(q6apm_graph_user, struct q6apm_graph *, q6apm_graph_user_put(_T)) + +static void q6apm_graph_abort_cmd(struct q6apm_graph *graph) +{ + spin_lock(&graph->result_lock); + graph->cmd_pending = false; + spin_unlock(&graph->result_lock); + wake_up(&graph->cmd_wait); +} + +static void q6apm_graph_mark_dying(struct q6apm_graph *graph) +{ + unsigned long flags; + + spin_lock_irqsave(&graph->lifecycle_lock, flags); + graph->dying = true; + spin_unlock_irqrestore(&graph->lifecycle_lock, flags); + q6apm_graph_abort_cmd(graph); +} + +static void q6apm_graph_wait_users(struct q6apm_graph *graph) +{ + wait_event(graph->users_wait, !READ_ONCE(graph->active_users)); +} -int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode) { + struct gpr_hdr *hdr = &pkt->hdr; gpr_device_t *gdev = apm->gdev; + u32 result_status; + bool aborted; + int ret; + + mutex_lock(&apm->lock); + if (READ_ONCE(apm->removing)) { + ret = -ESHUTDOWN; + goto unlock; + } + if (!hdr->token) { + do { + hdr->token = ++apm->cmd_token; + } while (!hdr->token); + } + spin_lock(&apm->result_lock); + apm->result.opcode = 0; + apm->result.status = 0; + apm->result_token = U32_MAX; + apm->pending_opcode = hdr->opcode; + apm->pending_rsp_opcode = rsp_opcode; + apm->pending_token = hdr->token; + apm->cmd_pending = true; + spin_unlock(&apm->result_lock); + + ret = gpr_send_pkt(gdev, pkt); + if (ret >= 0) + ret = wait_event_timeout(apm->wait, + !READ_ONCE(apm->cmd_pending) || + (((READ_ONCE(apm->result.opcode) == + hdr->opcode) || + (rsp_opcode && + READ_ONCE(apm->result.opcode) == + rsp_opcode)) && + READ_ONCE(apm->result_token) == + hdr->token), 5 * HZ); + + spin_lock(&apm->result_lock); + aborted = !apm->cmd_pending || apm->removing; + if (!ret && apm->result_token == hdr->token && + (apm->result.opcode == hdr->opcode || + (rsp_opcode && apm->result.opcode == rsp_opcode))) + ret = 1; + apm->cmd_pending = false; + result_status = apm->result.status; + spin_unlock(&apm->result_lock); + + if (aborted) { + ret = -ESHUTDOWN; + goto unlock; + } + if (ret < 0) + goto unlock; + if (!ret) { + dev_err(&gdev->dev, "CMD timeout for [%x] opcode\n", + hdr->opcode); + ret = -ETIMEDOUT; + } else if (result_status > 0) { + dev_err(&gdev->dev, "DSP returned error[%x] %x\n", + hdr->opcode, result_status); + ret = result_status == ADSP_EUNSUPPORTED ? + -EOPNOTSUPP : -EINVAL; + } else { + ret = 0; + } + +unlock: + mutex_unlock(&apm->lock); + return ret; +} + +static phys_addr_t q6apm_dsp_addr(struct device *dev, dma_addr_t dma_addr) +{ + struct of_phandle_args args; + phys_addr_t dsp_addr = dma_addr; + + if (!dev->of_node || + of_parse_phandle_with_fixed_args(dev->of_node, "iommus", 1, 0, + &args)) + return dsp_addr; + + dsp_addr |= (phys_addr_t)(args.args[0] & Q6APM_DSP_SID_MASK) << 32; + of_node_put(args.np); + + return dsp_addr; +} + +static u32 q6apm_next_mmap_token(u32 graph_id, u32 map_type) +{ + u32 sequence; + + sequence = (u32)atomic_inc_return(&q6apm_mmap_token_seq) << + APM_MMAP_TOKEN_OOB_SEQ_SHIFT; + + return (graph_id & APM_MMAP_TOKEN_GID_MASK) | map_type | + (sequence & APM_MMAP_TOKEN_OOB_SEQ_MASK); +} + +static int q6apm_map_oob_buffer(struct audioreach_graph *graph, size_t data_size) +{ + struct apm_shared_map_region_payload *region; + struct apm_cmd_shared_mem_map_regions *cmd; + struct gpr_pkt *pkt __free(kfree) = NULL; + u32 opcode = APM_CMD_SHARED_MEM_MAP_REGIONS; + int payload_size = sizeof(*cmd) + sizeof(*region); + bool map_uncertain; + bool mapped; + void *payload; + int ret; + + if (!graph->dma_dev) + return -ENODEV; + if (!data_size || data_size > U32_MAX - (PAGE_SIZE - 1)) + return -E2BIG; + graph->oob_token = q6apm_next_mmap_token(graph->id, + APM_MMAP_TOKEN_MAP_TYPE_OOB); + + graph->oob_size = PAGE_ALIGN(data_size); + graph->oob_virt = dma_alloc_coherent(graph->dma_dev, graph->oob_size, + &graph->oob_dma, GFP_KERNEL); + if (!graph->oob_virt) + return -ENOMEM; + graph->oob_dsp_addr = q6apm_dsp_addr(graph->dma_dev, graph->oob_dma); + + pkt = audioreach_alloc_apm_cmd_pkt(payload_size, opcode, graph->oob_token); + if (IS_ERR(pkt)) { + ret = PTR_ERR(pkt); + goto free_buffer; + } + + payload = (u8 *)pkt + GPR_HDR_SIZE; + cmd = payload; + cmd->mem_pool_id = APM_MEMORY_MAP_SHMEM8_4K_POOL; + cmd->num_regions = 1; + cmd->property_flag = 0; + region = payload + sizeof(*cmd); + region->shm_addr_lsw = lower_32_bits(graph->oob_dsp_addr); + region->shm_addr_msw = upper_32_bits(graph->oob_dsp_addr); + region->mem_size_bytes = graph->oob_size; + + graph->oob_map_uncertain = true; + ret = q6apm_send_cmd_sync(graph->apm, pkt, + APM_CMD_RSP_SHARED_MEM_MAP_REGIONS); + spin_lock(&graph->apm->graph_lock); + mapped = graph->oob_mem_map_handle; + map_uncertain = graph->oob_map_uncertain; + spin_unlock(&graph->apm->graph_lock); + if (mapped) + return 0; + if (!map_uncertain) { + if (!ret) + ret = -EIO; + goto free_buffer; + } + if (!ret) + ret = -EIO; + + /* + * A timeout does not prove that the DSP failed to map the buffer. Keep + * it allocated; teardown retains it unless a correlated response makes + * the mapping state certain. + */ + return ret; + +free_buffer: + dma_free_coherent(graph->dma_dev, graph->oob_size, graph->oob_virt, + graph->oob_dma); + graph->oob_virt = NULL; + graph->oob_dma = 0; + graph->oob_dsp_addr = 0; + graph->oob_size = 0; + return ret; +} + +static int q6apm_unmap_oob_buffer(struct audioreach_graph *graph) +{ + struct apm_cmd_shared_mem_unmap_regions *cmd; + u32 opcode = APM_CMD_SHARED_MEM_UNMAP_REGIONS; + u32 mem_map_handle; + bool map_uncertain; + int ret = 0; + + mutex_lock(&graph->oob_lock); + if (graph->oob_transfer_uncertain) { + ret = -EAGAIN; + goto unlock; + } + + spin_lock(&graph->apm->graph_lock); + map_uncertain = graph->oob_map_uncertain; + if (map_uncertain) + mem_map_handle = 0; + else + mem_map_handle = graph->oob_mem_map_handle; + spin_unlock(&graph->apm->graph_lock); + if (map_uncertain) { + ret = -EAGAIN; + goto unlock; + } + + if (mem_map_handle) { + struct gpr_pkt *pkt __free(kfree) = NULL; + + pkt = audioreach_alloc_apm_cmd_pkt(sizeof(*cmd), opcode, graph->oob_token); + + if (IS_ERR(pkt)) { + ret = PTR_ERR(pkt); + } else { + cmd = (void *)pkt + GPR_HDR_SIZE; + cmd->mem_map_handle = mem_map_handle; + ret = q6apm_send_cmd_sync(graph->apm, pkt, opcode); + } + if (ret) + goto unlock; + spin_lock(&graph->apm->graph_lock); + mem_map_handle = graph->oob_mem_map_handle; + spin_unlock(&graph->apm->graph_lock); + if (mem_map_handle) { + ret = -EIO; + goto unlock; + } + } + + if (graph->oob_virt) { + dma_free_coherent(graph->dma_dev, graph->oob_size, + graph->oob_virt, graph->oob_dma); + graph->oob_virt = NULL; + graph->oob_dma = 0; + graph->oob_dsp_addr = 0; + graph->oob_size = 0; + } + +unlock: + mutex_unlock(&graph->oob_lock); + return ret; +} + +static int __q6apm_send_oob_config(struct audioreach_graph *graph, + struct q6apm_graph *client, + const void *data, size_t size) +{ + struct apm_cmd_header *cmd; + struct gpr_pkt *pkt; + int ret; + + if (!data || !size || size > graph->oob_size || + !graph->oob_virt || !graph->oob_mem_map_handle) + return -EINVAL; + + mutex_lock(&graph->oob_lock); + if (graph->oob_transfer_uncertain) { + ret = -EIO; + goto unlock; + } + memset(graph->oob_virt, 0, graph->oob_size); + memcpy(graph->oob_virt, data, size); + dma_wmb(); + + if (client) + pkt = audioreach_alloc_cmd_pkt(0, APM_CMD_SET_CFG, 0, + client->port->id, + APM_MODULE_INSTANCE_ID); + else + pkt = audioreach_alloc_apm_cmd_pkt(0, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) { + ret = PTR_ERR(pkt); + goto unlock; + } + + cmd = (void *)pkt + GPR_HDR_SIZE; + cmd->payload_address_lsw = lower_32_bits(graph->oob_dsp_addr); + cmd->payload_address_msw = upper_32_bits(graph->oob_dsp_addr); + cmd->mem_map_handle = graph->oob_mem_map_handle; + cmd->payload_size = size; + + if (client) + ret = audioreach_graph_send_cmd_sync(client, pkt, 0); + else + ret = q6apm_send_cmd_sync(graph->apm, pkt, 0); + kfree(pkt); + if (ret == -ETIMEDOUT) + graph->oob_transfer_uncertain = true; - return audioreach_send_cmd_sync(&gdev->dev, gdev, &apm->result, &apm->lock, - NULL, &apm->wait, pkt, rsp_opcode); +unlock: + mutex_unlock(&graph->oob_lock); + return ret; +} + +int q6apm_send_oob_config(struct audioreach_graph *graph, + const void *data, size_t size) +{ + return __q6apm_send_oob_config(graph, NULL, data, size); } -static struct audioreach_graph *q6apm_get_audioreach_graph(struct q6apm *apm, uint32_t graph_id) +int q6apm_send_graph_oob_config(struct q6apm_graph *graph, + const void *data, size_t size) +{ + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + + if (!active) + return -ESHUTDOWN; + if (!graph->ar_graph) + return -ENODEV; + + return __q6apm_send_oob_config(graph->ar_graph, graph, data, size); +} + +static struct audioreach_graph * +q6apm_get_audioreach_graph(struct q6apm *apm, u32 graph_id, + bool enable_protection) { struct audioreach_graph_info *info; struct audioreach_graph *graph; + struct device *dma_dev; + bool graph_faulted; + bool mode_mismatch; + size_t oob_size; int id; mutex_lock(&apm->lock); + spin_lock(&apm->graph_lock); graph = idr_find(&apm->graph_idr, graph_id); + graph_faulted = graph && + (READ_ONCE(graph->protection_faulted) || + (enable_protection && READ_ONCE(graph->protection_malformed))); + mode_mismatch = graph && !graph->initializing && + ((enable_protection && graph->protection_profile) != + graph->protection_runtime); + if (graph && (graph->initializing || + graph_faulted || + mode_mismatch || + !kref_get_unless_zero(&graph->refcount))) { + spin_unlock(&apm->graph_lock); + mutex_unlock(&apm->lock); + return ERR_PTR(mode_mismatch ? -EBUSY : + graph_faulted ? -EIO : -EBUSY); + } + spin_unlock(&apm->graph_lock); + info = idr_find(&apm->graph_info_idr, graph_id); + dma_dev = !graph && info ? get_device(apm->dma_dev) : NULL; mutex_unlock(&apm->lock); - if (graph) { - kref_get(&graph->refcount); + if (graph) return graph; - } - - info = idr_find(&apm->graph_info_idr, graph_id); if (!info) return ERR_PTR(-ENODEV); graph = kzalloc_obj(*graph); - if (!graph) + if (!graph) { + put_device(dma_dev); return ERR_PTR(-ENOMEM); + } graph->apm = apm; graph->info = info; graph->id = graph_id; + graph->dma_dev = dma_dev; + graph->initializing = true; + mutex_init(&graph->oob_lock); + mutex_init(&graph->protection_lock); graph->graph = audioreach_alloc_graph_pkt(apm, info); if (IS_ERR(graph->graph)) { void *err = graph->graph; + put_device(graph->dma_dev); kfree(graph); return ERR_CAST(err); } + kref_init(&graph->refcount); mutex_lock(&apm->lock); - id = idr_alloc(&apm->graph_idr, graph, graph_id, graph_id + 1, GFP_KERNEL); + spin_lock(&apm->graph_lock); + id = idr_alloc(&apm->graph_idr, graph, graph_id, graph_id + 1, + GFP_ATOMIC); + spin_unlock(&apm->graph_lock); if (id < 0) { dev_err(apm->dev, "Unable to allocate graph id (%d)\n", graph_id); kfree(graph->graph); + put_device(graph->dma_dev); kfree(graph); mutex_unlock(&apm->lock); return ERR_PTR(id); } mutex_unlock(&apm->lock); - kref_init(&graph->refcount); + id = q6apm_send_cmd_sync(apm, graph->graph, 0); + if (id == -ETIMEDOUT) { + dev_err(apm->dev, + "retaining graph %u after uncertain GRAPH_OPEN\n", + graph_id); + return ERR_PTR(id); + } + if (id) + goto remove_graph; - q6apm_send_cmd_sync(apm, graph->graph, 0); + id = audioreach_graph_protection_oob_size(info, &oob_size); + if (id < 0) { + graph->protection_profile = true; + graph->protection_malformed = true; + if (enable_protection) { + graph->protection_runtime = true; + graph->protection_faulted = true; + dev_warn(apm->dev, + "protected topology is incomplete (%d); refusing runtime\n", + id); + } + goto graph_ready; + } + if (oob_size) { + graph->protection_profile = true; + if (!enable_protection) + goto graph_ready; + graph->protection_runtime = true; + id = q6apm_map_oob_buffer(graph, oob_size); + if (id) { + dev_warn(apm->dev, + "protected OOB map failed (%d); using bypass\n", id); + goto graph_ready; + } + id = audioreach_send_protected_graph_calibration(graph); + if (id) { + dev_warn(apm->dev, + "protected graph calibration failed (%d); using bypass\n", + id); + q6apm_unmap_oob_buffer(graph); + goto graph_ready; + } + graph->protection_available = true; + } +graph_ready: + spin_lock(&apm->graph_lock); + graph->initializing = false; + spin_unlock(&apm->graph_lock); return graph; + +remove_graph: + mutex_lock(&apm->lock); + spin_lock(&apm->graph_lock); + idr_remove(&apm->graph_idr, graph->id); + spin_unlock(&apm->graph_lock); + mutex_unlock(&apm->lock); + kfree(graph->graph); + put_device(graph->dma_dev); + kfree(graph); + return ERR_PTR(id); } static int audioreach_graph_mgmt_cmd(struct audioreach_graph *graph, uint32_t opcode) @@ -121,21 +588,81 @@ static int audioreach_graph_mgmt_cmd(struct audioreach_graph *graph, uint32_t op return q6apm_send_cmd_sync(apm, pkt, 0); } +static int audioreach_graph_client_mgmt_cmd(struct q6apm_graph *graph, + u32 opcode) +{ + struct audioreach_graph_info *info = graph->info; + int num_sub_graphs = info->num_sub_graphs; + struct apm_module_param_data *param_data; + struct apm_graph_mgmt_cmd *mgmt_cmd; + struct gpr_pkt *pkt __free(kfree) = NULL; + struct audioreach_sub_graph *sg; + int payload_size; + int i = 0; + u32 dest = APM_MODULE_INSTANCE_ID; + + payload_size = APM_GRAPH_MGMT_PSIZE(mgmt_cmd, num_sub_graphs); + pkt = audioreach_alloc_cmd_pkt(payload_size, opcode, 0, graph->port->id, dest); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + mgmt_cmd = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + mgmt_cmd->num_sub_graphs = num_sub_graphs; + param_data = &mgmt_cmd->param_data; + param_data->module_instance_id = APM_MODULE_INSTANCE_ID; + param_data->param_id = APM_PARAM_ID_SUB_GRAPH_LIST; + param_data->param_size = payload_size - APM_MODULE_PARAM_DATA_SIZE; + + /* FullIO emits the graph's required run-state order in this list. */ + list_for_each_entry(sg, &info->sg_list, node) + mgmt_cmd->sub_graph_id_list[i++] = sg->sub_graph_id; + + return audioreach_graph_send_cmd_sync(graph, pkt, 0); +} + static void q6apm_put_audioreach_graph(struct kref *ref) { struct audioreach_graph *graph; struct q6apm *apm; + int close_ret; + int unmap_ret = 0; graph = container_of(ref, struct audioreach_graph, refcount); apm = graph->apm; - audioreach_graph_mgmt_cmd(graph, APM_CMD_GRAPH_CLOSE); + /* Stop all DSP users before releasing their OOB mapping. */ + close_ret = graph->close_confirmed ? 0 : + audioreach_graph_mgmt_cmd(graph, APM_CMD_GRAPH_CLOSE); + if (close_ret && graph->protection_runtime) { + /* Keep the ID reserved until reboot or a proven reset hook. */ + spin_lock(&apm->graph_lock); + graph->initializing = true; + kref_init(&graph->refcount); + spin_unlock(&apm->graph_lock); + dev_err(apm->dev, + "retaining graph %u after uncertain GRAPH_CLOSE (%d)\n", + graph->id, close_ret); + return; + } + + mutex_lock(&graph->oob_lock); + graph->oob_transfer_uncertain = false; + mutex_unlock(&graph->oob_lock); + unmap_ret = q6apm_unmap_oob_buffer(graph); mutex_lock(&apm->lock); + spin_lock(&apm->graph_lock); graph = idr_remove(&apm->graph_idr, graph->id); + spin_unlock(&apm->graph_lock); mutex_unlock(&apm->lock); kfree(graph->graph); + if (graph->oob_virt) + dev_err(apm->dev, + "retaining protected OOB DMA buffer after teardown failure (close %d, unmap %d)\n", + close_ret, unmap_ret); + else + put_device(graph->dma_dev); kfree(graph); } @@ -154,10 +681,14 @@ static int q6apm_get_apm_state(struct q6apm *apm) bool q6apm_is_adsp_ready(void) { + bool ready = false; + + mutex_lock(&g_apm_lock); if (g_apm) - return q6apm_get_apm_state(g_apm); + ready = q6apm_get_apm_state(g_apm); + mutex_unlock(&g_apm_lock); - return false; + return ready; } EXPORT_SYMBOL_GPL(q6apm_is_adsp_ready); @@ -184,16 +715,28 @@ static struct audioreach_module *__q6apm_find_module_by_mid(struct q6apm *apm, int q6apm_graph_media_format_shmem(struct q6apm_graph *graph, struct audioreach_module_config *cfg) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct audioreach_module *module; + if (!active) + return -ESHUTDOWN; if (cfg->direction == SNDRV_PCM_STREAM_CAPTURE) { - module = q6apm_find_module_by_mid(graph, MODULE_ID_SH_MEM_PUSH_MODE); + module = __q6apm_find_module_by_mid(graph->apm, + graph->info, + MODULE_ID_SH_MEM_PUSH_MODE); if (!module) - module = q6apm_find_module_by_mid(graph, MODULE_ID_RD_SHARED_MEM_EP); + module = __q6apm_find_module_by_mid(graph->apm, + graph->info, + MODULE_ID_RD_SHARED_MEM_EP); } else { - module = q6apm_find_module_by_mid(graph, MODULE_ID_SH_MEM_PULL_MODE); + module = __q6apm_find_module_by_mid(graph->apm, + graph->info, + MODULE_ID_SH_MEM_PULL_MODE); if (!module) - module = q6apm_find_module_by_mid(graph, MODULE_ID_WR_SHARED_MEM_EP); + module = __q6apm_find_module_by_mid(graph->apm, + graph->info, + MODULE_ID_WR_SHARED_MEM_EP); } if (!module) { @@ -216,9 +759,15 @@ static int __q6apm_map_memory_fixed_region(struct device *dev, unsigned int grap uint32_t buf_sz; void *p; uint32_t pos_mask = is_pos_buf ? APM_MMAP_TOKEN_MAP_TYPE_POS_BUF : 0; + u32 token = q6apm_next_mmap_token(graph_id, pos_mask); struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, - APM_CMD_SHARED_MEM_MAP_REGIONS, (graph_id | pos_mask)); + APM_CMD_SHARED_MEM_MAP_REGIONS, token); + if (!apm) + return -ENODEV; + guard(mutex)(&apm->client_lock); + if (apm->removing) + return -ESHUTDOWN; if (IS_ERR(pkt)) return PTR_ERR(pkt); @@ -271,10 +820,14 @@ EXPORT_SYMBOL_GPL(q6apm_map_memory_fixed_region); int q6apm_alloc_fragments(struct q6apm_graph *graph, unsigned int dir, phys_addr_t phys, size_t period_sz, unsigned int periods) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct audioreach_graph_data *data; struct audio_buffer *buf; int cnt; + if (!active) + return -ESHUTDOWN; if (dir == SNDRV_PCM_STREAM_PLAYBACK) data = &graph->rx_data; else @@ -326,8 +879,15 @@ static int __q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int gr struct q6apm *apm = dev_get_drvdata(dev->parent); struct audioreach_graph_info *info; uint32_t mem_map_handle; + u32 map_type = is_pos_buf ? APM_MMAP_TOKEN_MAP_TYPE_POS_BUF : 0; + u32 token = q6apm_next_mmap_token(graph_id, map_type); struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(sizeof(*cmd), - APM_CMD_SHARED_MEM_UNMAP_REGIONS, graph_id); + APM_CMD_SHARED_MEM_UNMAP_REGIONS, token); + if (!apm) + return -ENODEV; + guard(mutex)(&apm->client_lock); + if (apm->removing) + return -ESHUTDOWN; if (IS_ERR(pkt)) return PTR_ERR(pkt); @@ -366,6 +926,11 @@ EXPORT_SYMBOL_GPL(q6apm_unmap_pos_buffer); int q6apm_free_fragments(struct q6apm_graph *graph, unsigned int dir) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + + if (!active) + return -ESHUTDOWN; audioreach_graph_free_buf(graph); return 0; @@ -374,9 +939,14 @@ EXPORT_SYMBOL_GPL(q6apm_free_fragments); int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct audioreach_module *module; - module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!active) + return -ESHUTDOWN; + module = __q6apm_find_module_by_mid(graph->apm, graph->info, + MODULE_ID_PLACEHOLDER_DECODER); if (!module) return -ENODEV; @@ -386,9 +956,14 @@ EXPORT_SYMBOL_GPL(q6apm_remove_initial_silence); int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct audioreach_module *module; - module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!active) + return -ESHUTDOWN; + module = __q6apm_find_module_by_mid(graph->apm, graph->info, + MODULE_ID_PLACEHOLDER_DECODER); if (!module) return -ENODEV; @@ -398,9 +973,14 @@ EXPORT_SYMBOL_GPL(q6apm_remove_trailing_silence); int q6apm_enable_compress_module(struct device *dev, struct q6apm_graph *graph, bool en) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct audioreach_module *module; - module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!active) + return -ESHUTDOWN; + module = __q6apm_find_module_by_mid(graph->apm, graph->info, + MODULE_ID_PLACEHOLDER_DECODER); if (!module) return -ENODEV; @@ -411,10 +991,15 @@ EXPORT_SYMBOL_GPL(q6apm_enable_compress_module); int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, uint32_t codec_id) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct audioreach_module *module; uint32_t module_id; - module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!active) + return -ESHUTDOWN; + module = __q6apm_find_module_by_mid(graph->apm, graph->info, + MODULE_ID_PLACEHOLDER_DECODER); if (!module) return -ENODEV; @@ -440,14 +1025,242 @@ int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, } EXPORT_SYMBOL_GPL(q6apm_set_real_module_id); +bool q6apm_graph_has_protection(const struct q6apm_graph *graph) +{ + return graph && READ_ONCE(graph->protection_runtime); +} +EXPORT_SYMBOL_GPL(q6apm_graph_has_protection); + +int q6apm_graph_configure_protection(struct q6apm_graph *graph) +{ + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + + if (!active) + return -ESHUTDOWN; + if (!q6apm_graph_has_protection(graph)) + return 0; + + return audioreach_configure_protection(graph); +} +EXPORT_SYMBOL_GPL(q6apm_graph_configure_protection); + +static int __q6apm_graph_id_for_backend(struct q6apm *apm, int backend_id) +{ + struct audioreach_graph_info *info; + struct audioreach_container *container; + struct audioreach_sub_graph *sg; + struct audioreach_module *module; + int graph_id = -ENOENT; + int profile; + int id; + + mutex_lock(&apm->lock); + idr_for_each_entry(&apm->graph_info_idr, info, id) { + profile = audioreach_graph_protection_profile(info); + if (profile <= 0) + continue; + list_for_each_entry(sg, &info->sg_list, node) { + list_for_each_entry(container, &sg->container_list, node) { + list_for_each_entry(module, + &container->modules_list, node) { + if (!module->integrated_backend_id || + module->integrated_backend_id != backend_id) + continue; + if (graph_id >= 0 && graph_id != info->id) { + graph_id = -EEXIST; + goto unlock; + } + graph_id = info->id; + } + } + } + } + +unlock: + mutex_unlock(&apm->lock); + return graph_id == -ENOENT ? backend_id : graph_id; +} + +int q6apm_graph_id_for_backend(struct device *dev, int backend_id) +{ + struct q6apm *apm = dev_get_drvdata(dev->parent); + + if (!apm) + return -ENODEV; + guard(mutex)(&apm->client_lock); + if (apm->removing) + return -ESHUTDOWN; + + return __q6apm_graph_id_for_backend(apm, backend_id); +} +EXPORT_SYMBOL_GPL(q6apm_graph_id_for_backend); + +int q6apm_set_protection_backend_ready(struct device *dev, int backend_id, + enum q6apm_protection_backend backend, + bool ready) +{ + struct q6apm *apm = dev_get_drvdata(dev->parent); + struct audioreach_graph *graph; + bool *backend_ready; + int graph_id; + int ret = 0; + + if (!apm) + return -ENODEV; + guard(mutex)(&apm->client_lock); + if (apm->removing) + return -ESHUTDOWN; + + graph_id = __q6apm_graph_id_for_backend(apm, backend_id); + if (graph_id < 0) + return graph_id; + + mutex_lock(&apm->lock); + spin_lock(&apm->graph_lock); + graph = idr_find(&apm->graph_idr, graph_id); + if (graph && (graph->initializing || + !kref_get_unless_zero(&graph->refcount))) + graph = NULL; + spin_unlock(&apm->graph_lock); + mutex_unlock(&apm->lock); + if (!graph) + return -ENODEV; + if (!graph->protection_profile) { + kref_put(&graph->refcount, q6apm_put_audioreach_graph); + return -ENODEV; + } + + mutex_lock(&graph->protection_lock); + switch (backend) { + case Q6APM_PROTECTION_BACKEND_VI: + backend_ready = &graph->protection_vi_ready; + break; + case Q6APM_PROTECTION_BACKEND_CPS: + backend_ready = &graph->protection_cps_ready; + break; + default: + ret = -EINVAL; + goto unlock; + } + if (*backend_ready == ready) + goto unlock; + if (graph->start_count > 0) { + ret = -EBUSY; + goto unlock; + } + if (ready && graph->protection_faulted) { + ret = -EIO; + goto unlock; + } + /* + * Keep ready=false available after a fault once execution has stopped so + * backend teardown can proceed. + */ + *backend_ready = ready; + graph->protection_configured = false; + graph->protection_bypass_confirmed = false; + +unlock: + mutex_unlock(&graph->protection_lock); + kref_put(&graph->refcount, q6apm_put_audioreach_graph); + + return ret; +} +EXPORT_SYMBOL_GPL(q6apm_set_protection_backend_ready); + +static const struct audioreach_module * +q6apm_find_module_by_iid(const struct audioreach_graph_info *info, u32 iid) +{ + struct audioreach_container *container; + struct audioreach_sub_graph *sg; + struct audioreach_module *module; + + list_for_each_entry(sg, &info->sg_list, node) + list_for_each_entry(container, &sg->container_list, node) + list_for_each_entry(module, &container->modules_list, node) + if (module->instance_id == iid) + return module; + + return NULL; +} + +static unsigned int +q6apm_graph_module_count(const struct audioreach_graph_info *info) +{ + struct audioreach_container *container; + struct audioreach_sub_graph *sg; + unsigned int count = 0; + + list_for_each_entry(sg, &info->sg_list, node) + list_for_each_entry(container, &sg->container_list, node) + count += container->num_modules; + + return count; +} + +static int q6apm_protected_media_format_pcm(struct q6apm_graph *graph, + struct audioreach_module_config *cfg) +{ + const struct audioreach_module *source; + const struct audioreach_module *module; + unsigned int limit, step; + bool pcm_configured = false; + int ret; + + if (cfg->direction != SNDRV_PCM_STREAM_PLAYBACK || + cfg->sample_rate != 48000 || cfg->bit_width != 16 || + cfg->num_channels != 2) + return -EINVAL; + + source = audioreach_graph_find_module(graph->info, + MODULE_ID_SH_MEM_PULL_MODE); + if (IS_ERR_OR_NULL(source)) + return source ? PTR_ERR(source) : -ENODEV; + + module = source; + limit = q6apm_graph_module_count(graph->info); + for (step = 0; step < limit; step++) { + if (module->module_id == MODULE_ID_PCM_CNV) { + if (pcm_configured) + return -EEXIST; + ret = audioreach_set_media_format(graph, module, cfg); + if (ret) + return ret; + pcm_configured = true; + } else if (module->module_id == MODULE_ID_MFC) { + if (!pcm_configured) + return -EINVAL; + return audioreach_set_media_format(graph, module, cfg); + } + + if (module->num_connections != 1) + return -EINVAL; + module = q6apm_find_module_by_iid(graph->info, + module->dst_mod_inst_id[0]); + if (!module) + return -ENODEV; + } + + return -ELOOP; +} + int q6apm_graph_media_format_pcm(struct q6apm_graph *graph, struct audioreach_module_config *cfg) { - struct audioreach_graph_info *info = graph->info; + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + struct audioreach_graph_info *info; struct audioreach_sub_graph *sgs; struct audioreach_container *container; struct audioreach_module *module; int ret; + if (!active) + return -ESHUTDOWN; + info = graph->info; + if (q6apm_graph_has_protection(graph)) + return q6apm_protected_media_format_pcm(graph, cfg); + list_for_each_entry(sgs, &info->sg_list, node) { list_for_each_entry(container, &sgs->container_list, node) { list_for_each_entry(module, &container->modules_list, node) { @@ -472,11 +1285,18 @@ EXPORT_SYMBOL_GPL(q6apm_graph_media_format_pcm); int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, uint32_t lsw_ts, uint32_t wflags) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct apm_data_cmd_wr_sh_mem_ep_data_buffer_v2 *write_buffer; struct audio_buffer *ab; + int ret; + + struct gpr_pkt *pkt __free(kfree) = NULL; - struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_pkt(sizeof(*write_buffer), - DATA_CMD_WR_SH_MEM_EP_DATA_BUFFER_V2, + if (!active) + return -ESHUTDOWN; + pkt = audioreach_alloc_pkt(sizeof(*write_buffer), + DATA_CMD_WR_SH_MEM_EP_DATA_BUFFER_V2, graph->rx_data.dsp_buf | (len << APM_WRITE_TOKEN_LEN_SHIFT), graph->port->id, graph->shm_iid); if (IS_ERR(pkt)) @@ -502,18 +1322,26 @@ int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, mutex_unlock(&graph->lock); - return gpr_send_port_pkt(graph->port, pkt); + ret = gpr_send_port_pkt(graph->port, pkt); + return ret; } EXPORT_SYMBOL_GPL(q6apm_write_async); int q6apm_read(struct q6apm_graph *graph) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct data_cmd_rd_sh_mem_ep_data_buffer_v2 *read_buffer; struct audioreach_graph_data *port; struct audio_buffer *ab; + int ret; - struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_pkt(sizeof(*read_buffer), - DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER_V2, + struct gpr_pkt *pkt __free(kfree) = NULL; + + if (!active) + return -ESHUTDOWN; + pkt = audioreach_alloc_pkt(sizeof(*read_buffer), + DATA_CMD_RD_SH_MEM_EP_DATA_BUFFER_V2, graph->tx_data.dsp_buf, graph->port->id, graph->shm_iid); if (IS_ERR(pkt)) return PTR_ERR(pkt); @@ -536,14 +1364,19 @@ int q6apm_read(struct q6apm_graph *graph) mutex_unlock(&graph->lock); - return gpr_send_port_pkt(graph->port, pkt); + ret = gpr_send_port_pkt(graph->port, pkt); + return ret; } EXPORT_SYMBOL_GPL(q6apm_read); int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; struct audioreach_graph_data *data; + if (!active) + return 0; if (dir == SNDRV_PCM_STREAM_PLAYBACK) data = &graph->rx_data; else @@ -553,6 +1386,32 @@ int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir) } EXPORT_SYMBOL_GPL(q6apm_get_hw_pointer); +static bool q6apm_graph_try_complete_cmd(struct q6apm_graph *graph, + const struct gpr_hdr *hdr, u32 opcode, + u32 status, bool basic_response) +{ + bool expected; + + spin_lock(&graph->result_lock); + expected = graph->cmd_pending && graph->pending_token == hdr->token && + (graph->pending_opcode == opcode || + graph->pending_rsp_opcode == opcode); + if (expected && basic_response && !status && + graph->pending_rsp_opcode && graph->pending_rsp_opcode != opcode) + expected = false; + if (expected) { + graph->result.status = status; + graph->result_token = hdr->token; + graph->result.opcode = opcode; + } + spin_unlock(&graph->result_lock); + + if (expected) + wake_up(&graph->cmd_wait); + + return expected; +} + static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) { struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done_v2 *rd_done; @@ -561,11 +1420,16 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) const struct gpr_ibasic_rsp_result_t *result; struct q6apm_graph *graph = priv; const struct gpr_hdr *hdr = &data->hdr; - struct device *dev = graph->dev; + struct device *dev; uint32_t client_event; + bool expected; phys_addr_t phys; int token; + if (!q6apm_graph_user_get(graph)) + return 0; + + dev = graph->dev; result = data->payload; switch (hdr->opcode) { @@ -597,8 +1461,6 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) atomic_set(&graph->rx_data.hw_ptr, token + 1); if (lower_32_bits(phys) == done->buf_addr_lsw && upper_32_bits(phys) == done->buf_addr_msw) { - graph->result.opcode = hdr->opcode; - graph->result.status = done->status; if (graph->cb) graph->cb(client_event, hdr->token, data->payload, graph->priv); } else { @@ -624,8 +1486,6 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) if (upper_32_bits(phys) == rd_done->buf_addr_msw && lower_32_bits(phys) == rd_done->buf_addr_lsw) { - graph->result.opcode = hdr->opcode; - graph->result.status = rd_done->status; if (graph->cb) graph->cb(client_event, hdr->token, data->payload, graph->priv); } else { @@ -644,12 +1504,15 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) case DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT: case APM_CMD_REGISTER_MODULE_EVENTS: case APM_CMD_SET_CFG: - graph->result.opcode = result->opcode; - graph->result.status = result->status; - if (result->status) + case APM_CMD_GRAPH_START: + case APM_CMD_GRAPH_STOP: + case APM_CMD_GRAPH_FLUSH: + expected = q6apm_graph_try_complete_cmd(graph, hdr, + result->opcode, + result->status, true); + if (expected && result->status) dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status, result->opcode); - wake_up(&graph->cmd_wait); break; default: break; @@ -658,12 +1521,18 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) default: break; } + q6apm_graph_user_put(graph); return 0; } int q6apm_register_watermark_event(struct q6apm_graph *graph, int water_mark_level_bytes, int num_levels) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + + if (!active) + return -ESHUTDOWN; return audioreach_shmem_register_event(graph, water_mark_level_bytes, num_levels); } EXPORT_SYMBOL_GPL(q6apm_register_watermark_event); @@ -671,7 +1540,13 @@ EXPORT_SYMBOL_GPL(q6apm_register_watermark_event); int q6apm_push_pull_config(struct q6apm_graph *graph, phys_addr_t bphys, phys_addr_t pphys, uint32_t size) { - struct audioreach_graph_info *info = graph->info; + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + struct audioreach_graph_info *info; + + if (!active) + return -ESHUTDOWN; + info = graph->info; return audioreach_setup_push_pull(graph, bphys, pphys, info->mem_map_handle, info->pos_buf_mem_map_handle, size); @@ -684,6 +1559,11 @@ bool q6apm_is_graph_in_push_pull_mode_from_id(struct device *dev, unsigned int g struct q6apm *apm = dev_get_drvdata(dev->parent); struct audioreach_module *module; + if (!apm) + return false; + guard(mutex)(&apm->client_lock); + if (apm->removing) + return false; info = idr_find(&apm->graph_info_idr, graph_id); if (!info) return false; @@ -700,7 +1580,7 @@ EXPORT_SYMBOL_GPL(q6apm_is_graph_in_push_pull_mode_from_id); bool q6apm_is_graph_in_push_pull_mode(struct q6apm_graph *graph) { - return graph->info->is_push_pull_mode; + return graph && READ_ONCE(graph->is_push_pull_mode); } EXPORT_SYMBOL_GPL(q6apm_is_graph_in_push_pull_mode); @@ -716,17 +1596,33 @@ static int q6apm_graph_get_module_iid(struct q6apm_graph *graph, uint32_t mid) } struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, - void *priv, int graph_id, int dir) + void *priv, int graph_id, int dir, + bool enable_protection) { struct q6apm *apm = dev_get_drvdata(dev->parent); struct audioreach_graph *ar_graph; struct q6apm_graph *graph; int ret, iid = 0; - ar_graph = q6apm_get_audioreach_graph(apm, graph_id); + if (!apm) + return ERR_PTR(-ENODEV); + + mutex_lock(&apm->client_lock); + if (apm->removing) { + ret = -ESHUTDOWN; + goto unlock_clients; + } + + ar_graph = q6apm_get_audioreach_graph(apm, graph_id, + enable_protection); if (IS_ERR(ar_graph)) { dev_err(dev, "No graph found with id %d\n", graph_id); - return ERR_CAST(ar_graph); + ret = PTR_ERR(ar_graph); + goto unlock_clients; + } + if (enable_protection && ar_graph->protection_malformed) { + ret = -EINVAL; + goto put_ar_graph; } graph = kzalloc_obj(*graph); @@ -741,7 +1637,12 @@ struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, graph->info = ar_graph->info; graph->ar_graph = ar_graph; graph->id = ar_graph->id; - graph->dev = dev; + graph->dev = get_device(dev); + graph->protection_profile = ar_graph->protection_profile; + graph->protection_runtime = ar_graph->protection_runtime; + spin_lock_init(&graph->lifecycle_lock); + init_waitqueue_head(&graph->users_wait); + init_completion(&graph->detached_done); if (dir == SNDRV_PCM_STREAM_PLAYBACK) { iid = q6apm_graph_get_module_iid(graph, MODULE_ID_SH_MEM_PULL_MODE); @@ -757,11 +1658,14 @@ struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, else graph->info->is_push_pull_mode = true; } + graph->is_push_pull_mode = graph->info->is_push_pull_mode; if (iid > 0) graph->shm_iid = iid; mutex_init(&graph->lock); + mutex_init(&graph->cmd_lock); + spin_lock_init(&graph->result_lock); init_waitqueue_head(&graph->cmd_wait); graph->port = gpr_alloc_port(apm->gdev, dev, graph_callback, graph); @@ -769,24 +1673,119 @@ struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, ret = PTR_ERR(graph->port); goto free_graph; } + list_add_tail(&graph->node, &apm->graph_client_list); + mutex_unlock(&apm->client_lock); return graph; free_graph: + put_device(graph->dev); kfree(graph); put_ar_graph: kref_put(&ar_graph->refcount, q6apm_put_audioreach_graph); + mutex_unlock(&apm->client_lock); + return ERR_PTR(ret); + +unlock_clients: + mutex_unlock(&apm->client_lock); return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(q6apm_graph_open); int q6apm_graph_close(struct q6apm_graph *graph) { - struct audioreach_graph *ar_graph = graph->ar_graph; + struct audioreach_graph *ar_graph; + struct q6apm *apm; + unsigned long flags; + int active_refs = 0; + int close_ret; + bool retain_graph = false; + bool final_protected; + bool retain_dma; + bool detached; + bool dying; + +retry: + spin_lock_irqsave(&graph->lifecycle_lock, flags); + detached = graph->detached; + dying = graph->dying; + retain_dma = graph->retain_dma_on_detach; + apm = graph->apm; + spin_unlock_irqrestore(&graph->lifecycle_lock, flags); + if (detached) { + if (retain_dma) + return -EBUSY; + put_device(graph->dev); + kfree(graph); + return 0; + } + if (dying) { + wait_for_completion(&graph->detached_done); + goto retry; + } + if (!apm) + return -ESHUTDOWN; + + mutex_lock(&apm->client_lock); + spin_lock_irqsave(&graph->lifecycle_lock, flags); + if (graph->dying || graph->detached) { + spin_unlock_irqrestore(&graph->lifecycle_lock, flags); + mutex_unlock(&apm->client_lock); + goto retry; + } + spin_unlock_irqrestore(&graph->lifecycle_lock, flags); + ar_graph = graph->ar_graph; + + if (graph->protection_runtime) { + mutex_lock(&ar_graph->protection_lock); + active_refs = ar_graph->start_count; + retain_graph = ar_graph->execution_uncertain || + (ar_graph->protection_faulted && active_refs > 0); + mutex_unlock(&ar_graph->protection_lock); + } + if (retain_graph) { + graph->retained = true; + mutex_unlock(&apm->client_lock); + dev_err(graph->dev, + "retaining uncertain DSP client with %d active references\n", + active_refs); + return -EBUSY; + } + q6apm_graph_mark_dying(graph); + q6apm_graph_wait_users(graph); + final_protected = graph->protection_runtime && + kref_read(&ar_graph->refcount) == 1; + if (final_protected) { + close_ret = audioreach_graph_mgmt_cmd(ar_graph, + APM_CMD_GRAPH_CLOSE); + if (close_ret) { + mutex_lock(&ar_graph->protection_lock); + ar_graph->protection_faulted = true; + ar_graph->execution_uncertain = true; + ar_graph->dma_quarantined = true; + mutex_unlock(&ar_graph->protection_lock); + spin_lock_irqsave(&graph->lifecycle_lock, flags); + graph->dying = false; + spin_unlock_irqrestore(&graph->lifecycle_lock, flags); + graph->retained = true; + mutex_unlock(&apm->client_lock); + dev_err(graph->dev, + "retaining protected client after GRAPH_CLOSE failure (%d)\n", + close_ret); + return close_ret; + } + mutex_lock(&ar_graph->protection_lock); + ar_graph->close_confirmed = true; + mutex_unlock(&ar_graph->protection_lock); + } + list_del_init(&graph->node); + gpr_free_port(graph->port); + graph->port = NULL; graph->ar_graph = NULL; kref_put(&ar_graph->refcount, q6apm_put_audioreach_graph); - gpr_free_port(graph->port); + mutex_unlock(&apm->client_lock); + put_device(graph->dev); kfree(graph); return 0; @@ -795,19 +1794,75 @@ EXPORT_SYMBOL_GPL(q6apm_graph_close); int q6apm_graph_prepare(struct q6apm_graph *graph) { - return audioreach_graph_mgmt_cmd(graph->ar_graph, APM_CMD_GRAPH_PREPARE); + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + struct audioreach_graph *ar_graph; + bool protected = q6apm_graph_has_protection(graph); + int ret; + + if (!active) + return -ESHUTDOWN; + ar_graph = graph->ar_graph; + ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_PREPARE); + if (protected) { + mutex_lock(&ar_graph->protection_lock); + ar_graph->prepared = !ret; + ar_graph->prepare_uncertain = ret == -ETIMEDOUT; + mutex_unlock(&ar_graph->protection_lock); + } + + return ret; } EXPORT_SYMBOL_GPL(q6apm_graph_prepare); int q6apm_graph_start(struct q6apm_graph *graph) { - struct audioreach_graph *ar_graph = graph->ar_graph; + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + struct audioreach_graph *ar_graph; + bool protected = q6apm_graph_has_protection(graph); int ret = 0; - if (ar_graph->start_count == 0) - ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_START); + if (!active) + return -ESHUTDOWN; + ar_graph = graph->ar_graph; + if (protected) + mutex_lock(&ar_graph->protection_lock); + if (protected && ar_graph->protection_faulted) { + ret = -EIO; + goto unlock; + } + if (protected && !ar_graph->protection_configured && + !ar_graph->protection_bypass_confirmed) { + ret = -EAGAIN; + goto unlock; + } + if (protected && (!ar_graph->prepared || + ar_graph->prepare_uncertain)) { + ret = -EAGAIN; + goto unlock; + } + if (ar_graph->start_count == 0) { + if (protected) + ret = audioreach_graph_client_mgmt_cmd(graph, APM_CMD_GRAPH_START); + else + ret = audioreach_graph_mgmt_cmd(ar_graph, + APM_CMD_GRAPH_START); + } + + if (!ret) + ar_graph->start_count++; + else if (protected) { + ar_graph->protection_faulted = true; + if (ret == -ETIMEDOUT) { + ar_graph->start_count = 1; + ar_graph->execution_uncertain = true; + } + } - ar_graph->start_count++; +unlock: + if (protected) + mutex_unlock(&ar_graph->protection_lock); return ret; } @@ -815,17 +1870,149 @@ EXPORT_SYMBOL_GPL(q6apm_graph_start); int q6apm_graph_stop(struct q6apm_graph *graph) { - struct audioreach_graph *ar_graph = graph->ar_graph; + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + struct audioreach_graph *ar_graph; + bool protected = q6apm_graph_has_protection(graph); + int ret; - if (--ar_graph->start_count > 0) - return 0; + if (!active) + return -ESHUTDOWN; + ar_graph = graph->ar_graph; + if (protected) + mutex_lock(&ar_graph->protection_lock); + if (ar_graph->start_count <= 0) { + ret = 0; + goto unlock; + } + if (--ar_graph->start_count > 0) { + ret = 0; + goto unlock; + } - return audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_STOP); + if (protected) + ret = audioreach_graph_client_mgmt_cmd(graph, APM_CMD_GRAPH_STOP); + else + ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_STOP); + if (ret) + ar_graph->start_count++; + if (ret && protected) { + ar_graph->protection_faulted = true; + ar_graph->execution_uncertain = true; + } else if (protected) { + ar_graph->execution_uncertain = false; + ar_graph->protection_configured = false; + ar_graph->protection_bypass_confirmed = false; + ar_graph->prepared = false; + ar_graph->prepare_uncertain = false; + } + +unlock: + if (protected) + mutex_unlock(&ar_graph->protection_lock); + return ret; } EXPORT_SYMBOL_GPL(q6apm_graph_stop); +bool q6apm_graph_execution_uncertain(struct q6apm_graph *graph) +{ + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + struct audioreach_graph *ar_graph; + bool uncertain; + + if (!active) + return false; + if (!q6apm_graph_has_protection(graph)) + return false; + ar_graph = graph->ar_graph; + + mutex_lock(&ar_graph->protection_lock); + uncertain = ar_graph->execution_uncertain; + mutex_unlock(&ar_graph->protection_lock); + + return uncertain; +} +EXPORT_SYMBOL_GPL(q6apm_graph_execution_uncertain); + +void q6apm_graph_quarantine_dma(struct q6apm_graph *graph) +{ + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + struct audioreach_graph *ar_graph; + + if (!active) + return; + if (!q6apm_graph_has_protection(graph)) + return; + ar_graph = graph->ar_graph; + + mutex_lock(&ar_graph->protection_lock); + ar_graph->dma_quarantined = true; + mutex_unlock(&ar_graph->protection_lock); +} +EXPORT_SYMBOL_GPL(q6apm_graph_quarantine_dma); + +bool q6apm_graph_dma_quarantined(struct device *dev, unsigned int graph_id) +{ + struct q6apm *apm = dev_get_drvdata(dev->parent); + struct audioreach_graph *graph; + bool quarantined = false; + + if (!apm) + return false; + + guard(mutex)(&apm->client_lock); + mutex_lock(&apm->lock); + spin_lock(&apm->graph_lock); + graph = idr_find(&apm->graph_idr, graph_id); + if (graph && !kref_get_unless_zero(&graph->refcount)) + graph = NULL; + spin_unlock(&apm->graph_lock); + mutex_unlock(&apm->lock); + if (!graph) + return false; + + mutex_lock(&graph->protection_lock); + quarantined = graph->dma_quarantined; + mutex_unlock(&graph->protection_lock); + kref_put(&graph->refcount, q6apm_put_audioreach_graph); + + return quarantined; +} +EXPORT_SYMBOL_GPL(q6apm_graph_dma_quarantined); + +bool q6apm_graph_id_has_protection(struct device *dev, unsigned int graph_id) +{ + struct q6apm *apm = dev_get_drvdata(dev->parent); + struct audioreach_graph_info *info; + int profile = 0; + + if (!apm) + return false; + guard(mutex)(&apm->client_lock); + mutex_lock(&apm->lock); + info = idr_find(&apm->graph_info_idr, graph_id); + if (info) + profile = audioreach_graph_protection_profile(info); + mutex_unlock(&apm->lock); + + /* A malformed partial declaration is protected and must fail closed. */ + return profile != 0; +} +EXPORT_SYMBOL_GPL(q6apm_graph_id_has_protection); + int q6apm_graph_flush(struct q6apm_graph *graph) { + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; + + if (!active) + return -ESHUTDOWN; + if (q6apm_graph_has_protection(graph)) + return audioreach_graph_client_mgmt_cmd(graph, + APM_CMD_GRAPH_FLUSH); + return audioreach_graph_mgmt_cmd(graph->ar_graph, APM_CMD_GRAPH_FLUSH); } EXPORT_SYMBOL_GPL(q6apm_graph_flush); @@ -863,11 +2050,15 @@ static int apm_probe(gpr_device_t *gdev) dev_set_drvdata(dev, apm); mutex_init(&apm->lock); + mutex_init(&apm->client_lock); + spin_lock_init(&apm->result_lock); + spin_lock_init(&apm->graph_lock); apm->dev = dev; apm->gdev = gdev; init_waitqueue_head(&apm->wait); INIT_LIST_HEAD(&apm->widget_list); + INIT_LIST_HEAD(&apm->graph_client_list); idr_init(&apm->graph_idr); idr_init(&apm->graph_info_idr); idr_init(&apm->sub_graphs_idr); @@ -875,7 +2066,9 @@ static int apm_probe(gpr_device_t *gdev) idr_init(&apm->modules_idr); + mutex_lock(&g_apm_lock); g_apm = apm; + mutex_unlock(&g_apm_lock); q6apm_get_apm_state(apm); @@ -892,45 +2085,208 @@ static int apm_probe(gpr_device_t *gdev) return ret; } +static void q6apm_abort_global_cmd(struct q6apm *apm) +{ + spin_lock(&apm->result_lock); + apm->cmd_pending = false; + spin_unlock(&apm->result_lock); + wake_up(&apm->wait); +} + +static void q6apm_detach_graph_clients(struct q6apm *apm) +{ + struct q6apm_graph *graph, *next; + LIST_HEAD(detaching); + + mutex_lock(&apm->client_lock); + apm->removing = true; + list_for_each_entry(graph, &apm->graph_client_list, node) + q6apm_graph_mark_dying(graph); + list_splice_init(&apm->graph_client_list, &detaching); + mutex_unlock(&apm->client_lock); + q6apm_abort_global_cmd(apm); + /* Drain the command thread awakened by the abort before teardown. */ + mutex_lock(&apm->lock); + mutex_unlock(&apm->lock); + + list_for_each_entry_safe(graph, next, &detaching, node) { + struct audioreach_graph *ar_graph = graph->ar_graph; + unsigned long flags; + bool retain_dma = false; + + list_del_init(&graph->node); + q6apm_graph_wait_users(graph); + gpr_free_port(graph->port); + + if (ar_graph && graph->protection_runtime) { + mutex_lock(&ar_graph->protection_lock); + retain_dma = ar_graph->start_count > 0 || + ar_graph->execution_uncertain || + ar_graph->dma_quarantined; + if (retain_dma) + ar_graph->dma_quarantined = true; + mutex_unlock(&ar_graph->protection_lock); + } + + spin_lock_irqsave(&graph->lifecycle_lock, flags); + graph->port = NULL; + graph->ar_graph = NULL; + graph->info = NULL; + graph->apm = NULL; + graph->retain_dma_on_detach = retain_dma; + graph->detached = true; + spin_unlock_irqrestore(&graph->lifecycle_lock, flags); + complete_all(&graph->detached_done); + /* The ALSA owner releases the detached client on its close path. */ + } +} + +static void q6apm_remove_graph_state(struct q6apm *apm) +{ + struct audioreach_graph *ar_graph; + int id = 0; + + guard(mutex)(&apm->client_lock); + while ((ar_graph = idr_get_next(&apm->graph_idr, &id))) { + idr_remove(&apm->graph_idr, id); + if (ar_graph->oob_virt) { + dev_err(apm->dev, + "retaining graph %u OOB DMA until reboot after service removal\n", + ar_graph->id); + /* Keep the DMA descriptor and its device reference pinned. */ + } else { + kfree(ar_graph->graph); + put_device(ar_graph->dma_dev); + kfree(ar_graph); + } + id++; + } +} + static void apm_remove(gpr_device_t *gdev) { + struct q6apm *apm = dev_get_drvdata(&gdev->dev); + + mutex_lock(&g_apm_lock); + if (g_apm == apm) + g_apm = NULL; + mutex_unlock(&g_apm_lock); + q6apm_detach_graph_clients(apm); of_platform_depopulate(&gdev->dev); snd_soc_unregister_component(&gdev->dev); + q6apm_remove_graph_state(apm); } struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph, uint32_t mid) { - struct audioreach_graph_info *info = graph->info; - struct q6apm *apm = graph->apm; + struct q6apm_graph *active __free(q6apm_graph_user) = + q6apm_graph_user_get(graph) ? graph : NULL; - return __q6apm_find_module_by_mid(apm, info, mid); + if (!active) + return NULL; + return __q6apm_find_module_by_mid(graph->apm, graph->info, mid); +} + +static bool __q6apm_cmd_response_expected(struct q6apm *apm, + const struct gpr_hdr *hdr, + u32 opcode, u32 status, + bool basic_response) +{ + bool expected; + + expected = apm->cmd_pending && apm->pending_token == hdr->token && + (apm->pending_opcode == opcode || + apm->pending_rsp_opcode == opcode); + if (expected && basic_response && !status && + apm->pending_rsp_opcode && apm->pending_rsp_opcode != opcode) + expected = false; + return expected; +} + +static bool q6apm_try_complete_cmd(struct q6apm *apm, + const struct gpr_hdr *hdr, u32 opcode, + u32 status, bool basic_response) +{ + bool expected; + + spin_lock(&apm->result_lock); + expected = __q6apm_cmd_response_expected(apm, hdr, opcode, status, + basic_response); + if (expected) { + apm->result.status = status; + apm->result_token = hdr->token; + apm->result.opcode = opcode; + } + spin_unlock(&apm->result_lock); + + if (expected) + wake_up(&apm->wait); + + return expected; } static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op) { gpr_device_t *gdev = priv; - struct audioreach_graph_info *info; + struct audioreach_graph *graph = NULL; + struct audioreach_graph_info *info = NULL; struct q6apm *apm = dev_get_drvdata(&gdev->dev); struct apm_cmd_rsp_shared_mem_map_regions *rsp; struct device *dev = &gdev->dev; struct gpr_ibasic_rsp_result_t *result; const struct gpr_hdr *hdr = &data->hdr; - int graph_id, is_pos_buf; + bool expected; + int graph_id, is_oob, is_pos_buf; result = data->payload; switch (hdr->opcode) { case APM_CMD_RSP_GET_SPF_STATE: - apm->result.opcode = hdr->opcode; - apm->result.status = 0; - /* First word of result it state */ - apm->state = result->opcode; - wake_up(&apm->wait); + /* First word of result is the state. */ + spin_lock(&apm->result_lock); + expected = __q6apm_cmd_response_expected(apm, hdr, hdr->opcode, + 0, false); + if (expected) { + apm->state = result->opcode; + apm->result.status = 0; + apm->result_token = hdr->token; + apm->result.opcode = hdr->opcode; + } + spin_unlock(&apm->result_lock); + if (expected) + wake_up(&apm->wait); break; case GPR_BASIC_RSP_RESULT: switch (result->opcode) { case APM_CMD_SHARED_MEM_MAP_REGIONS: + spin_lock(&apm->result_lock); + expected = __q6apm_cmd_response_expected(apm, hdr, + result->opcode, + result->status, true); + if (expected && result->status && + (hdr->token & APM_MMAP_TOKEN_MAP_TYPE_OOB)) { + graph_id = hdr->token & APM_MMAP_TOKEN_GID_MASK; + spin_lock(&apm->graph_lock); + graph = idr_find(&apm->graph_idr, graph_id); + if (graph && graph->oob_token == hdr->token) + graph->oob_map_uncertain = false; + spin_unlock(&apm->graph_lock); + } + if (expected) { + apm->result.status = result->status; + apm->result_token = hdr->token; + apm->result.opcode = result->opcode; + } + spin_unlock(&apm->result_lock); + if (expected) { + wake_up(&apm->wait); + if (result->status) + dev_err(dev, + "Error (%d) Processing 0x%08x cmd\n", + result->status, result->opcode); + } + break; case APM_CMD_GRAPH_START: case APM_CMD_GRAPH_OPEN: case APM_CMD_GRAPH_PREPARE: @@ -938,50 +2294,110 @@ static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op) case APM_CMD_GRAPH_FLUSH: case APM_CMD_GRAPH_STOP: case APM_CMD_SET_CFG: - apm->result.opcode = result->opcode; - apm->result.status = result->status; - if (result->status) + expected = q6apm_try_complete_cmd(apm, hdr, + result->opcode, + result->status, true); + if (expected && result->status) dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status, result->opcode); - wake_up(&apm->wait); break; case APM_CMD_SHARED_MEM_UNMAP_REGIONS: - apm->result.opcode = hdr->opcode; - apm->result.status = 0; - rsp = data->payload; - - info = idr_find(&apm->graph_info_idr, hdr->token); - if (info) - info->mem_map_handle = 0; - else - dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status, - result->opcode); + if (result->status) { + expected = q6apm_try_complete_cmd(apm, hdr, + result->opcode, + result->status, + true); + if (expected) { + dev_err(dev, + "Error (%d) Processing 0x%08x cmd\n", + result->status, result->opcode); + } + break; + } - wake_up(&apm->wait); + graph_id = hdr->token & APM_MMAP_TOKEN_GID_MASK; + is_oob = hdr->token & APM_MMAP_TOKEN_MAP_TYPE_OOB; + is_pos_buf = + hdr->token & APM_MMAP_TOKEN_MAP_TYPE_POS_BUF; + spin_lock(&apm->result_lock); + expected = __q6apm_cmd_response_expected(apm, hdr, + result->opcode, + 0, true); + if (is_oob && expected) { + spin_lock(&apm->graph_lock); + graph = idr_find(&apm->graph_idr, graph_id); + if (graph && graph->oob_token == hdr->token) + graph->oob_mem_map_handle = 0; + else + graph = NULL; + spin_unlock(&apm->graph_lock); + } else if (expected) { + info = idr_find(&apm->graph_info_idr, graph_id); + if (info && is_pos_buf) + info->pos_buf_mem_map_handle = 0; + else if (info) + info->mem_map_handle = 0; + } + if (expected) { + apm->result.status = 0; + apm->result_token = hdr->token; + apm->result.opcode = result->opcode; + } + spin_unlock(&apm->result_lock); + if (expected) { + wake_up(&apm->wait); + if ((!is_oob && !info) || (is_oob && !graph)) + dev_err(dev, + "no mapping for 0x%08x response token 0x%08x\n", + result->opcode, hdr->token); + } break; default: break; } break; case APM_CMD_RSP_SHARED_MEM_MAP_REGIONS: - apm->result.opcode = hdr->opcode; - apm->result.status = 0; rsp = data->payload; graph_id = hdr->token & APM_MMAP_TOKEN_GID_MASK; + is_oob = hdr->token & APM_MMAP_TOKEN_MAP_TYPE_OOB; is_pos_buf = hdr->token & APM_MMAP_TOKEN_MAP_TYPE_POS_BUF; - info = idr_find(&apm->graph_info_idr, graph_id); - if (info) { + spin_lock(&apm->result_lock); + expected = __q6apm_cmd_response_expected(apm, hdr, hdr->opcode, + 0, false); + if (is_oob && expected) { + spin_lock(&apm->graph_lock); + graph = idr_find(&apm->graph_idr, graph_id); + if (graph && graph->oob_token == hdr->token) { + graph->oob_mem_map_handle = rsp->mem_map_handle; + graph->oob_map_uncertain = false; + } else { + graph = NULL; + } + spin_unlock(&apm->graph_lock); + } else if (expected) { + info = idr_find(&apm->graph_info_idr, graph_id); + } + if (!is_oob && info) { if (is_pos_buf) info->pos_buf_mem_map_handle = rsp->mem_map_handle; else info->mem_map_handle = rsp->mem_map_handle; - } else { - dev_err(dev, "Error (%d) Processing 0x%08x cmd\n", result->status, - result->opcode); } - wake_up(&apm->wait); + if (expected) { + apm->result.status = 0; + apm->result_token = hdr->token; + apm->result.opcode = hdr->opcode; + } + spin_unlock(&apm->result_lock); + if (expected) + wake_up(&apm->wait); + if (expected && ((!is_oob && !info) || (is_oob && !graph))) { + dev_err(dev, + "no graph for 0x%08x response token 0x%08x\n", + hdr->opcode, hdr->token); + } break; default: break; diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 5cb51ca491dc4f..5ebec2938af39b 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -3,6 +3,7 @@ #define __Q6APM_H__ #include #include +#include #include #include #include @@ -52,17 +53,32 @@ struct q6apm { struct device *dev; + struct device *dma_dev; gpr_port_t *port; gpr_device_t *gdev; /* For Graph OPEN/START/STOP/CLOSE operations */ wait_queue_head_t wait; struct gpr_ibasic_rsp_result_t result; + u32 result_token; + u32 pending_opcode; + u32 pending_rsp_opcode; + u32 pending_token; + u32 cmd_token; + bool cmd_pending; struct mutex cmd_lock; struct mutex lock; + /* Serialize graph-client publication against service removal. */ + struct mutex client_lock; + /* Protect command matching from asynchronous GPR responses. */ + spinlock_t result_lock; + /* Protect graph lifetime during asynchronous GPR responses. */ + spinlock_t graph_lock; uint32_t state; struct list_head widget_list; + struct list_head graph_client_list; + bool removing; struct idr graph_idr; struct idr graph_info_idr; struct idr sub_graphs_idr; @@ -87,6 +103,34 @@ struct audioreach_graph { uint32_t id; int state; int start_count; + bool initializing; + struct device *dma_dev; + void *oob_virt; + dma_addr_t oob_dma; + phys_addr_t oob_dsp_addr; + size_t oob_size; + u32 oob_mem_map_handle; + u32 oob_token; + /* Serialize use of the graph's shared OOB buffer. */ + struct mutex oob_lock; + /* Serialize topology-derived protection state for this graph only. */ + struct mutex protection_lock; + bool protection_profile; + bool protection_malformed; + bool protection_runtime; + bool protection_available; + bool protection_configured; + bool protection_bypass_confirmed; + bool protection_vi_ready; + bool protection_cps_ready; + bool protection_faulted; + bool prepared; + bool prepare_uncertain; + bool close_confirmed; + bool execution_uncertain; + bool dma_quarantined; + bool oob_map_uncertain; + bool oob_transfer_uncertain; /* Cached Graph data */ void *graph; struct kref refcount; @@ -102,24 +146,54 @@ struct q6apm_graph { uint32_t shm_iid; struct device *dev; struct q6apm *apm; + struct list_head node; + bool retained; + bool protection_profile; + bool protection_runtime; + bool is_push_pull_mode; + bool dying; + bool detached; + bool retain_dma_on_detach; + unsigned int active_users; + /* Protect admission and detach state for graph API users. */ + spinlock_t lifecycle_lock; + wait_queue_head_t users_wait; + struct completion detached_done; gpr_port_t *port; struct audioreach_graph_data rx_data; struct audioreach_graph_data tx_data; struct gpr_ibasic_rsp_result_t result; + u32 result_token; + u32 pending_opcode; + u32 pending_rsp_opcode; + u32 pending_token; + u32 cmd_token; + bool cmd_pending; wait_queue_head_t cmd_wait; + /* Serialize synchronous commands without blocking data callbacks. */ + struct mutex cmd_lock; struct mutex lock; + /* Protect command matching from asynchronous client responses. */ + spinlock_t result_lock; struct audioreach_graph *ar_graph; struct audioreach_graph_info *info; }; /* Graph Operations */ struct q6apm_graph *q6apm_graph_open(struct device *dev, q6apm_cb cb, - void *priv, int graph_id, int dir); + void *priv, int graph_id, int dir, + bool enable_protection); +bool q6apm_graph_user_get(struct q6apm_graph *graph); +void q6apm_graph_user_put(struct q6apm_graph *graph); int q6apm_graph_close(struct q6apm_graph *graph); int q6apm_graph_prepare(struct q6apm_graph *graph); int q6apm_graph_start(struct q6apm_graph *graph); int q6apm_graph_stop(struct q6apm_graph *graph); int q6apm_graph_flush(struct q6apm_graph *graph); +bool q6apm_graph_execution_uncertain(struct q6apm_graph *graph); +void q6apm_graph_quarantine_dma(struct q6apm_graph *graph); +bool q6apm_graph_dma_quarantined(struct device *dev, unsigned int graph_id); +bool q6apm_graph_id_has_protection(struct device *dev, unsigned int graph_id); /* Media Format */ int q6apm_graph_media_format_pcm(struct q6apm_graph *graph, @@ -147,8 +221,24 @@ int q6apm_alloc_fragments(struct q6apm_graph *graph, int q6apm_free_fragments(struct q6apm_graph *graph, unsigned int dir); int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id); /* Helpers */ -int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode); +int q6apm_send_oob_config(struct audioreach_graph *graph, + const void *data, size_t size); +int q6apm_send_graph_oob_config(struct q6apm_graph *graph, + const void *data, size_t size); +int q6apm_graph_id_for_backend(struct device *dev, int backend_id); +bool q6apm_graph_has_protection(const struct q6apm_graph *graph); +int q6apm_graph_configure_protection(struct q6apm_graph *graph); + +enum q6apm_protection_backend { + Q6APM_PROTECTION_BACKEND_VI, + Q6APM_PROTECTION_BACKEND_CPS, +}; + +int q6apm_set_protection_backend_ready(struct device *dev, int backend_id, + enum q6apm_protection_backend backend, + bool ready); /* Callback for graph specific */ struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph, diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c index e5cd82f77b5520..6cdd6586ff1f8c 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2020, Linaro Limited +#include +#include #include #include #include @@ -694,6 +696,7 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, struct q6dsp_audio_port_dai_driver_config *cfg, int *num_dais) { + struct snd_soc_dai_driver *dais = q6dsp_audio_fe_dais; int i; for (i = 0; i < ARRAY_SIZE(q6dsp_audio_fe_dais); i++) { @@ -729,6 +732,43 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, } *num_dais = ARRAY_SIZE(q6dsp_audio_fe_dais); - return q6dsp_audio_fe_dais; + if (!of_machine_is_compatible("microsoft,denali") || + !of_device_is_compatible(dev->of_node, "qcom,q6apm-lpass-dais")) + return dais; + + dais = devm_kmemdup(dev, q6dsp_audio_fe_dais, sizeof(q6dsp_audio_fe_dais), GFP_KERNEL); + if (!dais) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < *num_dais; i++) { + switch (dais[i].id) { + case WSA_CODEC_DMA_TX_0: + dais[i].playback = (struct snd_soc_pcm_stream) { + .stream_name = "WSA_CODEC_DMA_TX_0 Protection", + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 8000, + }; + break; + case WSA_CODEC_DMA_TX_1: + dais[i].playback = (struct snd_soc_pcm_stream) { + .stream_name = "WSA_CODEC_DMA_TX_1 Protection", + .rates = SNDRV_PCM_RATE_24000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = 24000, + .rate_max = 24000, + }; + break; + default: + break; + } + } + + return dais; } EXPORT_SYMBOL_GPL(q6dsp_audio_ports_set_config); diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c index 1f69fba6de26d1..aa88a30d5c1658 100644 --- a/sound/soc/qcom/qdsp6/topology.c +++ b/sound/soc/qcom/qdsp6/topology.c @@ -2,6 +2,7 @@ // Copyright (c) 2020, Linaro Limited #include +#include #include #include #include @@ -138,6 +139,8 @@ static struct audioreach_container *audioreach_tplg_alloc_container(struct q6apm if (!cont) return ERR_PTR(-ENOMEM); + cont->parent_container_id = U32_MAX; + cont->heap_id = APM_CONT_HEAP_DEFAULT; INIT_LIST_HEAD(&cont->modules_list); mutex_lock(&apm->lock); @@ -206,131 +209,256 @@ static struct audioreach_module *audioreach_tplg_alloc_module(struct q6apm *apm, return mod; } -static const struct snd_soc_tplg_vendor_array * -audioreach_get_sg_array(const struct snd_soc_tplg_private *private) +static bool audioreach_is_raw_private_type(u32 type) { - const struct snd_soc_tplg_vendor_array *sg_array = NULL; - bool found = false; - int sz; - - for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) { - const struct snd_soc_tplg_vendor_value_elem *sg_elem; - int tkn_count = 0; - - sg_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz); - sg_elem = sg_array->value; - sz = sz + le32_to_cpu(sg_array->size); - while (!found && tkn_count <= (le32_to_cpu(sg_array->num_elems) - 1)) { - switch (le32_to_cpu(sg_elem->token)) { - case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: - found = true; - break; - default: - break; - } - tkn_count++; - sg_elem++; + return type == SND_SOC_AR_TPLG_MODULE_CFG_TYPE || + type == SND_SOC_AR_TPLG_CTRL_LINK_CFG_TYPE || + type == SND_SOC_AR_TPLG_GRAPH_CAL_CFG_TYPE || + type == SND_SOC_AR_TPLG_RENDER_EP_CFG_TYPE || + type == SND_SOC_AR_TPLG_SP_TAG_CFG_TYPE || + type == SND_SOC_AR_TPLG_SPVI_TAG_CFG_TYPE || + type == SND_SOC_AR_TPLG_VI_EP_CFG_TYPE || + type == SND_SOC_AR_TPLG_PROTECTION_DYNAMIC_CFG_TYPE || + type == SND_SOC_AR_TPLG_VOLUME_GAIN_CFG_TYPE || + type == SND_SOC_AR_TPLG_VOLUME_FILTER_CFG_TYPE || + type == SND_SOC_AR_TPLG_VOLUME_MUTE_CFG_TYPE || + type == SND_SOC_AR_TPLG_CHANNEL_MIXER_CFG_TYPE; +} + +static int +audioreach_next_private_block(const struct snd_soc_tplg_private *private, + size_t *offset, + const struct snd_soc_tplg_vendor_array **array, + bool *raw) +{ + const struct audioreach_module_priv_data *header; + size_t private_size = le32_to_cpu(private->size); + size_t block_size, elements_size, remaining; + u32 array_size, num_elems, type; + + if (*offset == private_size) + return 0; + if (*offset > private_size) + return -EINVAL; + + remaining = private_size - *offset; + if (remaining < 2 * sizeof(__le32)) + return -EINVAL; + header = (const void *)((const u8 *)private->array + *offset); + array_size = le32_to_cpu(header->size); + type = le32_to_cpu(header->type); + *raw = audioreach_is_raw_private_type(type); + *array = (const void *)header; + + if (*raw) { + if (remaining < sizeof(*header) || + check_add_overflow(sizeof(*header), (size_t)array_size, + &block_size) || + block_size > remaining) + return -EINVAL; + } else { + if (remaining < sizeof(**array) || + array_size < sizeof(**array) || array_size > remaining) + return -EINVAL; + block_size = array_size; + if (type == SND_SOC_TPLG_TUPLE_TYPE_WORD) { + num_elems = le32_to_cpu((*array)->num_elems); + if (check_mul_overflow((size_t)num_elems, + sizeof((*array)->value[0]), + &elements_size) || + elements_size > block_size - sizeof(**array)) + return -EINVAL; } } - if (found) - return sg_array; - - return NULL; + *offset += block_size; + return 1; } static const struct snd_soc_tplg_vendor_array * -audioreach_get_cont_array(const struct snd_soc_tplg_private *private) +audioreach_get_token_array(const struct snd_soc_tplg_private *private, u32 token) { - const struct snd_soc_tplg_vendor_array *cont_array = NULL; - bool found = false; - int sz; - - for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) { - const struct snd_soc_tplg_vendor_value_elem *cont_elem; - int tkn_count = 0; - - cont_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz); - cont_elem = cont_array->value; - sz = sz + le32_to_cpu(cont_array->size); - while (!found && tkn_count <= (le32_to_cpu(cont_array->num_elems) - 1)) { - switch (le32_to_cpu(cont_elem->token)) { - case AR_TKN_U32_CONTAINER_INSTANCE_ID: - found = true; - break; - default: - break; - } - tkn_count++; - cont_elem++; + const struct snd_soc_tplg_vendor_array *array; + const struct snd_soc_tplg_vendor_array *found = NULL; + size_t offset = 0; + bool raw; + int ret; + + while ((ret = audioreach_next_private_block(private, &offset, + &array, &raw)) > 0) { + u32 count, i; + + if (raw || le32_to_cpu(array->type) != + SND_SOC_TPLG_TUPLE_TYPE_WORD) + continue; + count = le32_to_cpu(array->num_elems); + for (i = 0; i < count; i++) { + if (le32_to_cpu(array->value[i].token) != token) + continue; + if (found) + return ERR_PTR(-EEXIST); + found = array; } } - if (found) - return cont_array; + return ret < 0 ? ERR_PTR(ret) : found; +} - return NULL; +static const struct snd_soc_tplg_vendor_array * +audioreach_get_sg_array(const struct snd_soc_tplg_private *private) +{ + return audioreach_get_token_array(private, + AR_TKN_U32_SUB_GRAPH_INSTANCE_ID); +} + +static const struct snd_soc_tplg_vendor_array * +audioreach_get_cont_array(const struct snd_soc_tplg_private *private) +{ + return audioreach_get_token_array(private, + AR_TKN_U32_CONTAINER_INSTANCE_ID); } static const struct snd_soc_tplg_vendor_array * audioreach_get_module_array(const struct snd_soc_tplg_private *private) { - const struct snd_soc_tplg_vendor_array *mod_array = NULL; - bool found = false; - int sz = 0; - - for (sz = 0; !found && (sz < le32_to_cpu(private->size)); ) { - const struct snd_soc_tplg_vendor_value_elem *mod_elem; - int tkn_count = 0; - - mod_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz); - mod_elem = mod_array->value; - sz = sz + le32_to_cpu(mod_array->size); - while (!found && tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) { - switch (le32_to_cpu(mod_elem->token)) { - case AR_TKN_U32_MODULE_INSTANCE_ID: - found = true; - break; - default: - break; - } - tkn_count++; - mod_elem++; + return audioreach_get_token_array(private, + AR_TKN_U32_MODULE_INSTANCE_ID); +} + +static struct audioreach_module_priv_data * +audioreach_get_module_priv_data(const struct snd_soc_tplg_private *private, + u32 requested_type) +{ + const struct snd_soc_tplg_vendor_array *array; + struct audioreach_module_priv_data *pdata = NULL; + size_t offset = 0; + bool raw; + int ret; + + while ((ret = audioreach_next_private_block(private, &offset, + &array, &raw)) > 0) { + size_t block_size, size; + + if (!raw || le32_to_cpu(array->type) != requested_type) + continue; + if (pdata) { + ret = -EEXIST; + goto error; } + + size = le32_to_cpu(array->size); + if (check_add_overflow(sizeof(*pdata), size, &block_size)) + return ERR_PTR(-EINVAL); + pdata = kmemdup(array, block_size, GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); } - if (found) - return mod_array; + if (ret < 0) + goto error; - return NULL; + return pdata; + +error: + kfree(pdata); + return ERR_PTR(ret); } -static struct audioreach_module_priv_data * -audioreach_get_module_priv_data(const struct snd_soc_tplg_private *private) +static int +audioreach_validate_ctrl_links(const struct audioreach_module_priv_data *pdata) { - int sz; + size_t size = le32_to_cpu(pdata->size); + const u8 *cursor = (const u8 *)pdata->data; + const u8 *end = cursor + size; + u32 links, link, property; - for (sz = 0; sz < le32_to_cpu(private->size); ) { - const struct snd_soc_tplg_vendor_array *mod_array; + if (size < sizeof(u32)) + return -EINVAL; - mod_array = (struct snd_soc_tplg_vendor_array *)((u8 *)private->array + sz); - if (le32_to_cpu(mod_array->type) == SND_SOC_AR_TPLG_MODULE_CFG_TYPE) { - struct audioreach_module_priv_data *pdata; + links = get_unaligned_le32(cursor); + if (!links) + return -EINVAL; + cursor += sizeof(u32); + for (link = 0; link < links; link++) { + u32 properties; + + if (end - cursor < 5 * sizeof(u32)) + return -EINVAL; + properties = get_unaligned_le32(cursor + 4 * sizeof(u32)); + cursor += 5 * sizeof(u32); + + for (property = 0; property < properties; property++) { + u32 property_size; + + if (end - cursor < 2 * sizeof(u32)) + return -EINVAL; + property_size = get_unaligned_le32(cursor + sizeof(u32)); + cursor += 2 * sizeof(u32); + if (property_size > end - cursor) + return -EINVAL; + cursor += property_size; + } + } - pdata = kzalloc_flex(*pdata, data, - le32_to_cpu(mod_array->size)); - if (!pdata) - return ERR_PTR(-ENOMEM); + return cursor == end ? 0 : -EINVAL; +} - memcpy(pdata, ((u8 *)private->data + sz), struct_size(pdata, data, - le32_to_cpu(mod_array->size))); - return pdata; - } +static void audioreach_free_extended_data(struct audioreach_module *mod) +{ + kfree(mod->ctrl_link_data); + kfree(mod->graph_cal_data); + kfree(mod->render_ep_data); + kfree(mod->sp_tag_data); + kfree(mod->spvi_tag_data); + kfree(mod->vi_ep_data); + kfree(mod->protection_dynamic_data); + kfree(mod->volume_gain_data); + kfree(mod->volume_filter_data); + kfree(mod->volume_mute_data); + kfree(mod->channel_mixer_data); +} - sz = sz + le32_to_cpu(mod_array->size); +static int audioreach_load_extended_data(struct audioreach_module *mod, + const struct snd_soc_tplg_private *private) +{ + static const u32 types[] = { + SND_SOC_AR_TPLG_GRAPH_CAL_CFG_TYPE, + SND_SOC_AR_TPLG_RENDER_EP_CFG_TYPE, + SND_SOC_AR_TPLG_SP_TAG_CFG_TYPE, + SND_SOC_AR_TPLG_SPVI_TAG_CFG_TYPE, + SND_SOC_AR_TPLG_VI_EP_CFG_TYPE, + SND_SOC_AR_TPLG_PROTECTION_DYNAMIC_CFG_TYPE, + SND_SOC_AR_TPLG_VOLUME_GAIN_CFG_TYPE, + SND_SOC_AR_TPLG_VOLUME_FILTER_CFG_TYPE, + SND_SOC_AR_TPLG_VOLUME_MUTE_CFG_TYPE, + SND_SOC_AR_TPLG_CHANNEL_MIXER_CFG_TYPE, + }; + struct audioreach_module_priv_data **destinations[] = { + &mod->graph_cal_data, + &mod->render_ep_data, + &mod->sp_tag_data, + &mod->spvi_tag_data, + &mod->vi_ep_data, + &mod->protection_dynamic_data, + &mod->volume_gain_data, + &mod->volume_filter_data, + &mod->volume_mute_data, + &mod->channel_mixer_data, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(types); i++) { + *destinations[i] = + audioreach_get_module_priv_data(private, types[i]); + if (IS_ERR(*destinations[i])) { + int ret = PTR_ERR(*destinations[i]); + + *destinations[i] = NULL; + return ret; + } } - return NULL; + return 0; } static struct audioreach_sub_graph *audioreach_parse_sg_tokens(struct q6apm *apm, @@ -340,13 +468,17 @@ static struct audioreach_sub_graph *audioreach_parse_sg_tokens(struct q6apm *apm const struct snd_soc_tplg_vendor_array *sg_array; struct audioreach_graph_info *info = NULL; int graph_id, sub_graph_id, tkn_count = 0; - struct audioreach_sub_graph *sg; + struct audioreach_sub_graph *sg = NULL; bool found; sg_array = audioreach_get_sg_array(private); + if (IS_ERR(sg_array)) + return ERR_CAST(sg_array); + if (!sg_array) + return ERR_PTR(-EINVAL); sg_elem = sg_array->value; - while (tkn_count <= (le32_to_cpu(sg_array->num_elems) - 1)) { + while (tkn_count < le32_to_cpu(sg_array->num_elems)) { switch (le32_to_cpu(sg_elem->token)) { case AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: sub_graph_id = le32_to_cpu(sg_elem->value); @@ -366,12 +498,18 @@ static struct audioreach_sub_graph *audioreach_parse_sg_tokens(struct q6apm *apm return ERR_CAST(info); break; case AR_TKN_U32_SUB_GRAPH_PERF_MODE: + if (!sg) + return ERR_PTR(-EINVAL); sg->perf_mode = le32_to_cpu(sg_elem->value); break; case AR_TKN_U32_SUB_GRAPH_DIRECTION: + if (!sg) + return ERR_PTR(-EINVAL); sg->direction = le32_to_cpu(sg_elem->value); break; case AR_TKN_U32_SUB_GRAPH_SCENARIO_ID: + if (!sg) + return ERR_PTR(-EINVAL); sg->scenario_id = le32_to_cpu(sg_elem->value); break; default: @@ -382,6 +520,8 @@ static struct audioreach_sub_graph *audioreach_parse_sg_tokens(struct q6apm *apm tkn_count++; sg_elem++; } + if (!sg) + return ERR_PTR(-EINVAL); /* Sub graph is associated with predefined graph */ if (info) @@ -396,14 +536,18 @@ static struct audioreach_container *audioreach_parse_cont_tokens(struct q6apm *a { const struct snd_soc_tplg_vendor_value_elem *cont_elem; const struct snd_soc_tplg_vendor_array *cont_array; - struct audioreach_container *cont; + struct audioreach_container *cont = NULL; int container_id, tkn_count = 0; bool found = false; cont_array = audioreach_get_cont_array(private); + if (IS_ERR(cont_array)) + return ERR_CAST(cont_array); + if (!cont_array) + return ERR_PTR(-EINVAL); cont_elem = cont_array->value; - while (tkn_count <= (le32_to_cpu(cont_array->num_elems) - 1)) { + while (tkn_count < le32_to_cpu(cont_array->num_elems)) { switch (le32_to_cpu(cont_elem->token)) { case AR_TKN_U32_CONTAINER_INSTANCE_ID: container_id = le32_to_cpu(cont_elem->value); @@ -412,17 +556,37 @@ static struct audioreach_container *audioreach_parse_cont_tokens(struct q6apm *a return cont; break; case AR_TKN_U32_CONTAINER_CAPABILITY_ID: + if (!cont) + return ERR_PTR(-EINVAL); cont->capability_id = le32_to_cpu(cont_elem->value); break; case AR_TKN_U32_CONTAINER_STACK_SIZE: + if (!cont) + return ERR_PTR(-EINVAL); cont->stack_size = le32_to_cpu(cont_elem->value); break; case AR_TKN_U32_CONTAINER_GRAPH_POS: + if (!cont) + return ERR_PTR(-EINVAL); cont->graph_pos = le32_to_cpu(cont_elem->value); break; case AR_TKN_U32_CONTAINER_PROC_DOMAIN: + if (!cont) + return ERR_PTR(-EINVAL); cont->proc_domain = le32_to_cpu(cont_elem->value); break; + case AR_TKN_U32_CONTAINER_PARENT_ID: + if (!cont) + return ERR_PTR(-EINVAL); + cont->parent_container_id = le32_to_cpu(cont_elem->value); + cont->has_extended_properties = true; + break; + case AR_TKN_U32_CONTAINER_HEAP_ID: + if (!cont) + return ERR_PTR(-EINVAL); + cont->heap_id = le32_to_cpu(cont_elem->value); + cont->has_extended_properties = true; + break; default: dev_err(apm->dev, "Not a valid token %d for graph\n", cont_elem->token); break; @@ -432,7 +596,7 @@ static struct audioreach_container *audioreach_parse_cont_tokens(struct q6apm *a cont_elem++; } - return cont; + return cont ?: ERR_PTR(-EINVAL); } static struct audioreach_module *audioreach_parse_common_tokens(struct q6apm *apm, @@ -445,6 +609,8 @@ static struct audioreach_module *audioreach_parse_common_tokens(struct q6apm *ap uint32_t dst_mod_inst_id[AR_MAX_MOD_LINKS] = { 0, }; uint32_t dst_mod_ip_port_id[AR_MAX_MOD_LINKS] = { 0, }; uint32_t src_mod_inst_id = 0; + u32 integrated_backend_id = 0; + bool speaker_protection_bypass = false; int module_id = 0, instance_id = 0, tkn_count = 0; const struct snd_soc_tplg_vendor_value_elem *mod_elem; @@ -455,9 +621,13 @@ static struct audioreach_module *audioreach_parse_common_tokens(struct q6apm *ap int max_tokens; mod_array = audioreach_get_module_array(private); + if (IS_ERR(mod_array)) + return ERR_CAST(mod_array); + if (!mod_array) + return ERR_PTR(-EINVAL); mod_elem = mod_array->value; max_tokens = le32_to_cpu(mod_array->num_elems); - while (tkn_count <= (max_tokens - 1)) { + while (tkn_count < max_tokens) { token = le32_to_cpu(mod_elem->token); switch (token) { /* common module info */ @@ -482,6 +652,8 @@ static struct audioreach_module *audioreach_parse_common_tokens(struct q6apm *ap break; case AR_TKN_U32_MODULE_MAX_OP_PORTS: max_op_port = le32_to_cpu(mod_elem->value); + if (max_op_port > AR_MAX_MOD_LINKS) + return ERR_PTR(-EINVAL); break; case AR_TKN_U32_MODULE_SRC_INSTANCE_ID: src_mod_inst_id = le32_to_cpu(mod_elem->value); @@ -558,6 +730,12 @@ static struct audioreach_module *audioreach_parse_common_tokens(struct q6apm *ap case AR_TKN_U32_MODULE_DST_IN_PORT_ID7: dst_mod_ip_port_id[7] = le32_to_cpu(mod_elem->value); break; + case AR_TKN_U32_MODULE_SPEAKER_PROTECTION_BYPASS: + speaker_protection_bypass = !!le32_to_cpu(mod_elem->value); + break; + case AR_TKN_U32_MODULE_INTEGRATED_BACKEND_ID: + integrated_backend_id = le32_to_cpu(mod_elem->value); + break; default: break; @@ -573,6 +751,8 @@ static struct audioreach_module *audioreach_parse_common_tokens(struct q6apm *ap mod->max_ip_port = max_ip_port; mod->max_op_port = max_op_port; mod->src_mod_inst_id = src_mod_inst_id; + mod->speaker_protection_bypass = speaker_protection_bypass; + mod->integrated_backend_id = integrated_backend_id; for (pn = 0; pn < mod->max_op_port; pn++) { if (src_mod_op_port_id[pn] && dst_mod_inst_id[pn] && dst_mod_ip_port_id[pn]) { @@ -585,7 +765,7 @@ static struct audioreach_module *audioreach_parse_common_tokens(struct q6apm *ap } } - return mod; + return mod ?: ERR_PTR(-EINVAL); } static int audioreach_widget_load_module_common(struct snd_soc_component *component, @@ -597,6 +777,7 @@ static int audioreach_widget_load_module_common(struct snd_soc_component *compon struct audioreach_sub_graph *sg; struct audioreach_module *mod; struct snd_soc_dobj *dobj; + const u32 ctrl_type = SND_SOC_AR_TPLG_CTRL_LINK_CFG_TYPE; sg = audioreach_parse_sg_tokens(apm, &tplg_w->priv); if (IS_ERR(sg)) @@ -610,12 +791,34 @@ static int audioreach_widget_load_module_common(struct snd_soc_component *compon if (IS_ERR_OR_NULL(mod)) return mod ? PTR_ERR(mod) : -ENODEV; - mod->data = audioreach_get_module_priv_data(&tplg_w->priv); - dobj = &w->dobj; dobj->private = mod; - return 0; + mod->data = audioreach_get_module_priv_data(&tplg_w->priv, + SND_SOC_AR_TPLG_MODULE_CFG_TYPE); + if (IS_ERR(mod->data)) { + int ret = PTR_ERR(mod->data); + + mod->data = NULL; + return ret; + } + + mod->ctrl_link_data = + audioreach_get_module_priv_data(&tplg_w->priv, ctrl_type); + if (IS_ERR(mod->ctrl_link_data)) { + int ret = PTR_ERR(mod->ctrl_link_data); + + mod->ctrl_link_data = NULL; + return ret; + } + if (mod->ctrl_link_data) { + int ret = audioreach_validate_ctrl_links(mod->ctrl_link_data); + + if (ret) + return ret; + } + + return audioreach_load_extended_data(mod, &tplg_w->priv); } static int audioreach_widget_load_enc_dec_cnv(struct snd_soc_component *component, @@ -636,9 +839,13 @@ static int audioreach_widget_load_enc_dec_cnv(struct snd_soc_component *componen dobj = &w->dobj; mod = dobj->private; mod_array = audioreach_get_module_array(&tplg_w->priv); + if (IS_ERR(mod_array)) + return PTR_ERR(mod_array); + if (!mod_array) + return -EINVAL; mod_elem = mod_array->value; - while (tkn_count <= (le32_to_cpu(mod_array->num_elems) - 1)) { + while (tkn_count < le32_to_cpu(mod_array->num_elems)) { switch (le32_to_cpu(mod_elem->token)) { case AR_TKN_U32_MODULE_FMT_INTERLEAVE: mod->interleave_type = le32_to_cpu(mod_elem->value); @@ -793,8 +1000,17 @@ static int audioreach_widget_load_buffer(struct snd_soc_component *component, mod = dobj->private; mod_array = audioreach_get_module_array(&tplg_w->priv); + if (IS_ERR(mod_array)) + return PTR_ERR(mod_array); + if (!mod_array) + return -EINVAL; switch (mod->module_id) { + case MODULE_ID_WR_SHARED_MEM_EP: + case MODULE_ID_RD_SHARED_MEM_EP: + case MODULE_ID_SH_MEM_PULL_MODE: + case MODULE_ID_SH_MEM_PUSH_MODE: + break; case MODULE_ID_CODEC_DMA_SINK: case MODULE_ID_CODEC_DMA_SOURCE: audioreach_widget_dma_module_load(mod, mod_array); @@ -810,7 +1026,8 @@ static int audioreach_widget_load_buffer(struct snd_soc_component *component, audioreach_widget_dp_module_load(mod, mod_array); break; default: - return -EINVAL; + /* Common tokens are sufficient for modules without a special loader. */ + break; } return 0; @@ -919,16 +1136,14 @@ static int audioreach_widget_ready(struct snd_soc_component *component, switch (w->id) { case snd_soc_dapm_aif_in: case snd_soc_dapm_aif_out: - audioreach_widget_load_buffer(component, index, w, tplg_w); - break; + return audioreach_widget_load_buffer(component, index, w, tplg_w); case snd_soc_dapm_decoder: case snd_soc_dapm_encoder: case snd_soc_dapm_src: - audioreach_widget_load_enc_dec_cnv(component, index, w, tplg_w); - break; + return audioreach_widget_load_enc_dec_cnv(component, index, w, + tplg_w); case snd_soc_dapm_buffer: - audioreach_widget_load_buffer(component, index, w, tplg_w); - break; + return audioreach_widget_load_buffer(component, index, w, tplg_w); case snd_soc_dapm_mixer: return audioreach_widget_load_mixer(component, index, w, tplg_w); case snd_soc_dapm_pga: @@ -972,6 +1187,7 @@ static int audioreach_widget_unload(struct snd_soc_component *scomp, list_del(&mod->node); kfree(mod->data); + audioreach_free_extended_data(mod); kfree(mod); /* Graph Info has N sub-graphs, sub-graph has N containers, Container has N Modules */ if (list_empty(&cont->modules_list)) { /* if no modules in the container then remove it */ @@ -1115,12 +1331,14 @@ static void audioreach_connect_sub_graphs(struct q6apm *apm, info->src_mod_op_port_id = 1; info->dst_mod_inst_id = m2->module_instance_id; info->dst_mod_ip_port_id = 2; + info->internal_vmixer_connection = m1->graph_id == m2->graph_id; } else { info->src_mod_inst_id = 0; info->src_mod_op_port_id = 0; info->dst_mod_inst_id = 0; info->dst_mod_ip_port_id = 0; + info->internal_vmixer_connection = false; } } diff --git a/sound/soc/qcom/x1e80100.c b/sound/soc/qcom/x1e80100.c index c92fe652f544ff..74b662296996e2 100644 --- a/sound/soc/qcom/x1e80100.c +++ b/sound/soc/qcom/x1e80100.c @@ -2,16 +2,19 @@ // Copyright (c) 2023, Linaro Limited #include +#include #include #include #include #include +#include #include #include #include #include "common.h" #include "qdsp6/q6afe.h" +#include "qdsp6/q6apm.h" #include "qdsp6/q6dsp-common.h" #include "sdw.h" @@ -19,6 +22,7 @@ struct x1e80100_snd_cfg { const char *driver_name; const unsigned int *channels_map; int channels_num; + bool protected_speaker_feedback; }; struct x1e80100_snd_data { @@ -57,6 +61,11 @@ static int x1e80100_snd_init(struct snd_soc_pcm_runtime *rtd) snd_soc_limit_volume(card, "WooferRight PA Volume", 6); snd_soc_limit_volume(card, "TweeterRight PA Volume", 6); break; + case WSA_CODEC_DMA_TX_0: + case WSA_CODEC_DMA_TX_1: + if (data->cfg->protected_speaker_feedback) + return 0; + break; case DISPLAY_PORT_RX_0: dp_pcm_id = 0; dp_jack = &data->dp_jack[dp_pcm_id]; @@ -78,11 +87,14 @@ static int x1e80100_snd_init(struct snd_soc_pcm_runtime *rtd) static int x1e80100_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { + struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); 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 *format = hw_param_mask(params, + SNDRV_PCM_HW_PARAM_FORMAT); rate->min = rate->max = 48000; switch (cpu_dai->id) { @@ -92,6 +104,26 @@ static int x1e80100_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, case TX_CODEC_DMA_TX_3: channels->min = 1; break; + case WSA_CODEC_DMA_TX_0: + if (!data->cfg->protected_speaker_feedback) + break; + rate->min = 8000; + rate->max = 8000; + channels->min = 2; + channels->max = 2; + snd_mask_none(format); + snd_mask_set_format(format, SNDRV_PCM_FORMAT_S32_LE); + break; + case WSA_CODEC_DMA_TX_1: + if (!data->cfg->protected_speaker_feedback) + break; + rate->min = 24000; + rate->max = 24000; + channels->min = 2; + channels->max = 2; + snd_mask_none(format); + snd_mask_set_format(format, SNDRV_PCM_FORMAT_S32_LE); + break; default: break; } @@ -135,13 +167,45 @@ static int x1e80100_snd_hw_map_channels(struct x1e80100_snd_data *data, return 0; } +static int x1e80100_protection_backend(unsigned int dai_id) +{ + switch (dai_id) { + case WSA_CODEC_DMA_TX_0: + return Q6APM_PROTECTION_BACKEND_VI; + case WSA_CODEC_DMA_TX_1: + return Q6APM_PROTECTION_BACKEND_CPS; + default: + return -EINVAL; + } +} + +static int x1e80100_set_protection_ready(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *cpu_dai, + int backend, bool ready) +{ + int ret; + + ret = q6apm_set_protection_backend_ready(cpu_dai->dev, cpu_dai->id, + backend, ready); + if (ret && ret != -ENODEV) + dev_warn(rtd->dev, + "failed to update protected backend readiness: %d\n", ret); + + return ret; +} + static int x1e80100_snd_prepare(struct snd_pcm_substream *substream) { 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 x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct sdw_stream_runtime *sruntime; unsigned int channels = substream->runtime->channels; unsigned int rx_slot[4]; + unsigned int tx_slot[4]; + bool ready; + int backend; + int protection_ret; int ret; switch (cpu_dai->id) { @@ -156,11 +220,67 @@ static int x1e80100_snd_prepare(struct snd_pcm_substream *substream) if (ret) return ret; break; + case WSA_CODEC_DMA_TX_0: + case WSA_CODEC_DMA_TX_1: + if (!data->cfg->protected_speaker_feedback) + break; + + ret = x1e80100_snd_hw_map_channels(data, tx_slot, channels); + if (ret) + return ret; + + ret = snd_soc_dai_set_channel_map(cpu_dai, channels, tx_slot, + 0, NULL); + if (ret) + return ret; + break; default: break; } + if (data->cfg->protected_speaker_feedback) { + backend = x1e80100_protection_backend(cpu_dai->id); + if (backend >= 0) { + sruntime = qcom_snd_sdw_get_stream(substream); + if (IS_ERR_OR_NULL(sruntime)) { + data->stream_prepared[cpu_dai->id] = false; + ret = x1e80100_set_protection_ready(rtd, cpu_dai, + backend, false); + if (ret && ret != -ENODEV) + return ret; + dev_warn(rtd->dev, + "speaker-feedback backend %d has no SoundWire runtime; using bypass\n", + cpu_dai->id); + return 0; + } + } + } + + ret = qcom_snd_sdw_prepare(substream, + &data->stream_prepared[cpu_dai->id]); + if (!data->cfg->protected_speaker_feedback) + return ret; + + backend = x1e80100_protection_backend(cpu_dai->id); + if (backend < 0) + return ret; + + ready = !ret && data->stream_prepared[cpu_dai->id]; + protection_ret = x1e80100_set_protection_ready(rtd, cpu_dai, backend, + ready); + if (!ready) { + if (protection_ret) + return ret ? ret : protection_ret; + dev_warn(rtd->dev, + "speaker-feedback backend %d unavailable; using bypass\n", + cpu_dai->id); + return 0; + } + if (protection_ret && protection_ret != -ENODEV) { + qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]); + return protection_ret; + } - return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]); + return 0; } static int x1e80100_snd_hw_free(struct snd_pcm_substream *substream) @@ -168,13 +288,46 @@ static int x1e80100_snd_hw_free(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int backend; + int ret; + + if (data->cfg->protected_speaker_feedback) { + backend = x1e80100_protection_backend(cpu_dai->id); + if (backend >= 0) { + ret = x1e80100_set_protection_ready(rtd, cpu_dai, + backend, false); + if (ret && ret != -ENODEV) + return ret; + } + } return qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]); } +static void x1e80100_snd_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct x1e80100_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int backend; + int ret; + + if (data->cfg->protected_speaker_feedback) { + backend = x1e80100_protection_backend(cpu_dai->id); + if (backend >= 0) { + ret = x1e80100_set_protection_ready(rtd, cpu_dai, + backend, false); + if (ret && ret != -ENODEV) + return; + } + } + + qcom_snd_sdw_shutdown(substream); +} + static const struct snd_soc_ops x1e80100_be_ops = { .startup = qcom_snd_sdw_startup, - .shutdown = qcom_snd_sdw_shutdown, + .shutdown = x1e80100_snd_shutdown, .hw_free = x1e80100_snd_hw_free, .prepare = x1e80100_snd_prepare, }; @@ -229,6 +382,11 @@ static const struct x1e80100_snd_cfg x1e80100_cfg = { .driver_name = "x1e80100", }; +static const struct x1e80100_snd_cfg denali_cfg = { + .driver_name = "x1e80100", + .protected_speaker_feedback = true, +}; + static const struct x1e80100_snd_cfg glymur_cfg = { .driver_name = "glymur", }; @@ -247,6 +405,7 @@ static const struct x1e80100_snd_cfg dell_xps13_9345_cfg = { }; static const struct of_device_id snd_x1e80100_dt_match[] = { + { .compatible = "microsoft,denali-sndcard", .data = &denali_cfg, }, { .compatible = "qcom,x1e80100-sndcard", .data = &x1e80100_cfg, }, { .compatible = "dell,xps13-9345-sndcard", .data = &dell_xps13_9345_cfg, }, { .compatible = "qcom,glymur-sndcard", .data = &glymur_cfg, },