File Index
Files and directories
Sign in
FILE

opl4_lib.c

File preview with syntax highlighting.

linux-6.19.14/sound/drivers/opl4/opl4_lib.c C
  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
 15MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
 16MODULE_DESCRIPTION("OPL4 driver");
 17MODULE_LICENSE("GPL");
 18
 19static 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
 26void 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
 35EXPORT_SYMBOL(snd_opl4_write);
 36
 37u8 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
 46EXPORT_SYMBOL(snd_opl4_read);
 47
 48void 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
 69EXPORT_SYMBOL(snd_opl4_read_memory);
 70
 71void 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
 92EXPORT_SYMBOL(snd_opl4_write_memory);
 93
 94static 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
104static 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)
138static 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
144static 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
158static 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
166static 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
173int 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
246EXPORT_SYMBOL(snd_opl4_create);
~/ Read-only file preview.