Message ID | 20231017200109.11407-10-quic_wcheng@quicinc.com |
---|---|
State | New |
Headers | show |
Series | Introduce QC USB SND audio offloading support | expand |
Hi Pierre, On 10/17/2023 2:32 PM, Pierre-Louis Bossart wrote: > > > On 10/17/23 15:00, Wesley Cheng wrote: >> The QC ADSP is able to support USB playback endpoints, so that the main > > playback only? > Correct, playback only at this time. >> application processor can be placed into lower CPU power modes. This adds >> the required AFE port configurations and port start command to start an >> audio session. >> >> Specifically, the QC ADSP can support all potential endpoints that are >> exposed by the audio data interface. This includes, feedback endpoints >> (both implicit and explicit) as well as the isochronous (data) endpoints. > > implicit feedback means support for capture. This is confusing... > I mean, a USB device can expose a capture path, but as of now, we won't enable the offloading to the audio DSP for it. However, if we're executing playback, and device does support implicit feedback, we will pass that along to the audio DSP to utilize. >> +static int q6usb_hw_params(struct snd_pcm_substream *substream, >> + struct snd_pcm_hw_params *params, >> + struct snd_soc_dai *dai) >> +{ >> + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); >> + int channels = params_channels(params); >> + int rate = params_rate(params); >> + struct q6afe_usb_cfg *usb = &dai_data->port_config[dai->id].usb_audio; >> + >> + usb->sample_rate = rate; >> + usb->num_channels = channels; >> + >> + switch (params_format(params)) { >> + case SNDRV_PCM_FORMAT_U16_LE: >> + case SNDRV_PCM_FORMAT_S16_LE: >> + case SNDRV_PCM_FORMAT_SPECIAL: > > what does FORMAT_SPECIAL mean? the only other reference I see to this is > related to SLIMbus, not sure how this is related? > Thanks for catching this. It shouldn't be included in this path. >> + usb->bit_width = 16; >> + break; >> + case SNDRV_PCM_FORMAT_S24_LE: >> + case SNDRV_PCM_FORMAT_S24_3LE: >> + usb->bit_width = 24; >> + break; >> + case SNDRV_PCM_FORMAT_S32_LE: >> + usb->bit_width = 32; >> + break; >> + default: >> + dev_err(dai->dev, "%s: invalid format %d\n", >> + __func__, params_format(params)); >> + return -EINVAL; >> + } >> + >> + return 0; >> +} > >> @@ -617,6 +655,9 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { >> {"TX_CODEC_DMA_TX_5", NULL, "TX_CODEC_DMA_TX_5 Capture"}, >> {"RX_CODEC_DMA_RX_6 Playback", NULL, "RX_CODEC_DMA_RX_6"}, >> {"RX_CODEC_DMA_RX_7 Playback", NULL, "RX_CODEC_DMA_RX_7"}, >> + >> + /* USB playback AFE port receives data for playback, hence use the RX port */ >> + {"USB Playback", NULL, "USB_RX"}, > > Capture for implicit feedback? > Please refer to the above comment. >> }; >> >> static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) >> @@ -644,6 +685,18 @@ static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) >> return 0; >> } >> >> +static const struct snd_soc_dai_ops q6usb_ops = { >> + .probe = msm_dai_q6_dai_probe, >> + .prepare = q6afe_dai_prepare, >> + .hw_params = q6usb_hw_params, > > this is rather confusing with two different layers used for hw_params > and prepare? Additional comments or explanations wouldn't hurt. > I thought this was how the ASoC design was. Each DAI defined for a particular path has it own set of callbacks implemented to bring up any required resources for that entity. So in this case, it initializes the "cpu" DAI, which is the main component that handles communication with the audio DSP. >> + .shutdown = q6afe_dai_shutdown, >> + /* >> + * Startup callback not needed, as AFE port start command passes the PCM >> + * parameters within the AFE command, which is provided by the PCM core >> + * during the prepare() stage. > > This doesn't really explain why you need a shutdown? > > Sure, I'll add a comment. shutdown() is needed to actually issue a AFE port stop command to stop pumping audio data on a particular AFE port. This occurs when userspace closes the PCM device for the platform sound card, and is triggered for all linked DAIs. >> + * struct afe_param_id_usb_audio_dev_latency_mode >> + * @cfg_minor_version: Minor version used for tracking USB audio device >> + * configuration. >> + * Supported values: >> + * AFE_API_MINOR_VERSION_USB_AUDIO_LATENCY_MODE >> + * @mode: latency mode for the USB audio device > > what are the different latency modes? and is this related to the latency > reporting that was added in the USB2 audio class IIRC? > Must've missed removing this part during one of the earlier revision cleanups I had done. We aren't setting this parameter currently on the AFE side, and it isn't utilized either in the audio DSP, so I will remove this definition. >> +static int afe_port_send_usb_dev_param(struct q6afe_port *port, struct q6afe_usb_cfg *cfg) >> +{ >> + union afe_port_config *pcfg = &port->port_cfg; >> + struct afe_param_id_usb_audio_dev_params usb_dev; >> + struct afe_param_id_usb_audio_dev_lpcm_fmt lpcm_fmt; >> + struct afe_param_id_usb_audio_svc_interval svc_int; >> + int ret = 0; > > useless init overridden... Will fix this. >> + >> + if (!pcfg) { >> + dev_err(port->afe->dev, "%s: Error, no configuration data\n", __func__); >> + ret = -EINVAL; >> + goto exit; >> + } >> + >> + memset(&usb_dev, 0, sizeof(usb_dev)); >> + memset(&lpcm_fmt, 0, sizeof(lpcm_fmt)); >> + memset(&svc_int, 0, sizeof(svc_int)); >> + >> + usb_dev.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; >> + ret = q6afe_port_set_param_v2(port, &usb_dev, > > .... here > >> + AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS, >> + AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(usb_dev)); >> + if (ret) { >> + dev_err(port->afe->dev, "%s: AFE device param cmd failed %d\n", >> + __func__, ret); >> + goto exit; >> + } >> + >> + lpcm_fmt.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; >> + lpcm_fmt.endian = pcfg->usb_cfg.endian; >> + ret = q6afe_port_set_param_v2(port, &lpcm_fmt, >> + AFE_PARAM_ID_USB_AUDIO_DEV_LPCM_FMT, >> + AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(lpcm_fmt)); >> + if (ret) { >> + dev_err(port->afe->dev, "%s: AFE device param cmd LPCM_FMT failed %d\n", >> + __func__, ret); >> + goto exit; >> + } >> + >> + svc_int.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; >> + svc_int.svc_interval = pcfg->usb_cfg.service_interval; >> + ret = q6afe_port_set_param_v2(port, &svc_int, >> + AFE_PARAM_ID_USB_AUDIO_SVC_INTERVAL, >> + AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(svc_int)); >> + if (ret) >> + dev_err(port->afe->dev, "%s: AFE device param cmd svc_interval failed %d\n", >> + __func__, ret); >> + >> +exit: >> + return ret; >> +} > >> -#define AFE_PORT_MAX 129 >> +#define AFE_PORT_MAX 137 > > does this mean 8 ports are reserved for USB? > > Or is this 137 just a random index coming from the AFE design? > > Its the latter. Each port has a defined number/ID on the audio DSP AFE end. Thanks Wesley Cheng
>>> Specifically, the QC ADSP can support all potential endpoints that are >>> exposed by the audio data interface. This includes, feedback endpoints >>> (both implicit and explicit) as well as the isochronous (data) >>> endpoints. >> >> implicit feedback means support for capture. This is confusing... >> > > I mean, a USB device can expose a capture path, but as of now, we won't > enable the offloading to the audio DSP for it. However, if we're > executing playback, and device does support implicit feedback, we will > pass that along to the audio DSP to utilize. Not following. Implicit feedback means a capture stream *SHALL* be started. Are you saying this capture stream is hidden and handled at the DSP level only? If yes, what prevents you from exposing the capture stream to userspace as well? I must be missing something. >>> +static const struct snd_soc_dai_ops q6usb_ops = { >>> + .probe = msm_dai_q6_dai_probe, >>> + .prepare = q6afe_dai_prepare, >>> + .hw_params = q6usb_hw_params, >> >> this is rather confusing with two different layers used for hw_params >> and prepare? Additional comments or explanations wouldn't hurt. >> > > I thought this was how the ASoC design was. Each DAI defined for a > particular path has it own set of callbacks implemented to bring up any > required resources for that entity. So in this case, it initializes the > "cpu" DAI, which is the main component that handles communication with > the audio DSP. Usually prepare and hw_params rely on the type of DAI callbacks, but here you are mixing "q6afe" and "q6usb" which are shown in your Patch0 diagram as "cpu" and "codec" dais respectively. I don't think it's correct to tie the two, it's a clear layering violation IMHO. The codec dai .prepare should not invoke something that modifies the state of the CPU dai, which should have its own .prepare callback.
Hi Pierre, On 10/18/2023 6:47 AM, Pierre-Louis Bossart wrote: > >>>> Specifically, the QC ADSP can support all potential endpoints that are >>>> exposed by the audio data interface. This includes, feedback endpoints >>>> (both implicit and explicit) as well as the isochronous (data) >>>> endpoints. >>> >>> implicit feedback means support for capture. This is confusing... >>> >> >> I mean, a USB device can expose a capture path, but as of now, we won't >> enable the offloading to the audio DSP for it. However, if we're >> executing playback, and device does support implicit feedback, we will >> pass that along to the audio DSP to utilize. > > Not following. Implicit feedback means a capture stream *SHALL* be > started. Are you saying this capture stream is hidden and handled at the > DSP level only? If yes, what prevents you from exposing the capture > stream to userspace as well? > > I must be missing something. > My understanding is that with implicit feedback endpoints, it allows for another data endpoint in the opposite direction to be utilized as a feedback endpoint (versus having to expose another EP, such as in the case of explicit feedback). For example, if we are enabling the playback path (and the device does have a capture data ep) then the data ep used for the capture path can be used. USB2.0 spec, section 5.12.4.3 (Implicit Feedback) " Two cases can arise: • One or more asynchronous sink endpoints are accompanied by an asynchronous source endpoint. The data rate on the source endpoint can be used as implicit feedback information to adjust the data rate on the sink endpoint(s). • One or more adaptive source endpoints are accompanied by an adaptive sink endpoint. The source endpoint can adjust its data rate based on the data rate received by the sink endpoint. " The DSP will get this as part of the USB sync endpoint information which it will use to enable this EP. >>>> +static const struct snd_soc_dai_ops q6usb_ops = { >>>> + .probe = msm_dai_q6_dai_probe, >>>> + .prepare = q6afe_dai_prepare, >>>> + .hw_params = q6usb_hw_params, >>> >>> this is rather confusing with two different layers used for hw_params >>> and prepare? Additional comments or explanations wouldn't hurt. >>> >> >> I thought this was how the ASoC design was. Each DAI defined for a >> particular path has it own set of callbacks implemented to bring up any >> required resources for that entity. So in this case, it initializes the >> "cpu" DAI, which is the main component that handles communication with >> the audio DSP. > > Usually prepare and hw_params rely on the type of DAI callbacks, but > here you are mixing "q6afe" and "q6usb" which are shown in your Patch0 > diagram as "cpu" and "codec" dais respectively. I don't think it's > correct to tie the two, it's a clear layering violation IMHO. The codec > dai .prepare should not invoke something that modifies the state of the > CPU dai, which should have its own .prepare callback. OK, I think I know where the misunderstanding is. The API callback for hw_params() that is being registered here exists in q6afe, but with the q6usb prefix. I will fix that in the next rev. Thanks Wesley Cheng
>>>>> Specifically, the QC ADSP can support all potential endpoints that are >>>>> exposed by the audio data interface. This includes, feedback >>>>> endpoints >>>>> (both implicit and explicit) as well as the isochronous (data) >>>>> endpoints. >>>> >>>> implicit feedback means support for capture. This is confusing... >>>> >>> >>> I mean, a USB device can expose a capture path, but as of now, we won't >>> enable the offloading to the audio DSP for it. However, if we're >>> executing playback, and device does support implicit feedback, we will >>> pass that along to the audio DSP to utilize. >> >> Not following. Implicit feedback means a capture stream *SHALL* be >> started. Are you saying this capture stream is hidden and handled at the >> DSP level only? If yes, what prevents you from exposing the capture >> stream to userspace as well? >> >> I must be missing something. >> > > My understanding is that with implicit feedback endpoints, it allows for > another data endpoint in the opposite direction to be utilized as a > feedback endpoint (versus having to expose another EP, such as in the > case of explicit feedback). For example, if we are enabling the > playback path (and the device does have a capture data ep) then the data > ep used for the capture path can be used. That's right, so all the plumbing is enabled for the capture path... Making a decision to discard the data is very odd, all the work has already been done at lower levels, so why not expose the captured data?
diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c index 3faa7e0eb0dd..e46c064253d7 100644 --- a/sound/soc/qcom/qdsp6/q6afe-dai.c +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -91,6 +91,40 @@ static int q6hdmi_hw_params(struct snd_pcm_substream *substream, return 0; } +static int q6usb_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + int channels = params_channels(params); + int rate = params_rate(params); + struct q6afe_usb_cfg *usb = &dai_data->port_config[dai->id].usb_audio; + + usb->sample_rate = rate; + usb->num_channels = channels; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_SPECIAL: + usb->bit_width = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_3LE: + usb->bit_width = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + usb->bit_width = 32; + break; + default: + dev_err(dai->dev, "%s: invalid format %d\n", + __func__, params_format(params)); + return -EINVAL; + } + + return 0; +} + static int q6i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -391,6 +425,10 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream, q6afe_cdc_dma_port_prepare(dai_data->port[dai->id], &dai_data->port_config[dai->id].dma_cfg); break; + case USB_RX: + q6afe_usb_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].usb_audio); + break; default: return -EINVAL; } @@ -617,6 +655,9 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { {"TX_CODEC_DMA_TX_5", NULL, "TX_CODEC_DMA_TX_5 Capture"}, {"RX_CODEC_DMA_RX_6 Playback", NULL, "RX_CODEC_DMA_RX_6"}, {"RX_CODEC_DMA_RX_7 Playback", NULL, "RX_CODEC_DMA_RX_7"}, + + /* USB playback AFE port receives data for playback, hence use the RX port */ + {"USB Playback", NULL, "USB_RX"}, }; static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) @@ -644,6 +685,18 @@ static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) return 0; } +static const struct snd_soc_dai_ops q6usb_ops = { + .probe = msm_dai_q6_dai_probe, + .prepare = q6afe_dai_prepare, + .hw_params = q6usb_hw_params, + .shutdown = q6afe_dai_shutdown, + /* + * Startup callback not needed, as AFE port start command passes the PCM + * parameters within the AFE command, which is provided by the PCM core + * during the prepare() stage. + */ +}; + static const struct snd_soc_dai_ops q6hdmi_ops = { .probe = msm_dai_q6_dai_probe, .remove = msm_dai_q6_dai_remove, @@ -942,6 +995,8 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_7", "NULL", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("USB_RX", NULL, 0, SND_SOC_NOPM, 0, 0), }; static const struct snd_soc_component_driver q6afe_dai_component = { @@ -1056,6 +1111,7 @@ static int q6afe_dai_dev_probe(struct platform_device *pdev) cfg.q6i2s_ops = &q6i2s_ops; cfg.q6tdm_ops = &q6tdm_ops; cfg.q6dma_ops = &q6dma_ops; + cfg.q6usb_ops = &q6usb_ops; dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); return devm_snd_soc_register_component(dev, &q6afe_dai_component, dais, num_dais); diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index 919e326b9462..f49c69472b5c 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -34,6 +34,8 @@ #define AFE_MODULE_TDM 0x0001028A #define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235 +#define AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS 0x000102A5 +#define AFE_PARAM_ID_USB_AUDIO_DEV_LPCM_FMT 0x000102AA #define AFE_PARAM_ID_LPAIF_CLK_CONFIG 0x00010238 #define AFE_PARAM_ID_INT_DIGITAL_CDC_CLK_CONFIG 0x00010239 @@ -43,6 +45,7 @@ #define AFE_PARAM_ID_TDM_CONFIG 0x0001029D #define AFE_PARAM_ID_PORT_SLOT_MAPPING_CONFIG 0x00010297 #define AFE_PARAM_ID_CODEC_DMA_CONFIG 0x000102B8 +#define AFE_PARAM_ID_USB_AUDIO_CONFIG 0x000102A4 #define AFE_CMD_REMOTE_LPASS_CORE_HW_VOTE_REQUEST 0x000100f4 #define AFE_CMD_RSP_REMOTE_LPASS_CORE_HW_VOTE_REQUEST 0x000100f5 #define AFE_CMD_REMOTE_LPASS_CORE_HW_DEVOTE_REQUEST 0x000100f6 @@ -71,12 +74,16 @@ #define AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL 0x1 #define AFE_LINEAR_PCM_DATA 0x0 +#define AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG 0x1 /* Port IDs */ #define AFE_API_VERSION_HDMI_CONFIG 0x1 #define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E #define AFE_PORT_ID_HDMI_OVER_DP_RX 0x6020 +/* USB AFE port */ +#define AFE_PORT_ID_USB_RX 0x7000 + #define AFE_API_VERSION_SLIMBUS_CONFIG 0x1 /* Clock set API version */ #define AFE_API_VERSION_CLOCK_SET 1 @@ -512,12 +519,109 @@ struct afe_param_id_cdc_dma_cfg { u16 active_channels_mask; } __packed; +struct afe_param_id_usb_cfg { +/* Minor version used for tracking USB audio device configuration. + * Supported values: AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG + */ + u32 cfg_minor_version; +/* Sampling rate of the port. + * Supported values: + * - AFE_PORT_SAMPLE_RATE_8K + * - AFE_PORT_SAMPLE_RATE_11025 + * - AFE_PORT_SAMPLE_RATE_12K + * - AFE_PORT_SAMPLE_RATE_16K + * - AFE_PORT_SAMPLE_RATE_22050 + * - AFE_PORT_SAMPLE_RATE_24K + * - AFE_PORT_SAMPLE_RATE_32K + * - AFE_PORT_SAMPLE_RATE_44P1K + * - AFE_PORT_SAMPLE_RATE_48K + * - AFE_PORT_SAMPLE_RATE_96K + * - AFE_PORT_SAMPLE_RATE_192K + */ + u32 sample_rate; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + u16 bit_width; +/* Number of channels. + * Supported values: 1 and 2 + */ + u16 num_channels; +/* Data format supported by the USB. The supported value is + * 0 (#AFE_USB_AUDIO_DATA_FORMAT_LINEAR_PCM). + */ + u16 data_format; +/* this field must be 0 */ + u16 reserved; +/* device token of actual end USB audio device */ + u32 dev_token; +/* endianness of this interface */ + u32 endian; +/* service interval */ + u32 service_interval; +} __packed; + +/** + * struct afe_param_id_usb_audio_dev_params + * @cfg_minor_version: Minor version used for tracking USB audio device + * configuration. + * Supported values: + * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG + * @dev_token: device token of actual end USB audio device + **/ +struct afe_param_id_usb_audio_dev_params { + u32 cfg_minor_version; + u32 dev_token; +} __packed; + +/** + * struct afe_param_id_usb_audio_dev_lpcm_fmt + * @cfg_minor_version: Minor version used for tracking USB audio device + * configuration. + * Supported values: + * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG + * @endian: endianness of this interface + **/ +struct afe_param_id_usb_audio_dev_lpcm_fmt { + u32 cfg_minor_version; + u32 endian; +} __packed; + +/** + * struct afe_param_id_usb_audio_dev_latency_mode + * @cfg_minor_version: Minor version used for tracking USB audio device + * configuration. + * Supported values: + * AFE_API_MINOR_VERSION_USB_AUDIO_LATENCY_MODE + * @mode: latency mode for the USB audio device + **/ +struct afe_param_id_usb_audio_dev_latency_mode { + u32 minor_version; + u32 mode; +} __packed; + +#define AFE_PARAM_ID_USB_AUDIO_SVC_INTERVAL 0x000102B7 + +/** + * struct afe_param_id_usb_audio_svc_interval + * @cfg_minor_version: Minor version used for tracking USB audio device + * configuration. + * Supported values: + * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG + * @svc_interval: service interval + **/ +struct afe_param_id_usb_audio_svc_interval { + u32 cfg_minor_version; + u32 svc_interval; +} __packed; + union afe_port_config { struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch; struct afe_param_id_slimbus_cfg slim_cfg; struct afe_param_id_i2s_cfg i2s_cfg; struct afe_param_id_tdm_cfg tdm_cfg; struct afe_param_id_cdc_dma_cfg dma_cfg; + struct afe_param_id_usb_cfg usb_cfg; } __packed; @@ -832,6 +936,7 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = { RX_CODEC_DMA_RX_6, 1, 1}, [RX_CODEC_DMA_RX_7] = { AFE_PORT_ID_RX_CODEC_DMA_RX_7, RX_CODEC_DMA_RX_7, 1, 1}, + [USB_RX] = { AFE_PORT_ID_USB_RX, USB_RX, 1, 1}, }; static void q6afe_port_free(struct kref *ref) @@ -1289,6 +1394,79 @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, } EXPORT_SYMBOL_GPL(q6afe_tdm_port_prepare); +static int afe_port_send_usb_dev_param(struct q6afe_port *port, struct q6afe_usb_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + struct afe_param_id_usb_audio_dev_params usb_dev; + struct afe_param_id_usb_audio_dev_lpcm_fmt lpcm_fmt; + struct afe_param_id_usb_audio_svc_interval svc_int; + int ret = 0; + + if (!pcfg) { + dev_err(port->afe->dev, "%s: Error, no configuration data\n", __func__); + ret = -EINVAL; + goto exit; + } + + memset(&usb_dev, 0, sizeof(usb_dev)); + memset(&lpcm_fmt, 0, sizeof(lpcm_fmt)); + memset(&svc_int, 0, sizeof(svc_int)); + + usb_dev.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; + ret = q6afe_port_set_param_v2(port, &usb_dev, + AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS, + AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(usb_dev)); + if (ret) { + dev_err(port->afe->dev, "%s: AFE device param cmd failed %d\n", + __func__, ret); + goto exit; + } + + lpcm_fmt.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; + lpcm_fmt.endian = pcfg->usb_cfg.endian; + ret = q6afe_port_set_param_v2(port, &lpcm_fmt, + AFE_PARAM_ID_USB_AUDIO_DEV_LPCM_FMT, + AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(lpcm_fmt)); + if (ret) { + dev_err(port->afe->dev, "%s: AFE device param cmd LPCM_FMT failed %d\n", + __func__, ret); + goto exit; + } + + svc_int.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; + svc_int.svc_interval = pcfg->usb_cfg.service_interval; + ret = q6afe_port_set_param_v2(port, &svc_int, + AFE_PARAM_ID_USB_AUDIO_SVC_INTERVAL, + AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(svc_int)); + if (ret) + dev_err(port->afe->dev, "%s: AFE device param cmd svc_interval failed %d\n", + __func__, ret); + +exit: + return ret; +} + +/** + * q6afe_usb_port_prepare() - Prepare usb afe port. + * + * @port: Instance of afe port + * @cfg: USB configuration for the afe port + * + */ +void q6afe_usb_port_prepare(struct q6afe_port *port, + struct q6afe_usb_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->usb_cfg.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; + pcfg->usb_cfg.sample_rate = cfg->sample_rate; + pcfg->usb_cfg.num_channels = cfg->num_channels; + pcfg->usb_cfg.bit_width = cfg->bit_width; + + afe_port_send_usb_dev_param(port, cfg); +} +EXPORT_SYMBOL_GPL(q6afe_usb_port_prepare); + /** * q6afe_hdmi_port_prepare() - Prepare hdmi afe port. * @@ -1611,7 +1789,10 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) break; case AFE_PORT_ID_WSA_CODEC_DMA_RX_0 ... AFE_PORT_ID_RX_CODEC_DMA_RX_7: cfg_type = AFE_PARAM_ID_CODEC_DMA_CONFIG; - break; + break; + case AFE_PORT_ID_USB_RX: + cfg_type = AFE_PARAM_ID_USB_AUDIO_CONFIG; + break; default: dev_err(dev, "Invalid port id 0x%x\n", port_id); return ERR_PTR(-EINVAL); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h index 30fd77e2f458..ef47b4ae9e27 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -5,7 +5,7 @@ #include <dt-bindings/sound/qcom,q6afe.h> -#define AFE_PORT_MAX 129 +#define AFE_PORT_MAX 137 #define MSM_AFE_PORT_TYPE_RX 0 #define MSM_AFE_PORT_TYPE_TX 1 @@ -205,6 +205,36 @@ struct q6afe_cdc_dma_cfg { u16 active_channels_mask; }; +/** + * struct q6afe_usb_cfg + * @cfg_minor_version: Minor version used for tracking USB audio device + * configuration. + * Supported values: + * AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG + * @sample_rate: Sampling rate of the port + * Supported values: + * AFE_PORT_SAMPLE_RATE_8K + * AFE_PORT_SAMPLE_RATE_11025 + * AFE_PORT_SAMPLE_RATE_12K + * AFE_PORT_SAMPLE_RATE_16K + * AFE_PORT_SAMPLE_RATE_22050 + * AFE_PORT_SAMPLE_RATE_24K + * AFE_PORT_SAMPLE_RATE_32K + * AFE_PORT_SAMPLE_RATE_44P1K + * AFE_PORT_SAMPLE_RATE_48K + * AFE_PORT_SAMPLE_RATE_96K + * AFE_PORT_SAMPLE_RATE_192K + * @bit_width: Bit width of the sample. + * Supported values: 16, 24 + * @num_channels: Number of channels + * Supported values: 1, 2 + **/ +struct q6afe_usb_cfg { + u32 cfg_minor_version; + u32 sample_rate; + u16 bit_width; + u16 num_channels; +}; struct q6afe_port_config { struct q6afe_hdmi_cfg hdmi; @@ -212,6 +242,7 @@ struct q6afe_port_config { struct q6afe_i2s_cfg i2s_cfg; struct q6afe_tdm_cfg tdm; struct q6afe_cdc_dma_cfg dma_cfg; + struct q6afe_usb_cfg usb_audio; }; struct q6afe_port; @@ -221,6 +252,8 @@ int q6afe_port_start(struct q6afe_port *port); int q6afe_port_stop(struct q6afe_port *port); void q6afe_port_put(struct q6afe_port *port); int q6afe_get_port_id(int index); +void q6afe_usb_port_prepare(struct q6afe_port *port, + struct q6afe_usb_cfg *cfg); void q6afe_hdmi_port_prepare(struct q6afe_port *port, struct q6afe_hdmi_cfg *cfg); void q6afe_slim_port_prepare(struct q6afe_port *port, diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c index 4919001de08b..4a96b11f7fd1 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -97,6 +97,26 @@ } static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { + { + .playback = { + .stream_name = "USB Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | + SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .id = USB_RX, + .name = "USB_RX", + }, { .playback = { .stream_name = "HDMI Playback", @@ -624,6 +644,9 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, case WSA_CODEC_DMA_RX_0 ... RX_CODEC_DMA_RX_7: q6dsp_audio_fe_dais[i].ops = cfg->q6dma_ops; break; + case USB_RX: + q6dsp_audio_fe_dais[i].ops = cfg->q6usb_ops; + break; default: break; } diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h index 7f052c8a1257..d8dde6dd0aca 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h @@ -11,6 +11,7 @@ struct q6dsp_audio_port_dai_driver_config { const struct snd_soc_dai_ops *q6i2s_ops; const struct snd_soc_dai_ops *q6tdm_ops; const struct snd_soc_dai_ops *q6dma_ops; + const struct snd_soc_dai_ops *q6usb_ops; }; struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c index bba07899f8fc..a57f45950ae3 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -514,6 +514,9 @@ static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, return 1; } +static const struct snd_kcontrol_new usb_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(USB_RX) }; + static const struct snd_kcontrol_new hdmi_mixer_controls[] = { Q6ROUTING_RX_MIXERS(HDMI_RX) }; @@ -949,6 +952,10 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { SND_SOC_DAPM_MIXER("MultiMedia8 Mixer", SND_SOC_NOPM, 0, 0, mmul8_mixer_controls, ARRAY_SIZE(mmul8_mixer_controls)), + SND_SOC_DAPM_MIXER("USB Mixer", SND_SOC_NOPM, 0, 0, + usb_mixer_controls, + ARRAY_SIZE(usb_mixer_controls)), + }; static const struct snd_soc_dapm_route intercon[] = { @@ -1042,6 +1049,8 @@ static const struct snd_soc_dapm_route intercon[] = { {"MM_UL6", NULL, "MultiMedia6 Mixer"}, {"MM_UL7", NULL, "MultiMedia7 Mixer"}, {"MM_UL8", NULL, "MultiMedia8 Mixer"}, + + Q6ROUTING_RX_DAPM_ROUTE("USB Mixer", "USB_RX"), }; static int routing_hw_params(struct snd_soc_component *component,
The QC ADSP is able to support USB playback endpoints, so that the main application processor can be placed into lower CPU power modes. This adds the required AFE port configurations and port start command to start an audio session. Specifically, the QC ADSP can support all potential endpoints that are exposed by the audio data interface. This includes, feedback endpoints (both implicit and explicit) as well as the isochronous (data) endpoints. The size of audio samples sent per USB frame (microframe) will be adjusted based on information received on the feedback endpoint. Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> --- sound/soc/qcom/qdsp6/q6afe-dai.c | 56 +++++++ sound/soc/qcom/qdsp6/q6afe.c | 183 ++++++++++++++++++++++- sound/soc/qcom/qdsp6/q6afe.h | 35 ++++- sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 23 +++ sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h | 1 + sound/soc/qcom/qdsp6/q6routing.c | 9 ++ 6 files changed, 305 insertions(+), 2 deletions(-)