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 	int curvol;
74 	unsigned long curfreq;
75 	int stereo;
76 };
77 
78 static int volconvert(int level)
79 {
80 	level>>=14;		/* Map 16bits down to 2 bit */
81 	level&=3;
82 
83 	/* convert to card-friendly values */
84 	switch (level)
85 	{
86 		case 0:
87 			return 0;
88 		case 1:
89 			return 1;
90 		case 2:
91 			return 4;
92 		case 3:
93 			return 5;
94 	}
95 	return 0;	/* Quieten gcc */
96 }
97 
98 static void send_0_byte (struct az_device *dev)
99 {
100 	udelay(radio_wait_time);
101 	outb_p(2+volconvert(dev->curvol), io);
102 	outb_p(64+2+volconvert(dev->curvol), io);
103 }
104 
105 static void send_1_byte (struct az_device *dev)
106 {
107 	udelay (radio_wait_time);
108 	outb_p(128+2+volconvert(dev->curvol), io);
109 	outb_p(128+64+2+volconvert(dev->curvol), io);
110 }
111 
112 static int az_setvol(struct az_device *dev, int vol)
113 {
114 	mutex_lock(&lock);
115 	outb (volconvert(vol), io);
116 	mutex_unlock(&lock);
117 	return 0;
118 }
119 
120 /* thanks to Michael Dwyer for giving me a dose of clues in
121  * the signal strength department..
122  *
123  * This card has a stereo bit - bit 0 set = mono, not set = stereo
124  * It also has a "signal" bit - bit 1 set = bad signal, not set = good
125  *
126  */
127 
128 static int az_getsigstr(struct az_device *dev)
129 {
130 	if (inb(io) & 2)	/* bit set = no signal present */
131 		return 0;
132 	return 1;		/* signal present */
133 }
134 
135 static int az_getstereo(struct az_device *dev)
136 {
137 	if (inb(io) & 1) 	/* bit set = mono */
138 		return 0;
139 	return 1;		/* stereo */
140 }
141 
142 static int az_setfreq(struct az_device *dev, unsigned long frequency)
143 {
144 	int  i;
145 
146 	frequency += 171200;		/* Add 10.7 MHz IF		*/
147 	frequency /= 800;		/* Convert to 50 kHz units	*/
148 
149 	mutex_lock(&lock);
150 
151 	send_0_byte (dev);		/*  0: LSB of frequency       */
152 
153 	for (i = 0; i < 13; i++)	/*   : frequency bits (1-13)  */
154 		if (frequency & (1 << i))
155 			send_1_byte (dev);
156 		else
157 			send_0_byte (dev);
158 
159 	send_0_byte (dev);		/* 14: test bit - always 0    */
160 	send_0_byte (dev);		/* 15: test bit - always 0    */
161 	send_0_byte (dev);		/* 16: band data 0 - always 0 */
162 	if (dev->stereo)		/* 17: stereo (1 to enable)   */
163 		send_1_byte (dev);
164 	else
165 		send_0_byte (dev);
166 
167 	send_1_byte (dev);		/* 18: band data 1 - unknown  */
168 	send_0_byte (dev);		/* 19: time base - always 0   */
169 	send_0_byte (dev);		/* 20: spacing (0 = 25 kHz)   */
170 	send_1_byte (dev);		/* 21: spacing (1 = 25 kHz)   */
171 	send_0_byte (dev);		/* 22: spacing (0 = 25 kHz)   */
172 	send_1_byte (dev);		/* 23: AM/FM (FM = 1, always) */
173 
174 	/* latch frequency */
175 
176 	udelay (radio_wait_time);
177 	outb_p(128+64+volconvert(dev->curvol), io);
178 
179 	mutex_unlock(&lock);
180 
181 	return 0;
182 }
183 
184 static int vidioc_querycap (struct file *file, void  *priv,
185 					struct v4l2_capability *v)
186 {
187 	strlcpy(v->driver, "radio-aztech", sizeof (v->driver));
188 	strlcpy(v->card, "Aztech Radio", sizeof (v->card));
189 	sprintf(v->bus_info,"ISA");
190 	v->version = RADIO_VERSION;
191 	v->capabilities = V4L2_CAP_TUNER;
192 	return 0;
193 }
194 
195 static int vidioc_g_tuner (struct file *file, void *priv,
196 				struct v4l2_tuner *v)
197 {
198 	struct video_device *dev = video_devdata(file);
199 	struct az_device *az = dev->priv;
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 video_device *dev = video_devdata(file);
268 	struct az_device *az = dev->priv;
269 
270 	az->curfreq = f->frequency;
271 	az_setfreq(az, az->curfreq);
272 	return 0;
273 }
274 
275 static int vidioc_g_frequency (struct file *file, void *priv,
276 				struct v4l2_frequency *f)
277 {
278 	struct video_device *dev = video_devdata(file);
279 	struct az_device *az = dev->priv;
280 
281 	f->type = V4L2_TUNER_RADIO;
282 	f->frequency = az->curfreq;
283 
284 	return 0;
285 }
286 
287 static int vidioc_queryctrl (struct file *file, void *priv,
288 			    struct v4l2_queryctrl *qc)
289 {
290 	int i;
291 
292 	for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
293 		if (qc->id && qc->id == radio_qctrl[i].id) {
294 			memcpy(qc, &(radio_qctrl[i]),
295 						sizeof(*qc));
296 			return (0);
297 		}
298 	}
299 	return -EINVAL;
300 }
301 
302 static int vidioc_g_ctrl (struct file *file, void *priv,
303 			    struct v4l2_control *ctrl)
304 {
305 	struct video_device *dev = video_devdata(file);
306 	struct az_device *az = dev->priv;
307 
308 	switch (ctrl->id) {
309 		case V4L2_CID_AUDIO_MUTE:
310 			if (az->curvol==0)
311 				ctrl->value=1;
312 			else
313 				ctrl->value=0;
314 			return (0);
315 		case V4L2_CID_AUDIO_VOLUME:
316 			ctrl->value=az->curvol * 6554;
317 			return (0);
318 	}
319 	return -EINVAL;
320 }
321 
322 static int vidioc_s_ctrl (struct file *file, void *priv,
323 			    struct v4l2_control *ctrl)
324 {
325 	struct video_device *dev = video_devdata(file);
326 	struct az_device *az = dev->priv;
327 
328 	switch (ctrl->id) {
329 		case V4L2_CID_AUDIO_MUTE:
330 			if (ctrl->value) {
331 				az_setvol(az,0);
332 			} else {
333 				az_setvol(az,az->curvol);
334 			}
335 			return (0);
336 		case V4L2_CID_AUDIO_VOLUME:
337 			az_setvol(az,ctrl->value);
338 			return (0);
339 	}
340 	return -EINVAL;
341 }
342 
343 static struct az_device aztech_unit;
344 
345 static const struct file_operations aztech_fops = {
346 	.owner		= THIS_MODULE,
347 	.open           = video_exclusive_open,
348 	.release        = video_exclusive_release,
349 	.ioctl		= video_ioctl2,
350 #ifdef CONFIG_COMPAT
351 	.compat_ioctl	= v4l_compat_ioctl32,
352 #endif
353 	.llseek         = no_llseek,
354 };
355 
356 static const struct v4l2_ioctl_ops aztech_ioctl_ops = {
357 	.vidioc_querycap    = vidioc_querycap,
358 	.vidioc_g_tuner     = vidioc_g_tuner,
359 	.vidioc_s_tuner     = vidioc_s_tuner,
360 	.vidioc_g_audio     = vidioc_g_audio,
361 	.vidioc_s_audio     = vidioc_s_audio,
362 	.vidioc_g_input     = vidioc_g_input,
363 	.vidioc_s_input     = vidioc_s_input,
364 	.vidioc_g_frequency = vidioc_g_frequency,
365 	.vidioc_s_frequency = vidioc_s_frequency,
366 	.vidioc_queryctrl   = vidioc_queryctrl,
367 	.vidioc_g_ctrl      = vidioc_g_ctrl,
368 	.vidioc_s_ctrl      = vidioc_s_ctrl,
369 };
370 
371 static struct video_device aztech_radio = {
372 	.name		    = "Aztech radio",
373 	.fops               = &aztech_fops,
374 	.ioctl_ops 	    = &aztech_ioctl_ops,
375 };
376 
377 module_param_named(debug,aztech_radio.debug, int, 0644);
378 MODULE_PARM_DESC(debug,"activates debug info");
379 
380 static int __init aztech_init(void)
381 {
382 	if(io==-1)
383 	{
384 		printk(KERN_ERR "You must set an I/O address with io=0x???\n");
385 		return -EINVAL;
386 	}
387 
388 	if (!request_region(io, 2, "aztech"))
389 	{
390 		printk(KERN_ERR "aztech: port 0x%x already in use\n", io);
391 		return -EBUSY;
392 	}
393 
394 	mutex_init(&lock);
395 	aztech_radio.priv=&aztech_unit;
396 
397 	if (video_register_device(&aztech_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
398 		release_region(io,2);
399 		return -EINVAL;
400 	}
401 
402 	printk(KERN_INFO "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n");
403 	/* mute card - prevents noisy bootups */
404 	outb (0, io);
405 	return 0;
406 }
407 
408 MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
409 MODULE_DESCRIPTION("A driver for the Aztech radio card.");
410 MODULE_LICENSE("GPL");
411 
412 module_param(io, int, 0);
413 module_param(radio_nr, int, 0);
414 MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)");
415 
416 static void __exit aztech_cleanup(void)
417 {
418 	video_unregister_device(&aztech_radio);
419 	release_region(io,2);
420 }
421 
422 module_init(aztech_init);
423 module_exit(aztech_cleanup);
424