18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Driver for generic MPU-401 boards (UART mode only)
48c2ecf20Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
58c2ecf20Sopenharmony_ci *  Copyright (c) 2004 by Castet Matthieu <castet.matthieu@free.fr>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/init.h>
98c2ecf20Sopenharmony_ci#include <linux/pnp.h>
108c2ecf20Sopenharmony_ci#include <linux/err.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <sound/core.h>
148c2ecf20Sopenharmony_ci#include <sound/mpu401.h>
158c2ecf20Sopenharmony_ci#include <sound/initval.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
188c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MPU-401 UART");
198c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistatic int index[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -2}; /* exclude the first card */
228c2ecf20Sopenharmony_cistatic char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
238c2ecf20Sopenharmony_cistatic bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;	/* Enable this card */
248c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
258c2ecf20Sopenharmony_cistatic bool pnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
268c2ecf20Sopenharmony_ci#endif
278c2ecf20Sopenharmony_cistatic long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;	/* MPU-401 port number */
288c2ecf20Sopenharmony_cistatic int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;	/* MPU-401 IRQ */
298c2ecf20Sopenharmony_cistatic bool uart_enter[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cimodule_param_array(index, int, NULL, 0444);
328c2ecf20Sopenharmony_ciMODULE_PARM_DESC(index, "Index value for MPU-401 device.");
338c2ecf20Sopenharmony_cimodule_param_array(id, charp, NULL, 0444);
348c2ecf20Sopenharmony_ciMODULE_PARM_DESC(id, "ID string for MPU-401 device.");
358c2ecf20Sopenharmony_cimodule_param_array(enable, bool, NULL, 0444);
368c2ecf20Sopenharmony_ciMODULE_PARM_DESC(enable, "Enable MPU-401 device.");
378c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
388c2ecf20Sopenharmony_cimodule_param_array(pnp, bool, NULL, 0444);
398c2ecf20Sopenharmony_ciMODULE_PARM_DESC(pnp, "PnP detection for MPU-401 device.");
408c2ecf20Sopenharmony_ci#endif
418c2ecf20Sopenharmony_cimodule_param_hw_array(port, long, ioport, NULL, 0444);
428c2ecf20Sopenharmony_ciMODULE_PARM_DESC(port, "Port # for MPU-401 device.");
438c2ecf20Sopenharmony_cimodule_param_hw_array(irq, int, irq, NULL, 0444);
448c2ecf20Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ # for MPU-401 device.");
458c2ecf20Sopenharmony_cimodule_param_array(uart_enter, bool, NULL, 0444);
468c2ecf20Sopenharmony_ciMODULE_PARM_DESC(uart_enter, "Issue UART_ENTER command at open.");
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic struct platform_device *platform_devices[SNDRV_CARDS];
498c2ecf20Sopenharmony_cistatic int pnp_registered;
508c2ecf20Sopenharmony_cistatic unsigned int snd_mpu401_devices;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic int snd_mpu401_create(struct device *devptr, int dev,
538c2ecf20Sopenharmony_ci			     struct snd_card **rcard)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	struct snd_card *card;
568c2ecf20Sopenharmony_ci	int err;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	if (!uart_enter[dev])
598c2ecf20Sopenharmony_ci		snd_printk(KERN_ERR "the uart_enter option is obsolete; remove it\n");
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	*rcard = NULL;
628c2ecf20Sopenharmony_ci	err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE,
638c2ecf20Sopenharmony_ci			   0, &card);
648c2ecf20Sopenharmony_ci	if (err < 0)
658c2ecf20Sopenharmony_ci		return err;
668c2ecf20Sopenharmony_ci	strcpy(card->driver, "MPU-401 UART");
678c2ecf20Sopenharmony_ci	strcpy(card->shortname, card->driver);
688c2ecf20Sopenharmony_ci	sprintf(card->longname, "%s at %#lx, ", card->shortname, port[dev]);
698c2ecf20Sopenharmony_ci	if (irq[dev] >= 0) {
708c2ecf20Sopenharmony_ci		sprintf(card->longname + strlen(card->longname), "irq %d", irq[dev]);
718c2ecf20Sopenharmony_ci	} else {
728c2ecf20Sopenharmony_ci		strcat(card->longname, "polled");
738c2ecf20Sopenharmony_ci	}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, port[dev], 0,
768c2ecf20Sopenharmony_ci				  irq[dev], NULL);
778c2ecf20Sopenharmony_ci	if (err < 0) {
788c2ecf20Sopenharmony_ci		printk(KERN_ERR "MPU401 not detected at 0x%lx\n", port[dev]);
798c2ecf20Sopenharmony_ci		goto _err;
808c2ecf20Sopenharmony_ci	}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	*rcard = card;
838c2ecf20Sopenharmony_ci	return 0;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci _err:
868c2ecf20Sopenharmony_ci	snd_card_free(card);
878c2ecf20Sopenharmony_ci	return err;
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic int snd_mpu401_probe(struct platform_device *devptr)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	int dev = devptr->id;
938c2ecf20Sopenharmony_ci	int err;
948c2ecf20Sopenharmony_ci	struct snd_card *card;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	if (port[dev] == SNDRV_AUTO_PORT) {
978c2ecf20Sopenharmony_ci		snd_printk(KERN_ERR "specify port\n");
988c2ecf20Sopenharmony_ci		return -EINVAL;
998c2ecf20Sopenharmony_ci	}
1008c2ecf20Sopenharmony_ci	if (irq[dev] == SNDRV_AUTO_IRQ) {
1018c2ecf20Sopenharmony_ci		snd_printk(KERN_ERR "specify or disable IRQ\n");
1028c2ecf20Sopenharmony_ci		return -EINVAL;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci	err = snd_mpu401_create(&devptr->dev, dev, &card);
1058c2ecf20Sopenharmony_ci	if (err < 0)
1068c2ecf20Sopenharmony_ci		return err;
1078c2ecf20Sopenharmony_ci	if ((err = snd_card_register(card)) < 0) {
1088c2ecf20Sopenharmony_ci		snd_card_free(card);
1098c2ecf20Sopenharmony_ci		return err;
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci	platform_set_drvdata(devptr, card);
1128c2ecf20Sopenharmony_ci	return 0;
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic int snd_mpu401_remove(struct platform_device *devptr)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	snd_card_free(platform_get_drvdata(devptr));
1188c2ecf20Sopenharmony_ci	return 0;
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci#define SND_MPU401_DRIVER	"snd_mpu401"
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_cistatic struct platform_driver snd_mpu401_driver = {
1248c2ecf20Sopenharmony_ci	.probe		= snd_mpu401_probe,
1258c2ecf20Sopenharmony_ci	.remove		= snd_mpu401_remove,
1268c2ecf20Sopenharmony_ci	.driver		= {
1278c2ecf20Sopenharmony_ci		.name	= SND_MPU401_DRIVER,
1288c2ecf20Sopenharmony_ci	},
1298c2ecf20Sopenharmony_ci};
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci#define IO_EXTENT 2
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_cistatic const struct pnp_device_id snd_mpu401_pnpids[] = {
1378c2ecf20Sopenharmony_ci	{ .id = "PNPb006" },
1388c2ecf20Sopenharmony_ci	{ .id = "" }
1398c2ecf20Sopenharmony_ci};
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pnp, snd_mpu401_pnpids);
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic int snd_mpu401_pnp(int dev, struct pnp_dev *device,
1448c2ecf20Sopenharmony_ci			  const struct pnp_device_id *id)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	if (!pnp_port_valid(device, 0) ||
1478c2ecf20Sopenharmony_ci	    pnp_port_flags(device, 0) & IORESOURCE_DISABLED) {
1488c2ecf20Sopenharmony_ci		snd_printk(KERN_ERR "no PnP port\n");
1498c2ecf20Sopenharmony_ci		return -ENODEV;
1508c2ecf20Sopenharmony_ci	}
1518c2ecf20Sopenharmony_ci	if (pnp_port_len(device, 0) < IO_EXTENT) {
1528c2ecf20Sopenharmony_ci		snd_printk(KERN_ERR "PnP port length is %llu, expected %d\n",
1538c2ecf20Sopenharmony_ci			   (unsigned long long)pnp_port_len(device, 0),
1548c2ecf20Sopenharmony_ci			   IO_EXTENT);
1558c2ecf20Sopenharmony_ci		return -ENODEV;
1568c2ecf20Sopenharmony_ci	}
1578c2ecf20Sopenharmony_ci	port[dev] = pnp_port_start(device, 0);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	if (!pnp_irq_valid(device, 0) ||
1608c2ecf20Sopenharmony_ci	    pnp_irq_flags(device, 0) & IORESOURCE_DISABLED) {
1618c2ecf20Sopenharmony_ci		snd_printk(KERN_WARNING "no PnP irq, using polling\n");
1628c2ecf20Sopenharmony_ci		irq[dev] = -1;
1638c2ecf20Sopenharmony_ci	} else {
1648c2ecf20Sopenharmony_ci		irq[dev] = pnp_irq(device, 0);
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci	return 0;
1678c2ecf20Sopenharmony_ci}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_cistatic int snd_mpu401_pnp_probe(struct pnp_dev *pnp_dev,
1708c2ecf20Sopenharmony_ci				const struct pnp_device_id *id)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	static int dev;
1738c2ecf20Sopenharmony_ci	struct snd_card *card;
1748c2ecf20Sopenharmony_ci	int err;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	for ( ; dev < SNDRV_CARDS; ++dev) {
1778c2ecf20Sopenharmony_ci		if (!enable[dev] || !pnp[dev])
1788c2ecf20Sopenharmony_ci			continue;
1798c2ecf20Sopenharmony_ci		err = snd_mpu401_pnp(dev, pnp_dev, id);
1808c2ecf20Sopenharmony_ci		if (err < 0)
1818c2ecf20Sopenharmony_ci			return err;
1828c2ecf20Sopenharmony_ci		err = snd_mpu401_create(&pnp_dev->dev, dev, &card);
1838c2ecf20Sopenharmony_ci		if (err < 0)
1848c2ecf20Sopenharmony_ci			return err;
1858c2ecf20Sopenharmony_ci		if ((err = snd_card_register(card)) < 0) {
1868c2ecf20Sopenharmony_ci			snd_card_free(card);
1878c2ecf20Sopenharmony_ci			return err;
1888c2ecf20Sopenharmony_ci		}
1898c2ecf20Sopenharmony_ci		pnp_set_drvdata(pnp_dev, card);
1908c2ecf20Sopenharmony_ci		snd_mpu401_devices++;
1918c2ecf20Sopenharmony_ci		++dev;
1928c2ecf20Sopenharmony_ci		return 0;
1938c2ecf20Sopenharmony_ci	}
1948c2ecf20Sopenharmony_ci	return -ENODEV;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic void snd_mpu401_pnp_remove(struct pnp_dev *dev)
1988c2ecf20Sopenharmony_ci{
1998c2ecf20Sopenharmony_ci	struct snd_card *card = (struct snd_card *) pnp_get_drvdata(dev);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	snd_card_disconnect(card);
2028c2ecf20Sopenharmony_ci	snd_card_free_when_closed(card);
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic struct pnp_driver snd_mpu401_pnp_driver = {
2068c2ecf20Sopenharmony_ci	.name = "mpu401",
2078c2ecf20Sopenharmony_ci	.id_table = snd_mpu401_pnpids,
2088c2ecf20Sopenharmony_ci	.probe = snd_mpu401_pnp_probe,
2098c2ecf20Sopenharmony_ci	.remove = snd_mpu401_pnp_remove,
2108c2ecf20Sopenharmony_ci};
2118c2ecf20Sopenharmony_ci#else
2128c2ecf20Sopenharmony_cistatic struct pnp_driver snd_mpu401_pnp_driver;
2138c2ecf20Sopenharmony_ci#endif
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cistatic void snd_mpu401_unregister_all(void)
2168c2ecf20Sopenharmony_ci{
2178c2ecf20Sopenharmony_ci	int i;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	if (pnp_registered)
2208c2ecf20Sopenharmony_ci		pnp_unregister_driver(&snd_mpu401_pnp_driver);
2218c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(platform_devices); ++i)
2228c2ecf20Sopenharmony_ci		platform_device_unregister(platform_devices[i]);
2238c2ecf20Sopenharmony_ci	platform_driver_unregister(&snd_mpu401_driver);
2248c2ecf20Sopenharmony_ci}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_cistatic int __init alsa_card_mpu401_init(void)
2278c2ecf20Sopenharmony_ci{
2288c2ecf20Sopenharmony_ci	int i, err;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	if ((err = platform_driver_register(&snd_mpu401_driver)) < 0)
2318c2ecf20Sopenharmony_ci		return err;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	for (i = 0; i < SNDRV_CARDS; i++) {
2348c2ecf20Sopenharmony_ci		struct platform_device *device;
2358c2ecf20Sopenharmony_ci		if (! enable[i])
2368c2ecf20Sopenharmony_ci			continue;
2378c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
2388c2ecf20Sopenharmony_ci		if (pnp[i])
2398c2ecf20Sopenharmony_ci			continue;
2408c2ecf20Sopenharmony_ci#endif
2418c2ecf20Sopenharmony_ci		device = platform_device_register_simple(SND_MPU401_DRIVER,
2428c2ecf20Sopenharmony_ci							 i, NULL, 0);
2438c2ecf20Sopenharmony_ci		if (IS_ERR(device))
2448c2ecf20Sopenharmony_ci			continue;
2458c2ecf20Sopenharmony_ci		if (!platform_get_drvdata(device)) {
2468c2ecf20Sopenharmony_ci			platform_device_unregister(device);
2478c2ecf20Sopenharmony_ci			continue;
2488c2ecf20Sopenharmony_ci		}
2498c2ecf20Sopenharmony_ci		platform_devices[i] = device;
2508c2ecf20Sopenharmony_ci		snd_mpu401_devices++;
2518c2ecf20Sopenharmony_ci	}
2528c2ecf20Sopenharmony_ci	err = pnp_register_driver(&snd_mpu401_pnp_driver);
2538c2ecf20Sopenharmony_ci	if (!err)
2548c2ecf20Sopenharmony_ci		pnp_registered = 1;
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	if (!snd_mpu401_devices) {
2578c2ecf20Sopenharmony_ci#ifdef MODULE
2588c2ecf20Sopenharmony_ci		printk(KERN_ERR "MPU-401 device not found or device busy\n");
2598c2ecf20Sopenharmony_ci#endif
2608c2ecf20Sopenharmony_ci		snd_mpu401_unregister_all();
2618c2ecf20Sopenharmony_ci		return -ENODEV;
2628c2ecf20Sopenharmony_ci	}
2638c2ecf20Sopenharmony_ci	return 0;
2648c2ecf20Sopenharmony_ci}
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_cistatic void __exit alsa_card_mpu401_exit(void)
2678c2ecf20Sopenharmony_ci{
2688c2ecf20Sopenharmony_ci	snd_mpu401_unregister_all();
2698c2ecf20Sopenharmony_ci}
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_cimodule_init(alsa_card_mpu401_init)
2728c2ecf20Sopenharmony_cimodule_exit(alsa_card_mpu401_exit)
273