diff options
Diffstat (limited to 'sound/soc')
-rw-r--r-- | sound/soc/msm/qdsp6v2/Makefile | 3 | ||||
-rw-r--r-- | sound/soc/msm/qdsp6v2/msm-pcm-q6-noirq.c | 810 | ||||
-rw-r--r-- | sound/soc/msm/qdsp6v2/q6asm.c | 383 |
3 files changed, 1195 insertions, 1 deletions
diff --git a/sound/soc/msm/qdsp6v2/Makefile b/sound/soc/msm/qdsp6v2/Makefile index d13f468f98cd..461c09db2937 100644 --- a/sound/soc/msm/qdsp6v2/Makefile +++ b/sound/soc/msm/qdsp6v2/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_DOLBY_DS2) += msm-ds2-dap-config.o obj-$(CONFIG_DTS_SRS_TM) += msm-dts-srs-tm-config.o obj-$(CONFIG_QTI_PP) += msm-qti-pp-config.o obj-y += audio_calibration.o audio_cal_utils.o q6adm.o q6afe.o q6asm.o \ - q6audio-v2.o q6voice.o q6core.o rtac.o q6lsm.o audio_slimslave.o + q6audio-v2.o q6voice.o q6core.o rtac.o q6lsm.o audio_slimslave.o \ + msm-pcm-q6-noirq.o ocmem-audio-objs += audio_ocmem.o obj-$(CONFIG_AUDIO_OCMEM) += ocmem-audio.o diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-q6-noirq.c b/sound/soc/msm/qdsp6v2/msm-pcm-q6-noirq.c new file mode 100644 index 000000000000..856f2b368d41 --- /dev/null +++ b/sound/soc/msm/qdsp6v2/msm-pcm-q6-noirq.c @@ -0,0 +1,810 @@ +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/of_device.h> +#include <linux/dma-mapping.h> +#include <linux/msm_audio_ion.h> + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/control.h> +#include <sound/q6audio-v2.h> +#include <sound/timer.h> +#include <asm/dma.h> +#include <sound/tlv.h> +#include <sound/pcm_params.h> + +#include "msm-pcm-q6-v2.h" +#include "msm-pcm-routing-v2.h" + +#define PCM_MASTER_VOL_MAX_STEPS 0x2000 +static const DECLARE_TLV_DB_LINEAR(msm_pcm_vol_gain, 0, + PCM_MASTER_VOL_MAX_STEPS); + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +#define CMD_EOS_MIN_TIMEOUT_LENGTH 50 +#define CMD_EOS_TIMEOUT_MULTIPLIER (HZ * 50) + +#define ATRACE_END() \ + trace_printk("tracing_mark_write: E\n") +#define ATRACE_BEGIN(name) \ + trace_printk("tracing_mark_write: B|%d|%s\n", current->tgid, name) +#define ATRACE_FUNC() ATRACE_BEGIN(__func__) +#define ATRACE_INT(name, value) \ + trace_printk("tracing_mark_write: C|%d|%s|%d\n", \ + current->tgid, name, (int)(value)) + +#define SIO_PLAYBACK_MAX_PERIOD_SIZE PLAYBACK_MAX_PERIOD_SIZE +#define SIO_PLAYBACK_MIN_PERIOD_SIZE 48 +#define SIO_PLAYBACK_MAX_NUM_PERIODS 512 +#define SIO_PLAYBACK_MIN_NUM_PERIODS PLAYBACK_MIN_NUM_PERIODS +#define SIO_PLAYBACK_MIN_BYTES (SIO_PLAYBACK_MIN_NUM_PERIODS * \ + SIO_PLAYBACK_MIN_PERIOD_SIZE) + +#define SIO_PLAYBACK_MAX_BYTES ((SIO_PLAYBACK_MAX_NUM_PERIODS) * \ + (SIO_PLAYBACK_MAX_PERIOD_SIZE)) + +#define SIO_CAPTURE_MAX_PERIOD_SIZE CAPTURE_MAX_PERIOD_SIZE +#define SIO_CAPTURE_MIN_PERIOD_SIZE 48 +#define SIO_CAPTURE_MAX_NUM_PERIODS 512 +#define SIO_CAPTURE_MIN_NUM_PERIODS CAPTURE_MIN_NUM_PERIODS + +#define SIO_CAPTURE_MIN_BYTES (SIO_CAPTURE_MIN_NUM_PERIODS * \ + SIO_CAPTURE_MIN_PERIOD_SIZE) + +#define SIO_CAPTURE_MAX_BYTES (SIO_CAPTURE_MAX_NUM_PERIODS * \ + SIO_CAPTURE_MAX_PERIOD_SIZE) + +static struct snd_pcm_hardware msm_pcm_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = SIO_PLAYBACK_MAX_NUM_PERIODS * + SIO_PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = SIO_PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = SIO_PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = SIO_PLAYBACK_MIN_NUM_PERIODS, + .periods_max = SIO_PLAYBACK_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_hardware_capture = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 4, + .buffer_bytes_max = SIO_CAPTURE_MAX_NUM_PERIODS * + SIO_CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = SIO_CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = SIO_CAPTURE_MAX_PERIOD_SIZE, + .periods_min = SIO_CAPTURE_MIN_NUM_PERIODS, + .periods_max = SIO_CAPTURE_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, + 88200, 96000, 176400, 192000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + uint32_t *ptrmem = (uint32_t *)payload; + + switch (opcode) { + case ASM_DATA_EVENT_WATERMARK: + pr_debug("%s: Watermark level = 0x%08x\n", __func__, *ptrmem); + break; + case APR_BASIC_RSP_RESULT: + pr_debug("%s: Payload = [0x%x]stat[0x%x]\n", + __func__, payload[0], payload[1]); + switch (payload[0]) { + case ASM_SESSION_CMD_RUN_V2: + case ASM_SESSION_CMD_PAUSE: + case ASM_STREAM_CMD_FLUSH: + break; + default: + break; + } + break; + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd; + int ret = 0; + + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + + if (prtd == NULL) + return -ENOMEM; + + prtd->substream = substream; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = msm_pcm_hardware_playback; + else + runtime->hw = msm_pcm_hardware_capture; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret) + pr_info("snd_pcm_hw_constraint_list failed\n"); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret) + pr_info("snd_pcm_hw_constraint_integer failed\n"); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + SIO_PLAYBACK_MIN_BYTES, + SIO_PLAYBACK_MAX_BYTES); + if (ret) { + pr_info("%s: P buffer bytes minmax constraint ret %d\n", + __func__, ret); + } + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + SIO_CAPTURE_MIN_BYTES, + SIO_CAPTURE_MAX_BYTES); + if (ret) { + pr_info("%s: C buffer bytes minmax constraint ret %d\n", + __func__, ret); + } + } + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (ret) { + pr_err("%s: Constraint for period bytes step ret = %d\n", + __func__, ret); + } + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + if (ret) { + pr_err("%s: Constraint for buffer bytes step ret = %d\n", + __func__, ret); + } + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_err("%s: client alloc failed\n", __func__); + ret = -ENOMEM; + goto fail_cmd; + } + prtd->dsp_cnt = 0; + prtd->set_channel_map = false; + runtime->private_data = prtd; + return 0; + +fail_cmd: + kfree(prtd); + return ret; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + struct msm_plat_data *pdata; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + struct shared_io_config config; + uint16_t sample_word_size; + uint16_t bits_per_sample = 16; + int ret; + int dir = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? IN : OUT; + + pdata = (struct msm_plat_data *) + dev_get_drvdata(soc_prtd->platform->dev); + if (!pdata) { + ret = -EINVAL; + pr_err("%s: platform data not populated ret: %d\n", __func__, + ret); + return ret; + } + + /* need to set LOW_LATENCY_PCM_MODE for capture since + * push mode does not support ULL + */ + prtd->audio_client->perf_mode = (dir == IN) ? + pdata->perf_mode : + LOW_LATENCY_PCM_MODE; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = params_rate(params); + prtd->channel_mode = params_channels(params); + if (prtd->enabled) + return 0; + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + bits_per_sample = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bits_per_sample = 24; + break; + } + + sample_word_size = (bits_per_sample == 16) ? 16 : 32; + + config.format = FORMAT_LINEAR_PCM; + config.bits_per_sample = bits_per_sample; + config.rate = params_rate(params); + config.channels = params_channels(params); + config.sample_word_size = sample_word_size; + config.bufsz = params_buffer_bytes(params) / params_periods(params); + config.bufcnt = params_periods(params); + + ret = q6asm_open_shared_io(prtd->audio_client, &config, dir); + if (ret) { + pr_err("%s: q6asm_open_write_shared_io failed ret: %d\n", + __func__, ret); + return ret; + } + + prtd->pcm_size = params_buffer_bytes(params); + prtd->pcm_count = params_buffer_bytes(params); + prtd->pcm_irq_pos = 0; + + buf = prtd->audio_client->port[dir].buf; + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf->data; + dma_buf->addr = buf->phys; + dma_buf->bytes = prtd->pcm_size; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + pr_debug("%s: session ID %d, perf %d\n", __func__, + prtd->audio_client->session, + prtd->audio_client->perf_mode); + prtd->session_id = prtd->audio_client->session; + + pr_debug("msm_pcm_routing_reg_phy_stream w/ id %d\n", + soc_prtd->dai_link->be_id); + ret = msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->audio_client->perf_mode, + prtd->session_id, substream->stream); + + if (ret) { + pr_err("%s: stream reg failed ret:%d\n", __func__, ret); + return ret; + } + + atomic_set(&prtd->out_count, runtime->periods); + prtd->enabled = 1; + prtd->cmd_pending = 0; + prtd->cmd_interrupt = 0; + + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int dir = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1; + struct audio_buffer *buf; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: %s Trigger start\n", __func__, + dir == 0 ? "P" : "C"); + ret = q6asm_run(prtd->audio_client, 0, 0, 0); + if (ret) + break; + atomic_set(&prtd->start, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__); + atomic_set(&prtd->start, 0); + q6asm_cmd(prtd->audio_client, CMD_PAUSE); + q6asm_cmd(prtd->audio_client, CMD_FLUSH); + buf = q6asm_shared_io_buf(prtd->audio_client, dir); + if (buf) + memset(buf->data, 0, buf->actual_size); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("%s: SNDRV_PCM_TRIGGER_PAUSE\n", __func__); + ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + uint32_t read_index, wall_clk_msw, wall_clk_lsw; + /*these are offsets, unlike ASoC's full values*/ + snd_pcm_sframes_t hw_ptr; + snd_pcm_sframes_t period_size; + int ret; + int retries = 10; + struct msm_audio *prtd = runtime->private_data; + + period_size = runtime->period_size; + + do { + ret = q6asm_get_shared_pos(prtd->audio_client, + &read_index, &wall_clk_msw, + &wall_clk_lsw); + } while (ret == -EAGAIN && --retries); + + if (ret || !period_size) { + pr_err("get_shared_pos error or zero period size\n"); + return 0; + } + + hw_ptr = bytes_to_frames(substream->runtime, + read_index); + + if (runtime->control->appl_ptr == 0) { + pr_debug("ptr(%s): appl(0), hw = %lu read_index = %u\n", + prtd->substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + "P" : "C", + hw_ptr, read_index); + } + return (hw_ptr/period_size) * period_size; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + return -EINVAL; +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct audio_client *ac = prtd->audio_client; + struct audio_port_data *apd = ac->port; + struct audio_buffer *ab; + int dir = -1; + int ret; + + pr_debug("%s: mmap begin\n", __func__); + prtd->mmap_flag = 1; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + dir = OUT; + + ab = &(apd[dir].buf[0]); + + ret = msm_audio_ion_mmap(ab, vma); + + if (ret) + prtd->mmap_flag = 0; + + return ret; +} + +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (!prtd || !prtd->mmap_flag) + return -EIO; + + return 0; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + struct audio_client *ac = prtd->audio_client; + uint32_t timeout; + int dir = 0; + int ret = 0; + + if (ac) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = OUT; + + /* determine timeout length */ + if (runtime->frame_bits == 0 || runtime->rate == 0) { + timeout = CMD_EOS_MIN_TIMEOUT_LENGTH; + } else { + timeout = (runtime->period_size * + CMD_EOS_TIMEOUT_MULTIPLIER) / + ((runtime->frame_bits / 8) * + runtime->rate); + if (timeout < CMD_EOS_MIN_TIMEOUT_LENGTH) + timeout = CMD_EOS_MIN_TIMEOUT_LENGTH; + } + + q6asm_cmd(ac, CMD_CLOSE); + + ret = q6asm_shared_io_free(ac, dir); + + if (ret) { + pr_err("%s: Failed to close pull mode, ret %d\n", + __func__, ret); + } + q6asm_audio_client_free(ac); + } + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + dir == IN ? + SNDRV_PCM_STREAM_PLAYBACK : + SNDRV_PCM_STREAM_CAPTURE); + kfree(prtd); + return 0; +} + +static int msm_pcm_set_volume(struct msm_audio *prtd, uint32_t volume) +{ + int rc = 0; + + if (prtd && prtd->audio_client) { + pr_debug("%s: channels %d volume 0x%x\n", __func__, + prtd->channel_mode, volume); + rc = q6asm_set_volume(prtd->audio_client, volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed rc=%d\n", + __func__, rc); + } + } + return rc; +} + +static int msm_pcm_volume_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcm_volume *vol = snd_kcontrol_chip(kcontrol); + struct snd_pcm_substream *substream = + vol->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + struct msm_audio *prtd; + + pr_debug("%s\n", __func__); + if (!substream) { + pr_err("%s substream not found\n", __func__); + return -ENODEV; + } + if (!substream->runtime) { + pr_err("%s substream runtime not found\n", __func__); + return 0; + } + prtd = substream->runtime->private_data; + if (prtd) + ucontrol->value.integer.value[0] = prtd->volume; + return 0; +} + +static int msm_pcm_volume_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct snd_pcm_volume *vol = snd_kcontrol_chip(kcontrol); + struct snd_pcm_substream *substream = + vol->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + struct msm_audio *prtd; + int volume = ucontrol->value.integer.value[0]; + + pr_debug("%s: volume : 0x%x\n", __func__, volume); + if (!substream) { + pr_err("%s substream not found\n", __func__); + return -ENODEV; + } + if (!substream->runtime) { + pr_err("%s substream runtime not found\n", __func__); + return 0; + } + prtd = substream->runtime->private_data; + if (prtd) { + rc = msm_pcm_set_volume(prtd, volume); + prtd->volume = volume; + } + return rc; +} + +static int msm_pcm_add_volume_control(struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_pcm *pcm = rtd->pcm; + struct snd_pcm_volume *volume_info; + struct snd_kcontrol *kctl; + + dev_dbg(rtd->dev, "%s, Volume control add\n", __func__); + ret = snd_pcm_add_volume_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + NULL, 1, rtd->dai_link->be_id, + &volume_info); + if (ret < 0) { + pr_err("%s volume control failed ret %d\n", __func__, ret); + return ret; + } + kctl = volume_info->kctl; + kctl->put = msm_pcm_volume_ctl_put; + kctl->get = msm_pcm_volume_ctl_get; + kctl->tlv.p = msm_pcm_vol_gain; + return 0; +} + +static int msm_pcm_chmap_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i; + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + struct snd_pcm_substream *substream; + struct msm_audio *prtd; + + pr_debug("%s", __func__); + substream = snd_pcm_chmap_substream(info, idx); + if (!substream) + return -ENODEV; + if (!substream->runtime) + return 0; + + prtd = substream->runtime->private_data; + if (prtd) { + prtd->set_channel_map = true; + for (i = 0; i < PCM_FORMAT_MAX_NUM_CHANNEL; i++) + prtd->channel_map[i] = + (char)(ucontrol->value.integer.value[i]); + } + return 0; +} + +static int msm_pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i; + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + struct snd_pcm_substream *substream; + struct msm_audio *prtd; + + pr_debug("%s", __func__); + substream = snd_pcm_chmap_substream(info, idx); + if (!substream) + return -ENODEV; + memset(ucontrol->value.integer.value, 0, + sizeof(ucontrol->value.integer.value)); + if (!substream->runtime) + return 0; /* no channels set */ + + prtd = substream->runtime->private_data; + + if (prtd && prtd->set_channel_map == true) { + for (i = 0; i < PCM_FORMAT_MAX_NUM_CHANNEL; i++) + ucontrol->value.integer.value[i] = + (int)prtd->channel_map[i]; + } else { + for (i = 0; i < PCM_FORMAT_MAX_NUM_CHANNEL; i++) + ucontrol->value.integer.value[i] = 0; + } + + return 0; +} + +static int msm_pcm_add_chmap_control(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct snd_pcm_chmap *chmap_info; + struct snd_kcontrol *kctl; + char device_num[12]; + int i, ret; + + pr_debug("%s, Channel map cntrl add\n", __func__); + ret = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_std_chmaps, + PCM_FORMAT_MAX_NUM_CHANNEL, 0, + &chmap_info); + if (ret) + return ret; + + kctl = chmap_info->kctl; + for (i = 0; i < kctl->count; i++) + kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_WRITE; + snprintf(device_num, sizeof(device_num), "%d", pcm->device); + strlcat(kctl->id.name, device_num, sizeof(kctl->id.name)); + pr_debug("%s, Overwriting channel map control name to: %s", + __func__, kctl->id.name); + kctl->put = msm_pcm_chmap_ctl_put; + kctl->get = msm_pcm_chmap_ctl_get; + return 0; +} + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + pr_debug("%s , register new control\n", __func__); + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + ret = msm_pcm_add_chmap_control(rtd); + if (ret) { + pr_err("%s failed to add chmap cntls\n", __func__); + goto exit; + } + ret = msm_pcm_add_volume_control(rtd); + if (ret) { + pr_err("%s: Could not add pcm Volume Control %d\n", + __func__, ret); + } + pcm->nonatomic = true; +exit: + return ret; +} + + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .prepare = msm_pcm_prepare, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .ioctl = snd_pcm_lib_ioctl, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, + .close = msm_pcm_close, +}; + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static int msm_pcm_probe(struct platform_device *pdev) +{ + int rc; + struct msm_plat_data *pdata; + const char *latency_level; + int perf_mode = LOW_LATENCY_PCM_MODE; + + dev_dbg(&pdev->dev, "Pull mode driver probe\n"); + + if (of_property_read_bool(pdev->dev.of_node, + "qcom,msm-pcm-low-latency")) { + + rc = of_property_read_string(pdev->dev.of_node, + "qcom,latency-level", &latency_level); + if (!rc && !strcmp(latency_level, "ultra")) + perf_mode = ULTRA_LOW_LATENCY_PCM_MODE; + } + + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct msm_plat_data), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->perf_mode = perf_mode; + + dev_set_drvdata(&pdev->dev, pdata); + + dev_dbg(&pdev->dev, "%s: dev name %s\n", + __func__, dev_name(&pdev->dev)); + dev_dbg(&pdev->dev, "Pull mode driver register\n"); + rc = snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); + + if (rc) + dev_err(&pdev->dev, "Failed to register pull mode driver\n"); + + return rc; +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + struct msm_plat_data *pdata; + + dev_dbg(&pdev->dev, "Pull mode remove\n"); + pdata = dev_get_drvdata(&pdev->dev); + devm_kfree(&pdev->dev, pdata); + snd_soc_unregister_platform(&pdev->dev); + return 0; +} +static const struct of_device_id msm_pcm_dt_match[] = { + {.compatible = "qcom,msm-pcm-dsp-noirq"}, + {} +}; +MODULE_DEVICE_TABLE(of, msm_pcm_dt_match); + +static struct platform_driver msm_pcm_driver_noirq = { + .driver = { + .name = "msm-pcm-dsp-noirq", + .owner = THIS_MODULE, + .of_match_table = msm_pcm_dt_match, + }, + .probe = msm_pcm_probe, + .remove = msm_pcm_remove, +}; + +static int __init msm_soc_platform_init(void) +{ + return platform_driver_register(&msm_pcm_driver_noirq); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver_noirq); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM NOIRQ module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6v2/q6asm.c b/sound/soc/msm/qdsp6v2/q6asm.c index 34d3844d57b8..761489b1c436 100644 --- a/sound/soc/msm/qdsp6v2/q6asm.c +++ b/sound/soc/msm/qdsp6v2/q6asm.c @@ -43,6 +43,7 @@ #define TRUE 0x01 #define FALSE 0x00 +#define SESSION_MAX 8 enum { ASM_TOPOLOGY_CAL = 0, @@ -1678,6 +1679,8 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) case ASM_SESSION_CMD_SET_MTMX_STRTR_PARAMS_V2: case ASM_STREAM_CMD_OPEN_READ_V3: case ASM_STREAM_CMD_OPEN_WRITE_V3: + case ASM_STREAM_CMD_OPEN_PULL_MODE_WRITE: + case ASM_STREAM_CMD_OPEN_PUSH_MODE_READ: case ASM_STREAM_CMD_OPEN_READWRITE_V2: case ASM_STREAM_CMD_OPEN_LOOPBACK_V2: case ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2: @@ -1728,6 +1731,11 @@ static int32_t q6asm_callback(struct apr_client_data *data, void *priv) ac->cb(data->opcode, data->token, (uint32_t *)data->payload, ac->priv); break; + case ASM_DATA_EVENT_WATERMARK: { + pr_debug("%s: Watermark opcode[0x%x] status[0x%x]", + __func__, payload[0], payload[1]); + break; + } case ASM_STREAM_CMD_GET_PP_PARAMS_V2: pr_debug("%s: ASM_STREAM_CMD_GET_PP_PARAMS_V2 session %d opcode 0x%x token 0x%x src %d dest %d\n", __func__, ac->session, @@ -2826,6 +2834,381 @@ fail_cmd: return rc; } +static +int q6asm_set_shared_circ_buff(struct audio_client *ac, + struct asm_stream_cmd_open_shared_io *open, + int bufsz, int bufcnt, + int dir) +{ + struct audio_buffer *buf_circ; + int bytes_to_alloc, rc, len; + + buf_circ = kzalloc(sizeof(struct audio_buffer), GFP_KERNEL); + + if (!buf_circ) { + rc = -ENOMEM; + goto done; + } + + mutex_lock(&ac->cmd_lock); + + ac->port[dir].buf = buf_circ; + + bytes_to_alloc = bufsz * bufcnt; + bytes_to_alloc = PAGE_ALIGN(bytes_to_alloc); + + rc = msm_audio_ion_alloc("audio_client", &buf_circ->client, + &buf_circ->handle, bytes_to_alloc, + (ion_phys_addr_t *)&buf_circ->phys, + (size_t *)&len, &buf_circ->data); + + if (rc) { + pr_err("%s: Audio ION alloc is failed, rc = %d\n", __func__, + rc); + mutex_unlock(&ac->cmd_lock); + kfree(buf_circ); + goto done; + } + + buf_circ->used = dir ^ 1; + buf_circ->size = bytes_to_alloc; + buf_circ->actual_size = bytes_to_alloc; + memset(buf_circ->data, 0, buf_circ->actual_size); + + ac->port[dir].max_buf_cnt = 1; + + open->shared_circ_buf_mem_pool_id = ADSP_MEMORY_MAP_SHMEM8_4K_POOL; + open->shared_circ_buf_num_regions = 1; + open->shared_circ_buf_property_flag = 0x00; + open->shared_circ_buf_start_phy_addr_lsw = + lower_32_bits(buf_circ->phys); + open->shared_circ_buf_start_phy_addr_msw = + upper_32_bits(buf_circ->phys); + open->shared_circ_buf_size = bufsz * bufcnt; + + open->map_region_circ_buf.shm_addr_lsw = lower_32_bits(buf_circ->phys); + open->map_region_circ_buf.shm_addr_msw = upper_32_bits(buf_circ->phys); + open->map_region_circ_buf.mem_size_bytes = bytes_to_alloc; + + mutex_unlock(&ac->cmd_lock); +done: + return rc; +} + + +static +int q6asm_set_shared_pos_buff(struct audio_client *ac, + struct asm_stream_cmd_open_shared_io *open, + int dir) +{ + struct audio_buffer *buf_pos = &ac->shared_pos_buf; + int len, rc; + int bytes_to_alloc = sizeof(struct asm_shared_position_buffer); + + mutex_lock(&ac->cmd_lock); + + bytes_to_alloc = PAGE_ALIGN(bytes_to_alloc); + + rc = msm_audio_ion_alloc("audio_client", &buf_pos->client, + &buf_pos->handle, bytes_to_alloc, + (ion_phys_addr_t *)&buf_pos->phys, (size_t *)&len, + &buf_pos->data); + + if (rc) { + pr_err("%s: Audio pos buf ION alloc is failed, rc = %d\n", + __func__, rc); + goto done; + } + + buf_pos->used = dir ^ 1; + buf_pos->size = bytes_to_alloc; + buf_pos->actual_size = bytes_to_alloc; + + open->shared_pos_buf_mem_pool_id = ADSP_MEMORY_MAP_SHMEM8_4K_POOL; + open->shared_pos_buf_num_regions = 1; + open->shared_pos_buf_property_flag = 0x00; + open->shared_pos_buf_phy_addr_lsw = lower_32_bits(buf_pos->phys); + open->shared_pos_buf_phy_addr_msw = upper_32_bits(buf_pos->phys); + + open->map_region_pos_buf.shm_addr_lsw = lower_32_bits(buf_pos->phys); + open->map_region_pos_buf.shm_addr_msw = upper_32_bits(buf_pos->phys); + open->map_region_pos_buf.mem_size_bytes = bytes_to_alloc; + +done: + mutex_unlock(&ac->cmd_lock); + return rc; +} + +/* + * q6asm_open_shared_io: Open an ASM session for pull mode (playback) + * or push mode (capture). + * parameters + * config - session parameters (channels, bits_per_sample, sr) + * dir - stream direction (IN for playback, OUT for capture) + * returns 0 if successful, error code otherwise + */ +int q6asm_open_shared_io(struct audio_client *ac, + struct shared_io_config *config, + int dir) +{ + struct asm_stream_cmd_open_shared_io *open; + u8 *channel_mapping; + int i, size_of_open, num_watermarks, bufsz, bufcnt, rc, flags = 0; + + if (!ac || !config) + return -EINVAL; + + bufsz = config->bufsz; + bufcnt = config->bufcnt; + num_watermarks = 0; + + ac->config = *config; + + if (ac->session <= 0 || ac->session > SESSION_MAX) { + pr_err("%s: Session %d is out of bounds\n", + __func__, ac->session); + return -EINVAL; + } + + size_of_open = sizeof(struct asm_stream_cmd_open_shared_io) + + (sizeof(struct asm_shared_watermark_level) * num_watermarks); + + open = kzalloc(PAGE_ALIGN(size_of_open), GFP_KERNEL); + if (!open) + return -ENOMEM; + + q6asm_stream_add_hdr(ac, &open->hdr, size_of_open, TRUE, + ac->stream_id); + + atomic_set(&ac->cmd_state, 1); + + pr_debug("%s: token = 0x%x, stream_id %d, session 0x%x, perf %d\n", + __func__, open->hdr.token, ac->stream_id, ac->session, + ac->perf_mode); + + open->hdr.opcode = + dir == IN ? ASM_STREAM_CMD_OPEN_PULL_MODE_WRITE : + ASM_STREAM_CMD_OPEN_PUSH_MODE_READ; + + pr_debug("%s perf_mode %d\n", __func__, ac->perf_mode); + if (dir == IN) + if (ac->perf_mode == ULL_POST_PROCESSING_PCM_MODE) + flags = 4 << ASM_SHIFT_STREAM_PERF_FLAG_PULL_MODE_WRITE; + else if (ac->perf_mode == ULTRA_LOW_LATENCY_PCM_MODE) + flags = 2 << ASM_SHIFT_STREAM_PERF_FLAG_PULL_MODE_WRITE; + else if (ac->perf_mode == LOW_LATENCY_PCM_MODE) + flags = 1 << ASM_SHIFT_STREAM_PERF_FLAG_PULL_MODE_WRITE; + else + pr_err("Invalid perf mode for pull write\n"); + else + if (ac->perf_mode == LOW_LATENCY_PCM_MODE) + flags = ASM_LOW_LATENCY_TX_STREAM_SESSION << + ASM_SHIFT_STREAM_PERF_FLAG_PUSH_MODE_READ; + else + pr_err("Invalid perf mode for push read\n"); + + if (flags == 0) { + pr_err("%s: Invalid mode[%d]\n", __func__, + ac->perf_mode); + kfree(open); + return -EINVAL; + + } + + pr_debug("open.mode_flags = 0x%x\n", flags); + open->mode_flags = flags; + open->endpoint_type = ASM_END_POINT_DEVICE_MATRIX; + open->topo_bits_per_sample = config->bits_per_sample; + + open->topo_id = q6asm_get_asm_topology_cal(); + + if (config->format == FORMAT_LINEAR_PCM) + open->fmt_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V3; + else { + pr_err("%s: Invalid format[%d]\n", __func__, config->format); + rc = -EINVAL; + goto done; + } + + if (ac->port[dir].buf) { + pr_err("%s: Buffer already allocated\n", __func__); + rc = -EINVAL; + goto done; + } + + rc = q6asm_set_shared_circ_buff(ac, open, bufsz, bufcnt, dir); + + if (rc) + goto done; + + ac->port[dir].tmp_hdl = 0; + + rc = q6asm_set_shared_pos_buff(ac, open, dir); + + if (rc) + goto done; + + /* asm_multi_channel_pcm_fmt_blk_v3 */ + open->fmt.num_channels = config->channels; + open->fmt.bits_per_sample = config->bits_per_sample; + open->fmt.sample_rate = config->rate; + open->fmt.is_signed = 1; + open->fmt.sample_word_size = config->sample_word_size; + + channel_mapping = open->fmt.channel_mapping; + + memset(channel_mapping, 0, PCM_FORMAT_MAX_NUM_CHANNEL); + + rc = q6asm_map_channels(channel_mapping, config->channels, false); + if (rc) { + pr_err("%s: Map channels failed, ret: %d\n", __func__, rc); + goto done; + } + + open->num_watermark_levels = num_watermarks; + for (i = 0; i < num_watermarks; i++) { + open->watermark[i].watermark_level_bytes = i * + ((bufsz * bufcnt) / num_watermarks); + pr_debug("%s: Watermark level set for %i\n", + __func__, + open->watermark[i].watermark_level_bytes); + } + + rc = apr_send_pkt(ac->apr, (uint32_t *) open); + if (rc < 0) { + pr_err("%s: Open failed op[0x%x]rc[%d]\n", + __func__, open->hdr.opcode, rc); + goto done; + } + + pr_debug("%s: sent open apr pkt\n", __func__); + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) <= 0), 5*HZ); + if (!rc) { + pr_err("%s: Timeout. Waited for open write apr pkt rc[%d]\n", + __func__, rc); + rc = -ETIMEDOUT; + goto done; + } + + if (atomic_read(&ac->cmd_state) < 0) { + pr_err("%s: DSP returned error [%d]\n", __func__, + atomic_read(&ac->cmd_state)); + rc = -EINVAL; + goto done; + } + + ac->io_mode |= TUN_WRITE_IO_MODE; + rc = 0; +done: + kfree(open); + return rc; +} +EXPORT_SYMBOL(q6asm_open_shared_io); + +/* + * q6asm_shared_io_buf: Returns handle to the shared circular buffer being + * used for pull/push mode. + * parameters + * dir - used to identify input/output port + * returns buffer handle + */ +struct audio_buffer *q6asm_shared_io_buf(struct audio_client *ac, + int dir) +{ + struct audio_port_data *port; + + if (!ac) { + pr_err("%s: ac is null\n", __func__); + return NULL; + } + port = &ac->port[dir]; + return port->buf; +} +EXPORT_SYMBOL(q6asm_shared_io_buf); + +/* + * q6asm_shared_io_free: Frees memory allocated for a pull/push session + * parameters + * dir - port direction + * returns 0 if successful, error otherwise + */ +int q6asm_shared_io_free(struct audio_client *ac, int dir) +{ + struct audio_port_data *port; + + if (!ac) { + pr_err("%s: audio client is null\n", __func__); + return -EINVAL; + } + port = &ac->port[dir]; + mutex_lock(&ac->cmd_lock); + if (port->buf && port->buf->data) { + msm_audio_ion_free(port->buf->client, port->buf->handle); + port->buf->client = NULL; + port->buf->handle = NULL; + port->max_buf_cnt = 0; + kfree(port->buf); + port->buf = NULL; + } + if (ac->shared_pos_buf.data) { + msm_audio_ion_free(ac->shared_pos_buf.client, + ac->shared_pos_buf.handle); + ac->shared_pos_buf.client = NULL; + ac->shared_pos_buf.handle = NULL; + } + mutex_unlock(&ac->cmd_lock); + return 0; +} +EXPORT_SYMBOL(q6asm_shared_io_free); + +/* + * q6asm_get_shared_pos: Returns current read index/write index as observed + * by the DSP. Note that this is an offset and iterates from [0,BUF_SIZE - 1] + * parameters - (all output) + * read_index - offset + * wall_clk_msw1 - ADSP wallclock msw + * wall_clk_lsw1 - ADSP wallclock lsw + * returns 0 if successful, -EAGAIN if DSP failed to update after some + * retries + */ +int q6asm_get_shared_pos(struct audio_client *ac, uint32_t *read_index, + uint32_t *wall_clk_msw1, uint32_t *wall_clk_lsw1) +{ + struct asm_shared_position_buffer *pos_buf; + uint32_t frame_cnt1, frame_cnt2; + int i, j; + + if (!ac) { + pr_err("%s: audio client is null\n", __func__); + return -EINVAL; + } + + pos_buf = ac->shared_pos_buf.data; + + /* always try to get the latest update in the shared pos buffer */ + for (i = 0; i < 2; i++) { + /* retry until there is an update from DSP */ + for (j = 0; j < 5; j++) { + frame_cnt1 = pos_buf->frame_counter; + if (frame_cnt1 != 0) + break; + } + + *wall_clk_msw1 = pos_buf->wall_clock_us_msw; + *wall_clk_lsw1 = pos_buf->wall_clock_us_lsw; + *read_index = pos_buf->index; + frame_cnt2 = pos_buf->frame_counter; + + if (frame_cnt1 != frame_cnt2) + continue; + return 0; + } + pr_err("%s out of tries trying to get a good read, try again\n", + __func__); + return -EAGAIN; +} + int q6asm_run(struct audio_client *ac, uint32_t flags, uint32_t msw_ts, uint32_t lsw_ts) { |