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-terratec.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-terratec.c')
-rw-r--r-- | drivers/media/radio/radio-terratec.c | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/drivers/media/radio/radio-terratec.c b/drivers/media/radio/radio-terratec.c new file mode 100644 index 000000000000..248d67fde037 --- /dev/null +++ b/drivers/media/radio/radio-terratec.c | |||
@@ -0,0 +1,341 @@ | |||
1 | /* Terratec ActiveRadio ISA Standalone card driver for Linux radio support | ||
2 | * (c) 1999 R. Offermanns (rolf@offermanns.de) | ||
3 | * based on the aimslab radio driver from M. Kirkwood | ||
4 | * many thanks to Michael Becker and Friedhelm Birth (from TerraTec) | ||
5 | * | ||
6 | * | ||
7 | * History: | ||
8 | * 1999-05-21 First preview release | ||
9 | * | ||
10 | * Notes on the hardware: | ||
11 | * There are two "main" chips on the card: | ||
12 | * - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf) | ||
13 | * - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf) | ||
14 | * (you can get the datasheet at the above links) | ||
15 | * | ||
16 | * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); | ||
17 | * Volume Control is done digitally | ||
18 | * | ||
19 | * there is a I2C controlled RDS decoder (SAA6588) onboard, which i would like to support someday | ||
20 | * (as soon i have understand how to get started :) | ||
21 | * If you can help me out with that, please contact me!! | ||
22 | * | ||
23 | * | ||
24 | */ | ||
25 | |||
26 | #include <linux/module.h> /* Modules */ | ||
27 | #include <linux/init.h> /* Initdata */ | ||
28 | #include <linux/ioport.h> /* check_region, request_region */ | ||
29 | #include <linux/delay.h> /* udelay */ | ||
30 | #include <asm/io.h> /* outb, outb_p */ | ||
31 | #include <asm/uaccess.h> /* copy to/from user */ | ||
32 | #include <linux/videodev.h> /* kernel radio structs */ | ||
33 | #include <linux/config.h> /* CONFIG_RADIO_TERRATEC_PORT */ | ||
34 | #include <linux/spinlock.h> | ||
35 | |||
36 | #ifndef CONFIG_RADIO_TERRATEC_PORT | ||
37 | #define CONFIG_RADIO_TERRATEC_PORT 0x590 | ||
38 | #endif | ||
39 | |||
40 | /**************** this ones are for the terratec *******************/ | ||
41 | #define BASEPORT 0x590 | ||
42 | #define VOLPORT 0x591 | ||
43 | #define WRT_DIS 0x00 | ||
44 | #define CLK_OFF 0x00 | ||
45 | #define IIC_DATA 0x01 | ||
46 | #define IIC_CLK 0x02 | ||
47 | #define DATA 0x04 | ||
48 | #define CLK_ON 0x08 | ||
49 | #define WRT_EN 0x10 | ||
50 | /*******************************************************************/ | ||
51 | |||
52 | static int io = CONFIG_RADIO_TERRATEC_PORT; | ||
53 | static int radio_nr = -1; | ||
54 | static spinlock_t lock; | ||
55 | |||
56 | struct tt_device | ||
57 | { | ||
58 | int port; | ||
59 | int curvol; | ||
60 | unsigned long curfreq; | ||
61 | int muted; | ||
62 | }; | ||
63 | |||
64 | |||
65 | /* local things */ | ||
66 | |||
67 | static void cardWriteVol(int volume) | ||
68 | { | ||
69 | int i; | ||
70 | volume = volume+(volume * 32); // change both channels | ||
71 | spin_lock(&lock); | ||
72 | for (i=0;i<8;i++) | ||
73 | { | ||
74 | if (volume & (0x80>>i)) | ||
75 | outb(0x80, VOLPORT); | ||
76 | else outb(0x00, VOLPORT); | ||
77 | } | ||
78 | spin_unlock(&lock); | ||
79 | } | ||
80 | |||
81 | |||
82 | |||
83 | static void tt_mute(struct tt_device *dev) | ||
84 | { | ||
85 | dev->muted = 1; | ||
86 | cardWriteVol(0); | ||
87 | } | ||
88 | |||
89 | static int tt_setvol(struct tt_device *dev, int vol) | ||
90 | { | ||
91 | |||
92 | // printk(KERN_ERR "setvol called, vol = %d\n", vol); | ||
93 | |||
94 | if(vol == dev->curvol) { /* requested volume = current */ | ||
95 | if (dev->muted) { /* user is unmuting the card */ | ||
96 | dev->muted = 0; | ||
97 | cardWriteVol(vol); /* enable card */ | ||
98 | } | ||
99 | |||
100 | return 0; | ||
101 | } | ||
102 | |||
103 | if(vol == 0) { /* volume = 0 means mute the card */ | ||
104 | cardWriteVol(0); /* "turn off card" by setting vol to 0 */ | ||
105 | dev->curvol = vol; /* track the volume state! */ | ||
106 | return 0; | ||
107 | } | ||
108 | |||
109 | dev->muted = 0; | ||
110 | |||
111 | cardWriteVol(vol); | ||
112 | |||
113 | dev->curvol = vol; | ||
114 | |||
115 | return 0; | ||
116 | |||
117 | } | ||
118 | |||
119 | |||
120 | /* this is the worst part in this driver */ | ||
121 | /* many more or less strange things are going on here, but hey, it works :) */ | ||
122 | |||
123 | static int tt_setfreq(struct tt_device *dev, unsigned long freq1) | ||
124 | { | ||
125 | int freq; | ||
126 | int i; | ||
127 | int p; | ||
128 | int temp; | ||
129 | long rest; | ||
130 | |||
131 | unsigned char buffer[25]; /* we have to bit shift 25 registers */ | ||
132 | freq = freq1/160; /* convert the freq. to a nice to handle value */ | ||
133 | for(i=24;i>-1;i--) | ||
134 | buffer[i]=0; | ||
135 | |||
136 | rest = freq*10+10700; /* i once had understood what is going on here */ | ||
137 | /* maybe some wise guy (friedhelm?) can comment this stuff */ | ||
138 | i=13; | ||
139 | p=10; | ||
140 | temp=102400; | ||
141 | while (rest!=0) | ||
142 | { | ||
143 | if (rest%temp == rest) | ||
144 | buffer[i] = 0; | ||
145 | else | ||
146 | { | ||
147 | buffer[i] = 1; | ||
148 | rest = rest-temp; | ||
149 | } | ||
150 | i--; | ||
151 | p--; | ||
152 | temp = temp/2; | ||
153 | } | ||
154 | |||
155 | spin_lock(&lock); | ||
156 | |||
157 | for (i=24;i>-1;i--) /* bit shift the values to the radiocard */ | ||
158 | { | ||
159 | if (buffer[i]==1) | ||
160 | { | ||
161 | outb(WRT_EN|DATA, BASEPORT); | ||
162 | outb(WRT_EN|DATA|CLK_ON , BASEPORT); | ||
163 | outb(WRT_EN|DATA, BASEPORT); | ||
164 | } | ||
165 | else | ||
166 | { | ||
167 | outb(WRT_EN|0x00, BASEPORT); | ||
168 | outb(WRT_EN|0x00|CLK_ON , BASEPORT); | ||
169 | } | ||
170 | } | ||
171 | outb(0x00, BASEPORT); | ||
172 | |||
173 | spin_unlock(&lock); | ||
174 | |||
175 | return 0; | ||
176 | } | ||
177 | |||
178 | static int tt_getsigstr(struct tt_device *dev) /* TODO */ | ||
179 | { | ||
180 | if (inb(io) & 2) /* bit set = no signal present */ | ||
181 | return 0; | ||
182 | return 1; /* signal present */ | ||
183 | } | ||
184 | |||
185 | |||
186 | /* implement the video4linux api */ | ||
187 | |||
188 | static int tt_do_ioctl(struct inode *inode, struct file *file, | ||
189 | unsigned int cmd, void *arg) | ||
190 | { | ||
191 | struct video_device *dev = video_devdata(file); | ||
192 | struct tt_device *tt=dev->priv; | ||
193 | |||
194 | switch(cmd) | ||
195 | { | ||
196 | case VIDIOCGCAP: | ||
197 | { | ||
198 | struct video_capability *v = arg; | ||
199 | memset(v,0,sizeof(*v)); | ||
200 | v->type=VID_TYPE_TUNER; | ||
201 | v->channels=1; | ||
202 | v->audios=1; | ||
203 | strcpy(v->name, "ActiveRadio"); | ||
204 | return 0; | ||
205 | } | ||
206 | case VIDIOCGTUNER: | ||
207 | { | ||
208 | struct video_tuner *v = arg; | ||
209 | if(v->tuner) /* Only 1 tuner */ | ||
210 | return -EINVAL; | ||
211 | v->rangelow=(87*16000); | ||
212 | v->rangehigh=(108*16000); | ||
213 | v->flags=VIDEO_TUNER_LOW; | ||
214 | v->mode=VIDEO_MODE_AUTO; | ||
215 | strcpy(v->name, "FM"); | ||
216 | v->signal=0xFFFF*tt_getsigstr(tt); | ||
217 | return 0; | ||
218 | } | ||
219 | case VIDIOCSTUNER: | ||
220 | { | ||
221 | struct video_tuner *v = arg; | ||
222 | if(v->tuner!=0) | ||
223 | return -EINVAL; | ||
224 | /* Only 1 tuner so no setting needed ! */ | ||
225 | return 0; | ||
226 | } | ||
227 | case VIDIOCGFREQ: | ||
228 | { | ||
229 | unsigned long *freq = arg; | ||
230 | *freq = tt->curfreq; | ||
231 | return 0; | ||
232 | } | ||
233 | case VIDIOCSFREQ: | ||
234 | { | ||
235 | unsigned long *freq = arg; | ||
236 | tt->curfreq = *freq; | ||
237 | tt_setfreq(tt, tt->curfreq); | ||
238 | return 0; | ||
239 | } | ||
240 | case VIDIOCGAUDIO: | ||
241 | { | ||
242 | struct video_audio *v = arg; | ||
243 | memset(v,0, sizeof(*v)); | ||
244 | v->flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; | ||
245 | v->volume=tt->curvol * 6554; | ||
246 | v->step=6554; | ||
247 | strcpy(v->name, "Radio"); | ||
248 | return 0; | ||
249 | } | ||
250 | case VIDIOCSAUDIO: | ||
251 | { | ||
252 | struct video_audio *v = arg; | ||
253 | if(v->audio) | ||
254 | return -EINVAL; | ||
255 | if(v->flags&VIDEO_AUDIO_MUTE) | ||
256 | tt_mute(tt); | ||
257 | else | ||
258 | tt_setvol(tt,v->volume/6554); | ||
259 | return 0; | ||
260 | } | ||
261 | default: | ||
262 | return -ENOIOCTLCMD; | ||
263 | } | ||
264 | } | ||
265 | |||
266 | static int tt_ioctl(struct inode *inode, struct file *file, | ||
267 | unsigned int cmd, unsigned long arg) | ||
268 | { | ||
269 | return video_usercopy(inode, file, cmd, arg, tt_do_ioctl); | ||
270 | } | ||
271 | |||
272 | static struct tt_device terratec_unit; | ||
273 | |||
274 | static struct file_operations terratec_fops = { | ||
275 | .owner = THIS_MODULE, | ||
276 | .open = video_exclusive_open, | ||
277 | .release = video_exclusive_release, | ||
278 | .ioctl = tt_ioctl, | ||
279 | .llseek = no_llseek, | ||
280 | }; | ||
281 | |||
282 | static struct video_device terratec_radio= | ||
283 | { | ||
284 | .owner = THIS_MODULE, | ||
285 | .name = "TerraTec ActiveRadio", | ||
286 | .type = VID_TYPE_TUNER, | ||
287 | .hardware = VID_HARDWARE_TERRATEC, | ||
288 | .fops = &terratec_fops, | ||
289 | }; | ||
290 | |||
291 | static int __init terratec_init(void) | ||
292 | { | ||
293 | if(io==-1) | ||
294 | { | ||
295 | printk(KERN_ERR "You must set an I/O address with io=0x???\n"); | ||
296 | return -EINVAL; | ||
297 | } | ||
298 | if (!request_region(io, 2, "terratec")) | ||
299 | { | ||
300 | printk(KERN_ERR "TerraTec: port 0x%x already in use\n", io); | ||
301 | return -EBUSY; | ||
302 | } | ||
303 | |||
304 | terratec_radio.priv=&terratec_unit; | ||
305 | |||
306 | spin_lock_init(&lock); | ||
307 | |||
308 | if(video_register_device(&terratec_radio, VFL_TYPE_RADIO, radio_nr)==-1) | ||
309 | { | ||
310 | release_region(io,2); | ||
311 | return -EINVAL; | ||
312 | } | ||
313 | |||
314 | printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver.\n"); | ||
315 | |||
316 | /* mute card - prevents noisy bootups */ | ||
317 | |||
318 | /* this ensures that the volume is all the way down */ | ||
319 | cardWriteVol(0); | ||
320 | terratec_unit.curvol = 0; | ||
321 | |||
322 | return 0; | ||
323 | } | ||
324 | |||
325 | MODULE_AUTHOR("R.OFFERMANNS & others"); | ||
326 | MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card."); | ||
327 | MODULE_LICENSE("GPL"); | ||
328 | module_param(io, int, 0); | ||
329 | MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)"); | ||
330 | module_param(radio_nr, int, 0); | ||
331 | |||
332 | static void __exit terratec_cleanup_module(void) | ||
333 | { | ||
334 | video_unregister_device(&terratec_radio); | ||
335 | release_region(io,2); | ||
336 | printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver unloaded.\n"); | ||
337 | } | ||
338 | |||
339 | module_init(terratec_init); | ||
340 | module_exit(terratec_cleanup_module); | ||
341 | |||