diff options
Diffstat (limited to 'sound/drivers/opl4/opl4_lib.c')
-rw-r--r-- | sound/drivers/opl4/opl4_lib.c | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/sound/drivers/opl4/opl4_lib.c b/sound/drivers/opl4/opl4_lib.c new file mode 100644 index 000000000000..8261464dade8 --- /dev/null +++ b/sound/drivers/opl4/opl4_lib.c | |||
@@ -0,0 +1,281 @@ | |||
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/init.h> | ||
24 | #include <asm/io.h> | ||
25 | |||
26 | MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); | ||
27 | MODULE_DESCRIPTION("OPL4 driver"); | ||
28 | MODULE_LICENSE("GPL"); | ||
29 | |||
30 | static void inline snd_opl4_wait(opl4_t *opl4) | ||
31 | { | ||
32 | int timeout = 10; | ||
33 | while ((inb(opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0) | ||
34 | ; | ||
35 | } | ||
36 | |||
37 | void snd_opl4_write(opl4_t *opl4, u8 reg, u8 value) | ||
38 | { | ||
39 | snd_opl4_wait(opl4); | ||
40 | outb(reg, opl4->pcm_port); | ||
41 | |||
42 | snd_opl4_wait(opl4); | ||
43 | outb(value, opl4->pcm_port + 1); | ||
44 | } | ||
45 | |||
46 | u8 snd_opl4_read(opl4_t *opl4, u8 reg) | ||
47 | { | ||
48 | snd_opl4_wait(opl4); | ||
49 | outb(reg, opl4->pcm_port); | ||
50 | |||
51 | snd_opl4_wait(opl4); | ||
52 | return inb(opl4->pcm_port + 1); | ||
53 | } | ||
54 | |||
55 | void snd_opl4_read_memory(opl4_t *opl4, char *buf, int offset, int size) | ||
56 | { | ||
57 | unsigned long flags; | ||
58 | u8 memcfg; | ||
59 | |||
60 | spin_lock_irqsave(&opl4->reg_lock, flags); | ||
61 | |||
62 | memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); | ||
63 | snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT); | ||
64 | |||
65 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16); | ||
66 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8); | ||
67 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset); | ||
68 | |||
69 | snd_opl4_wait(opl4); | ||
70 | outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port); | ||
71 | snd_opl4_wait(opl4); | ||
72 | insb(opl4->pcm_port + 1, buf, size); | ||
73 | |||
74 | snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg); | ||
75 | |||
76 | spin_unlock_irqrestore(&opl4->reg_lock, flags); | ||
77 | } | ||
78 | |||
79 | void snd_opl4_write_memory(opl4_t *opl4, const char *buf, int offset, int size) | ||
80 | { | ||
81 | unsigned long flags; | ||
82 | u8 memcfg; | ||
83 | |||
84 | spin_lock_irqsave(&opl4->reg_lock, flags); | ||
85 | |||
86 | memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); | ||
87 | snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT); | ||
88 | |||
89 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16); | ||
90 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8); | ||
91 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset); | ||
92 | |||
93 | snd_opl4_wait(opl4); | ||
94 | outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port); | ||
95 | snd_opl4_wait(opl4); | ||
96 | outsb(opl4->pcm_port + 1, buf, size); | ||
97 | |||
98 | snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg); | ||
99 | |||
100 | spin_unlock_irqrestore(&opl4->reg_lock, flags); | ||
101 | } | ||
102 | |||
103 | static void snd_opl4_enable_opl4(opl4_t *opl4) | ||
104 | { | ||
105 | outb(OPL3_REG_MODE, opl4->fm_port + 2); | ||
106 | inb(opl4->fm_port); | ||
107 | inb(opl4->fm_port); | ||
108 | outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3); | ||
109 | inb(opl4->fm_port); | ||
110 | inb(opl4->fm_port); | ||
111 | } | ||
112 | |||
113 | static int snd_opl4_detect(opl4_t *opl4) | ||
114 | { | ||
115 | u8 id1, id2; | ||
116 | |||
117 | snd_opl4_enable_opl4(opl4); | ||
118 | |||
119 | id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); | ||
120 | snd_printdd("OPL4[02]=%02x\n", id1); | ||
121 | switch (id1 & OPL4_DEVICE_ID_MASK) { | ||
122 | case 0x20: | ||
123 | opl4->hardware = OPL3_HW_OPL4; | ||
124 | break; | ||
125 | case 0x40: | ||
126 | opl4->hardware = OPL3_HW_OPL4_ML; | ||
127 | break; | ||
128 | default: | ||
129 | return -ENODEV; | ||
130 | } | ||
131 | |||
132 | snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00); | ||
133 | snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff); | ||
134 | id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM); | ||
135 | id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM); | ||
136 | snd_printdd("OPL4 id1=%02x id2=%02x\n", id1, id2); | ||
137 | if (id1 != 0x00 || id2 != 0xff) | ||
138 | return -ENODEV; | ||
139 | |||
140 | snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f); | ||
141 | snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f); | ||
142 | snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00); | ||
143 | return 0; | ||
144 | } | ||
145 | |||
146 | #if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) | ||
147 | static void snd_opl4_seq_dev_free(snd_seq_device_t *seq_dev) | ||
148 | { | ||
149 | opl4_t *opl4 = seq_dev->private_data; | ||
150 | opl4->seq_dev = NULL; | ||
151 | } | ||
152 | |||
153 | static int snd_opl4_create_seq_dev(opl4_t *opl4, int seq_device) | ||
154 | { | ||
155 | opl4->seq_dev_num = seq_device; | ||
156 | if (snd_seq_device_new(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4, | ||
157 | sizeof(opl4_t *), &opl4->seq_dev) >= 0) { | ||
158 | strcpy(opl4->seq_dev->name, "OPL4 Wavetable"); | ||
159 | *(opl4_t **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4; | ||
160 | opl4->seq_dev->private_data = opl4; | ||
161 | opl4->seq_dev->private_free = snd_opl4_seq_dev_free; | ||
162 | } | ||
163 | return 0; | ||
164 | } | ||
165 | #endif | ||
166 | |||
167 | static void snd_opl4_free(opl4_t *opl4) | ||
168 | { | ||
169 | #ifdef CONFIG_PROC_FS | ||
170 | snd_opl4_free_proc(opl4); | ||
171 | #endif | ||
172 | if (opl4->res_fm_port) { | ||
173 | release_resource(opl4->res_fm_port); | ||
174 | kfree_nocheck(opl4->res_fm_port); | ||
175 | } | ||
176 | if (opl4->res_pcm_port) { | ||
177 | release_resource(opl4->res_pcm_port); | ||
178 | kfree_nocheck(opl4->res_pcm_port); | ||
179 | } | ||
180 | kfree(opl4); | ||
181 | } | ||
182 | |||
183 | static int snd_opl4_dev_free(snd_device_t *device) | ||
184 | { | ||
185 | opl4_t *opl4 = device->device_data; | ||
186 | snd_opl4_free(opl4); | ||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | int snd_opl4_create(snd_card_t *card, | ||
191 | unsigned long fm_port, unsigned long pcm_port, | ||
192 | int seq_device, | ||
193 | opl3_t **ropl3, opl4_t **ropl4) | ||
194 | { | ||
195 | opl4_t *opl4; | ||
196 | opl3_t *opl3; | ||
197 | int err; | ||
198 | static snd_device_ops_t ops = { | ||
199 | .dev_free = snd_opl4_dev_free | ||
200 | }; | ||
201 | |||
202 | if (ropl3) | ||
203 | *ropl3 = NULL; | ||
204 | if (ropl4) | ||
205 | *ropl4 = NULL; | ||
206 | |||
207 | opl4 = kcalloc(1, sizeof(*opl4), GFP_KERNEL); | ||
208 | if (!opl4) | ||
209 | return -ENOMEM; | ||
210 | |||
211 | opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM"); | ||
212 | opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX"); | ||
213 | if (!opl4->res_fm_port || !opl4->res_pcm_port) { | ||
214 | snd_printk(KERN_ERR "opl4: can't grab ports 0x%lx, 0x%lx\n", fm_port, pcm_port); | ||
215 | snd_opl4_free(opl4); | ||
216 | return -EBUSY; | ||
217 | } | ||
218 | |||
219 | opl4->card = card; | ||
220 | opl4->fm_port = fm_port; | ||
221 | opl4->pcm_port = pcm_port; | ||
222 | spin_lock_init(&opl4->reg_lock); | ||
223 | init_MUTEX(&opl4->access_mutex); | ||
224 | |||
225 | err = snd_opl4_detect(opl4); | ||
226 | if (err < 0) { | ||
227 | snd_opl4_free(opl4); | ||
228 | snd_printd("OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port); | ||
229 | return err; | ||
230 | } | ||
231 | |||
232 | err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops); | ||
233 | if (err < 0) { | ||
234 | snd_opl4_free(opl4); | ||
235 | return err; | ||
236 | } | ||
237 | |||
238 | err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3); | ||
239 | if (err < 0) { | ||
240 | snd_device_free(card, opl4); | ||
241 | return err; | ||
242 | } | ||
243 | |||
244 | /* opl3 initialization disabled opl4, so reenable */ | ||
245 | snd_opl4_enable_opl4(opl4); | ||
246 | |||
247 | snd_opl4_create_mixer(opl4); | ||
248 | #ifdef CONFIG_PROC_FS | ||
249 | snd_opl4_create_proc(opl4); | ||
250 | #endif | ||
251 | |||
252 | #if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) | ||
253 | opl4->seq_client = -1; | ||
254 | if (opl4->hardware < OPL3_HW_OPL4_ML) | ||
255 | snd_opl4_create_seq_dev(opl4, seq_device); | ||
256 | #endif | ||
257 | |||
258 | if (ropl3) | ||
259 | *ropl3 = opl3; | ||
260 | if (ropl4) | ||
261 | *ropl4 = opl4; | ||
262 | return 0; | ||
263 | } | ||
264 | |||
265 | EXPORT_SYMBOL(snd_opl4_write); | ||
266 | EXPORT_SYMBOL(snd_opl4_read); | ||
267 | EXPORT_SYMBOL(snd_opl4_write_memory); | ||
268 | EXPORT_SYMBOL(snd_opl4_read_memory); | ||
269 | EXPORT_SYMBOL(snd_opl4_create); | ||
270 | |||
271 | static int __init alsa_opl4_init(void) | ||
272 | { | ||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | static void __exit alsa_opl4_exit(void) | ||
277 | { | ||
278 | } | ||
279 | |||
280 | module_init(alsa_opl4_init) | ||
281 | module_exit(alsa_opl4_exit) | ||