335 lines · c
1// SPDX-License-Identifier: GPL-2.02//3// SH7760 ("camelot") DMABRG audio DMA unit support4//5// Copyright (C) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>6//7// The SH7760 DMABRG provides 4 dma channels (2x rec, 2x play), which8// trigger an interrupt when one half of the programmed transfer size9// has been xmitted.10//11// FIXME: little-endian only for now12 13#include <linux/module.h>14#include <linux/gfp.h>15#include <linux/init.h>16#include <linux/platform_device.h>17#include <linux/dma-mapping.h>18#include <sound/core.h>19#include <sound/pcm.h>20#include <sound/pcm_params.h>21#include <sound/soc.h>22#include <asm/dmabrg.h>23 24 25/* registers and bits */26#define BRGATXSAR 0x0027#define BRGARXDAR 0x0428#define BRGATXTCR 0x0829#define BRGARXTCR 0x0C30#define BRGACR 0x1031#define BRGATXTCNT 0x1432#define BRGARXTCNT 0x1833 34#define ACR_RAR (1 << 18)35#define ACR_RDS (1 << 17)36#define ACR_RDE (1 << 16)37#define ACR_TAR (1 << 2)38#define ACR_TDS (1 << 1)39#define ACR_TDE (1 << 0)40 41/* receiver/transmitter data alignment */42#define ACR_RAM_NONE (0 << 24)43#define ACR_RAM_4BYTE (1 << 24)44#define ACR_RAM_2WORD (2 << 24)45#define ACR_TAM_NONE (0 << 8)46#define ACR_TAM_4BYTE (1 << 8)47#define ACR_TAM_2WORD (2 << 8)48 49 50struct camelot_pcm {51 unsigned long mmio; /* DMABRG audio channel control reg MMIO */52 unsigned int txid; /* ID of first DMABRG IRQ for this unit */53 54 struct snd_pcm_substream *tx_ss;55 unsigned long tx_period_size;56 unsigned int tx_period;57 58 struct snd_pcm_substream *rx_ss;59 unsigned long rx_period_size;60 unsigned int rx_period;61 62} cam_pcm_data[2] = {63 {64 .mmio = 0xFE3C0040,65 .txid = DMABRGIRQ_A0TXF,66 },67 {68 .mmio = 0xFE3C0060,69 .txid = DMABRGIRQ_A1TXF,70 },71};72 73#define BRGREG(x) (*(unsigned long *)(cam->mmio + (x)))74 75/*76 * set a minimum of 16kb per period, to avoid interrupt-"storm" and77 * resulting skipping. In general, the bigger the minimum size, the78 * better for overall system performance. (The SH7760 is a puny CPU79 * with a slow SDRAM interface and poor internal bus bandwidth,80 * *especially* when the LCDC is active). The minimum for the DMAC81 * is 8 bytes; 16kbytes are enough to get skip-free playback of a82 * 44kHz/16bit/stereo MP3 on a lightly loaded system, and maintain83 * reasonable responsiveness in MPlayer.84 */85#define DMABRG_PERIOD_MIN 16 * 102486#define DMABRG_PERIOD_MAX 0x03fffffc87#define DMABRG_PREALLOC_BUFFER 32 * 102488#define DMABRG_PREALLOC_BUFFER_MAX 32 * 102489 90static const struct snd_pcm_hardware camelot_pcm_hardware = {91 .info = (SNDRV_PCM_INFO_MMAP |92 SNDRV_PCM_INFO_INTERLEAVED |93 SNDRV_PCM_INFO_BLOCK_TRANSFER |94 SNDRV_PCM_INFO_MMAP_VALID |95 SNDRV_PCM_INFO_BATCH),96 .buffer_bytes_max = DMABRG_PERIOD_MAX,97 .period_bytes_min = DMABRG_PERIOD_MIN,98 .period_bytes_max = DMABRG_PERIOD_MAX / 2,99 .periods_min = 2,100 .periods_max = 2,101 .fifo_size = 128,102};103 104static void camelot_txdma(void *data)105{106 struct camelot_pcm *cam = data;107 cam->tx_period ^= 1;108 snd_pcm_period_elapsed(cam->tx_ss);109}110 111static void camelot_rxdma(void *data)112{113 struct camelot_pcm *cam = data;114 cam->rx_period ^= 1;115 snd_pcm_period_elapsed(cam->rx_ss);116}117 118static int camelot_pcm_open(struct snd_soc_component *component,119 struct snd_pcm_substream *substream)120{121 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);122 struct camelot_pcm *cam = &cam_pcm_data[snd_soc_rtd_to_cpu(rtd, 0)->id];123 int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;124 int ret, dmairq;125 126 snd_soc_set_runtime_hwparams(substream, &camelot_pcm_hardware);127 128 /* DMABRG buffer half/full events */129 dmairq = (recv) ? cam->txid + 2 : cam->txid;130 if (recv) {131 cam->rx_ss = substream;132 ret = dmabrg_request_irq(dmairq, camelot_rxdma, cam);133 if (unlikely(ret)) {134 pr_debug("audio unit %d irqs already taken!\n",135 snd_soc_rtd_to_cpu(rtd, 0)->id);136 return -EBUSY;137 }138 (void)dmabrg_request_irq(dmairq + 1,camelot_rxdma, cam);139 } else {140 cam->tx_ss = substream;141 ret = dmabrg_request_irq(dmairq, camelot_txdma, cam);142 if (unlikely(ret)) {143 pr_debug("audio unit %d irqs already taken!\n",144 snd_soc_rtd_to_cpu(rtd, 0)->id);145 return -EBUSY;146 }147 (void)dmabrg_request_irq(dmairq + 1, camelot_txdma, cam);148 }149 return 0;150}151 152static int camelot_pcm_close(struct snd_soc_component *component,153 struct snd_pcm_substream *substream)154{155 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);156 struct camelot_pcm *cam = &cam_pcm_data[snd_soc_rtd_to_cpu(rtd, 0)->id];157 int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;158 int dmairq;159 160 dmairq = (recv) ? cam->txid + 2 : cam->txid;161 162 if (recv)163 cam->rx_ss = NULL;164 else165 cam->tx_ss = NULL;166 167 dmabrg_free_irq(dmairq + 1);168 dmabrg_free_irq(dmairq);169 170 return 0;171}172 173static int camelot_hw_params(struct snd_soc_component *component,174 struct snd_pcm_substream *substream,175 struct snd_pcm_hw_params *hw_params)176{177 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);178 struct camelot_pcm *cam = &cam_pcm_data[snd_soc_rtd_to_cpu(rtd, 0)->id];179 int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;180 181 if (recv) {182 cam->rx_period_size = params_period_bytes(hw_params);183 cam->rx_period = 0;184 } else {185 cam->tx_period_size = params_period_bytes(hw_params);186 cam->tx_period = 0;187 }188 return 0;189}190 191static int camelot_prepare(struct snd_soc_component *component,192 struct snd_pcm_substream *substream)193{194 struct snd_pcm_runtime *runtime = substream->runtime;195 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);196 struct camelot_pcm *cam = &cam_pcm_data[snd_soc_rtd_to_cpu(rtd, 0)->id];197 198 pr_debug("PCM data: addr %pad len %zu\n", &runtime->dma_addr,199 runtime->dma_bytes);200 201 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {202 BRGREG(BRGATXSAR) = (unsigned long)runtime->dma_area;203 BRGREG(BRGATXTCR) = runtime->dma_bytes;204 } else {205 BRGREG(BRGARXDAR) = (unsigned long)runtime->dma_area;206 BRGREG(BRGARXTCR) = runtime->dma_bytes;207 }208 209 return 0;210}211 212static inline void dmabrg_play_dma_start(struct camelot_pcm *cam)213{214 unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);215 /* start DMABRG engine: XFER start, auto-addr-reload */216 BRGREG(BRGACR) = acr | ACR_TDE | ACR_TAR | ACR_TAM_2WORD;217}218 219static inline void dmabrg_play_dma_stop(struct camelot_pcm *cam)220{221 unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);222 /* forcibly terminate data transmission */223 BRGREG(BRGACR) = acr | ACR_TDS;224}225 226static inline void dmabrg_rec_dma_start(struct camelot_pcm *cam)227{228 unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);229 /* start DMABRG engine: recv start, auto-reload */230 BRGREG(BRGACR) = acr | ACR_RDE | ACR_RAR | ACR_RAM_2WORD;231}232 233static inline void dmabrg_rec_dma_stop(struct camelot_pcm *cam)234{235 unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS);236 /* forcibly terminate data receiver */237 BRGREG(BRGACR) = acr | ACR_RDS;238}239 240static int camelot_trigger(struct snd_soc_component *component,241 struct snd_pcm_substream *substream, int cmd)242{243 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);244 struct camelot_pcm *cam = &cam_pcm_data[snd_soc_rtd_to_cpu(rtd, 0)->id];245 int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;246 247 switch (cmd) {248 case SNDRV_PCM_TRIGGER_START:249 if (recv)250 dmabrg_rec_dma_start(cam);251 else252 dmabrg_play_dma_start(cam);253 break;254 case SNDRV_PCM_TRIGGER_STOP:255 if (recv)256 dmabrg_rec_dma_stop(cam);257 else258 dmabrg_play_dma_stop(cam);259 break;260 default:261 return -EINVAL;262 }263 264 return 0;265}266 267static snd_pcm_uframes_t camelot_pos(struct snd_soc_component *component,268 struct snd_pcm_substream *substream)269{270 struct snd_pcm_runtime *runtime = substream->runtime;271 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);272 struct camelot_pcm *cam = &cam_pcm_data[snd_soc_rtd_to_cpu(rtd, 0)->id];273 int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1;274 unsigned long pos;275 276 /* cannot use the DMABRG pointer register: under load, by the277 * time ALSA comes around to read the register, it is already278 * far ahead (or worse, already done with the fragment) of the279 * position at the time the IRQ was triggered, which results in280 * fast-playback sound in my test application (ScummVM)281 */282 if (recv)283 pos = cam->rx_period ? cam->rx_period_size : 0;284 else285 pos = cam->tx_period ? cam->tx_period_size : 0;286 287 return bytes_to_frames(runtime, pos);288}289 290static int camelot_pcm_new(struct snd_soc_component *component,291 struct snd_soc_pcm_runtime *rtd)292{293 struct snd_pcm *pcm = rtd->pcm;294 295 /* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel296 * in MMAP mode (i.e. aplay -M)297 */298 snd_pcm_set_managed_buffer_all(pcm,299 SNDRV_DMA_TYPE_CONTINUOUS,300 NULL,301 DMABRG_PREALLOC_BUFFER, DMABRG_PREALLOC_BUFFER_MAX);302 303 return 0;304}305 306static const struct snd_soc_component_driver sh7760_soc_component = {307 .open = camelot_pcm_open,308 .close = camelot_pcm_close,309 .hw_params = camelot_hw_params,310 .prepare = camelot_prepare,311 .trigger = camelot_trigger,312 .pointer = camelot_pos,313 .pcm_construct = camelot_pcm_new,314};315 316static int sh7760_soc_platform_probe(struct platform_device *pdev)317{318 return devm_snd_soc_register_component(&pdev->dev, &sh7760_soc_component,319 NULL, 0);320}321 322static struct platform_driver sh7760_pcm_driver = {323 .driver = {324 .name = "sh7760-pcm-audio",325 },326 327 .probe = sh7760_soc_platform_probe,328};329 330module_platform_driver(sh7760_pcm_driver);331 332MODULE_LICENSE("GPL v2");333MODULE_DESCRIPTION("SH7760 Audio DMA (DMABRG) driver");334MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");335