332 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC.4 *5 * Copyright (C) 2005 SAN People6 * Copyright (C) 2008 Atmel7 *8 * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>9 *10 * Based on at91-pcm. by:11 * Frank Mandarino <fmandarino@endrelia.com>12 * Copyright 2006 Endrelia Technologies Inc.13 *14 * Based on pxa2xx-pcm.c by:15 *16 * Author: Nicolas Pitre17 * Created: Nov 30, 200418 * Copyright: (C) 2004 MontaVista Software, Inc.19 */20 21#include <linux/module.h>22#include <linux/init.h>23#include <linux/platform_device.h>24#include <linux/slab.h>25#include <linux/dma-mapping.h>26#include <linux/atmel_pdc.h>27#include <linux/atmel-ssc.h>28 29#include <sound/core.h>30#include <sound/pcm.h>31#include <sound/pcm_params.h>32#include <sound/soc.h>33 34#include "atmel-pcm.h"35 36 37static int atmel_pcm_new(struct snd_soc_component *component,38 struct snd_soc_pcm_runtime *rtd)39{40 struct snd_card *card = rtd->card->snd_card;41 int ret;42 43 ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));44 if (ret)45 return ret;46 47 snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,48 card->dev, ATMEL_SSC_DMABUF_SIZE,49 ATMEL_SSC_DMABUF_SIZE);50 51 return 0;52}53 54/*--------------------------------------------------------------------------*\55 * Hardware definition56\*--------------------------------------------------------------------------*/57/* TODO: These values were taken from the AT91 platform driver, check58 * them against real values for AT3259 */60static const struct snd_pcm_hardware atmel_pcm_hardware = {61 .info = SNDRV_PCM_INFO_MMAP |62 SNDRV_PCM_INFO_MMAP_VALID |63 SNDRV_PCM_INFO_INTERLEAVED |64 SNDRV_PCM_INFO_PAUSE,65 .period_bytes_min = 32,66 .period_bytes_max = 8192,67 .periods_min = 2,68 .periods_max = 1024,69 .buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE,70};71 72 73/*--------------------------------------------------------------------------*\74 * Data types75\*--------------------------------------------------------------------------*/76struct atmel_runtime_data {77 struct atmel_pcm_dma_params *params;78 dma_addr_t dma_buffer; /* physical address of dma buffer */79 dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */80 size_t period_size;81 82 dma_addr_t period_ptr; /* physical address of next period */83};84 85/*--------------------------------------------------------------------------*\86 * ISR87\*--------------------------------------------------------------------------*/88static void atmel_pcm_dma_irq(u32 ssc_sr,89 struct snd_pcm_substream *substream)90{91 struct atmel_runtime_data *prtd = substream->runtime->private_data;92 struct atmel_pcm_dma_params *params = prtd->params;93 static int count;94 95 count++;96 97 if (ssc_sr & params->mask->ssc_endbuf) {98 pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n",99 substream->stream == SNDRV_PCM_STREAM_PLAYBACK100 ? "underrun" : "overrun",101 params->name, ssc_sr, count);102 103 /* re-start the PDC */104 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,105 params->mask->pdc_disable);106 prtd->period_ptr += prtd->period_size;107 if (prtd->period_ptr >= prtd->dma_buffer_end)108 prtd->period_ptr = prtd->dma_buffer;109 110 ssc_writex(params->ssc->regs, params->pdc->xpr,111 prtd->period_ptr);112 ssc_writex(params->ssc->regs, params->pdc->xcr,113 prtd->period_size / params->pdc_xfer_size);114 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,115 params->mask->pdc_enable);116 }117 118 if (ssc_sr & params->mask->ssc_endx) {119 /* Load the PDC next pointer and counter registers */120 prtd->period_ptr += prtd->period_size;121 if (prtd->period_ptr >= prtd->dma_buffer_end)122 prtd->period_ptr = prtd->dma_buffer;123 124 ssc_writex(params->ssc->regs, params->pdc->xnpr,125 prtd->period_ptr);126 ssc_writex(params->ssc->regs, params->pdc->xncr,127 prtd->period_size / params->pdc_xfer_size);128 }129 130 snd_pcm_period_elapsed(substream);131}132 133 134/*--------------------------------------------------------------------------*\135 * PCM operations136\*--------------------------------------------------------------------------*/137static int atmel_pcm_hw_params(struct snd_soc_component *component,138 struct snd_pcm_substream *substream,139 struct snd_pcm_hw_params *params)140{141 struct snd_pcm_runtime *runtime = substream->runtime;142 struct atmel_runtime_data *prtd = runtime->private_data;143 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);144 145 /* this may get called several times by oss emulation146 * with different params */147 148 prtd->params = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);149 prtd->params->dma_intr_handler = atmel_pcm_dma_irq;150 151 prtd->dma_buffer = runtime->dma_addr;152 prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;153 prtd->period_size = params_period_bytes(params);154 155 pr_debug("atmel-pcm: "156 "hw_params: DMA for %s initialized "157 "(dma_bytes=%zu, period_size=%zu)\n",158 prtd->params->name,159 runtime->dma_bytes,160 prtd->period_size);161 return 0;162}163 164static int atmel_pcm_hw_free(struct snd_soc_component *component,165 struct snd_pcm_substream *substream)166{167 struct atmel_runtime_data *prtd = substream->runtime->private_data;168 struct atmel_pcm_dma_params *params = prtd->params;169 170 if (params != NULL) {171 ssc_writex(params->ssc->regs, SSC_PDC_PTCR,172 params->mask->pdc_disable);173 prtd->params->dma_intr_handler = NULL;174 }175 176 return 0;177}178 179static int atmel_pcm_prepare(struct snd_soc_component *component,180 struct snd_pcm_substream *substream)181{182 struct atmel_runtime_data *prtd = substream->runtime->private_data;183 struct atmel_pcm_dma_params *params = prtd->params;184 185 ssc_writex(params->ssc->regs, SSC_IDR,186 params->mask->ssc_endx | params->mask->ssc_endbuf);187 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,188 params->mask->pdc_disable);189 return 0;190}191 192static int atmel_pcm_trigger(struct snd_soc_component *component,193 struct snd_pcm_substream *substream, int cmd)194{195 struct snd_pcm_runtime *rtd = substream->runtime;196 struct atmel_runtime_data *prtd = rtd->private_data;197 struct atmel_pcm_dma_params *params = prtd->params;198 int ret = 0;199 200 pr_debug("atmel-pcm:buffer_size = %ld,"201 "dma_area = %p, dma_bytes = %zu\n",202 rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);203 204 switch (cmd) {205 case SNDRV_PCM_TRIGGER_START:206 prtd->period_ptr = prtd->dma_buffer;207 208 ssc_writex(params->ssc->regs, params->pdc->xpr,209 prtd->period_ptr);210 ssc_writex(params->ssc->regs, params->pdc->xcr,211 prtd->period_size / params->pdc_xfer_size);212 213 prtd->period_ptr += prtd->period_size;214 ssc_writex(params->ssc->regs, params->pdc->xnpr,215 prtd->period_ptr);216 ssc_writex(params->ssc->regs, params->pdc->xncr,217 prtd->period_size / params->pdc_xfer_size);218 219 pr_debug("atmel-pcm: trigger: "220 "period_ptr=%lx, xpr=%u, "221 "xcr=%u, xnpr=%u, xncr=%u\n",222 (unsigned long)prtd->period_ptr,223 ssc_readx(params->ssc->regs, params->pdc->xpr),224 ssc_readx(params->ssc->regs, params->pdc->xcr),225 ssc_readx(params->ssc->regs, params->pdc->xnpr),226 ssc_readx(params->ssc->regs, params->pdc->xncr));227 228 ssc_writex(params->ssc->regs, SSC_IER,229 params->mask->ssc_endx | params->mask->ssc_endbuf);230 ssc_writex(params->ssc->regs, SSC_PDC_PTCR,231 params->mask->pdc_enable);232 233 pr_debug("sr=%u imr=%u\n",234 ssc_readx(params->ssc->regs, SSC_SR),235 ssc_readx(params->ssc->regs, SSC_IER));236 break; /* SNDRV_PCM_TRIGGER_START */237 238 case SNDRV_PCM_TRIGGER_STOP:239 case SNDRV_PCM_TRIGGER_SUSPEND:240 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:241 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,242 params->mask->pdc_disable);243 break;244 245 case SNDRV_PCM_TRIGGER_RESUME:246 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:247 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,248 params->mask->pdc_enable);249 break;250 251 default:252 ret = -EINVAL;253 }254 255 return ret;256}257 258static snd_pcm_uframes_t atmel_pcm_pointer(struct snd_soc_component *component,259 struct snd_pcm_substream *substream)260{261 struct snd_pcm_runtime *runtime = substream->runtime;262 struct atmel_runtime_data *prtd = runtime->private_data;263 struct atmel_pcm_dma_params *params = prtd->params;264 dma_addr_t ptr;265 snd_pcm_uframes_t x;266 267 ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);268 x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);269 270 if (x == runtime->buffer_size)271 x = 0;272 273 return x;274}275 276static int atmel_pcm_open(struct snd_soc_component *component,277 struct snd_pcm_substream *substream)278{279 struct snd_pcm_runtime *runtime = substream->runtime;280 struct atmel_runtime_data *prtd;281 int ret = 0;282 283 snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);284 285 /* ensure that buffer size is a multiple of period size */286 ret = snd_pcm_hw_constraint_integer(runtime,287 SNDRV_PCM_HW_PARAM_PERIODS);288 if (ret < 0)289 goto out;290 291 prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);292 if (prtd == NULL) {293 ret = -ENOMEM;294 goto out;295 }296 runtime->private_data = prtd;297 298 out:299 return ret;300}301 302static int atmel_pcm_close(struct snd_soc_component *component,303 struct snd_pcm_substream *substream)304{305 struct atmel_runtime_data *prtd = substream->runtime->private_data;306 307 kfree(prtd);308 return 0;309}310 311static const struct snd_soc_component_driver atmel_soc_platform = {312 .open = atmel_pcm_open,313 .close = atmel_pcm_close,314 .hw_params = atmel_pcm_hw_params,315 .hw_free = atmel_pcm_hw_free,316 .prepare = atmel_pcm_prepare,317 .trigger = atmel_pcm_trigger,318 .pointer = atmel_pcm_pointer,319 .pcm_construct = atmel_pcm_new,320};321 322int atmel_pcm_pdc_platform_register(struct device *dev)323{324 return devm_snd_soc_register_component(dev, &atmel_soc_platform,325 NULL, 0);326}327EXPORT_SYMBOL(atmel_pcm_pdc_platform_register);328 329MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");330MODULE_DESCRIPTION("Atmel PCM module");331MODULE_LICENSE("GPL");332