18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Functions for accessing OPL4 devices
48c2ecf20Sopenharmony_ci * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include "opl4_local.h"
88c2ecf20Sopenharmony_ci#include <sound/initval.h>
98c2ecf20Sopenharmony_ci#include <linux/ioport.h>
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <linux/init.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/io.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ciMODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
168c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("OPL4 driver");
178c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistatic inline void snd_opl4_wait(struct snd_opl4 *opl4)
208c2ecf20Sopenharmony_ci{
218c2ecf20Sopenharmony_ci	int timeout = 10;
228c2ecf20Sopenharmony_ci	while ((inb(opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0)
238c2ecf20Sopenharmony_ci		;
248c2ecf20Sopenharmony_ci}
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_civoid snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value)
278c2ecf20Sopenharmony_ci{
288c2ecf20Sopenharmony_ci	snd_opl4_wait(opl4);
298c2ecf20Sopenharmony_ci	outb(reg, opl4->pcm_port);
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	snd_opl4_wait(opl4);
328c2ecf20Sopenharmony_ci	outb(value, opl4->pcm_port + 1);
338c2ecf20Sopenharmony_ci}
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl4_write);
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ciu8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	snd_opl4_wait(opl4);
408c2ecf20Sopenharmony_ci	outb(reg, opl4->pcm_port);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	snd_opl4_wait(opl4);
438c2ecf20Sopenharmony_ci	return inb(opl4->pcm_port + 1);
448c2ecf20Sopenharmony_ci}
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl4_read);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_civoid snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	unsigned long flags;
518c2ecf20Sopenharmony_ci	u8 memcfg;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	spin_lock_irqsave(&opl4->reg_lock, flags);
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
568c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
598c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
608c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	snd_opl4_wait(opl4);
638c2ecf20Sopenharmony_ci	outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
648c2ecf20Sopenharmony_ci	snd_opl4_wait(opl4);
658c2ecf20Sopenharmony_ci	insb(opl4->pcm_port + 1, buf, size);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&opl4->reg_lock, flags);
708c2ecf20Sopenharmony_ci}
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl4_read_memory);
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_civoid snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	unsigned long flags;
778c2ecf20Sopenharmony_ci	u8 memcfg;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	spin_lock_irqsave(&opl4->reg_lock, flags);
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
828c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
858c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
868c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	snd_opl4_wait(opl4);
898c2ecf20Sopenharmony_ci	outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
908c2ecf20Sopenharmony_ci	snd_opl4_wait(opl4);
918c2ecf20Sopenharmony_ci	outsb(opl4->pcm_port + 1, buf, size);
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&opl4->reg_lock, flags);
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl4_write_memory);
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_cistatic void snd_opl4_enable_opl4(struct snd_opl4 *opl4)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	outb(OPL3_REG_MODE, opl4->fm_port + 2);
1038c2ecf20Sopenharmony_ci	inb(opl4->fm_port);
1048c2ecf20Sopenharmony_ci	inb(opl4->fm_port);
1058c2ecf20Sopenharmony_ci	outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3);
1068c2ecf20Sopenharmony_ci	inb(opl4->fm_port);
1078c2ecf20Sopenharmony_ci	inb(opl4->fm_port);
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic int snd_opl4_detect(struct snd_opl4 *opl4)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	u8 id1, id2;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	snd_opl4_enable_opl4(opl4);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
1178c2ecf20Sopenharmony_ci	snd_printdd("OPL4[02]=%02x\n", id1);
1188c2ecf20Sopenharmony_ci	switch (id1 & OPL4_DEVICE_ID_MASK) {
1198c2ecf20Sopenharmony_ci	case 0x20:
1208c2ecf20Sopenharmony_ci		opl4->hardware = OPL3_HW_OPL4;
1218c2ecf20Sopenharmony_ci		break;
1228c2ecf20Sopenharmony_ci	case 0x40:
1238c2ecf20Sopenharmony_ci		opl4->hardware = OPL3_HW_OPL4_ML;
1248c2ecf20Sopenharmony_ci		break;
1258c2ecf20Sopenharmony_ci	default:
1268c2ecf20Sopenharmony_ci		return -ENODEV;
1278c2ecf20Sopenharmony_ci	}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00);
1308c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff);
1318c2ecf20Sopenharmony_ci	id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM);
1328c2ecf20Sopenharmony_ci	id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM);
1338c2ecf20Sopenharmony_ci	snd_printdd("OPL4 id1=%02x id2=%02x\n", id1, id2);
1348c2ecf20Sopenharmony_ci       	if (id1 != 0x00 || id2 != 0xff)
1358c2ecf20Sopenharmony_ci		return -ENODEV;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f);
1388c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f);
1398c2ecf20Sopenharmony_ci	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00);
1408c2ecf20Sopenharmony_ci	return 0;
1418c2ecf20Sopenharmony_ci}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER)
1448c2ecf20Sopenharmony_cistatic void snd_opl4_seq_dev_free(struct snd_seq_device *seq_dev)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	struct snd_opl4 *opl4 = seq_dev->private_data;
1478c2ecf20Sopenharmony_ci	opl4->seq_dev = NULL;
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic int snd_opl4_create_seq_dev(struct snd_opl4 *opl4, int seq_device)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	opl4->seq_dev_num = seq_device;
1538c2ecf20Sopenharmony_ci	if (snd_seq_device_new(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4,
1548c2ecf20Sopenharmony_ci			       sizeof(struct snd_opl4 *), &opl4->seq_dev) >= 0) {
1558c2ecf20Sopenharmony_ci		strcpy(opl4->seq_dev->name, "OPL4 Wavetable");
1568c2ecf20Sopenharmony_ci		*(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4;
1578c2ecf20Sopenharmony_ci		opl4->seq_dev->private_data = opl4;
1588c2ecf20Sopenharmony_ci		opl4->seq_dev->private_free = snd_opl4_seq_dev_free;
1598c2ecf20Sopenharmony_ci	}
1608c2ecf20Sopenharmony_ci	return 0;
1618c2ecf20Sopenharmony_ci}
1628c2ecf20Sopenharmony_ci#endif
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic void snd_opl4_free(struct snd_opl4 *opl4)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	snd_opl4_free_proc(opl4);
1678c2ecf20Sopenharmony_ci	release_and_free_resource(opl4->res_fm_port);
1688c2ecf20Sopenharmony_ci	release_and_free_resource(opl4->res_pcm_port);
1698c2ecf20Sopenharmony_ci	kfree(opl4);
1708c2ecf20Sopenharmony_ci}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_cistatic int snd_opl4_dev_free(struct snd_device *device)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	struct snd_opl4 *opl4 = device->device_data;
1758c2ecf20Sopenharmony_ci	snd_opl4_free(opl4);
1768c2ecf20Sopenharmony_ci	return 0;
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ciint snd_opl4_create(struct snd_card *card,
1808c2ecf20Sopenharmony_ci		    unsigned long fm_port, unsigned long pcm_port,
1818c2ecf20Sopenharmony_ci		    int seq_device,
1828c2ecf20Sopenharmony_ci		    struct snd_opl3 **ropl3, struct snd_opl4 **ropl4)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	struct snd_opl4 *opl4;
1858c2ecf20Sopenharmony_ci	struct snd_opl3 *opl3;
1868c2ecf20Sopenharmony_ci	int err;
1878c2ecf20Sopenharmony_ci	static const struct snd_device_ops ops = {
1888c2ecf20Sopenharmony_ci		.dev_free = snd_opl4_dev_free
1898c2ecf20Sopenharmony_ci	};
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	if (ropl3)
1928c2ecf20Sopenharmony_ci		*ropl3 = NULL;
1938c2ecf20Sopenharmony_ci	if (ropl4)
1948c2ecf20Sopenharmony_ci		*ropl4 = NULL;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	opl4 = kzalloc(sizeof(*opl4), GFP_KERNEL);
1978c2ecf20Sopenharmony_ci	if (!opl4)
1988c2ecf20Sopenharmony_ci		return -ENOMEM;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM");
2018c2ecf20Sopenharmony_ci	opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX");
2028c2ecf20Sopenharmony_ci	if (!opl4->res_fm_port || !opl4->res_pcm_port) {
2038c2ecf20Sopenharmony_ci		snd_printk(KERN_ERR "opl4: can't grab ports 0x%lx, 0x%lx\n", fm_port, pcm_port);
2048c2ecf20Sopenharmony_ci		snd_opl4_free(opl4);
2058c2ecf20Sopenharmony_ci		return -EBUSY;
2068c2ecf20Sopenharmony_ci	}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	opl4->card = card;
2098c2ecf20Sopenharmony_ci	opl4->fm_port = fm_port;
2108c2ecf20Sopenharmony_ci	opl4->pcm_port = pcm_port;
2118c2ecf20Sopenharmony_ci	spin_lock_init(&opl4->reg_lock);
2128c2ecf20Sopenharmony_ci	mutex_init(&opl4->access_mutex);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	err = snd_opl4_detect(opl4);
2158c2ecf20Sopenharmony_ci	if (err < 0) {
2168c2ecf20Sopenharmony_ci		snd_opl4_free(opl4);
2178c2ecf20Sopenharmony_ci		snd_printd("OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port);
2188c2ecf20Sopenharmony_ci		return err;
2198c2ecf20Sopenharmony_ci	}
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops);
2228c2ecf20Sopenharmony_ci	if (err < 0) {
2238c2ecf20Sopenharmony_ci		snd_opl4_free(opl4);
2248c2ecf20Sopenharmony_ci		return err;
2258c2ecf20Sopenharmony_ci	}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3);
2288c2ecf20Sopenharmony_ci	if (err < 0) {
2298c2ecf20Sopenharmony_ci		snd_device_free(card, opl4);
2308c2ecf20Sopenharmony_ci		return err;
2318c2ecf20Sopenharmony_ci	}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	/* opl3 initialization disabled opl4, so reenable */
2348c2ecf20Sopenharmony_ci	snd_opl4_enable_opl4(opl4);
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	snd_opl4_create_mixer(opl4);
2378c2ecf20Sopenharmony_ci	snd_opl4_create_proc(opl4);
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER)
2408c2ecf20Sopenharmony_ci	opl4->seq_client = -1;
2418c2ecf20Sopenharmony_ci	if (opl4->hardware < OPL3_HW_OPL4_ML)
2428c2ecf20Sopenharmony_ci		snd_opl4_create_seq_dev(opl4, seq_device);
2438c2ecf20Sopenharmony_ci#endif
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	if (ropl3)
2468c2ecf20Sopenharmony_ci		*ropl3 = opl3;
2478c2ecf20Sopenharmony_ci	if (ropl4)
2488c2ecf20Sopenharmony_ci		*ropl4 = opl4;
2498c2ecf20Sopenharmony_ci	return 0;
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_opl4_create);
253