// SPDX-License-Identifier: GPL-2.0 // // Copyright (C) 2014-2016 Freescale Semiconductor, Inc. // Copyright (C) 2019-2024 NXP // // Freescale ASRC Memory to Memory (M2M) driver #include #include #include #include #include #include #include #include "fsl_asrc_common.h" #define DIR_STR(dir) (dir) == IN ? "in" : "out" #define ASRC_xPUT_DMA_CALLBACK(dir) \ (((dir) == IN) ? asrc_input_dma_callback \ : asrc_output_dma_callback) /* Maximum output and capture buffer size */ #define ASRC_M2M_BUFFER_SIZE (512 * 1024) /* Maximum output and capture period size */ #define ASRC_M2M_PERIOD_SIZE (48 * 1024) /* dma complete callback */ static void asrc_input_dma_callback(void *data) { struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; complete(&pair->complete[IN]); } /* dma complete callback */ static void asrc_output_dma_callback(void *data) { struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; complete(&pair->complete[OUT]); } /** *asrc_read_last_fifo: read all the remaining data from FIFO *@pair: Structure pointer of fsl_asrc_pair *@dma_vaddr: virtual address of capture buffer *@length: payload length of capture buffer */ static void asrc_read_last_fifo(struct fsl_asrc_pair *pair, void *dma_vaddr, u32 *length) { struct fsl_asrc *asrc = pair->asrc; enum asrc_pair_index index = pair->index; u32 i, reg, size, t_size = 0, width; u32 *reg32 = NULL; u16 *reg16 = NULL; u8 *reg24 = NULL; width = snd_pcm_format_physical_width(pair->sample_format[OUT]); if (width == 32) reg32 = dma_vaddr + *length; else if (width == 16) reg16 = dma_vaddr + *length; else reg24 = dma_vaddr + *length; retry: size = asrc->get_output_fifo_size(pair); if (size + *length > ASRC_M2M_BUFFER_SIZE) goto end; for (i = 0; i < size * pair->channels; i++) { regmap_read(asrc->regmap, asrc->get_fifo_addr(OUT, index), ®); if (reg32) { *reg32++ = reg; } else if (reg16) { *reg16++ = (u16)reg; } else { *reg24++ = (u8)reg; *reg24++ = (u8)(reg >> 8); *reg24++ = (u8)(reg >> 16); } } t_size += size; /* In case there is data left in FIFO */ if (size) goto retry; end: /* Update payload length */ if (reg32) *length += t_size * pair->channels * 4; else if (reg16) *length += t_size * pair->channels * 2; else *length += t_size * pair->channels * 3; } /* config dma channel */ static int asrc_dmaconfig(struct fsl_asrc_pair *pair, struct dma_chan *chan, u32 dma_addr, dma_addr_t buf_addr, u32 buf_len, int dir, int width) { struct fsl_asrc *asrc = pair->asrc; struct device *dev = &asrc->pdev->dev; struct dma_slave_config slave_config; enum dma_slave_buswidth buswidth; unsigned int sg_len, max_period_size; struct scatterlist *sg; int ret, i; switch (width) { case 8: buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; break; case 16: buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; break; case 24: buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES; break; case 32: buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; break; default: dev_err(dev, "invalid word width\n"); return -EINVAL; } memset(&slave_config, 0, sizeof(slave_config)); if (dir == IN) { slave_config.direction = DMA_MEM_TO_DEV; slave_config.dst_addr = dma_addr; slave_config.dst_addr_width = buswidth; slave_config.dst_maxburst = asrc->m2m_get_maxburst(IN, pair); } else { slave_config.direction = DMA_DEV_TO_MEM; slave_config.src_addr = dma_addr; slave_config.src_addr_width = buswidth; slave_config.src_maxburst = asrc->m2m_get_maxburst(OUT, pair); } ret = dmaengine_slave_config(chan, &slave_config); if (ret) { dev_err(dev, "failed to config dmaengine for %s task: %d\n", DIR_STR(dir), ret); return -EINVAL; } max_period_size = rounddown(ASRC_M2M_PERIOD_SIZE, width * pair->channels / 8); /* scatter gather mode */ sg_len = buf_len / max_period_size; if (buf_len % max_period_size) sg_len += 1; sg = kmalloc_array(sg_len, sizeof(*sg), GFP_KERNEL); if (!sg) return -ENOMEM; sg_init_table(sg, sg_len); for (i = 0; i < (sg_len - 1); i++) { sg_dma_address(&sg[i]) = buf_addr + i * max_period_size; sg_dma_len(&sg[i]) = max_period_size; } sg_dma_address(&sg[i]) = buf_addr + i * max_period_size; sg_dma_len(&sg[i]) = buf_len - i * max_period_size; pair->desc[dir] = dmaengine_prep_slave_sg(chan, sg, sg_len, slave_config.direction, DMA_PREP_INTERRUPT); kfree(sg); if (!pair->desc[dir]) { dev_err(dev, "failed to prepare dmaengine for %s task\n", DIR_STR(dir)); return -EINVAL; } pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir); pair->desc[dir]->callback_param = pair; return 0; } /* main function of converter */ static int asrc_m2m_device_run(struct fsl_asrc_pair *pair, struct snd_compr_task_runtime *task) { struct fsl_asrc *asrc = pair->asrc; struct device *dev = &asrc->pdev->dev; enum asrc_pair_index index = pair->index; struct snd_dma_buffer *src_buf, *dst_buf; unsigned int in_buf_len; unsigned int out_dma_len; unsigned int width; u32 fifo_addr; int ret = 0; /* set ratio mod */ if (asrc->m2m_set_ratio_mod) { if (pair->ratio_mod_flag) { asrc->m2m_set_ratio_mod(pair, pair->ratio_mod); pair->ratio_mod_flag = false; } } src_buf = &pair->dma_buffer[IN]; dst_buf = &pair->dma_buffer[OUT]; width = snd_pcm_format_physical_width(pair->sample_format[IN]); fifo_addr = asrc->paddr + asrc->get_fifo_addr(IN, index); in_buf_len = task->input_size; if (in_buf_len < width * pair->channels / 8 || in_buf_len > ASRC_M2M_BUFFER_SIZE || in_buf_len % (width * pair->channels / 8)) { dev_err(dev, "out buffer size is error: [%d]\n", in_buf_len); ret = -EINVAL; goto end; } /* dma config for output dma channel */ ret = asrc_dmaconfig(pair, pair->dma_chan[IN], fifo_addr, src_buf->addr, in_buf_len, IN, width); if (ret) { dev_err(dev, "out dma config error\n"); goto end; } width = snd_pcm_format_physical_width(pair->sample_format[OUT]); fifo_addr = asrc->paddr + asrc->get_fifo_addr(OUT, index); out_dma_len = asrc->m2m_calc_out_len(pair, in_buf_len); if (out_dma_len > 0 && out_dma_len <= ASRC_M2M_BUFFER_SIZE) { /* dma config for capture dma channel */ ret = asrc_dmaconfig(pair, pair->dma_chan[OUT], fifo_addr, dst_buf->addr, out_dma_len, OUT, width); if (ret) { dev_err(dev, "cap dma config error\n"); goto end; } } else if (out_dma_len > ASRC_M2M_BUFFER_SIZE) { dev_err(dev, "cap buffer size error\n"); ret = -EINVAL; goto end; } reinit_completion(&pair->complete[IN]); reinit_completion(&pair->complete[OUT]); /* Submit DMA request */ dmaengine_submit(pair->desc[IN]); dma_async_issue_pending(pair->desc[IN]->chan); if (out_dma_len > 0) { dmaengine_submit(pair->desc[OUT]); dma_async_issue_pending(pair->desc[OUT]->chan); } asrc->m2m_start(pair); if (!wait_for_completion_interruptible_timeout(&pair->complete[IN], 10 * HZ)) { dev_err(dev, "out DMA task timeout\n"); ret = -ETIMEDOUT; goto end; } if (out_dma_len > 0) { if (!wait_for_completion_interruptible_timeout(&pair->complete[OUT], 10 * HZ)) { dev_err(dev, "cap DMA task timeout\n"); ret = -ETIMEDOUT; goto end; } } /* read the last words from FIFO */ asrc_read_last_fifo(pair, dst_buf->area, &out_dma_len); /* update payload length for capture */ task->output_size = out_dma_len; end: return ret; } static int fsl_asrc_m2m_comp_open(struct snd_compr_stream *stream) { struct fsl_asrc *asrc = stream->private_data; struct snd_compr_runtime *runtime = stream->runtime; struct device *dev = &asrc->pdev->dev; struct fsl_asrc_pair *pair; int size, ret; pair = kzalloc(sizeof(*pair) + asrc->pair_priv_size, GFP_KERNEL); if (!pair) return -ENOMEM; pair->private = (void *)pair + sizeof(struct fsl_asrc_pair); pair->asrc = asrc; init_completion(&pair->complete[IN]); init_completion(&pair->complete[OUT]); runtime->private_data = pair; size = ASRC_M2M_BUFFER_SIZE; ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, &pair->dma_buffer[IN]); if (ret) goto error_alloc_in_buf; ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, &pair->dma_buffer[OUT]); if (ret) goto error_alloc_out_buf; ret = pm_runtime_get_sync(dev); if (ret < 0) { dev_err(dev, "Failed to power up asrc\n"); goto err_pm_runtime; } return 0; err_pm_runtime: snd_dma_free_pages(&pair->dma_buffer[OUT]); error_alloc_out_buf: snd_dma_free_pages(&pair->dma_buffer[IN]); error_alloc_in_buf: kfree(pair); return ret; } static int fsl_asrc_m2m_comp_release(struct snd_compr_stream *stream) { struct fsl_asrc *asrc = stream->private_data; struct snd_compr_runtime *runtime = stream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; struct device *dev = &asrc->pdev->dev; pm_runtime_put_sync(dev); snd_dma_free_pages(&pair->dma_buffer[IN]); snd_dma_free_pages(&pair->dma_buffer[OUT]); kfree(runtime->private_data); return 0; } static int fsl_asrc_m2m_comp_set_params(struct snd_compr_stream *stream, struct snd_compr_params *params) { struct fsl_asrc *asrc = stream->private_data; struct snd_compr_runtime *runtime = stream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; struct fsl_asrc_m2m_cap cap; int ret, i; ret = asrc->m2m_get_cap(&cap); if (ret) return -EINVAL; if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.format) & cap.fmt_in) pair->sample_format[IN] = (__force snd_pcm_format_t)params->codec.format; else return -EINVAL; if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.pcm_format) & cap.fmt_out) pair->sample_format[OUT] = (__force snd_pcm_format_t)params->codec.pcm_format; else return -EINVAL; /* check input rate is in scope */ for (i = 0; i < cap.rate_in_count; i++) if (params->codec.sample_rate == cap.rate_in[i]) { pair->rate[IN] = params->codec.sample_rate; break; } if (i == cap.rate_in_count) return -EINVAL; /* check output rate is in scope */ for (i = 0; i < cap.rate_out_count; i++) if (params->codec.options.src_d.out_sample_rate == cap.rate_out[i]) { pair->rate[OUT] = params->codec.options.src_d.out_sample_rate; break; } if (i == cap.rate_out_count) return -EINVAL; if (params->codec.ch_in != params->codec.ch_out || params->codec.ch_in < cap.chan_min || params->codec.ch_in > cap.chan_max) return -EINVAL; pair->channels = params->codec.ch_in; pair->buf_len[IN] = params->buffer.fragment_size; pair->buf_len[OUT] = params->buffer.fragment_size; return 0; } static int fsl_asrc_m2m_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma) { struct snd_dma_buffer *dmab = dmabuf->priv; return snd_dma_buffer_mmap(dmab, vma); } static struct sg_table *fsl_asrc_m2m_map_dma_buf(struct dma_buf_attachment *attachment, enum dma_data_direction direction) { struct snd_dma_buffer *dmab = attachment->dmabuf->priv; struct sg_table *sgt; sgt = kmalloc(sizeof(*sgt), GFP_KERNEL); if (!sgt) return NULL; if (dma_get_sgtable(attachment->dev, sgt, dmab->area, dmab->addr, dmab->bytes) < 0) goto free; if (dma_map_sgtable(attachment->dev, sgt, direction, 0)) goto free; return sgt; free: sg_free_table(sgt); kfree(sgt); return NULL; } static void fsl_asrc_m2m_unmap_dma_buf(struct dma_buf_attachment *attachment, struct sg_table *table, enum dma_data_direction direction) { dma_unmap_sgtable(attachment->dev, table, direction, 0); } static void fsl_asrc_m2m_release(struct dma_buf *dmabuf) { /* buffer is released by fsl_asrc_m2m_comp_release() */ } static const struct dma_buf_ops fsl_asrc_m2m_dma_buf_ops = { .mmap = fsl_asrc_m2m_mmap, .map_dma_buf = fsl_asrc_m2m_map_dma_buf, .unmap_dma_buf = fsl_asrc_m2m_unmap_dma_buf, .release = fsl_asrc_m2m_release, }; static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, struct snd_compr_task_runtime *task) { DEFINE_DMA_BUF_EXPORT_INFO(exp_info_in); DEFINE_DMA_BUF_EXPORT_INFO(exp_info_out); struct fsl_asrc *asrc = stream->private_data; struct snd_compr_runtime *runtime = stream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; struct device *dev = &asrc->pdev->dev; int ret; exp_info_in.ops = &fsl_asrc_m2m_dma_buf_ops; exp_info_in.size = ASRC_M2M_BUFFER_SIZE; exp_info_in.flags = O_RDWR; exp_info_in.priv = &pair->dma_buffer[IN]; task->input = dma_buf_export(&exp_info_in); if (IS_ERR(task->input)) { ret = PTR_ERR(task->input); return ret; } exp_info_out.ops = &fsl_asrc_m2m_dma_buf_ops; exp_info_out.size = ASRC_M2M_BUFFER_SIZE; exp_info_out.flags = O_RDWR; exp_info_out.priv = &pair->dma_buffer[OUT]; task->output = dma_buf_export(&exp_info_out); if (IS_ERR(task->output)) { ret = PTR_ERR(task->output); return ret; } /* Request asrc pair/context */ ret = asrc->request_pair(pair->channels, pair); if (ret) { dev_err(dev, "failed to request pair: %d\n", ret); goto err_request_pair; } ret = asrc->m2m_prepare(pair); if (ret) { dev_err(dev, "failed to start pair part one: %d\n", ret); goto err_start_part_one; } /* Request dma channels */ pair->dma_chan[IN] = asrc->get_dma_channel(pair, IN); if (!pair->dma_chan[IN]) { dev_err(dev, "[ctx%d] failed to get input DMA channel\n", pair->index); ret = -EBUSY; goto err_dma_channel_in; } pair->dma_chan[OUT] = asrc->get_dma_channel(pair, OUT); if (!pair->dma_chan[OUT]) { dev_err(dev, "[ctx%d] failed to get output DMA channel\n", pair->index); ret = -EBUSY; goto err_dma_channel_out; } return 0; err_dma_channel_out: dma_release_channel(pair->dma_chan[IN]); err_dma_channel_in: if (asrc->m2m_unprepare) asrc->m2m_unprepare(pair); err_start_part_one: asrc->release_pair(pair); err_request_pair: return ret; } static int fsl_asrc_m2m_comp_task_start(struct snd_compr_stream *stream, struct snd_compr_task_runtime *task) { struct snd_compr_runtime *runtime = stream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; return asrc_m2m_device_run(pair, task); } static int fsl_asrc_m2m_comp_task_stop(struct snd_compr_stream *stream, struct snd_compr_task_runtime *task) { return 0; } static int fsl_asrc_m2m_comp_task_free(struct snd_compr_stream *stream, struct snd_compr_task_runtime *task) { struct fsl_asrc *asrc = stream->private_data; struct snd_compr_runtime *runtime = stream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; /* Stop & release pair/context */ if (asrc->m2m_stop) asrc->m2m_stop(pair); if (asrc->m2m_unprepare) asrc->m2m_unprepare(pair); asrc->release_pair(pair); /* Release dma channel */ if (pair->dma_chan[IN]) dma_release_channel(pair->dma_chan[IN]); if (pair->dma_chan[OUT]) dma_release_channel(pair->dma_chan[OUT]); return 0; } static int fsl_asrc_m2m_get_caps(struct snd_compr_stream *cstream, struct snd_compr_caps *caps) { caps->num_codecs = 1; caps->min_fragment_size = 4096; caps->max_fragment_size = 4096; caps->min_fragments = 1; caps->max_fragments = 1; caps->codecs[0] = SND_AUDIOCODEC_PCM; return 0; } static int fsl_asrc_m2m_fill_codec_caps(struct fsl_asrc *asrc, struct snd_compr_codec_caps *codec) { struct fsl_asrc_m2m_cap cap; snd_pcm_format_t k; int j = 0; int ret; ret = asrc->m2m_get_cap(&cap); if (ret) return -EINVAL; pcm_for_each_format(k) { if (pcm_format_to_bits(k) & cap.fmt_in) { codec->descriptor[j].max_ch = cap.chan_max; memcpy(codec->descriptor[j].sample_rates, cap.rate_in, cap.rate_in_count * sizeof(__u32)); codec->descriptor[j].num_sample_rates = cap.rate_in_count; codec->descriptor[j].formats = (__force __u32)k; codec->descriptor[j].pcm_formats = cap.fmt_out; codec->descriptor[j].src.out_sample_rate_min = cap.rate_out[0]; codec->descriptor[j].src.out_sample_rate_max = cap.rate_out[cap.rate_out_count - 1]; j++; } } codec->codec = SND_AUDIOCODEC_PCM; codec->num_descriptors = j; return 0; } static int fsl_asrc_m2m_get_codec_caps(struct snd_compr_stream *stream, struct snd_compr_codec_caps *codec) { struct fsl_asrc *asrc = stream->private_data; return fsl_asrc_m2m_fill_codec_caps(asrc, codec); } static struct snd_compr_ops fsl_asrc_m2m_compr_ops = { .open = fsl_asrc_m2m_comp_open, .free = fsl_asrc_m2m_comp_release, .set_params = fsl_asrc_m2m_comp_set_params, .get_caps = fsl_asrc_m2m_get_caps, .get_codec_caps = fsl_asrc_m2m_get_codec_caps, .task_create = fsl_asrc_m2m_comp_task_create, .task_start = fsl_asrc_m2m_comp_task_start, .task_stop = fsl_asrc_m2m_comp_task_stop, .task_free = fsl_asrc_m2m_comp_task_free, }; int fsl_asrc_m2m_suspend(struct fsl_asrc *asrc) { struct fsl_asrc_pair *pair; int i; for (i = 0; i < PAIR_CTX_NUM; i++) { pair = asrc->pair[i]; if (!pair || !pair->dma_buffer[IN].area || !pair->dma_buffer[OUT].area) continue; if (!completion_done(&pair->complete[IN])) { if (pair->dma_chan[IN]) dmaengine_terminate_all(pair->dma_chan[IN]); asrc_input_dma_callback((void *)pair); } if (!completion_done(&pair->complete[OUT])) { if (pair->dma_chan[OUT]) dmaengine_terminate_all(pair->dma_chan[OUT]); asrc_output_dma_callback((void *)pair); } if (asrc->m2m_pair_suspend) asrc->m2m_pair_suspend(pair); } return 0; } EXPORT_SYMBOL_GPL(fsl_asrc_m2m_suspend); int fsl_asrc_m2m_resume(struct fsl_asrc *asrc) { struct fsl_asrc_pair *pair; int i; for (i = 0; i < PAIR_CTX_NUM; i++) { pair = asrc->pair[i]; if (!pair) continue; if (asrc->m2m_pair_resume) asrc->m2m_pair_resume(pair); } return 0; } EXPORT_SYMBOL_GPL(fsl_asrc_m2m_resume); int fsl_asrc_m2m_init(struct fsl_asrc *asrc) { struct device *dev = &asrc->pdev->dev; struct snd_card *card; struct snd_compr *compr; int ret; ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &card); if (ret < 0) return ret; strscpy(card->driver, "fsl-asrc-m2m", sizeof(card->driver)); strscpy(card->shortname, "ASRC-M2M", sizeof(card->shortname)); strscpy(card->longname, "ASRC-M2M", sizeof(card->shortname)); asrc->card = card; compr = devm_kzalloc(dev, sizeof(*compr), GFP_KERNEL); if (!compr) { ret = -ENOMEM; goto err; } compr->ops = &fsl_asrc_m2m_compr_ops; compr->private_data = asrc; ret = snd_compress_new(card, 0, SND_COMPRESS_ACCEL, "ASRC M2M", compr); if (ret < 0) goto err; ret = snd_card_register(card); if (ret < 0) goto err; return 0; err: snd_card_free(card); return ret; } EXPORT_SYMBOL_GPL(fsl_asrc_m2m_init); void fsl_asrc_m2m_exit(struct fsl_asrc *asrc) { struct snd_card *card = asrc->card; snd_card_free(card); } EXPORT_SYMBOL_GPL(fsl_asrc_m2m_exit); MODULE_IMPORT_NS("DMA_BUF"); MODULE_AUTHOR("Shengjiu Wang "); MODULE_DESCRIPTION("Freescale ASRC M2M driver"); MODULE_LICENSE("GPL");