| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <linux/module.h> |
| #include <sound/core.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| #include <sound/initval.h> |
| #include <sound/control.h> |
| #include <sound/soc.h> |
| #include "bridge_dsp_card.h" |
| |
| #define BUFF_SIZE_MAX (PAGE_SIZE * 64) |
| #define SOUND_CARD_ID 2 |
| struct aml_aprocess_card *aprocess_card; |
| unsigned int dsp_pcm_num; |
| static struct snd_pcm_hardware aml_aprocess_hardware = { |
| .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
| | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
| | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, |
| .formats = SNDRV_PCM_FMTBIT_S16_LE, |
| .rate_min = 16000, |
| .rate_max = 16000, |
| .channels_min = 2, |
| .channels_max = 2, |
| .buffer_bytes_max = BUFF_SIZE_MAX, |
| .period_bytes_max = 1024, |
| .period_bytes_min = 1024, |
| .periods_min = 4, |
| .periods_max = 4, |
| .fifo_size = 0, |
| }; |
| |
| void aml_aprocess_set_hw(struct aml_aprocess *p_aprocess, int channels, |
| int format, int rate, int period) |
| { |
| if (!p_aprocess) |
| return; |
| p_aprocess->g_hw.rate_min = rate; |
| p_aprocess->g_hw.rate_max = rate; |
| p_aprocess->g_hw.channels_min = channels; |
| p_aprocess->g_hw.channels_min = channels; |
| p_aprocess->g_hw.formats = format; |
| if (format == SNDRV_PCM_FMTBIT_S8) |
| p_aprocess->g_hw.period_bytes_min = channels * period; |
| else if (format == SNDRV_PCM_FMTBIT_S16) |
| p_aprocess->g_hw.period_bytes_min = 2 * channels * period; |
| else if (format == SNDRV_PCM_FMTBIT_S32) |
| p_aprocess->g_hw.period_bytes_min = 4 * channels * period; |
| else |
| p_aprocess->g_hw.period_bytes_min = 2 * channels * period; |
| |
| p_aprocess->g_hw.period_bytes_max = p_aprocess->g_hw.period_bytes_min; |
| p_aprocess->g_hw.period_bytes_max = p_aprocess->g_hw.period_bytes_min; |
| |
| p_aprocess->g_hw.buffer_bytes_max = 16 * p_aprocess->g_hw.period_bytes_max; |
| } |
| |
| int aml_aprocess_complete(struct aml_aprocess *p_aprocess, char *out, int size) |
| { |
| struct snd_pcm_substream *substream; |
| struct snd_pcm_runtime *runtime; |
| unsigned int pending; |
| unsigned long flags, flags2; |
| unsigned int hw_ptr; |
| |
| if (!p_aprocess) |
| return 0; |
| |
| spin_lock_irqsave(&p_aprocess->lock, flags); |
| if (!p_aprocess->status) |
| goto fail; |
| |
| substream = p_aprocess->g_substream; |
| if (!substream) |
| goto fail; |
| |
| snd_pcm_stream_lock_irqsave(substream, flags2); |
| |
| runtime = substream->runtime; |
| if (!runtime || !snd_pcm_running(substream)) { |
| snd_pcm_stream_unlock_irqrestore(substream, flags2); |
| goto fail; |
| } |
| hw_ptr = p_aprocess->g_hwptr; |
| |
| /* Pack DSP load in ALSA ring buffer */ |
| pending = runtime->dma_bytes - hw_ptr; |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| if (!snd_pcm_playback_hw_avail(runtime)) { |
| snd_pcm_stream_unlock_irqrestore(substream, flags2); |
| goto fail; |
| } |
| if (unlikely(pending < size)) { |
| memcpy(out, runtime->dma_area + hw_ptr, pending); |
| memcpy(out + pending, runtime->dma_area, |
| size - pending); |
| } else { |
| memcpy(out, runtime->dma_area + hw_ptr, |
| size); |
| } |
| } else { |
| if (!snd_pcm_capture_hw_avail(runtime)) { |
| snd_pcm_stream_unlock_irqrestore(substream, flags2); |
| goto fail; |
| } |
| if (unlikely(pending < size)) { |
| memcpy(runtime->dma_area + hw_ptr, out, pending); |
| memcpy(runtime->dma_area, out + pending, |
| size - pending); |
| } else { |
| memcpy(runtime->dma_area + hw_ptr, out, |
| size); |
| } |
| } |
| /* update hw_ptr after data is copied to memory */ |
| p_aprocess->g_hwptr = (hw_ptr + size) % runtime->dma_bytes; |
| hw_ptr = p_aprocess->g_hwptr; |
| snd_pcm_stream_unlock_irqrestore(substream, flags2); |
| spin_unlock_irqrestore(&p_aprocess->lock, flags); |
| if ((hw_ptr % snd_pcm_lib_period_bytes(substream)) < size) |
| snd_pcm_period_elapsed(substream); |
| |
| return size; |
| fail: |
| spin_unlock_irqrestore(&p_aprocess->lock, flags); |
| return 0; |
| } |
| |
| static int aml_aprocess_open(struct snd_pcm_substream *substream) |
| { |
| struct aml_aprocess *p_aprocess; |
| struct aml_aprocess_card *apro_card = snd_pcm_substream_chip(substream); |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| p_aprocess = (struct aml_aprocess *)apro_card->p_prm; |
| else |
| p_aprocess = (struct aml_aprocess *)apro_card->c_prm; |
| |
| runtime->hw = p_aprocess->g_hw; |
| snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
| return 0; |
| } |
| |
| static int aml_aprocess_close(struct snd_pcm_substream *substream) |
| { |
| return 0; |
| } |
| |
| static int aml_aprocess_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *hw_params) |
| { |
| return snd_pcm_lib_malloc_pages(substream, |
| params_buffer_bytes(hw_params)); |
| } |
| |
| static int aml_aprocess_hw_free(struct snd_pcm_substream *substream) |
| { |
| return snd_pcm_lib_free_pages(substream); |
| } |
| |
| static int aml_aprocess_prepare(struct snd_pcm_substream *substream) |
| { |
| return 0; |
| } |
| |
| static int aml_aprocess_trigger(struct snd_pcm_substream *substream, int cmd) |
| { |
| struct aml_aprocess *p_aprocess; |
| struct aml_aprocess_card *apro_card = snd_pcm_substream_chip(substream); |
| unsigned long flags; |
| int err = 0; |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| p_aprocess = (struct aml_aprocess *)apro_card->p_prm; |
| else |
| p_aprocess = (struct aml_aprocess *)apro_card->c_prm; |
| |
| spin_lock_irqsave(&p_aprocess->lock, flags); |
| p_aprocess->g_hwptr = 0; |
| switch (cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| case SNDRV_PCM_TRIGGER_RESUME: |
| p_aprocess->status = 1; |
| p_aprocess->g_substream = substream; |
| break; |
| case SNDRV_PCM_TRIGGER_STOP: |
| case SNDRV_PCM_TRIGGER_SUSPEND: |
| p_aprocess->status = 0; |
| p_aprocess->g_substream = NULL; |
| break; |
| default: |
| err = -EINVAL; |
| } |
| spin_unlock_irqrestore(&p_aprocess->lock, flags); |
| return err; |
| } |
| |
| static snd_pcm_uframes_t aml_aprocess_pointer(struct snd_pcm_substream *substream) |
| { |
| struct aml_aprocess *p_aprocess; |
| struct aml_aprocess_card *apro_card = snd_pcm_substream_chip(substream); |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| p_aprocess = (struct aml_aprocess *)apro_card->p_prm; |
| else |
| p_aprocess = (struct aml_aprocess *)apro_card->c_prm; |
| |
| return bytes_to_frames(runtime, p_aprocess->g_hwptr); |
| } |
| |
| static struct snd_pcm_ops aml_aprocess_ops = { |
| .open = aml_aprocess_open, |
| .close = aml_aprocess_close, |
| .ioctl = snd_pcm_lib_ioctl, |
| .hw_params = aml_aprocess_hw_params, |
| .hw_free = aml_aprocess_hw_free, |
| .prepare = aml_aprocess_prepare, |
| .trigger = aml_aprocess_trigger, |
| .pointer = aml_aprocess_pointer, |
| }; |
| |
| struct aml_aprocess *aml_aprocess_init(struct device *dev, const char *pcm_name, |
| const char *card_name, enum PCM_MODE modeid) |
| { |
| struct aml_aprocess *p_aprocess; |
| struct snd_card *card; |
| struct snd_pcm *pcm; |
| int i, err; |
| |
| p_aprocess = kzalloc(sizeof(*p_aprocess), GFP_KERNEL); |
| if (!p_aprocess) |
| return NULL; |
| dsp_pcm_num++; |
| if (!aprocess_card) { |
| aprocess_card = kzalloc(sizeof(*aprocess_card), GFP_KERNEL); |
| if (!aprocess_card) |
| return NULL; |
| |
| aprocess_card->dev = dev; |
| /* Choose any slot, with no id */ |
| for (i = 0; i < 3; i++) { |
| err = snd_card_new(dev, |
| SOUND_CARD_ID + i, NULL, THIS_MODULE, 0, &card); |
| if (!err) |
| break; |
| } |
| if (err < 0) |
| goto fail; |
| |
| aprocess_card->card = card; |
| /* |
| * Create first PCM device |
| * Create a substream only for non-zero channel streams |
| */ |
| err = snd_pcm_new(card, pcm_name, 0, 1, 1, &pcm); |
| if (err < 0) |
| goto snd_fail; |
| |
| strlcpy(pcm->name, pcm_name, sizeof(pcm->name)); |
| pcm->private_data = aprocess_card; |
| aprocess_card->pcm = pcm; |
| |
| snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &aml_aprocess_ops); |
| snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &aml_aprocess_ops); |
| |
| strlcpy(card->driver, card_name, sizeof(card->driver)); |
| strlcpy(card->shortname, card_name, sizeof(card->shortname)); |
| sprintf(card->longname, "%s %i", card_name, card->dev->id); |
| |
| snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, |
| snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX); |
| |
| err = snd_card_register(card); |
| if (err < 0) |
| goto snd_fail; |
| } |
| if (modeid == PCM_CAPTURE) |
| aprocess_card->c_prm = p_aprocess; |
| else |
| aprocess_card->p_prm = p_aprocess; |
| |
| p_aprocess->a_card = aprocess_card; |
| p_aprocess->status = 0; |
| spin_lock_init(&p_aprocess->lock); |
| memcpy(&p_aprocess->g_hw, &aml_aprocess_hardware, sizeof(p_aprocess->g_hw)); |
| return p_aprocess; |
| snd_fail: |
| dsp_pcm_num--; |
| if (!dsp_pcm_num && aprocess_card) { |
| snd_card_free(card); |
| kfree(aprocess_card); |
| aprocess_card = NULL; |
| } |
| fail: |
| kfree(p_aprocess); |
| return NULL; |
| } |
| |
| void aml_aprocess_destroy(struct aml_aprocess *p_aprocess) |
| { |
| if (!p_aprocess) |
| return; |
| dsp_pcm_num--; |
| if (!dsp_pcm_num && aprocess_card) { |
| snd_card_free(aprocess_card->card); |
| kfree(aprocess_card); |
| aprocess_card = NULL; |
| } |
| kfree(p_aprocess); |
| } |