1/* GemTek radio card driver for Linux (C) 1998 Jonas Munsin <jmunsin@iki.fi>
2 *
3 * GemTek hasn't released any specs on the card, so the protocol had to
4 * be reverse engineered with dosemu.
5 *
6 * Besides the protocol changes, this is mostly a copy of:
7 *
8 *    RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff
9 *
10 *    Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood
11 *    Coverted to new API by Alan Cox <Alan.Cox@linux.org>
12 *    Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
13 *
14 * TODO: Allow for more than one of these foolish entities :-)
15 *
16 */
17
18#include <linux/module.h>	/* Modules 			*/
19#include <linux/init.h>		/* Initdata			*/
20#include <linux/ioport.h>	/* check_region, request_region	*/
21#include <linux/delay.h>	/* udelay			*/
22#include <asm/io.h>		/* outb, outb_p			*/
23#include <asm/uaccess.h>	/* copy to/from user		*/
24#include <linux/videodev.h>	/* kernel radio structs		*/
25#include <linux/config.h>	/* CONFIG_RADIO_GEMTEK_PORT 	*/
26#include <linux/spinlock.h>
27
28#ifndef CONFIG_RADIO_GEMTEK_PORT
29#define CONFIG_RADIO_GEMTEK_PORT -1
30#endif
31
32static int io = CONFIG_RADIO_GEMTEK_PORT;
33static int radio_nr = -1;
34static int users = 0;
35static spinlock_t lock;
36
37struct gemtek_device
38{
39	int port;
40	unsigned long curfreq;
41	int muted;
42};
43
44
45/* local things */
46
47/* the correct way to mute the gemtek may be to write the last written
48 * frequency || 0x10, but just writing 0x10 once seems to do it as well
49 */
50static void gemtek_mute(struct gemtek_device *dev)
51{
52        if(dev->muted)
53		return;
54	spin_lock(&lock);
55	outb(0x10, io);
56	spin_unlock(&lock);
57	dev->muted = 1;
58}
59
60static void gemtek_unmute(struct gemtek_device *dev)
61{
62	if(dev->muted == 0)
63		return;
64	spin_lock(&lock);
65	outb(0x20, io);
66	spin_unlock(&lock);
67	dev->muted = 0;
68}
69
70static void zero(void)
71{
72	outb_p(0x04, io);
73	udelay(5);
74	outb_p(0x05, io);
75	udelay(5);
76}
77
78static void one(void)
79{
80	outb_p(0x06, io);
81	udelay(5);
82	outb_p(0x07, io);
83	udelay(5);
84}
85
86static int gemtek_setfreq(struct gemtek_device *dev, unsigned long freq)
87{
88	int i;
89
90/*        freq = 78.25*((float)freq/16000.0 + 10.52); */
91
92	freq /= 16;
93	freq += 10520;
94	freq *= 7825;
95	freq /= 100000;
96
97	spin_lock(&lock);
98
99	/* 2 start bits */
100	outb_p(0x03, io);
101	udelay(5);
102	outb_p(0x07, io);
103	udelay(5);
104
105        /* 28 frequency bits (lsb first) */
106	for (i = 0; i < 14; i++)
107		if (freq & (1 << i))
108			one();
109		else
110			zero();
111        /* 36 unknown bits */
112	for (i = 0; i < 11; i++)
113		zero();
114	one();
115	for (i = 0; i < 4; i++)
116		zero();
117	one();
118	zero();
119
120	/* 2 end bits */
121	outb_p(0x03, io);
122	udelay(5);
123	outb_p(0x07, io);
124	udelay(5);
125
126	spin_unlock(&lock);
127
128	return 0;
129}
130
131int gemtek_getsigstr(struct gemtek_device *dev)
132{
133	spin_lock(&lock);
134	inb(io);
135	udelay(5);
136	spin_unlock(&lock);
137	if (inb(io) & 8)		/* bit set = no signal present */
138		return 0;
139	return 1;		/* signal present */
140}
141
142static int gemtek_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
143{
144	struct gemtek_device *rt=dev->priv;
145
146	switch(cmd)
147	{
148		case VIDIOCGCAP:
149		{
150			struct video_capability v;
151			v.type=VID_TYPE_TUNER;
152			v.channels=1;
153			v.audios=1;
154			/* No we don't do pictures */
155			v.maxwidth=0;
156			v.maxheight=0;
157			v.minwidth=0;
158			v.minheight=0;
159			strcpy(v.name, "GemTek");
160			if(copy_to_user(arg,&v,sizeof(v)))
161				return -EFAULT;
162			return 0;
163		}
164		case VIDIOCGTUNER:
165		{
166			struct video_tuner v;
167			if(copy_from_user(&v, arg,sizeof(v))!=0)
168				return -EFAULT;
169			if(v.tuner)	/* Only 1 tuner */
170				return -EINVAL;
171			v.rangelow=87*16000;
172			v.rangehigh=108*16000;
173			v.flags=VIDEO_TUNER_LOW;
174			v.mode=VIDEO_MODE_AUTO;
175			v.signal=0xFFFF*gemtek_getsigstr(rt);
176			strcpy(v.name, "FM");
177			if(copy_to_user(arg,&v, sizeof(v)))
178				return -EFAULT;
179			return 0;
180		}
181		case VIDIOCSTUNER:
182		{
183			struct video_tuner v;
184			if(copy_from_user(&v, arg, sizeof(v)))
185				return -EFAULT;
186			if(v.tuner!=0)
187				return -EINVAL;
188			/* Only 1 tuner so no setting needed ! */
189			return 0;
190		}
191		case VIDIOCGFREQ:
192			if(copy_to_user(arg, &rt->curfreq, sizeof(rt->curfreq)))
193				return -EFAULT;
194			return 0;
195		case VIDIOCSFREQ:
196			if(copy_from_user(&rt->curfreq, arg,sizeof(rt->curfreq)))
197				return -EFAULT;
198		/* needs to be called twice in order for getsigstr to work */
199			gemtek_setfreq(rt, rt->curfreq);
200			gemtek_setfreq(rt, rt->curfreq);
201			return 0;
202		case VIDIOCGAUDIO:
203		{
204			struct video_audio v;
205			memset(&v,0, sizeof(v));
206			v.flags|=VIDEO_AUDIO_MUTABLE;
207			v.volume=1;
208			v.step=65535;
209			strcpy(v.name, "Radio");
210			if(copy_to_user(arg,&v, sizeof(v)))
211				return -EFAULT;
212			return 0;
213		}
214		case VIDIOCSAUDIO:
215		{
216			struct video_audio v;
217			if(copy_from_user(&v, arg, sizeof(v)))
218				return -EFAULT;
219			if(v.audio)
220				return -EINVAL;
221
222			if(v.flags&VIDEO_AUDIO_MUTE)
223				gemtek_mute(rt);
224			else
225			        gemtek_unmute(rt);
226
227			return 0;
228		}
229		default:
230			return -ENOIOCTLCMD;
231	}
232}
233
234static int gemtek_open(struct video_device *dev, int flags)
235{
236	if(users)
237		return -EBUSY;
238	users++;
239	return 0;
240}
241
242static void gemtek_close(struct video_device *dev)
243{
244	users--;
245}
246
247static struct gemtek_device gemtek_unit;
248
249static struct video_device gemtek_radio=
250{
251	owner:		THIS_MODULE,
252	name:		"GemTek radio",
253	type:		VID_TYPE_TUNER,
254	hardware:	VID_HARDWARE_GEMTEK,
255	open:		gemtek_open,
256	close:		gemtek_close,
257	ioctl:		gemtek_ioctl,
258};
259
260static int __init gemtek_init(void)
261{
262	if(io==-1)
263	{
264		printk(KERN_ERR "You must set an I/O address with io=0x20c, io=0x30c, io=0x24c or io=0x34c (io=0x020c or io=0x248 for the combined sound/radiocard)\n");
265		return -EINVAL;
266	}
267
268	if (!request_region(io, 4, "gemtek"))
269	{
270		printk(KERN_ERR "gemtek: port 0x%x already in use\n", io);
271		return -EBUSY;
272	}
273
274	gemtek_radio.priv=&gemtek_unit;
275
276	if(video_register_device(&gemtek_radio, VFL_TYPE_RADIO, radio_nr)==-1)
277	{
278		release_region(io, 4);
279		return -EINVAL;
280	}
281	printk(KERN_INFO "GemTek Radio Card driver.\n");
282
283	spin_lock_init(&lock);
284
285	/* this is _maybe_ unnecessary */
286	outb(0x01, io);
287
288 	/* mute card - prevents noisy bootups */
289	gemtek_unit.muted = 0;
290	gemtek_mute(&gemtek_unit);
291
292	return 0;
293}
294
295MODULE_AUTHOR("Jonas Munsin");
296MODULE_DESCRIPTION("A driver for the GemTek Radio Card");
297MODULE_LICENSE("GPL");
298
299MODULE_PARM(io, "i");
300MODULE_PARM_DESC(io, "I/O address of the GemTek card (0x20c, 0x30c, 0x24c or 0x34c (0x20c or 0x248 have been reported to work for the combined sound/radiocard)).");
301MODULE_PARM(radio_nr, "i");
302
303EXPORT_NO_SYMBOLS;
304
305static void __exit gemtek_cleanup(void)
306{
307	video_unregister_device(&gemtek_radio);
308	release_region(io,4);
309}
310
311module_init(gemtek_init);
312module_exit(gemtek_cleanup);
313
314/*
315  Local variables:
316  compile-command: "gcc -c -DMODVERSIONS -D__KERNEL__ -DMODULE -O6 -Wall -Wstrict-prototypes -I /home/blp/tmp/linux-2.1.111-rtrack/include radio-rtrack2.c"
317  End:
318*/
319