| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 9 | * | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 | * | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 16 | * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 17 | */ | 
|  | 18 |  | 
|  | 19 | #include <linux/module.h>	/* Modules 			*/ | 
|  | 20 | #include <linux/init.h>		/* Initdata			*/ | 
| Peter Osterlund | fb911ee | 2005-09-13 01:25:15 -0700 | [diff] [blame] | 21 | #include <linux/ioport.h>	/* request_region		*/ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 22 | #include <linux/delay.h>	/* udelay			*/ | 
|  | 23 | #include <asm/io.h>		/* outb, outb_p			*/ | 
|  | 24 | #include <asm/uaccess.h>	/* copy to/from user		*/ | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 25 | #include <linux/videodev2.h>	/* kernel radio structs		*/ | 
| Mauro Carvalho Chehab | 5e87efa | 2006-06-05 10:26:32 -0300 | [diff] [blame] | 26 | #include <media/v4l2-common.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 27 | #include <linux/spinlock.h> | 
|  | 28 |  | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 29 | #include <linux/version.h>      /* for KERNEL_VERSION MACRO     */ | 
|  | 30 | #define RADIO_VERSION KERNEL_VERSION(0,0,2) | 
|  | 31 |  | 
|  | 32 | static struct v4l2_queryctrl radio_qctrl[] = { | 
|  | 33 | { | 
|  | 34 | .id            = V4L2_CID_AUDIO_MUTE, | 
|  | 35 | .name          = "Mute", | 
|  | 36 | .minimum       = 0, | 
|  | 37 | .maximum       = 1, | 
|  | 38 | .default_value = 1, | 
|  | 39 | .type          = V4L2_CTRL_TYPE_BOOLEAN, | 
|  | 40 | },{ | 
|  | 41 | .id            = V4L2_CID_AUDIO_VOLUME, | 
|  | 42 | .name          = "Volume", | 
|  | 43 | .minimum       = 0, | 
|  | 44 | .maximum       = 65535, | 
|  | 45 | .step          = 65535, | 
|  | 46 | .default_value = 0xff, | 
|  | 47 | .type          = V4L2_CTRL_TYPE_INTEGER, | 
|  | 48 | } | 
|  | 49 | }; | 
|  | 50 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 51 | #ifndef CONFIG_RADIO_GEMTEK_PORT | 
|  | 52 | #define CONFIG_RADIO_GEMTEK_PORT -1 | 
|  | 53 | #endif | 
|  | 54 |  | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 55 | static int io = CONFIG_RADIO_GEMTEK_PORT; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 56 | static int radio_nr = -1; | 
|  | 57 | static spinlock_t lock; | 
|  | 58 |  | 
|  | 59 | struct gemtek_device | 
|  | 60 | { | 
|  | 61 | int port; | 
|  | 62 | unsigned long curfreq; | 
|  | 63 | int muted; | 
|  | 64 | }; | 
|  | 65 |  | 
|  | 66 |  | 
|  | 67 | /* local things */ | 
|  | 68 |  | 
|  | 69 | /* the correct way to mute the gemtek may be to write the last written | 
|  | 70 | * frequency || 0x10, but just writing 0x10 once seems to do it as well | 
|  | 71 | */ | 
|  | 72 | static void gemtek_mute(struct gemtek_device *dev) | 
|  | 73 | { | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 74 | if(dev->muted) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 75 | return; | 
|  | 76 | spin_lock(&lock); | 
|  | 77 | outb(0x10, io); | 
|  | 78 | spin_unlock(&lock); | 
|  | 79 | dev->muted = 1; | 
|  | 80 | } | 
|  | 81 |  | 
|  | 82 | static void gemtek_unmute(struct gemtek_device *dev) | 
|  | 83 | { | 
|  | 84 | if(dev->muted == 0) | 
|  | 85 | return; | 
|  | 86 | spin_lock(&lock); | 
|  | 87 | outb(0x20, io); | 
|  | 88 | spin_unlock(&lock); | 
|  | 89 | dev->muted = 0; | 
|  | 90 | } | 
|  | 91 |  | 
|  | 92 | static void zero(void) | 
|  | 93 | { | 
|  | 94 | outb_p(0x04, io); | 
|  | 95 | udelay(5); | 
|  | 96 | outb_p(0x05, io); | 
|  | 97 | udelay(5); | 
|  | 98 | } | 
|  | 99 |  | 
|  | 100 | static void one(void) | 
|  | 101 | { | 
|  | 102 | outb_p(0x06, io); | 
|  | 103 | udelay(5); | 
|  | 104 | outb_p(0x07, io); | 
|  | 105 | udelay(5); | 
|  | 106 | } | 
|  | 107 |  | 
|  | 108 | static int gemtek_setfreq(struct gemtek_device *dev, unsigned long freq) | 
|  | 109 | { | 
|  | 110 | int i; | 
|  | 111 |  | 
|  | 112 | /*        freq = 78.25*((float)freq/16000.0 + 10.52); */ | 
|  | 113 |  | 
|  | 114 | freq /= 16; | 
|  | 115 | freq += 10520; | 
|  | 116 | freq *= 7825; | 
|  | 117 | freq /= 100000; | 
|  | 118 |  | 
|  | 119 | spin_lock(&lock); | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 120 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 121 | /* 2 start bits */ | 
|  | 122 | outb_p(0x03, io); | 
|  | 123 | udelay(5); | 
|  | 124 | outb_p(0x07, io); | 
|  | 125 | udelay(5); | 
|  | 126 |  | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 127 | /* 28 frequency bits (lsb first) */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 128 | for (i = 0; i < 14; i++) | 
|  | 129 | if (freq & (1 << i)) | 
|  | 130 | one(); | 
|  | 131 | else | 
|  | 132 | zero(); | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 133 | /* 36 unknown bits */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 134 | for (i = 0; i < 11; i++) | 
|  | 135 | zero(); | 
|  | 136 | one(); | 
|  | 137 | for (i = 0; i < 4; i++) | 
|  | 138 | zero(); | 
|  | 139 | one(); | 
|  | 140 | zero(); | 
|  | 141 |  | 
|  | 142 | /* 2 end bits */ | 
|  | 143 | outb_p(0x03, io); | 
|  | 144 | udelay(5); | 
|  | 145 | outb_p(0x07, io); | 
|  | 146 | udelay(5); | 
|  | 147 |  | 
|  | 148 | spin_unlock(&lock); | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 149 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 150 | return 0; | 
|  | 151 | } | 
|  | 152 |  | 
|  | 153 | static int gemtek_getsigstr(struct gemtek_device *dev) | 
|  | 154 | { | 
|  | 155 | spin_lock(&lock); | 
|  | 156 | inb(io); | 
|  | 157 | udelay(5); | 
|  | 158 | spin_unlock(&lock); | 
|  | 159 | if (inb(io) & 8)		/* bit set = no signal present */ | 
|  | 160 | return 0; | 
|  | 161 | return 1;		/* signal present */ | 
|  | 162 | } | 
|  | 163 |  | 
|  | 164 | static int gemtek_do_ioctl(struct inode *inode, struct file *file, | 
|  | 165 | unsigned int cmd, void *arg) | 
|  | 166 | { | 
|  | 167 | struct video_device *dev = video_devdata(file); | 
|  | 168 | struct gemtek_device *rt=dev->priv; | 
|  | 169 |  | 
|  | 170 | switch(cmd) | 
|  | 171 | { | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 172 | case VIDIOC_QUERYCAP: | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 173 | { | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 174 | struct v4l2_capability *v = arg; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 175 | memset(v,0,sizeof(*v)); | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 176 | strlcpy(v->driver, "radio-gemtek", sizeof (v->driver)); | 
|  | 177 | strlcpy(v->card, "GemTek", sizeof (v->card)); | 
|  | 178 | sprintf(v->bus_info,"ISA"); | 
|  | 179 | v->version = RADIO_VERSION; | 
|  | 180 | v->capabilities = V4L2_CAP_TUNER; | 
|  | 181 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 182 | return 0; | 
|  | 183 | } | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 184 | case VIDIOC_G_TUNER: | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 185 | { | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 186 | struct v4l2_tuner *v = arg; | 
|  | 187 |  | 
|  | 188 | if (v->index > 0) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 189 | return -EINVAL; | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 190 |  | 
|  | 191 | memset(v,0,sizeof(*v)); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 192 | strcpy(v->name, "FM"); | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 193 | v->type = V4L2_TUNER_RADIO; | 
|  | 194 |  | 
|  | 195 | v->rangelow=(87*16000); | 
|  | 196 | v->rangehigh=(108*16000); | 
|  | 197 | v->rxsubchans =V4L2_TUNER_SUB_MONO; | 
|  | 198 | v->capability=V4L2_TUNER_CAP_LOW; | 
|  | 199 | v->audmode = V4L2_TUNER_MODE_MONO; | 
|  | 200 | v->signal=0xFFFF*gemtek_getsigstr(rt); | 
|  | 201 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 202 | return 0; | 
|  | 203 | } | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 204 | case VIDIOC_S_TUNER: | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 205 | { | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 206 | struct v4l2_tuner *v = arg; | 
|  | 207 |  | 
|  | 208 | if (v->index > 0) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 209 | return -EINVAL; | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 210 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 211 | return 0; | 
|  | 212 | } | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 213 | case VIDIOC_S_FREQUENCY: | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 214 | { | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 215 | struct v4l2_frequency *f = arg; | 
|  | 216 |  | 
|  | 217 | rt->curfreq = f->frequency; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 218 | /* needs to be called twice in order for getsigstr to work */ | 
|  | 219 | gemtek_setfreq(rt, rt->curfreq); | 
|  | 220 | gemtek_setfreq(rt, rt->curfreq); | 
|  | 221 | return 0; | 
|  | 222 | } | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 223 | case VIDIOC_G_FREQUENCY: | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 224 | { | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 225 | struct v4l2_frequency *f = arg; | 
|  | 226 |  | 
|  | 227 | f->type = V4L2_TUNER_RADIO; | 
|  | 228 | f->frequency = rt->curfreq; | 
|  | 229 |  | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 230 | return 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 231 | } | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 232 | case VIDIOC_QUERYCTRL: | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 233 | { | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 234 | struct v4l2_queryctrl *qc = arg; | 
|  | 235 | int i; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 236 |  | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 237 | for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { | 
|  | 238 | if (qc->id && qc->id == radio_qctrl[i].id) { | 
|  | 239 | memcpy(qc, &(radio_qctrl[i]), | 
|  | 240 | sizeof(*qc)); | 
|  | 241 | return (0); | 
|  | 242 | } | 
|  | 243 | } | 
|  | 244 | return -EINVAL; | 
|  | 245 | } | 
|  | 246 | case VIDIOC_G_CTRL: | 
|  | 247 | { | 
|  | 248 | struct v4l2_control *ctrl= arg; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 249 |  | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 250 | switch (ctrl->id) { | 
|  | 251 | case V4L2_CID_AUDIO_MUTE: | 
|  | 252 | ctrl->value=rt->muted; | 
|  | 253 | return (0); | 
|  | 254 | case V4L2_CID_AUDIO_VOLUME: | 
|  | 255 | if (rt->muted) | 
|  | 256 | ctrl->value=0; | 
|  | 257 | else | 
|  | 258 | ctrl->value=65535; | 
|  | 259 | return (0); | 
|  | 260 | } | 
|  | 261 | return -EINVAL; | 
|  | 262 | } | 
|  | 263 | case VIDIOC_S_CTRL: | 
|  | 264 | { | 
|  | 265 | struct v4l2_control *ctrl= arg; | 
|  | 266 |  | 
|  | 267 | switch (ctrl->id) { | 
|  | 268 | case V4L2_CID_AUDIO_MUTE: | 
|  | 269 | if (ctrl->value) { | 
|  | 270 | gemtek_mute(rt); | 
|  | 271 | } else { | 
|  | 272 | gemtek_unmute(rt); | 
|  | 273 | } | 
|  | 274 | return (0); | 
|  | 275 | case V4L2_CID_AUDIO_VOLUME: | 
|  | 276 | if (ctrl->value) { | 
|  | 277 | gemtek_unmute(rt); | 
|  | 278 | } else { | 
|  | 279 | gemtek_mute(rt); | 
|  | 280 | } | 
|  | 281 | return (0); | 
|  | 282 | } | 
|  | 283 | return -EINVAL; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 284 | } | 
|  | 285 | default: | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 286 | return v4l_compat_translate_ioctl(inode,file,cmd,arg, | 
|  | 287 | gemtek_do_ioctl); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 288 | } | 
|  | 289 | } | 
|  | 290 |  | 
|  | 291 | static int gemtek_ioctl(struct inode *inode, struct file *file, | 
|  | 292 | unsigned int cmd, unsigned long arg) | 
|  | 293 | { | 
|  | 294 | return video_usercopy(inode, file, cmd, arg, gemtek_do_ioctl); | 
|  | 295 | } | 
|  | 296 |  | 
|  | 297 | static struct gemtek_device gemtek_unit; | 
|  | 298 |  | 
|  | 299 | static struct file_operations gemtek_fops = { | 
|  | 300 | .owner		= THIS_MODULE, | 
|  | 301 | .open           = video_exclusive_open, | 
|  | 302 | .release        = video_exclusive_release, | 
|  | 303 | .ioctl		= gemtek_ioctl, | 
| Arnd Bergmann | 0d0fbf8 | 2006-01-09 15:24:57 -0200 | [diff] [blame] | 304 | .compat_ioctl	= v4l_compat_ioctl32, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 305 | .llseek         = no_llseek, | 
|  | 306 | }; | 
|  | 307 |  | 
|  | 308 | static struct video_device gemtek_radio= | 
|  | 309 | { | 
|  | 310 | .owner		= THIS_MODULE, | 
|  | 311 | .name		= "GemTek radio", | 
|  | 312 | .type		= VID_TYPE_TUNER, | 
| Mauro Carvalho Chehab | d1c4ecd | 2006-08-08 09:10:01 -0300 | [diff] [blame] | 313 | .hardware	= 0, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 314 | .fops           = &gemtek_fops, | 
|  | 315 | }; | 
|  | 316 |  | 
|  | 317 | static int __init gemtek_init(void) | 
|  | 318 | { | 
|  | 319 | if(io==-1) | 
|  | 320 | { | 
|  | 321 | 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"); | 
|  | 322 | return -EINVAL; | 
|  | 323 | } | 
|  | 324 |  | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 325 | if (!request_region(io, 4, "gemtek")) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 326 | { | 
|  | 327 | printk(KERN_ERR "gemtek: port 0x%x already in use\n", io); | 
|  | 328 | return -EBUSY; | 
|  | 329 | } | 
|  | 330 |  | 
|  | 331 | gemtek_radio.priv=&gemtek_unit; | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 332 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 333 | if(video_register_device(&gemtek_radio, VFL_TYPE_RADIO, radio_nr)==-1) | 
|  | 334 | { | 
|  | 335 | release_region(io, 4); | 
|  | 336 | return -EINVAL; | 
|  | 337 | } | 
|  | 338 | printk(KERN_INFO "GemTek Radio Card driver.\n"); | 
|  | 339 |  | 
|  | 340 | spin_lock_init(&lock); | 
|  | 341 |  | 
|  | 342 | /* this is _maybe_ unnecessary */ | 
|  | 343 | outb(0x01, io); | 
|  | 344 |  | 
| Mauro Carvalho Chehab | 4286c6f | 2006-04-08 16:06:16 -0300 | [diff] [blame] | 345 | /* mute card - prevents noisy bootups */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 346 | gemtek_unit.muted = 0; | 
|  | 347 | gemtek_mute(&gemtek_unit); | 
|  | 348 |  | 
|  | 349 | return 0; | 
|  | 350 | } | 
|  | 351 |  | 
|  | 352 | MODULE_AUTHOR("Jonas Munsin"); | 
|  | 353 | MODULE_DESCRIPTION("A driver for the GemTek Radio Card"); | 
|  | 354 | MODULE_LICENSE("GPL"); | 
|  | 355 |  | 
|  | 356 | module_param(io, int, 0); | 
|  | 357 | 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))."); | 
|  | 358 | module_param(radio_nr, int, 0); | 
|  | 359 |  | 
|  | 360 | static void __exit gemtek_cleanup(void) | 
|  | 361 | { | 
|  | 362 | video_unregister_device(&gemtek_radio); | 
|  | 363 | release_region(io,4); | 
|  | 364 | } | 
|  | 365 |  | 
|  | 366 | module_init(gemtek_init); | 
|  | 367 | module_exit(gemtek_cleanup); | 
|  | 368 |  | 
|  | 369 | /* | 
|  | 370 | Local variables: | 
|  | 371 | 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" | 
|  | 372 | End: | 
|  | 373 | */ |