1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | card-azt2320.c - driver for Aztech Systems AZT2320 based soundcards. |
4 | Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it> |
5 | |
6 | */ |
7 | |
8 | /* |
9 | This driver should provide support for most Aztech AZT2320 based cards. |
10 | Several AZT2316 chips are also supported/tested, but autoprobe doesn't |
11 | work: all module option have to be set. |
12 | |
13 | No docs available for us at Aztech headquarters !!! Unbelievable ... |
14 | No other help obtained. |
15 | |
16 | Thanks to Rainer Wiesner <rainer.wiesner@01019freenet.de> for the WSS |
17 | activation method (full-duplex audio!). |
18 | */ |
19 | |
20 | #include <linux/io.h> |
21 | #include <linux/delay.h> |
22 | #include <linux/init.h> |
23 | #include <linux/time.h> |
24 | #include <linux/wait.h> |
25 | #include <linux/pnp.h> |
26 | #include <linux/module.h> |
27 | #include <sound/core.h> |
28 | #include <sound/initval.h> |
29 | #include <sound/wss.h> |
30 | #include <sound/mpu401.h> |
31 | #include <sound/opl3.h> |
32 | |
33 | #define PFX "azt2320: " |
34 | |
35 | MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>" ); |
36 | MODULE_DESCRIPTION("Aztech Systems AZT2320" ); |
37 | MODULE_LICENSE("GPL" ); |
38 | |
39 | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ |
40 | static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ |
41 | static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ |
42 | static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ |
43 | static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ |
44 | static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ |
45 | static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ |
46 | static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ |
47 | static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ |
48 | static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ |
49 | static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ |
50 | |
51 | module_param_array(index, int, NULL, 0444); |
52 | MODULE_PARM_DESC(index, "Index value for azt2320 based soundcard." ); |
53 | module_param_array(id, charp, NULL, 0444); |
54 | MODULE_PARM_DESC(id, "ID string for azt2320 based soundcard." ); |
55 | module_param_array(enable, bool, NULL, 0444); |
56 | MODULE_PARM_DESC(enable, "Enable azt2320 based soundcard." ); |
57 | |
58 | struct snd_card_azt2320 { |
59 | int dev_no; |
60 | struct pnp_dev *dev; |
61 | struct pnp_dev *devmpu; |
62 | struct snd_wss *chip; |
63 | }; |
64 | |
65 | static const struct pnp_card_device_id snd_azt2320_pnpids[] = { |
66 | /* PRO16V */ |
67 | { .id = "AZT1008" , .devs = { { "AZT1008" }, { "AZT2001" }, } }, |
68 | /* Aztech Sound Galaxy 16 */ |
69 | { .id = "AZT2320" , .devs = { { "AZT0001" }, { "AZT0002" }, } }, |
70 | /* Packard Bell Sound III 336 AM/SP */ |
71 | { .id = "AZT3000" , .devs = { { "AZT1003" }, { "AZT2001" }, } }, |
72 | /* AT3300 */ |
73 | { .id = "AZT3002" , .devs = { { "AZT1004" }, { "AZT2001" }, } }, |
74 | /* --- */ |
75 | { .id = "AZT3005" , .devs = { { "AZT1003" }, { "AZT2001" }, } }, |
76 | /* --- */ |
77 | { .id = "AZT3011" , .devs = { { "AZT1003" }, { "AZT2001" }, } }, |
78 | { .id = "" } /* end */ |
79 | }; |
80 | |
81 | MODULE_DEVICE_TABLE(pnp_card, snd_azt2320_pnpids); |
82 | |
83 | #define DRIVER_NAME "snd-card-azt2320" |
84 | |
85 | static int snd_card_azt2320_pnp(int dev, struct snd_card_azt2320 *acard, |
86 | struct pnp_card_link *card, |
87 | const struct pnp_card_device_id *id) |
88 | { |
89 | struct pnp_dev *pdev; |
90 | int err; |
91 | |
92 | acard->dev = pnp_request_card_device(clink: card, id: id->devs[0].id, NULL); |
93 | if (acard->dev == NULL) |
94 | return -ENODEV; |
95 | |
96 | acard->devmpu = pnp_request_card_device(clink: card, id: id->devs[1].id, NULL); |
97 | |
98 | pdev = acard->dev; |
99 | |
100 | err = pnp_activate_dev(dev: pdev); |
101 | if (err < 0) { |
102 | snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n" ); |
103 | return err; |
104 | } |
105 | port[dev] = pnp_port_start(dev: pdev, bar: 0); |
106 | fm_port[dev] = pnp_port_start(dev: pdev, bar: 1); |
107 | wss_port[dev] = pnp_port_start(dev: pdev, bar: 2); |
108 | dma1[dev] = pnp_dma(dev: pdev, bar: 0); |
109 | dma2[dev] = pnp_dma(dev: pdev, bar: 1); |
110 | irq[dev] = pnp_irq(dev: pdev, bar: 0); |
111 | |
112 | pdev = acard->devmpu; |
113 | if (pdev != NULL) { |
114 | err = pnp_activate_dev(dev: pdev); |
115 | if (err < 0) |
116 | goto __mpu_error; |
117 | mpu_port[dev] = pnp_port_start(dev: pdev, bar: 0); |
118 | mpu_irq[dev] = pnp_irq(dev: pdev, bar: 0); |
119 | } else { |
120 | __mpu_error: |
121 | if (pdev) { |
122 | pnp_release_card_device(dev: pdev); |
123 | snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n" ); |
124 | } |
125 | acard->devmpu = NULL; |
126 | mpu_port[dev] = -1; |
127 | } |
128 | |
129 | return 0; |
130 | } |
131 | |
132 | /* same of snd_sbdsp_command by Jaroslav Kysela */ |
133 | static int snd_card_azt2320_command(unsigned long port, unsigned char val) |
134 | { |
135 | int i; |
136 | unsigned long limit; |
137 | |
138 | limit = jiffies + HZ / 10; |
139 | for (i = 50000; i && time_after(limit, jiffies); i--) |
140 | if (!(inb(port: port + 0x0c) & 0x80)) { |
141 | outb(value: val, port: port + 0x0c); |
142 | return 0; |
143 | } |
144 | return -EBUSY; |
145 | } |
146 | |
147 | static int snd_card_azt2320_enable_wss(unsigned long port) |
148 | { |
149 | int error; |
150 | |
151 | error = snd_card_azt2320_command(port, val: 0x09); |
152 | if (error) |
153 | return error; |
154 | error = snd_card_azt2320_command(port, val: 0x00); |
155 | if (error) |
156 | return error; |
157 | |
158 | mdelay(5); |
159 | return 0; |
160 | } |
161 | |
162 | static int snd_card_azt2320_probe(int dev, |
163 | struct pnp_card_link *pcard, |
164 | const struct pnp_card_device_id *pid) |
165 | { |
166 | int error; |
167 | struct snd_card *card; |
168 | struct snd_card_azt2320 *acard; |
169 | struct snd_wss *chip; |
170 | struct snd_opl3 *opl3; |
171 | |
172 | error = snd_devm_card_new(parent: &pcard->card->dev, |
173 | idx: index[dev], xid: id[dev], THIS_MODULE, |
174 | extra_size: sizeof(struct snd_card_azt2320), card_ret: &card); |
175 | if (error < 0) |
176 | return error; |
177 | acard = card->private_data; |
178 | |
179 | error = snd_card_azt2320_pnp(dev, acard, card: pcard, id: pid); |
180 | if (error) |
181 | return error; |
182 | |
183 | error = snd_card_azt2320_enable_wss(port: port[dev]); |
184 | if (error) |
185 | return error; |
186 | |
187 | error = snd_wss_create(card, port: wss_port[dev], cport: -1, |
188 | irq: irq[dev], |
189 | dma1: dma1[dev], dma2: dma2[dev], |
190 | WSS_HW_DETECT, hwshare: 0, rchip: &chip); |
191 | if (error < 0) |
192 | return error; |
193 | |
194 | strcpy(p: card->driver, q: "AZT2320" ); |
195 | strcpy(p: card->shortname, q: "Aztech AZT2320" ); |
196 | sprintf(buf: card->longname, fmt: "%s, WSS at 0x%lx, irq %i, dma %i&%i" , |
197 | card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]); |
198 | |
199 | error = snd_wss_pcm(chip, device: 0); |
200 | if (error < 0) |
201 | return error; |
202 | error = snd_wss_mixer(chip); |
203 | if (error < 0) |
204 | return error; |
205 | error = snd_wss_timer(chip, device: 0); |
206 | if (error < 0) |
207 | return error; |
208 | |
209 | if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) { |
210 | if (snd_mpu401_uart_new(card, device: 0, MPU401_HW_AZT2320, |
211 | port: mpu_port[dev], info_flags: 0, |
212 | irq: mpu_irq[dev], NULL) < 0) |
213 | snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n" , mpu_port[dev]); |
214 | } |
215 | |
216 | if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) { |
217 | if (snd_opl3_create(card, |
218 | l_port: fm_port[dev], r_port: fm_port[dev] + 2, |
219 | OPL3_HW_AUTO, integrated: 0, opl3: &opl3) < 0) { |
220 | snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n" , |
221 | fm_port[dev], fm_port[dev] + 2); |
222 | } else { |
223 | error = snd_opl3_timer_new(opl3, timer1_dev: 1, timer2_dev: 2); |
224 | if (error < 0) |
225 | return error; |
226 | error = snd_opl3_hwdep_new(opl3, device: 0, seq_device: 1, NULL); |
227 | if (error < 0) |
228 | return error; |
229 | } |
230 | } |
231 | |
232 | error = snd_card_register(card); |
233 | if (error < 0) |
234 | return error; |
235 | pnp_set_card_drvdata(pcard, data: card); |
236 | return 0; |
237 | } |
238 | |
239 | static unsigned int azt2320_devices; |
240 | |
241 | static int snd_azt2320_pnp_detect(struct pnp_card_link *card, |
242 | const struct pnp_card_device_id *id) |
243 | { |
244 | static int dev; |
245 | int res; |
246 | |
247 | for ( ; dev < SNDRV_CARDS; dev++) { |
248 | if (!enable[dev]) |
249 | continue; |
250 | res = snd_card_azt2320_probe(dev, pcard: card, pid: id); |
251 | if (res < 0) |
252 | return res; |
253 | dev++; |
254 | azt2320_devices++; |
255 | return 0; |
256 | } |
257 | return -ENODEV; |
258 | } |
259 | |
260 | #ifdef CONFIG_PM |
261 | static int snd_azt2320_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) |
262 | { |
263 | struct snd_card *card = pnp_get_card_drvdata(pcard); |
264 | struct snd_card_azt2320 *acard = card->private_data; |
265 | struct snd_wss *chip = acard->chip; |
266 | |
267 | snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); |
268 | chip->suspend(chip); |
269 | return 0; |
270 | } |
271 | |
272 | static int snd_azt2320_pnp_resume(struct pnp_card_link *pcard) |
273 | { |
274 | struct snd_card *card = pnp_get_card_drvdata(pcard); |
275 | struct snd_card_azt2320 *acard = card->private_data; |
276 | struct snd_wss *chip = acard->chip; |
277 | |
278 | chip->resume(chip); |
279 | snd_power_change_state(card, SNDRV_CTL_POWER_D0); |
280 | return 0; |
281 | } |
282 | #endif |
283 | |
284 | static struct pnp_card_driver azt2320_pnpc_driver = { |
285 | .flags = PNP_DRIVER_RES_DISABLE, |
286 | .name = "azt2320" , |
287 | .id_table = snd_azt2320_pnpids, |
288 | .probe = snd_azt2320_pnp_detect, |
289 | #ifdef CONFIG_PM |
290 | .suspend = snd_azt2320_pnp_suspend, |
291 | .resume = snd_azt2320_pnp_resume, |
292 | #endif |
293 | }; |
294 | |
295 | static int __init alsa_card_azt2320_init(void) |
296 | { |
297 | int err; |
298 | |
299 | err = pnp_register_card_driver(drv: &azt2320_pnpc_driver); |
300 | if (err) |
301 | return err; |
302 | |
303 | if (!azt2320_devices) { |
304 | pnp_unregister_card_driver(drv: &azt2320_pnpc_driver); |
305 | #ifdef MODULE |
306 | snd_printk(KERN_ERR "no AZT2320 based soundcards found\n" ); |
307 | #endif |
308 | return -ENODEV; |
309 | } |
310 | return 0; |
311 | } |
312 | |
313 | static void __exit alsa_card_azt2320_exit(void) |
314 | { |
315 | pnp_unregister_card_driver(drv: &azt2320_pnpc_driver); |
316 | } |
317 | |
318 | module_init(alsa_card_azt2320_init) |
319 | module_exit(alsa_card_azt2320_exit) |
320 | |