1/* Typhoon Radio Card driver for radio support
2 * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de>
3 *
4 * Card manufacturer:
5 * http://194.18.155.92/idc/prod2.idc?nr=50753&lang=e
6 *
7 * Notes on the hardware
8 *
9 * This card has two output sockets, one for speakers and one for line.
10 * The speaker output has volume control, but only in four discrete
11 * steps. The line output has neither volume control nor mute.
12 *
13 * The card has auto-stereo according to its manual, although it all
14 * sounds mono to me (even with the Win/DOS drivers). Maybe it's my
15 * antenna - I really don't know for sure.
16 *
17 * Frequency control is done digitally.
18 *
19 * Volume control is done digitally, but there are only four different
20 * possible values. So you should better always turn the volume up and
21 * use line control. I got the best results by connecting line output
22 * to the sound card microphone input. For such a configuration the
23 * volume control has no effect, since volume control only influences
24 * the speaker output.
25 *
26 * There is no explicit mute/unmute. So I set the radio frequency to a
27 * value where I do expect just noise and turn the speaker volume down.
28 * The frequency change is necessary since the card never seems to be
29 * completely silent.
30 *
31 * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
32 */
33
34#include <linux/module.h>	/* Modules                        */
35#include <linux/init.h>		/* Initdata                       */
36#include <linux/ioport.h>	/* request_region		  */
37#include <linux/proc_fs.h>	/* radio card status report	  */
38#include <asm/io.h>		/* outb, outb_p                   */
39#include <asm/uaccess.h>	/* copy to/from user              */
40#include <linux/videodev2.h>	/* kernel radio structs           */
41#include <media/v4l2-common.h>
42
43#include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
44#define RADIO_VERSION KERNEL_VERSION(0,1,1)
45#define BANNER "Typhoon Radio Card driver v0.1.1\n"
46
47static struct v4l2_queryctrl radio_qctrl[] = {
48	{
49		.id            = V4L2_CID_AUDIO_MUTE,
50		.name          = "Mute",
51		.minimum       = 0,
52		.maximum       = 1,
53		.default_value = 1,
54		.type          = V4L2_CTRL_TYPE_BOOLEAN,
55	},{
56		.id            = V4L2_CID_AUDIO_VOLUME,
57		.name          = "Volume",
58		.minimum       = 0,
59		.maximum       = 65535,
60		.step          = 1<<14,
61		.default_value = 0xff,
62		.type          = V4L2_CTRL_TYPE_INTEGER,
63	}
64};
65
66
67#ifndef CONFIG_RADIO_TYPHOON_PORT
68#define CONFIG_RADIO_TYPHOON_PORT -1
69#endif
70
71#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ
72#define CONFIG_RADIO_TYPHOON_MUTEFREQ 0
73#endif
74
75#ifndef CONFIG_PROC_FS
76#undef CONFIG_RADIO_TYPHOON_PROC_FS
77#endif
78
79struct typhoon_device {
80	int users;
81	int iobase;
82	int curvol;
83	int muted;
84	unsigned long curfreq;
85	unsigned long mutefreq;
86	struct mutex lock;
87};
88
89static void typhoon_setvol_generic(struct typhoon_device *dev, int vol);
90static int typhoon_setfreq_generic(struct typhoon_device *dev,
91				   unsigned long frequency);
92static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency);
93static void typhoon_mute(struct typhoon_device *dev);
94static void typhoon_unmute(struct typhoon_device *dev);
95static int typhoon_setvol(struct typhoon_device *dev, int vol);
96#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
97static int typhoon_get_info(char *buf, char **start, off_t offset, int len);
98#endif
99
100static void typhoon_setvol_generic(struct typhoon_device *dev, int vol)
101{
102	mutex_lock(&dev->lock);
103	vol >>= 14;				/* Map 16 bit to 2 bit */
104	vol &= 3;
105	outb_p(vol / 2, dev->iobase);		/* Set the volume, high bit. */
106	outb_p(vol % 2, dev->iobase + 2);	/* Set the volume, low bit. */
107	mutex_unlock(&dev->lock);
108}
109
110static int typhoon_setfreq_generic(struct typhoon_device *dev,
111				   unsigned long frequency)
112{
113	unsigned long outval;
114	unsigned long x;
115
116	/*
117	 * The frequency transfer curve is not linear. The best fit I could
118	 * get is
119	 *
120	 * outval = -155 + exp((f + 15.55) * 0.057))
121	 *
122	 * where frequency f is in MHz. Since we don't have exp in the kernel,
123	 * I approximate this function by a third order polynomial.
124	 *
125	 */
126
127	mutex_lock(&dev->lock);
128	x = frequency / 160;
129	outval = (x * x + 2500) / 5000;
130	outval = (outval * x + 5000) / 10000;
131	outval -= (10 * x * x + 10433) / 20866;
132	outval += 4 * x - 11505;
133
134	outb_p((outval >> 8) & 0x01, dev->iobase + 4);
135	outb_p(outval >> 9, dev->iobase + 6);
136	outb_p(outval & 0xff, dev->iobase + 8);
137	mutex_unlock(&dev->lock);
138
139	return 0;
140}
141
142static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency)
143{
144	typhoon_setfreq_generic(dev, frequency);
145	dev->curfreq = frequency;
146	return 0;
147}
148
149static void typhoon_mute(struct typhoon_device *dev)
150{
151	if (dev->muted == 1)
152		return;
153	typhoon_setvol_generic(dev, 0);
154	typhoon_setfreq_generic(dev, dev->mutefreq);
155	dev->muted = 1;
156}
157
158static void typhoon_unmute(struct typhoon_device *dev)
159{
160	if (dev->muted == 0)
161		return;
162	typhoon_setfreq_generic(dev, dev->curfreq);
163	typhoon_setvol_generic(dev, dev->curvol);
164	dev->muted = 0;
165}
166
167static int typhoon_setvol(struct typhoon_device *dev, int vol)
168{
169	if (dev->muted && vol != 0) {	/* user is unmuting the card */
170		dev->curvol = vol;
171		typhoon_unmute(dev);
172		return 0;
173	}
174	if (vol == dev->curvol)		/* requested volume == current */
175		return 0;
176
177	if (vol == 0) {			/* volume == 0 means mute the card */
178		typhoon_mute(dev);
179		dev->curvol = vol;
180		return 0;
181	}
182	typhoon_setvol_generic(dev, vol);
183	dev->curvol = vol;
184	return 0;
185}
186
187static int vidioc_querycap(struct file *file, void  *priv,
188					struct v4l2_capability *v)
189{
190	strlcpy(v->driver, "radio-typhoon", sizeof(v->driver));
191	strlcpy(v->card, "Typhoon Radio", sizeof(v->card));
192	sprintf(v->bus_info, "ISA");
193	v->version = RADIO_VERSION;
194	v->capabilities = V4L2_CAP_TUNER;
195	return 0;
196}
197
198static int vidioc_g_tuner(struct file *file, void *priv,
199					struct v4l2_tuner *v)
200{
201	if (v->index > 0)
202		return -EINVAL;
203
204	strcpy(v->name, "FM");
205	v->type = V4L2_TUNER_RADIO;
206	v->rangelow = (87.5*16000);
207	v->rangehigh = (108*16000);
208	v->rxsubchans = V4L2_TUNER_SUB_MONO;
209	v->capability = V4L2_TUNER_CAP_LOW;
210	v->audmode = V4L2_TUNER_MODE_MONO;
211	v->signal = 0xFFFF;     /* We can't get the signal strength */
212	return 0;
213}
214
215static int vidioc_s_tuner(struct file *file, void *priv,
216					struct v4l2_tuner *v)
217{
218	if (v->index > 0)
219		return -EINVAL;
220
221	return 0;
222}
223
224static int vidioc_s_frequency(struct file *file, void *priv,
225					struct v4l2_frequency *f)
226{
227	struct video_device *dev = video_devdata(file);
228	struct typhoon_device *typhoon = dev->priv;
229
230	typhoon->curfreq = f->frequency;
231	typhoon_setfreq(typhoon, typhoon->curfreq);
232	return 0;
233}
234
235static int vidioc_g_frequency(struct file *file, void *priv,
236					struct v4l2_frequency *f)
237{
238	struct video_device *dev = video_devdata(file);
239	struct typhoon_device *typhoon = dev->priv;
240
241	f->type = V4L2_TUNER_RADIO;
242	f->frequency = typhoon->curfreq;
243
244	return 0;
245}
246
247static int vidioc_queryctrl(struct file *file, void *priv,
248					struct v4l2_queryctrl *qc)
249{
250	int i;
251
252	for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
253		if (qc->id && qc->id == radio_qctrl[i].id) {
254			memcpy(qc, &(radio_qctrl[i]),
255						sizeof(*qc));
256			return 0;
257		}
258	}
259	return -EINVAL;
260}
261
262static int vidioc_g_ctrl(struct file *file, void *priv,
263					struct v4l2_control *ctrl)
264{
265	struct video_device *dev = video_devdata(file);
266	struct typhoon_device *typhoon = dev->priv;
267
268	switch (ctrl->id) {
269	case V4L2_CID_AUDIO_MUTE:
270		ctrl->value = typhoon->muted;
271		return 0;
272	case V4L2_CID_AUDIO_VOLUME:
273		ctrl->value = typhoon->curvol;
274		return 0;
275	}
276	return -EINVAL;
277}
278
279static int vidioc_s_ctrl (struct file *file, void *priv,
280					struct v4l2_control *ctrl)
281{
282	struct video_device *dev = video_devdata(file);
283	struct typhoon_device *typhoon = dev->priv;
284
285	switch (ctrl->id) {
286	case V4L2_CID_AUDIO_MUTE:
287		if (ctrl->value)
288			typhoon_mute(typhoon);
289		else
290			typhoon_unmute(typhoon);
291		return 0;
292	case V4L2_CID_AUDIO_VOLUME:
293		typhoon_setvol(typhoon, ctrl->value);
294		return 0;
295	}
296	return -EINVAL;
297}
298
299static int vidioc_g_audio(struct file *file, void *priv,
300					struct v4l2_audio *a)
301{
302	if (a->index > 1)
303		return -EINVAL;
304
305	strcpy(a->name, "Radio");
306	a->capability = V4L2_AUDCAP_STEREO;
307	return 0;
308}
309
310static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
311{
312	*i = 0;
313	return 0;
314}
315
316static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
317{
318	if (i != 0)
319		return -EINVAL;
320	return 0;
321}
322
323static int vidioc_s_audio(struct file *file, void *priv,
324					struct v4l2_audio *a)
325{
326	if (a->index != 0)
327		return -EINVAL;
328	return 0;
329}
330
331static struct typhoon_device typhoon_unit =
332{
333	.iobase		= CONFIG_RADIO_TYPHOON_PORT,
334	.curfreq	= CONFIG_RADIO_TYPHOON_MUTEFREQ,
335	.mutefreq	= CONFIG_RADIO_TYPHOON_MUTEFREQ,
336};
337
338static const struct file_operations typhoon_fops = {
339	.owner		= THIS_MODULE,
340	.open           = video_exclusive_open,
341	.release        = video_exclusive_release,
342	.ioctl		= video_ioctl2,
343	.compat_ioctl	= v4l_compat_ioctl32,
344	.llseek         = no_llseek,
345};
346
347static struct video_device typhoon_radio =
348{
349	.owner		= THIS_MODULE,
350	.name		= "Typhoon Radio",
351	.type		= VID_TYPE_TUNER,
352	.hardware	= 0,
353	.fops           = &typhoon_fops,
354	.vidioc_querycap    = vidioc_querycap,
355	.vidioc_g_tuner     = vidioc_g_tuner,
356	.vidioc_s_tuner     = vidioc_s_tuner,
357	.vidioc_g_audio     = vidioc_g_audio,
358	.vidioc_s_audio     = vidioc_s_audio,
359	.vidioc_g_input     = vidioc_g_input,
360	.vidioc_s_input     = vidioc_s_input,
361	.vidioc_g_frequency = vidioc_g_frequency,
362	.vidioc_s_frequency = vidioc_s_frequency,
363	.vidioc_queryctrl   = vidioc_queryctrl,
364	.vidioc_g_ctrl      = vidioc_g_ctrl,
365	.vidioc_s_ctrl      = vidioc_s_ctrl,
366};
367
368#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
369
370static int typhoon_get_info(char *buf, char **start, off_t offset, int len)
371{
372	char *out = buf;
373
374	#ifdef MODULE
375	    #define MODULEPROCSTRING "Driver loaded as a module"
376	#else
377	    #define MODULEPROCSTRING "Driver compiled into kernel"
378	#endif
379
380	/* output must be kept under PAGE_SIZE */
381	out += sprintf(out, BANNER);
382	out += sprintf(out, "Load type: " MODULEPROCSTRING "\n\n");
383	out += sprintf(out, "frequency = %lu kHz\n",
384		typhoon_unit.curfreq >> 4);
385	out += sprintf(out, "volume = %d\n", typhoon_unit.curvol);
386	out += sprintf(out, "mute = %s\n", typhoon_unit.muted ?
387		"on" : "off");
388	out += sprintf(out, "iobase = 0x%x\n", typhoon_unit.iobase);
389	out += sprintf(out, "mute frequency = %lu kHz\n",
390		typhoon_unit.mutefreq >> 4);
391	return out - buf;
392}
393
394#endif /* CONFIG_RADIO_TYPHOON_PROC_FS */
395
396MODULE_AUTHOR("Dr. Henrik Seidel");
397MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");
398MODULE_LICENSE("GPL");
399
400static int io = -1;
401static int radio_nr = -1;
402
403module_param(io, int, 0);
404MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)");
405module_param(radio_nr, int, 0);
406
407#ifdef MODULE
408static unsigned long mutefreq = 0;
409module_param(mutefreq, ulong, 0);
410MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)");
411#endif
412
413static int __init typhoon_init(void)
414{
415#ifdef MODULE
416	if (io == -1) {
417		printk(KERN_ERR "radio-typhoon: You must set an I/O address with io=0x316 or io=0x336\n");
418		return -EINVAL;
419	}
420	typhoon_unit.iobase = io;
421
422	if (mutefreq < 87000 || mutefreq > 108500) {
423		printk(KERN_ERR "radio-typhoon: You must set a frequency (in kHz) used when muting the card,\n");
424		printk(KERN_ERR "radio-typhoon: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n");
425		return -EINVAL;
426	}
427	typhoon_unit.mutefreq = mutefreq;
428#endif /* MODULE */
429
430	printk(KERN_INFO BANNER);
431	mutex_init(&typhoon_unit.lock);
432	io = typhoon_unit.iobase;
433	if (!request_region(io, 8, "typhoon")) {
434		printk(KERN_ERR "radio-typhoon: port 0x%x already in use\n",
435		       typhoon_unit.iobase);
436		return -EBUSY;
437	}
438
439	typhoon_radio.priv = &typhoon_unit;
440	if (video_register_device(&typhoon_radio, VFL_TYPE_RADIO, radio_nr) == -1)
441	{
442		release_region(io, 8);
443		return -EINVAL;
444	}
445	printk(KERN_INFO "radio-typhoon: port 0x%x.\n", typhoon_unit.iobase);
446	printk(KERN_INFO "radio-typhoon: mute frequency is %lu kHz.\n",
447	       typhoon_unit.mutefreq);
448	typhoon_unit.mutefreq <<= 4;
449
450	/* mute card - prevents noisy bootups */
451	typhoon_mute(&typhoon_unit);
452
453#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
454	if (!create_proc_info_entry("driver/radio-typhoon", 0, NULL,
455				    typhoon_get_info))
456		printk(KERN_ERR "radio-typhoon: registering /proc/driver/radio-typhoon failed\n");
457#endif
458
459	return 0;
460}
461
462static void __exit typhoon_cleanup_module(void)
463{
464
465#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
466	remove_proc_entry("driver/radio-typhoon", NULL);
467#endif
468
469	video_unregister_device(&typhoon_radio);
470	release_region(io, 8);
471}
472
473module_init(typhoon_init);
474module_exit(typhoon_cleanup_module);
475