162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
462306a36Sopenharmony_ci *                   Hannu Savolainen 1993-1996,
562306a36Sopenharmony_ci *                   Rob Hooft
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *  Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci *  Most if code is ported from OSS/Lite.
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <sound/opl3.h>
1362306a36Sopenharmony_ci#include <linux/io.h>
1462306a36Sopenharmony_ci#include <linux/delay.h>
1562306a36Sopenharmony_ci#include <linux/module.h>
1662306a36Sopenharmony_ci#include <linux/init.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci#include <linux/ioport.h>
1962306a36Sopenharmony_ci#include <sound/minors.h>
2062306a36Sopenharmony_ci#include "opl3_voice.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ciMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Hannu Savolainen 1993-1996, Rob Hooft");
2362306a36Sopenharmony_ciMODULE_DESCRIPTION("Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)");
2462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic void snd_opl2_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	unsigned long flags;
2962306a36Sopenharmony_ci	unsigned long port;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	/*
3262306a36Sopenharmony_ci	 * The original 2-OP synth requires a quite long delay
3362306a36Sopenharmony_ci	 * after writing to a register.
3462306a36Sopenharmony_ci	 */
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	spin_lock_irqsave(&opl3->reg_lock, flags);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	outb((unsigned char) cmd, port);
4162306a36Sopenharmony_ci	udelay(10);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	outb((unsigned char) val, port + 1);
4462306a36Sopenharmony_ci	udelay(30);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	spin_unlock_irqrestore(&opl3->reg_lock, flags);
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic void snd_opl3_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	unsigned long flags;
5262306a36Sopenharmony_ci	unsigned long port;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	/*
5562306a36Sopenharmony_ci	 * The OPL-3 survives with just two INBs
5662306a36Sopenharmony_ci	 * after writing to a register.
5762306a36Sopenharmony_ci	 */
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	spin_lock_irqsave(&opl3->reg_lock, flags);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	outb((unsigned char) cmd, port);
6462306a36Sopenharmony_ci	inb(opl3->l_port);
6562306a36Sopenharmony_ci	inb(opl3->l_port);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	outb((unsigned char) val, port + 1);
6862306a36Sopenharmony_ci	inb(opl3->l_port);
6962306a36Sopenharmony_ci	inb(opl3->l_port);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	spin_unlock_irqrestore(&opl3->reg_lock, flags);
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic int snd_opl3_detect(struct snd_opl3 * opl3)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	/*
7762306a36Sopenharmony_ci	 * This function returns 1 if the FM chip is present at the given I/O port
7862306a36Sopenharmony_ci	 * The detection algorithm plays with the timer built in the FM chip and
7962306a36Sopenharmony_ci	 * looks for a change in the status register.
8062306a36Sopenharmony_ci	 *
8162306a36Sopenharmony_ci	 * Note! The timers of the FM chip are not connected to AdLib (and compatible)
8262306a36Sopenharmony_ci	 * boards.
8362306a36Sopenharmony_ci	 *
8462306a36Sopenharmony_ci	 * Note2! The chip is initialized if detected.
8562306a36Sopenharmony_ci	 */
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	unsigned char stat1, stat2, signature;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	/* Reset timers 1 and 2 */
9062306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
9162306a36Sopenharmony_ci	/* Reset the IRQ of the FM chip */
9262306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
9362306a36Sopenharmony_ci	signature = stat1 = inb(opl3->l_port);	/* Status register */
9462306a36Sopenharmony_ci	if ((stat1 & 0xe0) != 0x00) {	/* Should be 0x00 */
9562306a36Sopenharmony_ci		snd_printd("OPL3: stat1 = 0x%x\n", stat1);
9662306a36Sopenharmony_ci		return -ENODEV;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci	/* Set timer1 to 0xff */
9962306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 0xff);
10062306a36Sopenharmony_ci	/* Unmask and start timer 1 */
10162306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER2_MASK | OPL3_TIMER1_START);
10262306a36Sopenharmony_ci	/* Now we have to delay at least 80us */
10362306a36Sopenharmony_ci	udelay(200);
10462306a36Sopenharmony_ci	/* Read status after timers have expired */
10562306a36Sopenharmony_ci	stat2 = inb(opl3->l_port);
10662306a36Sopenharmony_ci	/* Stop the timers */
10762306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
10862306a36Sopenharmony_ci	/* Reset the IRQ of the FM chip */
10962306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
11062306a36Sopenharmony_ci	if ((stat2 & 0xe0) != 0xc0) {	/* There is no YM3812 */
11162306a36Sopenharmony_ci		snd_printd("OPL3: stat2 = 0x%x\n", stat2);
11262306a36Sopenharmony_ci		return -ENODEV;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	/* If the toplevel code knows exactly the type of chip, don't try
11662306a36Sopenharmony_ci	   to detect it. */
11762306a36Sopenharmony_ci	if (opl3->hardware != OPL3_HW_AUTO)
11862306a36Sopenharmony_ci		return 0;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/* There is a FM chip on this address. Detect the type (OPL2 to OPL4) */
12162306a36Sopenharmony_ci	if (signature == 0x06) {	/* OPL2 */
12262306a36Sopenharmony_ci		opl3->hardware = OPL3_HW_OPL2;
12362306a36Sopenharmony_ci	} else {
12462306a36Sopenharmony_ci		/*
12562306a36Sopenharmony_ci		 * If we had an OPL4 chip, opl3->hardware would have been set
12662306a36Sopenharmony_ci		 * by the OPL4 driver; so we can assume OPL3 here.
12762306a36Sopenharmony_ci		 */
12862306a36Sopenharmony_ci		if (snd_BUG_ON(!opl3->r_port))
12962306a36Sopenharmony_ci			return -ENODEV;
13062306a36Sopenharmony_ci		opl3->hardware = OPL3_HW_OPL3;
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci	return 0;
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci/*
13662306a36Sopenharmony_ci *  AdLib timers
13762306a36Sopenharmony_ci */
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci/*
14062306a36Sopenharmony_ci *  Timer 1 - 80us
14162306a36Sopenharmony_ci */
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic int snd_opl3_timer1_start(struct snd_timer * timer)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	unsigned long flags;
14662306a36Sopenharmony_ci	unsigned char tmp;
14762306a36Sopenharmony_ci	unsigned int ticks;
14862306a36Sopenharmony_ci	struct snd_opl3 *opl3;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	opl3 = snd_timer_chip(timer);
15162306a36Sopenharmony_ci	spin_lock_irqsave(&opl3->timer_lock, flags);
15262306a36Sopenharmony_ci	ticks = timer->sticks;
15362306a36Sopenharmony_ci	tmp = (opl3->timer_enable | OPL3_TIMER1_START) & ~OPL3_TIMER1_MASK;
15462306a36Sopenharmony_ci	opl3->timer_enable = tmp;
15562306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 256 - ticks);	/* timer 1 count */
15662306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* enable timer 1 IRQ */
15762306a36Sopenharmony_ci	spin_unlock_irqrestore(&opl3->timer_lock, flags);
15862306a36Sopenharmony_ci	return 0;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic int snd_opl3_timer1_stop(struct snd_timer * timer)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	unsigned long flags;
16462306a36Sopenharmony_ci	unsigned char tmp;
16562306a36Sopenharmony_ci	struct snd_opl3 *opl3;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	opl3 = snd_timer_chip(timer);
16862306a36Sopenharmony_ci	spin_lock_irqsave(&opl3->timer_lock, flags);
16962306a36Sopenharmony_ci	tmp = (opl3->timer_enable | OPL3_TIMER1_MASK) & ~OPL3_TIMER1_START;
17062306a36Sopenharmony_ci	opl3->timer_enable = tmp;
17162306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* disable timer #1 */
17262306a36Sopenharmony_ci	spin_unlock_irqrestore(&opl3->timer_lock, flags);
17362306a36Sopenharmony_ci	return 0;
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci/*
17762306a36Sopenharmony_ci *  Timer 2 - 320us
17862306a36Sopenharmony_ci */
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic int snd_opl3_timer2_start(struct snd_timer * timer)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	unsigned long flags;
18362306a36Sopenharmony_ci	unsigned char tmp;
18462306a36Sopenharmony_ci	unsigned int ticks;
18562306a36Sopenharmony_ci	struct snd_opl3 *opl3;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	opl3 = snd_timer_chip(timer);
18862306a36Sopenharmony_ci	spin_lock_irqsave(&opl3->timer_lock, flags);
18962306a36Sopenharmony_ci	ticks = timer->sticks;
19062306a36Sopenharmony_ci	tmp = (opl3->timer_enable | OPL3_TIMER2_START) & ~OPL3_TIMER2_MASK;
19162306a36Sopenharmony_ci	opl3->timer_enable = tmp;
19262306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER2, 256 - ticks);	/* timer 1 count */
19362306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* enable timer 1 IRQ */
19462306a36Sopenharmony_ci	spin_unlock_irqrestore(&opl3->timer_lock, flags);
19562306a36Sopenharmony_ci	return 0;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic int snd_opl3_timer2_stop(struct snd_timer * timer)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	unsigned long flags;
20162306a36Sopenharmony_ci	unsigned char tmp;
20262306a36Sopenharmony_ci	struct snd_opl3 *opl3;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	opl3 = snd_timer_chip(timer);
20562306a36Sopenharmony_ci	spin_lock_irqsave(&opl3->timer_lock, flags);
20662306a36Sopenharmony_ci	tmp = (opl3->timer_enable | OPL3_TIMER2_MASK) & ~OPL3_TIMER2_START;
20762306a36Sopenharmony_ci	opl3->timer_enable = tmp;
20862306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* disable timer #1 */
20962306a36Sopenharmony_ci	spin_unlock_irqrestore(&opl3->timer_lock, flags);
21062306a36Sopenharmony_ci	return 0;
21162306a36Sopenharmony_ci}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci/*
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci */
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic const struct snd_timer_hardware snd_opl3_timer1 =
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	.flags =	SNDRV_TIMER_HW_STOP,
22062306a36Sopenharmony_ci	.resolution =	80000,
22162306a36Sopenharmony_ci	.ticks =	256,
22262306a36Sopenharmony_ci	.start =	snd_opl3_timer1_start,
22362306a36Sopenharmony_ci	.stop =		snd_opl3_timer1_stop,
22462306a36Sopenharmony_ci};
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_cistatic const struct snd_timer_hardware snd_opl3_timer2 =
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	.flags =	SNDRV_TIMER_HW_STOP,
22962306a36Sopenharmony_ci	.resolution =	320000,
23062306a36Sopenharmony_ci	.ticks =	256,
23162306a36Sopenharmony_ci	.start =	snd_opl3_timer2_start,
23262306a36Sopenharmony_ci	.stop =		snd_opl3_timer2_stop,
23362306a36Sopenharmony_ci};
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic int snd_opl3_timer1_init(struct snd_opl3 * opl3, int timer_no)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	struct snd_timer *timer = NULL;
23862306a36Sopenharmony_ci	struct snd_timer_id tid;
23962306a36Sopenharmony_ci	int err;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
24262306a36Sopenharmony_ci	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
24362306a36Sopenharmony_ci	tid.card = opl3->card->number;
24462306a36Sopenharmony_ci	tid.device = timer_no;
24562306a36Sopenharmony_ci	tid.subdevice = 0;
24662306a36Sopenharmony_ci	err = snd_timer_new(opl3->card, "AdLib timer #1", &tid, &timer);
24762306a36Sopenharmony_ci	if (err >= 0) {
24862306a36Sopenharmony_ci		strcpy(timer->name, "AdLib timer #1");
24962306a36Sopenharmony_ci		timer->private_data = opl3;
25062306a36Sopenharmony_ci		timer->hw = snd_opl3_timer1;
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci	opl3->timer1 = timer;
25362306a36Sopenharmony_ci	return err;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic int snd_opl3_timer2_init(struct snd_opl3 * opl3, int timer_no)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	struct snd_timer *timer = NULL;
25962306a36Sopenharmony_ci	struct snd_timer_id tid;
26062306a36Sopenharmony_ci	int err;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
26362306a36Sopenharmony_ci	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
26462306a36Sopenharmony_ci	tid.card = opl3->card->number;
26562306a36Sopenharmony_ci	tid.device = timer_no;
26662306a36Sopenharmony_ci	tid.subdevice = 0;
26762306a36Sopenharmony_ci	err = snd_timer_new(opl3->card, "AdLib timer #2", &tid, &timer);
26862306a36Sopenharmony_ci	if (err >= 0) {
26962306a36Sopenharmony_ci		strcpy(timer->name, "AdLib timer #2");
27062306a36Sopenharmony_ci		timer->private_data = opl3;
27162306a36Sopenharmony_ci		timer->hw = snd_opl3_timer2;
27262306a36Sopenharmony_ci	}
27362306a36Sopenharmony_ci	opl3->timer2 = timer;
27462306a36Sopenharmony_ci	return err;
27562306a36Sopenharmony_ci}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci/*
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci */
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_civoid snd_opl3_interrupt(struct snd_hwdep * hw)
28262306a36Sopenharmony_ci{
28362306a36Sopenharmony_ci	unsigned char status;
28462306a36Sopenharmony_ci	struct snd_opl3 *opl3;
28562306a36Sopenharmony_ci	struct snd_timer *timer;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	if (hw == NULL)
28862306a36Sopenharmony_ci		return;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	opl3 = hw->private_data;
29162306a36Sopenharmony_ci	status = inb(opl3->l_port);
29262306a36Sopenharmony_ci#if 0
29362306a36Sopenharmony_ci	snd_printk(KERN_DEBUG "AdLib IRQ status = 0x%x\n", status);
29462306a36Sopenharmony_ci#endif
29562306a36Sopenharmony_ci	if (!(status & 0x80))
29662306a36Sopenharmony_ci		return;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	if (status & 0x40) {
29962306a36Sopenharmony_ci		timer = opl3->timer1;
30062306a36Sopenharmony_ci		snd_timer_interrupt(timer, timer->sticks);
30162306a36Sopenharmony_ci	}
30262306a36Sopenharmony_ci	if (status & 0x20) {
30362306a36Sopenharmony_ci		timer = opl3->timer2;
30462306a36Sopenharmony_ci		snd_timer_interrupt(timer, timer->sticks);
30562306a36Sopenharmony_ci	}
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_interrupt);
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci/*
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci */
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_cistatic int snd_opl3_free(struct snd_opl3 *opl3)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci	if (snd_BUG_ON(!opl3))
31762306a36Sopenharmony_ci		return -ENXIO;
31862306a36Sopenharmony_ci	if (opl3->private_free)
31962306a36Sopenharmony_ci		opl3->private_free(opl3);
32062306a36Sopenharmony_ci	snd_opl3_clear_patches(opl3);
32162306a36Sopenharmony_ci	release_and_free_resource(opl3->res_l_port);
32262306a36Sopenharmony_ci	release_and_free_resource(opl3->res_r_port);
32362306a36Sopenharmony_ci	kfree(opl3);
32462306a36Sopenharmony_ci	return 0;
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_cistatic int snd_opl3_dev_free(struct snd_device *device)
32862306a36Sopenharmony_ci{
32962306a36Sopenharmony_ci	struct snd_opl3 *opl3 = device->device_data;
33062306a36Sopenharmony_ci	return snd_opl3_free(opl3);
33162306a36Sopenharmony_ci}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ciint snd_opl3_new(struct snd_card *card,
33462306a36Sopenharmony_ci		 unsigned short hardware,
33562306a36Sopenharmony_ci		 struct snd_opl3 **ropl3)
33662306a36Sopenharmony_ci{
33762306a36Sopenharmony_ci	static const struct snd_device_ops ops = {
33862306a36Sopenharmony_ci		.dev_free = snd_opl3_dev_free,
33962306a36Sopenharmony_ci	};
34062306a36Sopenharmony_ci	struct snd_opl3 *opl3;
34162306a36Sopenharmony_ci	int err;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	*ropl3 = NULL;
34462306a36Sopenharmony_ci	opl3 = kzalloc(sizeof(*opl3), GFP_KERNEL);
34562306a36Sopenharmony_ci	if (!opl3)
34662306a36Sopenharmony_ci		return -ENOMEM;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	opl3->card = card;
34962306a36Sopenharmony_ci	opl3->hardware = hardware;
35062306a36Sopenharmony_ci	spin_lock_init(&opl3->reg_lock);
35162306a36Sopenharmony_ci	spin_lock_init(&opl3->timer_lock);
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	err = snd_device_new(card, SNDRV_DEV_CODEC, opl3, &ops);
35462306a36Sopenharmony_ci	if (err < 0) {
35562306a36Sopenharmony_ci		snd_opl3_free(opl3);
35662306a36Sopenharmony_ci		return err;
35762306a36Sopenharmony_ci	}
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	*ropl3 = opl3;
36062306a36Sopenharmony_ci	return 0;
36162306a36Sopenharmony_ci}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_new);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ciint snd_opl3_init(struct snd_opl3 *opl3)
36662306a36Sopenharmony_ci{
36762306a36Sopenharmony_ci	if (! opl3->command) {
36862306a36Sopenharmony_ci		printk(KERN_ERR "snd_opl3_init: command not defined!\n");
36962306a36Sopenharmony_ci		return -EINVAL;
37062306a36Sopenharmony_ci	}
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT);
37362306a36Sopenharmony_ci	/* Melodic mode */
37462306a36Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00);
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	switch (opl3->hardware & OPL3_HW_MASK) {
37762306a36Sopenharmony_ci	case OPL3_HW_OPL2:
37862306a36Sopenharmony_ci		opl3->max_voices = MAX_OPL2_VOICES;
37962306a36Sopenharmony_ci		break;
38062306a36Sopenharmony_ci	case OPL3_HW_OPL3:
38162306a36Sopenharmony_ci	case OPL3_HW_OPL4:
38262306a36Sopenharmony_ci		opl3->max_voices = MAX_OPL3_VOICES;
38362306a36Sopenharmony_ci		/* Enter OPL3 mode */
38462306a36Sopenharmony_ci		opl3->command(opl3, OPL3_RIGHT | OPL3_REG_MODE, OPL3_OPL3_ENABLE);
38562306a36Sopenharmony_ci	}
38662306a36Sopenharmony_ci	return 0;
38762306a36Sopenharmony_ci}
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_init);
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ciint snd_opl3_create(struct snd_card *card,
39262306a36Sopenharmony_ci		    unsigned long l_port,
39362306a36Sopenharmony_ci		    unsigned long r_port,
39462306a36Sopenharmony_ci		    unsigned short hardware,
39562306a36Sopenharmony_ci		    int integrated,
39662306a36Sopenharmony_ci		    struct snd_opl3 ** ropl3)
39762306a36Sopenharmony_ci{
39862306a36Sopenharmony_ci	struct snd_opl3 *opl3;
39962306a36Sopenharmony_ci	int err;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	*ropl3 = NULL;
40262306a36Sopenharmony_ci	err = snd_opl3_new(card, hardware, &opl3);
40362306a36Sopenharmony_ci	if (err < 0)
40462306a36Sopenharmony_ci		return err;
40562306a36Sopenharmony_ci	if (! integrated) {
40662306a36Sopenharmony_ci		opl3->res_l_port = request_region(l_port, 2, "OPL2/3 (left)");
40762306a36Sopenharmony_ci		if (!opl3->res_l_port) {
40862306a36Sopenharmony_ci			snd_printk(KERN_ERR "opl3: can't grab left port 0x%lx\n", l_port);
40962306a36Sopenharmony_ci			snd_device_free(card, opl3);
41062306a36Sopenharmony_ci			return -EBUSY;
41162306a36Sopenharmony_ci		}
41262306a36Sopenharmony_ci		if (r_port != 0) {
41362306a36Sopenharmony_ci			opl3->res_r_port = request_region(r_port, 2, "OPL2/3 (right)");
41462306a36Sopenharmony_ci			if (!opl3->res_r_port) {
41562306a36Sopenharmony_ci				snd_printk(KERN_ERR "opl3: can't grab right port 0x%lx\n", r_port);
41662306a36Sopenharmony_ci				snd_device_free(card, opl3);
41762306a36Sopenharmony_ci				return -EBUSY;
41862306a36Sopenharmony_ci			}
41962306a36Sopenharmony_ci		}
42062306a36Sopenharmony_ci	}
42162306a36Sopenharmony_ci	opl3->l_port = l_port;
42262306a36Sopenharmony_ci	opl3->r_port = r_port;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	switch (opl3->hardware) {
42562306a36Sopenharmony_ci	/* some hardware doesn't support timers */
42662306a36Sopenharmony_ci	case OPL3_HW_OPL3_SV:
42762306a36Sopenharmony_ci	case OPL3_HW_OPL3_CS:
42862306a36Sopenharmony_ci	case OPL3_HW_OPL3_FM801:
42962306a36Sopenharmony_ci		opl3->command = &snd_opl3_command;
43062306a36Sopenharmony_ci		break;
43162306a36Sopenharmony_ci	default:
43262306a36Sopenharmony_ci		opl3->command = &snd_opl2_command;
43362306a36Sopenharmony_ci		err = snd_opl3_detect(opl3);
43462306a36Sopenharmony_ci		if (err < 0) {
43562306a36Sopenharmony_ci			snd_printd("OPL2/3 chip not detected at 0x%lx/0x%lx\n",
43662306a36Sopenharmony_ci				   opl3->l_port, opl3->r_port);
43762306a36Sopenharmony_ci			snd_device_free(card, opl3);
43862306a36Sopenharmony_ci			return err;
43962306a36Sopenharmony_ci		}
44062306a36Sopenharmony_ci		/* detect routine returns correct hardware type */
44162306a36Sopenharmony_ci		switch (opl3->hardware & OPL3_HW_MASK) {
44262306a36Sopenharmony_ci		case OPL3_HW_OPL3:
44362306a36Sopenharmony_ci		case OPL3_HW_OPL4:
44462306a36Sopenharmony_ci			opl3->command = &snd_opl3_command;
44562306a36Sopenharmony_ci		}
44662306a36Sopenharmony_ci	}
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	snd_opl3_init(opl3);
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	*ropl3 = opl3;
45162306a36Sopenharmony_ci	return 0;
45262306a36Sopenharmony_ci}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_create);
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ciint snd_opl3_timer_new(struct snd_opl3 * opl3, int timer1_dev, int timer2_dev)
45762306a36Sopenharmony_ci{
45862306a36Sopenharmony_ci	int err;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	if (timer1_dev >= 0) {
46162306a36Sopenharmony_ci		err = snd_opl3_timer1_init(opl3, timer1_dev);
46262306a36Sopenharmony_ci		if (err < 0)
46362306a36Sopenharmony_ci			return err;
46462306a36Sopenharmony_ci	}
46562306a36Sopenharmony_ci	if (timer2_dev >= 0) {
46662306a36Sopenharmony_ci		err = snd_opl3_timer2_init(opl3, timer2_dev);
46762306a36Sopenharmony_ci		if (err < 0) {
46862306a36Sopenharmony_ci			snd_device_free(opl3->card, opl3->timer1);
46962306a36Sopenharmony_ci			opl3->timer1 = NULL;
47062306a36Sopenharmony_ci			return err;
47162306a36Sopenharmony_ci		}
47262306a36Sopenharmony_ci	}
47362306a36Sopenharmony_ci	return 0;
47462306a36Sopenharmony_ci}
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_timer_new);
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ciint snd_opl3_hwdep_new(struct snd_opl3 * opl3,
47962306a36Sopenharmony_ci		       int device, int seq_device,
48062306a36Sopenharmony_ci		       struct snd_hwdep ** rhwdep)
48162306a36Sopenharmony_ci{
48262306a36Sopenharmony_ci	struct snd_hwdep *hw;
48362306a36Sopenharmony_ci	struct snd_card *card = opl3->card;
48462306a36Sopenharmony_ci	int err;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	if (rhwdep)
48762306a36Sopenharmony_ci		*rhwdep = NULL;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	/* create hardware dependent device (direct FM) */
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	err = snd_hwdep_new(card, "OPL2/OPL3", device, &hw);
49262306a36Sopenharmony_ci	if (err < 0) {
49362306a36Sopenharmony_ci		snd_device_free(card, opl3);
49462306a36Sopenharmony_ci		return err;
49562306a36Sopenharmony_ci	}
49662306a36Sopenharmony_ci	hw->private_data = opl3;
49762306a36Sopenharmony_ci	hw->exclusive = 1;
49862306a36Sopenharmony_ci#ifdef CONFIG_SND_OSSEMUL
49962306a36Sopenharmony_ci	if (device == 0)
50062306a36Sopenharmony_ci		hw->oss_type = SNDRV_OSS_DEVICE_TYPE_DMFM;
50162306a36Sopenharmony_ci#endif
50262306a36Sopenharmony_ci	strcpy(hw->name, hw->id);
50362306a36Sopenharmony_ci	switch (opl3->hardware & OPL3_HW_MASK) {
50462306a36Sopenharmony_ci	case OPL3_HW_OPL2:
50562306a36Sopenharmony_ci		strcpy(hw->name, "OPL2 FM");
50662306a36Sopenharmony_ci		hw->iface = SNDRV_HWDEP_IFACE_OPL2;
50762306a36Sopenharmony_ci		break;
50862306a36Sopenharmony_ci	case OPL3_HW_OPL3:
50962306a36Sopenharmony_ci		strcpy(hw->name, "OPL3 FM");
51062306a36Sopenharmony_ci		hw->iface = SNDRV_HWDEP_IFACE_OPL3;
51162306a36Sopenharmony_ci		break;
51262306a36Sopenharmony_ci	case OPL3_HW_OPL4:
51362306a36Sopenharmony_ci		strcpy(hw->name, "OPL4 FM");
51462306a36Sopenharmony_ci		hw->iface = SNDRV_HWDEP_IFACE_OPL4;
51562306a36Sopenharmony_ci		break;
51662306a36Sopenharmony_ci	}
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	/* operators - only ioctl */
51962306a36Sopenharmony_ci	hw->ops.open = snd_opl3_open;
52062306a36Sopenharmony_ci	hw->ops.ioctl = snd_opl3_ioctl;
52162306a36Sopenharmony_ci	hw->ops.write = snd_opl3_write;
52262306a36Sopenharmony_ci	hw->ops.release = snd_opl3_release;
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	opl3->hwdep = hw;
52562306a36Sopenharmony_ci	opl3->seq_dev_num = seq_device;
52662306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER)
52762306a36Sopenharmony_ci	if (snd_seq_device_new(card, seq_device, SNDRV_SEQ_DEV_ID_OPL3,
52862306a36Sopenharmony_ci			       sizeof(struct snd_opl3 *), &opl3->seq_dev) >= 0) {
52962306a36Sopenharmony_ci		strcpy(opl3->seq_dev->name, hw->name);
53062306a36Sopenharmony_ci		*(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(opl3->seq_dev) = opl3;
53162306a36Sopenharmony_ci	}
53262306a36Sopenharmony_ci#endif
53362306a36Sopenharmony_ci	if (rhwdep)
53462306a36Sopenharmony_ci		*rhwdep = hw;
53562306a36Sopenharmony_ci	return 0;
53662306a36Sopenharmony_ci}
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_hwdep_new);
539