162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// SH7760 ("camelot") DMABRG audio DMA unit support 462306a36Sopenharmony_ci// 562306a36Sopenharmony_ci// Copyright (C) 2007 Manuel Lauss <mano@roarinelk.homelinux.net> 662306a36Sopenharmony_ci// 762306a36Sopenharmony_ci// The SH7760 DMABRG provides 4 dma channels (2x rec, 2x play), which 862306a36Sopenharmony_ci// trigger an interrupt when one half of the programmed transfer size 962306a36Sopenharmony_ci// has been xmitted. 1062306a36Sopenharmony_ci// 1162306a36Sopenharmony_ci// FIXME: little-endian only for now 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/gfp.h> 1562306a36Sopenharmony_ci#include <linux/init.h> 1662306a36Sopenharmony_ci#include <linux/platform_device.h> 1762306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1862306a36Sopenharmony_ci#include <sound/core.h> 1962306a36Sopenharmony_ci#include <sound/pcm.h> 2062306a36Sopenharmony_ci#include <sound/pcm_params.h> 2162306a36Sopenharmony_ci#include <sound/soc.h> 2262306a36Sopenharmony_ci#include <asm/dmabrg.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* registers and bits */ 2662306a36Sopenharmony_ci#define BRGATXSAR 0x00 2762306a36Sopenharmony_ci#define BRGARXDAR 0x04 2862306a36Sopenharmony_ci#define BRGATXTCR 0x08 2962306a36Sopenharmony_ci#define BRGARXTCR 0x0C 3062306a36Sopenharmony_ci#define BRGACR 0x10 3162306a36Sopenharmony_ci#define BRGATXTCNT 0x14 3262306a36Sopenharmony_ci#define BRGARXTCNT 0x18 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define ACR_RAR (1 << 18) 3562306a36Sopenharmony_ci#define ACR_RDS (1 << 17) 3662306a36Sopenharmony_ci#define ACR_RDE (1 << 16) 3762306a36Sopenharmony_ci#define ACR_TAR (1 << 2) 3862306a36Sopenharmony_ci#define ACR_TDS (1 << 1) 3962306a36Sopenharmony_ci#define ACR_TDE (1 << 0) 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci/* receiver/transmitter data alignment */ 4262306a36Sopenharmony_ci#define ACR_RAM_NONE (0 << 24) 4362306a36Sopenharmony_ci#define ACR_RAM_4BYTE (1 << 24) 4462306a36Sopenharmony_ci#define ACR_RAM_2WORD (2 << 24) 4562306a36Sopenharmony_ci#define ACR_TAM_NONE (0 << 8) 4662306a36Sopenharmony_ci#define ACR_TAM_4BYTE (1 << 8) 4762306a36Sopenharmony_ci#define ACR_TAM_2WORD (2 << 8) 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistruct camelot_pcm { 5162306a36Sopenharmony_ci unsigned long mmio; /* DMABRG audio channel control reg MMIO */ 5262306a36Sopenharmony_ci unsigned int txid; /* ID of first DMABRG IRQ for this unit */ 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci struct snd_pcm_substream *tx_ss; 5562306a36Sopenharmony_ci unsigned long tx_period_size; 5662306a36Sopenharmony_ci unsigned int tx_period; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci struct snd_pcm_substream *rx_ss; 5962306a36Sopenharmony_ci unsigned long rx_period_size; 6062306a36Sopenharmony_ci unsigned int rx_period; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci} cam_pcm_data[2] = { 6362306a36Sopenharmony_ci { 6462306a36Sopenharmony_ci .mmio = 0xFE3C0040, 6562306a36Sopenharmony_ci .txid = DMABRGIRQ_A0TXF, 6662306a36Sopenharmony_ci }, 6762306a36Sopenharmony_ci { 6862306a36Sopenharmony_ci .mmio = 0xFE3C0060, 6962306a36Sopenharmony_ci .txid = DMABRGIRQ_A1TXF, 7062306a36Sopenharmony_ci }, 7162306a36Sopenharmony_ci}; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci#define BRGREG(x) (*(unsigned long *)(cam->mmio + (x))) 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/* 7662306a36Sopenharmony_ci * set a minimum of 16kb per period, to avoid interrupt-"storm" and 7762306a36Sopenharmony_ci * resulting skipping. In general, the bigger the minimum size, the 7862306a36Sopenharmony_ci * better for overall system performance. (The SH7760 is a puny CPU 7962306a36Sopenharmony_ci * with a slow SDRAM interface and poor internal bus bandwidth, 8062306a36Sopenharmony_ci * *especially* when the LCDC is active). The minimum for the DMAC 8162306a36Sopenharmony_ci * is 8 bytes; 16kbytes are enough to get skip-free playback of a 8262306a36Sopenharmony_ci * 44kHz/16bit/stereo MP3 on a lightly loaded system, and maintain 8362306a36Sopenharmony_ci * reasonable responsiveness in MPlayer. 8462306a36Sopenharmony_ci */ 8562306a36Sopenharmony_ci#define DMABRG_PERIOD_MIN 16 * 1024 8662306a36Sopenharmony_ci#define DMABRG_PERIOD_MAX 0x03fffffc 8762306a36Sopenharmony_ci#define DMABRG_PREALLOC_BUFFER 32 * 1024 8862306a36Sopenharmony_ci#define DMABRG_PREALLOC_BUFFER_MAX 32 * 1024 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic const struct snd_pcm_hardware camelot_pcm_hardware = { 9162306a36Sopenharmony_ci .info = (SNDRV_PCM_INFO_MMAP | 9262306a36Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 9362306a36Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER | 9462306a36Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 9562306a36Sopenharmony_ci SNDRV_PCM_INFO_BATCH), 9662306a36Sopenharmony_ci .buffer_bytes_max = DMABRG_PERIOD_MAX, 9762306a36Sopenharmony_ci .period_bytes_min = DMABRG_PERIOD_MIN, 9862306a36Sopenharmony_ci .period_bytes_max = DMABRG_PERIOD_MAX / 2, 9962306a36Sopenharmony_ci .periods_min = 2, 10062306a36Sopenharmony_ci .periods_max = 2, 10162306a36Sopenharmony_ci .fifo_size = 128, 10262306a36Sopenharmony_ci}; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic void camelot_txdma(void *data) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct camelot_pcm *cam = data; 10762306a36Sopenharmony_ci cam->tx_period ^= 1; 10862306a36Sopenharmony_ci snd_pcm_period_elapsed(cam->tx_ss); 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic void camelot_rxdma(void *data) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci struct camelot_pcm *cam = data; 11462306a36Sopenharmony_ci cam->rx_period ^= 1; 11562306a36Sopenharmony_ci snd_pcm_period_elapsed(cam->rx_ss); 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic int camelot_pcm_open(struct snd_soc_component *component, 11962306a36Sopenharmony_ci struct snd_pcm_substream *substream) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 12262306a36Sopenharmony_ci struct camelot_pcm *cam = &cam_pcm_data[asoc_rtd_to_cpu(rtd, 0)->id]; 12362306a36Sopenharmony_ci int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; 12462306a36Sopenharmony_ci int ret, dmairq; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci snd_soc_set_runtime_hwparams(substream, &camelot_pcm_hardware); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* DMABRG buffer half/full events */ 12962306a36Sopenharmony_ci dmairq = (recv) ? cam->txid + 2 : cam->txid; 13062306a36Sopenharmony_ci if (recv) { 13162306a36Sopenharmony_ci cam->rx_ss = substream; 13262306a36Sopenharmony_ci ret = dmabrg_request_irq(dmairq, camelot_rxdma, cam); 13362306a36Sopenharmony_ci if (unlikely(ret)) { 13462306a36Sopenharmony_ci pr_debug("audio unit %d irqs already taken!\n", 13562306a36Sopenharmony_ci asoc_rtd_to_cpu(rtd, 0)->id); 13662306a36Sopenharmony_ci return -EBUSY; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci (void)dmabrg_request_irq(dmairq + 1,camelot_rxdma, cam); 13962306a36Sopenharmony_ci } else { 14062306a36Sopenharmony_ci cam->tx_ss = substream; 14162306a36Sopenharmony_ci ret = dmabrg_request_irq(dmairq, camelot_txdma, cam); 14262306a36Sopenharmony_ci if (unlikely(ret)) { 14362306a36Sopenharmony_ci pr_debug("audio unit %d irqs already taken!\n", 14462306a36Sopenharmony_ci asoc_rtd_to_cpu(rtd, 0)->id); 14562306a36Sopenharmony_ci return -EBUSY; 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci (void)dmabrg_request_irq(dmairq + 1, camelot_txdma, cam); 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci return 0; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic int camelot_pcm_close(struct snd_soc_component *component, 15362306a36Sopenharmony_ci struct snd_pcm_substream *substream) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 15662306a36Sopenharmony_ci struct camelot_pcm *cam = &cam_pcm_data[asoc_rtd_to_cpu(rtd, 0)->id]; 15762306a36Sopenharmony_ci int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; 15862306a36Sopenharmony_ci int dmairq; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci dmairq = (recv) ? cam->txid + 2 : cam->txid; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci if (recv) 16362306a36Sopenharmony_ci cam->rx_ss = NULL; 16462306a36Sopenharmony_ci else 16562306a36Sopenharmony_ci cam->tx_ss = NULL; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci dmabrg_free_irq(dmairq + 1); 16862306a36Sopenharmony_ci dmabrg_free_irq(dmairq); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci return 0; 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic int camelot_hw_params(struct snd_soc_component *component, 17462306a36Sopenharmony_ci struct snd_pcm_substream *substream, 17562306a36Sopenharmony_ci struct snd_pcm_hw_params *hw_params) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 17862306a36Sopenharmony_ci struct camelot_pcm *cam = &cam_pcm_data[asoc_rtd_to_cpu(rtd, 0)->id]; 17962306a36Sopenharmony_ci int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (recv) { 18262306a36Sopenharmony_ci cam->rx_period_size = params_period_bytes(hw_params); 18362306a36Sopenharmony_ci cam->rx_period = 0; 18462306a36Sopenharmony_ci } else { 18562306a36Sopenharmony_ci cam->tx_period_size = params_period_bytes(hw_params); 18662306a36Sopenharmony_ci cam->tx_period = 0; 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci return 0; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic int camelot_prepare(struct snd_soc_component *component, 19262306a36Sopenharmony_ci struct snd_pcm_substream *substream) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 19562306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 19662306a36Sopenharmony_ci struct camelot_pcm *cam = &cam_pcm_data[asoc_rtd_to_cpu(rtd, 0)->id]; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci pr_debug("PCM data: addr 0x%08lx len %d\n", 19962306a36Sopenharmony_ci (u32)runtime->dma_addr, runtime->dma_bytes); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 20262306a36Sopenharmony_ci BRGREG(BRGATXSAR) = (unsigned long)runtime->dma_area; 20362306a36Sopenharmony_ci BRGREG(BRGATXTCR) = runtime->dma_bytes; 20462306a36Sopenharmony_ci } else { 20562306a36Sopenharmony_ci BRGREG(BRGARXDAR) = (unsigned long)runtime->dma_area; 20662306a36Sopenharmony_ci BRGREG(BRGARXTCR) = runtime->dma_bytes; 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return 0; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic inline void dmabrg_play_dma_start(struct camelot_pcm *cam) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); 21562306a36Sopenharmony_ci /* start DMABRG engine: XFER start, auto-addr-reload */ 21662306a36Sopenharmony_ci BRGREG(BRGACR) = acr | ACR_TDE | ACR_TAR | ACR_TAM_2WORD; 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic inline void dmabrg_play_dma_stop(struct camelot_pcm *cam) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); 22262306a36Sopenharmony_ci /* forcibly terminate data transmission */ 22362306a36Sopenharmony_ci BRGREG(BRGACR) = acr | ACR_TDS; 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic inline void dmabrg_rec_dma_start(struct camelot_pcm *cam) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); 22962306a36Sopenharmony_ci /* start DMABRG engine: recv start, auto-reload */ 23062306a36Sopenharmony_ci BRGREG(BRGACR) = acr | ACR_RDE | ACR_RAR | ACR_RAM_2WORD; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic inline void dmabrg_rec_dma_stop(struct camelot_pcm *cam) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); 23662306a36Sopenharmony_ci /* forcibly terminate data receiver */ 23762306a36Sopenharmony_ci BRGREG(BRGACR) = acr | ACR_RDS; 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cistatic int camelot_trigger(struct snd_soc_component *component, 24162306a36Sopenharmony_ci struct snd_pcm_substream *substream, int cmd) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 24462306a36Sopenharmony_ci struct camelot_pcm *cam = &cam_pcm_data[asoc_rtd_to_cpu(rtd, 0)->id]; 24562306a36Sopenharmony_ci int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci switch (cmd) { 24862306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 24962306a36Sopenharmony_ci if (recv) 25062306a36Sopenharmony_ci dmabrg_rec_dma_start(cam); 25162306a36Sopenharmony_ci else 25262306a36Sopenharmony_ci dmabrg_play_dma_start(cam); 25362306a36Sopenharmony_ci break; 25462306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 25562306a36Sopenharmony_ci if (recv) 25662306a36Sopenharmony_ci dmabrg_rec_dma_stop(cam); 25762306a36Sopenharmony_ci else 25862306a36Sopenharmony_ci dmabrg_play_dma_stop(cam); 25962306a36Sopenharmony_ci break; 26062306a36Sopenharmony_ci default: 26162306a36Sopenharmony_ci return -EINVAL; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci return 0; 26562306a36Sopenharmony_ci} 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_cistatic snd_pcm_uframes_t camelot_pos(struct snd_soc_component *component, 26862306a36Sopenharmony_ci struct snd_pcm_substream *substream) 26962306a36Sopenharmony_ci{ 27062306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 27162306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 27262306a36Sopenharmony_ci struct camelot_pcm *cam = &cam_pcm_data[asoc_rtd_to_cpu(rtd, 0)->id]; 27362306a36Sopenharmony_ci int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; 27462306a36Sopenharmony_ci unsigned long pos; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci /* cannot use the DMABRG pointer register: under load, by the 27762306a36Sopenharmony_ci * time ALSA comes around to read the register, it is already 27862306a36Sopenharmony_ci * far ahead (or worse, already done with the fragment) of the 27962306a36Sopenharmony_ci * position at the time the IRQ was triggered, which results in 28062306a36Sopenharmony_ci * fast-playback sound in my test application (ScummVM) 28162306a36Sopenharmony_ci */ 28262306a36Sopenharmony_ci if (recv) 28362306a36Sopenharmony_ci pos = cam->rx_period ? cam->rx_period_size : 0; 28462306a36Sopenharmony_ci else 28562306a36Sopenharmony_ci pos = cam->tx_period ? cam->tx_period_size : 0; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci return bytes_to_frames(runtime, pos); 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic int camelot_pcm_new(struct snd_soc_component *component, 29162306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci struct snd_pcm *pcm = rtd->pcm; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci /* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel 29662306a36Sopenharmony_ci * in MMAP mode (i.e. aplay -M) 29762306a36Sopenharmony_ci */ 29862306a36Sopenharmony_ci snd_pcm_set_managed_buffer_all(pcm, 29962306a36Sopenharmony_ci SNDRV_DMA_TYPE_CONTINUOUS, 30062306a36Sopenharmony_ci NULL, 30162306a36Sopenharmony_ci DMABRG_PREALLOC_BUFFER, DMABRG_PREALLOC_BUFFER_MAX); 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci return 0; 30462306a36Sopenharmony_ci} 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_cistatic const struct snd_soc_component_driver sh7760_soc_component = { 30762306a36Sopenharmony_ci .open = camelot_pcm_open, 30862306a36Sopenharmony_ci .close = camelot_pcm_close, 30962306a36Sopenharmony_ci .hw_params = camelot_hw_params, 31062306a36Sopenharmony_ci .prepare = camelot_prepare, 31162306a36Sopenharmony_ci .trigger = camelot_trigger, 31262306a36Sopenharmony_ci .pointer = camelot_pos, 31362306a36Sopenharmony_ci .pcm_construct = camelot_pcm_new, 31462306a36Sopenharmony_ci}; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_cistatic int sh7760_soc_platform_probe(struct platform_device *pdev) 31762306a36Sopenharmony_ci{ 31862306a36Sopenharmony_ci return devm_snd_soc_register_component(&pdev->dev, &sh7760_soc_component, 31962306a36Sopenharmony_ci NULL, 0); 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_cistatic struct platform_driver sh7760_pcm_driver = { 32362306a36Sopenharmony_ci .driver = { 32462306a36Sopenharmony_ci .name = "sh7760-pcm-audio", 32562306a36Sopenharmony_ci }, 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci .probe = sh7760_soc_platform_probe, 32862306a36Sopenharmony_ci}; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_cimodule_platform_driver(sh7760_pcm_driver); 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 33362306a36Sopenharmony_ciMODULE_DESCRIPTION("SH7760 Audio DMA (DMABRG) driver"); 33462306a36Sopenharmony_ciMODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>"); 335