xref: /linux/sound/drivers/opl4/opl4_lib.c (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
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  
snd_opl4_wait(struct snd_opl4 * opl4)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  
snd_opl4_write(struct snd_opl4 * opl4,u8 reg,u8 value)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  
snd_opl4_read(struct snd_opl4 * opl4,u8 reg)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  
snd_opl4_read_memory(struct snd_opl4 * opl4,char * buf,int offset,int size)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, opl4->pcm_port);
64  	snd_opl4_wait(opl4);
65  	insb(opl4->pcm_port + 1, buf, size);
66  
67  	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
68  
69  	spin_unlock_irqrestore(&opl4->reg_lock, flags);
70  }
71  
72  EXPORT_SYMBOL(snd_opl4_read_memory);
73  
snd_opl4_write_memory(struct snd_opl4 * opl4,const char * buf,int offset,int size)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, opl4->pcm_port);
90  	snd_opl4_wait(opl4);
91  	outsb(opl4->pcm_port + 1, buf, size);
92  
93  	snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
94  
95  	spin_unlock_irqrestore(&opl4->reg_lock, flags);
96  }
97  
98  EXPORT_SYMBOL(snd_opl4_write_memory);
99  
snd_opl4_enable_opl4(struct snd_opl4 * opl4)100  static void snd_opl4_enable_opl4(struct snd_opl4 *opl4)
101  {
102  	outb(OPL3_REG_MODE, opl4->fm_port + 2);
103  	inb(opl4->fm_port);
104  	inb(opl4->fm_port);
105  	outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3);
106  	inb(opl4->fm_port);
107  	inb(opl4->fm_port);
108  }
109  
snd_opl4_detect(struct snd_opl4 * opl4)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  	dev_dbg(opl4->card->dev, "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  	dev_dbg(opl4->card->dev, "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)
snd_opl4_seq_dev_free(struct snd_seq_device * seq_dev)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  
snd_opl4_create_seq_dev(struct snd_opl4 * opl4,int seq_device)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(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4,
154  			       sizeof(struct snd_opl4 *), &opl4->seq_dev) >= 0) {
155  		strcpy(opl4->seq_dev->name, "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  
snd_opl4_free(struct snd_opl4 * opl4)164  static void snd_opl4_free(struct snd_opl4 *opl4)
165  {
166  	snd_opl4_free_proc(opl4);
167  	release_and_free_resource(opl4->res_fm_port);
168  	release_and_free_resource(opl4->res_pcm_port);
169  	kfree(opl4);
170  }
171  
snd_opl4_dev_free(struct snd_device * device)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  
snd_opl4_create(struct snd_card * card,unsigned long fm_port,unsigned long pcm_port,int seq_device,struct snd_opl3 ** ropl3,struct snd_opl4 ** ropl4)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(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  		dev_err(card->dev, "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  		dev_dbg(card->dev, "OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port);
218  		return err;
219  	}
220  
221  	err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops);
222  	if (err < 0) {
223  		snd_opl4_free(opl4);
224  		return err;
225  	}
226  
227  	err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3);
228  	if (err < 0) {
229  		snd_device_free(card, 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