summaryrefslogtreecommitdiff
path: root/sound/soc/qcom/qdsp6
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/qcom/qdsp6')
-rw-r--r--sound/soc/qcom/qdsp6/Makefile1
-rw-r--r--sound/soc/qcom/qdsp6/q6afe-dai.c60
-rw-r--r--sound/soc/qcom/qdsp6/q6afe.c192
-rw-r--r--sound/soc/qcom/qdsp6/q6afe.h36
-rw-r--r--sound/soc/qcom/qdsp6/q6apm-dai.c60
-rw-r--r--sound/soc/qcom/qdsp6/q6apm-lpass-dais.c2
-rw-r--r--sound/soc/qcom/qdsp6/q6apm.c20
-rw-r--r--sound/soc/qcom/qdsp6/q6apm.h3
-rw-r--r--sound/soc/qcom/qdsp6/q6asm-dai.c19
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c23
-rw-r--r--sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h1
-rw-r--r--sound/soc/qcom/qdsp6/q6routing.c9
-rw-r--r--sound/soc/qcom/qdsp6/q6usb.c421
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");