diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/media/radio/radio-typhoon.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/media/radio/radio-typhoon.c')
-rw-r--r-- | drivers/media/radio/radio-typhoon.c | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/drivers/media/radio/radio-typhoon.c b/drivers/media/radio/radio-typhoon.c new file mode 100644 index 000000000000..d7da901ebe90 --- /dev/null +++ b/drivers/media/radio/radio-typhoon.c | |||
@@ -0,0 +1,383 @@ | |||
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 | |||
32 | #include <linux/module.h> /* Modules */ | ||
33 | #include <linux/init.h> /* Initdata */ | ||
34 | #include <linux/ioport.h> /* check_region, request_region */ | ||
35 | #include <linux/proc_fs.h> /* radio card status report */ | ||
36 | #include <asm/io.h> /* outb, outb_p */ | ||
37 | #include <asm/uaccess.h> /* copy to/from user */ | ||
38 | #include <linux/videodev.h> /* kernel radio structs */ | ||
39 | #include <linux/config.h> /* CONFIG_RADIO_TYPHOON_* */ | ||
40 | |||
41 | #define BANNER "Typhoon Radio Card driver v0.1\n" | ||
42 | |||
43 | #ifndef CONFIG_RADIO_TYPHOON_PORT | ||
44 | #define CONFIG_RADIO_TYPHOON_PORT -1 | ||
45 | #endif | ||
46 | |||
47 | #ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ | ||
48 | #define CONFIG_RADIO_TYPHOON_MUTEFREQ 0 | ||
49 | #endif | ||
50 | |||
51 | #ifndef CONFIG_PROC_FS | ||
52 | #undef CONFIG_RADIO_TYPHOON_PROC_FS | ||
53 | #endif | ||
54 | |||
55 | struct typhoon_device { | ||
56 | int users; | ||
57 | int iobase; | ||
58 | int curvol; | ||
59 | int muted; | ||
60 | unsigned long curfreq; | ||
61 | unsigned long mutefreq; | ||
62 | struct semaphore lock; | ||
63 | }; | ||
64 | |||
65 | static void typhoon_setvol_generic(struct typhoon_device *dev, int vol); | ||
66 | static int typhoon_setfreq_generic(struct typhoon_device *dev, | ||
67 | unsigned long frequency); | ||
68 | static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency); | ||
69 | static void typhoon_mute(struct typhoon_device *dev); | ||
70 | static void typhoon_unmute(struct typhoon_device *dev); | ||
71 | static int typhoon_setvol(struct typhoon_device *dev, int vol); | ||
72 | static int typhoon_ioctl(struct inode *inode, struct file *file, | ||
73 | unsigned int cmd, unsigned long arg); | ||
74 | #ifdef CONFIG_RADIO_TYPHOON_PROC_FS | ||
75 | static int typhoon_get_info(char *buf, char **start, off_t offset, int len); | ||
76 | #endif | ||
77 | |||
78 | static void typhoon_setvol_generic(struct typhoon_device *dev, int vol) | ||
79 | { | ||
80 | down(&dev->lock); | ||
81 | vol >>= 14; /* Map 16 bit to 2 bit */ | ||
82 | vol &= 3; | ||
83 | outb_p(vol / 2, dev->iobase); /* Set the volume, high bit. */ | ||
84 | outb_p(vol % 2, dev->iobase + 2); /* Set the volume, low bit. */ | ||
85 | up(&dev->lock); | ||
86 | } | ||
87 | |||
88 | static int typhoon_setfreq_generic(struct typhoon_device *dev, | ||
89 | unsigned long frequency) | ||
90 | { | ||
91 | unsigned long outval; | ||
92 | unsigned long x; | ||
93 | |||
94 | /* | ||
95 | * The frequency transfer curve is not linear. The best fit I could | ||
96 | * get is | ||
97 | * | ||
98 | * outval = -155 + exp((f + 15.55) * 0.057)) | ||
99 | * | ||
100 | * where frequency f is in MHz. Since we don't have exp in the kernel, | ||
101 | * I approximate this function by a third order polynomial. | ||
102 | * | ||
103 | */ | ||
104 | |||
105 | down(&dev->lock); | ||
106 | x = frequency / 160; | ||
107 | outval = (x * x + 2500) / 5000; | ||
108 | outval = (outval * x + 5000) / 10000; | ||
109 | outval -= (10 * x * x + 10433) / 20866; | ||
110 | outval += 4 * x - 11505; | ||
111 | |||
112 | outb_p((outval >> 8) & 0x01, dev->iobase + 4); | ||
113 | outb_p(outval >> 9, dev->iobase + 6); | ||
114 | outb_p(outval & 0xff, dev->iobase + 8); | ||
115 | up(&dev->lock); | ||
116 | |||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency) | ||
121 | { | ||
122 | typhoon_setfreq_generic(dev, frequency); | ||
123 | dev->curfreq = frequency; | ||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | static void typhoon_mute(struct typhoon_device *dev) | ||
128 | { | ||
129 | if (dev->muted == 1) | ||
130 | return; | ||
131 | typhoon_setvol_generic(dev, 0); | ||
132 | typhoon_setfreq_generic(dev, dev->mutefreq); | ||
133 | dev->muted = 1; | ||
134 | } | ||
135 | |||
136 | static void typhoon_unmute(struct typhoon_device *dev) | ||
137 | { | ||
138 | if (dev->muted == 0) | ||
139 | return; | ||
140 | typhoon_setfreq_generic(dev, dev->curfreq); | ||
141 | typhoon_setvol_generic(dev, dev->curvol); | ||
142 | dev->muted = 0; | ||
143 | } | ||
144 | |||
145 | static int typhoon_setvol(struct typhoon_device *dev, int vol) | ||
146 | { | ||
147 | if (dev->muted && vol != 0) { /* user is unmuting the card */ | ||
148 | dev->curvol = vol; | ||
149 | typhoon_unmute(dev); | ||
150 | return 0; | ||
151 | } | ||
152 | if (vol == dev->curvol) /* requested volume == current */ | ||
153 | return 0; | ||
154 | |||
155 | if (vol == 0) { /* volume == 0 means mute the card */ | ||
156 | typhoon_mute(dev); | ||
157 | dev->curvol = vol; | ||
158 | return 0; | ||
159 | } | ||
160 | typhoon_setvol_generic(dev, vol); | ||
161 | dev->curvol = vol; | ||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | |||
166 | static int typhoon_do_ioctl(struct inode *inode, struct file *file, | ||
167 | unsigned int cmd, void *arg) | ||
168 | { | ||
169 | struct video_device *dev = video_devdata(file); | ||
170 | struct typhoon_device *typhoon = dev->priv; | ||
171 | |||
172 | switch (cmd) { | ||
173 | case VIDIOCGCAP: | ||
174 | { | ||
175 | struct video_capability *v = arg; | ||
176 | memset(v,0,sizeof(*v)); | ||
177 | v->type = VID_TYPE_TUNER; | ||
178 | v->channels = 1; | ||
179 | v->audios = 1; | ||
180 | strcpy(v->name, "Typhoon Radio"); | ||
181 | return 0; | ||
182 | } | ||
183 | case VIDIOCGTUNER: | ||
184 | { | ||
185 | struct video_tuner *v = arg; | ||
186 | if (v->tuner) /* Only 1 tuner */ | ||
187 | return -EINVAL; | ||
188 | v->rangelow = 875 * 1600; | ||
189 | v->rangehigh = 1080 * 1600; | ||
190 | v->flags = VIDEO_TUNER_LOW; | ||
191 | v->mode = VIDEO_MODE_AUTO; | ||
192 | v->signal = 0xFFFF; /* We can't get the signal strength */ | ||
193 | strcpy(v->name, "FM"); | ||
194 | return 0; | ||
195 | } | ||
196 | case VIDIOCSTUNER: | ||
197 | { | ||
198 | struct video_tuner *v = arg; | ||
199 | if (v->tuner != 0) | ||
200 | return -EINVAL; | ||
201 | /* Only 1 tuner so no setting needed ! */ | ||
202 | return 0; | ||
203 | } | ||
204 | case VIDIOCGFREQ: | ||
205 | { | ||
206 | unsigned long *freq = arg; | ||
207 | *freq = typhoon->curfreq; | ||
208 | return 0; | ||
209 | } | ||
210 | case VIDIOCSFREQ: | ||
211 | { | ||
212 | unsigned long *freq = arg; | ||
213 | typhoon->curfreq = *freq; | ||
214 | typhoon_setfreq(typhoon, typhoon->curfreq); | ||
215 | return 0; | ||
216 | } | ||
217 | case VIDIOCGAUDIO: | ||
218 | { | ||
219 | struct video_audio *v = arg; | ||
220 | memset(v, 0, sizeof(*v)); | ||
221 | v->flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME; | ||
222 | v->mode |= VIDEO_SOUND_MONO; | ||
223 | v->volume = typhoon->curvol; | ||
224 | v->step = 1 << 14; | ||
225 | strcpy(v->name, "Typhoon Radio"); | ||
226 | return 0; | ||
227 | } | ||
228 | case VIDIOCSAUDIO: | ||
229 | { | ||
230 | struct video_audio *v = arg; | ||
231 | if (v->audio) | ||
232 | return -EINVAL; | ||
233 | if (v->flags & VIDEO_AUDIO_MUTE) | ||
234 | typhoon_mute(typhoon); | ||
235 | else | ||
236 | typhoon_unmute(typhoon); | ||
237 | if (v->flags & VIDEO_AUDIO_VOLUME) | ||
238 | typhoon_setvol(typhoon, v->volume); | ||
239 | return 0; | ||
240 | } | ||
241 | default: | ||
242 | return -ENOIOCTLCMD; | ||
243 | } | ||
244 | } | ||
245 | |||
246 | static int typhoon_ioctl(struct inode *inode, struct file *file, | ||
247 | unsigned int cmd, unsigned long arg) | ||
248 | { | ||
249 | return video_usercopy(inode, file, cmd, arg, typhoon_do_ioctl); | ||
250 | } | ||
251 | |||
252 | static struct typhoon_device typhoon_unit = | ||
253 | { | ||
254 | .iobase = CONFIG_RADIO_TYPHOON_PORT, | ||
255 | .curfreq = CONFIG_RADIO_TYPHOON_MUTEFREQ, | ||
256 | .mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ, | ||
257 | }; | ||
258 | |||
259 | static struct file_operations typhoon_fops = { | ||
260 | .owner = THIS_MODULE, | ||
261 | .open = video_exclusive_open, | ||
262 | .release = video_exclusive_release, | ||
263 | .ioctl = typhoon_ioctl, | ||
264 | .llseek = no_llseek, | ||
265 | }; | ||
266 | |||
267 | static struct video_device typhoon_radio = | ||
268 | { | ||
269 | .owner = THIS_MODULE, | ||
270 | .name = "Typhoon Radio", | ||
271 | .type = VID_TYPE_TUNER, | ||
272 | .hardware = VID_HARDWARE_TYPHOON, | ||
273 | .fops = &typhoon_fops, | ||
274 | }; | ||
275 | |||
276 | #ifdef CONFIG_RADIO_TYPHOON_PROC_FS | ||
277 | |||
278 | static int typhoon_get_info(char *buf, char **start, off_t offset, int len) | ||
279 | { | ||
280 | char *out = buf; | ||
281 | |||
282 | #ifdef MODULE | ||
283 | #define MODULEPROCSTRING "Driver loaded as a module" | ||
284 | #else | ||
285 | #define MODULEPROCSTRING "Driver compiled into kernel" | ||
286 | #endif | ||
287 | |||
288 | /* output must be kept under PAGE_SIZE */ | ||
289 | out += sprintf(out, BANNER); | ||
290 | out += sprintf(out, "Load type: " MODULEPROCSTRING "\n\n"); | ||
291 | out += sprintf(out, "frequency = %lu kHz\n", | ||
292 | typhoon_unit.curfreq >> 4); | ||
293 | out += sprintf(out, "volume = %d\n", typhoon_unit.curvol); | ||
294 | out += sprintf(out, "mute = %s\n", typhoon_unit.muted ? | ||
295 | "on" : "off"); | ||
296 | out += sprintf(out, "iobase = 0x%x\n", typhoon_unit.iobase); | ||
297 | out += sprintf(out, "mute frequency = %lu kHz\n", | ||
298 | typhoon_unit.mutefreq >> 4); | ||
299 | return out - buf; | ||
300 | } | ||
301 | |||
302 | #endif /* CONFIG_RADIO_TYPHOON_PROC_FS */ | ||
303 | |||
304 | MODULE_AUTHOR("Dr. Henrik Seidel"); | ||
305 | MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio)."); | ||
306 | MODULE_LICENSE("GPL"); | ||
307 | |||
308 | static int io = -1; | ||
309 | static int radio_nr = -1; | ||
310 | |||
311 | module_param(io, int, 0); | ||
312 | MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)"); | ||
313 | module_param(radio_nr, int, 0); | ||
314 | |||
315 | #ifdef MODULE | ||
316 | static unsigned long mutefreq = 0; | ||
317 | module_param(mutefreq, ulong, 0); | ||
318 | MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)"); | ||
319 | #endif | ||
320 | |||
321 | static int __init typhoon_init(void) | ||
322 | { | ||
323 | #ifdef MODULE | ||
324 | if (io == -1) { | ||
325 | printk(KERN_ERR "radio-typhoon: You must set an I/O address with io=0x316 or io=0x336\n"); | ||
326 | return -EINVAL; | ||
327 | } | ||
328 | typhoon_unit.iobase = io; | ||
329 | |||
330 | if (mutefreq < 87000 || mutefreq > 108500) { | ||
331 | printk(KERN_ERR "radio-typhoon: You must set a frequency (in kHz) used when muting the card,\n"); | ||
332 | printk(KERN_ERR "radio-typhoon: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n"); | ||
333 | return -EINVAL; | ||
334 | } | ||
335 | typhoon_unit.mutefreq = mutefreq; | ||
336 | #endif /* MODULE */ | ||
337 | |||
338 | printk(KERN_INFO BANNER); | ||
339 | init_MUTEX(&typhoon_unit.lock); | ||
340 | io = typhoon_unit.iobase; | ||
341 | if (!request_region(io, 8, "typhoon")) { | ||
342 | printk(KERN_ERR "radio-typhoon: port 0x%x already in use\n", | ||
343 | typhoon_unit.iobase); | ||
344 | return -EBUSY; | ||
345 | } | ||
346 | |||
347 | typhoon_radio.priv = &typhoon_unit; | ||
348 | if (video_register_device(&typhoon_radio, VFL_TYPE_RADIO, radio_nr) == -1) | ||
349 | { | ||
350 | release_region(io, 8); | ||
351 | return -EINVAL; | ||
352 | } | ||
353 | printk(KERN_INFO "radio-typhoon: port 0x%x.\n", typhoon_unit.iobase); | ||
354 | printk(KERN_INFO "radio-typhoon: mute frequency is %lu kHz.\n", | ||
355 | typhoon_unit.mutefreq); | ||
356 | typhoon_unit.mutefreq <<= 4; | ||
357 | |||
358 | /* mute card - prevents noisy bootups */ | ||
359 | typhoon_mute(&typhoon_unit); | ||
360 | |||
361 | #ifdef CONFIG_RADIO_TYPHOON_PROC_FS | ||
362 | if (!create_proc_info_entry("driver/radio-typhoon", 0, NULL, | ||
363 | typhoon_get_info)) | ||
364 | printk(KERN_ERR "radio-typhoon: registering /proc/driver/radio-typhoon failed\n"); | ||
365 | #endif | ||
366 | |||
367 | return 0; | ||
368 | } | ||
369 | |||
370 | static void __exit typhoon_cleanup_module(void) | ||
371 | { | ||
372 | |||
373 | #ifdef CONFIG_RADIO_TYPHOON_PROC_FS | ||
374 | remove_proc_entry("driver/radio-typhoon", NULL); | ||
375 | #endif | ||
376 | |||
377 | video_unregister_device(&typhoon_radio); | ||
378 | release_region(io, 8); | ||
379 | } | ||
380 | |||
381 | module_init(typhoon_init); | ||
382 | module_exit(typhoon_cleanup_module); | ||
383 | |||