162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Au1000/Au1500/Au1100 Audio DMA support. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * copied almost verbatim from the old ALSA driver, written by 862306a36Sopenharmony_ci * Charles Eidsness <charles@cooper-street.com> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1662306a36Sopenharmony_ci#include <sound/core.h> 1762306a36Sopenharmony_ci#include <sound/pcm.h> 1862306a36Sopenharmony_ci#include <sound/pcm_params.h> 1962306a36Sopenharmony_ci#include <sound/soc.h> 2062306a36Sopenharmony_ci#include <asm/mach-au1x00/au1000.h> 2162306a36Sopenharmony_ci#include <asm/mach-au1x00/au1000_dma.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include "psc.h" 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define DRV_NAME "au1x_dma" 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistruct pcm_period { 2862306a36Sopenharmony_ci u32 start; 2962306a36Sopenharmony_ci u32 relative_end; /* relative to start of buffer */ 3062306a36Sopenharmony_ci struct pcm_period *next; 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistruct audio_stream { 3462306a36Sopenharmony_ci struct snd_pcm_substream *substream; 3562306a36Sopenharmony_ci int dma; 3662306a36Sopenharmony_ci struct pcm_period *buffer; 3762306a36Sopenharmony_ci unsigned int period_size; 3862306a36Sopenharmony_ci unsigned int periods; 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistruct alchemy_pcm_ctx { 4262306a36Sopenharmony_ci struct audio_stream stream[2]; /* playback & capture */ 4362306a36Sopenharmony_ci}; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic void au1000_release_dma_link(struct audio_stream *stream) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci struct pcm_period *pointer; 4862306a36Sopenharmony_ci struct pcm_period *pointer_next; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci stream->period_size = 0; 5162306a36Sopenharmony_ci stream->periods = 0; 5262306a36Sopenharmony_ci pointer = stream->buffer; 5362306a36Sopenharmony_ci if (!pointer) 5462306a36Sopenharmony_ci return; 5562306a36Sopenharmony_ci do { 5662306a36Sopenharmony_ci pointer_next = pointer->next; 5762306a36Sopenharmony_ci kfree(pointer); 5862306a36Sopenharmony_ci pointer = pointer_next; 5962306a36Sopenharmony_ci } while (pointer != stream->buffer); 6062306a36Sopenharmony_ci stream->buffer = NULL; 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic int au1000_setup_dma_link(struct audio_stream *stream, 6462306a36Sopenharmony_ci unsigned int period_bytes, 6562306a36Sopenharmony_ci unsigned int periods) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci struct snd_pcm_substream *substream = stream->substream; 6862306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 6962306a36Sopenharmony_ci struct pcm_period *pointer; 7062306a36Sopenharmony_ci unsigned long dma_start; 7162306a36Sopenharmony_ci int i; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci dma_start = virt_to_phys(runtime->dma_area); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci if (stream->period_size == period_bytes && 7662306a36Sopenharmony_ci stream->periods == periods) 7762306a36Sopenharmony_ci return 0; /* not changed */ 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci au1000_release_dma_link(stream); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci stream->period_size = period_bytes; 8262306a36Sopenharmony_ci stream->periods = periods; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci stream->buffer = kmalloc(sizeof(struct pcm_period), GFP_KERNEL); 8562306a36Sopenharmony_ci if (!stream->buffer) 8662306a36Sopenharmony_ci return -ENOMEM; 8762306a36Sopenharmony_ci pointer = stream->buffer; 8862306a36Sopenharmony_ci for (i = 0; i < periods; i++) { 8962306a36Sopenharmony_ci pointer->start = (u32)(dma_start + (i * period_bytes)); 9062306a36Sopenharmony_ci pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1); 9162306a36Sopenharmony_ci if (i < periods - 1) { 9262306a36Sopenharmony_ci pointer->next = kmalloc(sizeof(struct pcm_period), 9362306a36Sopenharmony_ci GFP_KERNEL); 9462306a36Sopenharmony_ci if (!pointer->next) { 9562306a36Sopenharmony_ci au1000_release_dma_link(stream); 9662306a36Sopenharmony_ci return -ENOMEM; 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci pointer = pointer->next; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci pointer->next = stream->buffer; 10262306a36Sopenharmony_ci return 0; 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic void au1000_dma_stop(struct audio_stream *stream) 10662306a36Sopenharmony_ci{ 10762306a36Sopenharmony_ci if (stream->buffer) 10862306a36Sopenharmony_ci disable_dma(stream->dma); 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic void au1000_dma_start(struct audio_stream *stream) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci if (!stream->buffer) 11462306a36Sopenharmony_ci return; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci init_dma(stream->dma); 11762306a36Sopenharmony_ci if (get_dma_active_buffer(stream->dma) == 0) { 11862306a36Sopenharmony_ci clear_dma_done0(stream->dma); 11962306a36Sopenharmony_ci set_dma_addr0(stream->dma, stream->buffer->start); 12062306a36Sopenharmony_ci set_dma_count0(stream->dma, stream->period_size >> 1); 12162306a36Sopenharmony_ci set_dma_addr1(stream->dma, stream->buffer->next->start); 12262306a36Sopenharmony_ci set_dma_count1(stream->dma, stream->period_size >> 1); 12362306a36Sopenharmony_ci } else { 12462306a36Sopenharmony_ci clear_dma_done1(stream->dma); 12562306a36Sopenharmony_ci set_dma_addr1(stream->dma, stream->buffer->start); 12662306a36Sopenharmony_ci set_dma_count1(stream->dma, stream->period_size >> 1); 12762306a36Sopenharmony_ci set_dma_addr0(stream->dma, stream->buffer->next->start); 12862306a36Sopenharmony_ci set_dma_count0(stream->dma, stream->period_size >> 1); 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci enable_dma_buffers(stream->dma); 13162306a36Sopenharmony_ci start_dma(stream->dma); 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic irqreturn_t au1000_dma_interrupt(int irq, void *ptr) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci struct audio_stream *stream = (struct audio_stream *)ptr; 13762306a36Sopenharmony_ci struct snd_pcm_substream *substream = stream->substream; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci switch (get_dma_buffer_done(stream->dma)) { 14062306a36Sopenharmony_ci case DMA_D0: 14162306a36Sopenharmony_ci stream->buffer = stream->buffer->next; 14262306a36Sopenharmony_ci clear_dma_done0(stream->dma); 14362306a36Sopenharmony_ci set_dma_addr0(stream->dma, stream->buffer->next->start); 14462306a36Sopenharmony_ci set_dma_count0(stream->dma, stream->period_size >> 1); 14562306a36Sopenharmony_ci enable_dma_buffer0(stream->dma); 14662306a36Sopenharmony_ci break; 14762306a36Sopenharmony_ci case DMA_D1: 14862306a36Sopenharmony_ci stream->buffer = stream->buffer->next; 14962306a36Sopenharmony_ci clear_dma_done1(stream->dma); 15062306a36Sopenharmony_ci set_dma_addr1(stream->dma, stream->buffer->next->start); 15162306a36Sopenharmony_ci set_dma_count1(stream->dma, stream->period_size >> 1); 15262306a36Sopenharmony_ci enable_dma_buffer1(stream->dma); 15362306a36Sopenharmony_ci break; 15462306a36Sopenharmony_ci case (DMA_D0 | DMA_D1): 15562306a36Sopenharmony_ci pr_debug("DMA %d missed interrupt.\n", stream->dma); 15662306a36Sopenharmony_ci au1000_dma_stop(stream); 15762306a36Sopenharmony_ci au1000_dma_start(stream); 15862306a36Sopenharmony_ci break; 15962306a36Sopenharmony_ci case (~DMA_D0 & ~DMA_D1): 16062306a36Sopenharmony_ci pr_debug("DMA %d empty irq.\n", stream->dma); 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci snd_pcm_period_elapsed(substream); 16362306a36Sopenharmony_ci return IRQ_HANDLED; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic const struct snd_pcm_hardware alchemy_pcm_hardware = { 16762306a36Sopenharmony_ci .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | 16862306a36Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH, 16962306a36Sopenharmony_ci .period_bytes_min = 1024, 17062306a36Sopenharmony_ci .period_bytes_max = 16 * 1024 - 1, 17162306a36Sopenharmony_ci .periods_min = 4, 17262306a36Sopenharmony_ci .periods_max = 255, 17362306a36Sopenharmony_ci .buffer_bytes_max = 128 * 1024, 17462306a36Sopenharmony_ci .fifo_size = 16, 17562306a36Sopenharmony_ci}; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_cistatic inline struct alchemy_pcm_ctx *ss_to_ctx(struct snd_pcm_substream *ss, 17862306a36Sopenharmony_ci struct snd_soc_component *component) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci return snd_soc_component_get_drvdata(component); 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic inline struct audio_stream *ss_to_as(struct snd_pcm_substream *ss, 18462306a36Sopenharmony_ci struct snd_soc_component *component) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci struct alchemy_pcm_ctx *ctx = ss_to_ctx(ss, component); 18762306a36Sopenharmony_ci return &(ctx->stream[ss->stream]); 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_cistatic int alchemy_pcm_open(struct snd_soc_component *component, 19162306a36Sopenharmony_ci struct snd_pcm_substream *substream) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream, component); 19462306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 19562306a36Sopenharmony_ci int *dmaids, s = substream->stream; 19662306a36Sopenharmony_ci char *name; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci dmaids = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); 19962306a36Sopenharmony_ci if (!dmaids) 20062306a36Sopenharmony_ci return -ENODEV; /* whoa, has ordering changed? */ 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci /* DMA setup */ 20362306a36Sopenharmony_ci name = (s == SNDRV_PCM_STREAM_PLAYBACK) ? "audio-tx" : "audio-rx"; 20462306a36Sopenharmony_ci ctx->stream[s].dma = request_au1000_dma(dmaids[s], name, 20562306a36Sopenharmony_ci au1000_dma_interrupt, 0, 20662306a36Sopenharmony_ci &ctx->stream[s]); 20762306a36Sopenharmony_ci set_dma_mode(ctx->stream[s].dma, 20862306a36Sopenharmony_ci get_dma_mode(ctx->stream[s].dma) & ~DMA_NC); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci ctx->stream[s].substream = substream; 21162306a36Sopenharmony_ci ctx->stream[s].buffer = NULL; 21262306a36Sopenharmony_ci snd_soc_set_runtime_hwparams(substream, &alchemy_pcm_hardware); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci return 0; 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic int alchemy_pcm_close(struct snd_soc_component *component, 21862306a36Sopenharmony_ci struct snd_pcm_substream *substream) 21962306a36Sopenharmony_ci{ 22062306a36Sopenharmony_ci struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream, component); 22162306a36Sopenharmony_ci int stype = substream->stream; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci ctx->stream[stype].substream = NULL; 22462306a36Sopenharmony_ci free_au1000_dma(ctx->stream[stype].dma); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci return 0; 22762306a36Sopenharmony_ci} 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_cistatic int alchemy_pcm_hw_params(struct snd_soc_component *component, 23062306a36Sopenharmony_ci struct snd_pcm_substream *substream, 23162306a36Sopenharmony_ci struct snd_pcm_hw_params *hw_params) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci struct audio_stream *stream = ss_to_as(substream, component); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci return au1000_setup_dma_link(stream, 23662306a36Sopenharmony_ci params_period_bytes(hw_params), 23762306a36Sopenharmony_ci params_periods(hw_params)); 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cistatic int alchemy_pcm_hw_free(struct snd_soc_component *component, 24162306a36Sopenharmony_ci struct snd_pcm_substream *substream) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci struct audio_stream *stream = ss_to_as(substream, component); 24462306a36Sopenharmony_ci au1000_release_dma_link(stream); 24562306a36Sopenharmony_ci return 0; 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_cistatic int alchemy_pcm_trigger(struct snd_soc_component *component, 24962306a36Sopenharmony_ci struct snd_pcm_substream *substream, int cmd) 25062306a36Sopenharmony_ci{ 25162306a36Sopenharmony_ci struct audio_stream *stream = ss_to_as(substream, component); 25262306a36Sopenharmony_ci int err = 0; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci switch (cmd) { 25562306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 25662306a36Sopenharmony_ci au1000_dma_start(stream); 25762306a36Sopenharmony_ci break; 25862306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 25962306a36Sopenharmony_ci au1000_dma_stop(stream); 26062306a36Sopenharmony_ci break; 26162306a36Sopenharmony_ci default: 26262306a36Sopenharmony_ci err = -EINVAL; 26362306a36Sopenharmony_ci break; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci return err; 26662306a36Sopenharmony_ci} 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_cistatic snd_pcm_uframes_t alchemy_pcm_pointer(struct snd_soc_component *component, 26962306a36Sopenharmony_ci struct snd_pcm_substream *ss) 27062306a36Sopenharmony_ci{ 27162306a36Sopenharmony_ci struct audio_stream *stream = ss_to_as(ss, component); 27262306a36Sopenharmony_ci long location; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci location = get_dma_residue(stream->dma); 27562306a36Sopenharmony_ci location = stream->buffer->relative_end - location; 27662306a36Sopenharmony_ci if (location == -1) 27762306a36Sopenharmony_ci location = 0; 27862306a36Sopenharmony_ci return bytes_to_frames(ss->runtime, location); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic int alchemy_pcm_new(struct snd_soc_component *component, 28262306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci struct snd_pcm *pcm = rtd->pcm; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, 28762306a36Sopenharmony_ci NULL, 65536, (4096 * 1024) - 1); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci return 0; 29062306a36Sopenharmony_ci} 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_cistatic struct snd_soc_component_driver alchemy_pcm_soc_component = { 29362306a36Sopenharmony_ci .name = DRV_NAME, 29462306a36Sopenharmony_ci .open = alchemy_pcm_open, 29562306a36Sopenharmony_ci .close = alchemy_pcm_close, 29662306a36Sopenharmony_ci .hw_params = alchemy_pcm_hw_params, 29762306a36Sopenharmony_ci .hw_free = alchemy_pcm_hw_free, 29862306a36Sopenharmony_ci .trigger = alchemy_pcm_trigger, 29962306a36Sopenharmony_ci .pointer = alchemy_pcm_pointer, 30062306a36Sopenharmony_ci .pcm_construct = alchemy_pcm_new, 30162306a36Sopenharmony_ci}; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_cistatic int alchemy_pcm_drvprobe(struct platform_device *pdev) 30462306a36Sopenharmony_ci{ 30562306a36Sopenharmony_ci struct alchemy_pcm_ctx *ctx; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); 30862306a36Sopenharmony_ci if (!ctx) 30962306a36Sopenharmony_ci return -ENOMEM; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci platform_set_drvdata(pdev, ctx); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci return devm_snd_soc_register_component(&pdev->dev, 31462306a36Sopenharmony_ci &alchemy_pcm_soc_component, NULL, 0); 31562306a36Sopenharmony_ci} 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_cistatic struct platform_driver alchemy_pcmdma_driver = { 31862306a36Sopenharmony_ci .driver = { 31962306a36Sopenharmony_ci .name = "alchemy-pcm-dma", 32062306a36Sopenharmony_ci }, 32162306a36Sopenharmony_ci .probe = alchemy_pcm_drvprobe, 32262306a36Sopenharmony_ci}; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_cimodule_platform_driver(alchemy_pcmdma_driver); 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 32762306a36Sopenharmony_ciMODULE_DESCRIPTION("Au1000/Au1500/Au1100 Audio DMA driver"); 32862306a36Sopenharmony_ciMODULE_AUTHOR("Manuel Lauss"); 329