18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Driver for Gravis UltraSound Classic soundcard
48c2ecf20Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/init.h>
88c2ecf20Sopenharmony_ci#include <linux/err.h>
98c2ecf20Sopenharmony_ci#include <linux/isa.h>
108c2ecf20Sopenharmony_ci#include <linux/delay.h>
118c2ecf20Sopenharmony_ci#include <linux/time.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <asm/dma.h>
148c2ecf20Sopenharmony_ci#include <sound/core.h>
158c2ecf20Sopenharmony_ci#include <sound/gus.h>
168c2ecf20Sopenharmony_ci#define SNDRV_LEGACY_FIND_FREE_IRQ
178c2ecf20Sopenharmony_ci#define SNDRV_LEGACY_FIND_FREE_DMA
188c2ecf20Sopenharmony_ci#include <sound/initval.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define CRD_NAME "Gravis UltraSound Classic"
218c2ecf20Sopenharmony_ci#define DEV_NAME "gusclassic"
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(CRD_NAME);
248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
258c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
268c2ecf20Sopenharmony_ciMODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Classic}}");
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
298c2ecf20Sopenharmony_cistatic char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
308c2ecf20Sopenharmony_cistatic bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
318c2ecf20Sopenharmony_cistatic long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* 0x220,0x230,0x240,0x250,0x260 */
328c2ecf20Sopenharmony_cistatic int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* 3,5,9,11,12,15 */
338c2ecf20Sopenharmony_cistatic int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
348c2ecf20Sopenharmony_cistatic int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;	/* 1,3,5,6,7 */
358c2ecf20Sopenharmony_cistatic int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
368c2ecf20Sopenharmony_ci				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
378c2ecf20Sopenharmony_cistatic int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
388c2ecf20Sopenharmony_cistatic int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cimodule_param_array(index, int, NULL, 0444);
418c2ecf20Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
428c2ecf20Sopenharmony_cimodule_param_array(id, charp, NULL, 0444);
438c2ecf20Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard.");
448c2ecf20Sopenharmony_cimodule_param_array(enable, bool, NULL, 0444);
458c2ecf20Sopenharmony_ciMODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard.");
468c2ecf20Sopenharmony_cimodule_param_hw_array(port, long, ioport, NULL, 0444);
478c2ecf20Sopenharmony_ciMODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver.");
488c2ecf20Sopenharmony_cimodule_param_hw_array(irq, int, irq, NULL, 0444);
498c2ecf20Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver.");
508c2ecf20Sopenharmony_cimodule_param_hw_array(dma1, int, dma, NULL, 0444);
518c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver.");
528c2ecf20Sopenharmony_cimodule_param_hw_array(dma2, int, dma, NULL, 0444);
538c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dma2, "DMA2 # for " CRD_NAME " driver.");
548c2ecf20Sopenharmony_cimodule_param_array(joystick_dac, int, NULL, 0444);
558c2ecf20Sopenharmony_ciMODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for " CRD_NAME " driver.");
568c2ecf20Sopenharmony_cimodule_param_array(channels, int, NULL, 0444);
578c2ecf20Sopenharmony_ciMODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver.");
588c2ecf20Sopenharmony_cimodule_param_array(pcm_channels, int, NULL, 0444);
598c2ecf20Sopenharmony_ciMODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver.");
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic int snd_gusclassic_match(struct device *dev, unsigned int n)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	return enable[n];
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic int snd_gusclassic_create(struct snd_card *card,
678c2ecf20Sopenharmony_ci				 struct device *dev, unsigned int n,
688c2ecf20Sopenharmony_ci				 struct snd_gus_card **rgus)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	static const long possible_ports[] = {0x220, 0x230, 0x240, 0x250, 0x260};
718c2ecf20Sopenharmony_ci	static const int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, 4, -1};
728c2ecf20Sopenharmony_ci	static const int possible_dmas[] = {5, 6, 7, 1, 3, -1};
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	int i, error;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	if (irq[n] == SNDRV_AUTO_IRQ) {
778c2ecf20Sopenharmony_ci		irq[n] = snd_legacy_find_free_irq(possible_irqs);
788c2ecf20Sopenharmony_ci		if (irq[n] < 0) {
798c2ecf20Sopenharmony_ci			dev_err(dev, "unable to find a free IRQ\n");
808c2ecf20Sopenharmony_ci			return -EBUSY;
818c2ecf20Sopenharmony_ci		}
828c2ecf20Sopenharmony_ci	}
838c2ecf20Sopenharmony_ci	if (dma1[n] == SNDRV_AUTO_DMA) {
848c2ecf20Sopenharmony_ci		dma1[n] = snd_legacy_find_free_dma(possible_dmas);
858c2ecf20Sopenharmony_ci		if (dma1[n] < 0) {
868c2ecf20Sopenharmony_ci			dev_err(dev, "unable to find a free DMA1\n");
878c2ecf20Sopenharmony_ci			return -EBUSY;
888c2ecf20Sopenharmony_ci		}
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci	if (dma2[n] == SNDRV_AUTO_DMA) {
918c2ecf20Sopenharmony_ci		dma2[n] = snd_legacy_find_free_dma(possible_dmas);
928c2ecf20Sopenharmony_ci		if (dma2[n] < 0) {
938c2ecf20Sopenharmony_ci			dev_err(dev, "unable to find a free DMA2\n");
948c2ecf20Sopenharmony_ci			return -EBUSY;
958c2ecf20Sopenharmony_ci		}
968c2ecf20Sopenharmony_ci	}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	if (port[n] != SNDRV_AUTO_PORT)
998c2ecf20Sopenharmony_ci		return snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n],
1008c2ecf20Sopenharmony_ci				0, channels[n], pcm_channels[n], 0, rgus);
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	i = 0;
1038c2ecf20Sopenharmony_ci	do {
1048c2ecf20Sopenharmony_ci		port[n] = possible_ports[i];
1058c2ecf20Sopenharmony_ci		error = snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n],
1068c2ecf20Sopenharmony_ci				0, channels[n], pcm_channels[n], 0, rgus);
1078c2ecf20Sopenharmony_ci	} while (error < 0 && ++i < ARRAY_SIZE(possible_ports));
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	return error;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic int snd_gusclassic_detect(struct snd_gus_card *gus)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	unsigned char d;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0);	/* reset GF1 */
1178c2ecf20Sopenharmony_ci	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) {
1188c2ecf20Sopenharmony_ci		snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d);
1198c2ecf20Sopenharmony_ci		return -ENODEV;
1208c2ecf20Sopenharmony_ci	}
1218c2ecf20Sopenharmony_ci	udelay(160);
1228c2ecf20Sopenharmony_ci	snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);	/* release reset */
1238c2ecf20Sopenharmony_ci	udelay(160);
1248c2ecf20Sopenharmony_ci	if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) {
1258c2ecf20Sopenharmony_ci		snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d);
1268c2ecf20Sopenharmony_ci		return -ENODEV;
1278c2ecf20Sopenharmony_ci	}
1288c2ecf20Sopenharmony_ci	return 0;
1298c2ecf20Sopenharmony_ci}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_cistatic int snd_gusclassic_probe(struct device *dev, unsigned int n)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	struct snd_card *card;
1348c2ecf20Sopenharmony_ci	struct snd_gus_card *gus;
1358c2ecf20Sopenharmony_ci	int error;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	error = snd_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card);
1388c2ecf20Sopenharmony_ci	if (error < 0)
1398c2ecf20Sopenharmony_ci		return error;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	if (pcm_channels[n] < 2)
1428c2ecf20Sopenharmony_ci		pcm_channels[n] = 2;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	error = snd_gusclassic_create(card, dev, n, &gus);
1458c2ecf20Sopenharmony_ci	if (error < 0)
1468c2ecf20Sopenharmony_ci		goto out;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	error = snd_gusclassic_detect(gus);
1498c2ecf20Sopenharmony_ci	if (error < 0)
1508c2ecf20Sopenharmony_ci		goto out;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	gus->joystick_dac = joystick_dac[n];
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	error = snd_gus_initialize(gus);
1558c2ecf20Sopenharmony_ci	if (error < 0)
1568c2ecf20Sopenharmony_ci		goto out;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	error = -ENODEV;
1598c2ecf20Sopenharmony_ci	if (gus->max_flag || gus->ess_flag) {
1608c2ecf20Sopenharmony_ci		dev_err(dev, "GUS Classic or ACE soundcard was "
1618c2ecf20Sopenharmony_ci			"not detected at 0x%lx\n", gus->gf1.port);
1628c2ecf20Sopenharmony_ci		goto out;
1638c2ecf20Sopenharmony_ci	}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	error = snd_gf1_new_mixer(gus);
1668c2ecf20Sopenharmony_ci	if (error < 0)
1678c2ecf20Sopenharmony_ci		goto out;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	error = snd_gf1_pcm_new(gus, 0, 0);
1708c2ecf20Sopenharmony_ci	if (error < 0)
1718c2ecf20Sopenharmony_ci		goto out;
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	if (!gus->ace_flag) {
1748c2ecf20Sopenharmony_ci		error = snd_gf1_rawmidi_new(gus, 0);
1758c2ecf20Sopenharmony_ci		if (error < 0)
1768c2ecf20Sopenharmony_ci			goto out;
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	sprintf(card->longname + strlen(card->longname),
1808c2ecf20Sopenharmony_ci		" at 0x%lx, irq %d, dma %d",
1818c2ecf20Sopenharmony_ci		gus->gf1.port, gus->gf1.irq, gus->gf1.dma1);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	if (gus->gf1.dma2 >= 0)
1848c2ecf20Sopenharmony_ci		sprintf(card->longname + strlen(card->longname),
1858c2ecf20Sopenharmony_ci			"&%d", gus->gf1.dma2);
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	error = snd_card_register(card);
1888c2ecf20Sopenharmony_ci	if (error < 0)
1898c2ecf20Sopenharmony_ci		goto out;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, card);
1928c2ecf20Sopenharmony_ci	return 0;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ciout:	snd_card_free(card);
1958c2ecf20Sopenharmony_ci	return error;
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic int snd_gusclassic_remove(struct device *dev, unsigned int n)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	snd_card_free(dev_get_drvdata(dev));
2018c2ecf20Sopenharmony_ci	return 0;
2028c2ecf20Sopenharmony_ci}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_cistatic struct isa_driver snd_gusclassic_driver = {
2058c2ecf20Sopenharmony_ci	.match		= snd_gusclassic_match,
2068c2ecf20Sopenharmony_ci	.probe		= snd_gusclassic_probe,
2078c2ecf20Sopenharmony_ci	.remove		= snd_gusclassic_remove,
2088c2ecf20Sopenharmony_ci#if 0	/* FIXME */
2098c2ecf20Sopenharmony_ci	.suspend	= snd_gusclassic_suspend,
2108c2ecf20Sopenharmony_ci	.remove		= snd_gusclassic_remove,
2118c2ecf20Sopenharmony_ci#endif
2128c2ecf20Sopenharmony_ci	.driver		= {
2138c2ecf20Sopenharmony_ci		.name	= DEV_NAME
2148c2ecf20Sopenharmony_ci	}
2158c2ecf20Sopenharmony_ci};
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_cimodule_isa_driver(snd_gusclassic_driver, SNDRV_CARDS);
218