brintos

brintos / linux-shallow public Read only

0
0
Text · 15.6 KiB · 9234d4f Raw
656 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * pcm emulation on emu8000 wavetable4 *5 *  Copyright (C) 2002 Takashi Iwai <tiwai@suse.de>6 */7 8#include "emu8000_local.h"9 10#include <linux/sched/signal.h>11#include <linux/init.h>12#include <linux/slab.h>13#include <sound/initval.h>14#include <sound/pcm.h>15 16/*17 * define the following if you want to use this pcm with non-interleaved mode18 */19/* #define USE_NONINTERLEAVE */20 21/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set22 * mmap_emulation flag to 1 in your .asoundrc, such like23 *24 *	pcm.emu8k {25 *		type plug26 *		slave.pcm {27 *			type hw28 *			card 029 *			device 130 *			mmap_emulation 131 *		}32 *	}33 *34 * besides, for the time being, the non-interleaved mode doesn't work well on35 * alsa-lib...36 */37 38 39struct snd_emu8k_pcm {40	struct snd_emu8000 *emu;41	struct snd_pcm_substream *substream;42 43	unsigned int allocated_bytes;44	struct snd_util_memblk *block;45	unsigned int offset;46	unsigned int buf_size;47	unsigned int period_size;48	unsigned int loop_start[2];49	unsigned int pitch;50	int panning[2];51	int last_ptr;52	int period_pos;53	int voices;54	unsigned int dram_opened: 1;55	unsigned int running: 1;56	unsigned int timer_running: 1;57	struct timer_list timer;58	spinlock_t timer_lock;59};60 61#define LOOP_BLANK_SIZE		862 63 64/*65 * open up channels for the simultaneous data transfer and playback66 */67static int68emu8k_open_dram_for_pcm(struct snd_emu8000 *emu, int channels)69{70	int i;71 72	/* reserve up to 2 voices for playback */73	snd_emux_lock_voice(emu->emu, 0);74	if (channels > 1)75		snd_emux_lock_voice(emu->emu, 1);76 77	/* reserve 28 voices for loading */78	for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) {79		unsigned int mode = EMU8000_RAM_WRITE;80		snd_emux_lock_voice(emu->emu, i);81#ifndef USE_NONINTERLEAVE82		if (channels > 1 && (i & 1) != 0)83			mode |= EMU8000_RAM_RIGHT;84#endif85		snd_emu8000_dma_chan(emu, i, mode);86	}87 88	/* assign voice 31 and 32 to ROM */89	EMU8000_VTFT_WRITE(emu, 30, 0);90	EMU8000_PSST_WRITE(emu, 30, 0x1d8);91	EMU8000_CSL_WRITE(emu, 30, 0x1e0);92	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);93	EMU8000_VTFT_WRITE(emu, 31, 0);94	EMU8000_PSST_WRITE(emu, 31, 0x1d8);95	EMU8000_CSL_WRITE(emu, 31, 0x1e0);96	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);97 98	return 0;99}100 101/*102 */103static void104snd_emu8000_write_wait(struct snd_emu8000 *emu, int can_schedule)105{106	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {107		if (can_schedule) {108			schedule_timeout_interruptible(1);109			if (signal_pending(current))110				break;111		}112	}113}114 115/*116 * close all channels117 */118static void119emu8k_close_dram(struct snd_emu8000 *emu)120{121	int i;122 123	for (i = 0; i < 2; i++)124		snd_emux_unlock_voice(emu->emu, i);125	for (; i < EMU8000_DRAM_VOICES; i++) {126		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);127		snd_emux_unlock_voice(emu->emu, i);128	}129}130 131/*132 * convert Hz to AWE32 rate offset (see emux/soundfont.c)133 */134 135#define OFFSET_SAMPLERATE	1011119		/* base = 44100 */136#define SAMPLERATE_RATIO	4096137 138static int calc_rate_offset(int hz)139{140	return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO);141}142 143 144/*145 */146 147static const struct snd_pcm_hardware emu8k_pcm_hw = {148#ifdef USE_NONINTERLEAVE149	.info =			SNDRV_PCM_INFO_NONINTERLEAVED,150#else151	.info =			SNDRV_PCM_INFO_INTERLEAVED,152#endif153	.formats =		SNDRV_PCM_FMTBIT_S16_LE,154	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,155	.rate_min =		4000,156	.rate_max =		48000,157	.channels_min =		1,158	.channels_max =		2,159	.buffer_bytes_max =	(128*1024),160	.period_bytes_min =	1024,161	.period_bytes_max =	(128*1024),162	.periods_min =		2,163	.periods_max =		1024,164	.fifo_size =		0,165 166};167 168/*169 * get the current position at the given channel from CCCA register170 */171static inline int emu8k_get_curpos(struct snd_emu8k_pcm *rec, int ch)172{173	int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff;174	val -= rec->loop_start[ch] - 1;175	return val;176}177 178 179/*180 * timer interrupt handler181 * check the current position and update the period if necessary.182 */183static void emu8k_pcm_timer_func(struct timer_list *t)184{185	struct snd_emu8k_pcm *rec = from_timer(rec, t, timer);186	int ptr, delta;187 188	spin_lock(&rec->timer_lock);189	/* update the current pointer */190	ptr = emu8k_get_curpos(rec, 0);191	if (ptr < rec->last_ptr)192		delta = ptr + rec->buf_size - rec->last_ptr;193	else194		delta = ptr - rec->last_ptr;195	rec->period_pos += delta;196	rec->last_ptr = ptr;197 198	/* reprogram timer */199	mod_timer(&rec->timer, jiffies + 1);200 201	/* update period */202	if (rec->period_pos >= (int)rec->period_size) {203		rec->period_pos %= rec->period_size;204		spin_unlock(&rec->timer_lock);205		snd_pcm_period_elapsed(rec->substream);206		return;207	}208	spin_unlock(&rec->timer_lock);209}210 211 212/*213 * open pcm214 * creating an instance here215 */216static int emu8k_pcm_open(struct snd_pcm_substream *subs)217{218	struct snd_emu8000 *emu = snd_pcm_substream_chip(subs);219	struct snd_emu8k_pcm *rec;220	struct snd_pcm_runtime *runtime = subs->runtime;221 222	rec = kzalloc(sizeof(*rec), GFP_KERNEL);223	if (! rec)224		return -ENOMEM;225 226	rec->emu = emu;227	rec->substream = subs;228	runtime->private_data = rec;229 230	spin_lock_init(&rec->timer_lock);231	timer_setup(&rec->timer, emu8k_pcm_timer_func, 0);232 233	runtime->hw = emu8k_pcm_hw;234	runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3;235	runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2;236 237	/* use timer to update periods.. (specified in msec) */238	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,239				     DIV_ROUND_UP(1000000, HZ), UINT_MAX);240 241	return 0;242}243 244static int emu8k_pcm_close(struct snd_pcm_substream *subs)245{246	struct snd_emu8k_pcm *rec = subs->runtime->private_data;247	kfree(rec);248	subs->runtime->private_data = NULL;249	return 0;250}251 252/*253 * calculate pitch target254 */255static int calc_pitch_target(int pitch)256{257	int ptarget = 1 << (pitch >> 12);258	if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710;259	if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710;260	if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710;261	ptarget += (ptarget >> 1);262	if (ptarget > 0xffff) ptarget = 0xffff;263	return ptarget;264}265 266/*267 * set up the voice268 */269static void setup_voice(struct snd_emu8k_pcm *rec, int ch)270{271	struct snd_emu8000 *hw = rec->emu;272	unsigned int temp;273 274	/* channel to be silent and idle */275	EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);276	EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);277	EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);278	EMU8000_PTRX_WRITE(hw, ch, 0);279	EMU8000_CPF_WRITE(hw, ch, 0);280 281	/* pitch offset */282	EMU8000_IP_WRITE(hw, ch, rec->pitch);283	/* set envelope parameters */284	EMU8000_ENVVAL_WRITE(hw, ch, 0x8000);285	EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f);286	EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f);287	EMU8000_ENVVOL_WRITE(hw, ch, 0x8000);288	EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f);289	/* decay/sustain parameter for volume envelope is used290	   for triggerg the voice */291	/* modulation envelope heights */292	EMU8000_PEFE_WRITE(hw, ch, 0x0);293	/* lfo1/2 delay */294	EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000);295	EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000);296	/* lfo1 pitch & cutoff shift */297	EMU8000_FMMOD_WRITE(hw, ch, 0);298	/* lfo1 volume & freq */299	EMU8000_TREMFRQ_WRITE(hw, ch, 0);300	/* lfo2 pitch & freq */301	EMU8000_FM2FRQ2_WRITE(hw, ch, 0);302	/* pan & loop start */303	temp = rec->panning[ch];304	temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1);305	EMU8000_PSST_WRITE(hw, ch, temp);306	/* chorus & loop end (chorus 8bit, MSB) */307	temp = 0; // chorus308	temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1);309	EMU8000_CSL_WRITE(hw, ch, temp);310	/* Q & current address (Q 4bit value, MSB) */311	temp = 0; // filterQ312	temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1);313	EMU8000_CCCA_WRITE(hw, ch, temp);314	/* clear unknown registers */315	EMU8000_00A0_WRITE(hw, ch, 0);316	EMU8000_0080_WRITE(hw, ch, 0);317}318 319/*320 * trigger the voice321 */322static void start_voice(struct snd_emu8k_pcm *rec, int ch)323{324	unsigned long flags;325	struct snd_emu8000 *hw = rec->emu;326	unsigned int temp, aux;327	int pt = calc_pitch_target(rec->pitch);328 329	/* cutoff and volume */330	EMU8000_IFATN_WRITE(hw, ch, 0xff00);331	EMU8000_VTFT_WRITE(hw, ch, 0xffff);332	EMU8000_CVCF_WRITE(hw, ch, 0xffff);333	/* trigger envelope */334	EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f);335	/* set reverb and pitch target */336	temp = 0; // reverb337	if (rec->panning[ch] == 0)338		aux = 0xff;339	else340		aux = (-rec->panning[ch]) & 0xff;341	temp = (temp << 8) | (pt << 16) | aux;342	EMU8000_PTRX_WRITE(hw, ch, temp);343	EMU8000_CPF_WRITE(hw, ch, pt << 16);344 345	/* start timer */346	spin_lock_irqsave(&rec->timer_lock, flags);347	if (! rec->timer_running) {348		mod_timer(&rec->timer, jiffies + 1);349		rec->timer_running = 1;350	}351	spin_unlock_irqrestore(&rec->timer_lock, flags);352}353 354/*355 * stop the voice immediately356 */357static void stop_voice(struct snd_emu8k_pcm *rec, int ch)358{359	unsigned long flags;360	struct snd_emu8000 *hw = rec->emu;361 362	EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);363 364	/* stop timer */365	spin_lock_irqsave(&rec->timer_lock, flags);366	if (rec->timer_running) {367		del_timer(&rec->timer);368		rec->timer_running = 0;369	}370	spin_unlock_irqrestore(&rec->timer_lock, flags);371}372 373static int emu8k_pcm_trigger(struct snd_pcm_substream *subs, int cmd)374{375	struct snd_emu8k_pcm *rec = subs->runtime->private_data;376	int ch;377 378	switch (cmd) {379	case SNDRV_PCM_TRIGGER_START:380		for (ch = 0; ch < rec->voices; ch++)381			start_voice(rec, ch);382		rec->running = 1;383		break;384	case SNDRV_PCM_TRIGGER_STOP:385		rec->running = 0;386		for (ch = 0; ch < rec->voices; ch++)387			stop_voice(rec, ch);388		break;389	default:390		return -EINVAL;391	}392	return 0;393}394 395 396/*397 * copy / silence ops398 */399 400/*401 * this macro should be inserted in the copy/silence loops402 * to reduce the latency.  without this, the system will hang up403 * during the whole loop.404 */405#define CHECK_SCHEDULER() \406do { \407	cond_resched();\408	if (signal_pending(current))\409		return -EAGAIN;\410} while (0)411 412#define GET_VAL(sval, iter)						\413	do {								\414		if (!iter)						\415			sval = 0;					\416		else if (copy_from_iter(&sval, 2, iter) != 2)		\417			return -EFAULT;					\418	} while (0)419 420#ifdef USE_NONINTERLEAVE421 422#define LOOP_WRITE(rec, offset, iter, count)			\423	do {							\424		struct snd_emu8000 *emu = (rec)->emu;		\425		snd_emu8000_write_wait(emu, 1);			\426		EMU8000_SMALW_WRITE(emu, offset);		\427		while (count > 0) {				\428			unsigned short sval;			\429			CHECK_SCHEDULER();			\430			GET_VAL(sval, iter);			\431			EMU8000_SMLD_WRITE(emu, sval);		\432			count--;				\433		}						\434	} while (0)435 436/* copy one channel block */437static int emu8k_pcm_copy(struct snd_pcm_substream *subs,438			  int voice, unsigned long pos,439			  struct iov_iter *src, unsigned long count)440{441	struct snd_emu8k_pcm *rec = subs->runtime->private_data;442 443	/* convert to word unit */444	pos = (pos << 1) + rec->loop_start[voice];445	count <<= 1;446	LOOP_WRITE(rec, pos, src, count);447	return 0;448}449 450/* make a channel block silence */451static int emu8k_pcm_silence(struct snd_pcm_substream *subs,452			     int voice, unsigned long pos, unsigned long count)453{454	struct snd_emu8k_pcm *rec = subs->runtime->private_data;455 456	/* convert to word unit */457	pos = (pos << 1) + rec->loop_start[voice];458	count <<= 1;459	LOOP_WRITE(rec, pos, NULL, count);460	return 0;461}462 463#else /* interleave */464 465#define LOOP_WRITE(rec, pos, iter, count)				\466	do {								\467		struct snd_emu8000 *emu = rec->emu;			\468		snd_emu8000_write_wait(emu, 1);				\469		EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]);	\470		if (rec->voices > 1)					\471			EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]); \472		while (count > 0) {					\473			unsigned short sval;				\474			CHECK_SCHEDULER();				\475			GET_VAL(sval, iter);				\476			EMU8000_SMLD_WRITE(emu, sval);			\477			if (rec->voices > 1) {				\478				CHECK_SCHEDULER();			\479				GET_VAL(sval, iter);			\480				EMU8000_SMRD_WRITE(emu, sval);		\481			}						\482			count--;					\483		}							\484	} while (0)485 486 487/*488 * copy the interleaved data can be done easily by using489 * DMA "left" and "right" channels on emu8k engine.490 */491static int emu8k_pcm_copy(struct snd_pcm_substream *subs,492			  int voice, unsigned long pos,493			  struct iov_iter *src, unsigned long count)494{495	struct snd_emu8k_pcm *rec = subs->runtime->private_data;496 497	/* convert to frames */498	pos = bytes_to_frames(subs->runtime, pos);499	count = bytes_to_frames(subs->runtime, count);500	LOOP_WRITE(rec, pos, src, count);501	return 0;502}503 504static int emu8k_pcm_silence(struct snd_pcm_substream *subs,505			     int voice, unsigned long pos, unsigned long count)506{507	struct snd_emu8k_pcm *rec = subs->runtime->private_data;508 509	/* convert to frames */510	pos = bytes_to_frames(subs->runtime, pos);511	count = bytes_to_frames(subs->runtime, count);512	LOOP_WRITE(rec, pos, NULL, count);513	return 0;514}515#endif516 517 518/*519 * allocate a memory block520 */521static int emu8k_pcm_hw_params(struct snd_pcm_substream *subs,522			       struct snd_pcm_hw_params *hw_params)523{524	struct snd_emu8k_pcm *rec = subs->runtime->private_data;525 526	if (rec->block) {527		/* reallocation - release the old block */528		snd_util_mem_free(rec->emu->memhdr, rec->block);529		rec->block = NULL;530	}531 532	rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4;533	rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes);534	if (! rec->block)535		return -ENOMEM;536	rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */537	/* at least dma_bytes must be set for non-interleaved mode */538	subs->dma_buffer.bytes = params_buffer_bytes(hw_params);539 540	return 0;541}542 543/*544 * free the memory block545 */546static int emu8k_pcm_hw_free(struct snd_pcm_substream *subs)547{548	struct snd_emu8k_pcm *rec = subs->runtime->private_data;549 550	if (rec->block) {551		int ch;552		for (ch = 0; ch < rec->voices; ch++)553			stop_voice(rec, ch); // to be sure554		if (rec->dram_opened)555			emu8k_close_dram(rec->emu);556		snd_util_mem_free(rec->emu->memhdr, rec->block);557		rec->block = NULL;558	}559	return 0;560}561 562/*563 */564static int emu8k_pcm_prepare(struct snd_pcm_substream *subs)565{566	struct snd_emu8k_pcm *rec = subs->runtime->private_data;567 568	rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate);569	rec->last_ptr = 0;570	rec->period_pos = 0;571 572	rec->buf_size = subs->runtime->buffer_size;573	rec->period_size = subs->runtime->period_size;574	rec->voices = subs->runtime->channels;575	rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE;576	if (rec->voices > 1)577		rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE;578	if (rec->voices > 1) {579		rec->panning[0] = 0xff;580		rec->panning[1] = 0x00;581	} else582		rec->panning[0] = 0x80;583 584	if (! rec->dram_opened) {585		int err, i, ch;586 587		snd_emux_terminate_all(rec->emu->emu);588		err = emu8k_open_dram_for_pcm(rec->emu, rec->voices);589		if (err)590			return err;591		rec->dram_opened = 1;592 593		/* clear loop blanks */594		snd_emu8000_write_wait(rec->emu, 0);595		EMU8000_SMALW_WRITE(rec->emu, rec->offset);596		for (i = 0; i < LOOP_BLANK_SIZE; i++)597			EMU8000_SMLD_WRITE(rec->emu, 0);598		for (ch = 0; ch < rec->voices; ch++) {599			EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size);600			for (i = 0; i < LOOP_BLANK_SIZE; i++)601				EMU8000_SMLD_WRITE(rec->emu, 0);602		}603	}604 605	setup_voice(rec, 0);606	if (rec->voices > 1)607		setup_voice(rec, 1);608	return 0;609}610 611static snd_pcm_uframes_t emu8k_pcm_pointer(struct snd_pcm_substream *subs)612{613	struct snd_emu8k_pcm *rec = subs->runtime->private_data;614	if (rec->running)615		return emu8k_get_curpos(rec, 0);616	return 0;617}618 619 620static const struct snd_pcm_ops emu8k_pcm_ops = {621	.open =		emu8k_pcm_open,622	.close =	emu8k_pcm_close,623	.hw_params =	emu8k_pcm_hw_params,624	.hw_free =	emu8k_pcm_hw_free,625	.prepare =	emu8k_pcm_prepare,626	.trigger =	emu8k_pcm_trigger,627	.pointer =	emu8k_pcm_pointer,628	.copy =		emu8k_pcm_copy,629	.fill_silence =	emu8k_pcm_silence,630};631 632 633static void snd_emu8000_pcm_free(struct snd_pcm *pcm)634{635	struct snd_emu8000 *emu = pcm->private_data;636	emu->pcm = NULL;637}638 639int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index)640{641	struct snd_pcm *pcm;642	int err;643 644	err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm);645	if (err < 0)646		return err;647	pcm->private_data = emu;648	pcm->private_free = snd_emu8000_pcm_free;649	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops);650	emu->pcm = pcm;651 652	snd_device_register(card, pcm);653 654	return 0;655}656