Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/drivers/opl4/opl4_lib.c
29266 views
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/*
3
* Functions for accessing OPL4 devices
4
* Copyright (c) 2003 by Clemens Ladisch <[email protected]>
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 <[email protected]>");
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
248