162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Patch routines for the emu8000 (AWE32/64)
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 1999 Steve Ratcliffe
662306a36Sopenharmony_ci *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include "emu8000_local.h"
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/sched/signal.h>
1262306a36Sopenharmony_ci#include <linux/uaccess.h>
1362306a36Sopenharmony_ci#include <linux/moduleparam.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistatic int emu8000_reset_addr;
1662306a36Sopenharmony_cimodule_param(emu8000_reset_addr, int, 0444);
1762306a36Sopenharmony_ciMODULE_PARM_DESC(emu8000_reset_addr, "reset write address at each time (makes slowdown)");
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/*
2162306a36Sopenharmony_ci * Open up channels.
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_cistatic int
2462306a36Sopenharmony_cisnd_emu8000_open_dma(struct snd_emu8000 *emu, int write)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	int i;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	/* reserve all 30 voices for loading */
2962306a36Sopenharmony_ci	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
3062306a36Sopenharmony_ci		snd_emux_lock_voice(emu->emu, i);
3162306a36Sopenharmony_ci		snd_emu8000_dma_chan(emu, i, write);
3262306a36Sopenharmony_ci	}
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	/* assign voice 31 and 32 to ROM */
3562306a36Sopenharmony_ci	EMU8000_VTFT_WRITE(emu, 30, 0);
3662306a36Sopenharmony_ci	EMU8000_PSST_WRITE(emu, 30, 0x1d8);
3762306a36Sopenharmony_ci	EMU8000_CSL_WRITE(emu, 30, 0x1e0);
3862306a36Sopenharmony_ci	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
3962306a36Sopenharmony_ci	EMU8000_VTFT_WRITE(emu, 31, 0);
4062306a36Sopenharmony_ci	EMU8000_PSST_WRITE(emu, 31, 0x1d8);
4162306a36Sopenharmony_ci	EMU8000_CSL_WRITE(emu, 31, 0x1e0);
4262306a36Sopenharmony_ci	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	return 0;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci/*
4862306a36Sopenharmony_ci * Close all dram channels.
4962306a36Sopenharmony_ci */
5062306a36Sopenharmony_cistatic void
5162306a36Sopenharmony_cisnd_emu8000_close_dma(struct snd_emu8000 *emu)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	int i;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
5662306a36Sopenharmony_ci		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
5762306a36Sopenharmony_ci		snd_emux_unlock_voice(emu->emu, i);
5862306a36Sopenharmony_ci	}
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci/*
6262306a36Sopenharmony_ci */
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci#define BLANK_LOOP_START	4
6562306a36Sopenharmony_ci#define BLANK_LOOP_END		8
6662306a36Sopenharmony_ci#define BLANK_LOOP_SIZE		12
6762306a36Sopenharmony_ci#define BLANK_HEAD_SIZE		48
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci/*
7062306a36Sopenharmony_ci * Read a word from userland, taking care of conversions from
7162306a36Sopenharmony_ci * 8bit samples etc.
7262306a36Sopenharmony_ci */
7362306a36Sopenharmony_cistatic unsigned short
7462306a36Sopenharmony_ciread_word(const void __user *buf, int offset, int mode)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	unsigned short c;
7762306a36Sopenharmony_ci	if (mode & SNDRV_SFNT_SAMPLE_8BITS) {
7862306a36Sopenharmony_ci		unsigned char cc;
7962306a36Sopenharmony_ci		get_user(cc, (unsigned char __user *)buf + offset);
8062306a36Sopenharmony_ci		c = cc << 8; /* convert 8bit -> 16bit */
8162306a36Sopenharmony_ci	} else {
8262306a36Sopenharmony_ci#ifdef SNDRV_LITTLE_ENDIAN
8362306a36Sopenharmony_ci		get_user(c, (unsigned short __user *)buf + offset);
8462306a36Sopenharmony_ci#else
8562306a36Sopenharmony_ci		unsigned short cc;
8662306a36Sopenharmony_ci		get_user(cc, (unsigned short __user *)buf + offset);
8762306a36Sopenharmony_ci		c = swab16(cc);
8862306a36Sopenharmony_ci#endif
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci	if (mode & SNDRV_SFNT_SAMPLE_UNSIGNED)
9162306a36Sopenharmony_ci		c ^= 0x8000; /* unsigned -> signed */
9262306a36Sopenharmony_ci	return c;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci/*
9662306a36Sopenharmony_ci */
9762306a36Sopenharmony_cistatic void
9862306a36Sopenharmony_cisnd_emu8000_write_wait(struct snd_emu8000 *emu)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
10162306a36Sopenharmony_ci		schedule_timeout_interruptible(1);
10262306a36Sopenharmony_ci		if (signal_pending(current))
10362306a36Sopenharmony_ci			break;
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci/*
10862306a36Sopenharmony_ci * write sample word data
10962306a36Sopenharmony_ci *
11062306a36Sopenharmony_ci * You should not have to keep resetting the address each time
11162306a36Sopenharmony_ci * as the chip is supposed to step on the next address automatically.
11262306a36Sopenharmony_ci * It mostly does, but during writes of some samples at random it
11362306a36Sopenharmony_ci * completely loses words (every one in 16 roughly but with no
11462306a36Sopenharmony_ci * obvious pattern).
11562306a36Sopenharmony_ci *
11662306a36Sopenharmony_ci * This is therefore much slower than need be, but is at least
11762306a36Sopenharmony_ci * working.
11862306a36Sopenharmony_ci */
11962306a36Sopenharmony_cistatic inline void
12062306a36Sopenharmony_ciwrite_word(struct snd_emu8000 *emu, int *offset, unsigned short data)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	if (emu8000_reset_addr) {
12362306a36Sopenharmony_ci		if (emu8000_reset_addr > 1)
12462306a36Sopenharmony_ci			snd_emu8000_write_wait(emu);
12562306a36Sopenharmony_ci		EMU8000_SMALW_WRITE(emu, *offset);
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci	EMU8000_SMLD_WRITE(emu, data);
12862306a36Sopenharmony_ci	*offset += 1;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci/*
13262306a36Sopenharmony_ci * Write the sample to EMU800 memory.  This routine is invoked out of
13362306a36Sopenharmony_ci * the generic soundfont routines as a callback.
13462306a36Sopenharmony_ci */
13562306a36Sopenharmony_ciint
13662306a36Sopenharmony_cisnd_emu8000_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
13762306a36Sopenharmony_ci		       struct snd_util_memhdr *hdr,
13862306a36Sopenharmony_ci		       const void __user *data, long count)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	int  i;
14162306a36Sopenharmony_ci	int  rc;
14262306a36Sopenharmony_ci	int  offset;
14362306a36Sopenharmony_ci	int  truesize;
14462306a36Sopenharmony_ci	int  dram_offset, dram_start;
14562306a36Sopenharmony_ci	struct snd_emu8000 *emu;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	emu = rec->hw;
14862306a36Sopenharmony_ci	if (snd_BUG_ON(!sp))
14962306a36Sopenharmony_ci		return -EINVAL;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	if (sp->v.size == 0)
15262306a36Sopenharmony_ci		return 0;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	/* be sure loop points start < end */
15562306a36Sopenharmony_ci	if (sp->v.loopstart > sp->v.loopend)
15662306a36Sopenharmony_ci		swap(sp->v.loopstart, sp->v.loopend);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	/* compute true data size to be loaded */
15962306a36Sopenharmony_ci	truesize = sp->v.size;
16062306a36Sopenharmony_ci	if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
16162306a36Sopenharmony_ci		truesize += sp->v.loopend - sp->v.loopstart;
16262306a36Sopenharmony_ci	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
16362306a36Sopenharmony_ci		truesize += BLANK_LOOP_SIZE;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	sp->block = snd_util_mem_alloc(hdr, truesize * 2);
16662306a36Sopenharmony_ci	if (sp->block == NULL) {
16762306a36Sopenharmony_ci		/*snd_printd("EMU8000: out of memory\n");*/
16862306a36Sopenharmony_ci		/* not ENOMEM (for compatibility) */
16962306a36Sopenharmony_ci		return -ENOSPC;
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) {
17362306a36Sopenharmony_ci		if (!access_ok(data, sp->v.size))
17462306a36Sopenharmony_ci			return -EFAULT;
17562306a36Sopenharmony_ci	} else {
17662306a36Sopenharmony_ci		if (!access_ok(data, sp->v.size * 2))
17762306a36Sopenharmony_ci			return -EFAULT;
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	/* recalculate address offset */
18162306a36Sopenharmony_ci	sp->v.end -= sp->v.start;
18262306a36Sopenharmony_ci	sp->v.loopstart -= sp->v.start;
18362306a36Sopenharmony_ci	sp->v.loopend -= sp->v.start;
18462306a36Sopenharmony_ci	sp->v.start = 0;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	/* dram position (in word) -- mem_offset is byte */
18762306a36Sopenharmony_ci	dram_offset = EMU8000_DRAM_OFFSET + (sp->block->offset >> 1);
18862306a36Sopenharmony_ci	dram_start = dram_offset;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	/* set the total size (store onto obsolete checksum value) */
19162306a36Sopenharmony_ci	sp->v.truesize = truesize * 2; /* in bytes */
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	snd_emux_terminate_all(emu->emu);
19462306a36Sopenharmony_ci	rc = snd_emu8000_open_dma(emu, EMU8000_RAM_WRITE);
19562306a36Sopenharmony_ci	if (rc)
19662306a36Sopenharmony_ci		return rc;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	/* Set the address to start writing at */
19962306a36Sopenharmony_ci	snd_emu8000_write_wait(emu);
20062306a36Sopenharmony_ci	EMU8000_SMALW_WRITE(emu, dram_offset);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	/*snd_emu8000_init_fm(emu);*/
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci#if 0
20562306a36Sopenharmony_ci	/* first block - write 48 samples for silence */
20662306a36Sopenharmony_ci	if (! sp->block->offset) {
20762306a36Sopenharmony_ci		for (i = 0; i < BLANK_HEAD_SIZE; i++) {
20862306a36Sopenharmony_ci			write_word(emu, &dram_offset, 0);
20962306a36Sopenharmony_ci		}
21062306a36Sopenharmony_ci	}
21162306a36Sopenharmony_ci#endif
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	offset = 0;
21462306a36Sopenharmony_ci	for (i = 0; i < sp->v.size; i++) {
21562306a36Sopenharmony_ci		unsigned short s;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci		s = read_word(data, offset, sp->v.mode_flags);
21862306a36Sopenharmony_ci		offset++;
21962306a36Sopenharmony_ci		write_word(emu, &dram_offset, s);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci		/* we may take too long time in this loop.
22262306a36Sopenharmony_ci		 * so give controls back to kernel if needed.
22362306a36Sopenharmony_ci		 */
22462306a36Sopenharmony_ci		cond_resched();
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci		if (i == sp->v.loopend &&
22762306a36Sopenharmony_ci		    (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)))
22862306a36Sopenharmony_ci		{
22962306a36Sopenharmony_ci			int looplen = sp->v.loopend - sp->v.loopstart;
23062306a36Sopenharmony_ci			int k;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci			/* copy reverse loop */
23362306a36Sopenharmony_ci			for (k = 1; k <= looplen; k++) {
23462306a36Sopenharmony_ci				s = read_word(data, offset - k, sp->v.mode_flags);
23562306a36Sopenharmony_ci				write_word(emu, &dram_offset, s);
23662306a36Sopenharmony_ci			}
23762306a36Sopenharmony_ci			if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
23862306a36Sopenharmony_ci				sp->v.loopend += looplen;
23962306a36Sopenharmony_ci			} else {
24062306a36Sopenharmony_ci				sp->v.loopstart += looplen;
24162306a36Sopenharmony_ci				sp->v.loopend += looplen;
24262306a36Sopenharmony_ci			}
24362306a36Sopenharmony_ci			sp->v.end += looplen;
24462306a36Sopenharmony_ci		}
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	/* if no blank loop is attached in the sample, add it */
24862306a36Sopenharmony_ci	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
24962306a36Sopenharmony_ci		for (i = 0; i < BLANK_LOOP_SIZE; i++) {
25062306a36Sopenharmony_ci			write_word(emu, &dram_offset, 0);
25162306a36Sopenharmony_ci		}
25262306a36Sopenharmony_ci		if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) {
25362306a36Sopenharmony_ci			sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
25462306a36Sopenharmony_ci			sp->v.loopend = sp->v.end + BLANK_LOOP_END;
25562306a36Sopenharmony_ci		}
25662306a36Sopenharmony_ci	}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	/* add dram offset */
25962306a36Sopenharmony_ci	sp->v.start += dram_start;
26062306a36Sopenharmony_ci	sp->v.end += dram_start;
26162306a36Sopenharmony_ci	sp->v.loopstart += dram_start;
26262306a36Sopenharmony_ci	sp->v.loopend += dram_start;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	snd_emu8000_close_dma(emu);
26562306a36Sopenharmony_ci	snd_emu8000_init_fm(emu);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	return 0;
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci/*
27162306a36Sopenharmony_ci * free a sample block
27262306a36Sopenharmony_ci */
27362306a36Sopenharmony_ciint
27462306a36Sopenharmony_cisnd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
27562306a36Sopenharmony_ci			struct snd_util_memhdr *hdr)
27662306a36Sopenharmony_ci{
27762306a36Sopenharmony_ci	if (sp->block) {
27862306a36Sopenharmony_ci		snd_util_mem_free(hdr, sp->block);
27962306a36Sopenharmony_ci		sp->block = NULL;
28062306a36Sopenharmony_ci	}
28162306a36Sopenharmony_ci	return 0;
28262306a36Sopenharmony_ci}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci/*
28662306a36Sopenharmony_ci * sample_reset callback - terminate voices
28762306a36Sopenharmony_ci */
28862306a36Sopenharmony_civoid
28962306a36Sopenharmony_cisnd_emu8000_sample_reset(struct snd_emux *rec)
29062306a36Sopenharmony_ci{
29162306a36Sopenharmony_ci	snd_emux_terminate_all(rec);
29262306a36Sopenharmony_ci}
293