• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /asuswrt-rt-n18u-9.0.0.4.380.2695/release/src-rt-6.x.4708/linux/linux-2.6.36/drivers/media/radio/
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 <linux/videodev2.h>	/* kernel radio structs           */
37#include <linux/mutex.h>
38#include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
39#include <linux/io.h>		/* outb, outb_p                   */
40#include <media/v4l2-device.h>
41#include <media/v4l2-ioctl.h>
42
43MODULE_AUTHOR("C.van Schaik");
44MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
45MODULE_LICENSE("GPL");
46
47#ifndef CONFIG_RADIO_ZOLTRIX_PORT
48#define CONFIG_RADIO_ZOLTRIX_PORT -1
49#endif
50
51static int io = CONFIG_RADIO_ZOLTRIX_PORT;
52static int radio_nr = -1;
53
54module_param(io, int, 0);
55MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
56module_param(radio_nr, int, 0);
57
58#define RADIO_VERSION KERNEL_VERSION(0, 0, 2)
59
60struct zoltrix {
61	struct v4l2_device v4l2_dev;
62	struct video_device vdev;
63	int io;
64	int curvol;
65	unsigned long curfreq;
66	int muted;
67	unsigned int stereo;
68	struct mutex lock;
69};
70
71static struct zoltrix zoltrix_card;
72
73static int zol_setvol(struct zoltrix *zol, int vol)
74{
75	zol->curvol = vol;
76	if (zol->muted)
77		return 0;
78
79	mutex_lock(&zol->lock);
80	if (vol == 0) {
81		outb(0, zol->io);
82		outb(0, zol->io);
83		inb(zol->io + 3);    /* Zoltrix needs to be read to confirm */
84		mutex_unlock(&zol->lock);
85		return 0;
86	}
87
88	outb(zol->curvol-1, zol->io);
89	msleep(10);
90	inb(zol->io + 2);
91	mutex_unlock(&zol->lock);
92	return 0;
93}
94
95static void zol_mute(struct zoltrix *zol)
96{
97	zol->muted = 1;
98	mutex_lock(&zol->lock);
99	outb(0, zol->io);
100	outb(0, zol->io);
101	inb(zol->io + 3);            /* Zoltrix needs to be read to confirm */
102	mutex_unlock(&zol->lock);
103}
104
105static void zol_unmute(struct zoltrix *zol)
106{
107	zol->muted = 0;
108	zol_setvol(zol, zol->curvol);
109}
110
111static int zol_setfreq(struct zoltrix *zol, unsigned long freq)
112{
113	/* tunes the radio to the desired frequency */
114	struct v4l2_device *v4l2_dev = &zol->v4l2_dev;
115	unsigned long long bitmask, f, m;
116	unsigned int stereo = zol->stereo;
117	int i;
118
119	if (freq == 0) {
120		v4l2_warn(v4l2_dev, "cannot set a frequency of 0.\n");
121		return -EINVAL;
122	}
123
124	m = (freq / 160 - 8800) * 2;
125	f = (unsigned long long)m + 0x4d1c;
126
127	bitmask = 0xc480402c10080000ull;
128	i = 45;
129
130	mutex_lock(&zol->lock);
131
132	zol->curfreq = freq;
133
134	outb(0, zol->io);
135	outb(0, zol->io);
136	inb(zol->io + 3);            /* Zoltrix needs to be read to confirm */
137
138	outb(0x40, zol->io);
139	outb(0xc0, zol->io);
140
141	bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31));
142	while (i--) {
143		if ((bitmask & 0x8000000000000000ull) != 0) {
144			outb(0x80, zol->io);
145			udelay(50);
146			outb(0x00, zol->io);
147			udelay(50);
148			outb(0x80, zol->io);
149			udelay(50);
150		} else {
151			outb(0xc0, zol->io);
152			udelay(50);
153			outb(0x40, zol->io);
154			udelay(50);
155			outb(0xc0, zol->io);
156			udelay(50);
157		}
158		bitmask *= 2;
159	}
160	/* termination sequence */
161	outb(0x80, zol->io);
162	outb(0xc0, zol->io);
163	outb(0x40, zol->io);
164	udelay(1000);
165	inb(zol->io + 2);
166
167	udelay(1000);
168
169	if (zol->muted) {
170		outb(0, zol->io);
171		outb(0, zol->io);
172		inb(zol->io + 3);
173		udelay(1000);
174	}
175
176	mutex_unlock(&zol->lock);
177
178	if (!zol->muted)
179		zol_setvol(zol, zol->curvol);
180	return 0;
181}
182
183/* Get signal strength */
184static int zol_getsigstr(struct zoltrix *zol)
185{
186	int a, b;
187
188	mutex_lock(&zol->lock);
189	outb(0x00, zol->io);         /* This stuff I found to do nothing */
190	outb(zol->curvol, zol->io);
191	msleep(20);
192
193	a = inb(zol->io);
194	msleep(10);
195	b = inb(zol->io);
196
197	mutex_unlock(&zol->lock);
198
199	if (a != b)
200		return 0;
201
202	/* I found this out by playing with a binary scanner on the card io */
203	return a == 0xcf || a == 0xdf || a == 0xef;
204}
205
206static int zol_is_stereo(struct zoltrix *zol)
207{
208	int x1, x2;
209
210	mutex_lock(&zol->lock);
211
212	outb(0x00, zol->io);
213	outb(zol->curvol, zol->io);
214	msleep(20);
215
216	x1 = inb(zol->io);
217	msleep(10);
218	x2 = inb(zol->io);
219
220	mutex_unlock(&zol->lock);
221
222	return x1 == x2 && x1 == 0xcf;
223}
224
225static int vidioc_querycap(struct file *file, void  *priv,
226					struct v4l2_capability *v)
227{
228	strlcpy(v->driver, "radio-zoltrix", sizeof(v->driver));
229	strlcpy(v->card, "Zoltrix Radio", sizeof(v->card));
230	strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
231	v->version = RADIO_VERSION;
232	v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
233	return 0;
234}
235
236static int vidioc_g_tuner(struct file *file, void *priv,
237					struct v4l2_tuner *v)
238{
239	struct zoltrix *zol = video_drvdata(file);
240
241	if (v->index > 0)
242		return -EINVAL;
243
244	strlcpy(v->name, "FM", sizeof(v->name));
245	v->type = V4L2_TUNER_RADIO;
246	v->rangelow = 88 * 16000;
247	v->rangehigh = 108 * 16000;
248	v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
249	v->capability = V4L2_TUNER_CAP_LOW;
250	if (zol_is_stereo(zol))
251		v->audmode = V4L2_TUNER_MODE_STEREO;
252	else
253		v->audmode = V4L2_TUNER_MODE_MONO;
254	v->signal = 0xFFFF * zol_getsigstr(zol);
255	return 0;
256}
257
258static int vidioc_s_tuner(struct file *file, void *priv,
259					struct v4l2_tuner *v)
260{
261	return v->index ? -EINVAL : 0;
262}
263
264static int vidioc_s_frequency(struct file *file, void *priv,
265					struct v4l2_frequency *f)
266{
267	struct zoltrix *zol = video_drvdata(file);
268
269	if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
270		return -EINVAL;
271	if (zol_setfreq(zol, f->frequency) != 0)
272		return -EINVAL;
273	return 0;
274}
275
276static int vidioc_g_frequency(struct file *file, void *priv,
277					struct v4l2_frequency *f)
278{
279	struct zoltrix *zol = video_drvdata(file);
280
281	if (f->tuner != 0)
282		return -EINVAL;
283	f->type = V4L2_TUNER_RADIO;
284	f->frequency = zol->curfreq;
285	return 0;
286}
287
288static int vidioc_queryctrl(struct file *file, void *priv,
289					struct v4l2_queryctrl *qc)
290{
291	switch (qc->id) {
292	case V4L2_CID_AUDIO_MUTE:
293		return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
294	case V4L2_CID_AUDIO_VOLUME:
295		return v4l2_ctrl_query_fill(qc, 0, 65535, 4096, 65535);
296	}
297	return -EINVAL;
298}
299
300static int vidioc_g_ctrl(struct file *file, void *priv,
301				struct v4l2_control *ctrl)
302{
303	struct zoltrix *zol = video_drvdata(file);
304
305	switch (ctrl->id) {
306	case V4L2_CID_AUDIO_MUTE:
307		ctrl->value = zol->muted;
308		return 0;
309	case V4L2_CID_AUDIO_VOLUME:
310		ctrl->value = zol->curvol * 4096;
311		return 0;
312	}
313	return -EINVAL;
314}
315
316static int vidioc_s_ctrl(struct file *file, void *priv,
317				struct v4l2_control *ctrl)
318{
319	struct zoltrix *zol = video_drvdata(file);
320
321	switch (ctrl->id) {
322	case V4L2_CID_AUDIO_MUTE:
323		if (ctrl->value)
324			zol_mute(zol);
325		else {
326			zol_unmute(zol);
327			zol_setvol(zol, zol->curvol);
328		}
329		return 0;
330	case V4L2_CID_AUDIO_VOLUME:
331		zol_setvol(zol, ctrl->value / 4096);
332		return 0;
333	}
334	zol->stereo = 1;
335	if (zol_setfreq(zol, zol->curfreq) != 0)
336		return -EINVAL;
337	return -EINVAL;
338}
339
340static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
341{
342	*i = 0;
343	return 0;
344}
345
346static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
347{
348	return i ? -EINVAL : 0;
349}
350
351static int vidioc_g_audio(struct file *file, void *priv,
352					struct v4l2_audio *a)
353{
354	a->index = 0;
355	strlcpy(a->name, "Radio", sizeof(a->name));
356	a->capability = V4L2_AUDCAP_STEREO;
357	return 0;
358}
359
360static int vidioc_s_audio(struct file *file, void *priv,
361					struct v4l2_audio *a)
362{
363	return a->index ? -EINVAL : 0;
364}
365
366static const struct v4l2_file_operations zoltrix_fops =
367{
368	.owner		= THIS_MODULE,
369	.ioctl		= video_ioctl2,
370};
371
372static const struct v4l2_ioctl_ops zoltrix_ioctl_ops = {
373	.vidioc_querycap    = vidioc_querycap,
374	.vidioc_g_tuner     = vidioc_g_tuner,
375	.vidioc_s_tuner     = vidioc_s_tuner,
376	.vidioc_g_audio     = vidioc_g_audio,
377	.vidioc_s_audio     = vidioc_s_audio,
378	.vidioc_g_input     = vidioc_g_input,
379	.vidioc_s_input     = vidioc_s_input,
380	.vidioc_g_frequency = vidioc_g_frequency,
381	.vidioc_s_frequency = vidioc_s_frequency,
382	.vidioc_queryctrl   = vidioc_queryctrl,
383	.vidioc_g_ctrl      = vidioc_g_ctrl,
384	.vidioc_s_ctrl      = vidioc_s_ctrl,
385};
386
387static int __init zoltrix_init(void)
388{
389	struct zoltrix *zol = &zoltrix_card;
390	struct v4l2_device *v4l2_dev = &zol->v4l2_dev;
391	int res;
392
393	strlcpy(v4l2_dev->name, "zoltrix", sizeof(v4l2_dev->name));
394	zol->io = io;
395	if (zol->io == -1) {
396		v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or 0x30c\n");
397		return -EINVAL;
398	}
399	if (zol->io != 0x20c && zol->io != 0x30c) {
400		v4l2_err(v4l2_dev, "invalid port, try 0x20c or 0x30c\n");
401		return -ENXIO;
402	}
403
404	if (!request_region(zol->io, 2, "zoltrix")) {
405		v4l2_err(v4l2_dev, "port 0x%x already in use\n", zol->io);
406		return -EBUSY;
407	}
408
409	res = v4l2_device_register(NULL, v4l2_dev);
410	if (res < 0) {
411		release_region(zol->io, 2);
412		v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
413		return res;
414	}
415
416	strlcpy(zol->vdev.name, v4l2_dev->name, sizeof(zol->vdev.name));
417	zol->vdev.v4l2_dev = v4l2_dev;
418	zol->vdev.fops = &zoltrix_fops;
419	zol->vdev.ioctl_ops = &zoltrix_ioctl_ops;
420	zol->vdev.release = video_device_release_empty;
421	video_set_drvdata(&zol->vdev, zol);
422
423	if (video_register_device(&zol->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
424		v4l2_device_unregister(v4l2_dev);
425		release_region(zol->io, 2);
426		return -EINVAL;
427	}
428	v4l2_info(v4l2_dev, "Zoltrix Radio Plus card driver.\n");
429
430	mutex_init(&zol->lock);
431
432	/* mute card - prevents noisy bootups */
433
434	/* this ensures that the volume is all the way down  */
435
436	outb(0, zol->io);
437	outb(0, zol->io);
438	msleep(20);
439	inb(zol->io + 3);
440
441	zol->curvol = 0;
442	zol->stereo = 1;
443
444	return 0;
445}
446
447static void __exit zoltrix_exit(void)
448{
449	struct zoltrix *zol = &zoltrix_card;
450
451	video_unregister_device(&zol->vdev);
452	v4l2_device_unregister(&zol->v4l2_dev);
453	release_region(zol->io, 2);
454}
455
456module_init(zoltrix_init);
457module_exit(zoltrix_exit);
458