diff options
author | Mark Brown | 2023-06-20 15:24:06 +0100 |
---|---|---|
committer | Mark Brown | 2023-06-20 15:24:06 +0100 |
commit | d4b2aee1be41adbbbe9132104620dfc70032a044 (patch) | |
tree | 472225e04deb1a602fbb17d24cc6e9da86a3af2a /sound/soc | |
parent | 29735f6fb0f57c8010c9486216361c0f68c90226 (diff) | |
parent | c317d148a2b02c4756832fb4bd00a6480d874606 (diff) |
ASoC: qcom: audioreach: add compress offload
Merge series from Srinivas Kandagatla <srinivas.kandagatla@linaro.org>:
This patchset adds compressed offload support to Qualcomm audioreach drivers.
Currently it supports AAC, MP3 and FALC along with gapless.
Tested this on SM8450 and sc7280.
Diffstat (limited to 'sound/soc')
-rw-r--r-- | sound/soc/qcom/qdsp6/audioreach.c | 250 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/audioreach.h | 51 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6apm-dai.c | 445 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6apm.c | 68 | ||||
-rw-r--r-- | sound/soc/qcom/qdsp6/q6apm.h | 6 | ||||
-rw-r--r-- | sound/soc/qcom/sc7280.c | 23 |
6 files changed, 747 insertions, 96 deletions
diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index 8d9410dcbd45..5974c7929dd3 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -732,33 +732,32 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, return rc; } -static int audioreach_sal_limiter_enable(struct q6apm_graph *graph, - struct audioreach_module *module, bool enable) +int audioreach_send_u32_param(struct q6apm_graph *graph, struct audioreach_module *module, + uint32_t param_id, uint32_t param_val) { struct apm_module_param_data *param_data; - struct param_id_sal_limiter_enable *limiter_enable; - int payload_size; struct gpr_pkt *pkt; - int rc; + uint32_t *param; + int rc, payload_size; void *p; - payload_size = sizeof(*limiter_enable) + APM_MODULE_PARAM_DATA_SIZE; - - pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); - if (IS_ERR(pkt)) - return PTR_ERR(pkt); + payload_size = sizeof(uint32_t) + APM_MODULE_PARAM_DATA_SIZE; + p = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); + if (IS_ERR(p)) + return -ENOMEM; - p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + pkt = p; + p = p + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; param_data = p; param_data->module_instance_id = module->instance_id; param_data->error_code = 0; - param_data->param_id = PARAM_ID_SAL_LIMITER_ENABLE; - param_data->param_size = sizeof(*limiter_enable); - p = p + APM_MODULE_PARAM_DATA_SIZE; - limiter_enable = p; + param_data->param_id = param_id; + param_data->param_size = sizeof(uint32_t); - limiter_enable->enable_lim = enable; + p = p + APM_MODULE_PARAM_DATA_SIZE; + param = p; + *param = param_val; rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); @@ -766,77 +765,34 @@ static int audioreach_sal_limiter_enable(struct q6apm_graph *graph, return rc; } +EXPORT_SYMBOL_GPL(audioreach_send_u32_param); + +static int audioreach_sal_limiter_enable(struct q6apm_graph *graph, + struct audioreach_module *module, bool enable) +{ + return audioreach_send_u32_param(graph, module, PARAM_ID_SAL_LIMITER_ENABLE, enable); +} static int audioreach_sal_set_media_format(struct q6apm_graph *graph, struct audioreach_module *module, struct audioreach_module_config *cfg) { - struct apm_module_param_data *param_data; - struct param_id_sal_output_config *media_format; - int payload_size; - struct gpr_pkt *pkt; - int rc; - void *p; - - payload_size = sizeof(*media_format) + APM_MODULE_PARAM_DATA_SIZE; - - pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); - if (IS_ERR(pkt)) - return PTR_ERR(pkt); - - p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; - - param_data = p; - param_data->module_instance_id = module->instance_id; - param_data->error_code = 0; - param_data->param_id = PARAM_ID_SAL_OUTPUT_CFG; - param_data->param_size = sizeof(*media_format); - p = p + APM_MODULE_PARAM_DATA_SIZE; - media_format = p; - - media_format->bits_per_sample = cfg->bit_width; - - rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); - - kfree(pkt); - - return rc; + return audioreach_send_u32_param(graph, module, PARAM_ID_SAL_OUTPUT_CFG, cfg->bit_width); } static int audioreach_module_enable(struct q6apm_graph *graph, struct audioreach_module *module, bool enable) { - struct apm_module_param_data *param_data; - struct param_id_module_enable *param; - int payload_size; - struct gpr_pkt *pkt; - int rc; - void *p; - - payload_size = sizeof(*param) + APM_MODULE_PARAM_DATA_SIZE; - - pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); - if (IS_ERR(pkt)) - return PTR_ERR(pkt); - - p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; - - param_data = p; - param_data->module_instance_id = module->instance_id; - param_data->error_code = 0; - param_data->param_id = PARAM_ID_MODULE_ENABLE; - param_data->param_size = sizeof(*param); - p = p + APM_MODULE_PARAM_DATA_SIZE; - param = p; - - param->enable = enable; - - rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); - - kfree(pkt); + return audioreach_send_u32_param(graph, module, PARAM_ID_MODULE_ENABLE, enable); +} - return rc; +static int audioreach_gapless_set_media_format(struct q6apm_graph *graph, + struct audioreach_module *module, + struct audioreach_module_config *cfg) +{ + return audioreach_send_u32_param(graph, module, PARAM_ID_EARLY_EOS_DELAY, + EARLY_EOS_DELAY_MS); } static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, @@ -886,6 +842,99 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, return rc; } +static int audioreach_set_compr_media_format(struct media_format *media_fmt_hdr, + void *p, struct audioreach_module_config *mcfg) +{ + struct payload_media_fmt_aac_t *aac_cfg; + struct payload_media_fmt_pcm *mp3_cfg; + struct payload_media_fmt_flac_t *flac_cfg; + + switch (mcfg->fmt) { + case SND_AUDIOCODEC_MP3: + media_fmt_hdr->data_format = DATA_FORMAT_RAW_COMPRESSED; + media_fmt_hdr->fmt_id = MEDIA_FMT_ID_MP3; + media_fmt_hdr->payload_size = 0; + p = p + sizeof(*media_fmt_hdr); + mp3_cfg = p; + mp3_cfg->sample_rate = mcfg->sample_rate; + mp3_cfg->bit_width = mcfg->bit_width; + mp3_cfg->alignment = PCM_LSB_ALIGNED; + mp3_cfg->bits_per_sample = mcfg->bit_width; + mp3_cfg->q_factor = mcfg->bit_width - 1; + mp3_cfg->endianness = PCM_LITTLE_ENDIAN; + mp3_cfg->num_channels = mcfg->num_channels; + + if (mcfg->num_channels == 1) { + mp3_cfg->channel_mapping[0] = PCM_CHANNEL_L; + } else if (mcfg->num_channels == 2) { + mp3_cfg->channel_mapping[0] = PCM_CHANNEL_L; + mp3_cfg->channel_mapping[1] = PCM_CHANNEL_R; + } + break; + case SND_AUDIOCODEC_AAC: + media_fmt_hdr->data_format = DATA_FORMAT_RAW_COMPRESSED; + media_fmt_hdr->fmt_id = MEDIA_FMT_ID_AAC; + media_fmt_hdr->payload_size = sizeof(struct payload_media_fmt_aac_t); + p = p + sizeof(*media_fmt_hdr); + aac_cfg = p; + aac_cfg->aac_fmt_flag = 0; + aac_cfg->audio_obj_type = 5; + aac_cfg->num_channels = mcfg->num_channels; + aac_cfg->total_size_of_PCE_bits = 0; + aac_cfg->sample_rate = mcfg->sample_rate; + break; + case SND_AUDIOCODEC_FLAC: + media_fmt_hdr->data_format = DATA_FORMAT_RAW_COMPRESSED; + media_fmt_hdr->fmt_id = MEDIA_FMT_ID_FLAC; + media_fmt_hdr->payload_size = sizeof(struct payload_media_fmt_flac_t); + p = p + sizeof(*media_fmt_hdr); + flac_cfg = p; + flac_cfg->sample_size = mcfg->codec.options.flac_d.sample_size; + flac_cfg->num_channels = mcfg->num_channels; + flac_cfg->min_blk_size = mcfg->codec.options.flac_d.min_blk_size; + flac_cfg->max_blk_size = mcfg->codec.options.flac_d.max_blk_size; + flac_cfg->sample_rate = mcfg->sample_rate; + flac_cfg->min_frame_size = mcfg->codec.options.flac_d.min_frame_size; + flac_cfg->max_frame_size = mcfg->codec.options.flac_d.max_frame_size; + break; + default: + return -EINVAL; + } + + return 0; +} + +int audioreach_compr_set_param(struct q6apm_graph *graph, struct audioreach_module_config *mcfg) +{ + struct media_format *header; + struct gpr_pkt *pkt; + int iid, payload_size, rc; + void *p; + + payload_size = sizeof(struct apm_sh_module_media_fmt_cmd); + + iid = q6apm_graph_get_rx_shmem_module_iid(graph); + pkt = audioreach_alloc_cmd_pkt(payload_size, DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT, + 0, graph->port->id, iid); + + if (IS_ERR(pkt)) + return -ENOMEM; + + p = (void *)pkt + GPR_HDR_SIZE; + header = p; + rc = audioreach_set_compr_media_format(header, p, mcfg); + if (rc) { + kfree(pkt); + return rc; + } + + rc = gpr_send_port_pkt(graph->port, pkt); + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(audioreach_compr_set_param); + static int audioreach_i2s_set_media_format(struct q6apm_graph *graph, struct audioreach_module *module, struct audioreach_module_config *cfg) @@ -1089,25 +1138,33 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, p = p + APM_MODULE_PARAM_DATA_SIZE; header = p; - header->data_format = DATA_FORMAT_FIXED_POINT; - header->fmt_id = MEDIA_FMT_ID_PCM; - header->payload_size = payload_size - sizeof(*header); - - p = p + sizeof(*header); - cfg = p; - cfg->sample_rate = mcfg->sample_rate; - cfg->bit_width = mcfg->bit_width; - cfg->alignment = PCM_LSB_ALIGNED; - cfg->bits_per_sample = mcfg->bit_width; - cfg->q_factor = mcfg->bit_width - 1; - cfg->endianness = PCM_LITTLE_ENDIAN; - cfg->num_channels = mcfg->num_channels; - - if (mcfg->num_channels == 1) { - cfg->channel_mapping[0] = PCM_CHANNEL_L; - } else if (num_channels == 2) { - cfg->channel_mapping[0] = PCM_CHANNEL_L; - cfg->channel_mapping[1] = PCM_CHANNEL_R; + if (mcfg->fmt == SND_AUDIOCODEC_PCM) { + header->data_format = DATA_FORMAT_FIXED_POINT; + header->fmt_id = MEDIA_FMT_ID_PCM; + header->payload_size = payload_size - sizeof(*header); + + p = p + sizeof(*header); + cfg = p; + cfg->sample_rate = mcfg->sample_rate; + cfg->bit_width = mcfg->bit_width; + cfg->alignment = PCM_LSB_ALIGNED; + cfg->bits_per_sample = mcfg->bit_width; + cfg->q_factor = mcfg->bit_width - 1; + cfg->endianness = PCM_LITTLE_ENDIAN; + cfg->num_channels = mcfg->num_channels; + + if (mcfg->num_channels == 1) + cfg->channel_mapping[0] = PCM_CHANNEL_L; + else if (num_channels == 2) { + cfg->channel_mapping[0] = PCM_CHANNEL_L; + cfg->channel_mapping[1] = PCM_CHANNEL_R; + } + } else { + rc = audioreach_set_compr_media_format(header, p, mcfg); + if (rc) { + kfree(pkt); + return rc; + } } rc = audioreach_graph_send_cmd_sync(graph, pkt, 0); @@ -1192,6 +1249,8 @@ int audioreach_set_media_format(struct q6apm_graph *graph, struct audioreach_mod case MODULE_ID_PCM_DEC: case MODULE_ID_PCM_ENC: case MODULE_ID_PCM_CNV: + case MODULE_ID_PLACEHOLDER_DECODER: + case MODULE_ID_PLACEHOLDER_ENCODER: rc = audioreach_pcm_set_media_format(graph, module, cfg); break; case MODULE_ID_DISPLAY_PORT_SINK: @@ -1219,6 +1278,9 @@ int audioreach_set_media_format(struct q6apm_graph *graph, struct audioreach_mod case MODULE_ID_MFC: rc = audioreach_mfc_set_media_format(graph, module, cfg); break; + case MODULE_ID_GAPLESS: + rc = audioreach_gapless_set_media_format(graph, module, cfg); + break; default: rc = 0; } diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 3ebb81cd7cb0..e38111ffd7b9 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -15,6 +15,8 @@ struct q6apm_graph; #define MODULE_ID_PCM_CNV 0x07001003 #define MODULE_ID_PCM_ENC 0x07001004 #define MODULE_ID_PCM_DEC 0x07001005 +#define MODULE_ID_PLACEHOLDER_ENCODER 0x07001008 +#define MODULE_ID_PLACEHOLDER_DECODER 0x07001009 #define MODULE_ID_SAL 0x07001010 #define MODULE_ID_MFC 0x07001015 #define MODULE_ID_CODEC_DMA_SINK 0x07001023 @@ -22,6 +24,10 @@ struct q6apm_graph; #define MODULE_ID_I2S_SINK 0x0700100A #define MODULE_ID_I2S_SOURCE 0x0700100B #define MODULE_ID_DATA_LOGGING 0x0700101A +#define MODULE_ID_AAC_DEC 0x0700101F +#define MODULE_ID_FLAC_DEC 0x0700102F +#define MODULE_ID_MP3_DECODE 0x0700103B +#define MODULE_ID_GAPLESS 0x0700104D #define MODULE_ID_DISPLAY_PORT_SINK 0x07001069 #define APM_CMD_GET_SPF_STATE 0x01001021 @@ -143,12 +149,15 @@ struct param_id_enc_bitrate_param { } __packed; #define DATA_FORMAT_FIXED_POINT 1 +#define DATA_FORMAT_GENERIC_COMPRESSED 5 +#define DATA_FORMAT_RAW_COMPRESSED 6 #define PCM_LSB_ALIGNED 1 #define PCM_MSB_ALIGNED 2 #define PCM_LITTLE_ENDIAN 1 #define PCM_BIT_ENDIAN 2 #define MEDIA_FMT_ID_PCM 0x09001000 +#define MEDIA_FMT_ID_MP3 0x09001009 #define PCM_CHANNEL_L 1 #define PCM_CHANNEL_R 2 #define SAMPLE_RATE_48K 48000 @@ -226,6 +235,28 @@ struct apm_media_format { uint32_t payload_size; } __packed; +#define MEDIA_FMT_ID_FLAC 0x09001004 + +struct payload_media_fmt_flac_t { + uint16_t num_channels; + uint16_t sample_size; + uint16_t min_blk_size; + uint16_t max_blk_size; + uint32_t sample_rate; + uint32_t min_frame_size; + uint32_t max_frame_size; +} __packed; + +#define MEDIA_FMT_ID_AAC 0x09001001 + +struct payload_media_fmt_aac_t { + uint16_t aac_fmt_flag; + uint16_t audio_obj_type; + uint16_t num_channels; + uint16_t total_size_of_PCE_bits; + uint32_t sample_rate; +} __packed; + #define DATA_CMD_WR_SH_MEM_EP_EOS 0x04001002 #define WR_SH_MEM_EP_EOS_POLICY_LAST 1 #define WR_SH_MEM_EP_EOS_POLICY_EACH 2 @@ -522,6 +553,8 @@ struct param_id_sal_limiter_enable { } __packed; #define PARAM_ID_MFC_OUTPUT_MEDIA_FORMAT 0x08001024 +#define PARAM_ID_EARLY_EOS_DELAY 0x0800114C +#define EARLY_EOS_DELAY_MS 150 struct param_id_mfc_media_format { uint32_t sample_rate; @@ -530,6 +563,10 @@ struct param_id_mfc_media_format { uint16_t channel_mapping[]; } __packed; +struct param_id_gapless_early_eos_delay_t { + uint32_t early_eos_delay_ms; +} __packed; + struct media_format { uint32_t data_format; uint32_t fmt_id; @@ -608,6 +645,15 @@ struct param_id_vol_ctrl_master_gain { } __packed; +#define PARAM_ID_REMOVE_INITIAL_SILENCE 0x0800114B +#define PARAM_ID_REMOVE_TRAILING_SILENCE 0x0800115D + +#define PARAM_ID_REAL_MODULE_ID 0x0800100B + +struct param_id_placeholder_real_module_id { + uint32_t real_module_id; +} __packed; + /* Graph */ struct audioreach_connection { /* Connections */ @@ -716,6 +762,7 @@ struct audioreach_module_config { u32 channel_allocation; u32 sd_line_mask; int fmt; + struct snd_codec codec; u8 channel_map[AR_PCM_MAX_NUM_CHANNEL]; }; @@ -752,4 +799,8 @@ int audioreach_set_media_format(struct q6apm_graph *graph, int audioreach_shared_memory_send_eos(struct q6apm_graph *graph); int audioreach_gain_set_vol_ctrl(struct q6apm *apm, struct audioreach_module *module, int vol); +int audioreach_send_u32_param(struct q6apm_graph *graph, struct audioreach_module *module, + uint32_t param_id, uint32_t param_val); +int audioreach_compr_set_param(struct q6apm_graph *graph, struct audioreach_module_config *mcfg); + #endif /* __AUDIOREACH_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 7f02f5b2c33f..5eb0b864c740 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -28,8 +28,27 @@ #define CAPTURE_MIN_PERIOD_SIZE 320 #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) +#define COMPR_PLAYBACK_MAX_NUM_FRAGMENTS (16 * 4) +#define COMPR_PLAYBACK_MIN_FRAGMENT_SIZE (8 * 1024) +#define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4) #define SID_MASK_DEFAULT 0xF +static const struct snd_compr_codec_caps q6apm_compr_caps = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates = { 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000, 88200, + 96000, 176400, 192000 }, + .descriptor[0].num_sample_rates = 13, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 128, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = 0, + .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO, + .descriptor[0].formats = 0, +}; + enum stream_state { Q6APM_STREAM_IDLE = 0, Q6APM_STREAM_STOPPED, @@ -39,6 +58,7 @@ enum stream_state { struct q6apm_dai_rtd { struct snd_pcm_substream *substream; struct snd_compr_stream *cstream; + struct snd_codec codec; struct snd_compr_params codec_param; struct snd_dma_buffer dma_buffer; phys_addr_t phys; @@ -52,9 +72,13 @@ struct q6apm_dai_rtd { uint16_t bits_per_sample; uint16_t source; /* Encoding source bit mask */ uint16_t session_id; + bool next_track; enum stream_state state; struct q6apm_graph *graph; spinlock_t lock; + uint32_t initial_samples_drop; + uint32_t trailing_samples_drop; + bool notify_on_drain; }; struct q6apm_dai_data { @@ -132,6 +156,69 @@ static void event_handler(uint32_t opcode, uint32_t token, uint32_t *payload, vo } } +static void event_handler_compr(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6apm_dai_rtd *prtd = priv; + struct snd_compr_stream *substream = prtd->cstream; + unsigned long flags; + uint32_t wflags = 0; + uint64_t avail; + uint32_t bytes_written, bytes_to_write; + bool is_last_buffer = false; + + switch (opcode) { + case APM_CLIENT_EVENT_CMD_EOS_DONE: + spin_lock_irqsave(&prtd->lock, flags); + if (prtd->notify_on_drain) { + snd_compr_drain_notify(prtd->cstream); + prtd->notify_on_drain = false; + } else { + prtd->state = Q6APM_STREAM_STOPPED; + } + spin_unlock_irqrestore(&prtd->lock, flags); + break; + case APM_CLIENT_EVENT_DATA_WRITE_DONE: + spin_lock_irqsave(&prtd->lock, flags); + bytes_written = token >> APM_WRITE_TOKEN_LEN_SHIFT; + prtd->copied_total += bytes_written; + snd_compr_fragment_elapsed(substream); + + if (prtd->state != Q6APM_STREAM_RUNNING) { + spin_unlock_irqrestore(&prtd->lock, flags); + break; + } + + avail = prtd->bytes_received - prtd->bytes_sent; + + if (avail > prtd->pcm_count) { + bytes_to_write = prtd->pcm_count; + } else { + if (substream->partial_drain || prtd->notify_on_drain) + is_last_buffer = true; + bytes_to_write = avail; + } + + if (bytes_to_write) { + if (substream->partial_drain && is_last_buffer) + wflags |= APM_LAST_BUFFER_FLAG; + + q6apm_write_async(prtd->graph, + bytes_to_write, 0, 0, wflags); + + prtd->bytes_sent += bytes_to_write; + + if (prtd->notify_on_drain && is_last_buffer) + audioreach_shared_memory_send_eos(prtd->graph); + } + + spin_unlock_irqrestore(&prtd->lock, flags); + break; + default: + break; + } +} + static int q6apm_dai_prepare(struct snd_soc_component *component, struct snd_pcm_substream *substream) { @@ -155,6 +242,7 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, cfg.sample_rate = runtime->rate; cfg.num_channels = runtime->channels; cfg.bit_width = prtd->bits_per_sample; + cfg.fmt = SND_AUDIOCODEC_PCM; if (prtd->state) { /* clear the previous setup if any */ @@ -386,6 +474,362 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, component->dev, size); } +static int q6apm_dai_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *stream) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd; + struct q6apm_dai_data *pdata; + struct device *dev = component->dev; + int ret, size; + int graph_id; + + graph_id = cpu_dai->driver->id; + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) + return -EINVAL; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + prtd->cstream = stream; + prtd->graph = q6apm_graph_open(dev, (q6apm_cb)event_handler_compr, prtd, graph_id); + if (IS_ERR(prtd->graph)) { + ret = PTR_ERR(prtd->graph); + kfree(prtd); + return ret; + } + + runtime->private_data = prtd; + runtime->dma_bytes = BUFFER_BYTES_MAX; + size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE * COMPR_PLAYBACK_MAX_NUM_FRAGMENTS; + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, &prtd->dma_buffer); + if (ret) + return ret; + + if (pdata->sid < 0) + prtd->phys = prtd->dma_buffer.addr; + else + prtd->phys = prtd->dma_buffer.addr | (pdata->sid << 32); + + snd_compr_set_runtime_buffer(stream, &prtd->dma_buffer); + spin_lock_init(&prtd->lock); + + q6apm_enable_compress_module(dev, prtd->graph, true); + return 0; +} + +static int q6apm_dai_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *stream) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + + q6apm_graph_stop(prtd->graph); + q6apm_unmap_memory_regions(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK); + q6apm_graph_close(prtd->graph); + snd_dma_free_pages(&prtd->dma_buffer); + prtd->graph = NULL; + kfree(prtd); + runtime->private_data = NULL; + + return 0; +} + +static int q6apm_dai_compr_get_caps(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_caps *caps) +{ + caps->direction = SND_COMPRESS_PLAYBACK; + caps->min_fragment_size = COMPR_PLAYBACK_MIN_FRAGMENT_SIZE; + caps->max_fragment_size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE; + caps->min_fragments = COMPR_PLAYBACK_MIN_NUM_FRAGMENTS; + caps->max_fragments = COMPR_PLAYBACK_MAX_NUM_FRAGMENTS; + caps->num_codecs = 3; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + caps->codecs[2] = SND_AUDIOCODEC_FLAC; + + return 0; +} + +static int q6apm_dai_compr_get_codec_caps(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_codec_caps *codec) +{ + switch (codec->codec) { + case SND_AUDIOCODEC_MP3: + *codec = q6apm_compr_caps; + break; + default: + break; + } + + return 0; +} + +static int q6apm_dai_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + unsigned long flags; + + spin_lock_irqsave(&prtd->lock, flags); + tstamp->copied_total = prtd->copied_total; + tstamp->byte_offset = prtd->copied_total % prtd->pcm_size; + spin_unlock_irqrestore(&prtd->lock, flags); + + return 0; +} + +static int q6apm_dai_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *stream, int cmd) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = q6apm_write_async(prtd->graph, prtd->pcm_count, 0, 0, NO_TIMESTAMP); + break; + case SNDRV_PCM_TRIGGER_STOP: + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + case SND_COMPR_TRIGGER_NEXT_TRACK: + prtd->next_track = true; + break; + case SND_COMPR_TRIGGER_DRAIN: + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + prtd->notify_on_drain = true; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int q6apm_dai_compr_ack(struct snd_soc_component *component, struct snd_compr_stream *stream, + size_t count) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + unsigned long flags; + + spin_lock_irqsave(&prtd->lock, flags); + prtd->bytes_received += count; + spin_unlock_irqrestore(&prtd->lock, flags); + + return count; +} + +static int q6apm_dai_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_params *params) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + struct q6apm_dai_data *pdata; + struct audioreach_module_config cfg; + struct snd_codec *codec = ¶ms->codec; + int dir = stream->direction; + int ret; + + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) + return -EINVAL; + + prtd->periods = runtime->fragments; + prtd->pcm_count = runtime->fragment_size; + 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)); + + ret = q6apm_set_real_module_id(component->dev, prtd->graph, codec->id); + if (ret) + return ret; + + cfg.direction = dir; + cfg.sample_rate = codec->sample_rate; + cfg.num_channels = 2; + cfg.bit_width = prtd->bits_per_sample; + cfg.fmt = codec->id; + memcpy(&cfg.codec, codec, sizeof(*codec)); + + ret = q6apm_graph_media_format_shmem(prtd->graph, &cfg); + if (ret < 0) + return ret; + + ret = q6apm_graph_media_format_pcm(prtd->graph, &cfg); + if (ret) + return ret; + + ret = q6apm_map_memory_regions(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK, + prtd->phys, (prtd->pcm_size / prtd->periods), + prtd->periods); + if (ret < 0) + return -ENOMEM; + + ret = q6apm_graph_prepare(prtd->graph); + if (ret) + return ret; + + ret = q6apm_graph_start(prtd->graph); + if (ret) + return ret; + + } else { + cfg.direction = dir; + cfg.sample_rate = codec->sample_rate; + cfg.num_channels = 2; + cfg.bit_width = prtd->bits_per_sample; + cfg.fmt = codec->id; + memcpy(&cfg.codec, codec, sizeof(*codec)); + + ret = audioreach_compr_set_param(prtd->graph, &cfg); + if (ret < 0) + return ret; + } + prtd->state = Q6APM_STREAM_RUNNING; + + return 0; +} + +static int q6apm_dai_compr_set_metadata(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_metadata *metadata) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + int ret = 0; + + switch (metadata->key) { + case SNDRV_COMPRESS_ENCODER_PADDING: + prtd->trailing_samples_drop = metadata->value[0]; + q6apm_remove_trailing_silence(component->dev, prtd->graph, + prtd->trailing_samples_drop); + break; + case SNDRV_COMPRESS_ENCODER_DELAY: + prtd->initial_samples_drop = metadata->value[0]; + q6apm_remove_initial_silence(component->dev, prtd->graph, + prtd->initial_samples_drop); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int q6apm_dai_compr_mmap(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct vm_area_struct *vma) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + struct device *dev = component->dev; + + return dma_mmap_coherent(dev, vma, prtd->dma_buffer.area, prtd->dma_buffer.addr, + prtd->dma_buffer.bytes); +} + +static int q6apm_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *stream, char __user *buf, + size_t count) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + void *dstn; + unsigned long flags; + size_t copy; + u32 wflags = 0; + u32 app_pointer; + u32 bytes_received; + uint32_t bytes_to_write; + int avail, bytes_in_flight = 0; + + bytes_received = prtd->bytes_received; + + /** + * Make sure that next track data pointer is aligned at 32 bit boundary + * This is a Mandatory requirement from DSP data buffers alignment + */ + if (prtd->next_track) + bytes_received = ALIGN(prtd->bytes_received, prtd->pcm_count); + + app_pointer = bytes_received/prtd->pcm_size; + app_pointer = bytes_received - (app_pointer * prtd->pcm_size); + dstn = prtd->dma_buffer.area + app_pointer; + + if (count < prtd->pcm_size - app_pointer) { + if (copy_from_user(dstn, buf, count)) + return -EFAULT; + } else { + copy = prtd->pcm_size - app_pointer; + if (copy_from_user(dstn, buf, copy)) + return -EFAULT; + if (copy_from_user(prtd->dma_buffer.area, buf + copy, count - copy)) + return -EFAULT; + } + + spin_lock_irqsave(&prtd->lock, flags); + bytes_in_flight = prtd->bytes_received - prtd->copied_total; + + if (prtd->next_track) { + prtd->next_track = false; + prtd->copied_total = ALIGN(prtd->copied_total, prtd->pcm_count); + prtd->bytes_sent = ALIGN(prtd->bytes_sent, prtd->pcm_count); + } + + prtd->bytes_received = bytes_received + count; + + /* Kick off the data to dsp if its starving!! */ + if (prtd->state == Q6APM_STREAM_RUNNING && (bytes_in_flight == 0)) { + bytes_to_write = prtd->pcm_count; + avail = prtd->bytes_received - prtd->bytes_sent; + + if (avail < prtd->pcm_count) + bytes_to_write = avail; + + q6apm_write_async(prtd->graph, bytes_to_write, 0, 0, wflags); + prtd->bytes_sent += bytes_to_write; + } + + spin_unlock_irqrestore(&prtd->lock, flags); + + return count; +} + +static const struct snd_compress_ops q6apm_dai_compress_ops = { + .open = q6apm_dai_compr_open, + .free = q6apm_dai_compr_free, + .get_caps = q6apm_dai_compr_get_caps, + .get_codec_caps = q6apm_dai_compr_get_codec_caps, + .pointer = q6apm_dai_compr_pointer, + .trigger = q6apm_dai_compr_trigger, + .ack = q6apm_dai_compr_ack, + .set_params = q6apm_dai_compr_set_params, + .set_metadata = q6apm_dai_compr_set_metadata, + .mmap = q6apm_dai_compr_mmap, + .copy = q6apm_compr_copy, +}; + static const struct snd_soc_component_driver q6apm_fe_dai_component = { .name = DRV_NAME, .open = q6apm_dai_open, @@ -395,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, + .compress_ops = &q6apm_dai_compress_ops, }; static int q6apm_dai_probe(struct platform_device *pdev) diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index a7a3f973eb6d..7bfac9492ab5 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -298,6 +298,71 @@ int q6apm_unmap_memory_regions(struct q6apm_graph *graph, unsigned int dir) } EXPORT_SYMBOL_GPL(q6apm_unmap_memory_regions); +int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples) +{ + struct audioreach_module *module; + + module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!module) + return -ENODEV; + + return audioreach_send_u32_param(graph, module, PARAM_ID_REMOVE_INITIAL_SILENCE, samples); +} +EXPORT_SYMBOL_GPL(q6apm_remove_initial_silence); + +int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples) +{ + struct audioreach_module *module; + + module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!module) + return -ENODEV; + + return audioreach_send_u32_param(graph, module, PARAM_ID_REMOVE_TRAILING_SILENCE, samples); +} +EXPORT_SYMBOL_GPL(q6apm_remove_trailing_silence); + +int q6apm_enable_compress_module(struct device *dev, struct q6apm_graph *graph, bool en) +{ + struct audioreach_module *module; + + module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!module) + return -ENODEV; + + return audioreach_send_u32_param(graph, module, PARAM_ID_MODULE_ENABLE, en); +} +EXPORT_SYMBOL_GPL(q6apm_enable_compress_module); + +int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, + uint32_t codec_id) +{ + struct audioreach_module *module; + uint32_t module_id; + + module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!module) + return -ENODEV; + + switch (codec_id) { + case SND_AUDIOCODEC_MP3: + module_id = MODULE_ID_MP3_DECODE; + break; + case SND_AUDIOCODEC_AAC: + module_id = MODULE_ID_AAC_DEC; + break; + case SND_AUDIOCODEC_FLAC: + module_id = MODULE_ID_FLAC_DEC; + break; + default: + return -EINVAL; + } + + return audioreach_send_u32_param(graph, module, PARAM_ID_REAL_MODULE_ID, + module_id); +} +EXPORT_SYMBOL_GPL(q6apm_set_real_module_id); + int q6apm_graph_media_format_pcm(struct q6apm_graph *graph, struct audioreach_module_config *cfg) { struct audioreach_graph_info *info = graph->info; @@ -497,6 +562,9 @@ static int graph_callback(struct gpr_resp_pkt *data, void *priv, int op) } break; case DATA_CMD_WR_SH_MEM_EP_EOS_RENDERED: + client_event = APM_CLIENT_EVENT_CMD_EOS_DONE; + if (graph->cb) + graph->cb(client_event, hdr->token, data->payload, graph->priv); break; case GPR_BASIC_RSP_RESULT: switch (result->opcode) { diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 7005be9b63e3..8ee40732ce9e 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -45,6 +45,8 @@ #define APM_WRITE_TOKEN_LEN_SHIFT 16 #define APM_MAX_SESSIONS 8 +#define APM_LAST_BUFFER_FLAG BIT(30) +#define NO_TIMESTAMP 0xFF00 struct q6apm { struct device *dev; @@ -147,4 +149,8 @@ int q6apm_graph_get_rx_shmem_module_iid(struct q6apm_graph *graph); bool q6apm_is_adsp_ready(void); +int q6apm_enable_compress_module(struct device *dev, struct q6apm_graph *graph, bool en); +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); #endif /* __APM_GRAPH_ */ diff --git a/sound/soc/qcom/sc7280.c b/sound/soc/qcom/sc7280.c index da7469a6a267..787dd49e03f6 100644 --- a/sound/soc/qcom/sc7280.c +++ b/sound/soc/qcom/sc7280.c @@ -14,6 +14,7 @@ #include <sound/soc.h> #include <sound/rt5682s.h> #include <linux/soundwire/sdw.h> +#include <sound/pcm_params.h> #include "../codecs/rt5682.h" #include "../codecs/rt5682s.h" @@ -196,8 +197,10 @@ static int sc7280_snd_hw_params(struct snd_pcm_substream *substream, struct sdw_stream_runtime *sruntime; int i; - snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2); - snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, 48000, 48000); + if (!rtd->dai_link->no_pcm) { + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, 48000, 48000); + } switch (cpu_dai->id) { case LPASS_CDC_DMA_TX3: @@ -358,6 +361,20 @@ static const struct snd_soc_dapm_widget sc7280_snd_widgets[] = { SND_SOC_DAPM_MIC("Headset Mic", NULL), }; +static int sc7280_snd_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + static int sc7280_snd_platform_probe(struct platform_device *pdev) { struct snd_soc_card *card; @@ -387,6 +404,8 @@ static int sc7280_snd_platform_probe(struct platform_device *pdev) for_each_card_prelinks(card, i, link) { link->init = sc7280_init; link->ops = &sc7280_ops; + if (link->no_pcm == 1) + link->be_hw_params_fixup = sc7280_snd_be_hw_params_fixup; } return devm_snd_soc_register_card(dev, card); |