1/* zoltrix radio plus driver for Linux radio support
2 * (c) 1998 C. van Schaik <carl@leg.uct.ac.za>
3 *
4 * BUGS
5 *  Due to the inconsistency in reading from the signal flags
6 *  it is difficult to get an accurate tuned signal.
7 *
8 *  It seems that the card is not linear to 0 volume. It cuts off
9 *  at a low volume, and it is not possible (at least I have not found)
10 *  to get fine volume control over the low volume range.
11 *
12 *  Some code derived from code by Romolo Manfredini
13 *				   romolo@bicnet.it
14 *
15 * 1999-05-06 - (C. van Schaik)
16 *	      - Make signal strength and stereo scans
17 *		kinder to cpu while in delay
18 * 1999-01-05 - (C. van Schaik)
19 *	      - Changed tuning to 1/160Mhz accuracy
20 *	      - Added stereo support
21 *		(card defaults to stereo)
22 *		(can explicitly force mono on the card)
23 *		(can detect if station is in stereo)
24 *	      - Added unmute function
25 *	      - Reworked ioctl functions
26 * 2002-07-15 - Fix Stereo typo
27 *
28 * 2006-07-24 - Converted to V4L2 API
29 *		by Mauro Carvalho Chehab <mchehab@infradead.org>
30 */
31
32#include <linux/module.h>	/* Modules                        */
33#include <linux/init.h>		/* Initdata                       */
34#include <linux/ioport.h>	/* request_region		  */
35#include <linux/delay.h>	/* udelay, msleep                 */
36#include <asm/io.h>		/* outb, outb_p                   */
37#include <asm/uaccess.h>	/* copy to/from user              */
38#include <linux/videodev2.h>	/* kernel radio structs           */
39#include <media/v4l2-common.h>
40
41#include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
42#define RADIO_VERSION KERNEL_VERSION(0,0,2)
43
44static struct v4l2_queryctrl radio_qctrl[] = {
45	{
46		.id            = V4L2_CID_AUDIO_MUTE,
47		.name          = "Mute",
48		.minimum       = 0,
49		.maximum       = 1,
50		.default_value = 1,
51		.type          = V4L2_CTRL_TYPE_BOOLEAN,
52	},{
53		.id            = V4L2_CID_AUDIO_VOLUME,
54		.name          = "Volume",
55		.minimum       = 0,
56		.maximum       = 65535,
57		.step          = 4096,
58		.default_value = 0xff,
59		.type          = V4L2_CTRL_TYPE_INTEGER,
60	}
61};
62
63#ifndef CONFIG_RADIO_ZOLTRIX_PORT
64#define CONFIG_RADIO_ZOLTRIX_PORT -1
65#endif
66
67static int io = CONFIG_RADIO_ZOLTRIX_PORT;
68static int radio_nr = -1;
69
70struct zol_device {
71	int port;
72	int curvol;
73	unsigned long curfreq;
74	int muted;
75	unsigned int stereo;
76	struct mutex lock;
77};
78
79static int zol_setvol(struct zol_device *dev, int vol)
80{
81	dev->curvol = vol;
82	if (dev->muted)
83		return 0;
84
85	mutex_lock(&dev->lock);
86	if (vol == 0) {
87		outb(0, io);
88		outb(0, io);
89		inb(io + 3);    /* Zoltrix needs to be read to confirm */
90		mutex_unlock(&dev->lock);
91		return 0;
92	}
93
94	outb(dev->curvol-1, io);
95	msleep(10);
96	inb(io + 2);
97	mutex_unlock(&dev->lock);
98	return 0;
99}
100
101static void zol_mute(struct zol_device *dev)
102{
103	dev->muted = 1;
104	mutex_lock(&dev->lock);
105	outb(0, io);
106	outb(0, io);
107	inb(io + 3);            /* Zoltrix needs to be read to confirm */
108	mutex_unlock(&dev->lock);
109}
110
111static void zol_unmute(struct zol_device *dev)
112{
113	dev->muted = 0;
114	zol_setvol(dev, dev->curvol);
115}
116
117static int zol_setfreq(struct zol_device *dev, unsigned long freq)
118{
119	/* tunes the radio to the desired frequency */
120	unsigned long long bitmask, f, m;
121	unsigned int stereo = dev->stereo;
122	int i;
123
124	if (freq == 0)
125		return 1;
126	m = (freq / 160 - 8800) * 2;
127	f = (unsigned long long) m + 0x4d1c;
128
129	bitmask = 0xc480402c10080000ull;
130	i = 45;
131
132	mutex_lock(&dev->lock);
133
134	outb(0, io);
135	outb(0, io);
136	inb(io + 3);            /* Zoltrix needs to be read to confirm */
137
138	outb(0x40, io);
139	outb(0xc0, io);
140
141	bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31));
142	while (i--) {
143		if ((bitmask & 0x8000000000000000ull) != 0) {
144			outb(0x80, io);
145			udelay(50);
146			outb(0x00, io);
147			udelay(50);
148			outb(0x80, io);
149			udelay(50);
150		} else {
151			outb(0xc0, io);
152			udelay(50);
153			outb(0x40, io);
154			udelay(50);
155			outb(0xc0, io);
156			udelay(50);
157		}
158		bitmask *= 2;
159	}
160	/* termination sequence */
161	outb(0x80, io);
162	outb(0xc0, io);
163	outb(0x40, io);
164	udelay(1000);
165	inb(io+2);
166
167	udelay(1000);
168
169	if (dev->muted)
170	{
171		outb(0, io);
172		outb(0, io);
173		inb(io + 3);
174		udelay(1000);
175	}
176
177	mutex_unlock(&dev->lock);
178
179	if(!dev->muted)
180	{
181		zol_setvol(dev, dev->curvol);
182	}
183	return 0;
184}
185
186/* Get signal strength */
187
188static int zol_getsigstr(struct zol_device *dev)
189{
190	int a, b;
191
192	mutex_lock(&dev->lock);
193	outb(0x00, io);         /* This stuff I found to do nothing */
194	outb(dev->curvol, io);
195	msleep(20);
196
197	a = inb(io);
198	msleep(10);
199	b = inb(io);
200
201	mutex_unlock(&dev->lock);
202
203	if (a != b)
204		return (0);
205
206	if ((a == 0xcf) || (a == 0xdf)  /* I found this out by playing */
207		|| (a == 0xef))       /* with a binary scanner on the card io */
208		return (1);
209	return (0);
210}
211
212static int zol_is_stereo (struct zol_device *dev)
213{
214	int x1, x2;
215
216	mutex_lock(&dev->lock);
217
218	outb(0x00, io);
219	outb(dev->curvol, io);
220	msleep(20);
221
222	x1 = inb(io);
223	msleep(10);
224	x2 = inb(io);
225
226	mutex_unlock(&dev->lock);
227
228	if ((x1 == x2) && (x1 == 0xcf))
229		return 1;
230	return 0;
231}
232
233static int vidioc_querycap(struct file *file, void  *priv,
234					struct v4l2_capability *v)
235{
236	strlcpy(v->driver, "radio-zoltrix", sizeof(v->driver));
237	strlcpy(v->card, "Zoltrix Radio", sizeof(v->card));
238	sprintf(v->bus_info, "ISA");
239	v->version = RADIO_VERSION;
240	v->capabilities = V4L2_CAP_TUNER;
241	return 0;
242}
243
244static int vidioc_g_tuner(struct file *file, void *priv,
245					struct v4l2_tuner *v)
246{
247	struct video_device *dev = video_devdata(file);
248	struct zol_device *zol = dev->priv;
249
250	if (v->index > 0)
251		return -EINVAL;
252
253	strcpy(v->name, "FM");
254	v->type = V4L2_TUNER_RADIO;
255	v->rangelow = (88*16000);
256	v->rangehigh = (108*16000);
257	v->rxsubchans = V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
258	v->capability = V4L2_TUNER_CAP_LOW;
259	if (zol_is_stereo(zol))
260		v->audmode = V4L2_TUNER_MODE_STEREO;
261	else
262		v->audmode = V4L2_TUNER_MODE_MONO;
263	v->signal = 0xFFFF*zol_getsigstr(zol);
264	return 0;
265}
266
267static int vidioc_s_tuner(struct file *file, void *priv,
268					struct v4l2_tuner *v)
269{
270	if (v->index > 0)
271		return -EINVAL;
272	return 0;
273}
274
275static int vidioc_s_frequency(struct file *file, void *priv,
276					struct v4l2_frequency *f)
277{
278	struct video_device *dev = video_devdata(file);
279	struct zol_device *zol = dev->priv;
280
281	zol->curfreq = f->frequency;
282	zol_setfreq(zol, zol->curfreq);
283	return 0;
284}
285
286static int vidioc_g_frequency(struct file *file, void *priv,
287					struct v4l2_frequency *f)
288{
289	struct video_device *dev = video_devdata(file);
290	struct zol_device *zol = dev->priv;
291
292	f->type = V4L2_TUNER_RADIO;
293	f->frequency = zol->curfreq;
294	return 0;
295}
296
297static int vidioc_queryctrl(struct file *file, void *priv,
298					struct v4l2_queryctrl *qc)
299{
300	int i;
301
302	for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
303		if (qc->id && qc->id == radio_qctrl[i].id) {
304			memcpy(qc, &(radio_qctrl[i]),
305						sizeof(*qc));
306			return 0;
307		}
308	}
309	return -EINVAL;
310}
311
312static int vidioc_g_ctrl(struct file *file, void *priv,
313				struct v4l2_control *ctrl)
314{
315	struct video_device *dev = video_devdata(file);
316	struct zol_device *zol = dev->priv;
317
318	switch (ctrl->id) {
319	case V4L2_CID_AUDIO_MUTE:
320		ctrl->value = zol->muted;
321		return 0;
322	case V4L2_CID_AUDIO_VOLUME:
323		ctrl->value = zol->curvol * 4096;
324		return 0;
325	}
326	return -EINVAL;
327}
328
329static int vidioc_s_ctrl(struct file *file, void *priv,
330				struct v4l2_control *ctrl)
331{
332	struct video_device *dev = video_devdata(file);
333	struct zol_device *zol = dev->priv;
334
335	switch (ctrl->id) {
336	case V4L2_CID_AUDIO_MUTE:
337		if (ctrl->value)
338			zol_mute(zol);
339		else {
340			zol_unmute(zol);
341			zol_setvol(zol,zol->curvol);
342		}
343		return 0;
344	case V4L2_CID_AUDIO_VOLUME:
345		zol_setvol(zol,ctrl->value/4096);
346		return 0;
347	}
348	zol->stereo = 1;
349	zol_setfreq(zol, zol->curfreq);
350	return -EINVAL;
351}
352
353static int vidioc_g_audio(struct file *file, void *priv,
354					struct v4l2_audio *a)
355{
356	if (a->index > 1)
357		return -EINVAL;
358
359	strcpy(a->name, "Radio");
360	a->capability = V4L2_AUDCAP_STEREO;
361	return 0;
362}
363
364static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
365{
366	*i = 0;
367	return 0;
368}
369
370static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
371{
372	if (i != 0)
373		return -EINVAL;
374	return 0;
375}
376
377static int vidioc_s_audio(struct file *file, void *priv,
378					struct v4l2_audio *a)
379{
380	if (a->index != 0)
381		return -EINVAL;
382	return 0;
383}
384
385static struct zol_device zoltrix_unit;
386
387static const struct file_operations zoltrix_fops =
388{
389	.owner		= THIS_MODULE,
390	.open           = video_exclusive_open,
391	.release        = video_exclusive_release,
392	.ioctl		= video_ioctl2,
393	.compat_ioctl	= v4l_compat_ioctl32,
394	.llseek         = no_llseek,
395};
396
397static struct video_device zoltrix_radio =
398{
399	.owner		= THIS_MODULE,
400	.name		= "Zoltrix Radio Plus",
401	.type		= VID_TYPE_TUNER,
402	.fops           = &zoltrix_fops,
403	.vidioc_querycap    = vidioc_querycap,
404	.vidioc_g_tuner     = vidioc_g_tuner,
405	.vidioc_s_tuner     = vidioc_s_tuner,
406	.vidioc_g_audio     = vidioc_g_audio,
407	.vidioc_s_audio     = vidioc_s_audio,
408	.vidioc_g_input     = vidioc_g_input,
409	.vidioc_s_input     = vidioc_s_input,
410	.vidioc_g_frequency = vidioc_g_frequency,
411	.vidioc_s_frequency = vidioc_s_frequency,
412	.vidioc_queryctrl   = vidioc_queryctrl,
413	.vidioc_g_ctrl      = vidioc_g_ctrl,
414	.vidioc_s_ctrl      = vidioc_s_ctrl,
415};
416
417static int __init zoltrix_init(void)
418{
419	if (io == -1) {
420		printk(KERN_ERR "You must set an I/O address with io=0x???\n");
421		return -EINVAL;
422	}
423	if ((io != 0x20c) && (io != 0x30c)) {
424		printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n");
425		return -ENXIO;
426	}
427
428	zoltrix_radio.priv = &zoltrix_unit;
429	if (!request_region(io, 2, "zoltrix")) {
430		printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io);
431		return -EBUSY;
432	}
433
434	if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1)
435	{
436		release_region(io, 2);
437		return -EINVAL;
438	}
439	printk(KERN_INFO "Zoltrix Radio Plus card driver.\n");
440
441	mutex_init(&zoltrix_unit.lock);
442
443	/* mute card - prevents noisy bootups */
444
445	/* this ensures that the volume is all the way down  */
446
447	outb(0, io);
448	outb(0, io);
449	msleep(20);
450	inb(io + 3);
451
452	zoltrix_unit.curvol = 0;
453	zoltrix_unit.stereo = 1;
454
455	return 0;
456}
457
458MODULE_AUTHOR("C.van Schaik");
459MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
460MODULE_LICENSE("GPL");
461
462module_param(io, int, 0);
463MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
464module_param(radio_nr, int, 0);
465
466static void __exit zoltrix_cleanup_module(void)
467{
468	video_unregister_device(&zoltrix_radio);
469	release_region(io, 2);
470}
471
472module_init(zoltrix_init);
473module_exit(zoltrix_cleanup_module);
474