Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Functions for accessing OPL4 devices
0004  * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
0005  */
0006 
0007 #include "opl4_local.h"
0008 #include <sound/initval.h>
0009 #include <linux/ioport.h>
0010 #include <linux/slab.h>
0011 #include <linux/init.h>
0012 #include <linux/module.h>
0013 #include <linux/io.h>
0014 
0015 MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
0016 MODULE_DESCRIPTION("OPL4 driver");
0017 MODULE_LICENSE("GPL");
0018 
0019 static inline void snd_opl4_wait(struct snd_opl4 *opl4)
0020 {
0021     int timeout = 10;
0022     while ((inb(opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0)
0023         ;
0024 }
0025 
0026 void snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value)
0027 {
0028     snd_opl4_wait(opl4);
0029     outb(reg, opl4->pcm_port);
0030 
0031     snd_opl4_wait(opl4);
0032     outb(value, opl4->pcm_port + 1);
0033 }
0034 
0035 EXPORT_SYMBOL(snd_opl4_write);
0036 
0037 u8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg)
0038 {
0039     snd_opl4_wait(opl4);
0040     outb(reg, opl4->pcm_port);
0041 
0042     snd_opl4_wait(opl4);
0043     return inb(opl4->pcm_port + 1);
0044 }
0045 
0046 EXPORT_SYMBOL(snd_opl4_read);
0047 
0048 void snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size)
0049 {
0050     unsigned long flags;
0051     u8 memcfg;
0052 
0053     spin_lock_irqsave(&opl4->reg_lock, flags);
0054 
0055     memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
0056     snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
0057 
0058     snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
0059     snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
0060     snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
0061 
0062     snd_opl4_wait(opl4);
0063     outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
0064     snd_opl4_wait(opl4);
0065     insb(opl4->pcm_port + 1, buf, size);
0066 
0067     snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
0068 
0069     spin_unlock_irqrestore(&opl4->reg_lock, flags);
0070 }
0071 
0072 EXPORT_SYMBOL(snd_opl4_read_memory);
0073 
0074 void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size)
0075 {
0076     unsigned long flags;
0077     u8 memcfg;
0078 
0079     spin_lock_irqsave(&opl4->reg_lock, flags);
0080 
0081     memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
0082     snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
0083 
0084     snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
0085     snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
0086     snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
0087 
0088     snd_opl4_wait(opl4);
0089     outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
0090     snd_opl4_wait(opl4);
0091     outsb(opl4->pcm_port + 1, buf, size);
0092 
0093     snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
0094 
0095     spin_unlock_irqrestore(&opl4->reg_lock, flags);
0096 }
0097 
0098 EXPORT_SYMBOL(snd_opl4_write_memory);
0099 
0100 static void snd_opl4_enable_opl4(struct snd_opl4 *opl4)
0101 {
0102     outb(OPL3_REG_MODE, opl4->fm_port + 2);
0103     inb(opl4->fm_port);
0104     inb(opl4->fm_port);
0105     outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3);
0106     inb(opl4->fm_port);
0107     inb(opl4->fm_port);
0108 }
0109 
0110 static int snd_opl4_detect(struct snd_opl4 *opl4)
0111 {
0112     u8 id1, id2;
0113 
0114     snd_opl4_enable_opl4(opl4);
0115 
0116     id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
0117     snd_printdd("OPL4[02]=%02x\n", id1);
0118     switch (id1 & OPL4_DEVICE_ID_MASK) {
0119     case 0x20:
0120         opl4->hardware = OPL3_HW_OPL4;
0121         break;
0122     case 0x40:
0123         opl4->hardware = OPL3_HW_OPL4_ML;
0124         break;
0125     default:
0126         return -ENODEV;
0127     }
0128 
0129     snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00);
0130     snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff);
0131     id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM);
0132     id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM);
0133     snd_printdd("OPL4 id1=%02x id2=%02x\n", id1, id2);
0134         if (id1 != 0x00 || id2 != 0xff)
0135         return -ENODEV;
0136 
0137     snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f);
0138     snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f);
0139     snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00);
0140     return 0;
0141 }
0142 
0143 #if IS_ENABLED(CONFIG_SND_SEQUENCER)
0144 static void snd_opl4_seq_dev_free(struct snd_seq_device *seq_dev)
0145 {
0146     struct snd_opl4 *opl4 = seq_dev->private_data;
0147     opl4->seq_dev = NULL;
0148 }
0149 
0150 static int snd_opl4_create_seq_dev(struct snd_opl4 *opl4, int seq_device)
0151 {
0152     opl4->seq_dev_num = seq_device;
0153     if (snd_seq_device_new(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4,
0154                    sizeof(struct snd_opl4 *), &opl4->seq_dev) >= 0) {
0155         strcpy(opl4->seq_dev->name, "OPL4 Wavetable");
0156         *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4;
0157         opl4->seq_dev->private_data = opl4;
0158         opl4->seq_dev->private_free = snd_opl4_seq_dev_free;
0159     }
0160     return 0;
0161 }
0162 #endif
0163 
0164 static void snd_opl4_free(struct snd_opl4 *opl4)
0165 {
0166     snd_opl4_free_proc(opl4);
0167     release_and_free_resource(opl4->res_fm_port);
0168     release_and_free_resource(opl4->res_pcm_port);
0169     kfree(opl4);
0170 }
0171 
0172 static int snd_opl4_dev_free(struct snd_device *device)
0173 {
0174     struct snd_opl4 *opl4 = device->device_data;
0175     snd_opl4_free(opl4);
0176     return 0;
0177 }
0178 
0179 int snd_opl4_create(struct snd_card *card,
0180             unsigned long fm_port, unsigned long pcm_port,
0181             int seq_device,
0182             struct snd_opl3 **ropl3, struct snd_opl4 **ropl4)
0183 {
0184     struct snd_opl4 *opl4;
0185     struct snd_opl3 *opl3;
0186     int err;
0187     static const struct snd_device_ops ops = {
0188         .dev_free = snd_opl4_dev_free
0189     };
0190 
0191     if (ropl3)
0192         *ropl3 = NULL;
0193     if (ropl4)
0194         *ropl4 = NULL;
0195 
0196     opl4 = kzalloc(sizeof(*opl4), GFP_KERNEL);
0197     if (!opl4)
0198         return -ENOMEM;
0199 
0200     opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM");
0201     opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX");
0202     if (!opl4->res_fm_port || !opl4->res_pcm_port) {
0203         snd_printk(KERN_ERR "opl4: can't grab ports 0x%lx, 0x%lx\n", fm_port, pcm_port);
0204         snd_opl4_free(opl4);
0205         return -EBUSY;
0206     }
0207 
0208     opl4->card = card;
0209     opl4->fm_port = fm_port;
0210     opl4->pcm_port = pcm_port;
0211     spin_lock_init(&opl4->reg_lock);
0212     mutex_init(&opl4->access_mutex);
0213 
0214     err = snd_opl4_detect(opl4);
0215     if (err < 0) {
0216         snd_opl4_free(opl4);
0217         snd_printd("OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port);
0218         return err;
0219     }
0220 
0221     err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops);
0222     if (err < 0) {
0223         snd_opl4_free(opl4);
0224         return err;
0225     }
0226 
0227     err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3);
0228     if (err < 0) {
0229         snd_device_free(card, opl4);
0230         return err;
0231     }
0232 
0233     /* opl3 initialization disabled opl4, so reenable */
0234     snd_opl4_enable_opl4(opl4);
0235 
0236     snd_opl4_create_mixer(opl4);
0237     snd_opl4_create_proc(opl4);
0238 
0239 #if IS_ENABLED(CONFIG_SND_SEQUENCER)
0240     opl4->seq_client = -1;
0241     if (opl4->hardware < OPL3_HW_OPL4_ML)
0242         snd_opl4_create_seq_dev(opl4, seq_device);
0243 #endif
0244 
0245     if (ropl3)
0246         *ropl3 = opl3;
0247     if (ropl4)
0248         *ropl4 = opl4;
0249     return 0;
0250 }
0251 
0252 EXPORT_SYMBOL(snd_opl4_create);