1 /* radio-aztech.c - Aztech radio card driver for Linux 2.2
2  *
3  * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
4  * Adapted to support the Video for Linux API by
5  * Russell Kroll <rkroll@exploits.org>.  Based on original tuner code by:
6  *
7  * Quay Ly
8  * Donald Song
9  * Jason Lewis      (jlewis@twilight.vtc.vsc.edu)
10  * Scott McGrath    (smcgrath@twilight.vtc.vsc.edu)
11  * William McGrath  (wmcgrath@twilight.vtc.vsc.edu)
12  *
13  * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/
14  * along with more information on the card itself.
15  *
16  * History:
17  * 1999-02-24	Russell Kroll <rkroll@exploits.org>
18  *		Fine tuning/VIDEO_TUNER_LOW
19  * 		Range expanded to 87-108 MHz (from 87.9-107.8)
20  *
21  * Notable changes from the original source:
22  * - includes stripped down to the essentials
23  * - for loops used as delays replaced with udelay()
24  * - #defines removed, changed to static values
25  * - tuning structure changed - no more character arrays, other changes
26 */
27 
28 #include <linux/module.h>	/* Modules 			*/
29 #include <linux/init.h>		/* Initdata			*/
30 #include <linux/ioport.h>	/* request_region		*/
31 #include <linux/delay.h>	/* udelay			*/
32 #include <asm/io.h>		/* outb, outb_p			*/
33 #include <asm/uaccess.h>	/* copy to/from user		*/
34 #include <linux/videodev2.h>	/* kernel radio structs		*/
35 #include <media/v4l2-common.h>
36 #include <media/v4l2-ioctl.h>
37 
38 #include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
39 #define RADIO_VERSION KERNEL_VERSION(0,0,2)
40 
41 static struct v4l2_queryctrl radio_qctrl[] = {
42 	{
43 		.id            = V4L2_CID_AUDIO_MUTE,
44 		.name          = "Mute",
45 		.minimum       = 0,
46 		.maximum       = 1,
47 		.default_value = 1,
48 		.type          = V4L2_CTRL_TYPE_BOOLEAN,
49 	},{
50 		.id            = V4L2_CID_AUDIO_VOLUME,
51 		.name          = "Volume",
52 		.minimum       = 0,
53 		.maximum       = 0xff,
54 		.step          = 1,
55 		.default_value = 0xff,
56 		.type          = V4L2_CTRL_TYPE_INTEGER,
57 	}
58 };
59 
60 /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
61 
62 #ifndef CONFIG_RADIO_AZTECH_PORT
63 #define CONFIG_RADIO_AZTECH_PORT -1
64 #endif
65 
66 static int io = CONFIG_RADIO_AZTECH_PORT;
67 static int radio_nr = -1;
68 static int radio_wait_time = 1000;
69 static struct mutex lock;
70 
71 struct az_device
72 {
73 	unsigned long in_use;
74 	int curvol;
75 	unsigned long curfreq;
76 	int stereo;
77 };
78 
79 static int volconvert(int level)
80 {
81 	level>>=14;		/* Map 16bits down to 2 bit */
82 	level&=3;
83 
84 	/* convert to card-friendly values */
85 	switch (level)
86 	{
87 		case 0:
88 			return 0;
89 		case 1:
90 			return 1;
91 		case 2:
92 			return 4;
93 		case 3:
94 			return 5;
95 	}
96 	return 0;	/* Quieten gcc */
97 }
98 
99 static void send_0_byte (struct az_device *dev)
100 {
101 	udelay(radio_wait_time);
102 	outb_p(2+volconvert(dev->curvol), io);
103 	outb_p(64+2+volconvert(dev->curvol), io);
104 }
105 
106 static void send_1_byte (struct az_device *dev)
107 {
108 	udelay (radio_wait_time);
109 	outb_p(128+2+volconvert(dev->curvol), io);
110 	outb_p(128+64+2+volconvert(dev->curvol), io);
111 }
112 
113 static int az_setvol(struct az_device *dev, int vol)
114 {
115 	mutex_lock(&lock);
116 	outb (volconvert(vol), io);
117 	mutex_unlock(&lock);
118 	return 0;
119 }
120 
121 /* thanks to Michael Dwyer for giving me a dose of clues in
122  * the signal strength department..
123  *
124  * This card has a stereo bit - bit 0 set = mono, not set = stereo
125  * It also has a "signal" bit - bit 1 set = bad signal, not set = good
126  *
127  */
128 
129 static int az_getsigstr(struct az_device *dev)
130 {
131 	if (inb(io) & 2)	/* bit set = no signal present */
132 		return 0;
133 	return 1;		/* signal present */
134 }
135 
136 static int az_getstereo(struct az_device *dev)
137 {
138 	if (inb(io) & 1) 	/* bit set = mono */
139 		return 0;
140 	return 1;		/* stereo */
141 }
142 
143 static int az_setfreq(struct az_device *dev, unsigned long frequency)
144 {
145 	int  i;
146 
147 	frequency += 171200;		/* Add 10.7 MHz IF		*/
148 	frequency /= 800;		/* Convert to 50 kHz units	*/
149 
150 	mutex_lock(&lock);
151 
152 	send_0_byte (dev);		/*  0: LSB of frequency       */
153 
154 	for (i = 0; i < 13; i++)	/*   : frequency bits (1-13)  */
155 		if (frequency & (1 << i))
156 			send_1_byte (dev);
157 		else
158 			send_0_byte (dev);
159 
160 	send_0_byte (dev);		/* 14: test bit - always 0    */
161 	send_0_byte (dev);		/* 15: test bit - always 0    */
162 	send_0_byte (dev);		/* 16: band data 0 - always 0 */
163 	if (dev->stereo)		/* 17: stereo (1 to enable)   */
164 		send_1_byte (dev);
165 	else
166 		send_0_byte (dev);
167 
168 	send_1_byte (dev);		/* 18: band data 1 - unknown  */
169 	send_0_byte (dev);		/* 19: time base - always 0   */
170 	send_0_byte (dev);		/* 20: spacing (0 = 25 kHz)   */
171 	send_1_byte (dev);		/* 21: spacing (1 = 25 kHz)   */
172 	send_0_byte (dev);		/* 22: spacing (0 = 25 kHz)   */
173 	send_1_byte (dev);		/* 23: AM/FM (FM = 1, always) */
174 
175 	/* latch frequency */
176 
177 	udelay (radio_wait_time);
178 	outb_p(128+64+volconvert(dev->curvol), io);
179 
180 	mutex_unlock(&lock);
181 
182 	return 0;
183 }
184 
185 static int vidioc_querycap (struct file *file, void  *priv,
186 					struct v4l2_capability *v)
187 {
188 	strlcpy(v->driver, "radio-aztech", sizeof (v->driver));
189 	strlcpy(v->card, "Aztech Radio", sizeof (v->card));
190 	sprintf(v->bus_info,"ISA");
191 	v->version = RADIO_VERSION;
192 	v->capabilities = V4L2_CAP_TUNER;
193 	return 0;
194 }
195 
196 static int vidioc_g_tuner (struct file *file, void *priv,
197 				struct v4l2_tuner *v)
198 {
199 	struct az_device *az = video_drvdata(file);
200 
201 	if (v->index > 0)
202 		return -EINVAL;
203 
204 	strcpy(v->name, "FM");
205 	v->type = V4L2_TUNER_RADIO;
206 
207 	v->rangelow=(87*16000);
208 	v->rangehigh=(108*16000);
209 	v->rxsubchans =V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
210 	v->capability=V4L2_TUNER_CAP_LOW;
211 	if(az_getstereo(az))
212 		v->audmode = V4L2_TUNER_MODE_STEREO;
213 	else
214 		v->audmode = V4L2_TUNER_MODE_MONO;
215 	v->signal=0xFFFF*az_getsigstr(az);
216 
217 	return 0;
218 }
219 
220 
221 static int vidioc_s_tuner (struct file *file, void *priv,
222 				struct v4l2_tuner *v)
223 {
224 	if (v->index > 0)
225 		return -EINVAL;
226 
227 	return 0;
228 }
229 
230 static int vidioc_g_audio (struct file *file, void *priv,
231 			   struct v4l2_audio *a)
232 {
233 	if (a->index > 1)
234 		return -EINVAL;
235 
236 	strcpy(a->name, "Radio");
237 	a->capability = V4L2_AUDCAP_STEREO;
238 	return 0;
239 }
240 
241 static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
242 {
243 	*i = 0;
244 	return 0;
245 }
246 
247 static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
248 {
249 	if (i != 0)
250 		return -EINVAL;
251 	return 0;
252 }
253 
254 
255 static int vidioc_s_audio (struct file *file, void *priv,
256 			   struct v4l2_audio *a)
257 {
258 	if (a->index != 0)
259 		return -EINVAL;
260 
261 	return 0;
262 }
263 
264 static int vidioc_s_frequency (struct file *file, void *priv,
265 				struct v4l2_frequency *f)
266 {
267 	struct az_device *az = video_drvdata(file);
268 
269 	az->curfreq = f->frequency;
270 	az_setfreq(az, az->curfreq);
271 	return 0;
272 }
273 
274 static int vidioc_g_frequency (struct file *file, void *priv,
275 				struct v4l2_frequency *f)
276 {
277 	struct az_device *az = video_drvdata(file);
278 
279 	f->type = V4L2_TUNER_RADIO;
280 	f->frequency = az->curfreq;
281 
282 	return 0;
283 }
284 
285 static int vidioc_queryctrl (struct file *file, void *priv,
286 			    struct v4l2_queryctrl *qc)
287 {
288 	int i;
289 
290 	for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
291 		if (qc->id && qc->id == radio_qctrl[i].id) {
292 			memcpy(qc, &(radio_qctrl[i]),
293 						sizeof(*qc));
294 			return (0);
295 		}
296 	}
297 	return -EINVAL;
298 }
299 
300 static int vidioc_g_ctrl (struct file *file, void *priv,
301 			    struct v4l2_control *ctrl)
302 {
303 	struct az_device *az = video_drvdata(file);
304 
305 	switch (ctrl->id) {
306 		case V4L2_CID_AUDIO_MUTE:
307 			if (az->curvol==0)
308 				ctrl->value=1;
309 			else
310 				ctrl->value=0;
311 			return (0);
312 		case V4L2_CID_AUDIO_VOLUME:
313 			ctrl->value=az->curvol * 6554;
314 			return (0);
315 	}
316 	return -EINVAL;
317 }
318 
319 static int vidioc_s_ctrl (struct file *file, void *priv,
320 			    struct v4l2_control *ctrl)
321 {
322 	struct az_device *az = video_drvdata(file);
323 
324 	switch (ctrl->id) {
325 		case V4L2_CID_AUDIO_MUTE:
326 			if (ctrl->value) {
327 				az_setvol(az,0);
328 			} else {
329 				az_setvol(az,az->curvol);
330 			}
331 			return (0);
332 		case V4L2_CID_AUDIO_VOLUME:
333 			az_setvol(az,ctrl->value);
334 			return (0);
335 	}
336 	return -EINVAL;
337 }
338 
339 static struct az_device aztech_unit;
340 
341 static int aztech_exclusive_open(struct inode *inode, struct file *file)
342 {
343 	return test_and_set_bit(0, &aztech_unit.in_use) ? -EBUSY : 0;
344 }
345 
346 static int aztech_exclusive_release(struct inode *inode, struct file *file)
347 {
348 	clear_bit(0, &aztech_unit.in_use);
349 	return 0;
350 }
351 
352 static const struct file_operations aztech_fops = {
353 	.owner		= THIS_MODULE,
354 	.open           = aztech_exclusive_open,
355 	.release        = aztech_exclusive_release,
356 	.ioctl		= video_ioctl2,
357 #ifdef CONFIG_COMPAT
358 	.compat_ioctl	= v4l_compat_ioctl32,
359 #endif
360 	.llseek         = no_llseek,
361 };
362 
363 static const struct v4l2_ioctl_ops aztech_ioctl_ops = {
364 	.vidioc_querycap    = vidioc_querycap,
365 	.vidioc_g_tuner     = vidioc_g_tuner,
366 	.vidioc_s_tuner     = vidioc_s_tuner,
367 	.vidioc_g_audio     = vidioc_g_audio,
368 	.vidioc_s_audio     = vidioc_s_audio,
369 	.vidioc_g_input     = vidioc_g_input,
370 	.vidioc_s_input     = vidioc_s_input,
371 	.vidioc_g_frequency = vidioc_g_frequency,
372 	.vidioc_s_frequency = vidioc_s_frequency,
373 	.vidioc_queryctrl   = vidioc_queryctrl,
374 	.vidioc_g_ctrl      = vidioc_g_ctrl,
375 	.vidioc_s_ctrl      = vidioc_s_ctrl,
376 };
377 
378 static struct video_device aztech_radio = {
379 	.name		= "Aztech radio",
380 	.fops           = &aztech_fops,
381 	.ioctl_ops 	= &aztech_ioctl_ops,
382 	.release	= video_device_release_empty,
383 };
384 
385 module_param_named(debug,aztech_radio.debug, int, 0644);
386 MODULE_PARM_DESC(debug,"activates debug info");
387 
388 static int __init aztech_init(void)
389 {
390 	if(io==-1)
391 	{
392 		printk(KERN_ERR "You must set an I/O address with io=0x???\n");
393 		return -EINVAL;
394 	}
395 
396 	if (!request_region(io, 2, "aztech"))
397 	{
398 		printk(KERN_ERR "aztech: port 0x%x already in use\n", io);
399 		return -EBUSY;
400 	}
401 
402 	mutex_init(&lock);
403 	video_set_drvdata(&aztech_radio, &aztech_unit);
404 
405 	if (video_register_device(&aztech_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
406 		release_region(io,2);
407 		return -EINVAL;
408 	}
409 
410 	printk(KERN_INFO "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n");
411 	/* mute card - prevents noisy bootups */
412 	outb (0, io);
413 	return 0;
414 }
415 
416 MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
417 MODULE_DESCRIPTION("A driver for the Aztech radio card.");
418 MODULE_LICENSE("GPL");
419 
420 module_param(io, int, 0);
421 module_param(radio_nr, int, 0);
422 MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)");
423 
424 static void __exit aztech_cleanup(void)
425 {
426 	video_unregister_device(&aztech_radio);
427 	release_region(io,2);
428 }
429 
430 module_init(aztech_init);
431 module_exit(aztech_cleanup);
432