aboutsummaryrefslogtreecommitdiffstats
path: root/sound/pci/trident/trident_synth.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 18:20:36 -0400
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 18:20:36 -0400
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/pci/trident/trident_synth.c
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'sound/pci/trident/trident_synth.c')
-rw-r--r--sound/pci/trident/trident_synth.c1031
1 files changed, 1031 insertions, 0 deletions
diff --git a/sound/pci/trident/trident_synth.c b/sound/pci/trident/trident_synth.c
new file mode 100644
index 000000000000..5d5a719b0585
--- /dev/null
+++ b/sound/pci/trident/trident_synth.c
@@ -0,0 +1,1031 @@
1/*
2 * Routines for Trident 4DWave NX/DX soundcards - Synthesizer
3 * Copyright (c) by Scott McNab <jedi@tartarus.uwa.edu.au>
4 *
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 *
20 */
21
22#include <sound/driver.h>
23#include <asm/io.h>
24#include <linux/init.h>
25#include <linux/slab.h>
26#include <linux/pci.h>
27#include <sound/core.h>
28#include <sound/trident.h>
29#include <sound/seq_device.h>
30
31MODULE_AUTHOR("Scott McNab <jedi@tartarus.uwa.edu.au>");
32MODULE_DESCRIPTION("Routines for Trident 4DWave NX/DX soundcards - Synthesizer");
33MODULE_LICENSE("GPL");
34
35/* linear to log pan conversion table (4.2 channel attenuation format) */
36static unsigned int pan_table[63] = {
37 7959, 7733, 7514, 7301, 7093, 6892, 6697, 6507,
38 6322, 6143, 5968, 5799, 5634, 5475, 5319, 5168,
39 5022, 4879, 4741, 4606, 4475, 4349, 4225, 4105,
40 3989, 3876, 3766, 3659, 3555, 3454, 3356, 3261,
41 3168, 3078, 2991, 2906, 2824, 2744, 2666, 2590,
42 2517, 2445, 2376, 2308, 2243, 2179, 2117, 2057,
43 1999, 1942, 1887, 1833, 1781, 1731, 1682, 1634,
44 1588, 1543, 1499, 1456, 1415, 1375, 1336
45};
46
47#define LOG_TABLE_SIZE 386
48
49/* Linear half-attenuation to log conversion table in the format:
50 * {linear volume, logarithmic attenuation equivalent}, ...
51 *
52 * Provides conversion from a linear half-volume value in the range
53 * [0,8192] to a logarithmic attenuation value in the range 0 to 6.02dB.
54 * Halving the linear volume is equivalent to an additional 6dB of
55 * logarithmic attenuation. The algorithm used in log_from_linear()
56 * therefore uses this table as follows:
57 *
58 * - loop and for every time the volume is less than half the maximum
59 * volume (16384), add another 6dB and halve the maximum value used
60 * for this comparison.
61 * - when the volume is greater than half the maximum volume, take
62 * the difference of the volume to half volume (in the range [0,8192])
63 * and look up the log_table[] to find the nearest entry.
64 * - take the logarithic component of this entry and add it to the
65 * resulting attenuation.
66 *
67 * Thus this routine provides a linear->log conversion for a range of
68 * [0,16384] using only 386 table entries
69 *
70 * Note: although this table stores log attenuation in 8.8 format, values
71 * were only calculated for 6 bits fractional precision, since that is
72 * the most precision offered by the trident hardware.
73 */
74
75static unsigned short log_table[LOG_TABLE_SIZE*2] =
76{
77 4, 0x0604, 19, 0x0600, 34, 0x05fc,
78 49, 0x05f8, 63, 0x05f4, 78, 0x05f0, 93, 0x05ec, 108, 0x05e8,
79 123, 0x05e4, 138, 0x05e0, 153, 0x05dc, 168, 0x05d8, 183, 0x05d4,
80 198, 0x05d0, 213, 0x05cc, 228, 0x05c8, 244, 0x05c4, 259, 0x05c0,
81 274, 0x05bc, 289, 0x05b8, 304, 0x05b4, 320, 0x05b0, 335, 0x05ac,
82 350, 0x05a8, 366, 0x05a4, 381, 0x05a0, 397, 0x059c, 412, 0x0598,
83 428, 0x0594, 443, 0x0590, 459, 0x058c, 474, 0x0588, 490, 0x0584,
84 506, 0x0580, 521, 0x057c, 537, 0x0578, 553, 0x0574, 568, 0x0570,
85 584, 0x056c, 600, 0x0568, 616, 0x0564, 632, 0x0560, 647, 0x055c,
86 663, 0x0558, 679, 0x0554, 695, 0x0550, 711, 0x054c, 727, 0x0548,
87 743, 0x0544, 759, 0x0540, 776, 0x053c, 792, 0x0538, 808, 0x0534,
88 824, 0x0530, 840, 0x052c, 857, 0x0528, 873, 0x0524, 889, 0x0520,
89 906, 0x051c, 922, 0x0518, 938, 0x0514, 955, 0x0510, 971, 0x050c,
90 988, 0x0508, 1004, 0x0504, 1021, 0x0500, 1037, 0x04fc, 1054, 0x04f8,
91 1071, 0x04f4, 1087, 0x04f0, 1104, 0x04ec, 1121, 0x04e8, 1138, 0x04e4,
92 1154, 0x04e0, 1171, 0x04dc, 1188, 0x04d8, 1205, 0x04d4, 1222, 0x04d0,
93 1239, 0x04cc, 1256, 0x04c8, 1273, 0x04c4, 1290, 0x04c0, 1307, 0x04bc,
94 1324, 0x04b8, 1341, 0x04b4, 1358, 0x04b0, 1376, 0x04ac, 1393, 0x04a8,
95 1410, 0x04a4, 1427, 0x04a0, 1445, 0x049c, 1462, 0x0498, 1479, 0x0494,
96 1497, 0x0490, 1514, 0x048c, 1532, 0x0488, 1549, 0x0484, 1567, 0x0480,
97 1584, 0x047c, 1602, 0x0478, 1620, 0x0474, 1637, 0x0470, 1655, 0x046c,
98 1673, 0x0468, 1690, 0x0464, 1708, 0x0460, 1726, 0x045c, 1744, 0x0458,
99 1762, 0x0454, 1780, 0x0450, 1798, 0x044c, 1816, 0x0448, 1834, 0x0444,
100 1852, 0x0440, 1870, 0x043c, 1888, 0x0438, 1906, 0x0434, 1924, 0x0430,
101 1943, 0x042c, 1961, 0x0428, 1979, 0x0424, 1997, 0x0420, 2016, 0x041c,
102 2034, 0x0418, 2053, 0x0414, 2071, 0x0410, 2089, 0x040c, 2108, 0x0408,
103 2127, 0x0404, 2145, 0x0400, 2164, 0x03fc, 2182, 0x03f8, 2201, 0x03f4,
104 2220, 0x03f0, 2239, 0x03ec, 2257, 0x03e8, 2276, 0x03e4, 2295, 0x03e0,
105 2314, 0x03dc, 2333, 0x03d8, 2352, 0x03d4, 2371, 0x03d0, 2390, 0x03cc,
106 2409, 0x03c8, 2428, 0x03c4, 2447, 0x03c0, 2466, 0x03bc, 2485, 0x03b8,
107 2505, 0x03b4, 2524, 0x03b0, 2543, 0x03ac, 2562, 0x03a8, 2582, 0x03a4,
108 2601, 0x03a0, 2621, 0x039c, 2640, 0x0398, 2660, 0x0394, 2679, 0x0390,
109 2699, 0x038c, 2718, 0x0388, 2738, 0x0384, 2758, 0x0380, 2777, 0x037c,
110 2797, 0x0378, 2817, 0x0374, 2837, 0x0370, 2857, 0x036c, 2876, 0x0368,
111 2896, 0x0364, 2916, 0x0360, 2936, 0x035c, 2956, 0x0358, 2976, 0x0354,
112 2997, 0x0350, 3017, 0x034c, 3037, 0x0348, 3057, 0x0344, 3077, 0x0340,
113 3098, 0x033c, 3118, 0x0338, 3138, 0x0334, 3159, 0x0330, 3179, 0x032c,
114 3200, 0x0328, 3220, 0x0324, 3241, 0x0320, 3261, 0x031c, 3282, 0x0318,
115 3303, 0x0314, 3323, 0x0310, 3344, 0x030c, 3365, 0x0308, 3386, 0x0304,
116 3406, 0x0300, 3427, 0x02fc, 3448, 0x02f8, 3469, 0x02f4, 3490, 0x02f0,
117 3511, 0x02ec, 3532, 0x02e8, 3553, 0x02e4, 3575, 0x02e0, 3596, 0x02dc,
118 3617, 0x02d8, 3638, 0x02d4, 3660, 0x02d0, 3681, 0x02cc, 3702, 0x02c8,
119 3724, 0x02c4, 3745, 0x02c0, 3767, 0x02bc, 3788, 0x02b8, 3810, 0x02b4,
120 3831, 0x02b0, 3853, 0x02ac, 3875, 0x02a8, 3896, 0x02a4, 3918, 0x02a0,
121 3940, 0x029c, 3962, 0x0298, 3984, 0x0294, 4006, 0x0290, 4028, 0x028c,
122 4050, 0x0288, 4072, 0x0284, 4094, 0x0280, 4116, 0x027c, 4138, 0x0278,
123 4160, 0x0274, 4182, 0x0270, 4205, 0x026c, 4227, 0x0268, 4249, 0x0264,
124 4272, 0x0260, 4294, 0x025c, 4317, 0x0258, 4339, 0x0254, 4362, 0x0250,
125 4384, 0x024c, 4407, 0x0248, 4430, 0x0244, 4453, 0x0240, 4475, 0x023c,
126 4498, 0x0238, 4521, 0x0234, 4544, 0x0230, 4567, 0x022c, 4590, 0x0228,
127 4613, 0x0224, 4636, 0x0220, 4659, 0x021c, 4682, 0x0218, 4705, 0x0214,
128 4728, 0x0210, 4752, 0x020c, 4775, 0x0208, 4798, 0x0204, 4822, 0x0200,
129 4845, 0x01fc, 4869, 0x01f8, 4892, 0x01f4, 4916, 0x01f0, 4939, 0x01ec,
130 4963, 0x01e8, 4987, 0x01e4, 5010, 0x01e0, 5034, 0x01dc, 5058, 0x01d8,
131 5082, 0x01d4, 5106, 0x01d0, 5130, 0x01cc, 5154, 0x01c8, 5178, 0x01c4,
132 5202, 0x01c0, 5226, 0x01bc, 5250, 0x01b8, 5274, 0x01b4, 5299, 0x01b0,
133 5323, 0x01ac, 5347, 0x01a8, 5372, 0x01a4, 5396, 0x01a0, 5420, 0x019c,
134 5445, 0x0198, 5469, 0x0194, 5494, 0x0190, 5519, 0x018c, 5543, 0x0188,
135 5568, 0x0184, 5593, 0x0180, 5618, 0x017c, 5643, 0x0178, 5668, 0x0174,
136 5692, 0x0170, 5717, 0x016c, 5743, 0x0168, 5768, 0x0164, 5793, 0x0160,
137 5818, 0x015c, 5843, 0x0158, 5868, 0x0154, 5894, 0x0150, 5919, 0x014c,
138 5945, 0x0148, 5970, 0x0144, 5995, 0x0140, 6021, 0x013c, 6047, 0x0138,
139 6072, 0x0134, 6098, 0x0130, 6124, 0x012c, 6149, 0x0128, 6175, 0x0124,
140 6201, 0x0120, 6227, 0x011c, 6253, 0x0118, 6279, 0x0114, 6305, 0x0110,
141 6331, 0x010c, 6357, 0x0108, 6384, 0x0104, 6410, 0x0100, 6436, 0x00fc,
142 6462, 0x00f8, 6489, 0x00f4, 6515, 0x00f0, 6542, 0x00ec, 6568, 0x00e8,
143 6595, 0x00e4, 6621, 0x00e0, 6648, 0x00dc, 6675, 0x00d8, 6702, 0x00d4,
144 6728, 0x00d0, 6755, 0x00cc, 6782, 0x00c8, 6809, 0x00c4, 6836, 0x00c0,
145 6863, 0x00bc, 6890, 0x00b8, 6917, 0x00b4, 6945, 0x00b0, 6972, 0x00ac,
146 6999, 0x00a8, 7027, 0x00a4, 7054, 0x00a0, 7081, 0x009c, 7109, 0x0098,
147 7136, 0x0094, 7164, 0x0090, 7192, 0x008c, 7219, 0x0088, 7247, 0x0084,
148 7275, 0x0080, 7303, 0x007c, 7331, 0x0078, 7359, 0x0074, 7387, 0x0070,
149 7415, 0x006c, 7443, 0x0068, 7471, 0x0064, 7499, 0x0060, 7527, 0x005c,
150 7556, 0x0058, 7584, 0x0054, 7613, 0x0050, 7641, 0x004c, 7669, 0x0048,
151 7698, 0x0044, 7727, 0x0040, 7755, 0x003c, 7784, 0x0038, 7813, 0x0034,
152 7842, 0x0030, 7870, 0x002c, 7899, 0x0028, 7928, 0x0024, 7957, 0x0020,
153 7986, 0x001c, 8016, 0x0018, 8045, 0x0014, 8074, 0x0010, 8103, 0x000c,
154 8133, 0x0008, 8162, 0x0004, 8192, 0x0000
155};
156
157static unsigned short lookup_volume_table( unsigned short value )
158{
159 /* This code is an optimised version of:
160 * int i = 0;
161 * while( volume_table[i*2] < value )
162 * i++;
163 * return volume_table[i*2+1];
164 */
165 unsigned short *ptr = log_table;
166 while( *ptr < value )
167 ptr += 2;
168 return *(ptr+1);
169}
170
171/* this function calculates a 8.8 fixed point logarithmic attenuation
172 * value from a linear volume value in the range 0 to 16384 */
173static unsigned short log_from_linear( unsigned short value )
174{
175 if (value >= 16384)
176 return 0x0000;
177 if (value) {
178 unsigned short result = 0;
179 int v, c;
180 for( c = 0, v = 8192; c < 14; c++, v >>= 1 ) {
181 if( value >= v ) {
182 result += lookup_volume_table( (value - v) << c );
183 return result;
184 }
185 result += 0x0605; /* 6.0205 (result of -20*log10(0.5)) */
186 }
187 }
188 return 0xffff;
189}
190
191/*
192 * Sample handling operations
193 */
194
195static void sample_start(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position);
196static void sample_stop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_stop_mode_t mode);
197static void sample_freq(trident_t * trident, snd_trident_voice_t * voice, snd_seq_frequency_t freq);
198static void sample_volume(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_volume_t * volume);
199static void sample_loop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_loop_t * loop);
200static void sample_pos(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position);
201static void sample_private1(trident_t * trident, snd_trident_voice_t * voice, unsigned char *data);
202
203static snd_trident_sample_ops_t sample_ops =
204{
205 sample_start,
206 sample_stop,
207 sample_freq,
208 sample_volume,
209 sample_loop,
210 sample_pos,
211 sample_private1
212};
213
214static void snd_trident_simple_init(snd_trident_voice_t * voice)
215{
216 //voice->handler_wave = interrupt_wave;
217 //voice->handler_volume = interrupt_volume;
218 //voice->handler_effect = interrupt_effect;
219 //voice->volume_change = NULL;
220 voice->sample_ops = &sample_ops;
221}
222
223static void sample_start(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position)
224{
225 simple_instrument_t *simple;
226 snd_seq_kinstr_t *instr;
227 unsigned long flags;
228 unsigned int loop_start, loop_end, sample_start, sample_end, start_offset;
229 unsigned int value;
230 unsigned int shift = 0;
231
232 instr = snd_seq_instr_find(trident->synth.ilist, &voice->instr, 0, 1);
233 if (instr == NULL)
234 return;
235 voice->instr = instr->instr; /* copy ID to speedup aliases */
236 simple = KINSTR_DATA(instr);
237
238 spin_lock_irqsave(&trident->reg_lock, flags);
239
240 if (trident->device == TRIDENT_DEVICE_ID_SI7018)
241 voice->GVSel = 1; /* route to Wave volume */
242
243 voice->CTRL = 0;
244 voice->Alpha = 0;
245 voice->FMS = 0;
246
247 loop_start = simple->loop_start >> 4;
248 loop_end = simple->loop_end >> 4;
249 sample_start = (simple->start + position) >> 4;
250 if( sample_start >= simple->size )
251 sample_start = simple->start >> 4;
252 sample_end = simple->size;
253 start_offset = position >> 4;
254
255 if (simple->format & SIMPLE_WAVE_16BIT) {
256 voice->CTRL |= 8;
257 shift++;
258 }
259 if (simple->format & SIMPLE_WAVE_STEREO) {
260 voice->CTRL |= 4;
261 shift++;
262 }
263 if (!(simple->format & SIMPLE_WAVE_UNSIGNED))
264 voice->CTRL |= 2;
265
266 voice->LBA = simple->address.memory;
267
268 if (simple->format & SIMPLE_WAVE_LOOP) {
269 voice->CTRL |= 1;
270 voice->LBA += loop_start << shift;
271 if( start_offset >= loop_start ) {
272 voice->CSO = start_offset - loop_start;
273 voice->negCSO = 0;
274 } else {
275 voice->CSO = loop_start - start_offset;
276 voice->negCSO = 1;
277 }
278 voice->ESO = loop_end - loop_start - 1;
279 } else {
280 voice->LBA += start_offset << shift;
281 voice->CSO = sample_start;
282 voice->ESO = sample_end - 1;
283 voice->negCSO = 0;
284 }
285
286 if (voice->flags & SNDRV_TRIDENT_VFLG_RUNNING) {
287 snd_trident_stop_voice(trident, voice->number);
288 voice->flags &= ~SNDRV_TRIDENT_VFLG_RUNNING;
289 }
290
291 /* set CSO sign */
292 value = inl(TRID_REG(trident, T4D_SIGN_CSO_A));
293 if( voice->negCSO ) {
294 value |= 1 << (voice->number&31);
295 } else {
296 value &= ~(1 << (voice->number&31));
297 }
298 outl(value,TRID_REG(trident, T4D_SIGN_CSO_A));
299
300 voice->Attribute = 0;
301 snd_trident_write_voice_regs(trident, voice);
302 snd_trident_start_voice(trident, voice->number);
303 voice->flags |= SNDRV_TRIDENT_VFLG_RUNNING;
304 spin_unlock_irqrestore(&trident->reg_lock, flags);
305 snd_seq_instr_free_use(trident->synth.ilist, instr);
306}
307
308static void sample_stop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_stop_mode_t mode)
309{
310 unsigned long flags;
311
312 if (!(voice->flags & SNDRV_TRIDENT_VFLG_RUNNING))
313 return;
314
315 switch (mode) {
316 default:
317 spin_lock_irqsave(&trident->reg_lock, flags);
318 snd_trident_stop_voice(trident, voice->number);
319 voice->flags &= ~SNDRV_TRIDENT_VFLG_RUNNING;
320 spin_unlock_irqrestore(&trident->reg_lock, flags);
321 break;
322 case SAMPLE_STOP_LOOP: /* disable loop only */
323 voice->CTRL &= ~1;
324 spin_lock_irqsave(&trident->reg_lock, flags);
325 outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
326 outw((((voice->CTRL << 12) | (voice->EC & 0x0fff)) & 0xffff), CH_GVSEL_PAN_VOL_CTRL_EC);
327 spin_unlock_irqrestore(&trident->reg_lock, flags);
328 break;
329 }
330}
331
332static void sample_freq(trident_t * trident, snd_trident_voice_t * voice, snd_seq_frequency_t freq)
333{
334 unsigned long flags;
335 freq >>= 4;
336
337 spin_lock_irqsave(&trident->reg_lock, flags);
338 if (freq == 44100)
339 voice->Delta = 0xeb3;
340 else if (freq == 8000)
341 voice->Delta = 0x2ab;
342 else if (freq == 48000)
343 voice->Delta = 0x1000;
344 else
345 voice->Delta = (((freq << 12) + freq) / 48000) & 0x0000ffff;
346
347 outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
348 if (trident->device == TRIDENT_DEVICE_ID_NX) {
349 outb((unsigned char) voice->Delta, TRID_REG(trident, CH_NX_DELTA_CSO + 3));
350 outb((unsigned char) (voice->Delta >> 8), TRID_REG(trident, CH_NX_DELTA_ESO + 3));
351 } else {
352 outw((unsigned short) voice->Delta, TRID_REG(trident, CH_DX_ESO_DELTA));
353 }
354
355 spin_unlock_irqrestore(&trident->reg_lock, flags);
356}
357
358static void sample_volume(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_volume_t * volume)
359{
360 unsigned long flags;
361 unsigned short value;
362
363 spin_lock_irqsave(&trident->reg_lock, flags);
364 voice->GVSel = 0; /* use global music volume */
365 voice->FMC = 0x03; /* fixme: can we do something useful with FMC? */
366 if (volume->volume >= 0) {
367 volume->volume &= 0x3fff;
368 /* linear volume -> logarithmic attenuation conversion
369 * uses EC register for greater resolution (6.6 bits) than Vol register (5.3 bits)
370 * Vol register used when additional attenuation is required */
371 voice->RVol = 0;
372 voice->CVol = 0;
373 value = log_from_linear( volume->volume );
374 voice->Vol = 0;
375 voice->EC = (value & 0x3fff) >> 2;
376 if (value > 0x3fff) {
377 voice->EC |= 0xfc0;
378 if (value < 0x5f00 )
379 voice->Vol = ((value >> 8) - 0x3f) << 5;
380 else {
381 voice->Vol = 0x3ff;
382 voice->EC = 0xfff;
383 }
384 }
385 }
386 if (volume->lr >= 0) {
387 volume->lr &= 0x3fff;
388 /* approximate linear pan by attenuating channels */
389 if (volume->lr >= 0x2000) { /* attenuate left (pan right) */
390 value = 0x3fff - volume->lr;
391 for (voice->Pan = 0; voice->Pan < 63; voice->Pan++ )
392 if (value >= pan_table[voice->Pan] )
393 break;
394 } else { /* attenuate right (pan left) */
395 for (voice->Pan = 0; voice->Pan < 63; voice->Pan++ )
396 if ((unsigned int)volume->lr >= pan_table[voice->Pan] )
397 break;
398 voice->Pan |= 0x40;
399 }
400 }
401 outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
402 outl((voice->GVSel << 31) | ((voice->Pan & 0x0000007f) << 24) |
403 ((voice->Vol & 0x000000ff) << 16) | ((voice->CTRL & 0x0000000f) << 12) |
404 (voice->EC & 0x00000fff), TRID_REG(trident, CH_GVSEL_PAN_VOL_CTRL_EC));
405 value = ((voice->FMC & 0x03) << 14) | ((voice->RVol & 0x7f) << 7) | (voice->CVol & 0x7f);
406 outw(value, TRID_REG(trident, CH_DX_FMC_RVOL_CVOL));
407 spin_unlock_irqrestore(&trident->reg_lock, flags);
408}
409
410static void sample_loop(trident_t * trident, snd_trident_voice_t * voice, snd_seq_ev_loop_t * loop)
411{
412 unsigned long flags;
413 simple_instrument_t *simple;
414 snd_seq_kinstr_t *instr;
415 unsigned int loop_start, loop_end;
416
417 instr = snd_seq_instr_find(trident->synth.ilist, &voice->instr, 0, 1);
418 if (instr == NULL)
419 return;
420 voice->instr = instr->instr; /* copy ID to speedup aliases */
421 simple = KINSTR_DATA(instr);
422
423 loop_start = loop->start >> 4;
424 loop_end = loop->end >> 4;
425
426 spin_lock_irqsave(&trident->reg_lock, flags);
427
428 voice->LBA = simple->address.memory + loop_start;
429 voice->CSO = 0;
430 voice->ESO = loop_end - loop_start - 1;
431
432 outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
433 outb((voice->LBA >> 16), TRID_REG(trident, CH_LBA + 2));
434 outw((voice->LBA & 0xffff), TRID_REG(trident, CH_LBA));
435 if (trident->device == TRIDENT_DEVICE_ID_NX) {
436 outb((voice->ESO >> 16), TRID_REG(trident, CH_NX_DELTA_ESO + 2));
437 outw((voice->ESO & 0xffff), TRID_REG(trident, CH_NX_DELTA_ESO));
438 outb((voice->CSO >> 16), TRID_REG(trident, CH_NX_DELTA_CSO + 2));
439 outw((voice->CSO & 0xffff), TRID_REG(trident, CH_NX_DELTA_CSO));
440 } else {
441 outw((voice->ESO & 0xffff), TRID_REG(trident, CH_DX_ESO_DELTA + 2));
442 outw((voice->CSO & 0xffff), TRID_REG(trident, CH_DX_CSO_ALPHA_FMS + 2));
443 }
444
445 spin_unlock_irqrestore(&trident->reg_lock, flags);
446 snd_seq_instr_free_use(trident->synth.ilist, instr);
447}
448
449static void sample_pos(trident_t * trident, snd_trident_voice_t * voice, snd_seq_position_t position)
450{
451 unsigned long flags;
452 simple_instrument_t *simple;
453 snd_seq_kinstr_t *instr;
454 unsigned int value;
455
456 instr = snd_seq_instr_find(trident->synth.ilist, &voice->instr, 0, 1);
457 if (instr == NULL)
458 return;
459 voice->instr = instr->instr; /* copy ID to speedup aliases */
460 simple = KINSTR_DATA(instr);
461
462 spin_lock_irqsave(&trident->reg_lock, flags);
463
464 if (simple->format & SIMPLE_WAVE_LOOP) {
465 if( position >= simple->loop_start ) {
466 voice->CSO = (position - simple->loop_start) >> 4;
467 voice->negCSO = 0;
468 } else {
469 voice->CSO = (simple->loop_start - position) >> 4;
470 voice->negCSO = 1;
471 }
472 } else {
473 voice->CSO = position >> 4;
474 voice->negCSO = 0;
475 }
476
477 /* set CSO sign */
478 value = inl(TRID_REG(trident, T4D_SIGN_CSO_A));
479 if( voice->negCSO ) {
480 value |= 1 << (voice->number&31);
481 } else {
482 value &= ~(1 << (voice->number&31));
483 }
484 outl(value,TRID_REG(trident, T4D_SIGN_CSO_A));
485
486
487 outb((unsigned char) voice->number, TRID_REG(trident, T4D_LFO_GC_CIR));
488 if (trident->device == TRIDENT_DEVICE_ID_NX) {
489 outw((voice->CSO & 0xffff), TRID_REG(trident, CH_NX_DELTA_CSO));
490 outb((voice->CSO >> 16), TRID_REG(trident, CH_NX_DELTA_CSO + 2));
491 } else {
492 outw((voice->CSO & 0xffff), TRID_REG(trident, CH_DX_CSO_ALPHA_FMS) + 2);
493 }
494
495 spin_unlock_irqrestore(&trident->reg_lock, flags);
496 snd_seq_instr_free_use(trident->synth.ilist, instr);
497}
498
499static void sample_private1(trident_t * trident, snd_trident_voice_t * voice, unsigned char *data)
500{
501}
502
503/*
504 * Memory management / sample loading
505 */
506
507static int snd_trident_simple_put_sample(void *private_data, simple_instrument_t * instr,
508 char __user *data, long len, int atomic)
509{
510 trident_t *trident = private_data;
511 int size = instr->size;
512 int shift = 0;
513
514 if (instr->format & SIMPLE_WAVE_BACKWARD ||
515 instr->format & SIMPLE_WAVE_BIDIR ||
516 instr->format & SIMPLE_WAVE_ULAW)
517 return -EINVAL; /* not supported */
518
519 if (instr->format & SIMPLE_WAVE_16BIT)
520 shift++;
521 if (instr->format & SIMPLE_WAVE_STEREO)
522 shift++;
523 size <<= shift;
524
525 if (trident->synth.current_size + size > trident->synth.max_size)
526 return -ENOMEM;
527
528 if (!access_ok(VERIFY_READ, data, size))
529 return -EFAULT;
530
531 if (trident->tlb.entries) {
532 snd_util_memblk_t *memblk;
533 memblk = snd_trident_synth_alloc(trident, size);
534 if (memblk == NULL)
535 return -ENOMEM;
536 if (snd_trident_synth_copy_from_user(trident, memblk, 0, data, size) ) {
537 snd_trident_synth_free(trident, memblk);
538 return -EFAULT;
539 }
540 instr->address.ptr = (unsigned char*)memblk;
541 instr->address.memory = memblk->offset;
542 } else {
543 struct snd_dma_buffer dmab;
544 if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci),
545 size, &dmab) < 0)
546 return -ENOMEM;
547
548 if (copy_from_user(dmab.area, data, size)) {
549 snd_dma_free_pages(&dmab);
550 return -EFAULT;
551 }
552 instr->address.ptr = dmab.area;
553 instr->address.memory = dmab.addr;
554 }
555
556 trident->synth.current_size += size;
557 return 0;
558}
559
560static int snd_trident_simple_get_sample(void *private_data, simple_instrument_t * instr,
561 char __user *data, long len, int atomic)
562{
563 //trident_t *trident = private_data;
564 int size = instr->size;
565 int shift = 0;
566
567 if (instr->format & SIMPLE_WAVE_16BIT)
568 shift++;
569 if (instr->format & SIMPLE_WAVE_STEREO)
570 shift++;
571 size <<= shift;
572
573 if (!access_ok(VERIFY_WRITE, data, size))
574 return -EFAULT;
575
576 /* FIXME: not implemented yet */
577
578 return -EBUSY;
579}
580
581static int snd_trident_simple_remove_sample(void *private_data, simple_instrument_t * instr,
582 int atomic)
583{
584 trident_t *trident = private_data;
585 int size = instr->size;
586
587 if (instr->format & SIMPLE_WAVE_16BIT)
588 size <<= 1;
589 if (instr->format & SIMPLE_WAVE_STEREO)
590 size <<= 1;
591
592 if (trident->tlb.entries) {
593 snd_util_memblk_t *memblk = (snd_util_memblk_t*)instr->address.ptr;
594 if (memblk)
595 snd_trident_synth_free(trident, memblk);
596 else
597 return -EFAULT;
598 } else {
599 struct snd_dma_buffer dmab;
600 dmab.dev.type = SNDRV_DMA_TYPE_DEV;
601 dmab.dev.dev = snd_dma_pci_data(trident->pci);
602 dmab.area = instr->address.ptr;
603 dmab.addr = instr->address.memory;
604 dmab.bytes = size;
605 snd_dma_free_pages(&dmab);
606 }
607
608 trident->synth.current_size -= size;
609 if (trident->synth.current_size < 0) /* shouldn't need this check... */
610 trident->synth.current_size = 0;
611
612 return 0;
613}
614
615static void select_instrument(trident_t * trident, snd_trident_voice_t * v)
616{
617 snd_seq_kinstr_t *instr;
618 instr = snd_seq_instr_find(trident->synth.ilist, &v->instr, 0, 1);
619 if (instr != NULL) {
620 if (instr->ops) {
621 if (!strcmp(instr->ops->instr_type, SNDRV_SEQ_INSTR_ID_SIMPLE))
622 snd_trident_simple_init(v);
623 }
624 snd_seq_instr_free_use(trident->synth.ilist, instr);
625 }
626}
627
628/*
629
630 */
631
632static void event_sample(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
633{
634 if (v->sample_ops && v->sample_ops->sample_stop)
635 v->sample_ops->sample_stop(p->trident, v, SAMPLE_STOP_IMMEDIATELY);
636 v->instr.std = ev->data.sample.param.sample.std;
637 if (v->instr.std & 0xff000000) { /* private instrument */
638 v->instr.std &= 0x00ffffff;
639 v->instr.std |= (unsigned int)ev->source.client << 24;
640 }
641 v->instr.bank = ev->data.sample.param.sample.bank;
642 v->instr.prg = ev->data.sample.param.sample.prg;
643 select_instrument(p->trident, v);
644}
645
646static void event_cluster(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
647{
648 if (v->sample_ops && v->sample_ops->sample_stop)
649 v->sample_ops->sample_stop(p->trident, v, SAMPLE_STOP_IMMEDIATELY);
650 v->instr.cluster = ev->data.sample.param.cluster.cluster;
651 select_instrument(p->trident, v);
652}
653
654static void event_start(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
655{
656 if (v->sample_ops && v->sample_ops->sample_start)
657 v->sample_ops->sample_start(p->trident, v, ev->data.sample.param.position);
658}
659
660static void event_stop(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
661{
662 if (v->sample_ops && v->sample_ops->sample_stop)
663 v->sample_ops->sample_stop(p->trident, v, ev->data.sample.param.stop_mode);
664}
665
666static void event_freq(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
667{
668 if (v->sample_ops && v->sample_ops->sample_freq)
669 v->sample_ops->sample_freq(p->trident, v, ev->data.sample.param.frequency);
670}
671
672static void event_volume(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
673{
674 if (v->sample_ops && v->sample_ops->sample_volume)
675 v->sample_ops->sample_volume(p->trident, v, &ev->data.sample.param.volume);
676}
677
678static void event_loop(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
679{
680 if (v->sample_ops && v->sample_ops->sample_loop)
681 v->sample_ops->sample_loop(p->trident, v, &ev->data.sample.param.loop);
682}
683
684static void event_position(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
685{
686 if (v->sample_ops && v->sample_ops->sample_pos)
687 v->sample_ops->sample_pos(p->trident, v, ev->data.sample.param.position);
688}
689
690static void event_private1(snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v)
691{
692 if (v->sample_ops && v->sample_ops->sample_private1)
693 v->sample_ops->sample_private1(p->trident, v, (unsigned char *) &ev->data.sample.param.raw8);
694}
695
696typedef void (trident_sample_event_handler_t) (snd_seq_event_t * ev, snd_trident_port_t * p, snd_trident_voice_t * v);
697
698static trident_sample_event_handler_t *trident_sample_event_handlers[9] =
699{
700 event_sample,
701 event_cluster,
702 event_start,
703 event_stop,
704 event_freq,
705 event_volume,
706 event_loop,
707 event_position,
708 event_private1
709};
710
711static void snd_trident_sample_event(snd_seq_event_t * ev, snd_trident_port_t * p)
712{
713 int idx, voice;
714 trident_t *trident = p->trident;
715 snd_trident_voice_t *v;
716 unsigned long flags;
717
718 idx = ev->type - SNDRV_SEQ_EVENT_SAMPLE;
719 if (idx < 0 || idx > 8)
720 return;
721 for (voice = 0; voice < 64; voice++) {
722 v = &trident->synth.voices[voice];
723 if (v->use && v->client == ev->source.client &&
724 v->port == ev->source.port &&
725 v->index == ev->data.sample.channel) {
726 spin_lock_irqsave(&trident->event_lock, flags);
727 trident_sample_event_handlers[idx] (ev, p, v);
728 spin_unlock_irqrestore(&trident->event_lock, flags);
729 return;
730 }
731 }
732}
733
734/*
735
736 */
737
738static void snd_trident_synth_free_voices(trident_t * trident, int client, int port)
739{
740 int idx;
741 snd_trident_voice_t *voice;
742
743 for (idx = 0; idx < 32; idx++) {
744 voice = &trident->synth.voices[idx];
745 if (voice->use && voice->client == client && voice->port == port)
746 snd_trident_free_voice(trident, voice);
747 }
748}
749
750static int snd_trident_synth_use(void *private_data, snd_seq_port_subscribe_t * info)
751{
752 snd_trident_port_t *port = (snd_trident_port_t *) private_data;
753 trident_t *trident = port->trident;
754 snd_trident_voice_t *voice;
755 unsigned int idx;
756 unsigned long flags;
757
758 if (info->voices > 32)
759 return -EINVAL;
760 spin_lock_irqsave(&trident->reg_lock, flags);
761 for (idx = 0; idx < info->voices; idx++) {
762 voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_SYNTH, info->sender.client, info->sender.port);
763 if (voice == NULL) {
764 snd_trident_synth_free_voices(trident, info->sender.client, info->sender.port);
765 spin_unlock_irqrestore(&trident->reg_lock, flags);
766 return -EBUSY;
767 }
768 voice->index = idx;
769 voice->Vol = 0x3ff;
770 voice->EC = 0x0fff;
771 }
772#if 0
773 for (idx = 0; idx < info->midi_voices; idx++) {
774 port->midi_has_voices = 1;
775 voice = snd_trident_alloc_voice(trident, SNDRV_TRIDENT_VOICE_TYPE_MIDI, info->sender.client, info->sender.port);
776 if (voice == NULL) {
777 snd_trident_synth_free_voices(trident, info->sender.client, info->sender.port);
778 spin_unlock_irqrestore(&trident->reg_lock, flags);
779 return -EBUSY;
780 }
781 voice->Vol = 0x3ff;
782 voice->EC = 0x0fff;
783 }
784#endif
785 spin_unlock_irqrestore(&trident->reg_lock, flags);
786 return 0;
787}
788
789static int snd_trident_synth_unuse(void *private_data, snd_seq_port_subscribe_t * info)
790{
791 snd_trident_port_t *port = (snd_trident_port_t *) private_data;
792 trident_t *trident = port->trident;
793 unsigned long flags;
794
795 spin_lock_irqsave(&trident->reg_lock, flags);
796 snd_trident_synth_free_voices(trident, info->sender.client, info->sender.port);
797 spin_unlock_irqrestore(&trident->reg_lock, flags);
798 return 0;
799}
800
801/*
802
803 */
804
805static void snd_trident_synth_free_private_instruments(snd_trident_port_t * p, int client)
806{
807 snd_seq_instr_header_t ifree;
808
809 memset(&ifree, 0, sizeof(ifree));
810 ifree.cmd = SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE;
811 snd_seq_instr_list_free_cond(p->trident->synth.ilist, &ifree, client, 0);
812}
813
814static int snd_trident_synth_event_input(snd_seq_event_t * ev, int direct, void *private_data, int atomic, int hop)
815{
816 snd_trident_port_t *p = (snd_trident_port_t *) private_data;
817
818 if (p == NULL)
819 return -EINVAL;
820 if (ev->type >= SNDRV_SEQ_EVENT_SAMPLE &&
821 ev->type <= SNDRV_SEQ_EVENT_SAMPLE_PRIVATE1) {
822 snd_trident_sample_event(ev, p);
823 return 0;
824 }
825 if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM &&
826 ev->source.port == SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE) {
827 if (ev->type == SNDRV_SEQ_EVENT_CLIENT_EXIT) {
828 snd_trident_synth_free_private_instruments(p, ev->data.addr.client);
829 return 0;
830 }
831 }
832 if (direct) {
833 if (ev->type >= SNDRV_SEQ_EVENT_INSTR_BEGIN) {
834 snd_seq_instr_event(&p->trident->synth.simple_ops.kops,
835 p->trident->synth.ilist, ev,
836 p->trident->synth.seq_client, atomic, hop);
837 return 0;
838 }
839 }
840 return 0;
841}
842
843static void snd_trident_synth_instr_notify(void *private_data,
844 snd_seq_kinstr_t * instr,
845 int what)
846{
847 int idx;
848 trident_t *trident = private_data;
849 snd_trident_voice_t *pvoice;
850 unsigned long flags;
851
852 spin_lock_irqsave(&trident->event_lock, flags);
853 for (idx = 0; idx < 64; idx++) {
854 pvoice = &trident->synth.voices[idx];
855 if (pvoice->use && !memcmp(&pvoice->instr, &instr->instr, sizeof(pvoice->instr))) {
856 if (pvoice->sample_ops && pvoice->sample_ops->sample_stop) {
857 pvoice->sample_ops->sample_stop(trident, pvoice, SAMPLE_STOP_IMMEDIATELY);
858 } else {
859 snd_trident_stop_voice(trident, pvoice->number);
860 pvoice->flags &= ~SNDRV_TRIDENT_VFLG_RUNNING;
861 }
862 }
863 }
864 spin_unlock_irqrestore(&trident->event_lock, flags);
865}
866
867/*
868
869 */
870
871static void snd_trident_synth_free_port(void *private_data)
872{
873 snd_trident_port_t *p = (snd_trident_port_t *) private_data;
874
875 if (p)
876 snd_midi_channel_free_set(p->chset);
877}
878
879static int snd_trident_synth_create_port(trident_t * trident, int idx)
880{
881 snd_trident_port_t *p;
882 snd_seq_port_callback_t callbacks;
883 char name[32];
884 char *str;
885 int result;
886
887 p = &trident->synth.seq_ports[idx];
888 p->chset = snd_midi_channel_alloc_set(16);
889 if (p->chset == NULL)
890 return -ENOMEM;
891 p->chset->private_data = p;
892 p->trident = trident;
893 p->client = trident->synth.seq_client;
894
895 memset(&callbacks, 0, sizeof(callbacks));
896 callbacks.owner = THIS_MODULE;
897 callbacks.use = snd_trident_synth_use;
898 callbacks.unuse = snd_trident_synth_unuse;
899 callbacks.event_input = snd_trident_synth_event_input;
900 callbacks.private_free = snd_trident_synth_free_port;
901 callbacks.private_data = p;
902
903 str = "???";
904 switch (trident->device) {
905 case TRIDENT_DEVICE_ID_DX: str = "Trident 4DWave-DX"; break;
906 case TRIDENT_DEVICE_ID_NX: str = "Trident 4DWave-NX"; break;
907 case TRIDENT_DEVICE_ID_SI7018: str = "SiS 7018"; break;
908 }
909 sprintf(name, "%s port %i", str, idx);
910 p->chset->port = snd_seq_event_port_attach(trident->synth.seq_client,
911 &callbacks,
912 SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE,
913 SNDRV_SEQ_PORT_TYPE_DIRECT_SAMPLE |
914 SNDRV_SEQ_PORT_TYPE_SYNTH,
915 16, 0,
916 name);
917 if (p->chset->port < 0) {
918 result = p->chset->port;
919 snd_trident_synth_free_port(p);
920 return result;
921 }
922 p->port = p->chset->port;
923 return 0;
924}
925
926/*
927
928 */
929
930static int snd_trident_synth_new_device(snd_seq_device_t *dev)
931{
932 trident_t *trident;
933 int client, i;
934 snd_seq_client_callback_t callbacks;
935 snd_seq_client_info_t cinfo;
936 snd_seq_port_subscribe_t sub;
937 snd_simple_ops_t *simpleops;
938 char *str;
939
940 trident = *(trident_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
941 if (trident == NULL)
942 return -EINVAL;
943
944 trident->synth.seq_client = -1;
945
946 /* allocate new client */
947 memset(&callbacks, 0, sizeof(callbacks));
948 callbacks.private_data = trident;
949 callbacks.allow_output = callbacks.allow_input = 1;
950 client = trident->synth.seq_client =
951 snd_seq_create_kernel_client(trident->card, 1, &callbacks);
952 if (client < 0)
953 return client;
954
955 /* change name of client */
956 memset(&cinfo, 0, sizeof(cinfo));
957 cinfo.client = client;
958 cinfo.type = KERNEL_CLIENT;
959 str = "???";
960 switch (trident->device) {
961 case TRIDENT_DEVICE_ID_DX: str = "Trident 4DWave-DX"; break;
962 case TRIDENT_DEVICE_ID_NX: str = "Trident 4DWave-NX"; break;
963 case TRIDENT_DEVICE_ID_SI7018: str = "SiS 7018"; break;
964 }
965 sprintf(cinfo.name, str);
966 snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
967
968 for (i = 0; i < 4; i++)
969 snd_trident_synth_create_port(trident, i);
970
971 trident->synth.ilist = snd_seq_instr_list_new();
972 if (trident->synth.ilist == NULL) {
973 snd_seq_delete_kernel_client(client);
974 trident->synth.seq_client = -1;
975 return -ENOMEM;
976 }
977 trident->synth.ilist->flags = SNDRV_SEQ_INSTR_FLG_DIRECT;
978
979 simpleops = &trident->synth.simple_ops;
980 snd_seq_simple_init(simpleops, trident, NULL);
981 simpleops->put_sample = snd_trident_simple_put_sample;
982 simpleops->get_sample = snd_trident_simple_get_sample;
983 simpleops->remove_sample = snd_trident_simple_remove_sample;
984 simpleops->notify = snd_trident_synth_instr_notify;
985
986 memset(&sub, 0, sizeof(sub));
987 sub.sender.client = SNDRV_SEQ_CLIENT_SYSTEM;
988 sub.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
989 sub.dest.client = client;
990 sub.dest.port = 0;
991 snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &sub);
992
993 return 0;
994}
995
996static int snd_trident_synth_delete_device(snd_seq_device_t *dev)
997{
998 trident_t *trident;
999
1000 trident = *(trident_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
1001 if (trident == NULL)
1002 return -EINVAL;
1003
1004 if (trident->synth.seq_client >= 0) {
1005 snd_seq_delete_kernel_client(trident->synth.seq_client);
1006 trident->synth.seq_client = -1;
1007 }
1008 if (trident->synth.ilist)
1009 snd_seq_instr_list_free(&trident->synth.ilist);
1010 return 0;
1011}
1012
1013static int __init alsa_trident_synth_init(void)
1014{
1015 static snd_seq_dev_ops_t ops =
1016 {
1017 snd_trident_synth_new_device,
1018 snd_trident_synth_delete_device
1019 };
1020
1021 return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_TRIDENT, &ops,
1022 sizeof(trident_t*));
1023}
1024
1025static void __exit alsa_trident_synth_exit(void)
1026{
1027 snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_TRIDENT);
1028}
1029
1030module_init(alsa_trident_synth_init)
1031module_exit(alsa_trident_synth_exit)