brintos

brintos / linux-shallow public Read only

0
0
Text · 16.7 KiB · 936cd6e Raw
636 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3*4* Copyright Adrian McMenamin 2005, 2006, 20075* <adrian@mcmen.demon.co.uk>6* Requires firmware (BSD licenced) available from:7* http://linuxdc.cvs.sourceforge.net/linuxdc/linux-sh-dc/sound/oss/aica/firmware/8* or the maintainer9*/10 11#include <linux/init.h>12#include <linux/jiffies.h>13#include <linux/slab.h>14#include <linux/time.h>15#include <linux/wait.h>16#include <linux/module.h>17#include <linux/platform_device.h>18#include <linux/firmware.h>19#include <linux/timer.h>20#include <linux/delay.h>21#include <linux/workqueue.h>22#include <linux/io.h>23#include <sound/core.h>24#include <sound/control.h>25#include <sound/pcm.h>26#include <sound/initval.h>27#include <sound/info.h>28#include <asm/dma.h>29#include <mach/sysasic.h>30#include "aica.h"31 32MODULE_AUTHOR("Adrian McMenamin <adrian@mcmen.demon.co.uk>");33MODULE_DESCRIPTION("Dreamcast AICA sound (pcm) driver");34MODULE_LICENSE("GPL");35MODULE_FIRMWARE("aica_firmware.bin");36 37/* module parameters */38#define CARD_NAME "AICA"39static int index = -1;40static char *id;41static bool enable = 1;42module_param(index, int, 0444);43MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");44module_param(id, charp, 0444);45MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");46module_param(enable, bool, 0644);47MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");48 49/* Simple platform device */50static struct platform_device *pd;51static struct resource aica_memory_space[2] = {52	{53	 .name = "AICA ARM CONTROL",54	 .start = ARM_RESET_REGISTER,55	 .flags = IORESOURCE_MEM,56	 .end = ARM_RESET_REGISTER + 3,57	 },58	{59	 .name = "AICA Sound RAM",60	 .start = SPU_MEMORY_BASE,61	 .flags = IORESOURCE_MEM,62	 .end = SPU_MEMORY_BASE + 0x200000 - 1,63	 },64};65 66/* SPU specific functions */67/* spu_write_wait - wait for G2-SH FIFO to clear */68static void spu_write_wait(void)69{70	int time_count;71	time_count = 0;72	while (1) {73		if (!(readl(G2_FIFO) & 0x11))74			break;75		/* To ensure hardware failure doesn't wedge kernel */76		time_count++;77		if (time_count > 0x10000) {78			pr_warn("WARNING: G2 FIFO appears to be blocked.\n");79			break;80		}81	}82}83 84/* spu_memset - write to memory in SPU address space */85static void spu_memset(u32 toi, u32 what, int length)86{87	int i;88	unsigned long flags;89	if (snd_BUG_ON(length % 4))90		return;91	for (i = 0; i < length; i++) {92		if (!(i % 8))93			spu_write_wait();94		local_irq_save(flags);95		writel(what, toi + SPU_MEMORY_BASE);96		local_irq_restore(flags);97		toi++;98	}99}100 101/* spu_memload - write to SPU address space */102static void spu_memload(u32 toi, const void *from, int length)103{104	unsigned long flags;105	const u32 *froml = from;106	u32 __iomem *to = (u32 __iomem *) (SPU_MEMORY_BASE + toi);107	int i;108	u32 val;109	length = DIV_ROUND_UP(length, 4);110	spu_write_wait();111	for (i = 0; i < length; i++) {112		if (!(i % 8))113			spu_write_wait();114		val = *froml;115		local_irq_save(flags);116		writel(val, to);117		local_irq_restore(flags);118		froml++;119		to++;120	}121}122 123/* spu_disable - set spu registers to stop sound output */124static void spu_disable(void)125{126	int i;127	unsigned long flags;128	u32 regval;129	spu_write_wait();130	regval = readl(ARM_RESET_REGISTER);131	regval |= 1;132	spu_write_wait();133	local_irq_save(flags);134	writel(regval, ARM_RESET_REGISTER);135	local_irq_restore(flags);136	for (i = 0; i < 64; i++) {137		spu_write_wait();138		regval = readl(SPU_REGISTER_BASE + (i * 0x80));139		regval = (regval & ~0x4000) | 0x8000;140		spu_write_wait();141		local_irq_save(flags);142		writel(regval, SPU_REGISTER_BASE + (i * 0x80));143		local_irq_restore(flags);144	}145}146 147/* spu_enable - set spu registers to enable sound output */148static void spu_enable(void)149{150	unsigned long flags;151	u32 regval = readl(ARM_RESET_REGISTER);152	regval &= ~1;153	spu_write_wait();154	local_irq_save(flags);155	writel(regval, ARM_RESET_REGISTER);156	local_irq_restore(flags);157}158 159/* 160 * Halt the sound processor, clear the memory,161 * load some default ARM7 code, and then restart ARM7162*/163static void spu_reset(void)164{165	unsigned long flags;166	spu_disable();167	spu_memset(0, 0, 0x200000 / 4);168	/* Put ARM7 in endless loop */169	local_irq_save(flags);170	__raw_writel(0xea000002, SPU_MEMORY_BASE);171	local_irq_restore(flags);172	spu_enable();173}174 175/* aica_chn_start - write to spu to start playback */176static void aica_chn_start(void)177{178	unsigned long flags;179	spu_write_wait();180	local_irq_save(flags);181	writel(AICA_CMD_KICK | AICA_CMD_START, (u32 *) AICA_CONTROL_POINT);182	local_irq_restore(flags);183}184 185/* aica_chn_halt - write to spu to halt playback */186static void aica_chn_halt(void)187{188	unsigned long flags;189	spu_write_wait();190	local_irq_save(flags);191	writel(AICA_CMD_KICK | AICA_CMD_STOP, (u32 *) AICA_CONTROL_POINT);192	local_irq_restore(flags);193}194 195/* ALSA code below */196static const struct snd_pcm_hardware snd_pcm_aica_playback_hw = {197	.info = (SNDRV_PCM_INFO_NONINTERLEAVED),198	.formats =199	    (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |200	     SNDRV_PCM_FMTBIT_IMA_ADPCM),201	.rates = SNDRV_PCM_RATE_8000_48000,202	.rate_min = 8000,203	.rate_max = 48000,204	.channels_min = 1,205	.channels_max = 2,206	.buffer_bytes_max = AICA_BUFFER_SIZE,207	.period_bytes_min = AICA_PERIOD_SIZE,208	.period_bytes_max = AICA_PERIOD_SIZE,209	.periods_min = AICA_PERIOD_NUMBER,210	.periods_max = AICA_PERIOD_NUMBER,211};212 213static int aica_dma_transfer(int channels, int buffer_size,214			     struct snd_pcm_substream *substream)215{216	int q, err, period_offset;217	struct snd_card_aica *dreamcastcard;218	struct snd_pcm_runtime *runtime;219	unsigned long flags;220	err = 0;221	dreamcastcard = substream->pcm->private_data;222	period_offset = dreamcastcard->clicks;223	period_offset %= (AICA_PERIOD_NUMBER / channels);224	runtime = substream->runtime;225	for (q = 0; q < channels; q++) {226		local_irq_save(flags);227		err = dma_xfer(AICA_DMA_CHANNEL,228			       (unsigned long) (runtime->dma_area +229						(AICA_BUFFER_SIZE * q) /230						channels +231						AICA_PERIOD_SIZE *232						period_offset),233			       AICA_CHANNEL0_OFFSET + q * CHANNEL_OFFSET +234			       AICA_PERIOD_SIZE * period_offset,235			       buffer_size / channels, AICA_DMA_MODE);236		if (unlikely(err < 0)) {237			local_irq_restore(flags);238			break;239		}240		dma_wait_for_completion(AICA_DMA_CHANNEL);241		local_irq_restore(flags);242	}243	return err;244}245 246static void startup_aica(struct snd_card_aica *dreamcastcard)247{248	spu_memload(AICA_CHANNEL0_CONTROL_OFFSET,249		    dreamcastcard->channel, sizeof(struct aica_channel));250	aica_chn_start();251}252 253static void run_spu_dma(struct work_struct *work)254{255	int buffer_size;256	struct snd_pcm_runtime *runtime;257	struct snd_card_aica *dreamcastcard;258	dreamcastcard =259	    container_of(work, struct snd_card_aica, spu_dma_work);260	runtime = dreamcastcard->substream->runtime;261	if (unlikely(dreamcastcard->dma_check == 0)) {262		buffer_size =263		    frames_to_bytes(runtime, runtime->buffer_size);264		if (runtime->channels > 1)265			dreamcastcard->channel->flags |= 0x01;266		aica_dma_transfer(runtime->channels, buffer_size,267				  dreamcastcard->substream);268		startup_aica(dreamcastcard);269		dreamcastcard->clicks =270		    buffer_size / (AICA_PERIOD_SIZE * runtime->channels);271		return;272	} else {273		aica_dma_transfer(runtime->channels,274				  AICA_PERIOD_SIZE * runtime->channels,275				  dreamcastcard->substream);276		snd_pcm_period_elapsed(dreamcastcard->substream);277		dreamcastcard->clicks++;278		if (unlikely(dreamcastcard->clicks >= AICA_PERIOD_NUMBER))279			dreamcastcard->clicks %= AICA_PERIOD_NUMBER;280		if (snd_pcm_running(dreamcastcard->substream))281			mod_timer(&dreamcastcard->timer, jiffies + 1);282	}283}284 285static void aica_period_elapsed(struct timer_list *t)286{287	struct snd_card_aica *dreamcastcard = from_timer(dreamcastcard,288							      t, timer);289	struct snd_pcm_substream *substream = dreamcastcard->substream;290	/*timer function - so cannot sleep */291	int play_period;292	struct snd_pcm_runtime *runtime;293	if (!snd_pcm_running(substream))294		return;295	runtime = substream->runtime;296	dreamcastcard = substream->pcm->private_data;297	/* Have we played out an additional period? */298	play_period =299	    frames_to_bytes(runtime,300			    readl301			    (AICA_CONTROL_CHANNEL_SAMPLE_NUMBER)) /302	    AICA_PERIOD_SIZE;303	if (play_period == dreamcastcard->current_period) {304		/* reschedule the timer */305		mod_timer(&(dreamcastcard->timer), jiffies + 1);306		return;307	}308	if (runtime->channels > 1)309		dreamcastcard->current_period = play_period;310	if (unlikely(dreamcastcard->dma_check == 0))311		dreamcastcard->dma_check = 1;312	schedule_work(&(dreamcastcard->spu_dma_work));313}314 315static void spu_begin_dma(struct snd_pcm_substream *substream)316{317	struct snd_card_aica *dreamcastcard;318	struct snd_pcm_runtime *runtime;319	runtime = substream->runtime;320	dreamcastcard = substream->pcm->private_data;321	/*get the queue to do the work */322	schedule_work(&(dreamcastcard->spu_dma_work));323	mod_timer(&dreamcastcard->timer, jiffies + 4);324}325 326static int snd_aicapcm_pcm_open(struct snd_pcm_substream327				*substream)328{329	struct snd_pcm_runtime *runtime;330	struct aica_channel *channel;331	struct snd_card_aica *dreamcastcard;332	if (!enable)333		return -ENOENT;334	dreamcastcard = substream->pcm->private_data;335	channel = kmalloc(sizeof(struct aica_channel), GFP_KERNEL);336	if (!channel)337		return -ENOMEM;338	/* set defaults for channel */339	channel->sfmt = SM_8BIT;340	channel->cmd = AICA_CMD_START;341	channel->vol = dreamcastcard->master_volume;342	channel->pan = 0x80;343	channel->pos = 0;344	channel->flags = 0;	/* default to mono */345	dreamcastcard->channel = channel;346	runtime = substream->runtime;347	runtime->hw = snd_pcm_aica_playback_hw;348	spu_enable();349	dreamcastcard->clicks = 0;350	dreamcastcard->current_period = 0;351	dreamcastcard->dma_check = 0;352	return 0;353}354 355static int snd_aicapcm_pcm_sync_stop(struct snd_pcm_substream *substream)356{357	struct snd_card_aica *dreamcastcard = substream->pcm->private_data;358 359	del_timer_sync(&dreamcastcard->timer);360	cancel_work_sync(&dreamcastcard->spu_dma_work);361	return 0;362}363 364static int snd_aicapcm_pcm_close(struct snd_pcm_substream365				 *substream)366{367	struct snd_card_aica *dreamcastcard = substream->pcm->private_data;368	dreamcastcard->substream = NULL;369	kfree(dreamcastcard->channel);370	spu_disable();371	return 0;372}373 374static int snd_aicapcm_pcm_prepare(struct snd_pcm_substream375				   *substream)376{377	struct snd_card_aica *dreamcastcard = substream->pcm->private_data;378	if ((substream->runtime)->format == SNDRV_PCM_FORMAT_S16_LE)379		dreamcastcard->channel->sfmt = SM_16BIT;380	dreamcastcard->channel->freq = substream->runtime->rate;381	dreamcastcard->substream = substream;382	return 0;383}384 385static int snd_aicapcm_pcm_trigger(struct snd_pcm_substream386				   *substream, int cmd)387{388	switch (cmd) {389	case SNDRV_PCM_TRIGGER_START:390		spu_begin_dma(substream);391		break;392	case SNDRV_PCM_TRIGGER_STOP:393		aica_chn_halt();394		break;395	default:396		return -EINVAL;397	}398	return 0;399}400 401static unsigned long snd_aicapcm_pcm_pointer(struct snd_pcm_substream402					     *substream)403{404	return readl(AICA_CONTROL_CHANNEL_SAMPLE_NUMBER);405}406 407static const struct snd_pcm_ops snd_aicapcm_playback_ops = {408	.open = snd_aicapcm_pcm_open,409	.close = snd_aicapcm_pcm_close,410	.prepare = snd_aicapcm_pcm_prepare,411	.trigger = snd_aicapcm_pcm_trigger,412	.pointer = snd_aicapcm_pcm_pointer,413	.sync_stop = snd_aicapcm_pcm_sync_stop,414};415 416/* TO DO: set up to handle more than one pcm instance */417static int __init snd_aicapcmchip(struct snd_card_aica418				  *dreamcastcard, int pcm_index)419{420	struct snd_pcm *pcm;421	int err;422	/* AICA has no capture ability */423	err =424	    snd_pcm_new(dreamcastcard->card, "AICA PCM", pcm_index, 1, 0,425			&pcm);426	if (unlikely(err < 0))427		return err;428	pcm->private_data = dreamcastcard;429	strcpy(pcm->name, "AICA PCM");430	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,431			&snd_aicapcm_playback_ops);432	/* Allocate the DMA buffers */433	snd_pcm_set_managed_buffer_all(pcm,434				       SNDRV_DMA_TYPE_CONTINUOUS,435				       NULL,436				       AICA_BUFFER_SIZE,437				       AICA_BUFFER_SIZE);438	return 0;439}440 441/* Mixer controls */442#define aica_pcmswitch_info		snd_ctl_boolean_mono_info443 444static int aica_pcmswitch_get(struct snd_kcontrol *kcontrol,445			      struct snd_ctl_elem_value *ucontrol)446{447	ucontrol->value.integer.value[0] = 1;	/* TO DO: Fix me */448	return 0;449}450 451static int aica_pcmswitch_put(struct snd_kcontrol *kcontrol,452			      struct snd_ctl_elem_value *ucontrol)453{454	if (ucontrol->value.integer.value[0] == 1)455		return 0;	/* TO DO: Fix me */456	else457		aica_chn_halt();458	return 0;459}460 461static int aica_pcmvolume_info(struct snd_kcontrol *kcontrol,462			       struct snd_ctl_elem_info *uinfo)463{464	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;465	uinfo->count = 1;466	uinfo->value.integer.min = 0;467	uinfo->value.integer.max = 0xFF;468	return 0;469}470 471static int aica_pcmvolume_get(struct snd_kcontrol *kcontrol,472			      struct snd_ctl_elem_value *ucontrol)473{474	struct snd_card_aica *dreamcastcard;475	dreamcastcard = kcontrol->private_data;476	if (unlikely(!dreamcastcard->channel))477		return -ETXTBSY;	/* we've not yet been set up */478	ucontrol->value.integer.value[0] = dreamcastcard->channel->vol;479	return 0;480}481 482static int aica_pcmvolume_put(struct snd_kcontrol *kcontrol,483			      struct snd_ctl_elem_value *ucontrol)484{485	struct snd_card_aica *dreamcastcard;486	unsigned int vol;487	dreamcastcard = kcontrol->private_data;488	if (unlikely(!dreamcastcard->channel))489		return -ETXTBSY;490	vol = ucontrol->value.integer.value[0];491	if (vol > 0xff)492		return -EINVAL;493	if (unlikely(dreamcastcard->channel->vol == vol))494		return 0;495	dreamcastcard->channel->vol = ucontrol->value.integer.value[0];496	dreamcastcard->master_volume = ucontrol->value.integer.value[0];497	spu_memload(AICA_CHANNEL0_CONTROL_OFFSET,498		    dreamcastcard->channel, sizeof(struct aica_channel));499	return 1;500}501 502static const struct snd_kcontrol_new snd_aica_pcmswitch_control = {503	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,504	.name = "PCM Playback Switch",505	.index = 0,506	.info = aica_pcmswitch_info,507	.get = aica_pcmswitch_get,508	.put = aica_pcmswitch_put509};510 511static const struct snd_kcontrol_new snd_aica_pcmvolume_control = {512	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,513	.name = "PCM Playback Volume",514	.index = 0,515	.info = aica_pcmvolume_info,516	.get = aica_pcmvolume_get,517	.put = aica_pcmvolume_put518};519 520static int load_aica_firmware(void)521{522	int err;523	const struct firmware *fw_entry;524	spu_reset();525	err = request_firmware(&fw_entry, "aica_firmware.bin", &pd->dev);526	if (unlikely(err))527		return err;528	/* write firmware into memory */529	spu_disable();530	spu_memload(0, fw_entry->data, fw_entry->size);531	spu_enable();532	release_firmware(fw_entry);533	return err;534}535 536static int add_aicamixer_controls(struct snd_card_aica *dreamcastcard)537{538	int err;539	err = snd_ctl_add540	    (dreamcastcard->card,541	     snd_ctl_new1(&snd_aica_pcmvolume_control, dreamcastcard));542	if (unlikely(err < 0))543		return err;544	err = snd_ctl_add545	    (dreamcastcard->card,546	     snd_ctl_new1(&snd_aica_pcmswitch_control, dreamcastcard));547	if (unlikely(err < 0))548		return err;549	return 0;550}551 552static void snd_aica_remove(struct platform_device *devptr)553{554	struct snd_card_aica *dreamcastcard;555	dreamcastcard = platform_get_drvdata(devptr);556	snd_card_free(dreamcastcard->card);557	kfree(dreamcastcard);558}559 560static int snd_aica_probe(struct platform_device *devptr)561{562	int err;563	struct snd_card_aica *dreamcastcard;564	dreamcastcard = kzalloc(sizeof(struct snd_card_aica), GFP_KERNEL);565	if (unlikely(!dreamcastcard))566		return -ENOMEM;567	err = snd_card_new(&devptr->dev, index, SND_AICA_DRIVER,568			   THIS_MODULE, 0, &dreamcastcard->card);569	if (unlikely(err < 0)) {570		kfree(dreamcastcard);571		return err;572	}573	strcpy(dreamcastcard->card->driver, "snd_aica");574	strcpy(dreamcastcard->card->shortname, SND_AICA_DRIVER);575	strcpy(dreamcastcard->card->longname,576	       "Yamaha AICA Super Intelligent Sound Processor for SEGA Dreamcast");577	/* Prepare to use the queue */578	INIT_WORK(&(dreamcastcard->spu_dma_work), run_spu_dma);579	timer_setup(&dreamcastcard->timer, aica_period_elapsed, 0);580	/* Load the PCM 'chip' */581	err = snd_aicapcmchip(dreamcastcard, 0);582	if (unlikely(err < 0))583		goto freedreamcast;584	/* Add basic controls */585	err = add_aicamixer_controls(dreamcastcard);586	if (unlikely(err < 0))587		goto freedreamcast;588	/* Register the card with ALSA subsystem */589	err = snd_card_register(dreamcastcard->card);590	if (unlikely(err < 0))591		goto freedreamcast;592	platform_set_drvdata(devptr, dreamcastcard);593	dev_info(&devptr->dev,594		 "ALSA Driver for Yamaha AICA Super Intelligent Sound Processor\n");595	return 0;596      freedreamcast:597	snd_card_free(dreamcastcard->card);598	kfree(dreamcastcard);599	return err;600}601 602static struct platform_driver snd_aica_driver = {603	.probe = snd_aica_probe,604	.remove_new = snd_aica_remove,605	.driver = {606		.name = SND_AICA_DRIVER,607	},608};609 610static int __init aica_init(void)611{612	int err;613	err = platform_driver_register(&snd_aica_driver);614	if (unlikely(err < 0))615		return err;616	pd = platform_device_register_simple(SND_AICA_DRIVER, -1,617					     aica_memory_space, 2);618	if (IS_ERR(pd)) {619		platform_driver_unregister(&snd_aica_driver);620		return PTR_ERR(pd);621	}622	/* Load the firmware */623	return load_aica_firmware();624}625 626static void __exit aica_exit(void)627{628	platform_device_unregister(pd);629	platform_driver_unregister(&snd_aica_driver);630	/* Kill any sound still playing and reset ARM7 to safe state */631	spu_reset();632}633 634module_init(aica_init);635module_exit(aica_exit);636