xref: /linux/sound/drivers/opl4/opl4_lib.c (revision 05a54fa773284d1a7923cdfdd8f0c8dabb98bd26)
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(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(reg, opl4->pcm_port);
30 
31 	snd_opl4_wait(opl4);
32 	outb(value, 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(reg, opl4->pcm_port);
41 
42 	snd_opl4_wait(opl4);
43 	return inb(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 	u8 memcfg;
51 
52 	guard(spinlock_irqsave)(&opl4->reg_lock);
53 
54 	memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
55 	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
56 
57 	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
58 	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
59 	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
60 
61 	snd_opl4_wait(opl4);
62 	outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
63 	snd_opl4_wait(opl4);
64 	insb(opl4->pcm_port + 1, buf, size);
65 
66 	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
67 }
68 
69 EXPORT_SYMBOL(snd_opl4_read_memory);
70 
71 void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size)
72 {
73 	u8 memcfg;
74 
75 	guard(spinlock_irqsave)(&opl4->reg_lock);
76 
77 	memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
78 	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
79 
80 	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
81 	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
82 	snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
83 
84 	snd_opl4_wait(opl4);
85 	outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
86 	snd_opl4_wait(opl4);
87 	outsb(opl4->pcm_port + 1, buf, size);
88 
89 	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
90 }
91 
92 EXPORT_SYMBOL(snd_opl4_write_memory);
93 
94 static void snd_opl4_enable_opl4(struct snd_opl4 *opl4)
95 {
96 	outb(OPL3_REG_MODE, opl4->fm_port + 2);
97 	inb(opl4->fm_port);
98 	inb(opl4->fm_port);
99 	outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3);
100 	inb(opl4->fm_port);
101 	inb(opl4->fm_port);
102 }
103 
104 static int snd_opl4_detect(struct snd_opl4 *opl4)
105 {
106 	u8 id1, id2;
107 
108 	snd_opl4_enable_opl4(opl4);
109 
110 	id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
111 	dev_dbg(opl4->card->dev, "OPL4[02]=%02x\n", id1);
112 	switch (id1 & OPL4_DEVICE_ID_MASK) {
113 	case 0x20:
114 		opl4->hardware = OPL3_HW_OPL4;
115 		break;
116 	case 0x40:
117 		opl4->hardware = OPL3_HW_OPL4_ML;
118 		break;
119 	default:
120 		return -ENODEV;
121 	}
122 
123 	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00);
124 	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff);
125 	id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM);
126 	id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM);
127 	dev_dbg(opl4->card->dev, "OPL4 id1=%02x id2=%02x\n", id1, id2);
128        	if (id1 != 0x00 || id2 != 0xff)
129 		return -ENODEV;
130 
131 	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f);
132 	snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f);
133 	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00);
134 	return 0;
135 }
136 
137 #if IS_ENABLED(CONFIG_SND_SEQUENCER)
138 static void snd_opl4_seq_dev_free(struct snd_seq_device *seq_dev)
139 {
140 	struct snd_opl4 *opl4 = seq_dev->private_data;
141 	opl4->seq_dev = NULL;
142 }
143 
144 static int snd_opl4_create_seq_dev(struct snd_opl4 *opl4, int seq_device)
145 {
146 	opl4->seq_dev_num = seq_device;
147 	if (snd_seq_device_new(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4,
148 			       sizeof(struct snd_opl4 *), &opl4->seq_dev) >= 0) {
149 		strscpy(opl4->seq_dev->name, "OPL4 Wavetable");
150 		*(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4;
151 		opl4->seq_dev->private_data = opl4;
152 		opl4->seq_dev->private_free = snd_opl4_seq_dev_free;
153 	}
154 	return 0;
155 }
156 #endif
157 
158 static void snd_opl4_free(struct snd_opl4 *opl4)
159 {
160 	snd_opl4_free_proc(opl4);
161 	release_and_free_resource(opl4->res_fm_port);
162 	release_and_free_resource(opl4->res_pcm_port);
163 	kfree(opl4);
164 }
165 
166 static int snd_opl4_dev_free(struct snd_device *device)
167 {
168 	struct snd_opl4 *opl4 = device->device_data;
169 	snd_opl4_free(opl4);
170 	return 0;
171 }
172 
173 int snd_opl4_create(struct snd_card *card,
174 		    unsigned long fm_port, unsigned long pcm_port,
175 		    int seq_device,
176 		    struct snd_opl3 **ropl3, struct snd_opl4 **ropl4)
177 {
178 	struct snd_opl4 *opl4;
179 	struct snd_opl3 *opl3;
180 	int err;
181 	static const struct snd_device_ops ops = {
182 		.dev_free = snd_opl4_dev_free
183 	};
184 
185 	if (ropl3)
186 		*ropl3 = NULL;
187 	if (ropl4)
188 		*ropl4 = NULL;
189 
190 	opl4 = kzalloc(sizeof(*opl4), GFP_KERNEL);
191 	if (!opl4)
192 		return -ENOMEM;
193 
194 	opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM");
195 	opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX");
196 	if (!opl4->res_fm_port || !opl4->res_pcm_port) {
197 		dev_err(card->dev, "opl4: can't grab ports 0x%lx, 0x%lx\n", fm_port, pcm_port);
198 		snd_opl4_free(opl4);
199 		return -EBUSY;
200 	}
201 
202 	opl4->card = card;
203 	opl4->fm_port = fm_port;
204 	opl4->pcm_port = pcm_port;
205 	spin_lock_init(&opl4->reg_lock);
206 	mutex_init(&opl4->access_mutex);
207 
208 	err = snd_opl4_detect(opl4);
209 	if (err < 0) {
210 		snd_opl4_free(opl4);
211 		dev_dbg(card->dev, "OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port);
212 		return err;
213 	}
214 
215 	err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops);
216 	if (err < 0) {
217 		snd_opl4_free(opl4);
218 		return err;
219 	}
220 
221 	err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3);
222 	if (err < 0) {
223 		snd_device_free(card, opl4);
224 		return err;
225 	}
226 
227 	/* opl3 initialization disabled opl4, so reenable */
228 	snd_opl4_enable_opl4(opl4);
229 
230 	snd_opl4_create_mixer(opl4);
231 	snd_opl4_create_proc(opl4);
232 
233 #if IS_ENABLED(CONFIG_SND_SEQUENCER)
234 	opl4->seq_client = -1;
235 	if (opl4->hardware < OPL3_HW_OPL4_ML)
236 		snd_opl4_create_seq_dev(opl4, seq_device);
237 #endif
238 
239 	if (ropl3)
240 		*ropl3 = opl3;
241 	if (ropl4)
242 		*ropl4 = opl4;
243 	return 0;
244 }
245 
246 EXPORT_SYMBOL(snd_opl4_create);
247