162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Copyright (c) 1998-2002 by Paul Davis <pbd@op.net>
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/io.h>
762306a36Sopenharmony_ci#include <linux/init.h>
862306a36Sopenharmony_ci#include <linux/time.h>
962306a36Sopenharmony_ci#include <linux/wait.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/firmware.h>
1362306a36Sopenharmony_ci#include <sound/core.h>
1462306a36Sopenharmony_ci#include <sound/snd_wavefront.h>
1562306a36Sopenharmony_ci#include <sound/initval.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci/* Control bits for the Load Control Register
1862306a36Sopenharmony_ci */
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define FX_LSB_TRANSFER 0x01    /* transfer after DSP LSB byte written */
2162306a36Sopenharmony_ci#define FX_MSB_TRANSFER 0x02    /* transfer after DSP MSB byte written */
2262306a36Sopenharmony_ci#define FX_AUTO_INCR    0x04    /* auto-increment DSP address after transfer */
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define WAIT_IDLE	0xff
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic int
2762306a36Sopenharmony_ciwavefront_fx_idle (snd_wavefront_t *dev)
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	int i;
3162306a36Sopenharmony_ci	unsigned int x = 0x80;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	for (i = 0; i < 1000; i++) {
3462306a36Sopenharmony_ci		x = inb (dev->fx_status);
3562306a36Sopenharmony_ci		if ((x & 0x80) == 0) {
3662306a36Sopenharmony_ci			break;
3762306a36Sopenharmony_ci		}
3862306a36Sopenharmony_ci	}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if (x & 0x80) {
4162306a36Sopenharmony_ci		snd_printk ("FX device never idle.\n");
4262306a36Sopenharmony_ci		return 0;
4362306a36Sopenharmony_ci	}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	return (1);
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic void
4962306a36Sopenharmony_ciwavefront_fx_mute (snd_wavefront_t *dev, int onoff)
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	if (!wavefront_fx_idle(dev)) {
5362306a36Sopenharmony_ci		return;
5462306a36Sopenharmony_ci	}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	outb (onoff ? 0x02 : 0x00, dev->fx_op);
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic int
6062306a36Sopenharmony_ciwavefront_fx_memset (snd_wavefront_t *dev,
6162306a36Sopenharmony_ci		     int page,
6262306a36Sopenharmony_ci		     int addr,
6362306a36Sopenharmony_ci		     int cnt,
6462306a36Sopenharmony_ci		     unsigned short *data)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	if (page < 0 || page > 7) {
6762306a36Sopenharmony_ci		snd_printk ("FX memset: "
6862306a36Sopenharmony_ci			"page must be >= 0 and <= 7\n");
6962306a36Sopenharmony_ci		return -EINVAL;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	if (addr < 0 || addr > 0x7f) {
7362306a36Sopenharmony_ci		snd_printk ("FX memset: "
7462306a36Sopenharmony_ci			"addr must be >= 0 and <= 7f\n");
7562306a36Sopenharmony_ci		return -EINVAL;
7662306a36Sopenharmony_ci	}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	if (cnt == 1) {
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci		outb (FX_LSB_TRANSFER, dev->fx_lcr);
8162306a36Sopenharmony_ci		outb (page, dev->fx_dsp_page);
8262306a36Sopenharmony_ci		outb (addr, dev->fx_dsp_addr);
8362306a36Sopenharmony_ci		outb ((data[0] >> 8), dev->fx_dsp_msb);
8462306a36Sopenharmony_ci		outb ((data[0] & 0xff), dev->fx_dsp_lsb);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci		snd_printk ("FX: addr %d:%x set to 0x%x\n",
8762306a36Sopenharmony_ci			page, addr, data[0]);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	} else {
9062306a36Sopenharmony_ci		int i;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci		outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr);
9362306a36Sopenharmony_ci		outb (page, dev->fx_dsp_page);
9462306a36Sopenharmony_ci		outb (addr, dev->fx_dsp_addr);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		for (i = 0; i < cnt; i++) {
9762306a36Sopenharmony_ci			outb ((data[i] >> 8), dev->fx_dsp_msb);
9862306a36Sopenharmony_ci			outb ((data[i] & 0xff), dev->fx_dsp_lsb);
9962306a36Sopenharmony_ci			if (!wavefront_fx_idle (dev)) {
10062306a36Sopenharmony_ci				break;
10162306a36Sopenharmony_ci			}
10262306a36Sopenharmony_ci		}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci		if (i != cnt) {
10562306a36Sopenharmony_ci			snd_printk ("FX memset "
10662306a36Sopenharmony_ci				    "(0x%x, 0x%x, 0x%lx, %d) incomplete\n",
10762306a36Sopenharmony_ci				    page, addr, (unsigned long) data, cnt);
10862306a36Sopenharmony_ci			return -EIO;
10962306a36Sopenharmony_ci		}
11062306a36Sopenharmony_ci	}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	return 0;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ciint
11662306a36Sopenharmony_cisnd_wavefront_fx_detect (snd_wavefront_t *dev)
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	/* This is a crude check, but its the best one I have for now.
12062306a36Sopenharmony_ci	   Certainly on the Maui and the Tropez, wavefront_fx_idle() will
12162306a36Sopenharmony_ci	   report "never idle", which suggests that this test should
12262306a36Sopenharmony_ci	   work OK.
12362306a36Sopenharmony_ci	*/
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	if (inb (dev->fx_status) & 0x80) {
12662306a36Sopenharmony_ci		snd_printk ("Hmm, probably a Maui or Tropez.\n");
12762306a36Sopenharmony_ci		return -1;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	return 0;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ciint
13462306a36Sopenharmony_cisnd_wavefront_fx_open (struct snd_hwdep *hw, struct file *file)
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci{
13762306a36Sopenharmony_ci	if (!try_module_get(hw->card->module))
13862306a36Sopenharmony_ci		return -EFAULT;
13962306a36Sopenharmony_ci	file->private_data = hw;
14062306a36Sopenharmony_ci	return 0;
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ciint
14462306a36Sopenharmony_cisnd_wavefront_fx_release (struct snd_hwdep *hw, struct file *file)
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	module_put(hw->card->module);
14862306a36Sopenharmony_ci	return 0;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ciint
15262306a36Sopenharmony_cisnd_wavefront_fx_ioctl (struct snd_hwdep *sdev, struct file *file,
15362306a36Sopenharmony_ci			unsigned int cmd, unsigned long arg)
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	struct snd_card *card;
15762306a36Sopenharmony_ci	snd_wavefront_card_t *acard;
15862306a36Sopenharmony_ci	snd_wavefront_t *dev;
15962306a36Sopenharmony_ci	wavefront_fx_info r;
16062306a36Sopenharmony_ci	unsigned short *page_data = NULL;
16162306a36Sopenharmony_ci	unsigned short *pd;
16262306a36Sopenharmony_ci	int err = 0;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	card = sdev->card;
16562306a36Sopenharmony_ci	if (snd_BUG_ON(!card))
16662306a36Sopenharmony_ci		return -ENODEV;
16762306a36Sopenharmony_ci	if (snd_BUG_ON(!card->private_data))
16862306a36Sopenharmony_ci		return -ENODEV;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	acard = card->private_data;
17162306a36Sopenharmony_ci	dev = &acard->wavefront;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	if (copy_from_user (&r, (void __user *)arg, sizeof (wavefront_fx_info)))
17462306a36Sopenharmony_ci		return -EFAULT;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	switch (r.request) {
17762306a36Sopenharmony_ci	case WFFX_MUTE:
17862306a36Sopenharmony_ci		wavefront_fx_mute (dev, r.data[0]);
17962306a36Sopenharmony_ci		return -EIO;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	case WFFX_MEMSET:
18262306a36Sopenharmony_ci		if (r.data[2] <= 0) {
18362306a36Sopenharmony_ci			snd_printk ("cannot write "
18462306a36Sopenharmony_ci				"<= 0 bytes to FX\n");
18562306a36Sopenharmony_ci			return -EIO;
18662306a36Sopenharmony_ci		} else if (r.data[2] == 1) {
18762306a36Sopenharmony_ci			pd = (unsigned short *) &r.data[3];
18862306a36Sopenharmony_ci		} else {
18962306a36Sopenharmony_ci			if (r.data[2] > 256) {
19062306a36Sopenharmony_ci				snd_printk ("cannot write "
19162306a36Sopenharmony_ci					    "> 512 bytes to FX\n");
19262306a36Sopenharmony_ci				return -EIO;
19362306a36Sopenharmony_ci			}
19462306a36Sopenharmony_ci			page_data = memdup_user((unsigned char __user *)
19562306a36Sopenharmony_ci						r.data[3],
19662306a36Sopenharmony_ci						r.data[2] * sizeof(short));
19762306a36Sopenharmony_ci			if (IS_ERR(page_data))
19862306a36Sopenharmony_ci				return PTR_ERR(page_data);
19962306a36Sopenharmony_ci			pd = page_data;
20062306a36Sopenharmony_ci		}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci		err = wavefront_fx_memset (dev,
20362306a36Sopenharmony_ci			     r.data[0], /* page */
20462306a36Sopenharmony_ci			     r.data[1], /* addr */
20562306a36Sopenharmony_ci			     r.data[2], /* cnt */
20662306a36Sopenharmony_ci			     pd);
20762306a36Sopenharmony_ci		kfree(page_data);
20862306a36Sopenharmony_ci		break;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	default:
21162306a36Sopenharmony_ci		snd_printk ("FX: ioctl %d not yet supported\n",
21262306a36Sopenharmony_ci			    r.request);
21362306a36Sopenharmony_ci		return -ENOTTY;
21462306a36Sopenharmony_ci	}
21562306a36Sopenharmony_ci	return err;
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci/* YSS225 initialization.
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci   This code was developed using DOSEMU. The Turtle Beach SETUPSND
22162306a36Sopenharmony_ci   utility was run with I/O tracing in DOSEMU enabled, and a reconstruction
22262306a36Sopenharmony_ci   of the port I/O done, using the Yamaha faxback document as a guide
22362306a36Sopenharmony_ci   to add more logic to the code. Its really pretty weird.
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci   This is the approach of just dumping the whole I/O
22662306a36Sopenharmony_ci   sequence as a series of port/value pairs and a simple loop
22762306a36Sopenharmony_ci   that outputs it.
22862306a36Sopenharmony_ci*/
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ciint
23162306a36Sopenharmony_cisnd_wavefront_fx_start (snd_wavefront_t *dev)
23262306a36Sopenharmony_ci{
23362306a36Sopenharmony_ci	unsigned int i;
23462306a36Sopenharmony_ci	int err;
23562306a36Sopenharmony_ci	const struct firmware *firmware = NULL;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	if (dev->fx_initialized)
23862306a36Sopenharmony_ci		return 0;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	err = request_firmware(&firmware, "yamaha/yss225_registers.bin",
24162306a36Sopenharmony_ci			       dev->card->dev);
24262306a36Sopenharmony_ci	if (err < 0) {
24362306a36Sopenharmony_ci		err = -1;
24462306a36Sopenharmony_ci		goto out;
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	for (i = 0; i + 1 < firmware->size; i += 2) {
24862306a36Sopenharmony_ci		if (firmware->data[i] >= 8 && firmware->data[i] < 16) {
24962306a36Sopenharmony_ci			outb(firmware->data[i + 1],
25062306a36Sopenharmony_ci			     dev->base + firmware->data[i]);
25162306a36Sopenharmony_ci		} else if (firmware->data[i] == WAIT_IDLE) {
25262306a36Sopenharmony_ci			if (!wavefront_fx_idle(dev)) {
25362306a36Sopenharmony_ci				err = -1;
25462306a36Sopenharmony_ci				goto out;
25562306a36Sopenharmony_ci			}
25662306a36Sopenharmony_ci		} else {
25762306a36Sopenharmony_ci			snd_printk(KERN_ERR "invalid address"
25862306a36Sopenharmony_ci				   " in register data\n");
25962306a36Sopenharmony_ci			err = -1;
26062306a36Sopenharmony_ci			goto out;
26162306a36Sopenharmony_ci		}
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	dev->fx_initialized = 1;
26562306a36Sopenharmony_ci	err = 0;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ciout:
26862306a36Sopenharmony_ci	release_firmware(firmware);
26962306a36Sopenharmony_ci	return err;
27062306a36Sopenharmony_ci}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ciMODULE_FIRMWARE("yamaha/yss225_registers.bin");
273