diff options
Diffstat (limited to 'sound/soc/qcom/qdsp6')
-rw-r--r-- | sound/soc/qcom/qdsp6/Makefile | 1 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6afe-dai.c | 60 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6afe.c | 192 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6afe.h | 36 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6apm-dai.c | 60 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6apm-lpass-dais.c | 2 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6apm.c | 20 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6apm.h | 3 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6asm-dai.c | 19 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 23 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h | 1 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6routing.c | 9 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6usb.c | 421 |
13 files changed, 806 insertions, 41 deletions
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 26b7c55c9c11..67267304e7e9 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o obj-$(CONFIG_SND_SOC_QDSP6_APM_LPASS_DAI) += q6apm-lpass-dais.o obj-$(CONFIG_SND_SOC_QDSP6_PRM) += q6prm.o obj-$(CONFIG_SND_SOC_QDSP6_PRM_LPASS_CLOCKS) += q6prm-clocks.o +obj-$(CONFIG_SND_SOC_QDSP6_USB) += q6usb.o diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c index 7d9628cda875..0f47aadaabe1 100644 --- a/sound/soc/qcom/qdsp6/q6afe-dai.c +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -92,6 +92,39 @@ static int q6hdmi_hw_params(struct snd_pcm_substream *substream, return 0; } +static int q6afe_usb_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: + 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) @@ -394,6 +427,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; } @@ -622,6 +659,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) @@ -649,6 +689,23 @@ static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) return 0; } +static const struct snd_soc_dai_ops q6afe_usb_ops = { + .probe = msm_dai_q6_dai_probe, + .prepare = q6afe_dai_prepare, + .hw_params = q6afe_usb_hw_params, + /* + * Shutdown callback required to stop the USB AFE port, which is enabled + * by the prepare() stage. This stops the audio traffic on the USB AFE + * port on the Q6DSP. + */ + .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, @@ -947,6 +1004,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 = { @@ -1061,6 +1120,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 = &q6afe_usb_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 ef7557be5d66..7b59d514b432 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -35,6 +35,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 @@ -44,6 +46,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 @@ -72,12 +75,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 @@ -359,7 +366,7 @@ #define AFE_API_VERSION_SLOT_MAPPING_CONFIG 1 #define AFE_API_VERSION_CODEC_DMA_CONFIG 1 -#define TIMEOUT_MS 1000 +#define TIMEOUT_MS 3000 #define AFE_CMD_RESP_AVAIL 0 #define AFE_CMD_RESP_NONE 1 #define AFE_CLK_TOKEN 1024 @@ -513,12 +520,96 @@ 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; + +#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; @@ -833,6 +924,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) @@ -1291,6 +1383,99 @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, EXPORT_SYMBOL_GPL(q6afe_tdm_port_prepare); /** + * afe_port_send_usb_dev_param() - Send USB dev token + * + * @port: Instance of afe port + * @cardidx: USB SND card index to reference + * @pcmidx: USB SND PCM device index to reference + * + * The USB dev token carries information about which USB SND card instance and + * PCM device to execute the offload on. This information is carried through + * to the stream enable QMI request, which is handled by the offload class + * driver. The information is parsed to determine which USB device to query + * the required resources for. + */ +int afe_port_send_usb_dev_param(struct q6afe_port *port, int cardidx, int pcmidx) +{ + struct afe_param_id_usb_audio_dev_params usb_dev; + int ret; + + memset(&usb_dev, 0, sizeof(usb_dev)); + + usb_dev.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; + usb_dev.dev_token = (cardidx << 16) | (pcmidx << 8); + 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); + + return ret; +} +EXPORT_SYMBOL_GPL(afe_port_send_usb_dev_param); + +static int afe_port_send_usb_params(struct q6afe_port *port, struct q6afe_usb_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + struct afe_param_id_usb_audio_dev_lpcm_fmt lpcm_fmt; + struct afe_param_id_usb_audio_svc_interval svc_int; + int ret; + + if (!pcfg) { + dev_err(port->afe->dev, "%s: Error, no configuration data\n", __func__); + return -EINVAL; + } + + memset(&lpcm_fmt, 0, sizeof(lpcm_fmt)); + memset(&svc_int, 0, sizeof(svc_int)); + + 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); + return ret; + } + + 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); + + 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_params(port, cfg); +} +EXPORT_SYMBOL_GPL(q6afe_usb_port_prepare); + +/** * q6afe_hdmi_port_prepare() - Prepare hdmi afe port. * * @port: Instance of afe port @@ -1612,7 +1797,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 65d0676075e1..a29abe4ce436 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -3,7 +3,7 @@ #ifndef __Q6AFE_H__ #define __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 @@ -203,6 +203,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; @@ -210,6 +240,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; @@ -219,6 +250,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, @@ -228,6 +261,7 @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, struct q6afe_tdm_cfg *cfg); void q6afe_cdc_dma_port_prepare(struct q6afe_port *port, struct q6afe_cdc_dma_cfg *cfg); +int afe_port_send_usb_dev_param(struct q6afe_port *port, int cardidx, int pcmidx); int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id, int clk_src, int clk_root, unsigned int freq, int dir); diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index c9404b5934c7..2cd522108221 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -24,8 +24,8 @@ #define PLAYBACK_MIN_PERIOD_SIZE 128 #define CAPTURE_MIN_NUM_PERIODS 2 #define CAPTURE_MAX_NUM_PERIODS 8 -#define CAPTURE_MAX_PERIOD_SIZE 4096 -#define CAPTURE_MIN_PERIOD_SIZE 320 +#define CAPTURE_MAX_PERIOD_SIZE 65536 +#define CAPTURE_MIN_PERIOD_SIZE 6144 #define BUFFER_BYTES_MAX (PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE) #define BUFFER_BYTES_MIN (PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE) #define COMPR_PLAYBACK_MAX_FRAGMENT_SIZE (128 * 1024) @@ -64,12 +64,12 @@ struct q6apm_dai_rtd { phys_addr_t phys; unsigned int pcm_size; unsigned int pcm_count; - unsigned int pos; /* Buffer position */ unsigned int periods; unsigned int bytes_sent; unsigned int bytes_received; unsigned int copied_total; uint16_t bits_per_sample; + snd_pcm_uframes_t queue_ptr; bool next_track; enum stream_state state; struct q6apm_graph *graph; @@ -123,25 +123,16 @@ static void event_handler(uint32_t opcode, uint32_t token, void *payload, void * { struct q6apm_dai_rtd *prtd = priv; struct snd_pcm_substream *substream = prtd->substream; - unsigned long flags; switch (opcode) { case APM_CLIENT_EVENT_CMD_EOS_DONE: prtd->state = Q6APM_STREAM_STOPPED; break; case APM_CLIENT_EVENT_DATA_WRITE_DONE: - spin_lock_irqsave(&prtd->lock, flags); - prtd->pos += prtd->pcm_count; - spin_unlock_irqrestore(&prtd->lock, flags); snd_pcm_period_elapsed(substream); - if (prtd->state == Q6APM_STREAM_RUNNING) - q6apm_write_async(prtd->graph, prtd->pcm_count, 0, 0, 0); break; case APM_CLIENT_EVENT_DATA_READ_DONE: - spin_lock_irqsave(&prtd->lock, flags); - prtd->pos += prtd->pcm_count; - spin_unlock_irqrestore(&prtd->lock, flags); snd_pcm_period_elapsed(substream); if (prtd->state == Q6APM_STREAM_RUNNING) q6apm_read(prtd->graph); @@ -248,7 +239,6 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, } prtd->pcm_count = snd_pcm_lib_period_bytes(substream); - prtd->pos = 0; /* rate and channels are sent to audio driver */ ret = q6apm_graph_media_format_shmem(prtd->graph, &cfg); if (ret < 0) { @@ -294,6 +284,27 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, return 0; } +static int q6apm_dai_ack(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; + int i, ret = 0, avail_periods; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + avail_periods = (runtime->control->appl_ptr - prtd->queue_ptr)/runtime->period_size; + for (i = 0; i < avail_periods; i++) { + ret = q6apm_write_async(prtd->graph, prtd->pcm_count, 0, 0, NO_TIMESTAMP); + if (ret < 0) { + dev_err(component->dev, "Error queuing playback buffer %d\n", ret); + return ret; + } + prtd->queue_ptr += runtime->period_size; + } + } + + return ret; +} + static int q6apm_dai_trigger(struct snd_soc_component *component, struct snd_pcm_substream *substream, int cmd) { @@ -305,9 +316,6 @@ static int q6apm_dai_trigger(struct snd_soc_component *component, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - /* start writing buffers for playback only as we already queued capture buffers */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - ret = q6apm_write_async(prtd->graph, prtd->pcm_count, 0, 0, 0); break; case SNDRV_PCM_TRIGGER_STOP: /* TODO support be handled via SoftPause Module */ @@ -377,13 +385,14 @@ static int q6apm_dai_open(struct snd_soc_component *component, } } - ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + /* setup 10ms latency to accommodate DSP restrictions */ + ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 480); if (ret < 0) { dev_err(dev, "constraint for period bytes step ret = %d\n", ret); goto err; } - ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 480); if (ret < 0) { dev_err(dev, "constraint for buffer bytes step ret = %d\n", ret); goto err; @@ -428,16 +437,12 @@ static snd_pcm_uframes_t q6apm_dai_pointer(struct snd_soc_component *component, struct snd_pcm_runtime *runtime = substream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; snd_pcm_uframes_t ptr; - unsigned long flags; - spin_lock_irqsave(&prtd->lock, flags); - if (prtd->pos == prtd->pcm_size) - prtd->pos = 0; - - ptr = bytes_to_frames(runtime, prtd->pos); - spin_unlock_irqrestore(&prtd->lock, flags); + ptr = q6apm_get_hw_pointer(prtd->graph, substream->stream) * runtime->period_size; + if (ptr) + return ptr - 1; - return ptr; + return 0; } static int q6apm_dai_hw_params(struct snd_soc_component *component, @@ -652,8 +657,6 @@ static int q6apm_dai_compr_set_params(struct snd_soc_component *component, prtd->pcm_size = runtime->fragments * runtime->fragment_size; prtd->bits_per_sample = 16; - prtd->pos = 0; - if (prtd->next_track != true) { memcpy(&prtd->codec, codec, sizeof(*codec)); @@ -836,6 +839,7 @@ static const struct snd_soc_component_driver q6apm_fe_dai_component = { .hw_params = q6apm_dai_hw_params, .pointer = q6apm_dai_pointer, .trigger = q6apm_dai_trigger, + .ack = q6apm_dai_ack, .compress_ops = &q6apm_dai_compress_ops, .use_dai_pcm_id = true, }; diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index 9c98a35ad099..a0d90462fd6a 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -206,7 +206,7 @@ static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct s rc = q6apm_graph_start(dai_data->graph[dai->id]); if (rc < 0) { - dev_err(dai->dev, "fail to start APM port %x\n", dai->id); + dev_err(dai->dev, "Failed to start APM port %d\n", dai->id); goto err; } dai_data->is_port_started[dai->id] = true; diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 2a2a5bd98110..b4ffa0f0b188 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -230,7 +230,7 @@ int q6apm_map_memory_regions(struct q6apm_graph *graph, unsigned int dir, phys_a return 0; } - buf = kzalloc(((sizeof(struct audio_buffer)) * periods), GFP_KERNEL); + buf = kcalloc(periods, sizeof(struct audio_buffer), GFP_KERNEL); if (!buf) { mutex_unlock(&graph->lock); return -ENOMEM; @@ -494,6 +494,19 @@ int q6apm_read(struct q6apm_graph *graph) } EXPORT_SYMBOL_GPL(q6apm_read); +int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir) +{ + struct audioreach_graph_data *data; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + data = &graph->rx_data; + else + data = &graph->tx_data; + + return (int)atomic_read(&data->hw_ptr); +} +EXPORT_SYMBOL_GPL(q6apm_get_hw_pointer); + static int graph_callback(struct gpr_resp_pkt *data, void *priv, int op) { struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done_v2 *rd_done; @@ -520,7 +533,8 @@ static int graph_callback(struct gpr_resp_pkt *data, void *priv, int op) done = data->payload; phys = graph->rx_data.buf[token].phys; mutex_unlock(&graph->lock); - + /* token numbering starts at 0 */ + 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; @@ -553,6 +567,8 @@ static int graph_callback(struct gpr_resp_pkt *data, void *priv, int op) rd_done = data->payload; phys = graph->tx_data.buf[hdr->token].phys; mutex_unlock(&graph->lock); + /* token numbering starts at 0 */ + atomic_set(&graph->tx_data.hw_ptr, hdr->token + 1); if (upper_32_bits(phys) == rd_done->buf_addr_msw && lower_32_bits(phys) == rd_done->buf_addr_lsw) { diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index c248c8d2b1ab..7ce08b401e31 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -2,6 +2,7 @@ #ifndef __Q6APM_H__ #define __Q6APM_H__ #include <linux/types.h> +#include <linux/atomic.h> #include <linux/slab.h> #include <linux/wait.h> #include <linux/kernel.h> @@ -77,6 +78,7 @@ struct audioreach_graph_data { uint32_t num_periods; uint32_t dsp_buf; uint32_t mem_map_handle; + atomic_t hw_ptr; }; struct audioreach_graph { @@ -150,4 +152,5 @@ int q6apm_enable_compress_module(struct device *dev, struct q6apm_graph *graph, int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples); int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples); int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, uint32_t codec_id); +int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir); #endif /* __APM_GRAPH_ */ diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index 045100c94352..a400c9a31fea 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -892,9 +892,7 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component, if (ret < 0) { dev_err(dev, "q6asm_open_write failed\n"); - q6asm_audio_client_free(prtd->audio_client); - prtd->audio_client = NULL; - return ret; + goto open_err; } } @@ -903,7 +901,7 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component, prtd->session_id, dir); if (ret) { dev_err(dev, "Stream reg failed ret:%d\n", ret); - return ret; + goto q6_err; } ret = __q6asm_dai_compr_set_codec_params(component, stream, @@ -911,7 +909,7 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component, prtd->stream_id); if (ret) { dev_err(dev, "codec param setup failed ret:%d\n", ret); - return ret; + goto q6_err; } ret = q6asm_map_memory_regions(dir, prtd->audio_client, prtd->phys, @@ -920,12 +918,21 @@ static int q6asm_dai_compr_set_params(struct snd_soc_component *component, if (ret < 0) { dev_err(dev, "Buffer Mapping failed ret:%d\n", ret); - return -ENOMEM; + ret = -ENOMEM; + goto q6_err; } prtd->state = Q6ASM_STREAM_RUNNING; return 0; + +q6_err: + q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE); + +open_err: + q6asm_audio_client_free(prtd->audio_client); + prtd->audio_client = NULL; + return ret; } static int q6asm_dai_compr_set_metadata(struct snd_soc_component *component, diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c index 4919001de08b..4eed54b071a5 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -99,6 +99,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", .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | @@ -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 90228699ba7d..f49243daa517 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -435,6 +435,7 @@ static struct session_data *get_session_from_id(struct msm_routing_data *data, return NULL; } + /** * q6routing_stream_close() - Deregister a stream * @@ -515,6 +516,9 @@ static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, return 1; } +static const struct snd_kcontrol_new usb_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(USB_RX) }; + static const struct snd_kcontrol_new hdmi_mixer_controls[] = { Q6ROUTING_RX_MIXERS(HDMI_RX) }; @@ -933,6 +937,9 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, rx_codec_dma_rx_7_mixer_controls, ARRAY_SIZE(rx_codec_dma_rx_7_mixer_controls)), + SND_SOC_DAPM_MIXER("USB_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + usb_rx_mixer_controls, + ARRAY_SIZE(usb_rx_mixer_controls)), SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, @@ -949,7 +956,6 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { mmul7_mixer_controls, ARRAY_SIZE(mmul7_mixer_controls)), SND_SOC_DAPM_MIXER("MultiMedia8 Mixer", SND_SOC_NOPM, 0, 0, mmul8_mixer_controls, ARRAY_SIZE(mmul8_mixer_controls)), - }; static const struct snd_soc_dapm_route intercon[] = { @@ -1026,6 +1032,7 @@ static const struct snd_soc_dapm_route intercon[] = { Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_5 Audio Mixer", "RX_CODEC_DMA_RX_5"), Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_6 Audio Mixer", "RX_CODEC_DMA_RX_6"), Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_7 Audio Mixer", "RX_CODEC_DMA_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("USB_RX Audio Mixer", "USB_RX"), Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"), Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"), Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"), diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c new file mode 100644 index 000000000000..ebe0c2425927 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6usb.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <linux/auxiliary_bus.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/dma-map-ops.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/iommu.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <sound/asound.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/q6usboffload.h> +#include <sound/soc.h> +#include <sound/soc-usb.h> + +#include <dt-bindings/sound/qcom,q6afe.h> + +#include "q6afe.h" +#include "q6dsp-lpass-ports.h" + +#define Q6_USB_SID_MASK 0xF + +struct q6usb_port_data { + struct auxiliary_device uauxdev; + struct q6afe_usb_cfg usb_cfg; + struct snd_soc_usb *usb; + struct snd_soc_jack *hs_jack; + struct q6usb_offload priv; + + /* Protects against operations between SOC USB and ASoC */ + struct mutex mutex; + struct list_head devices; +}; + +static const struct snd_soc_dapm_widget q6usb_dai_widgets[] = { + SND_SOC_DAPM_HP("USB_RX_BE", NULL), +}; + +static const struct snd_soc_dapm_route q6usb_dapm_routes[] = { + {"USB Playback", NULL, "USB_RX_BE"}, +}; + +static int q6usb_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6usb_port_data *data = dev_get_drvdata(dai->dev); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int direction = substream->stream; + struct q6afe_port *q6usb_afe; + struct snd_soc_usb_device *sdev; + int ret = -EINVAL; + + mutex_lock(&data->mutex); + + /* No active chip index */ + if (list_empty(&data->devices)) + goto out; + + sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list); + + ret = snd_soc_usb_find_supported_format(sdev->chip_idx, params, direction); + if (ret < 0) + goto out; + + q6usb_afe = q6afe_port_get_from_id(cpu_dai->dev, USB_RX); + if (IS_ERR(q6usb_afe)) { + ret = PTR_ERR(q6usb_afe); + goto out; + } + + /* Notify audio DSP about the devices being offloaded */ + ret = afe_port_send_usb_dev_param(q6usb_afe, sdev->card_idx, + sdev->ppcm_idx[sdev->num_playback - 1]); + +out: + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct snd_soc_dai_ops q6usb_ops = { + .hw_params = q6usb_hw_params, +}; + +static struct snd_soc_dai_driver q6usb_be_dais[] = { + { + .playback = { + .stream_name = "USB BE RX", + .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_max = 192000, + .rate_min = 8000, + }, + .id = USB_RX, + .name = "USB_RX_BE", + .ops = &q6usb_ops, + }, +}; + +static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + int id = args->args[0]; + int ret = -EINVAL; + int i; + + for (i = 0; i < ARRAY_SIZE(q6usb_be_dais); i++) { + if (q6usb_be_dais[i].id == id) { + *dai_name = q6usb_be_dais[i].name; + ret = 0; + break; + } + } + + return ret; +} + +static int q6usb_get_pcm_id_from_widget(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *dai; + + for_each_card_rtds(w->dapm->card, rtd) { + dai = snd_soc_rtd_to_cpu(rtd, 0); + /* + * Only look for playback widget. RTD number carries the assigned + * PCM index. + */ + if (dai->stream[0].widget == w) + return rtd->id; + } + + return -1; +} + +static int q6usb_usb_mixer_enabled(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *p; + + /* Checks to ensure USB path is enabled/connected */ + snd_soc_dapm_widget_for_each_sink_path(w, p) + if (!strcmp(p->sink->name, "USB Mixer") && p->connect) + return 1; + + return 0; +} + +static int q6usb_get_pcm_id(struct snd_soc_component *component) +{ + struct snd_soc_dapm_widget *w; + struct snd_soc_dapm_path *p; + int pidx; + + /* + * Traverse widgets to find corresponding FE widget. The DAI links are + * built like the following: + * MultiMedia* <-> MM_DL* <-> USB Mixer* + */ + for_each_card_widgets(component->card, w) { + if (!strncmp(w->name, "MultiMedia", 10)) { + /* + * Look up all paths associated with the FE widget to see if + * the USB BE is enabled. The sink widget is responsible to + * link with the USB mixers. + */ + snd_soc_dapm_widget_for_each_sink_path(w, p) { + if (q6usb_usb_mixer_enabled(p->sink)) { + pidx = q6usb_get_pcm_id_from_widget(w); + return pidx; + } + } + } + } + + return -1; +} + +static int q6usb_update_offload_route(struct snd_soc_component *component, int card, + int pcm, int direction, enum snd_soc_usb_kctl path, + long *route) +{ + struct q6usb_port_data *data = dev_get_drvdata(component->dev); + struct snd_soc_usb_device *sdev; + int ret = 0; + int idx = -1; + + mutex_lock(&data->mutex); + + if (list_empty(&data->devices) || + direction == SNDRV_PCM_STREAM_CAPTURE) { + ret = -ENODEV; + goto out; + } + + sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list); + + /* + * Will always look for last PCM device discovered/probed as the + * active offload index. + */ + if (card == sdev->card_idx && + pcm == sdev->ppcm_idx[sdev->num_playback - 1]) { + idx = path == SND_SOC_USB_KCTL_CARD_ROUTE ? + component->card->snd_card->number : + q6usb_get_pcm_id(component); + } + +out: + route[0] = idx; + mutex_unlock(&data->mutex); + + return ret; +} + +static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, + struct snd_soc_usb_device *sdev, bool connected) +{ + struct q6usb_port_data *data; + + if (!usb->component) + return -ENODEV; + + data = dev_get_drvdata(usb->component->dev); + + mutex_lock(&data->mutex); + if (connected) { + if (data->hs_jack) + snd_jack_report(data->hs_jack->jack, SND_JACK_USB); + + /* Selects the latest USB headset plugged in for offloading */ + list_add_tail(&sdev->list, &data->devices); + } else { + list_del(&sdev->list); + + if (data->hs_jack) + snd_jack_report(data->hs_jack->jack, 0); + } + mutex_unlock(&data->mutex); + + return 0; +} + +static void q6usb_component_disable_jack(struct q6usb_port_data *data) +{ + /* Offload jack has already been disabled */ + if (!data->hs_jack) + return; + + snd_jack_report(data->hs_jack->jack, 0); + data->hs_jack = NULL; +} + +static void q6usb_component_enable_jack(struct q6usb_port_data *data, + struct snd_soc_jack *jack) +{ + snd_jack_report(jack->jack, !list_empty(&data->devices) ? SND_JACK_USB : 0); + data->hs_jack = jack; +} + +static int q6usb_component_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *priv) +{ + struct q6usb_port_data *data = dev_get_drvdata(component->dev); + + mutex_lock(&data->mutex); + if (jack) + q6usb_component_enable_jack(data, jack); + else + q6usb_component_disable_jack(data); + mutex_unlock(&data->mutex); + + return 0; +} + +static void q6usb_dai_aux_release(struct device *dev) {} + +static int q6usb_dai_add_aux_device(struct q6usb_port_data *data, + struct auxiliary_device *auxdev) +{ + int ret; + + auxdev->dev.parent = data->priv.dev; + auxdev->dev.release = q6usb_dai_aux_release; + auxdev->name = "qc-usb-audio-offload"; + + ret = auxiliary_device_init(auxdev); + if (ret) + return ret; + + ret = auxiliary_device_add(auxdev); + if (ret) + auxiliary_device_uninit(auxdev); + + return ret; +} + +static int q6usb_component_probe(struct snd_soc_component *component) +{ + struct q6usb_port_data *data = dev_get_drvdata(component->dev); + struct snd_soc_usb *usb; + int ret; + + /* Add the QC USB SND aux device */ + ret = q6usb_dai_add_aux_device(data, &data->uauxdev); + if (ret < 0) + return ret; + + usb = snd_soc_usb_allocate_port(component, &data->priv); + if (IS_ERR(usb)) + return -ENOMEM; + + usb->connection_status_cb = q6usb_alsa_connection_cb; + usb->update_offload_route_info = q6usb_update_offload_route; + + snd_soc_usb_add_port(usb); + data->usb = usb; + + return 0; +} + +static void q6usb_component_remove(struct snd_soc_component *component) +{ + struct q6usb_port_data *data = dev_get_drvdata(component->dev); + + snd_soc_usb_remove_port(data->usb); + auxiliary_device_delete(&data->uauxdev); + auxiliary_device_uninit(&data->uauxdev); + snd_soc_usb_free_port(data->usb); +} + +static const struct snd_soc_component_driver q6usb_dai_component = { + .probe = q6usb_component_probe, + .set_jack = q6usb_component_set_jack, + .remove = q6usb_component_remove, + .name = "q6usb-dai-component", + .dapm_widgets = q6usb_dai_widgets, + .num_dapm_widgets = ARRAY_SIZE(q6usb_dai_widgets), + .dapm_routes = q6usb_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(q6usb_dapm_routes), + .of_xlate_dai_name = q6usb_audio_ports_of_xlate_dai_name, +}; + +static int q6usb_dai_dev_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct q6usb_port_data *data; + struct device *dev = &pdev->dev; + struct of_phandle_args args; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = of_property_read_u16(node, "qcom,usb-audio-intr-idx", + &data->priv.intr_num); + if (ret) { + dev_err(&pdev->dev, "failed to read intr idx.\n"); + return ret; + } + + ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); + if (!ret) + data->priv.sid = args.args[0] & Q6_USB_SID_MASK; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret < 0) + return ret; + + data->priv.domain = iommu_get_domain_for_dev(&pdev->dev); + + data->priv.dev = dev; + INIT_LIST_HEAD(&data->devices); + dev_set_drvdata(dev, data); + + return devm_snd_soc_register_component(dev, &q6usb_dai_component, + q6usb_be_dais, ARRAY_SIZE(q6usb_be_dais)); +} + +static const struct of_device_id q6usb_dai_device_id[] = { + { .compatible = "qcom,q6usb" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6usb_dai_device_id); + +static struct platform_driver q6usb_dai_platform_driver = { + .driver = { + .name = "q6usb-dai", + .of_match_table = q6usb_dai_device_id, + }, + .probe = q6usb_dai_dev_probe, + /* + * Remove not required as resources are cleaned up as part of + * component removal. Others are device managed resources. + */ +}; +module_platform_driver(q6usb_dai_platform_driver); + +MODULE_DESCRIPTION("Q6 USB backend dai driver"); +MODULE_LICENSE("GPL"); |