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