18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Patch routines for the emu8000 (AWE32/64)
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 1999 Steve Ratcliffe
68c2ecf20Sopenharmony_ci *  Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include "emu8000_local.h"
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/sched/signal.h>
128c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
138c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_cistatic int emu8000_reset_addr;
168c2ecf20Sopenharmony_cimodule_param(emu8000_reset_addr, int, 0444);
178c2ecf20Sopenharmony_ciMODULE_PARM_DESC(emu8000_reset_addr, "reset write address at each time (makes slowdown)");
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci/*
218c2ecf20Sopenharmony_ci * Open up channels.
228c2ecf20Sopenharmony_ci */
238c2ecf20Sopenharmony_cistatic int
248c2ecf20Sopenharmony_cisnd_emu8000_open_dma(struct snd_emu8000 *emu, int write)
258c2ecf20Sopenharmony_ci{
268c2ecf20Sopenharmony_ci	int i;
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci	/* reserve all 30 voices for loading */
298c2ecf20Sopenharmony_ci	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
308c2ecf20Sopenharmony_ci		snd_emux_lock_voice(emu->emu, i);
318c2ecf20Sopenharmony_ci		snd_emu8000_dma_chan(emu, i, write);
328c2ecf20Sopenharmony_ci	}
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	/* assign voice 31 and 32 to ROM */
358c2ecf20Sopenharmony_ci	EMU8000_VTFT_WRITE(emu, 30, 0);
368c2ecf20Sopenharmony_ci	EMU8000_PSST_WRITE(emu, 30, 0x1d8);
378c2ecf20Sopenharmony_ci	EMU8000_CSL_WRITE(emu, 30, 0x1e0);
388c2ecf20Sopenharmony_ci	EMU8000_CCCA_WRITE(emu, 30, 0x1d8);
398c2ecf20Sopenharmony_ci	EMU8000_VTFT_WRITE(emu, 31, 0);
408c2ecf20Sopenharmony_ci	EMU8000_PSST_WRITE(emu, 31, 0x1d8);
418c2ecf20Sopenharmony_ci	EMU8000_CSL_WRITE(emu, 31, 0x1e0);
428c2ecf20Sopenharmony_ci	EMU8000_CCCA_WRITE(emu, 31, 0x1d8);
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	return 0;
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci/*
488c2ecf20Sopenharmony_ci * Close all dram channels.
498c2ecf20Sopenharmony_ci */
508c2ecf20Sopenharmony_cistatic void
518c2ecf20Sopenharmony_cisnd_emu8000_close_dma(struct snd_emu8000 *emu)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	int i;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	for (i = 0; i < EMU8000_DRAM_VOICES; i++) {
568c2ecf20Sopenharmony_ci		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE);
578c2ecf20Sopenharmony_ci		snd_emux_unlock_voice(emu->emu, i);
588c2ecf20Sopenharmony_ci	}
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci/*
628c2ecf20Sopenharmony_ci */
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci#define BLANK_LOOP_START	4
658c2ecf20Sopenharmony_ci#define BLANK_LOOP_END		8
668c2ecf20Sopenharmony_ci#define BLANK_LOOP_SIZE		12
678c2ecf20Sopenharmony_ci#define BLANK_HEAD_SIZE		48
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci/*
708c2ecf20Sopenharmony_ci * Read a word from userland, taking care of conversions from
718c2ecf20Sopenharmony_ci * 8bit samples etc.
728c2ecf20Sopenharmony_ci */
738c2ecf20Sopenharmony_cistatic unsigned short
748c2ecf20Sopenharmony_ciread_word(const void __user *buf, int offset, int mode)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	unsigned short c;
778c2ecf20Sopenharmony_ci	if (mode & SNDRV_SFNT_SAMPLE_8BITS) {
788c2ecf20Sopenharmony_ci		unsigned char cc;
798c2ecf20Sopenharmony_ci		get_user(cc, (unsigned char __user *)buf + offset);
808c2ecf20Sopenharmony_ci		c = cc << 8; /* convert 8bit -> 16bit */
818c2ecf20Sopenharmony_ci	} else {
828c2ecf20Sopenharmony_ci#ifdef SNDRV_LITTLE_ENDIAN
838c2ecf20Sopenharmony_ci		get_user(c, (unsigned short __user *)buf + offset);
848c2ecf20Sopenharmony_ci#else
858c2ecf20Sopenharmony_ci		unsigned short cc;
868c2ecf20Sopenharmony_ci		get_user(cc, (unsigned short __user *)buf + offset);
878c2ecf20Sopenharmony_ci		c = swab16(cc);
888c2ecf20Sopenharmony_ci#endif
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci	if (mode & SNDRV_SFNT_SAMPLE_UNSIGNED)
918c2ecf20Sopenharmony_ci		c ^= 0x8000; /* unsigned -> signed */
928c2ecf20Sopenharmony_ci	return c;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci/*
968c2ecf20Sopenharmony_ci */
978c2ecf20Sopenharmony_cistatic void
988c2ecf20Sopenharmony_cisnd_emu8000_write_wait(struct snd_emu8000 *emu)
998c2ecf20Sopenharmony_ci{
1008c2ecf20Sopenharmony_ci	while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) {
1018c2ecf20Sopenharmony_ci		schedule_timeout_interruptible(1);
1028c2ecf20Sopenharmony_ci		if (signal_pending(current))
1038c2ecf20Sopenharmony_ci			break;
1048c2ecf20Sopenharmony_ci	}
1058c2ecf20Sopenharmony_ci}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci/*
1088c2ecf20Sopenharmony_ci * write sample word data
1098c2ecf20Sopenharmony_ci *
1108c2ecf20Sopenharmony_ci * You should not have to keep resetting the address each time
1118c2ecf20Sopenharmony_ci * as the chip is supposed to step on the next address automatically.
1128c2ecf20Sopenharmony_ci * It mostly does, but during writes of some samples at random it
1138c2ecf20Sopenharmony_ci * completely loses words (every one in 16 roughly but with no
1148c2ecf20Sopenharmony_ci * obvious pattern).
1158c2ecf20Sopenharmony_ci *
1168c2ecf20Sopenharmony_ci * This is therefore much slower than need be, but is at least
1178c2ecf20Sopenharmony_ci * working.
1188c2ecf20Sopenharmony_ci */
1198c2ecf20Sopenharmony_cistatic inline void
1208c2ecf20Sopenharmony_ciwrite_word(struct snd_emu8000 *emu, int *offset, unsigned short data)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	if (emu8000_reset_addr) {
1238c2ecf20Sopenharmony_ci		if (emu8000_reset_addr > 1)
1248c2ecf20Sopenharmony_ci			snd_emu8000_write_wait(emu);
1258c2ecf20Sopenharmony_ci		EMU8000_SMALW_WRITE(emu, *offset);
1268c2ecf20Sopenharmony_ci	}
1278c2ecf20Sopenharmony_ci	EMU8000_SMLD_WRITE(emu, data);
1288c2ecf20Sopenharmony_ci	*offset += 1;
1298c2ecf20Sopenharmony_ci}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci/*
1328c2ecf20Sopenharmony_ci * Write the sample to EMU800 memory.  This routine is invoked out of
1338c2ecf20Sopenharmony_ci * the generic soundfont routines as a callback.
1348c2ecf20Sopenharmony_ci */
1358c2ecf20Sopenharmony_ciint
1368c2ecf20Sopenharmony_cisnd_emu8000_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
1378c2ecf20Sopenharmony_ci		       struct snd_util_memhdr *hdr,
1388c2ecf20Sopenharmony_ci		       const void __user *data, long count)
1398c2ecf20Sopenharmony_ci{
1408c2ecf20Sopenharmony_ci	int  i;
1418c2ecf20Sopenharmony_ci	int  rc;
1428c2ecf20Sopenharmony_ci	int  offset;
1438c2ecf20Sopenharmony_ci	int  truesize;
1448c2ecf20Sopenharmony_ci	int  dram_offset, dram_start;
1458c2ecf20Sopenharmony_ci	struct snd_emu8000 *emu;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	emu = rec->hw;
1488c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!sp))
1498c2ecf20Sopenharmony_ci		return -EINVAL;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	if (sp->v.size == 0)
1528c2ecf20Sopenharmony_ci		return 0;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	/* be sure loop points start < end */
1558c2ecf20Sopenharmony_ci	if (sp->v.loopstart > sp->v.loopend)
1568c2ecf20Sopenharmony_ci		swap(sp->v.loopstart, sp->v.loopend);
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	/* compute true data size to be loaded */
1598c2ecf20Sopenharmony_ci	truesize = sp->v.size;
1608c2ecf20Sopenharmony_ci	if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
1618c2ecf20Sopenharmony_ci		truesize += sp->v.loopend - sp->v.loopstart;
1628c2ecf20Sopenharmony_ci	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
1638c2ecf20Sopenharmony_ci		truesize += BLANK_LOOP_SIZE;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	sp->block = snd_util_mem_alloc(hdr, truesize * 2);
1668c2ecf20Sopenharmony_ci	if (sp->block == NULL) {
1678c2ecf20Sopenharmony_ci		/*snd_printd("EMU8000: out of memory\n");*/
1688c2ecf20Sopenharmony_ci		/* not ENOMEM (for compatibility) */
1698c2ecf20Sopenharmony_ci		return -ENOSPC;
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) {
1738c2ecf20Sopenharmony_ci		if (!access_ok(data, sp->v.size))
1748c2ecf20Sopenharmony_ci			return -EFAULT;
1758c2ecf20Sopenharmony_ci	} else {
1768c2ecf20Sopenharmony_ci		if (!access_ok(data, sp->v.size * 2))
1778c2ecf20Sopenharmony_ci			return -EFAULT;
1788c2ecf20Sopenharmony_ci	}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	/* recalculate address offset */
1818c2ecf20Sopenharmony_ci	sp->v.end -= sp->v.start;
1828c2ecf20Sopenharmony_ci	sp->v.loopstart -= sp->v.start;
1838c2ecf20Sopenharmony_ci	sp->v.loopend -= sp->v.start;
1848c2ecf20Sopenharmony_ci	sp->v.start = 0;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	/* dram position (in word) -- mem_offset is byte */
1878c2ecf20Sopenharmony_ci	dram_offset = EMU8000_DRAM_OFFSET + (sp->block->offset >> 1);
1888c2ecf20Sopenharmony_ci	dram_start = dram_offset;
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	/* set the total size (store onto obsolete checksum value) */
1918c2ecf20Sopenharmony_ci	sp->v.truesize = truesize * 2; /* in bytes */
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	snd_emux_terminate_all(emu->emu);
1948c2ecf20Sopenharmony_ci	if ((rc = snd_emu8000_open_dma(emu, EMU8000_RAM_WRITE)) != 0)
1958c2ecf20Sopenharmony_ci		return rc;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	/* Set the address to start writing at */
1988c2ecf20Sopenharmony_ci	snd_emu8000_write_wait(emu);
1998c2ecf20Sopenharmony_ci	EMU8000_SMALW_WRITE(emu, dram_offset);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	/*snd_emu8000_init_fm(emu);*/
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci#if 0
2048c2ecf20Sopenharmony_ci	/* first block - write 48 samples for silence */
2058c2ecf20Sopenharmony_ci	if (! sp->block->offset) {
2068c2ecf20Sopenharmony_ci		for (i = 0; i < BLANK_HEAD_SIZE; i++) {
2078c2ecf20Sopenharmony_ci			write_word(emu, &dram_offset, 0);
2088c2ecf20Sopenharmony_ci		}
2098c2ecf20Sopenharmony_ci	}
2108c2ecf20Sopenharmony_ci#endif
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	offset = 0;
2138c2ecf20Sopenharmony_ci	for (i = 0; i < sp->v.size; i++) {
2148c2ecf20Sopenharmony_ci		unsigned short s;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci		s = read_word(data, offset, sp->v.mode_flags);
2178c2ecf20Sopenharmony_ci		offset++;
2188c2ecf20Sopenharmony_ci		write_word(emu, &dram_offset, s);
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci		/* we may take too long time in this loop.
2218c2ecf20Sopenharmony_ci		 * so give controls back to kernel if needed.
2228c2ecf20Sopenharmony_ci		 */
2238c2ecf20Sopenharmony_ci		cond_resched();
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci		if (i == sp->v.loopend &&
2268c2ecf20Sopenharmony_ci		    (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)))
2278c2ecf20Sopenharmony_ci		{
2288c2ecf20Sopenharmony_ci			int looplen = sp->v.loopend - sp->v.loopstart;
2298c2ecf20Sopenharmony_ci			int k;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci			/* copy reverse loop */
2328c2ecf20Sopenharmony_ci			for (k = 1; k <= looplen; k++) {
2338c2ecf20Sopenharmony_ci				s = read_word(data, offset - k, sp->v.mode_flags);
2348c2ecf20Sopenharmony_ci				write_word(emu, &dram_offset, s);
2358c2ecf20Sopenharmony_ci			}
2368c2ecf20Sopenharmony_ci			if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
2378c2ecf20Sopenharmony_ci				sp->v.loopend += looplen;
2388c2ecf20Sopenharmony_ci			} else {
2398c2ecf20Sopenharmony_ci				sp->v.loopstart += looplen;
2408c2ecf20Sopenharmony_ci				sp->v.loopend += looplen;
2418c2ecf20Sopenharmony_ci			}
2428c2ecf20Sopenharmony_ci			sp->v.end += looplen;
2438c2ecf20Sopenharmony_ci		}
2448c2ecf20Sopenharmony_ci	}
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	/* if no blank loop is attached in the sample, add it */
2478c2ecf20Sopenharmony_ci	if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
2488c2ecf20Sopenharmony_ci		for (i = 0; i < BLANK_LOOP_SIZE; i++) {
2498c2ecf20Sopenharmony_ci			write_word(emu, &dram_offset, 0);
2508c2ecf20Sopenharmony_ci		}
2518c2ecf20Sopenharmony_ci		if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) {
2528c2ecf20Sopenharmony_ci			sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
2538c2ecf20Sopenharmony_ci			sp->v.loopend = sp->v.end + BLANK_LOOP_END;
2548c2ecf20Sopenharmony_ci		}
2558c2ecf20Sopenharmony_ci	}
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	/* add dram offset */
2588c2ecf20Sopenharmony_ci	sp->v.start += dram_start;
2598c2ecf20Sopenharmony_ci	sp->v.end += dram_start;
2608c2ecf20Sopenharmony_ci	sp->v.loopstart += dram_start;
2618c2ecf20Sopenharmony_ci	sp->v.loopend += dram_start;
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	snd_emu8000_close_dma(emu);
2648c2ecf20Sopenharmony_ci	snd_emu8000_init_fm(emu);
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	return 0;
2678c2ecf20Sopenharmony_ci}
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci/*
2708c2ecf20Sopenharmony_ci * free a sample block
2718c2ecf20Sopenharmony_ci */
2728c2ecf20Sopenharmony_ciint
2738c2ecf20Sopenharmony_cisnd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
2748c2ecf20Sopenharmony_ci			struct snd_util_memhdr *hdr)
2758c2ecf20Sopenharmony_ci{
2768c2ecf20Sopenharmony_ci	if (sp->block) {
2778c2ecf20Sopenharmony_ci		snd_util_mem_free(hdr, sp->block);
2788c2ecf20Sopenharmony_ci		sp->block = NULL;
2798c2ecf20Sopenharmony_ci	}
2808c2ecf20Sopenharmony_ci	return 0;
2818c2ecf20Sopenharmony_ci}
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci/*
2858c2ecf20Sopenharmony_ci * sample_reset callback - terminate voices
2868c2ecf20Sopenharmony_ci */
2878c2ecf20Sopenharmony_civoid
2888c2ecf20Sopenharmony_cisnd_emu8000_sample_reset(struct snd_emux *rec)
2898c2ecf20Sopenharmony_ci{
2908c2ecf20Sopenharmony_ci	snd_emux_terminate_all(rec);
2918c2ecf20Sopenharmony_ci}
292