1/* 2 * Functions for accessing OPL4 devices 3 * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 */ 19 20#include "opl4_local.h" 21#include <sound/initval.h> 22#include <linux/ioport.h> 23#include <linux/slab.h> 24#include <linux/init.h> 25#include <linux/module.h> 26#include <linux/io.h> 27 28MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); 29MODULE_DESCRIPTION("OPL4 driver"); 30MODULE_LICENSE("GPL"); 31 32static void inline snd_opl4_wait(struct snd_opl4 *opl4) 33{ 34 int timeout = 10; 35 while ((inb(opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0) 36 ; 37} 38 39void snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value) 40{ 41 snd_opl4_wait(opl4); 42 outb(reg, opl4->pcm_port); 43 44 snd_opl4_wait(opl4); 45 outb(value, opl4->pcm_port + 1); 46} 47 48EXPORT_SYMBOL(snd_opl4_write); 49 50u8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg) 51{ 52 snd_opl4_wait(opl4); 53 outb(reg, opl4->pcm_port); 54 55 snd_opl4_wait(opl4); 56 return inb(opl4->pcm_port + 1); 57} 58 59EXPORT_SYMBOL(snd_opl4_read); 60 61void snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size) 62{ 63 unsigned long flags; 64 u8 memcfg; 65 66 spin_lock_irqsave(&opl4->reg_lock, flags); 67 68 memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); 69 snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT); 70 71 snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16); 72 snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8); 73 snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset); 74 75 snd_opl4_wait(opl4); 76 outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port); 77 snd_opl4_wait(opl4); 78 insb(opl4->pcm_port + 1, buf, size); 79 80 snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg); 81 82 spin_unlock_irqrestore(&opl4->reg_lock, flags); 83} 84 85EXPORT_SYMBOL(snd_opl4_read_memory); 86 87void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size) 88{ 89 unsigned long flags; 90 u8 memcfg; 91 92 spin_lock_irqsave(&opl4->reg_lock, flags); 93 94 memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); 95 snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT); 96 97 snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16); 98 snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8); 99 snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset); 100 101 snd_opl4_wait(opl4); 102 outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port); 103 snd_opl4_wait(opl4); 104 outsb(opl4->pcm_port + 1, buf, size); 105 106 snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg); 107 108 spin_unlock_irqrestore(&opl4->reg_lock, flags); 109} 110 111EXPORT_SYMBOL(snd_opl4_write_memory); 112 113static void snd_opl4_enable_opl4(struct snd_opl4 *opl4) 114{ 115 outb(OPL3_REG_MODE, opl4->fm_port + 2); 116 inb(opl4->fm_port); 117 inb(opl4->fm_port); 118 outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3); 119 inb(opl4->fm_port); 120 inb(opl4->fm_port); 121} 122 123static int snd_opl4_detect(struct snd_opl4 *opl4) 124{ 125 u8 id1, id2; 126 127 snd_opl4_enable_opl4(opl4); 128 129 id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); 130 snd_printdd("OPL4[02]=%02x\n", id1); 131 switch (id1 & OPL4_DEVICE_ID_MASK) { 132 case 0x20: 133 opl4->hardware = OPL3_HW_OPL4; 134 break; 135 case 0x40: 136 opl4->hardware = OPL3_HW_OPL4_ML; 137 break; 138 default: 139 return -ENODEV; 140 } 141 142 snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00); 143 snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff); 144 id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM); 145 id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM); 146 snd_printdd("OPL4 id1=%02x id2=%02x\n", id1, id2); 147 if (id1 != 0x00 || id2 != 0xff) 148 return -ENODEV; 149 150 snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f); 151 snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f); 152 snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00); 153 return 0; 154} 155 156#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) 157static void snd_opl4_seq_dev_free(struct snd_seq_device *seq_dev) 158{ 159 struct snd_opl4 *opl4 = seq_dev->private_data; 160 opl4->seq_dev = NULL; 161} 162 163static int snd_opl4_create_seq_dev(struct snd_opl4 *opl4, int seq_device) 164{ 165 opl4->seq_dev_num = seq_device; 166 if (snd_seq_device_new(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4, 167 sizeof(struct snd_opl4 *), &opl4->seq_dev) >= 0) { 168 strcpy(opl4->seq_dev->name, "OPL4 Wavetable"); 169 *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4; 170 opl4->seq_dev->private_data = opl4; 171 opl4->seq_dev->private_free = snd_opl4_seq_dev_free; 172 } 173 return 0; 174} 175#endif 176 177static void snd_opl4_free(struct snd_opl4 *opl4) 178{ 179#ifdef CONFIG_PROC_FS 180 snd_opl4_free_proc(opl4); 181#endif 182 release_and_free_resource(opl4->res_fm_port); 183 release_and_free_resource(opl4->res_pcm_port); 184 kfree(opl4); 185} 186 187static int snd_opl4_dev_free(struct snd_device *device) 188{ 189 struct snd_opl4 *opl4 = device->device_data; 190 snd_opl4_free(opl4); 191 return 0; 192} 193 194int snd_opl4_create(struct snd_card *card, 195 unsigned long fm_port, unsigned long pcm_port, 196 int seq_device, 197 struct snd_opl3 **ropl3, struct snd_opl4 **ropl4) 198{ 199 struct snd_opl4 *opl4; 200 struct snd_opl3 *opl3; 201 int err; 202 static struct snd_device_ops ops = { 203 .dev_free = snd_opl4_dev_free 204 }; 205 206 if (ropl3) 207 *ropl3 = NULL; 208 if (ropl4) 209 *ropl4 = NULL; 210 211 opl4 = kzalloc(sizeof(*opl4), GFP_KERNEL); 212 if (!opl4) 213 return -ENOMEM; 214 215 opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM"); 216 opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX"); 217 if (!opl4->res_fm_port || !opl4->res_pcm_port) { 218 snd_printk(KERN_ERR "opl4: can't grab ports 0x%lx, 0x%lx\n", fm_port, pcm_port); 219 snd_opl4_free(opl4); 220 return -EBUSY; 221 } 222 223 opl4->card = card; 224 opl4->fm_port = fm_port; 225 opl4->pcm_port = pcm_port; 226 spin_lock_init(&opl4->reg_lock); 227 mutex_init(&opl4->access_mutex); 228 229 err = snd_opl4_detect(opl4); 230 if (err < 0) { 231 snd_opl4_free(opl4); 232 snd_printd("OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port); 233 return err; 234 } 235 236 err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops); 237 if (err < 0) { 238 snd_opl4_free(opl4); 239 return err; 240 } 241 242 err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3); 243 if (err < 0) { 244 snd_device_free(card, opl4); 245 return err; 246 } 247 248 /* opl3 initialization disabled opl4, so reenable */ 249 snd_opl4_enable_opl4(opl4); 250 251 snd_opl4_create_mixer(opl4); 252#ifdef CONFIG_PROC_FS 253 snd_opl4_create_proc(opl4); 254#endif 255 256#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) 257 opl4->seq_client = -1; 258 if (opl4->hardware < OPL3_HW_OPL4_ML) 259 snd_opl4_create_seq_dev(opl4, seq_device); 260#endif 261 262 if (ropl3) 263 *ropl3 = opl3; 264 if (ropl4) 265 *ropl4 = opl4; 266 return 0; 267} 268 269EXPORT_SYMBOL(snd_opl4_create); 270 271static int __init alsa_opl4_init(void) 272{ 273 return 0; 274} 275 276static void __exit alsa_opl4_exit(void) 277{ 278} 279 280module_init(alsa_opl4_init) 281module_exit(alsa_opl4_exit) 282