18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
48c2ecf20Sopenharmony_ci *                   Hannu Savolainen 1993-1996,
58c2ecf20Sopenharmony_ci *                   Rob Hooft
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci *  Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci *  Most if code is ported from OSS/Lite.
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <sound/opl3.h>
138c2ecf20Sopenharmony_ci#include <linux/io.h>
148c2ecf20Sopenharmony_ci#include <linux/delay.h>
158c2ecf20Sopenharmony_ci#include <linux/module.h>
168c2ecf20Sopenharmony_ci#include <linux/init.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci#include <linux/ioport.h>
198c2ecf20Sopenharmony_ci#include <sound/minors.h>
208c2ecf20Sopenharmony_ci#include "opl3_voice.h"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Hannu Savolainen 1993-1996, Rob Hooft");
238c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)");
248c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic void snd_opl2_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val)
278c2ecf20Sopenharmony_ci{
288c2ecf20Sopenharmony_ci	unsigned long flags;
298c2ecf20Sopenharmony_ci	unsigned long port;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	/*
328c2ecf20Sopenharmony_ci	 * The original 2-OP synth requires a quite long delay
338c2ecf20Sopenharmony_ci	 * after writing to a register.
348c2ecf20Sopenharmony_ci	 */
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	spin_lock_irqsave(&opl3->reg_lock, flags);
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	outb((unsigned char) cmd, port);
418c2ecf20Sopenharmony_ci	udelay(10);
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	outb((unsigned char) val, port + 1);
448c2ecf20Sopenharmony_ci	udelay(30);
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&opl3->reg_lock, flags);
478c2ecf20Sopenharmony_ci}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic void snd_opl3_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val)
508c2ecf20Sopenharmony_ci{
518c2ecf20Sopenharmony_ci	unsigned long flags;
528c2ecf20Sopenharmony_ci	unsigned long port;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	/*
558c2ecf20Sopenharmony_ci	 * The OPL-3 survives with just two INBs
568c2ecf20Sopenharmony_ci	 * after writing to a register.
578c2ecf20Sopenharmony_ci	 */
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	spin_lock_irqsave(&opl3->reg_lock, flags);
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	outb((unsigned char) cmd, port);
648c2ecf20Sopenharmony_ci	inb(opl3->l_port);
658c2ecf20Sopenharmony_ci	inb(opl3->l_port);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	outb((unsigned char) val, port + 1);
688c2ecf20Sopenharmony_ci	inb(opl3->l_port);
698c2ecf20Sopenharmony_ci	inb(opl3->l_port);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&opl3->reg_lock, flags);
728c2ecf20Sopenharmony_ci}
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic int snd_opl3_detect(struct snd_opl3 * opl3)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	/*
778c2ecf20Sopenharmony_ci	 * This function returns 1 if the FM chip is present at the given I/O port
788c2ecf20Sopenharmony_ci	 * The detection algorithm plays with the timer built in the FM chip and
798c2ecf20Sopenharmony_ci	 * looks for a change in the status register.
808c2ecf20Sopenharmony_ci	 *
818c2ecf20Sopenharmony_ci	 * Note! The timers of the FM chip are not connected to AdLib (and compatible)
828c2ecf20Sopenharmony_ci	 * boards.
838c2ecf20Sopenharmony_ci	 *
848c2ecf20Sopenharmony_ci	 * Note2! The chip is initialized if detected.
858c2ecf20Sopenharmony_ci	 */
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	unsigned char stat1, stat2, signature;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	/* Reset timers 1 and 2 */
908c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
918c2ecf20Sopenharmony_ci	/* Reset the IRQ of the FM chip */
928c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
938c2ecf20Sopenharmony_ci	signature = stat1 = inb(opl3->l_port);	/* Status register */
948c2ecf20Sopenharmony_ci	if ((stat1 & 0xe0) != 0x00) {	/* Should be 0x00 */
958c2ecf20Sopenharmony_ci		snd_printd("OPL3: stat1 = 0x%x\n", stat1);
968c2ecf20Sopenharmony_ci		return -ENODEV;
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci	/* Set timer1 to 0xff */
998c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 0xff);
1008c2ecf20Sopenharmony_ci	/* Unmask and start timer 1 */
1018c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER2_MASK | OPL3_TIMER1_START);
1028c2ecf20Sopenharmony_ci	/* Now we have to delay at least 80us */
1038c2ecf20Sopenharmony_ci	udelay(200);
1048c2ecf20Sopenharmony_ci	/* Read status after timers have expired */
1058c2ecf20Sopenharmony_ci	stat2 = inb(opl3->l_port);
1068c2ecf20Sopenharmony_ci	/* Stop the timers */
1078c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
1088c2ecf20Sopenharmony_ci	/* Reset the IRQ of the FM chip */
1098c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
1108c2ecf20Sopenharmony_ci	if ((stat2 & 0xe0) != 0xc0) {	/* There is no YM3812 */
1118c2ecf20Sopenharmony_ci		snd_printd("OPL3: stat2 = 0x%x\n", stat2);
1128c2ecf20Sopenharmony_ci		return -ENODEV;
1138c2ecf20Sopenharmony_ci	}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	/* If the toplevel code knows exactly the type of chip, don't try
1168c2ecf20Sopenharmony_ci	   to detect it. */
1178c2ecf20Sopenharmony_ci	if (opl3->hardware != OPL3_HW_AUTO)
1188c2ecf20Sopenharmony_ci		return 0;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	/* There is a FM chip on this address. Detect the type (OPL2 to OPL4) */
1218c2ecf20Sopenharmony_ci	if (signature == 0x06) {	/* OPL2 */
1228c2ecf20Sopenharmony_ci		opl3->hardware = OPL3_HW_OPL2;
1238c2ecf20Sopenharmony_ci	} else {
1248c2ecf20Sopenharmony_ci		/*
1258c2ecf20Sopenharmony_ci		 * If we had an OPL4 chip, opl3->hardware would have been set
1268c2ecf20Sopenharmony_ci		 * by the OPL4 driver; so we can assume OPL3 here.
1278c2ecf20Sopenharmony_ci		 */
1288c2ecf20Sopenharmony_ci		if (snd_BUG_ON(!opl3->r_port))
1298c2ecf20Sopenharmony_ci			return -ENODEV;
1308c2ecf20Sopenharmony_ci		opl3->hardware = OPL3_HW_OPL3;
1318c2ecf20Sopenharmony_ci	}
1328c2ecf20Sopenharmony_ci	return 0;
1338c2ecf20Sopenharmony_ci}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci/*
1368c2ecf20Sopenharmony_ci *  AdLib timers
1378c2ecf20Sopenharmony_ci */
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci/*
1408c2ecf20Sopenharmony_ci *  Timer 1 - 80us
1418c2ecf20Sopenharmony_ci */
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic int snd_opl3_timer1_start(struct snd_timer * timer)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	unsigned long flags;
1468c2ecf20Sopenharmony_ci	unsigned char tmp;
1478c2ecf20Sopenharmony_ci	unsigned int ticks;
1488c2ecf20Sopenharmony_ci	struct snd_opl3 *opl3;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	opl3 = snd_timer_chip(timer);
1518c2ecf20Sopenharmony_ci	spin_lock_irqsave(&opl3->timer_lock, flags);
1528c2ecf20Sopenharmony_ci	ticks = timer->sticks;
1538c2ecf20Sopenharmony_ci	tmp = (opl3->timer_enable | OPL3_TIMER1_START) & ~OPL3_TIMER1_MASK;
1548c2ecf20Sopenharmony_ci	opl3->timer_enable = tmp;
1558c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 256 - ticks);	/* timer 1 count */
1568c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* enable timer 1 IRQ */
1578c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&opl3->timer_lock, flags);
1588c2ecf20Sopenharmony_ci	return 0;
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_cistatic int snd_opl3_timer1_stop(struct snd_timer * timer)
1628c2ecf20Sopenharmony_ci{
1638c2ecf20Sopenharmony_ci	unsigned long flags;
1648c2ecf20Sopenharmony_ci	unsigned char tmp;
1658c2ecf20Sopenharmony_ci	struct snd_opl3 *opl3;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	opl3 = snd_timer_chip(timer);
1688c2ecf20Sopenharmony_ci	spin_lock_irqsave(&opl3->timer_lock, flags);
1698c2ecf20Sopenharmony_ci	tmp = (opl3->timer_enable | OPL3_TIMER1_MASK) & ~OPL3_TIMER1_START;
1708c2ecf20Sopenharmony_ci	opl3->timer_enable = tmp;
1718c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* disable timer #1 */
1728c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&opl3->timer_lock, flags);
1738c2ecf20Sopenharmony_ci	return 0;
1748c2ecf20Sopenharmony_ci}
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci/*
1778c2ecf20Sopenharmony_ci *  Timer 2 - 320us
1788c2ecf20Sopenharmony_ci */
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic int snd_opl3_timer2_start(struct snd_timer * timer)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	unsigned long flags;
1838c2ecf20Sopenharmony_ci	unsigned char tmp;
1848c2ecf20Sopenharmony_ci	unsigned int ticks;
1858c2ecf20Sopenharmony_ci	struct snd_opl3 *opl3;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	opl3 = snd_timer_chip(timer);
1888c2ecf20Sopenharmony_ci	spin_lock_irqsave(&opl3->timer_lock, flags);
1898c2ecf20Sopenharmony_ci	ticks = timer->sticks;
1908c2ecf20Sopenharmony_ci	tmp = (opl3->timer_enable | OPL3_TIMER2_START) & ~OPL3_TIMER2_MASK;
1918c2ecf20Sopenharmony_ci	opl3->timer_enable = tmp;
1928c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER2, 256 - ticks);	/* timer 1 count */
1938c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* enable timer 1 IRQ */
1948c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&opl3->timer_lock, flags);
1958c2ecf20Sopenharmony_ci	return 0;
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic int snd_opl3_timer2_stop(struct snd_timer * timer)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	unsigned long flags;
2018c2ecf20Sopenharmony_ci	unsigned char tmp;
2028c2ecf20Sopenharmony_ci	struct snd_opl3 *opl3;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	opl3 = snd_timer_chip(timer);
2058c2ecf20Sopenharmony_ci	spin_lock_irqsave(&opl3->timer_lock, flags);
2068c2ecf20Sopenharmony_ci	tmp = (opl3->timer_enable | OPL3_TIMER2_MASK) & ~OPL3_TIMER2_START;
2078c2ecf20Sopenharmony_ci	opl3->timer_enable = tmp;
2088c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp);	/* disable timer #1 */
2098c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&opl3->timer_lock, flags);
2108c2ecf20Sopenharmony_ci	return 0;
2118c2ecf20Sopenharmony_ci}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci/*
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci */
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_cistatic const struct snd_timer_hardware snd_opl3_timer1 =
2188c2ecf20Sopenharmony_ci{
2198c2ecf20Sopenharmony_ci	.flags =	SNDRV_TIMER_HW_STOP,
2208c2ecf20Sopenharmony_ci	.resolution =	80000,
2218c2ecf20Sopenharmony_ci	.ticks =	256,
2228c2ecf20Sopenharmony_ci	.start =	snd_opl3_timer1_start,
2238c2ecf20Sopenharmony_ci	.stop =		snd_opl3_timer1_stop,
2248c2ecf20Sopenharmony_ci};
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_cistatic const struct snd_timer_hardware snd_opl3_timer2 =
2278c2ecf20Sopenharmony_ci{
2288c2ecf20Sopenharmony_ci	.flags =	SNDRV_TIMER_HW_STOP,
2298c2ecf20Sopenharmony_ci	.resolution =	320000,
2308c2ecf20Sopenharmony_ci	.ticks =	256,
2318c2ecf20Sopenharmony_ci	.start =	snd_opl3_timer2_start,
2328c2ecf20Sopenharmony_ci	.stop =		snd_opl3_timer2_stop,
2338c2ecf20Sopenharmony_ci};
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_cistatic int snd_opl3_timer1_init(struct snd_opl3 * opl3, int timer_no)
2368c2ecf20Sopenharmony_ci{
2378c2ecf20Sopenharmony_ci	struct snd_timer *timer = NULL;
2388c2ecf20Sopenharmony_ci	struct snd_timer_id tid;
2398c2ecf20Sopenharmony_ci	int err;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
2428c2ecf20Sopenharmony_ci	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
2438c2ecf20Sopenharmony_ci	tid.card = opl3->card->number;
2448c2ecf20Sopenharmony_ci	tid.device = timer_no;
2458c2ecf20Sopenharmony_ci	tid.subdevice = 0;
2468c2ecf20Sopenharmony_ci	if ((err = snd_timer_new(opl3->card, "AdLib timer #1", &tid, &timer)) >= 0) {
2478c2ecf20Sopenharmony_ci		strcpy(timer->name, "AdLib timer #1");
2488c2ecf20Sopenharmony_ci		timer->private_data = opl3;
2498c2ecf20Sopenharmony_ci		timer->hw = snd_opl3_timer1;
2508c2ecf20Sopenharmony_ci	}
2518c2ecf20Sopenharmony_ci	opl3->timer1 = timer;
2528c2ecf20Sopenharmony_ci	return err;
2538c2ecf20Sopenharmony_ci}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistatic int snd_opl3_timer2_init(struct snd_opl3 * opl3, int timer_no)
2568c2ecf20Sopenharmony_ci{
2578c2ecf20Sopenharmony_ci	struct snd_timer *timer = NULL;
2588c2ecf20Sopenharmony_ci	struct snd_timer_id tid;
2598c2ecf20Sopenharmony_ci	int err;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	tid.dev_class = SNDRV_TIMER_CLASS_CARD;
2628c2ecf20Sopenharmony_ci	tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
2638c2ecf20Sopenharmony_ci	tid.card = opl3->card->number;
2648c2ecf20Sopenharmony_ci	tid.device = timer_no;
2658c2ecf20Sopenharmony_ci	tid.subdevice = 0;
2668c2ecf20Sopenharmony_ci	if ((err = snd_timer_new(opl3->card, "AdLib timer #2", &tid, &timer)) >= 0) {
2678c2ecf20Sopenharmony_ci		strcpy(timer->name, "AdLib timer #2");
2688c2ecf20Sopenharmony_ci		timer->private_data = opl3;
2698c2ecf20Sopenharmony_ci		timer->hw = snd_opl3_timer2;
2708c2ecf20Sopenharmony_ci	}
2718c2ecf20Sopenharmony_ci	opl3->timer2 = timer;
2728c2ecf20Sopenharmony_ci	return err;
2738c2ecf20Sopenharmony_ci}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci/*
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci */
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_civoid snd_opl3_interrupt(struct snd_hwdep * hw)
2808c2ecf20Sopenharmony_ci{
2818c2ecf20Sopenharmony_ci	unsigned char status;
2828c2ecf20Sopenharmony_ci	struct snd_opl3 *opl3;
2838c2ecf20Sopenharmony_ci	struct snd_timer *timer;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	if (hw == NULL)
2868c2ecf20Sopenharmony_ci		return;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	opl3 = hw->private_data;
2898c2ecf20Sopenharmony_ci	status = inb(opl3->l_port);
2908c2ecf20Sopenharmony_ci#if 0
2918c2ecf20Sopenharmony_ci	snd_printk(KERN_DEBUG "AdLib IRQ status = 0x%x\n", status);
2928c2ecf20Sopenharmony_ci#endif
2938c2ecf20Sopenharmony_ci	if (!(status & 0x80))
2948c2ecf20Sopenharmony_ci		return;
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	if (status & 0x40) {
2978c2ecf20Sopenharmony_ci		timer = opl3->timer1;
2988c2ecf20Sopenharmony_ci		snd_timer_interrupt(timer, timer->sticks);
2998c2ecf20Sopenharmony_ci	}
3008c2ecf20Sopenharmony_ci	if (status & 0x20) {
3018c2ecf20Sopenharmony_ci		timer = opl3->timer2;
3028c2ecf20Sopenharmony_ci		snd_timer_interrupt(timer, timer->sticks);
3038c2ecf20Sopenharmony_ci	}
3048c2ecf20Sopenharmony_ci}
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_interrupt);
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci/*
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci */
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_cistatic int snd_opl3_free(struct snd_opl3 *opl3)
3138c2ecf20Sopenharmony_ci{
3148c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!opl3))
3158c2ecf20Sopenharmony_ci		return -ENXIO;
3168c2ecf20Sopenharmony_ci	if (opl3->private_free)
3178c2ecf20Sopenharmony_ci		opl3->private_free(opl3);
3188c2ecf20Sopenharmony_ci	snd_opl3_clear_patches(opl3);
3198c2ecf20Sopenharmony_ci	release_and_free_resource(opl3->res_l_port);
3208c2ecf20Sopenharmony_ci	release_and_free_resource(opl3->res_r_port);
3218c2ecf20Sopenharmony_ci	kfree(opl3);
3228c2ecf20Sopenharmony_ci	return 0;
3238c2ecf20Sopenharmony_ci}
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_cistatic int snd_opl3_dev_free(struct snd_device *device)
3268c2ecf20Sopenharmony_ci{
3278c2ecf20Sopenharmony_ci	struct snd_opl3 *opl3 = device->device_data;
3288c2ecf20Sopenharmony_ci	return snd_opl3_free(opl3);
3298c2ecf20Sopenharmony_ci}
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ciint snd_opl3_new(struct snd_card *card,
3328c2ecf20Sopenharmony_ci		 unsigned short hardware,
3338c2ecf20Sopenharmony_ci		 struct snd_opl3 **ropl3)
3348c2ecf20Sopenharmony_ci{
3358c2ecf20Sopenharmony_ci	static const struct snd_device_ops ops = {
3368c2ecf20Sopenharmony_ci		.dev_free = snd_opl3_dev_free,
3378c2ecf20Sopenharmony_ci	};
3388c2ecf20Sopenharmony_ci	struct snd_opl3 *opl3;
3398c2ecf20Sopenharmony_ci	int err;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	*ropl3 = NULL;
3428c2ecf20Sopenharmony_ci	opl3 = kzalloc(sizeof(*opl3), GFP_KERNEL);
3438c2ecf20Sopenharmony_ci	if (!opl3)
3448c2ecf20Sopenharmony_ci		return -ENOMEM;
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ci	opl3->card = card;
3478c2ecf20Sopenharmony_ci	opl3->hardware = hardware;
3488c2ecf20Sopenharmony_ci	spin_lock_init(&opl3->reg_lock);
3498c2ecf20Sopenharmony_ci	spin_lock_init(&opl3->timer_lock);
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_ci	if ((err = snd_device_new(card, SNDRV_DEV_CODEC, opl3, &ops)) < 0) {
3528c2ecf20Sopenharmony_ci		snd_opl3_free(opl3);
3538c2ecf20Sopenharmony_ci		return err;
3548c2ecf20Sopenharmony_ci	}
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	*ropl3 = opl3;
3578c2ecf20Sopenharmony_ci	return 0;
3588c2ecf20Sopenharmony_ci}
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_new);
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ciint snd_opl3_init(struct snd_opl3 *opl3)
3638c2ecf20Sopenharmony_ci{
3648c2ecf20Sopenharmony_ci	if (! opl3->command) {
3658c2ecf20Sopenharmony_ci		printk(KERN_ERR "snd_opl3_init: command not defined!\n");
3668c2ecf20Sopenharmony_ci		return -EINVAL;
3678c2ecf20Sopenharmony_ci	}
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT);
3708c2ecf20Sopenharmony_ci	/* Melodic mode */
3718c2ecf20Sopenharmony_ci	opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00);
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	switch (opl3->hardware & OPL3_HW_MASK) {
3748c2ecf20Sopenharmony_ci	case OPL3_HW_OPL2:
3758c2ecf20Sopenharmony_ci		opl3->max_voices = MAX_OPL2_VOICES;
3768c2ecf20Sopenharmony_ci		break;
3778c2ecf20Sopenharmony_ci	case OPL3_HW_OPL3:
3788c2ecf20Sopenharmony_ci	case OPL3_HW_OPL4:
3798c2ecf20Sopenharmony_ci		opl3->max_voices = MAX_OPL3_VOICES;
3808c2ecf20Sopenharmony_ci		/* Enter OPL3 mode */
3818c2ecf20Sopenharmony_ci		opl3->command(opl3, OPL3_RIGHT | OPL3_REG_MODE, OPL3_OPL3_ENABLE);
3828c2ecf20Sopenharmony_ci	}
3838c2ecf20Sopenharmony_ci	return 0;
3848c2ecf20Sopenharmony_ci}
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_init);
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ciint snd_opl3_create(struct snd_card *card,
3898c2ecf20Sopenharmony_ci		    unsigned long l_port,
3908c2ecf20Sopenharmony_ci		    unsigned long r_port,
3918c2ecf20Sopenharmony_ci		    unsigned short hardware,
3928c2ecf20Sopenharmony_ci		    int integrated,
3938c2ecf20Sopenharmony_ci		    struct snd_opl3 ** ropl3)
3948c2ecf20Sopenharmony_ci{
3958c2ecf20Sopenharmony_ci	struct snd_opl3 *opl3;
3968c2ecf20Sopenharmony_ci	int err;
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_ci	*ropl3 = NULL;
3998c2ecf20Sopenharmony_ci	if ((err = snd_opl3_new(card, hardware, &opl3)) < 0)
4008c2ecf20Sopenharmony_ci		return err;
4018c2ecf20Sopenharmony_ci	if (! integrated) {
4028c2ecf20Sopenharmony_ci		if ((opl3->res_l_port = request_region(l_port, 2, "OPL2/3 (left)")) == NULL) {
4038c2ecf20Sopenharmony_ci			snd_printk(KERN_ERR "opl3: can't grab left port 0x%lx\n", l_port);
4048c2ecf20Sopenharmony_ci			snd_device_free(card, opl3);
4058c2ecf20Sopenharmony_ci			return -EBUSY;
4068c2ecf20Sopenharmony_ci		}
4078c2ecf20Sopenharmony_ci		if (r_port != 0 &&
4088c2ecf20Sopenharmony_ci		    (opl3->res_r_port = request_region(r_port, 2, "OPL2/3 (right)")) == NULL) {
4098c2ecf20Sopenharmony_ci			snd_printk(KERN_ERR "opl3: can't grab right port 0x%lx\n", r_port);
4108c2ecf20Sopenharmony_ci			snd_device_free(card, opl3);
4118c2ecf20Sopenharmony_ci			return -EBUSY;
4128c2ecf20Sopenharmony_ci		}
4138c2ecf20Sopenharmony_ci	}
4148c2ecf20Sopenharmony_ci	opl3->l_port = l_port;
4158c2ecf20Sopenharmony_ci	opl3->r_port = r_port;
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	switch (opl3->hardware) {
4188c2ecf20Sopenharmony_ci	/* some hardware doesn't support timers */
4198c2ecf20Sopenharmony_ci	case OPL3_HW_OPL3_SV:
4208c2ecf20Sopenharmony_ci	case OPL3_HW_OPL3_CS:
4218c2ecf20Sopenharmony_ci	case OPL3_HW_OPL3_FM801:
4228c2ecf20Sopenharmony_ci		opl3->command = &snd_opl3_command;
4238c2ecf20Sopenharmony_ci		break;
4248c2ecf20Sopenharmony_ci	default:
4258c2ecf20Sopenharmony_ci		opl3->command = &snd_opl2_command;
4268c2ecf20Sopenharmony_ci		if ((err = snd_opl3_detect(opl3)) < 0) {
4278c2ecf20Sopenharmony_ci			snd_printd("OPL2/3 chip not detected at 0x%lx/0x%lx\n",
4288c2ecf20Sopenharmony_ci				   opl3->l_port, opl3->r_port);
4298c2ecf20Sopenharmony_ci			snd_device_free(card, opl3);
4308c2ecf20Sopenharmony_ci			return err;
4318c2ecf20Sopenharmony_ci		}
4328c2ecf20Sopenharmony_ci		/* detect routine returns correct hardware type */
4338c2ecf20Sopenharmony_ci		switch (opl3->hardware & OPL3_HW_MASK) {
4348c2ecf20Sopenharmony_ci		case OPL3_HW_OPL3:
4358c2ecf20Sopenharmony_ci		case OPL3_HW_OPL4:
4368c2ecf20Sopenharmony_ci			opl3->command = &snd_opl3_command;
4378c2ecf20Sopenharmony_ci		}
4388c2ecf20Sopenharmony_ci	}
4398c2ecf20Sopenharmony_ci
4408c2ecf20Sopenharmony_ci	snd_opl3_init(opl3);
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	*ropl3 = opl3;
4438c2ecf20Sopenharmony_ci	return 0;
4448c2ecf20Sopenharmony_ci}
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_create);
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ciint snd_opl3_timer_new(struct snd_opl3 * opl3, int timer1_dev, int timer2_dev)
4498c2ecf20Sopenharmony_ci{
4508c2ecf20Sopenharmony_ci	int err;
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	if (timer1_dev >= 0)
4538c2ecf20Sopenharmony_ci		if ((err = snd_opl3_timer1_init(opl3, timer1_dev)) < 0)
4548c2ecf20Sopenharmony_ci			return err;
4558c2ecf20Sopenharmony_ci	if (timer2_dev >= 0) {
4568c2ecf20Sopenharmony_ci		if ((err = snd_opl3_timer2_init(opl3, timer2_dev)) < 0) {
4578c2ecf20Sopenharmony_ci			snd_device_free(opl3->card, opl3->timer1);
4588c2ecf20Sopenharmony_ci			opl3->timer1 = NULL;
4598c2ecf20Sopenharmony_ci			return err;
4608c2ecf20Sopenharmony_ci		}
4618c2ecf20Sopenharmony_ci	}
4628c2ecf20Sopenharmony_ci	return 0;
4638c2ecf20Sopenharmony_ci}
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_timer_new);
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ciint snd_opl3_hwdep_new(struct snd_opl3 * opl3,
4688c2ecf20Sopenharmony_ci		       int device, int seq_device,
4698c2ecf20Sopenharmony_ci		       struct snd_hwdep ** rhwdep)
4708c2ecf20Sopenharmony_ci{
4718c2ecf20Sopenharmony_ci	struct snd_hwdep *hw;
4728c2ecf20Sopenharmony_ci	struct snd_card *card = opl3->card;
4738c2ecf20Sopenharmony_ci	int err;
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	if (rhwdep)
4768c2ecf20Sopenharmony_ci		*rhwdep = NULL;
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_ci	/* create hardware dependent device (direct FM) */
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci	if ((err = snd_hwdep_new(card, "OPL2/OPL3", device, &hw)) < 0) {
4818c2ecf20Sopenharmony_ci		snd_device_free(card, opl3);
4828c2ecf20Sopenharmony_ci		return err;
4838c2ecf20Sopenharmony_ci	}
4848c2ecf20Sopenharmony_ci	hw->private_data = opl3;
4858c2ecf20Sopenharmony_ci	hw->exclusive = 1;
4868c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_OSSEMUL
4878c2ecf20Sopenharmony_ci	if (device == 0)
4888c2ecf20Sopenharmony_ci		hw->oss_type = SNDRV_OSS_DEVICE_TYPE_DMFM;
4898c2ecf20Sopenharmony_ci#endif
4908c2ecf20Sopenharmony_ci	strcpy(hw->name, hw->id);
4918c2ecf20Sopenharmony_ci	switch (opl3->hardware & OPL3_HW_MASK) {
4928c2ecf20Sopenharmony_ci	case OPL3_HW_OPL2:
4938c2ecf20Sopenharmony_ci		strcpy(hw->name, "OPL2 FM");
4948c2ecf20Sopenharmony_ci		hw->iface = SNDRV_HWDEP_IFACE_OPL2;
4958c2ecf20Sopenharmony_ci		break;
4968c2ecf20Sopenharmony_ci	case OPL3_HW_OPL3:
4978c2ecf20Sopenharmony_ci		strcpy(hw->name, "OPL3 FM");
4988c2ecf20Sopenharmony_ci		hw->iface = SNDRV_HWDEP_IFACE_OPL3;
4998c2ecf20Sopenharmony_ci		break;
5008c2ecf20Sopenharmony_ci	case OPL3_HW_OPL4:
5018c2ecf20Sopenharmony_ci		strcpy(hw->name, "OPL4 FM");
5028c2ecf20Sopenharmony_ci		hw->iface = SNDRV_HWDEP_IFACE_OPL4;
5038c2ecf20Sopenharmony_ci		break;
5048c2ecf20Sopenharmony_ci	}
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci	/* operators - only ioctl */
5078c2ecf20Sopenharmony_ci	hw->ops.open = snd_opl3_open;
5088c2ecf20Sopenharmony_ci	hw->ops.ioctl = snd_opl3_ioctl;
5098c2ecf20Sopenharmony_ci	hw->ops.write = snd_opl3_write;
5108c2ecf20Sopenharmony_ci	hw->ops.release = snd_opl3_release;
5118c2ecf20Sopenharmony_ci
5128c2ecf20Sopenharmony_ci	opl3->hwdep = hw;
5138c2ecf20Sopenharmony_ci	opl3->seq_dev_num = seq_device;
5148c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER)
5158c2ecf20Sopenharmony_ci	if (snd_seq_device_new(card, seq_device, SNDRV_SEQ_DEV_ID_OPL3,
5168c2ecf20Sopenharmony_ci			       sizeof(struct snd_opl3 *), &opl3->seq_dev) >= 0) {
5178c2ecf20Sopenharmony_ci		strcpy(opl3->seq_dev->name, hw->name);
5188c2ecf20Sopenharmony_ci		*(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(opl3->seq_dev) = opl3;
5198c2ecf20Sopenharmony_ci	}
5208c2ecf20Sopenharmony_ci#endif
5218c2ecf20Sopenharmony_ci	if (rhwdep)
5228c2ecf20Sopenharmony_ci		*rhwdep = hw;
5238c2ecf20Sopenharmony_ci	return 0;
5248c2ecf20Sopenharmony_ci}
5258c2ecf20Sopenharmony_ci
5268c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl3_hwdep_new);
527