162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Functions for accessing OPL4 devices 462306a36Sopenharmony_ci * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include "opl4_local.h" 862306a36Sopenharmony_ci#include <sound/initval.h> 962306a36Sopenharmony_ci#include <linux/ioport.h> 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/io.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ciMODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); 1662306a36Sopenharmony_ciMODULE_DESCRIPTION("OPL4 driver"); 1762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistatic inline void snd_opl4_wait(struct snd_opl4 *opl4) 2062306a36Sopenharmony_ci{ 2162306a36Sopenharmony_ci int timeout = 10; 2262306a36Sopenharmony_ci while ((inb(opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0) 2362306a36Sopenharmony_ci ; 2462306a36Sopenharmony_ci} 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_civoid snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci snd_opl4_wait(opl4); 2962306a36Sopenharmony_ci outb(reg, opl4->pcm_port); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci snd_opl4_wait(opl4); 3262306a36Sopenharmony_ci outb(value, opl4->pcm_port + 1); 3362306a36Sopenharmony_ci} 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl4_write); 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ciu8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg) 3862306a36Sopenharmony_ci{ 3962306a36Sopenharmony_ci snd_opl4_wait(opl4); 4062306a36Sopenharmony_ci outb(reg, opl4->pcm_port); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci snd_opl4_wait(opl4); 4362306a36Sopenharmony_ci return inb(opl4->pcm_port + 1); 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl4_read); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_civoid snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci unsigned long flags; 5162306a36Sopenharmony_ci u8 memcfg; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci spin_lock_irqsave(&opl4->reg_lock, flags); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); 5662306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16); 5962306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8); 6062306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci snd_opl4_wait(opl4); 6362306a36Sopenharmony_ci outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port); 6462306a36Sopenharmony_ci snd_opl4_wait(opl4); 6562306a36Sopenharmony_ci insb(opl4->pcm_port + 1, buf, size); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci spin_unlock_irqrestore(&opl4->reg_lock, flags); 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl4_read_memory); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_civoid snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci unsigned long flags; 7762306a36Sopenharmony_ci u8 memcfg; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci spin_lock_irqsave(&opl4->reg_lock, flags); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); 8262306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16); 8562306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8); 8662306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci snd_opl4_wait(opl4); 8962306a36Sopenharmony_ci outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port); 9062306a36Sopenharmony_ci snd_opl4_wait(opl4); 9162306a36Sopenharmony_ci outsb(opl4->pcm_port + 1, buf, size); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci spin_unlock_irqrestore(&opl4->reg_lock, flags); 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl4_write_memory); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic void snd_opl4_enable_opl4(struct snd_opl4 *opl4) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci outb(OPL3_REG_MODE, opl4->fm_port + 2); 10362306a36Sopenharmony_ci inb(opl4->fm_port); 10462306a36Sopenharmony_ci inb(opl4->fm_port); 10562306a36Sopenharmony_ci outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3); 10662306a36Sopenharmony_ci inb(opl4->fm_port); 10762306a36Sopenharmony_ci inb(opl4->fm_port); 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic int snd_opl4_detect(struct snd_opl4 *opl4) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci u8 id1, id2; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci snd_opl4_enable_opl4(opl4); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); 11762306a36Sopenharmony_ci snd_printdd("OPL4[02]=%02x\n", id1); 11862306a36Sopenharmony_ci switch (id1 & OPL4_DEVICE_ID_MASK) { 11962306a36Sopenharmony_ci case 0x20: 12062306a36Sopenharmony_ci opl4->hardware = OPL3_HW_OPL4; 12162306a36Sopenharmony_ci break; 12262306a36Sopenharmony_ci case 0x40: 12362306a36Sopenharmony_ci opl4->hardware = OPL3_HW_OPL4_ML; 12462306a36Sopenharmony_ci break; 12562306a36Sopenharmony_ci default: 12662306a36Sopenharmony_ci return -ENODEV; 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00); 13062306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff); 13162306a36Sopenharmony_ci id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM); 13262306a36Sopenharmony_ci id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM); 13362306a36Sopenharmony_ci snd_printdd("OPL4 id1=%02x id2=%02x\n", id1, id2); 13462306a36Sopenharmony_ci if (id1 != 0x00 || id2 != 0xff) 13562306a36Sopenharmony_ci return -ENODEV; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f); 13862306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f); 13962306a36Sopenharmony_ci snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00); 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER) 14462306a36Sopenharmony_cistatic void snd_opl4_seq_dev_free(struct snd_seq_device *seq_dev) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci struct snd_opl4 *opl4 = seq_dev->private_data; 14762306a36Sopenharmony_ci opl4->seq_dev = NULL; 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic int snd_opl4_create_seq_dev(struct snd_opl4 *opl4, int seq_device) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci opl4->seq_dev_num = seq_device; 15362306a36Sopenharmony_ci if (snd_seq_device_new(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4, 15462306a36Sopenharmony_ci sizeof(struct snd_opl4 *), &opl4->seq_dev) >= 0) { 15562306a36Sopenharmony_ci strcpy(opl4->seq_dev->name, "OPL4 Wavetable"); 15662306a36Sopenharmony_ci *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4; 15762306a36Sopenharmony_ci opl4->seq_dev->private_data = opl4; 15862306a36Sopenharmony_ci opl4->seq_dev->private_free = snd_opl4_seq_dev_free; 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci#endif 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic void snd_opl4_free(struct snd_opl4 *opl4) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci snd_opl4_free_proc(opl4); 16762306a36Sopenharmony_ci release_and_free_resource(opl4->res_fm_port); 16862306a36Sopenharmony_ci release_and_free_resource(opl4->res_pcm_port); 16962306a36Sopenharmony_ci kfree(opl4); 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic int snd_opl4_dev_free(struct snd_device *device) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci struct snd_opl4 *opl4 = device->device_data; 17562306a36Sopenharmony_ci snd_opl4_free(opl4); 17662306a36Sopenharmony_ci return 0; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ciint snd_opl4_create(struct snd_card *card, 18062306a36Sopenharmony_ci unsigned long fm_port, unsigned long pcm_port, 18162306a36Sopenharmony_ci int seq_device, 18262306a36Sopenharmony_ci struct snd_opl3 **ropl3, struct snd_opl4 **ropl4) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci struct snd_opl4 *opl4; 18562306a36Sopenharmony_ci struct snd_opl3 *opl3; 18662306a36Sopenharmony_ci int err; 18762306a36Sopenharmony_ci static const struct snd_device_ops ops = { 18862306a36Sopenharmony_ci .dev_free = snd_opl4_dev_free 18962306a36Sopenharmony_ci }; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci if (ropl3) 19262306a36Sopenharmony_ci *ropl3 = NULL; 19362306a36Sopenharmony_ci if (ropl4) 19462306a36Sopenharmony_ci *ropl4 = NULL; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci opl4 = kzalloc(sizeof(*opl4), GFP_KERNEL); 19762306a36Sopenharmony_ci if (!opl4) 19862306a36Sopenharmony_ci return -ENOMEM; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM"); 20162306a36Sopenharmony_ci opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX"); 20262306a36Sopenharmony_ci if (!opl4->res_fm_port || !opl4->res_pcm_port) { 20362306a36Sopenharmony_ci snd_printk(KERN_ERR "opl4: can't grab ports 0x%lx, 0x%lx\n", fm_port, pcm_port); 20462306a36Sopenharmony_ci snd_opl4_free(opl4); 20562306a36Sopenharmony_ci return -EBUSY; 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci opl4->card = card; 20962306a36Sopenharmony_ci opl4->fm_port = fm_port; 21062306a36Sopenharmony_ci opl4->pcm_port = pcm_port; 21162306a36Sopenharmony_ci spin_lock_init(&opl4->reg_lock); 21262306a36Sopenharmony_ci mutex_init(&opl4->access_mutex); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci err = snd_opl4_detect(opl4); 21562306a36Sopenharmony_ci if (err < 0) { 21662306a36Sopenharmony_ci snd_opl4_free(opl4); 21762306a36Sopenharmony_ci snd_printd("OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port); 21862306a36Sopenharmony_ci return err; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops); 22262306a36Sopenharmony_ci if (err < 0) { 22362306a36Sopenharmony_ci snd_opl4_free(opl4); 22462306a36Sopenharmony_ci return err; 22562306a36Sopenharmony_ci } 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3); 22862306a36Sopenharmony_ci if (err < 0) { 22962306a36Sopenharmony_ci snd_device_free(card, opl4); 23062306a36Sopenharmony_ci return err; 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci /* opl3 initialization disabled opl4, so reenable */ 23462306a36Sopenharmony_ci snd_opl4_enable_opl4(opl4); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci snd_opl4_create_mixer(opl4); 23762306a36Sopenharmony_ci snd_opl4_create_proc(opl4); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER) 24062306a36Sopenharmony_ci opl4->seq_client = -1; 24162306a36Sopenharmony_ci if (opl4->hardware < OPL3_HW_OPL4_ML) 24262306a36Sopenharmony_ci snd_opl4_create_seq_dev(opl4, seq_device); 24362306a36Sopenharmony_ci#endif 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci if (ropl3) 24662306a36Sopenharmony_ci *ropl3 = opl3; 24762306a36Sopenharmony_ci if (ropl4) 24862306a36Sopenharmony_ci *ropl4 = opl4; 24962306a36Sopenharmony_ci return 0; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ciEXPORT_SYMBOL(snd_opl4_create); 253