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