// SPDX-License-Identifier: GPL-2.0-or-later /* * ALSA SoC - Samsung Abox WDMA driver * * Copyright (c) 2016 Samsung Electronics Co. Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "abox_util.h" #include "abox_gic.h" #include "abox_dbg.h" #include "abox_vss.h" #include "abox_cmpnt.h" #include "abox.h" #include "abox_dma.h" #include "abox_memlog.h" static int abox_wdma_request_ipc(struct abox_dma_data *data, ABOX_IPC_MSG *msg, int atomic, int sync) { return abox_request_ipc(data->dev_abox, msg->ipcid, msg, sizeof(*msg), atomic, sync); } static const struct snd_pcm_hardware abox_wdma_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID, .formats = ABOX_SAMPLE_FORMATS, .channels_min = 1, .channels_max = 8, .buffer_bytes_max = BUFFER_BYTES_MAX, .period_bytes_min = PERIOD_BYTES_MIN, .period_bytes_max = PERIOD_BYTES_MAX, .periods_min = BUFFER_BYTES_MAX / PERIOD_BYTES_MAX, .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, }; static irqreturn_t abox_wdma_fade_done(int irq, void *dev_id) { struct device *dev = dev_id; struct abox_dma_data *data = dev_get_drvdata(dev); abox_info(dev, "%s(%d)\n", __func__, irq); complete(&data->func_changed); return IRQ_HANDLED; } static irqreturn_t abox_wdma_ipc_handler(int ipc, void *dev_id, ABOX_IPC_MSG *msg) { struct abox_data *abox_data = dev_id; struct IPC_PCMTASK_MSG *pcmtask_msg = &msg->msg.pcmtask; int id = pcmtask_msg->channel_id; struct abox_dma_data *data; struct device *dev; if (id >= ARRAY_SIZE(abox_data->dev_wdma) || !abox_data->dev_wdma[id]) return IRQ_NONE; dev = abox_data->dev_wdma[id]; data = dev_get_drvdata(dev); abox_dbg(dev, "%s(%d)\n", __func__, pcmtask_msg->msgtype); switch (pcmtask_msg->msgtype) { case PCM_PLTDAI_POINTER: if (data->backend) { dev_warn_ratelimited(dev, "pointer ipc to backend\n"); break; } data->pointer = pcmtask_msg->param.pointer; snd_pcm_period_elapsed(data->substream); break; case PCM_PLTDAI_ACK: data->ack_enabled = !!pcmtask_msg->param.trigger; break; case PCM_PLTDAI_CLOSED: complete(&data->closed); break; default: abox_warn(dev, "unknown message: %d\n", pcmtask_msg->msgtype); return IRQ_NONE; } return IRQ_HANDLED; } static int abox_wdma_enabled(struct abox_dma_data *data) { unsigned int val = 0; regmap_read(data->abox_data->regmap, ABOX_WDMA_CTRL(data->id), &val); return !!(val & ABOX_WDMA_ENABLE_MASK); } static int abox_wdma_progress(struct abox_dma_data *data) { unsigned int val = 0; regmap_read(data->abox_data->regmap, ABOX_WDMA_STATUS(data->id), &val); return !!(val & ABOX_WDMA_PROGRESS_MASK); } static void abox_wdma_disable_barrier(struct device *dev, struct abox_dma_data *data) { struct abox_data *abox_data = data->abox_data; u64 timeout = local_clock() + abox_get_waiting_ns(true); while (abox_wdma_progress(data) || abox_wdma_enabled(data)) { if (local_clock() <= timeout) { cond_resched(); continue; } dev_warn_ratelimited(dev, "WDMA disable timeout\n"); abox_dbg_dump_simple(dev, abox_data, "WDMA disable timeout"); /* Disable DMA by force */ regmap_update_bits_base(abox_data->regmap, ABOX_WDMA_CTRL(data->id), ABOX_WDMA_ENABLE_MASK, 0, NULL, false, true); break; } } static int abox_wdma_backend(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); return (asoc_rtd_to_cpu(rtd, 0)->id >= ABOX_WDMA0_BE); } static int abox_wdma_hw_params(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai); struct device *dev = data->dev; struct abox_data *abox_data = data->abox_data; struct device *dev_abox = abox_data->dev; struct snd_dma_buffer *dmab; int id = data->id; size_t buffer_bytes = params_buffer_bytes(params); unsigned int iova; int burst_len, ret; ABOX_IPC_MSG msg; struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; if (abox_wdma_backend(substream) && !abox_dma_can_params(rtd, substream->stream)) { abox_info(dev, "%s skip\n", __func__); return 0; } abox_dbg(dev, "%s\n", __func__); data->hw_params = *params; switch (data->buf_type) { case BUFFER_TYPE_RAM: if (data->ramb.bytes >= buffer_bytes) { iova = data->ramb.addr; dmab = &data->ramb; break; } /* fallback to DMA mode */ abox_info(dev, "fallback to dma buffer\n"); case BUFFER_TYPE_DMA: if (data->dmab.bytes < buffer_bytes) { abox_iommu_unmap(dev_abox, IOVA_WDMA_BUFFER(id)); snd_dma_free_pages(&data->dmab); ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, PAGE_ALIGN(buffer_bytes), &data->dmab); if (ret < 0) return ret; ret = abox_iommu_map(dev_abox, IOVA_WDMA_BUFFER(id), data->dmab.addr, data->dmab.bytes, data->dmab.area); if (ret < 0) return ret; abox_info(dev, "dma buffer changed\n"); } iova = IOVA_WDMA_BUFFER(id); dmab = &data->dmab; break; case BUFFER_TYPE_ION: if (data->dmab.bytes < buffer_bytes) return -ENOMEM; abox_info(dev, "ion_buffer %s bytes(%zu) size(%zu)\n", __func__, buffer_bytes, data->ion_buf->size); iova = IOVA_WDMA_BUFFER(id); dmab = &data->dmab; break; default: abox_err(dev, "buf_type is not defined\n"); break; } if (!abox_wdma_backend(substream)) { snd_pcm_set_runtime_buffer(substream, dmab); runtime->dma_bytes = buffer_bytes; abox_dma_acquire_irq(data, DMA_IRQ_FADE_DONE); } else { abox_dbg(dev, "backend dai mode\n"); } data->backend = abox_wdma_backend(substream); if (abox_dma_is_sync_mode(data)) burst_len = 0x1; else burst_len = 0x4; ret = snd_soc_component_update_bits(data->cmpnt, DMA_REG_CTRL0, ABOX_DMA_BURST_LEN_MASK, burst_len << ABOX_DMA_BURST_LEN_L); if (ret < 0) abox_err(dev, "burst length write error: %d\n", ret); pcmtask_msg->channel_id = id; msg.ipcid = IPC_PCMCAPTURE; msg.task_id = pcmtask_msg->channel_id = id; pcmtask_msg->msgtype = PCM_SET_BUFFER; pcmtask_msg->param.setbuff.addr = iova; pcmtask_msg->param.setbuff.size = params_period_bytes(params); pcmtask_msg->param.setbuff.count = params_periods(params); ret = abox_wdma_request_ipc(data, &msg, 0, 0); if (ret < 0) return ret; pcmtask_msg->msgtype = PCM_PLTDAI_HW_PARAMS; pcmtask_msg->param.hw_params.sample_rate = params_rate(params); pcmtask_msg->param.hw_params.bit_depth = params_width(params); pcmtask_msg->param.hw_params.channels = params_channels(params); if (params_format(params) == SNDRV_PCM_FORMAT_S24_3LE) pcmtask_msg->param.hw_params.packed = 1; else pcmtask_msg->param.hw_params.packed = 0; ret = abox_wdma_request_ipc(data, &msg, 0, 0); if (ret < 0) return ret; if (params_rate(params) > 48000) abox_request_cpu_gear_dai(dev, abox_data, cpu_dai, abox_data->cpu_gear_min - 1); abox_info(dev, "%s:Total=%u PrdSz=%u(%u) #Prds=%u rate=%u, width=%d, channels=%u\n", snd_pcm_stream_str(substream), params_buffer_bytes(params), params_period_size(params), params_period_bytes(params), params_periods(params), params_rate(params), params_width(params), params_channels(params)); return 0; } static int abox_wdma_hw_free(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai); struct device *dev = data->dev; int id = data->id; ABOX_IPC_MSG msg; struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; if (abox_wdma_backend(substream) && !abox_dma_can_free(rtd, substream->stream)) { abox_dbg(dev, "%s skip\n", __func__); return 0; } abox_dbg(dev, "%s\n", __func__); msg.ipcid = IPC_PCMCAPTURE; pcmtask_msg->msgtype = PCM_PLTDAI_HW_FREE; msg.task_id = pcmtask_msg->channel_id = id; abox_wdma_request_ipc(data, &msg, 0, 0); if (!abox_wdma_backend(substream)) { abox_dma_release_irq(data, DMA_IRQ_FADE_DONE); snd_pcm_set_runtime_buffer(substream, NULL); } return 0; } static int abox_wdma_prepare(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai); struct device *dev = data->dev; int id = data->id; int ret; ABOX_IPC_MSG msg; struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; if (abox_wdma_backend(substream) && !abox_dma_can_prepare(rtd, substream->stream)) { abox_dbg(dev, "%s skip\n", __func__); return 0; } abox_dbg(dev, "%s\n", __func__); data->pointer = IOVA_WDMA_BUFFER(id); /* set auto fade in before dma enable */ snd_soc_component_update_bits(data->cmpnt, DMA_REG_CTRL, ABOX_DMA_AUTO_FADE_IN_MASK, data->auto_fade_in ? ABOX_DMA_AUTO_FADE_IN_MASK : 0); msg.ipcid = IPC_PCMCAPTURE; pcmtask_msg->msgtype = PCM_PLTDAI_PREPARE; msg.task_id = pcmtask_msg->channel_id = id; ret = abox_wdma_request_ipc(data, &msg, 0, 0); return ret; } static int abox_wdma_trigger_ipc(struct abox_dma_data *data, bool atomic, bool start) { ABOX_IPC_MSG msg; struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; if (data->enabled == start) return 0; data->enabled = start; msg.ipcid = IPC_PCMCAPTURE; pcmtask_msg->msgtype = PCM_PLTDAI_TRIGGER; msg.task_id = pcmtask_msg->channel_id = data->id; pcmtask_msg->param.trigger = start; return abox_wdma_request_ipc(data, &msg, atomic, 0); } static int abox_wdma_trigger(struct snd_soc_component *component, struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai); struct device *dev = data->dev; int ret; /* use mute_stream callback instead, if the stream is backend */ if (abox_wdma_backend(substream)) return 0; abox_info(dev, "%s(%d)\n", __func__, cmd); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ret = abox_wdma_trigger_ipc(data, true, true); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ret = abox_wdma_trigger_ipc(data, true, false); if (!completion_done(&data->func_changed)) complete(&data->func_changed); break; default: ret = -EINVAL; break; } return ret; } static snd_pcm_uframes_t abox_wdma_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai); struct device *dev = data->dev; int id = data->id; ssize_t pointer; unsigned int status = 0; bool progress; regmap_read(data->abox_data->regmap, ABOX_WDMA_STATUS(id), &status); progress = !!(status & ABOX_WDMA_PROGRESS_MASK); if (data->pointer >= IOVA_WDMA_BUFFER(id)) { pointer = data->pointer - IOVA_WDMA_BUFFER(id); } else if (((data->type == PLATFORM_NORMAL) || (data->type == PLATFORM_SYNC)) && progress) { ssize_t offset, count; ssize_t buffer_bytes, period_bytes; buffer_bytes = snd_pcm_lib_buffer_bytes(substream); period_bytes = snd_pcm_lib_period_bytes(substream); if (hweight_long(ABOX_WDMA_WBUF_OFFSET_MASK) > 8) offset = ((status & ABOX_WDMA_WBUF_OFFSET_MASK) >> ABOX_WDMA_WBUF_OFFSET_L) << 4; else offset = ((status & ABOX_WDMA_WBUF_OFFSET_MASK) >> ABOX_WDMA_WBUF_OFFSET_L) * period_bytes; if (period_bytes > ABOX_WDMA_WBUF_CNT_MASK + 1) count = 0; else count = (status & ABOX_WDMA_WBUF_CNT_MASK); while ((offset % period_bytes) && (buffer_bytes >= 0)) { buffer_bytes -= period_bytes; if ((buffer_bytes & offset) == offset) offset = buffer_bytes; } pointer = offset + count; } else { pointer = 0; } abox_dbg(dev, "%s: pointer=%08zx\n", __func__, pointer); return bytes_to_frames(runtime, pointer); } static int abox_wdma_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai); struct device *dev = data->dev; struct abox_data *abox_data = data->abox_data; int id = data->id; int ret; long time; ABOX_IPC_MSG msg; struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; if (abox_wdma_backend(substream) && !abox_dma_can_open(rtd, substream->stream)) { abox_info(dev, "%s skip\n", __func__); return 0; } abox_info(dev, "%s\n", __func__); abox_wait_restored(abox_data); if (data->closing) { data->closing = false; /* complete close before new open */ time = wait_for_completion_timeout(&data->closed, abox_get_waiting_jiffies(true)); if (time == 0) abox_warn(dev, "close timeout\n"); } if (data->type == PLATFORM_CALL) { if (abox_cpu_gear_idle(dev, ABOX_CPU_GEAR_CALL_VSS)) abox_request_cpu_gear_sync(dev, abox_data, ABOX_CPU_GEAR_CALL_KERNEL, ABOX_CPU_GEAR_MAX, cpu_dai->name); ret = abox_vss_notify_call(dev, abox_data, 1); if (ret < 0) abox_warn(dev, "call notify failed: %d\n", ret); } abox_request_cpu_gear_dai(dev, abox_data, cpu_dai, abox_data->cpu_gear_min); if (substream->runtime) snd_soc_set_runtime_hwparams(substream, &abox_wdma_hardware); data->substream = substream; msg.ipcid = IPC_PCMCAPTURE; pcmtask_msg->msgtype = PCM_PLTDAI_OPEN; msg.task_id = pcmtask_msg->channel_id = id; ret = abox_wdma_request_ipc(data, &msg, 0, 0); return ret; } static int abox_wdma_close(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai); struct device *dev = data->dev; struct abox_data *abox_data = data->abox_data; int id = data->id; int ret; ABOX_IPC_MSG msg; struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; if (abox_wdma_backend(substream) && !abox_dma_can_close(rtd, substream->stream)) { abox_info(dev, "%s skip\n", __func__); return 0; } abox_info(dev, "%s\n", __func__); abox_wdma_disable_barrier(dev, data); data->substream = NULL; if (!abox_data->failsafe) data->closing = true; msg.ipcid = IPC_PCMCAPTURE; pcmtask_msg->msgtype = PCM_PLTDAI_CLOSE; msg.task_id = pcmtask_msg->channel_id = id; ret = abox_wdma_request_ipc(data, &msg, 0, 0); abox_request_cpu_gear_dai(dev, abox_data, cpu_dai, 0); if (data->type == PLATFORM_CALL) { abox_request_cpu_gear(dev, abox_data, ABOX_CPU_GEAR_CALL_KERNEL, 0, cpu_dai->name); ret = abox_vss_notify_call(dev, abox_data, 0); if (ret < 0) abox_warn(dev, "call notify failed: %d\n", ret); } /* Release ASRC to reuse it in other DMA */ abox_cmpnt_asrc_release(abox_data, SNDRV_PCM_STREAM_CAPTURE, id); return ret; } static int abox_wdma_mmap(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct vm_area_struct *vma) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai); struct device *dev = data->dev; abox_info(dev, "%s\n", __func__); if (data->buf_type == BUFFER_TYPE_ION) return dma_buf_mmap(data->ion_buf->dma_buf, vma, 0); else return dma_mmap_wc(dev, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); } static int abox_wdma_copy_user(struct snd_soc_component *component, struct snd_pcm_substream *substream, int channel, unsigned long pos, void __user *buf, unsigned long bytes) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(cpu_dai); struct device *dev = data->dev; int id = data->id; ABOX_IPC_MSG msg; struct IPC_PCMTASK_MSG *pcmtask_msg = &msg.msg.pcmtask; unsigned long appl_bytes = (pos + bytes) % runtime->dma_bytes; unsigned long start; void *ptr; int ret = 0; start = pos + channel * (runtime->dma_bytes / runtime->channels); ptr = runtime->dma_area + start; if (copy_to_user(buf, ptr, bytes)) ret = -EFAULT; if (!data->ack_enabled) return ret; abox_dbg(dev, "%s: %ld\n", __func__, appl_bytes); msg.ipcid = IPC_PCMCAPTURE; pcmtask_msg->msgtype = PCM_PLTDAI_ACK; pcmtask_msg->param.pointer = (unsigned int)appl_bytes; msg.task_id = pcmtask_msg->channel_id = id; return abox_wdma_request_ipc(data, &msg, 1, 0); } static int abox_wdma_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(dai); struct device *dev = data->dev; struct device *dev_abox = data->abox_data->dev; int id = data->id; size_t buffer_bytes = data->dmab.bytes; int ret; switch (data->buf_type) { case BUFFER_TYPE_ION: buffer_bytes = BUFFER_ION_BYTES_MAX; data->ion_buf = abox_ion_alloc(dev, data->abox_data, IOVA_WDMA_BUFFER(id), buffer_bytes, false); if (!IS_ERR(data->ion_buf)) { /* update buffer infomation using ion allocated buffer */ data->dmab.area = data->ion_buf->kva; data->dmab.addr = data->ion_buf->iova; data->dmab.bytes = data->ion_buf->size; ret = abox_ion_new_hwdep(rtd, data->ion_buf, &data->hwdep); if (ret < 0) { abox_err(dev, "failed to add hwdep: %d\n", ret); return ret; } break; } abox_warn(dev, "fallback to dma alloc\n"); data->buf_type = BUFFER_TYPE_DMA; case BUFFER_TYPE_DMA: if (buffer_bytes < BUFFER_BYTES_MIN) buffer_bytes = BUFFER_BYTES_MIN; ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, buffer_bytes, &data->dmab); if (ret < 0) return ret; ret = abox_iommu_map(dev_abox, IOVA_WDMA_BUFFER(id), data->dmab.addr, data->dmab.bytes, data->dmab.area); break; case BUFFER_TYPE_RAM: ret = abox_of_get_addr(data->abox_data, dev->of_node, "samsung,buffer-address", (void **)&data->ramb.area, &data->ramb.addr, &data->ramb.bytes); break; } return ret; } static void abox_wdma_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm) { struct snd_soc_pcm_runtime *rtd = snd_pcm_chip(pcm); struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0); struct abox_dma_data *data = snd_soc_dai_get_drvdata(dai); struct device *dev = data->dev; struct device *dev_abox = data->abox_data->dev; int id = data->id; int ret = 0; switch (data->buf_type) { case BUFFER_TYPE_ION: if (data->ion_buf) { ret = abox_ion_free(dev, data->abox_data, data->ion_buf); if (ret < 0) abox_err(dev, "abox_ion_free() failed %d\n", ret); data->ion_buf = NULL; } if (data->hwdep) { snd_device_free(rtd->card->snd_card, data->hwdep); data->hwdep = NULL; } break; case BUFFER_TYPE_DMA: abox_iommu_unmap(dev_abox, IOVA_WDMA_BUFFER(id)); if (data->dmab.area) { snd_dma_free_pages(&data->dmab); data->dmab.area = NULL; } break; default: /* nothing to do */ break; } } static const char * const dither_type_texts[] = { "OFF", "RPDF", "TPDF", }; static SOC_ENUM_SINGLE_DECL(dither_type_enum, DMA_REG_BIT_CTRL, ABOX_DMA_DITHER_TYPE_L, dither_type_texts); static const struct snd_kcontrol_new abox_wdma_bit_controls[] = { SOC_ENUM("Dither Type", dither_type_enum), }; static int abox_wdma_probe(struct snd_soc_component *cmpnt) { struct device *dev = cmpnt->dev; struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt); u32 id; int ret; abox_dbg(dev, "%s\n", __func__); snd_soc_add_component_controls(cmpnt, abox_wdma_bit_controls, ARRAY_SIZE(abox_wdma_bit_controls)); data->cmpnt = cmpnt; abox_cmpnt_register_wdma(data->abox_data->dev, dev, data->id, data->dai_drv[DMA_DAI_PCM].name); ret = of_samsung_property_read_u32(dev, dev->of_node, "asrc-id", &id); if (ret >= 0) { ret = abox_cmpnt_asrc_lock(data->abox_data, SNDRV_PCM_STREAM_CAPTURE, data->id, id); if (ret < 0) abox_err(dev, "asrc id lock failed\n"); else abox_info(dev, "asrc id locked: %u\n", id); } return 0; } static void abox_wdma_remove(struct snd_soc_component *cmpnt) { struct device *dev = cmpnt->dev; abox_info(dev, "%s\n", __func__); } static unsigned int abox_wdma_read(struct snd_soc_component *cmpnt, unsigned int reg) { struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt); struct abox_data *abox_data = data->abox_data; unsigned int id = data->id; unsigned int base = ABOX_WDMA_CTRL(id); unsigned int val = 0; if (reg > DMA_REG_STATUS) { abox_warn(cmpnt->dev, "invalid dma register:%#x\n", reg); dump_stack(); } /* CTRL register is shared with firmware */ if (reg == DMA_REG_CTRL) { if (pm_runtime_get_if_in_use(cmpnt->dev) > 0) { regmap_read(abox_data->regmap, base + reg, &val); pm_runtime_put(cmpnt->dev); } else { val = data->c_reg_ctrl; } } else { regmap_read(abox_data->regmap, base + reg, &val); } return val; } static int abox_wdma_write(struct snd_soc_component *cmpnt, unsigned int reg, unsigned int val) { struct abox_dma_data *data = snd_soc_component_get_drvdata(cmpnt); struct abox_data *abox_data = data->abox_data; unsigned int id = data->id; unsigned int base = ABOX_WDMA_CTRL(id); int ret = 0; if (reg > DMA_REG_STATUS) { abox_warn(cmpnt->dev, "invalid dma register:%#x\n", reg); dump_stack(); } /* CTRL register is shared with firmware */ if (reg == DMA_REG_CTRL) { data->c_reg_ctrl &= ~REG_CTRL_KERNEL_MASK; data->c_reg_ctrl |= val & REG_CTRL_KERNEL_MASK; if (pm_runtime_get_if_in_use(cmpnt->dev) > 0) { ret = regmap_update_bits(abox_data->regmap, base + reg, REG_CTRL_KERNEL_MASK, val); pm_runtime_put(cmpnt->dev); } } else { ret = regmap_write(abox_data->regmap, base + reg, val); } return ret; } static const struct snd_kcontrol_new abox_wdma_controls[] = { SOC_SINGLE_EXT("Rate", DMA_RATE, 0, 384000, 0, abox_dma_hw_params_get, abox_dma_hw_params_put), SOC_SINGLE_EXT("Width", DMA_WIDTH, 0, 32, 0, abox_dma_hw_params_get, abox_dma_hw_params_put), SOC_SINGLE_EXT("Channel", DMA_CHANNEL, 0, 8, 0, abox_dma_hw_params_get, abox_dma_hw_params_put), SOC_SINGLE_EXT("Period", DMA_PERIOD, 0, INT_MAX, 0, abox_dma_hw_params_get, abox_dma_hw_params_put), SOC_SINGLE_EXT("Periods", DMA_PERIODS, 0, INT_MAX, 0, abox_dma_hw_params_get, abox_dma_hw_params_put), SOC_SINGLE_EXT("Packed", DMA_PACKED, 0, 1, 0, abox_dma_hw_params_get, abox_dma_hw_params_put), SOC_ENUM_EXT("Func", abox_dma_func_enum, snd_soc_get_enum_double, abox_dma_func_put), SOC_SINGLE_EXT("Auto Fade In", DMA_REG_CTRL, ABOX_DMA_AUTO_FADE_IN_L, 1, 0, abox_dma_auto_fade_in_get, abox_dma_auto_fade_in_put), SOC_SINGLE("Vol Factor", DMA_REG_VOL_FACTOR, ABOX_DMA_VOL_FACTOR_L, 0xffffff, 0), SOC_SINGLE("Vol Change", DMA_REG_VOL_CHANGE, ABOX_DMA_VOL_FACTOR_L, 0xffffff, 0), ABOX_DMA_SINGLE_S("Dither Seed", DMA_REG_DITHER_SEED, ABOX_DMA_DITHER_SEED_L, INT_MAX, 31, 0), }; static const struct snd_soc_component_driver abox_wdma = { .controls = abox_wdma_controls, .num_controls = ARRAY_SIZE(abox_wdma_controls), .probe = abox_wdma_probe, .remove = abox_wdma_remove, .read = abox_wdma_read, .write = abox_wdma_write, .pcm_construct = abox_wdma_pcm_new, .pcm_destruct = abox_wdma_pcm_free, .open = abox_wdma_open, .close = abox_wdma_close, .hw_params = abox_wdma_hw_params, .hw_free = abox_wdma_hw_free, .prepare = abox_wdma_prepare, .trigger = abox_wdma_trigger, .pointer = abox_wdma_pointer, .copy_user = abox_wdma_copy_user, .mmap = abox_wdma_mmap, }; static int abox_wdma_mute_stream(struct snd_soc_dai *dai, int mute, int stream) { struct abox_dma_data *data = snd_soc_dai_get_drvdata(dai); struct snd_soc_pcm_runtime *rtd; struct device *dev = dai->dev; if (!data->substream) { abox_warn(dev, "%s(%d): substream is null\n", __func__, mute); return 0; } rtd = asoc_substream_to_rtd(data->substream); if (mute) { if (!abox_dma_can_stop(rtd, stream)) return 0; } else { if (!abox_dma_can_start(rtd, stream)) return 0; } abox_info(dev, "%s(%d)\n", __func__, mute); return abox_wdma_trigger_ipc(data, false, !mute); } static const struct snd_soc_dai_ops abox_wdma_be_dai_ops = { .mute_stream = abox_wdma_mute_stream, }; static const struct snd_soc_dai_driver abox_wdma_dai_drv[] = { { .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 8, .rates = ABOX_SAMPLING_RATES, .rate_min = 8000, .rate_max = 384000, .formats = ABOX_SAMPLE_FORMATS, }, }, { .ops = &abox_wdma_be_dai_ops, .playback = { .stream_name = "BE Playback", .channels_min = 1, .channels_max = 8, .rates = ABOX_SAMPLING_RATES, .rate_min = 8000, .rate_max = 384000, .formats = ABOX_SAMPLE_FORMATS, }, .capture = { .stream_name = "BE Capture", .channels_min = 1, .channels_max = 8, .rates = ABOX_SAMPLING_RATES, .rate_min = 8000, .rate_max = 384000, .formats = ABOX_SAMPLE_FORMATS, }, .symmetric_rates = 1, .symmetric_channels = 1, .symmetric_samplebits = 1, }, }; static enum abox_irq abox_wdma_get_irq(struct abox_dma_data *data, enum abox_dma_irq irq) { unsigned int id = data->id; enum abox_irq ret; if (id >= COUNT_SPUM) return -EINVAL; switch (irq) { case DMA_IRQ_BUF_FULL: ret = IRQ_WDMA0_BUF_FULL + id; break; case DMA_IRQ_FADE_DONE: ret = IRQ_WDMA0_FADE_DONE + id; break; case DMA_IRQ_ERR: ret = IRQ_WDMA0_ERR + id; break; default: ret = -EINVAL; break; } return ret; } static enum abox_dai abox_wdma_get_dai_id(enum abox_dma_dai dai, int id) { enum abox_dai ret; if (id >= COUNT_SPUM) return -EINVAL; switch (dai) { case DMA_DAI_PCM: ret = ABOX_WDMA0 + id; break; case DMA_DAI_BE: ret = ABOX_WDMA0_BE + id; break; default: ret = -EINVAL; break; } return ret; } static char *abox_wdma_get_dai_name(struct device *dev, enum abox_dma_dai dai, int id) { char *ret; if (id >= COUNT_SPUM) return ERR_PTR(-EINVAL); switch (dai) { case DMA_DAI_PCM: ret = devm_kasprintf(dev, GFP_KERNEL, "WDMA%d", id); break; case DMA_DAI_BE: ret = devm_kasprintf(dev, GFP_KERNEL, "WDMA%d BE", id); break; default: ret = ERR_PTR(-EINVAL); break; } return ret; } static const struct of_device_id samsung_abox_wdma_match[] = { { .compatible = "samsung,abox-wdma", .data = (void *)&(struct abox_dma_of_data){ .get_irq = abox_wdma_get_irq, .get_dai_id = abox_wdma_get_dai_id, .get_dai_name = abox_wdma_get_dai_name, .dai_drv = abox_wdma_dai_drv, .num_dai = ARRAY_SIZE(abox_wdma_dai_drv), .cmpnt_drv = &abox_wdma }, }, {}, }; MODULE_DEVICE_TABLE(of, samsung_abox_wdma_match); static int abox_wdma_runtime_suspend(struct device *dev) { abox_dbg(dev, "%s\n", __func__); return 0; } static int abox_wdma_runtime_resume(struct device *dev) { struct abox_dma_data *data = dev_get_drvdata(dev); abox_dbg(dev, "%s\n", __func__); regmap_update_bits(data->abox_data->regmap, ABOX_WDMA_CTRL0(data->id), REG_CTRL_KERNEL_MASK, data->c_reg_ctrl); return 0; } static int samsung_abox_wdma_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct abox_dma_data *data; const struct abox_dma_of_data *of_data; int i, ret; u32 value; const char *type; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; platform_set_drvdata(pdev, data); data->dev = dev; dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); data->sfr_base = devm_get_ioremap(pdev, "sfr", NULL, NULL); if (IS_ERR(data->sfr_base)) return PTR_ERR(data->sfr_base); data->dev_abox = pdev->dev.parent; if (!data->dev_abox) { abox_err(dev, "Failed to get abox device\n"); return -EPROBE_DEFER; } data->abox_data = dev_get_drvdata(data->dev_abox); init_completion(&data->closed); init_completion(&data->func_changed); abox_register_ipc_handler(data->dev_abox, IPC_PCMCAPTURE, abox_wdma_ipc_handler, data->abox_data); ret = of_samsung_property_read_u32(dev, np, "id", &data->id); if (ret < 0) return ret; ret = of_samsung_property_read_string(dev, np, "type", &type); if (ret < 0) type = ""; if (!strncmp(type, "call", sizeof("call"))) data->type = PLATFORM_CALL; else if (!strncmp(type, "compress", sizeof("compress"))) data->type = PLATFORM_COMPRESS; else if (!strncmp(type, "realtime", sizeof("realtime"))) data->type = PLATFORM_REALTIME; else if (!strncmp(type, "vi-sensing", sizeof("vi-sensing"))) data->type = PLATFORM_VI_SENSING; else if (!strncmp(type, "sync", sizeof("sync"))) data->type = PLATFORM_SYNC; else data->type = PLATFORM_NORMAL; ret = of_samsung_property_read_u32(dev, np, "buffer_bytes", &value); if (ret < 0) value = 0; data->dmab.bytes = value; ret = of_samsung_property_read_string(dev, np, "buffer_type", &type); if (ret < 0) type = ""; if (!strncmp(type, "ion", sizeof("ion"))) data->buf_type = BUFFER_TYPE_ION; else if (!strncmp(type, "dma", sizeof("dma"))) data->buf_type = BUFFER_TYPE_DMA; else if (!strncmp(type, "ram", sizeof("ram"))) data->buf_type = BUFFER_TYPE_RAM; else data->buf_type = BUFFER_TYPE_DMA; of_data = data->of_data = of_device_get_match_data(dev); data->num_dai = of_data->num_dai; data->dai_drv = devm_kmemdup(dev, of_data->dai_drv, sizeof(*of_data->dai_drv) * data->num_dai, GFP_KERNEL); if (!data->dai_drv) return -ENOMEM; for (i = 0; i < data->num_dai; i++) { data->dai_drv[i].id = of_data->get_dai_id(i, data->id); data->dai_drv[i].name = of_data->get_dai_name(dev, i, data->id); } ret = devm_snd_soc_register_component(dev, data->of_data->cmpnt_drv, data->dai_drv, data->num_dai); if (ret < 0) return ret; pm_runtime_enable(dev); abox_dma_register_irq(data, DMA_IRQ_FADE_DONE, abox_wdma_fade_done, dev); data->hwdep = NULL; return 0; } static int samsung_abox_wdma_remove(struct platform_device *pdev) { return 0; } static void samsung_abox_wdma_shutdown(struct platform_device *pdev) { struct device *dev = &pdev->dev; abox_dbg(dev, "%s\n", __func__); pm_runtime_disable(dev); } static const struct dev_pm_ops samsung_abox_wdma_pm = { SET_RUNTIME_PM_OPS(abox_wdma_runtime_suspend, abox_wdma_runtime_resume, NULL) }; struct platform_driver samsung_abox_wdma_driver = { .probe = samsung_abox_wdma_probe, .remove = samsung_abox_wdma_remove, .shutdown = samsung_abox_wdma_shutdown, .driver = { .name = "abox-wdma", .owner = THIS_MODULE, .of_match_table = of_match_ptr(samsung_abox_wdma_match), .pm = &samsung_abox_wdma_pm, }, };