Commit | Line | Data |
---|---|---|
1da177e4 LT |
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 | |
4286c6f6 | 9 | * |
1da177e4 LT |
10 | * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood |
11 | * Converted 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 */ | |
fb911ee8 | 20 | #include <linux/ioport.h> /* request_region */ |
1da177e4 LT |
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 */ | |
5e87efa3 | 25 | #include <media/v4l2-common.h> |
1da177e4 LT |
26 | #include <linux/config.h> /* CONFIG_RADIO_GEMTEK_PORT */ |
27 | #include <linux/spinlock.h> | |
28 | ||
29 | #ifndef CONFIG_RADIO_GEMTEK_PORT | |
30 | #define CONFIG_RADIO_GEMTEK_PORT -1 | |
31 | #endif | |
32 | ||
4286c6f6 | 33 | static int io = CONFIG_RADIO_GEMTEK_PORT; |
1da177e4 LT |
34 | static int radio_nr = -1; |
35 | static spinlock_t lock; | |
36 | ||
37 | struct 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 | */ | |
50 | static void gemtek_mute(struct gemtek_device *dev) | |
51 | { | |
4286c6f6 | 52 | if(dev->muted) |
1da177e4 LT |
53 | return; |
54 | spin_lock(&lock); | |
55 | outb(0x10, io); | |
56 | spin_unlock(&lock); | |
57 | dev->muted = 1; | |
58 | } | |
59 | ||
60 | static 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 | ||
70 | static void zero(void) | |
71 | { | |
72 | outb_p(0x04, io); | |
73 | udelay(5); | |
74 | outb_p(0x05, io); | |
75 | udelay(5); | |
76 | } | |
77 | ||
78 | static void one(void) | |
79 | { | |
80 | outb_p(0x06, io); | |
81 | udelay(5); | |
82 | outb_p(0x07, io); | |
83 | udelay(5); | |
84 | } | |
85 | ||
86 | static 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); | |
4286c6f6 | 98 | |
1da177e4 LT |
99 | /* 2 start bits */ |
100 | outb_p(0x03, io); | |
101 | udelay(5); | |
102 | outb_p(0x07, io); | |
103 | udelay(5); | |
104 | ||
4286c6f6 | 105 | /* 28 frequency bits (lsb first) */ |
1da177e4 LT |
106 | for (i = 0; i < 14; i++) |
107 | if (freq & (1 << i)) | |
108 | one(); | |
109 | else | |
110 | zero(); | |
4286c6f6 | 111 | /* 36 unknown bits */ |
1da177e4 LT |
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); | |
4286c6f6 | 127 | |
1da177e4 LT |
128 | return 0; |
129 | } | |
130 | ||
131 | static int 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 | ||
142 | static int gemtek_do_ioctl(struct inode *inode, struct file *file, | |
143 | unsigned int cmd, void *arg) | |
144 | { | |
145 | struct video_device *dev = video_devdata(file); | |
146 | struct gemtek_device *rt=dev->priv; | |
147 | ||
148 | switch(cmd) | |
149 | { | |
150 | case VIDIOCGCAP: | |
151 | { | |
152 | struct video_capability *v = arg; | |
153 | memset(v,0,sizeof(*v)); | |
154 | v->type=VID_TYPE_TUNER; | |
155 | v->channels=1; | |
156 | v->audios=1; | |
157 | strcpy(v->name, "GemTek"); | |
158 | return 0; | |
159 | } | |
160 | case VIDIOCGTUNER: | |
161 | { | |
162 | struct video_tuner *v = arg; | |
4286c6f6 | 163 | if(v->tuner) /* Only 1 tuner */ |
1da177e4 LT |
164 | return -EINVAL; |
165 | v->rangelow=87*16000; | |
166 | v->rangehigh=108*16000; | |
167 | v->flags=VIDEO_TUNER_LOW; | |
168 | v->mode=VIDEO_MODE_AUTO; | |
169 | v->signal=0xFFFF*gemtek_getsigstr(rt); | |
170 | strcpy(v->name, "FM"); | |
171 | return 0; | |
172 | } | |
173 | case VIDIOCSTUNER: | |
174 | { | |
175 | struct video_tuner *v = arg; | |
176 | if(v->tuner!=0) | |
177 | return -EINVAL; | |
178 | /* Only 1 tuner so no setting needed ! */ | |
179 | return 0; | |
180 | } | |
181 | case VIDIOCGFREQ: | |
182 | { | |
183 | unsigned long *freq = arg; | |
184 | *freq = rt->curfreq; | |
185 | return 0; | |
186 | } | |
187 | case VIDIOCSFREQ: | |
188 | { | |
189 | unsigned long *freq = arg; | |
190 | rt->curfreq = *freq; | |
191 | /* needs to be called twice in order for getsigstr to work */ | |
192 | gemtek_setfreq(rt, rt->curfreq); | |
193 | gemtek_setfreq(rt, rt->curfreq); | |
194 | return 0; | |
195 | } | |
196 | case VIDIOCGAUDIO: | |
4286c6f6 | 197 | { |
1da177e4 LT |
198 | struct video_audio *v = arg; |
199 | memset(v,0, sizeof(*v)); | |
200 | v->flags|=VIDEO_AUDIO_MUTABLE; | |
201 | v->volume=1; | |
202 | v->step=65535; | |
203 | strcpy(v->name, "Radio"); | |
4286c6f6 | 204 | return 0; |
1da177e4 LT |
205 | } |
206 | case VIDIOCSAUDIO: | |
207 | { | |
208 | struct video_audio *v = arg; | |
4286c6f6 | 209 | if(v->audio) |
1da177e4 LT |
210 | return -EINVAL; |
211 | ||
4286c6f6 | 212 | if(v->flags&VIDEO_AUDIO_MUTE) |
1da177e4 LT |
213 | gemtek_mute(rt); |
214 | else | |
4286c6f6 | 215 | gemtek_unmute(rt); |
1da177e4 LT |
216 | |
217 | return 0; | |
218 | } | |
219 | default: | |
220 | return -ENOIOCTLCMD; | |
221 | } | |
222 | } | |
223 | ||
224 | static int gemtek_ioctl(struct inode *inode, struct file *file, | |
225 | unsigned int cmd, unsigned long arg) | |
226 | { | |
227 | return video_usercopy(inode, file, cmd, arg, gemtek_do_ioctl); | |
228 | } | |
229 | ||
230 | static struct gemtek_device gemtek_unit; | |
231 | ||
232 | static struct file_operations gemtek_fops = { | |
233 | .owner = THIS_MODULE, | |
234 | .open = video_exclusive_open, | |
235 | .release = video_exclusive_release, | |
236 | .ioctl = gemtek_ioctl, | |
0d0fbf81 | 237 | .compat_ioctl = v4l_compat_ioctl32, |
1da177e4 LT |
238 | .llseek = no_llseek, |
239 | }; | |
240 | ||
241 | static struct video_device gemtek_radio= | |
242 | { | |
243 | .owner = THIS_MODULE, | |
244 | .name = "GemTek radio", | |
245 | .type = VID_TYPE_TUNER, | |
246 | .hardware = VID_HARDWARE_GEMTEK, | |
247 | .fops = &gemtek_fops, | |
248 | }; | |
249 | ||
250 | static int __init gemtek_init(void) | |
251 | { | |
252 | if(io==-1) | |
253 | { | |
254 | 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"); | |
255 | return -EINVAL; | |
256 | } | |
257 | ||
4286c6f6 | 258 | if (!request_region(io, 4, "gemtek")) |
1da177e4 LT |
259 | { |
260 | printk(KERN_ERR "gemtek: port 0x%x already in use\n", io); | |
261 | return -EBUSY; | |
262 | } | |
263 | ||
264 | gemtek_radio.priv=&gemtek_unit; | |
4286c6f6 | 265 | |
1da177e4 LT |
266 | if(video_register_device(&gemtek_radio, VFL_TYPE_RADIO, radio_nr)==-1) |
267 | { | |
268 | release_region(io, 4); | |
269 | return -EINVAL; | |
270 | } | |
271 | printk(KERN_INFO "GemTek Radio Card driver.\n"); | |
272 | ||
273 | spin_lock_init(&lock); | |
274 | ||
275 | /* this is _maybe_ unnecessary */ | |
276 | outb(0x01, io); | |
277 | ||
4286c6f6 | 278 | /* mute card - prevents noisy bootups */ |
1da177e4 LT |
279 | gemtek_unit.muted = 0; |
280 | gemtek_mute(&gemtek_unit); | |
281 | ||
282 | return 0; | |
283 | } | |
284 | ||
285 | MODULE_AUTHOR("Jonas Munsin"); | |
286 | MODULE_DESCRIPTION("A driver for the GemTek Radio Card"); | |
287 | MODULE_LICENSE("GPL"); | |
288 | ||
289 | module_param(io, int, 0); | |
290 | MODULE_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))."); | |
291 | module_param(radio_nr, int, 0); | |
292 | ||
293 | static void __exit gemtek_cleanup(void) | |
294 | { | |
295 | video_unregister_device(&gemtek_radio); | |
296 | release_region(io,4); | |
297 | } | |
298 | ||
299 | module_init(gemtek_init); | |
300 | module_exit(gemtek_cleanup); | |
301 | ||
302 | /* | |
303 | Local variables: | |
304 | 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" | |
305 | End: | |
306 | */ |