1 /*
2  * sound/ics2101.c
3  *
4  * Driver for the ICS2101 mixer of GUS v3.7.
5  *
6  *
7  * Copyright (C) by Hannu Savolainen 1993-1997
8  *
9  * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
10  * Version 2 (June 1991). See the "COPYING" file distributed with this software
11  * for more info.
12  *
13  *
14  * Thomas Sailer   : ioctl code reworked (vmalloc/vfree removed)
15  * Bartlomiej Zolnierkiewicz : added __init to ics2101_mixer_init()
16  */
17 #include <linux/init.h>
18 #include "sound_config.h"
19 
20 #include <linux/ultrasound.h>
21 
22 #include "gus.h"
23 #include "gus_hw.h"
24 
25 #define MIX_DEVS	(SOUND_MASK_MIC|SOUND_MASK_LINE| \
26 			 SOUND_MASK_SYNTH| \
27   			 SOUND_MASK_CD | SOUND_MASK_VOLUME)
28 
29 extern int     *gus_osp;
30 extern int      gus_base;
31 static int      volumes[ICS_MIXDEVS];
32 static int      left_fix[ICS_MIXDEVS] =
33 {1, 1, 1, 2, 1, 2};
34 static int      right_fix[ICS_MIXDEVS] =
35 {2, 2, 2, 1, 2, 1};
36 
scale_vol(int vol)37 static int scale_vol(int vol)
38 {
39 	/*
40 	 *  Experimental volume scaling by Risto Kankkunen.
41 	 *  This should give smoother volume response than just
42 	 *  a plain multiplication.
43 	 */
44 
45 	int e;
46 
47 	if (vol < 0)
48 		vol = 0;
49 	if (vol > 100)
50 		vol = 100;
51 	vol = (31 * vol + 50) / 100;
52 	e = 0;
53 	if (vol)
54 	{
55 		while (vol < 16)
56 		{
57 			vol <<= 1;
58 			e--;
59 		}
60 		vol -= 16;
61 		e += 7;
62 	}
63 	return ((e << 4) + vol);
64 }
65 
write_mix(int dev,int chn,int vol)66 static void write_mix(int dev, int chn, int vol)
67 {
68 	int *selector;
69 	unsigned long flags;
70 	int ctrl_addr = dev << 3;
71 	int attn_addr = dev << 3;
72 
73 	vol = scale_vol(vol);
74 
75 	if (chn == CHN_LEFT)
76 	{
77 		selector = left_fix;
78 		ctrl_addr |= 0x00;
79 		attn_addr |= 0x02;
80 	}
81 	else
82 	{
83 		selector = right_fix;
84 		ctrl_addr |= 0x01;
85 		attn_addr |= 0x03;
86 	}
87 
88 	save_flags(flags);
89 	cli();
90 	outb((ctrl_addr), u_MixSelect);
91 	outb((selector[dev]), u_MixData);
92 	outb((attn_addr), u_MixSelect);
93 	outb(((unsigned char) vol), u_MixData);
94 	restore_flags(flags);
95 }
96 
set_volumes(int dev,int vol)97 static int set_volumes(int dev, int vol)
98 {
99 	int left = vol & 0x00ff;
100 	int right = (vol >> 8) & 0x00ff;
101 
102 	if (left < 0)
103 		left = 0;
104 	if (left > 100)
105 		left = 100;
106 	if (right < 0)
107 		right = 0;
108 	if (right > 100)
109 		right = 100;
110 
111 	write_mix(dev, CHN_LEFT, left);
112 	write_mix(dev, CHN_RIGHT, right);
113 
114 	vol = left + (right << 8);
115 	volumes[dev] = vol;
116 	return vol;
117 }
118 
ics2101_mixer_ioctl(int dev,unsigned int cmd,caddr_t arg)119 static int ics2101_mixer_ioctl(int dev, unsigned int cmd, caddr_t arg)
120 {
121 	int val;
122 
123 	if (((cmd >> 8) & 0xff) == 'M') {
124 		if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
125 
126 			if (get_user(val, (int *)arg))
127 				return -EFAULT;
128 			switch (cmd & 0xff) {
129 			case SOUND_MIXER_RECSRC:
130 				return gus_default_mixer_ioctl(dev, cmd, arg);
131 
132 			case SOUND_MIXER_MIC:
133 				val = set_volumes(DEV_MIC, val);
134 				break;
135 
136 			case SOUND_MIXER_CD:
137 				val = set_volumes(DEV_CD, val);
138 				break;
139 
140 			case SOUND_MIXER_LINE:
141 				val = set_volumes(DEV_LINE, val);
142 				break;
143 
144 			case SOUND_MIXER_SYNTH:
145 				val = set_volumes(DEV_GF1, val);
146 				break;
147 
148 			case SOUND_MIXER_VOLUME:
149 				val = set_volumes(DEV_VOL, val);
150 				break;
151 
152 			default:
153 				return -EINVAL;
154 			}
155 			return put_user(val, (int *)arg);
156 		} else {
157 			switch (cmd & 0xff) {
158 				/*
159 				 * Return parameters
160 				 */
161 			case SOUND_MIXER_RECSRC:
162 				return gus_default_mixer_ioctl(dev, cmd, arg);
163 
164 			case SOUND_MIXER_DEVMASK:
165 				val = MIX_DEVS;
166 				break;
167 
168 			case SOUND_MIXER_STEREODEVS:
169 				val = SOUND_MASK_LINE | SOUND_MASK_CD | SOUND_MASK_SYNTH | SOUND_MASK_VOLUME | SOUND_MASK_MIC;
170 				break;
171 
172 			case SOUND_MIXER_RECMASK:
173 				val = SOUND_MASK_MIC | SOUND_MASK_LINE;
174 				break;
175 
176 			case SOUND_MIXER_CAPS:
177 				val = 0;
178 				break;
179 
180 			case SOUND_MIXER_MIC:
181 				val = volumes[DEV_MIC];
182 				break;
183 
184 			case SOUND_MIXER_LINE:
185 				val = volumes[DEV_LINE];
186 				break;
187 
188 			case SOUND_MIXER_CD:
189 				val = volumes[DEV_CD];
190 				break;
191 
192 			case SOUND_MIXER_VOLUME:
193 				val = volumes[DEV_VOL];
194 				break;
195 
196 			case SOUND_MIXER_SYNTH:
197 				val = volumes[DEV_GF1];
198 				break;
199 
200 			default:
201 				return -EINVAL;
202 			}
203 			return put_user(val, (int *)arg);
204 		}
205 	}
206 	return -EINVAL;
207 }
208 
209 static struct mixer_operations ics2101_mixer_operations =
210 {
211 	owner:	THIS_MODULE,
212 	id:	"ICS2101",
213 	name:	"ICS2101 Multimedia Mixer",
214 	ioctl:	ics2101_mixer_ioctl
215 };
216 
ics2101_mixer_init(void)217 int __init ics2101_mixer_init(void)
218 {
219 	int i;
220 	int n;
221 
222 	if ((n = sound_alloc_mixerdev()) != -1)
223 	{
224 		mixer_devs[n] = &ics2101_mixer_operations;
225 
226 		/*
227 		 * Some GUS v3.7 cards had some channels flipped. Disable
228 		 * the flipping feature if the model id is other than 5.
229 		 */
230 
231 		if (inb(u_MixSelect) != 5)
232 		{
233 			for (i = 0; i < ICS_MIXDEVS; i++)
234 				left_fix[i] = 1;
235 			for (i = 0; i < ICS_MIXDEVS; i++)
236 				right_fix[i] = 2;
237 		}
238 		set_volumes(DEV_GF1, 0x5a5a);
239 		set_volumes(DEV_CD, 0x5a5a);
240 		set_volumes(DEV_MIC, 0x0000);
241 		set_volumes(DEV_LINE, 0x5a5a);
242 		set_volumes(DEV_VOL, 0x5a5a);
243 		set_volumes(DEV_UNUSED, 0x0000);
244 	}
245 	return n;
246 }
247