1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Functions for accessing OPL4 devices |
4 | * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de> |
5 | */ |
6 | |
7 | #include "opl4_local.h" |
8 | #include <sound/initval.h> |
9 | #include <linux/ioport.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/init.h> |
12 | #include <linux/module.h> |
13 | #include <linux/io.h> |
14 | |
15 | MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>" ); |
16 | MODULE_DESCRIPTION("OPL4 driver" ); |
17 | MODULE_LICENSE("GPL" ); |
18 | |
19 | static inline void snd_opl4_wait(struct snd_opl4 *opl4) |
20 | { |
21 | int timeout = 10; |
22 | while ((inb(port: opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0) |
23 | ; |
24 | } |
25 | |
26 | void snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value) |
27 | { |
28 | snd_opl4_wait(opl4); |
29 | outb(value: reg, port: opl4->pcm_port); |
30 | |
31 | snd_opl4_wait(opl4); |
32 | outb(value, port: opl4->pcm_port + 1); |
33 | } |
34 | |
35 | EXPORT_SYMBOL(snd_opl4_write); |
36 | |
37 | u8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg) |
38 | { |
39 | snd_opl4_wait(opl4); |
40 | outb(value: reg, port: opl4->pcm_port); |
41 | |
42 | snd_opl4_wait(opl4); |
43 | return inb(port: opl4->pcm_port + 1); |
44 | } |
45 | |
46 | EXPORT_SYMBOL(snd_opl4_read); |
47 | |
48 | void snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size) |
49 | { |
50 | unsigned long flags; |
51 | u8 memcfg; |
52 | |
53 | spin_lock_irqsave(&opl4->reg_lock, flags); |
54 | |
55 | memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); |
56 | snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT); |
57 | |
58 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16); |
59 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8); |
60 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset); |
61 | |
62 | snd_opl4_wait(opl4); |
63 | outb(OPL4_REG_MEMORY_DATA, port: opl4->pcm_port); |
64 | snd_opl4_wait(opl4); |
65 | insb(port: opl4->pcm_port + 1, addr: buf, count: size); |
66 | |
67 | snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg); |
68 | |
69 | spin_unlock_irqrestore(lock: &opl4->reg_lock, flags); |
70 | } |
71 | |
72 | EXPORT_SYMBOL(snd_opl4_read_memory); |
73 | |
74 | void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size) |
75 | { |
76 | unsigned long flags; |
77 | u8 memcfg; |
78 | |
79 | spin_lock_irqsave(&opl4->reg_lock, flags); |
80 | |
81 | memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); |
82 | snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT); |
83 | |
84 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16); |
85 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8); |
86 | snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset); |
87 | |
88 | snd_opl4_wait(opl4); |
89 | outb(OPL4_REG_MEMORY_DATA, port: opl4->pcm_port); |
90 | snd_opl4_wait(opl4); |
91 | outsb(port: opl4->pcm_port + 1, addr: buf, count: size); |
92 | |
93 | snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg); |
94 | |
95 | spin_unlock_irqrestore(lock: &opl4->reg_lock, flags); |
96 | } |
97 | |
98 | EXPORT_SYMBOL(snd_opl4_write_memory); |
99 | |
100 | static void snd_opl4_enable_opl4(struct snd_opl4 *opl4) |
101 | { |
102 | outb(OPL3_REG_MODE, port: opl4->fm_port + 2); |
103 | inb(port: opl4->fm_port); |
104 | inb(port: opl4->fm_port); |
105 | outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, port: opl4->fm_port + 3); |
106 | inb(port: opl4->fm_port); |
107 | inb(port: opl4->fm_port); |
108 | } |
109 | |
110 | static int snd_opl4_detect(struct snd_opl4 *opl4) |
111 | { |
112 | u8 id1, id2; |
113 | |
114 | snd_opl4_enable_opl4(opl4); |
115 | |
116 | id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); |
117 | snd_printdd("OPL4[02]=%02x\n" , id1); |
118 | switch (id1 & OPL4_DEVICE_ID_MASK) { |
119 | case 0x20: |
120 | opl4->hardware = OPL3_HW_OPL4; |
121 | break; |
122 | case 0x40: |
123 | opl4->hardware = OPL3_HW_OPL4_ML; |
124 | break; |
125 | default: |
126 | return -ENODEV; |
127 | } |
128 | |
129 | snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00); |
130 | snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff); |
131 | id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM); |
132 | id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM); |
133 | snd_printdd("OPL4 id1=%02x id2=%02x\n" , id1, id2); |
134 | if (id1 != 0x00 || id2 != 0xff) |
135 | return -ENODEV; |
136 | |
137 | snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f); |
138 | snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f); |
139 | snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00); |
140 | return 0; |
141 | } |
142 | |
143 | #if IS_ENABLED(CONFIG_SND_SEQUENCER) |
144 | static void snd_opl4_seq_dev_free(struct snd_seq_device *seq_dev) |
145 | { |
146 | struct snd_opl4 *opl4 = seq_dev->private_data; |
147 | opl4->seq_dev = NULL; |
148 | } |
149 | |
150 | static int snd_opl4_create_seq_dev(struct snd_opl4 *opl4, int seq_device) |
151 | { |
152 | opl4->seq_dev_num = seq_device; |
153 | if (snd_seq_device_new(card: opl4->card, device: seq_device, SNDRV_SEQ_DEV_ID_OPL4, |
154 | argsize: sizeof(struct snd_opl4 *), result: &opl4->seq_dev) >= 0) { |
155 | strcpy(p: opl4->seq_dev->name, q: "OPL4 Wavetable" ); |
156 | *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4; |
157 | opl4->seq_dev->private_data = opl4; |
158 | opl4->seq_dev->private_free = snd_opl4_seq_dev_free; |
159 | } |
160 | return 0; |
161 | } |
162 | #endif |
163 | |
164 | static void snd_opl4_free(struct snd_opl4 *opl4) |
165 | { |
166 | snd_opl4_free_proc(opl4); |
167 | release_and_free_resource(res: opl4->res_fm_port); |
168 | release_and_free_resource(res: opl4->res_pcm_port); |
169 | kfree(objp: opl4); |
170 | } |
171 | |
172 | static int snd_opl4_dev_free(struct snd_device *device) |
173 | { |
174 | struct snd_opl4 *opl4 = device->device_data; |
175 | snd_opl4_free(opl4); |
176 | return 0; |
177 | } |
178 | |
179 | int snd_opl4_create(struct snd_card *card, |
180 | unsigned long fm_port, unsigned long pcm_port, |
181 | int seq_device, |
182 | struct snd_opl3 **ropl3, struct snd_opl4 **ropl4) |
183 | { |
184 | struct snd_opl4 *opl4; |
185 | struct snd_opl3 *opl3; |
186 | int err; |
187 | static const struct snd_device_ops ops = { |
188 | .dev_free = snd_opl4_dev_free |
189 | }; |
190 | |
191 | if (ropl3) |
192 | *ropl3 = NULL; |
193 | if (ropl4) |
194 | *ropl4 = NULL; |
195 | |
196 | opl4 = kzalloc(size: sizeof(*opl4), GFP_KERNEL); |
197 | if (!opl4) |
198 | return -ENOMEM; |
199 | |
200 | opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM" ); |
201 | opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX" ); |
202 | if (!opl4->res_fm_port || !opl4->res_pcm_port) { |
203 | snd_printk(KERN_ERR "opl4: can't grab ports 0x%lx, 0x%lx\n" , fm_port, pcm_port); |
204 | snd_opl4_free(opl4); |
205 | return -EBUSY; |
206 | } |
207 | |
208 | opl4->card = card; |
209 | opl4->fm_port = fm_port; |
210 | opl4->pcm_port = pcm_port; |
211 | spin_lock_init(&opl4->reg_lock); |
212 | mutex_init(&opl4->access_mutex); |
213 | |
214 | err = snd_opl4_detect(opl4); |
215 | if (err < 0) { |
216 | snd_opl4_free(opl4); |
217 | snd_printd("OPL4 chip not detected at %#lx/%#lx\n" , fm_port, pcm_port); |
218 | return err; |
219 | } |
220 | |
221 | err = snd_device_new(card, type: SNDRV_DEV_CODEC, device_data: opl4, ops: &ops); |
222 | if (err < 0) { |
223 | snd_opl4_free(opl4); |
224 | return err; |
225 | } |
226 | |
227 | err = snd_opl3_create(card, l_port: fm_port, r_port: fm_port + 2, hardware: opl4->hardware, integrated: 1, opl3: &opl3); |
228 | if (err < 0) { |
229 | snd_device_free(card, device_data: opl4); |
230 | return err; |
231 | } |
232 | |
233 | /* opl3 initialization disabled opl4, so reenable */ |
234 | snd_opl4_enable_opl4(opl4); |
235 | |
236 | snd_opl4_create_mixer(opl4); |
237 | snd_opl4_create_proc(opl4); |
238 | |
239 | #if IS_ENABLED(CONFIG_SND_SEQUENCER) |
240 | opl4->seq_client = -1; |
241 | if (opl4->hardware < OPL3_HW_OPL4_ML) |
242 | snd_opl4_create_seq_dev(opl4, seq_device); |
243 | #endif |
244 | |
245 | if (ropl3) |
246 | *ropl3 = opl3; |
247 | if (ropl4) |
248 | *ropl4 = opl4; |
249 | return 0; |
250 | } |
251 | |
252 | EXPORT_SYMBOL(snd_opl4_create); |
253 | |