brintos

brintos / linux-shallow public Read only

0
0
Text · 9.3 KiB · c535394 Raw
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