File: /usr/src/linux/drivers/media/radio/radio-aimslab.c
1 /* radiotrack (radioreveal) driver for Linux radio support
2 * (c) 1997 M. Kirkwood
3 * Coverted to new API by Alan Cox <Alan.Cox@linux.org>
4 * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
5 *
6 * History:
7 * 1999-02-24 Russell Kroll <rkroll@exploits.org>
8 * Fine tuning/VIDEO_TUNER_LOW
9 * Frequency range expanded to start at 87 MHz
10 *
11 * TODO: Allow for more than one of these foolish entities :-)
12 *
13 * Notes on the hardware (reverse engineered from other peoples'
14 * reverse engineering of AIMS' code :-)
15 *
16 * Frequency control is done digitally -- ie out(port,encodefreq(95.8));
17 *
18 * The signal strength query is unsurprisingly inaccurate. And it seems
19 * to indicate that (on my card, at least) the frequency setting isn't
20 * too great. (I have to tune up .025MHz from what the freq should be
21 * to get a report that the thing is tuned.)
22 *
23 * Volume control is (ugh) analogue:
24 * out(port, start_increasing_volume);
25 * wait(a_wee_while);
26 * out(port, stop_changing_the_volume);
27 *
28 */
29
30 #include <linux/module.h> /* Modules */
31 #include <linux/init.h> /* Initdata */
32 #include <linux/ioport.h> /* check_region, request_region */
33 #include <linux/delay.h> /* udelay */
34 #include <asm/io.h> /* outb, outb_p */
35 #include <asm/uaccess.h> /* copy to/from user */
36 #include <linux/videodev.h> /* kernel radio structs */
37 #include <linux/config.h> /* CONFIG_RADIO_RTRACK_PORT */
38 #include <asm/semaphore.h> /* Lock for the I/O */
39
40 #ifndef CONFIG_RADIO_RTRACK_PORT
41 #define CONFIG_RADIO_RTRACK_PORT -1
42 #endif
43
44 static int io = CONFIG_RADIO_RTRACK_PORT;
45 static int radio_nr = -1;
46 static int users = 0;
47 static struct semaphore lock;
48
49 struct rt_device
50 {
51 int port;
52 int curvol;
53 unsigned long curfreq;
54 int muted;
55 };
56
57
58 /* local things */
59
60 static void sleep_delay(long n)
61 {
62 /* Sleep nicely for 'n' uS */
63 int d=n/(1000000/HZ);
64 if(!d)
65 udelay(n);
66 else
67 {
68 /* Yield CPU time */
69 unsigned long x=jiffies;
70 while((jiffies-x)<=d)
71 schedule();
72 }
73 }
74
75 static void rt_decvol(void)
76 {
77 outb(0x58, io); /* volume down + sigstr + on */
78 sleep_delay(100000);
79 outb(0xd8, io); /* volume steady + sigstr + on */
80 }
81
82 static void rt_incvol(void)
83 {
84 outb(0x98, io); /* volume up + sigstr + on */
85 sleep_delay(100000);
86 outb(0xd8, io); /* volume steady + sigstr + on */
87 }
88
89 static void rt_mute(struct rt_device *dev)
90 {
91 dev->muted = 1;
92 down(&lock);
93 outb(0xd0, io); /* volume steady, off */
94 up(&lock);
95 }
96
97 static int rt_setvol(struct rt_device *dev, int vol)
98 {
99 int i;
100
101 down(&lock);
102
103 if(vol == dev->curvol) { /* requested volume = current */
104 if (dev->muted) { /* user is unmuting the card */
105 dev->muted = 0;
106 outb (0xd8, io); /* enable card */
107 }
108 up(&lock);
109 return 0;
110 }
111
112 if(vol == 0) { /* volume = 0 means mute the card */
113 outb(0x48, io); /* volume down but still "on" */
114 sleep_delay(2000000); /* make sure it's totally down */
115 outb(0xd0, io); /* volume steady, off */
116 dev->curvol = 0; /* track the volume state! */
117 up(&lock);
118 return 0;
119 }
120
121 dev->muted = 0;
122 if(vol > dev->curvol)
123 for(i = dev->curvol; i < vol; i++)
124 rt_incvol();
125 else
126 for(i = dev->curvol; i > vol; i--)
127 rt_decvol();
128
129 dev->curvol = vol;
130 up(&lock);
131 return 0;
132 }
133
134 /* the 128+64 on these outb's is to keep the volume stable while tuning
135 * without them, the volume _will_ creep up with each frequency change
136 * and bit 4 (+16) is to keep the signal strength meter enabled
137 */
138
139 void send_0_byte(int port, struct rt_device *dev)
140 {
141 if ((dev->curvol == 0) || (dev->muted)) {
142 outb_p(128+64+16+ 1, port); /* wr-enable + data low */
143 outb_p(128+64+16+2+1, port); /* clock */
144 }
145 else {
146 outb_p(128+64+16+8+ 1, port); /* on + wr-enable + data low */
147 outb_p(128+64+16+8+2+1, port); /* clock */
148 }
149 sleep_delay(1000);
150 }
151
152 void send_1_byte(int port, struct rt_device *dev)
153 {
154 if ((dev->curvol == 0) || (dev->muted)) {
155 outb_p(128+64+16+4 +1, port); /* wr-enable+data high */
156 outb_p(128+64+16+4+2+1, port); /* clock */
157 }
158 else {
159 outb_p(128+64+16+8+4 +1, port); /* on+wr-enable+data high */
160 outb_p(128+64+16+8+4+2+1, port); /* clock */
161 }
162
163 sleep_delay(1000);
164 }
165
166 static int rt_setfreq(struct rt_device *dev, unsigned long freq)
167 {
168 int i;
169
170 /* adapted from radio-aztech.c */
171
172 /* now uses VIDEO_TUNER_LOW for fine tuning */
173
174 freq += 171200; /* Add 10.7 MHz IF */
175 freq /= 800; /* Convert to 50 kHz units */
176
177 down(&lock); /* Stop other ops interfering */
178
179 send_0_byte (io, dev); /* 0: LSB of frequency */
180
181 for (i = 0; i < 13; i++) /* : frequency bits (1-13) */
182 if (freq & (1 << i))
183 send_1_byte (io, dev);
184 else
185 send_0_byte (io, dev);
186
187 send_0_byte (io, dev); /* 14: test bit - always 0 */
188 send_0_byte (io, dev); /* 15: test bit - always 0 */
189
190 send_0_byte (io, dev); /* 16: band data 0 - always 0 */
191 send_0_byte (io, dev); /* 17: band data 1 - always 0 */
192 send_0_byte (io, dev); /* 18: band data 2 - always 0 */
193 send_0_byte (io, dev); /* 19: time base - always 0 */
194
195 send_0_byte (io, dev); /* 20: spacing (0 = 25 kHz) */
196 send_1_byte (io, dev); /* 21: spacing (1 = 25 kHz) */
197 send_0_byte (io, dev); /* 22: spacing (0 = 25 kHz) */
198 send_1_byte (io, dev); /* 23: AM/FM (FM = 1, always) */
199
200 if ((dev->curvol == 0) || (dev->muted))
201 outb (0xd0, io); /* volume steady + sigstr */
202 else
203 outb (0xd8, io); /* volume steady + sigstr + on */
204
205 up(&lock);
206
207 return 0;
208 }
209
210 static int rt_getsigstr(struct rt_device *dev)
211 {
212 if (inb(io) & 2) /* bit set = no signal present */
213 return 0;
214 return 1; /* signal present */
215 }
216
217 static int rt_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
218 {
219 struct rt_device *rt=dev->priv;
220
221 switch(cmd)
222 {
223 case VIDIOCGCAP:
224 {
225 struct video_capability v;
226 v.type=VID_TYPE_TUNER;
227 v.channels=1;
228 v.audios=1;
229 /* No we don't do pictures */
230 v.maxwidth=0;
231 v.maxheight=0;
232 v.minwidth=0;
233 v.minheight=0;
234 strcpy(v.name, "RadioTrack");
235 if(copy_to_user(arg,&v,sizeof(v)))
236 return -EFAULT;
237 return 0;
238 }
239 case VIDIOCGTUNER:
240 {
241 struct video_tuner v;
242 if(copy_from_user(&v, arg,sizeof(v))!=0)
243 return -EFAULT;
244 if(v.tuner) /* Only 1 tuner */
245 return -EINVAL;
246 v.rangelow=(87*16000);
247 v.rangehigh=(108*16000);
248 v.flags=VIDEO_TUNER_LOW;
249 v.mode=VIDEO_MODE_AUTO;
250 strcpy(v.name, "FM");
251 v.signal=0xFFFF*rt_getsigstr(rt);
252 if(copy_to_user(arg,&v, sizeof(v)))
253 return -EFAULT;
254 return 0;
255 }
256 case VIDIOCSTUNER:
257 {
258 struct video_tuner v;
259 if(copy_from_user(&v, arg, sizeof(v)))
260 return -EFAULT;
261 if(v.tuner!=0)
262 return -EINVAL;
263 /* Only 1 tuner so no setting needed ! */
264 return 0;
265 }
266 case VIDIOCGFREQ:
267 if(copy_to_user(arg, &rt->curfreq, sizeof(rt->curfreq)))
268 return -EFAULT;
269 return 0;
270 case VIDIOCSFREQ:
271 if(copy_from_user(&rt->curfreq, arg,sizeof(rt->curfreq)))
272 return -EFAULT;
273 rt_setfreq(rt, rt->curfreq);
274 return 0;
275 case VIDIOCGAUDIO:
276 {
277 struct video_audio v;
278 memset(&v,0, sizeof(v));
279 v.flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
280 v.volume=rt->curvol * 6554;
281 v.step=6554;
282 strcpy(v.name, "Radio");
283 if(copy_to_user(arg,&v, sizeof(v)))
284 return -EFAULT;
285 return 0;
286 }
287 case VIDIOCSAUDIO:
288 {
289 struct video_audio v;
290 if(copy_from_user(&v, arg, sizeof(v)))
291 return -EFAULT;
292 if(v.audio)
293 return -EINVAL;
294
295 if(v.flags&VIDEO_AUDIO_MUTE)
296 rt_mute(rt);
297 else
298 rt_setvol(rt,v.volume/6554);
299
300 return 0;
301 }
302 default:
303 return -ENOIOCTLCMD;
304 }
305 }
306
307 static int rt_open(struct video_device *dev, int flags)
308 {
309 if(users)
310 return -EBUSY;
311 users++;
312 return 0;
313 }
314
315 static void rt_close(struct video_device *dev)
316 {
317 users--;
318 }
319
320 static struct rt_device rtrack_unit;
321
322 static struct video_device rtrack_radio=
323 {
324 owner: THIS_MODULE,
325 name: "RadioTrack radio",
326 type: VID_TYPE_TUNER,
327 hardware: VID_HARDWARE_RTRACK,
328 open: rt_open,
329 close: rt_close,
330 ioctl: rt_ioctl,
331 };
332
333 static int __init rtrack_init(void)
334 {
335 if(io==-1)
336 {
337 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
338 return -EINVAL;
339 }
340
341 if (!request_region(io, 2, "rtrack"))
342 {
343 printk(KERN_ERR "rtrack: port 0x%x already in use\n", io);
344 return -EBUSY;
345 }
346
347 rtrack_radio.priv=&rtrack_unit;
348
349 if(video_register_device(&rtrack_radio, VFL_TYPE_RADIO, radio_nr)==-1)
350 {
351 release_region(io, 2);
352 return -EINVAL;
353 }
354 printk(KERN_INFO "AIMSlab RadioTrack/RadioReveal card driver.\n");
355
356 /* Set up the I/O locking */
357
358 init_MUTEX(&lock);
359
360 /* mute card - prevents noisy bootups */
361
362 /* this ensures that the volume is all the way down */
363 outb(0x48, io); /* volume down but still "on" */
364 sleep_delay(2000000); /* make sure it's totally down */
365 outb(0xc0, io); /* steady volume, mute card */
366 rtrack_unit.curvol = 0;
367
368 return 0;
369 }
370
371 MODULE_AUTHOR("M.Kirkwood");
372 MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card.");
373 MODULE_PARM(io, "i");
374 MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)");
375 MODULE_PARM(radio_nr, "i");
376
377 EXPORT_NO_SYMBOLS;
378
379 static void __exit cleanup_rtrack_module(void)
380 {
381 video_unregister_device(&rtrack_radio);
382 release_region(io,2);
383 }
384
385 module_init(rtrack_init);
386 module_exit(cleanup_rtrack_module);
387
388