162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * pcm emulation on emu8000 wavetable
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2002 Takashi Iwai <tiwai@suse.de>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include "emu8000_local.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/sched/signal.h>
1162306a36Sopenharmony_ci#include <linux/init.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <sound/initval.h>
1462306a36Sopenharmony_ci#include <sound/pcm.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/*
1762306a36Sopenharmony_ci * define the following if you want to use this pcm with non-interleaved mode
1862306a36Sopenharmony_ci */
1962306a36Sopenharmony_ci/* #define USE_NONINTERLEAVE */
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set
2262306a36Sopenharmony_ci * mmap_emulation flag to 1 in your .asoundrc, such like
2362306a36Sopenharmony_ci *
2462306a36Sopenharmony_ci *	pcm.emu8k {
2562306a36Sopenharmony_ci *		type plug
2662306a36Sopenharmony_ci *		slave.pcm {
2762306a36Sopenharmony_ci *			type hw
2862306a36Sopenharmony_ci *			card 0
2962306a36Sopenharmony_ci *			device 1
3062306a36Sopenharmony_ci *			mmap_emulation 1
3162306a36Sopenharmony_ci *		}
3262306a36Sopenharmony_ci *	}
3362306a36Sopenharmony_ci *
3462306a36Sopenharmony_ci * besides, for the time being, the non-interleaved mode doesn't work well on
3562306a36Sopenharmony_ci * alsa-lib...
3662306a36Sopenharmony_ci */
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistruct snd_emu8k_pcm {
4062306a36Sopenharmony_ci	struct snd_emu8000 *emu;
4162306a36Sopenharmony_ci	struct snd_pcm_substream *substream;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	unsigned int allocated_bytes;
4462306a36Sopenharmony_ci	struct snd_util_memblk *block;
4562306a36Sopenharmony_ci	unsigned int offset;
4662306a36Sopenharmony_ci	unsigned int buf_size;
4762306a36Sopenharmony_ci	unsigned int period_size;
4862306a36Sopenharmony_ci	unsigned int loop_start[2];
4962306a36Sopenharmony_ci	unsigned int pitch;
5062306a36Sopenharmony_ci	int panning[2];
5162306a36Sopenharmony_ci	int last_ptr;
5262306a36Sopenharmony_ci	int period_pos;
5362306a36Sopenharmony_ci	int voices;
5462306a36Sopenharmony_ci	unsigned int dram_opened: 1;
5562306a36Sopenharmony_ci	unsigned int running: 1;
5662306a36Sopenharmony_ci	unsigned int timer_running: 1;
5762306a36Sopenharmony_ci	struct timer_list timer;
5862306a36Sopenharmony_ci	spinlock_t timer_lock;
5962306a36Sopenharmony_ci};
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci#define LOOP_BLANK_SIZE		8
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci/*
6562306a36Sopenharmony_ci * open up channels for the simultaneous data transfer and playback
6662306a36Sopenharmony_ci */
6762306a36Sopenharmony_cistatic int
6862306a36Sopenharmony_ciemu8k_open_dram_for_pcm(struct snd_emu8000 *emu, int channels)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	int i;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/* reserve up to 2 voices for playback */
7362306a36Sopenharmony_ci	snd_emux_lock_voice(emu->emu, 0);
7462306a36Sopenharmony_ci	if (channels > 1)
7562306a36Sopenharmony_ci		snd_emux_lock_voice(emu->emu, 1);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	/* reserve 28 voices for loading */
7862306a36Sopenharmony_ci	for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) {
7962306a36Sopenharmony_ci		unsigned int mode = EMU8000_RAM_WRITE;
8062306a36Sopenharmony_ci		snd_emux_lock_voice(emu->emu, i);
8162306a36Sopenharmony_ci#ifndef USE_NONINTERLEAVE
8262306a36Sopenharmony_ci		if (channels > 1 && (i & 1) != 0)
8362306a36Sopenharmony_ci			mode |= EMU8000_RAM_RIGHT;
8462306a36Sopenharmony_ci#endif
8562306a36Sopenharmony_ci		snd_emu8000_dma_chan(emu, i, mode);
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	/* assign voice 31 and 32 to ROM */
8962306a36Sopenharmony_ci	EMU8000_VTFT_WRITE(emu, 30, 0);
9062306a36Sopenharmony_ci	EMU8000_PSST_WRITE(emu, 30, 0x1d8);
9162306a36Sopenharmony_ci	EMU8000_CSL_WRITE(emu, 30, 0x1e0);
9262306a36Sopenharmony_ci	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
9362306a36Sopenharmony_ci	EMU8000_VTFT_WRITE(emu, 31, 0);
9462306a36Sopenharmony_ci	EMU8000_PSST_WRITE(emu, 31, 0x1d8);
9562306a36Sopenharmony_ci	EMU8000_CSL_WRITE(emu, 31, 0x1e0);
9662306a36Sopenharmony_ci	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	return 0;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci/*
10262306a36Sopenharmony_ci */
10362306a36Sopenharmony_cistatic void
10462306a36Sopenharmony_cisnd_emu8000_write_wait(struct snd_emu8000 *emu, int can_schedule)
10562306a36Sopenharmony_ci{
10662306a36Sopenharmony_ci	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
10762306a36Sopenharmony_ci		if (can_schedule) {
10862306a36Sopenharmony_ci			schedule_timeout_interruptible(1);
10962306a36Sopenharmony_ci			if (signal_pending(current))
11062306a36Sopenharmony_ci				break;
11162306a36Sopenharmony_ci		}
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci/*
11662306a36Sopenharmony_ci * close all channels
11762306a36Sopenharmony_ci */
11862306a36Sopenharmony_cistatic void
11962306a36Sopenharmony_ciemu8k_close_dram(struct snd_emu8000 *emu)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	int i;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	for (i = 0; i < 2; i++)
12462306a36Sopenharmony_ci		snd_emux_unlock_voice(emu->emu, i);
12562306a36Sopenharmony_ci	for (; i < EMU8000_DRAM_VOICES; i++) {
12662306a36Sopenharmony_ci		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
12762306a36Sopenharmony_ci		snd_emux_unlock_voice(emu->emu, i);
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci/*
13262306a36Sopenharmony_ci * convert Hz to AWE32 rate offset (see emux/soundfont.c)
13362306a36Sopenharmony_ci */
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci#define OFFSET_SAMPLERATE	1011119		/* base = 44100 */
13662306a36Sopenharmony_ci#define SAMPLERATE_RATIO	4096
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic int calc_rate_offset(int hz)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO);
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci/*
14562306a36Sopenharmony_ci */
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic const struct snd_pcm_hardware emu8k_pcm_hw = {
14862306a36Sopenharmony_ci#ifdef USE_NONINTERLEAVE
14962306a36Sopenharmony_ci	.info =			SNDRV_PCM_INFO_NONINTERLEAVED,
15062306a36Sopenharmony_ci#else
15162306a36Sopenharmony_ci	.info =			SNDRV_PCM_INFO_INTERLEAVED,
15262306a36Sopenharmony_ci#endif
15362306a36Sopenharmony_ci	.formats =		SNDRV_PCM_FMTBIT_S16_LE,
15462306a36Sopenharmony_ci	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
15562306a36Sopenharmony_ci	.rate_min =		4000,
15662306a36Sopenharmony_ci	.rate_max =		48000,
15762306a36Sopenharmony_ci	.channels_min =		1,
15862306a36Sopenharmony_ci	.channels_max =		2,
15962306a36Sopenharmony_ci	.buffer_bytes_max =	(128*1024),
16062306a36Sopenharmony_ci	.period_bytes_min =	1024,
16162306a36Sopenharmony_ci	.period_bytes_max =	(128*1024),
16262306a36Sopenharmony_ci	.periods_min =		2,
16362306a36Sopenharmony_ci	.periods_max =		1024,
16462306a36Sopenharmony_ci	.fifo_size =		0,
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci};
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci/*
16962306a36Sopenharmony_ci * get the current position at the given channel from CCCA register
17062306a36Sopenharmony_ci */
17162306a36Sopenharmony_cistatic inline int emu8k_get_curpos(struct snd_emu8k_pcm *rec, int ch)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff;
17462306a36Sopenharmony_ci	val -= rec->loop_start[ch] - 1;
17562306a36Sopenharmony_ci	return val;
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci/*
18062306a36Sopenharmony_ci * timer interrupt handler
18162306a36Sopenharmony_ci * check the current position and update the period if necessary.
18262306a36Sopenharmony_ci */
18362306a36Sopenharmony_cistatic void emu8k_pcm_timer_func(struct timer_list *t)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = from_timer(rec, t, timer);
18662306a36Sopenharmony_ci	int ptr, delta;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	spin_lock(&rec->timer_lock);
18962306a36Sopenharmony_ci	/* update the current pointer */
19062306a36Sopenharmony_ci	ptr = emu8k_get_curpos(rec, 0);
19162306a36Sopenharmony_ci	if (ptr < rec->last_ptr)
19262306a36Sopenharmony_ci		delta = ptr + rec->buf_size - rec->last_ptr;
19362306a36Sopenharmony_ci	else
19462306a36Sopenharmony_ci		delta = ptr - rec->last_ptr;
19562306a36Sopenharmony_ci	rec->period_pos += delta;
19662306a36Sopenharmony_ci	rec->last_ptr = ptr;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	/* reprogram timer */
19962306a36Sopenharmony_ci	mod_timer(&rec->timer, jiffies + 1);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	/* update period */
20262306a36Sopenharmony_ci	if (rec->period_pos >= (int)rec->period_size) {
20362306a36Sopenharmony_ci		rec->period_pos %= rec->period_size;
20462306a36Sopenharmony_ci		spin_unlock(&rec->timer_lock);
20562306a36Sopenharmony_ci		snd_pcm_period_elapsed(rec->substream);
20662306a36Sopenharmony_ci		return;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci	spin_unlock(&rec->timer_lock);
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci/*
21362306a36Sopenharmony_ci * open pcm
21462306a36Sopenharmony_ci * creating an instance here
21562306a36Sopenharmony_ci */
21662306a36Sopenharmony_cistatic int emu8k_pcm_open(struct snd_pcm_substream *subs)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	struct snd_emu8000 *emu = snd_pcm_substream_chip(subs);
21962306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec;
22062306a36Sopenharmony_ci	struct snd_pcm_runtime *runtime = subs->runtime;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	rec = kzalloc(sizeof(*rec), GFP_KERNEL);
22362306a36Sopenharmony_ci	if (! rec)
22462306a36Sopenharmony_ci		return -ENOMEM;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	rec->emu = emu;
22762306a36Sopenharmony_ci	rec->substream = subs;
22862306a36Sopenharmony_ci	runtime->private_data = rec;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	spin_lock_init(&rec->timer_lock);
23162306a36Sopenharmony_ci	timer_setup(&rec->timer, emu8k_pcm_timer_func, 0);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	runtime->hw = emu8k_pcm_hw;
23462306a36Sopenharmony_ci	runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3;
23562306a36Sopenharmony_ci	runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	/* use timer to update periods.. (specified in msec) */
23862306a36Sopenharmony_ci	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
23962306a36Sopenharmony_ci				     DIV_ROUND_UP(1000000, HZ), UINT_MAX);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	return 0;
24262306a36Sopenharmony_ci}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_cistatic int emu8k_pcm_close(struct snd_pcm_substream *subs)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
24762306a36Sopenharmony_ci	kfree(rec);
24862306a36Sopenharmony_ci	subs->runtime->private_data = NULL;
24962306a36Sopenharmony_ci	return 0;
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci/*
25362306a36Sopenharmony_ci * calculate pitch target
25462306a36Sopenharmony_ci */
25562306a36Sopenharmony_cistatic int calc_pitch_target(int pitch)
25662306a36Sopenharmony_ci{
25762306a36Sopenharmony_ci	int ptarget = 1 << (pitch >> 12);
25862306a36Sopenharmony_ci	if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710;
25962306a36Sopenharmony_ci	if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710;
26062306a36Sopenharmony_ci	if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710;
26162306a36Sopenharmony_ci	ptarget += (ptarget >> 1);
26262306a36Sopenharmony_ci	if (ptarget > 0xffff) ptarget = 0xffff;
26362306a36Sopenharmony_ci	return ptarget;
26462306a36Sopenharmony_ci}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci/*
26762306a36Sopenharmony_ci * set up the voice
26862306a36Sopenharmony_ci */
26962306a36Sopenharmony_cistatic void setup_voice(struct snd_emu8k_pcm *rec, int ch)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	struct snd_emu8000 *hw = rec->emu;
27262306a36Sopenharmony_ci	unsigned int temp;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	/* channel to be silent and idle */
27562306a36Sopenharmony_ci	EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080);
27662306a36Sopenharmony_ci	EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF);
27762306a36Sopenharmony_ci	EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF);
27862306a36Sopenharmony_ci	EMU8000_PTRX_WRITE(hw, ch, 0);
27962306a36Sopenharmony_ci	EMU8000_CPF_WRITE(hw, ch, 0);
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	/* pitch offset */
28262306a36Sopenharmony_ci	EMU8000_IP_WRITE(hw, ch, rec->pitch);
28362306a36Sopenharmony_ci	/* set envelope parameters */
28462306a36Sopenharmony_ci	EMU8000_ENVVAL_WRITE(hw, ch, 0x8000);
28562306a36Sopenharmony_ci	EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f);
28662306a36Sopenharmony_ci	EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f);
28762306a36Sopenharmony_ci	EMU8000_ENVVOL_WRITE(hw, ch, 0x8000);
28862306a36Sopenharmony_ci	EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f);
28962306a36Sopenharmony_ci	/* decay/sustain parameter for volume envelope is used
29062306a36Sopenharmony_ci	   for triggerg the voice */
29162306a36Sopenharmony_ci	/* modulation envelope heights */
29262306a36Sopenharmony_ci	EMU8000_PEFE_WRITE(hw, ch, 0x0);
29362306a36Sopenharmony_ci	/* lfo1/2 delay */
29462306a36Sopenharmony_ci	EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000);
29562306a36Sopenharmony_ci	EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000);
29662306a36Sopenharmony_ci	/* lfo1 pitch & cutoff shift */
29762306a36Sopenharmony_ci	EMU8000_FMMOD_WRITE(hw, ch, 0);
29862306a36Sopenharmony_ci	/* lfo1 volume & freq */
29962306a36Sopenharmony_ci	EMU8000_TREMFRQ_WRITE(hw, ch, 0);
30062306a36Sopenharmony_ci	/* lfo2 pitch & freq */
30162306a36Sopenharmony_ci	EMU8000_FM2FRQ2_WRITE(hw, ch, 0);
30262306a36Sopenharmony_ci	/* pan & loop start */
30362306a36Sopenharmony_ci	temp = rec->panning[ch];
30462306a36Sopenharmony_ci	temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1);
30562306a36Sopenharmony_ci	EMU8000_PSST_WRITE(hw, ch, temp);
30662306a36Sopenharmony_ci	/* chorus & loop end (chorus 8bit, MSB) */
30762306a36Sopenharmony_ci	temp = 0; // chorus
30862306a36Sopenharmony_ci	temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1);
30962306a36Sopenharmony_ci	EMU8000_CSL_WRITE(hw, ch, temp);
31062306a36Sopenharmony_ci	/* Q & current address (Q 4bit value, MSB) */
31162306a36Sopenharmony_ci	temp = 0; // filterQ
31262306a36Sopenharmony_ci	temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1);
31362306a36Sopenharmony_ci	EMU8000_CCCA_WRITE(hw, ch, temp);
31462306a36Sopenharmony_ci	/* clear unknown registers */
31562306a36Sopenharmony_ci	EMU8000_00A0_WRITE(hw, ch, 0);
31662306a36Sopenharmony_ci	EMU8000_0080_WRITE(hw, ch, 0);
31762306a36Sopenharmony_ci}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci/*
32062306a36Sopenharmony_ci * trigger the voice
32162306a36Sopenharmony_ci */
32262306a36Sopenharmony_cistatic void start_voice(struct snd_emu8k_pcm *rec, int ch)
32362306a36Sopenharmony_ci{
32462306a36Sopenharmony_ci	unsigned long flags;
32562306a36Sopenharmony_ci	struct snd_emu8000 *hw = rec->emu;
32662306a36Sopenharmony_ci	unsigned int temp, aux;
32762306a36Sopenharmony_ci	int pt = calc_pitch_target(rec->pitch);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	/* cutoff and volume */
33062306a36Sopenharmony_ci	EMU8000_IFATN_WRITE(hw, ch, 0xff00);
33162306a36Sopenharmony_ci	EMU8000_VTFT_WRITE(hw, ch, 0xffff);
33262306a36Sopenharmony_ci	EMU8000_CVCF_WRITE(hw, ch, 0xffff);
33362306a36Sopenharmony_ci	/* trigger envelope */
33462306a36Sopenharmony_ci	EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f);
33562306a36Sopenharmony_ci	/* set reverb and pitch target */
33662306a36Sopenharmony_ci	temp = 0; // reverb
33762306a36Sopenharmony_ci	if (rec->panning[ch] == 0)
33862306a36Sopenharmony_ci		aux = 0xff;
33962306a36Sopenharmony_ci	else
34062306a36Sopenharmony_ci		aux = (-rec->panning[ch]) & 0xff;
34162306a36Sopenharmony_ci	temp = (temp << 8) | (pt << 16) | aux;
34262306a36Sopenharmony_ci	EMU8000_PTRX_WRITE(hw, ch, temp);
34362306a36Sopenharmony_ci	EMU8000_CPF_WRITE(hw, ch, pt << 16);
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	/* start timer */
34662306a36Sopenharmony_ci	spin_lock_irqsave(&rec->timer_lock, flags);
34762306a36Sopenharmony_ci	if (! rec->timer_running) {
34862306a36Sopenharmony_ci		mod_timer(&rec->timer, jiffies + 1);
34962306a36Sopenharmony_ci		rec->timer_running = 1;
35062306a36Sopenharmony_ci	}
35162306a36Sopenharmony_ci	spin_unlock_irqrestore(&rec->timer_lock, flags);
35262306a36Sopenharmony_ci}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci/*
35562306a36Sopenharmony_ci * stop the voice immediately
35662306a36Sopenharmony_ci */
35762306a36Sopenharmony_cistatic void stop_voice(struct snd_emu8k_pcm *rec, int ch)
35862306a36Sopenharmony_ci{
35962306a36Sopenharmony_ci	unsigned long flags;
36062306a36Sopenharmony_ci	struct snd_emu8000 *hw = rec->emu;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F);
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	/* stop timer */
36562306a36Sopenharmony_ci	spin_lock_irqsave(&rec->timer_lock, flags);
36662306a36Sopenharmony_ci	if (rec->timer_running) {
36762306a36Sopenharmony_ci		del_timer(&rec->timer);
36862306a36Sopenharmony_ci		rec->timer_running = 0;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci	spin_unlock_irqrestore(&rec->timer_lock, flags);
37162306a36Sopenharmony_ci}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_cistatic int emu8k_pcm_trigger(struct snd_pcm_substream *subs, int cmd)
37462306a36Sopenharmony_ci{
37562306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
37662306a36Sopenharmony_ci	int ch;
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	switch (cmd) {
37962306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_START:
38062306a36Sopenharmony_ci		for (ch = 0; ch < rec->voices; ch++)
38162306a36Sopenharmony_ci			start_voice(rec, ch);
38262306a36Sopenharmony_ci		rec->running = 1;
38362306a36Sopenharmony_ci		break;
38462306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_STOP:
38562306a36Sopenharmony_ci		rec->running = 0;
38662306a36Sopenharmony_ci		for (ch = 0; ch < rec->voices; ch++)
38762306a36Sopenharmony_ci			stop_voice(rec, ch);
38862306a36Sopenharmony_ci		break;
38962306a36Sopenharmony_ci	default:
39062306a36Sopenharmony_ci		return -EINVAL;
39162306a36Sopenharmony_ci	}
39262306a36Sopenharmony_ci	return 0;
39362306a36Sopenharmony_ci}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci/*
39762306a36Sopenharmony_ci * copy / silence ops
39862306a36Sopenharmony_ci */
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci/*
40162306a36Sopenharmony_ci * this macro should be inserted in the copy/silence loops
40262306a36Sopenharmony_ci * to reduce the latency.  without this, the system will hang up
40362306a36Sopenharmony_ci * during the whole loop.
40462306a36Sopenharmony_ci */
40562306a36Sopenharmony_ci#define CHECK_SCHEDULER() \
40662306a36Sopenharmony_cido { \
40762306a36Sopenharmony_ci	cond_resched();\
40862306a36Sopenharmony_ci	if (signal_pending(current))\
40962306a36Sopenharmony_ci		return -EAGAIN;\
41062306a36Sopenharmony_ci} while (0)
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci#define GET_VAL(sval, iter)						\
41362306a36Sopenharmony_ci	do {								\
41462306a36Sopenharmony_ci		if (!iter)						\
41562306a36Sopenharmony_ci			sval = 0;					\
41662306a36Sopenharmony_ci		else if (copy_from_iter(&sval, 2, iter) != 2)		\
41762306a36Sopenharmony_ci			return -EFAULT;					\
41862306a36Sopenharmony_ci	} while (0)
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci#ifdef USE_NONINTERLEAVE
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci#define LOOP_WRITE(rec, offset, iter, count)			\
42362306a36Sopenharmony_ci	do {							\
42462306a36Sopenharmony_ci		struct snd_emu8000 *emu = (rec)->emu;		\
42562306a36Sopenharmony_ci		snd_emu8000_write_wait(emu, 1);			\
42662306a36Sopenharmony_ci		EMU8000_SMALW_WRITE(emu, offset);		\
42762306a36Sopenharmony_ci		while (count > 0) {				\
42862306a36Sopenharmony_ci			unsigned short sval;			\
42962306a36Sopenharmony_ci			CHECK_SCHEDULER();			\
43062306a36Sopenharmony_ci			GET_VAL(sval, iter);			\
43162306a36Sopenharmony_ci			EMU8000_SMLD_WRITE(emu, sval);		\
43262306a36Sopenharmony_ci			count--;				\
43362306a36Sopenharmony_ci		}						\
43462306a36Sopenharmony_ci	} while (0)
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci/* copy one channel block */
43762306a36Sopenharmony_cistatic int emu8k_pcm_copy(struct snd_pcm_substream *subs,
43862306a36Sopenharmony_ci			  int voice, unsigned long pos,
43962306a36Sopenharmony_ci			  struct iov_iter *src, unsigned long count)
44062306a36Sopenharmony_ci{
44162306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	/* convert to word unit */
44462306a36Sopenharmony_ci	pos = (pos << 1) + rec->loop_start[voice];
44562306a36Sopenharmony_ci	count <<= 1;
44662306a36Sopenharmony_ci	LOOP_WRITE(rec, pos, src, count);
44762306a36Sopenharmony_ci	return 0;
44862306a36Sopenharmony_ci}
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci/* make a channel block silence */
45162306a36Sopenharmony_cistatic int emu8k_pcm_silence(struct snd_pcm_substream *subs,
45262306a36Sopenharmony_ci			     int voice, unsigned long pos, unsigned long count)
45362306a36Sopenharmony_ci{
45462306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	/* convert to word unit */
45762306a36Sopenharmony_ci	pos = (pos << 1) + rec->loop_start[voice];
45862306a36Sopenharmony_ci	count <<= 1;
45962306a36Sopenharmony_ci	LOOP_WRITE(rec, pos, NULL, count);
46062306a36Sopenharmony_ci	return 0;
46162306a36Sopenharmony_ci}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci#else /* interleave */
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci#define LOOP_WRITE(rec, pos, iter, count)				\
46662306a36Sopenharmony_ci	do {								\
46762306a36Sopenharmony_ci		struct snd_emu8000 *emu = rec->emu;			\
46862306a36Sopenharmony_ci		snd_emu8000_write_wait(emu, 1);				\
46962306a36Sopenharmony_ci		EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]);	\
47062306a36Sopenharmony_ci		if (rec->voices > 1)					\
47162306a36Sopenharmony_ci			EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]); \
47262306a36Sopenharmony_ci		while (count > 0) {					\
47362306a36Sopenharmony_ci			unsigned short sval;				\
47462306a36Sopenharmony_ci			CHECK_SCHEDULER();				\
47562306a36Sopenharmony_ci			GET_VAL(sval, iter);				\
47662306a36Sopenharmony_ci			EMU8000_SMLD_WRITE(emu, sval);			\
47762306a36Sopenharmony_ci			if (rec->voices > 1) {				\
47862306a36Sopenharmony_ci				CHECK_SCHEDULER();			\
47962306a36Sopenharmony_ci				GET_VAL(sval, iter);			\
48062306a36Sopenharmony_ci				EMU8000_SMRD_WRITE(emu, sval);		\
48162306a36Sopenharmony_ci			}						\
48262306a36Sopenharmony_ci			count--;					\
48362306a36Sopenharmony_ci		}							\
48462306a36Sopenharmony_ci	} while (0)
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci/*
48862306a36Sopenharmony_ci * copy the interleaved data can be done easily by using
48962306a36Sopenharmony_ci * DMA "left" and "right" channels on emu8k engine.
49062306a36Sopenharmony_ci */
49162306a36Sopenharmony_cistatic int emu8k_pcm_copy(struct snd_pcm_substream *subs,
49262306a36Sopenharmony_ci			  int voice, unsigned long pos,
49362306a36Sopenharmony_ci			  struct iov_iter *src, unsigned long count)
49462306a36Sopenharmony_ci{
49562306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	/* convert to frames */
49862306a36Sopenharmony_ci	pos = bytes_to_frames(subs->runtime, pos);
49962306a36Sopenharmony_ci	count = bytes_to_frames(subs->runtime, count);
50062306a36Sopenharmony_ci	LOOP_WRITE(rec, pos, src, count);
50162306a36Sopenharmony_ci	return 0;
50262306a36Sopenharmony_ci}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_cistatic int emu8k_pcm_silence(struct snd_pcm_substream *subs,
50562306a36Sopenharmony_ci			     int voice, unsigned long pos, unsigned long count)
50662306a36Sopenharmony_ci{
50762306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	/* convert to frames */
51062306a36Sopenharmony_ci	pos = bytes_to_frames(subs->runtime, pos);
51162306a36Sopenharmony_ci	count = bytes_to_frames(subs->runtime, count);
51262306a36Sopenharmony_ci	LOOP_WRITE(rec, pos, NULL, count);
51362306a36Sopenharmony_ci	return 0;
51462306a36Sopenharmony_ci}
51562306a36Sopenharmony_ci#endif
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci/*
51962306a36Sopenharmony_ci * allocate a memory block
52062306a36Sopenharmony_ci */
52162306a36Sopenharmony_cistatic int emu8k_pcm_hw_params(struct snd_pcm_substream *subs,
52262306a36Sopenharmony_ci			       struct snd_pcm_hw_params *hw_params)
52362306a36Sopenharmony_ci{
52462306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	if (rec->block) {
52762306a36Sopenharmony_ci		/* reallocation - release the old block */
52862306a36Sopenharmony_ci		snd_util_mem_free(rec->emu->memhdr, rec->block);
52962306a36Sopenharmony_ci		rec->block = NULL;
53062306a36Sopenharmony_ci	}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4;
53362306a36Sopenharmony_ci	rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes);
53462306a36Sopenharmony_ci	if (! rec->block)
53562306a36Sopenharmony_ci		return -ENOMEM;
53662306a36Sopenharmony_ci	rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */
53762306a36Sopenharmony_ci	/* at least dma_bytes must be set for non-interleaved mode */
53862306a36Sopenharmony_ci	subs->dma_buffer.bytes = params_buffer_bytes(hw_params);
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	return 0;
54162306a36Sopenharmony_ci}
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci/*
54462306a36Sopenharmony_ci * free the memory block
54562306a36Sopenharmony_ci */
54662306a36Sopenharmony_cistatic int emu8k_pcm_hw_free(struct snd_pcm_substream *subs)
54762306a36Sopenharmony_ci{
54862306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	if (rec->block) {
55162306a36Sopenharmony_ci		int ch;
55262306a36Sopenharmony_ci		for (ch = 0; ch < rec->voices; ch++)
55362306a36Sopenharmony_ci			stop_voice(rec, ch); // to be sure
55462306a36Sopenharmony_ci		if (rec->dram_opened)
55562306a36Sopenharmony_ci			emu8k_close_dram(rec->emu);
55662306a36Sopenharmony_ci		snd_util_mem_free(rec->emu->memhdr, rec->block);
55762306a36Sopenharmony_ci		rec->block = NULL;
55862306a36Sopenharmony_ci	}
55962306a36Sopenharmony_ci	return 0;
56062306a36Sopenharmony_ci}
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci/*
56362306a36Sopenharmony_ci */
56462306a36Sopenharmony_cistatic int emu8k_pcm_prepare(struct snd_pcm_substream *subs)
56562306a36Sopenharmony_ci{
56662306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate);
56962306a36Sopenharmony_ci	rec->last_ptr = 0;
57062306a36Sopenharmony_ci	rec->period_pos = 0;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	rec->buf_size = subs->runtime->buffer_size;
57362306a36Sopenharmony_ci	rec->period_size = subs->runtime->period_size;
57462306a36Sopenharmony_ci	rec->voices = subs->runtime->channels;
57562306a36Sopenharmony_ci	rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE;
57662306a36Sopenharmony_ci	if (rec->voices > 1)
57762306a36Sopenharmony_ci		rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE;
57862306a36Sopenharmony_ci	if (rec->voices > 1) {
57962306a36Sopenharmony_ci		rec->panning[0] = 0xff;
58062306a36Sopenharmony_ci		rec->panning[1] = 0x00;
58162306a36Sopenharmony_ci	} else
58262306a36Sopenharmony_ci		rec->panning[0] = 0x80;
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	if (! rec->dram_opened) {
58562306a36Sopenharmony_ci		int err, i, ch;
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci		snd_emux_terminate_all(rec->emu->emu);
58862306a36Sopenharmony_ci		err = emu8k_open_dram_for_pcm(rec->emu, rec->voices);
58962306a36Sopenharmony_ci		if (err)
59062306a36Sopenharmony_ci			return err;
59162306a36Sopenharmony_ci		rec->dram_opened = 1;
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci		/* clear loop blanks */
59462306a36Sopenharmony_ci		snd_emu8000_write_wait(rec->emu, 0);
59562306a36Sopenharmony_ci		EMU8000_SMALW_WRITE(rec->emu, rec->offset);
59662306a36Sopenharmony_ci		for (i = 0; i < LOOP_BLANK_SIZE; i++)
59762306a36Sopenharmony_ci			EMU8000_SMLD_WRITE(rec->emu, 0);
59862306a36Sopenharmony_ci		for (ch = 0; ch < rec->voices; ch++) {
59962306a36Sopenharmony_ci			EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size);
60062306a36Sopenharmony_ci			for (i = 0; i < LOOP_BLANK_SIZE; i++)
60162306a36Sopenharmony_ci				EMU8000_SMLD_WRITE(rec->emu, 0);
60262306a36Sopenharmony_ci		}
60362306a36Sopenharmony_ci	}
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	setup_voice(rec, 0);
60662306a36Sopenharmony_ci	if (rec->voices > 1)
60762306a36Sopenharmony_ci		setup_voice(rec, 1);
60862306a36Sopenharmony_ci	return 0;
60962306a36Sopenharmony_ci}
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_cistatic snd_pcm_uframes_t emu8k_pcm_pointer(struct snd_pcm_substream *subs)
61262306a36Sopenharmony_ci{
61362306a36Sopenharmony_ci	struct snd_emu8k_pcm *rec = subs->runtime->private_data;
61462306a36Sopenharmony_ci	if (rec->running)
61562306a36Sopenharmony_ci		return emu8k_get_curpos(rec, 0);
61662306a36Sopenharmony_ci	return 0;
61762306a36Sopenharmony_ci}
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_cistatic const struct snd_pcm_ops emu8k_pcm_ops = {
62162306a36Sopenharmony_ci	.open =		emu8k_pcm_open,
62262306a36Sopenharmony_ci	.close =	emu8k_pcm_close,
62362306a36Sopenharmony_ci	.hw_params =	emu8k_pcm_hw_params,
62462306a36Sopenharmony_ci	.hw_free =	emu8k_pcm_hw_free,
62562306a36Sopenharmony_ci	.prepare =	emu8k_pcm_prepare,
62662306a36Sopenharmony_ci	.trigger =	emu8k_pcm_trigger,
62762306a36Sopenharmony_ci	.pointer =	emu8k_pcm_pointer,
62862306a36Sopenharmony_ci	.copy =		emu8k_pcm_copy,
62962306a36Sopenharmony_ci	.fill_silence =	emu8k_pcm_silence,
63062306a36Sopenharmony_ci};
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_cistatic void snd_emu8000_pcm_free(struct snd_pcm *pcm)
63462306a36Sopenharmony_ci{
63562306a36Sopenharmony_ci	struct snd_emu8000 *emu = pcm->private_data;
63662306a36Sopenharmony_ci	emu->pcm = NULL;
63762306a36Sopenharmony_ci}
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ciint snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index)
64062306a36Sopenharmony_ci{
64162306a36Sopenharmony_ci	struct snd_pcm *pcm;
64262306a36Sopenharmony_ci	int err;
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm);
64562306a36Sopenharmony_ci	if (err < 0)
64662306a36Sopenharmony_ci		return err;
64762306a36Sopenharmony_ci	pcm->private_data = emu;
64862306a36Sopenharmony_ci	pcm->private_free = snd_emu8000_pcm_free;
64962306a36Sopenharmony_ci	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops);
65062306a36Sopenharmony_ci	emu->pcm = pcm;
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci	snd_device_register(card, pcm);
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	return 0;
65562306a36Sopenharmony_ci}
656