1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card |
3 | * |
4 | * by Fred Gleason <fredg@wava.com> |
5 | * Version 0.3.3 |
6 | * |
7 | * (Loosely) based on code for the Aztech radio card by |
8 | * |
9 | * Russell Kroll (rkroll@exploits.org) |
10 | * Quay Ly |
11 | * Donald Song |
12 | * Jason Lewis (jlewis@twilight.vtc.vsc.edu) |
13 | * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) |
14 | * William McGrath (wmcgrath@twilight.vtc.vsc.edu) |
15 | * |
16 | * History: |
17 | * 2000-04-29 Russell Kroll <rkroll@exploits.org> |
18 | * Added ISAPnP detection for Linux 2.3/2.4 |
19 | * |
20 | * 2001-01-10 Russell Kroll <rkroll@exploits.org> |
21 | * Removed dead CONFIG_RADIO_CADET_PORT code |
22 | * PnP detection on load is now default (no args necessary) |
23 | * |
24 | * 2002-01-17 Adam Belay <ambx1@neo.rr.com> |
25 | * Updated to latest pnp code |
26 | * |
27 | * 2003-01-31 Alan Cox <alan@lxorguk.ukuu.org.uk> |
28 | * Cleaned up locking, delay code, general odds and ends |
29 | * |
30 | * 2006-07-30 Hans J. Koch <koch@hjk-az.de> |
31 | * Changed API to V4L2 |
32 | */ |
33 | |
34 | #include <linux/module.h> /* Modules */ |
35 | #include <linux/init.h> /* Initdata */ |
36 | #include <linux/ioport.h> /* request_region */ |
37 | #include <linux/delay.h> /* udelay */ |
38 | #include <linux/videodev2.h> /* V4L2 API defs */ |
39 | #include <linux/param.h> |
40 | #include <linux/pnp.h> |
41 | #include <linux/sched.h> |
42 | #include <linux/io.h> /* outb, outb_p */ |
43 | #include <media/v4l2-device.h> |
44 | #include <media/v4l2-ioctl.h> |
45 | #include <media/v4l2-ctrls.h> |
46 | #include <media/v4l2-fh.h> |
47 | #include <media/v4l2-event.h> |
48 | |
49 | MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath" ); |
50 | MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card." ); |
51 | MODULE_LICENSE("GPL" ); |
52 | MODULE_VERSION("0.3.4" ); |
53 | |
54 | static int io = -1; /* default to isapnp activation */ |
55 | static int radio_nr = -1; |
56 | |
57 | module_param(io, int, 0); |
58 | MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)" ); |
59 | module_param(radio_nr, int, 0); |
60 | |
61 | #define RDS_BUFFER 256 |
62 | #define RDS_RX_FLAG 1 |
63 | #define MBS_RX_FLAG 2 |
64 | |
65 | struct cadet { |
66 | struct v4l2_device v4l2_dev; |
67 | struct video_device vdev; |
68 | struct v4l2_ctrl_handler ctrl_handler; |
69 | int io; |
70 | bool is_fm_band; |
71 | u32 curfreq; |
72 | int tunestat; |
73 | int sigstrength; |
74 | wait_queue_head_t read_queue; |
75 | struct timer_list readtimer; |
76 | u8 rdsin, rdsout, rdsstat; |
77 | unsigned char rdsbuf[RDS_BUFFER]; |
78 | struct mutex lock; |
79 | int reading; |
80 | }; |
81 | |
82 | static struct cadet cadet_card; |
83 | |
84 | /* |
85 | * Signal Strength Threshold Values |
86 | * The V4L API spec does not define any particular unit for the signal |
87 | * strength value. These values are in microvolts of RF at the tuner's input. |
88 | */ |
89 | static u16 sigtable[2][4] = { |
90 | { 1835, 2621, 4128, 65535 }, |
91 | { 2185, 4369, 13107, 65535 }, |
92 | }; |
93 | |
94 | static const struct v4l2_frequency_band bands[] = { |
95 | { |
96 | .index = 0, |
97 | .type = V4L2_TUNER_RADIO, |
98 | .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, |
99 | .rangelow = 8320, /* 520 kHz */ |
100 | .rangehigh = 26400, /* 1650 kHz */ |
101 | .modulation = V4L2_BAND_MODULATION_AM, |
102 | }, { |
103 | .index = 1, |
104 | .type = V4L2_TUNER_RADIO, |
105 | .capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | |
106 | V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_LOW | |
107 | V4L2_TUNER_CAP_FREQ_BANDS, |
108 | .rangelow = 1400000, /* 87.5 MHz */ |
109 | .rangehigh = 1728000, /* 108.0 MHz */ |
110 | .modulation = V4L2_BAND_MODULATION_FM, |
111 | }, |
112 | }; |
113 | |
114 | |
115 | static int cadet_getstereo(struct cadet *dev) |
116 | { |
117 | int ret = V4L2_TUNER_SUB_MONO; |
118 | |
119 | if (!dev->is_fm_band) /* Only FM has stereo capability! */ |
120 | return V4L2_TUNER_SUB_MONO; |
121 | |
122 | outb(value: 7, port: dev->io); /* Select tuner control */ |
123 | if ((inb(port: dev->io + 1) & 0x40) == 0) |
124 | ret = V4L2_TUNER_SUB_STEREO; |
125 | return ret; |
126 | } |
127 | |
128 | static unsigned cadet_gettune(struct cadet *dev) |
129 | { |
130 | int curvol, i; |
131 | unsigned fifo = 0; |
132 | |
133 | /* |
134 | * Prepare for read |
135 | */ |
136 | |
137 | outb(value: 7, port: dev->io); /* Select tuner control */ |
138 | curvol = inb(port: dev->io + 1); /* Save current volume/mute setting */ |
139 | outb(value: 0x00, port: dev->io + 1); /* Ensure WRITE-ENABLE is LOW */ |
140 | dev->tunestat = 0xffff; |
141 | |
142 | /* |
143 | * Read the shift register |
144 | */ |
145 | for (i = 0; i < 25; i++) { |
146 | fifo = (fifo << 1) | ((inb(port: dev->io + 1) >> 7) & 0x01); |
147 | if (i < 24) { |
148 | outb(value: 0x01, port: dev->io + 1); |
149 | dev->tunestat &= inb(port: dev->io + 1); |
150 | outb(value: 0x00, port: dev->io + 1); |
151 | } |
152 | } |
153 | |
154 | /* |
155 | * Restore volume/mute setting |
156 | */ |
157 | outb(value: curvol, port: dev->io + 1); |
158 | return fifo; |
159 | } |
160 | |
161 | static unsigned cadet_getfreq(struct cadet *dev) |
162 | { |
163 | int i; |
164 | unsigned freq = 0, test, fifo = 0; |
165 | |
166 | /* |
167 | * Read current tuning |
168 | */ |
169 | fifo = cadet_gettune(dev); |
170 | |
171 | /* |
172 | * Convert to actual frequency |
173 | */ |
174 | if (!dev->is_fm_band) /* AM */ |
175 | return ((fifo & 0x7fff) - 450) * 16; |
176 | |
177 | test = 12500; |
178 | for (i = 0; i < 14; i++) { |
179 | if ((fifo & 0x01) != 0) |
180 | freq += test; |
181 | test = test << 1; |
182 | fifo = fifo >> 1; |
183 | } |
184 | freq -= 10700000; /* IF frequency is 10.7 MHz */ |
185 | freq = (freq * 16) / 1000; /* Make it 1/16 kHz */ |
186 | return freq; |
187 | } |
188 | |
189 | static void cadet_settune(struct cadet *dev, unsigned fifo) |
190 | { |
191 | int i; |
192 | unsigned test; |
193 | |
194 | outb(value: 7, port: dev->io); /* Select tuner control */ |
195 | /* |
196 | * Write the shift register |
197 | */ |
198 | test = 0; |
199 | test = (fifo >> 23) & 0x02; /* Align data for SDO */ |
200 | test |= 0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */ |
201 | outb(value: 7, port: dev->io); /* Select tuner control */ |
202 | outb(value: test, port: dev->io + 1); /* Initialize for write */ |
203 | for (i = 0; i < 25; i++) { |
204 | test |= 0x01; /* Toggle SCK High */ |
205 | outb(value: test, port: dev->io + 1); |
206 | test &= 0xfe; /* Toggle SCK Low */ |
207 | outb(value: test, port: dev->io + 1); |
208 | fifo = fifo << 1; /* Prepare the next bit */ |
209 | test = 0x1c | ((fifo >> 23) & 0x02); |
210 | outb(value: test, port: dev->io + 1); |
211 | } |
212 | } |
213 | |
214 | static void cadet_setfreq(struct cadet *dev, unsigned freq) |
215 | { |
216 | unsigned fifo; |
217 | int i, j, test; |
218 | int curvol; |
219 | |
220 | freq = clamp(freq, bands[dev->is_fm_band].rangelow, |
221 | bands[dev->is_fm_band].rangehigh); |
222 | dev->curfreq = freq; |
223 | /* |
224 | * Formulate a fifo command |
225 | */ |
226 | fifo = 0; |
227 | if (dev->is_fm_band) { /* FM */ |
228 | test = 102400; |
229 | freq = freq / 16; /* Make it kHz */ |
230 | freq += 10700; /* IF is 10700 kHz */ |
231 | for (i = 0; i < 14; i++) { |
232 | fifo = fifo << 1; |
233 | if (freq >= test) { |
234 | fifo |= 0x01; |
235 | freq -= test; |
236 | } |
237 | test = test >> 1; |
238 | } |
239 | } else { /* AM */ |
240 | fifo = (freq / 16) + 450; /* Make it kHz */ |
241 | fifo |= 0x100000; /* Select AM Band */ |
242 | } |
243 | |
244 | /* |
245 | * Save current volume/mute setting |
246 | */ |
247 | |
248 | outb(value: 7, port: dev->io); /* Select tuner control */ |
249 | curvol = inb(port: dev->io + 1); |
250 | |
251 | /* |
252 | * Tune the card |
253 | */ |
254 | for (j = 3; j > -1; j--) { |
255 | cadet_settune(dev, fifo: fifo | (j << 16)); |
256 | |
257 | outb(value: 7, port: dev->io); /* Select tuner control */ |
258 | outb(value: curvol, port: dev->io + 1); |
259 | |
260 | msleep(msecs: 100); |
261 | |
262 | cadet_gettune(dev); |
263 | if ((dev->tunestat & 0x40) == 0) { /* Tuned */ |
264 | dev->sigstrength = sigtable[dev->is_fm_band][j]; |
265 | goto reset_rds; |
266 | } |
267 | } |
268 | dev->sigstrength = 0; |
269 | reset_rds: |
270 | outb(value: 3, port: dev->io); |
271 | outb(inb(port: dev->io + 1) & 0x7f, port: dev->io + 1); |
272 | } |
273 | |
274 | static bool cadet_has_rds_data(struct cadet *dev) |
275 | { |
276 | bool result; |
277 | |
278 | mutex_lock(&dev->lock); |
279 | result = dev->rdsin != dev->rdsout; |
280 | mutex_unlock(lock: &dev->lock); |
281 | return result; |
282 | } |
283 | |
284 | |
285 | static void cadet_handler(struct timer_list *t) |
286 | { |
287 | struct cadet *dev = from_timer(dev, t, readtimer); |
288 | |
289 | /* Service the RDS fifo */ |
290 | if (mutex_trylock(lock: &dev->lock)) { |
291 | outb(value: 0x3, port: dev->io); /* Select RDS Decoder Control */ |
292 | if ((inb(port: dev->io + 1) & 0x20) != 0) |
293 | pr_err("cadet: RDS fifo overflow\n" ); |
294 | outb(value: 0x80, port: dev->io); /* Select RDS fifo */ |
295 | |
296 | while ((inb(port: dev->io) & 0x80) != 0) { |
297 | dev->rdsbuf[dev->rdsin] = inb(port: dev->io + 1); |
298 | if (dev->rdsin + 1 != dev->rdsout) |
299 | dev->rdsin++; |
300 | } |
301 | mutex_unlock(lock: &dev->lock); |
302 | } |
303 | |
304 | /* |
305 | * Service pending read |
306 | */ |
307 | if (cadet_has_rds_data(dev)) |
308 | wake_up_interruptible(&dev->read_queue); |
309 | |
310 | /* |
311 | * Clean up and exit |
312 | */ |
313 | dev->readtimer.expires = jiffies + msecs_to_jiffies(m: 50); |
314 | add_timer(timer: &dev->readtimer); |
315 | } |
316 | |
317 | static void cadet_start_rds(struct cadet *dev) |
318 | { |
319 | dev->rdsstat = 1; |
320 | outb(value: 0x80, port: dev->io); /* Select RDS fifo */ |
321 | timer_setup(&dev->readtimer, cadet_handler, 0); |
322 | dev->readtimer.expires = jiffies + msecs_to_jiffies(m: 50); |
323 | add_timer(timer: &dev->readtimer); |
324 | } |
325 | |
326 | static ssize_t cadet_read(struct file *file, char __user *data, size_t count, loff_t *ppos) |
327 | { |
328 | struct cadet *dev = video_drvdata(file); |
329 | unsigned char readbuf[RDS_BUFFER]; |
330 | int i = 0; |
331 | |
332 | mutex_lock(&dev->lock); |
333 | if (dev->rdsstat == 0) |
334 | cadet_start_rds(dev); |
335 | mutex_unlock(lock: &dev->lock); |
336 | |
337 | if (!cadet_has_rds_data(dev) && (file->f_flags & O_NONBLOCK)) |
338 | return -EWOULDBLOCK; |
339 | i = wait_event_interruptible(dev->read_queue, cadet_has_rds_data(dev)); |
340 | if (i) |
341 | return i; |
342 | |
343 | mutex_lock(&dev->lock); |
344 | while (i < count && dev->rdsin != dev->rdsout) |
345 | readbuf[i++] = dev->rdsbuf[dev->rdsout++]; |
346 | mutex_unlock(lock: &dev->lock); |
347 | |
348 | if (i && copy_to_user(to: data, from: readbuf, n: i)) |
349 | return -EFAULT; |
350 | return i; |
351 | } |
352 | |
353 | |
354 | static int vidioc_querycap(struct file *file, void *priv, |
355 | struct v4l2_capability *v) |
356 | { |
357 | strscpy(v->driver, "ADS Cadet" , sizeof(v->driver)); |
358 | strscpy(v->card, "ADS Cadet" , sizeof(v->card)); |
359 | strscpy(v->bus_info, "ISA:radio-cadet" , sizeof(v->bus_info)); |
360 | return 0; |
361 | } |
362 | |
363 | static int vidioc_g_tuner(struct file *file, void *priv, |
364 | struct v4l2_tuner *v) |
365 | { |
366 | struct cadet *dev = video_drvdata(file); |
367 | |
368 | if (v->index) |
369 | return -EINVAL; |
370 | v->type = V4L2_TUNER_RADIO; |
371 | strscpy(v->name, "Radio" , sizeof(v->name)); |
372 | v->capability = bands[0].capability | bands[1].capability; |
373 | v->rangelow = bands[0].rangelow; /* 520 kHz (start of AM band) */ |
374 | v->rangehigh = bands[1].rangehigh; /* 108.0 MHz (end of FM band) */ |
375 | if (dev->is_fm_band) { |
376 | v->rxsubchans = cadet_getstereo(dev); |
377 | outb(value: 3, port: dev->io); |
378 | outb(inb(port: dev->io + 1) & 0x7f, port: dev->io + 1); |
379 | mdelay(100); |
380 | outb(value: 3, port: dev->io); |
381 | if (inb(port: dev->io + 1) & 0x80) |
382 | v->rxsubchans |= V4L2_TUNER_SUB_RDS; |
383 | } else { |
384 | v->rangelow = 8320; /* 520 kHz */ |
385 | v->rangehigh = 26400; /* 1650 kHz */ |
386 | v->rxsubchans = V4L2_TUNER_SUB_MONO; |
387 | } |
388 | v->audmode = V4L2_TUNER_MODE_STEREO; |
389 | v->signal = dev->sigstrength; /* We might need to modify scaling of this */ |
390 | return 0; |
391 | } |
392 | |
393 | static int vidioc_s_tuner(struct file *file, void *priv, |
394 | const struct v4l2_tuner *v) |
395 | { |
396 | return v->index ? -EINVAL : 0; |
397 | } |
398 | |
399 | static int vidioc_enum_freq_bands(struct file *file, void *priv, |
400 | struct v4l2_frequency_band *band) |
401 | { |
402 | if (band->tuner) |
403 | return -EINVAL; |
404 | if (band->index >= ARRAY_SIZE(bands)) |
405 | return -EINVAL; |
406 | *band = bands[band->index]; |
407 | return 0; |
408 | } |
409 | |
410 | static int vidioc_g_frequency(struct file *file, void *priv, |
411 | struct v4l2_frequency *f) |
412 | { |
413 | struct cadet *dev = video_drvdata(file); |
414 | |
415 | if (f->tuner) |
416 | return -EINVAL; |
417 | f->type = V4L2_TUNER_RADIO; |
418 | f->frequency = dev->curfreq; |
419 | return 0; |
420 | } |
421 | |
422 | |
423 | static int vidioc_s_frequency(struct file *file, void *priv, |
424 | const struct v4l2_frequency *f) |
425 | { |
426 | struct cadet *dev = video_drvdata(file); |
427 | |
428 | if (f->tuner) |
429 | return -EINVAL; |
430 | dev->is_fm_band = |
431 | f->frequency >= (bands[0].rangehigh + bands[1].rangelow) / 2; |
432 | cadet_setfreq(dev, freq: f->frequency); |
433 | return 0; |
434 | } |
435 | |
436 | static int cadet_s_ctrl(struct v4l2_ctrl *ctrl) |
437 | { |
438 | struct cadet *dev = container_of(ctrl->handler, struct cadet, ctrl_handler); |
439 | |
440 | switch (ctrl->id) { |
441 | case V4L2_CID_AUDIO_MUTE: |
442 | outb(value: 7, port: dev->io); /* Select tuner control */ |
443 | if (ctrl->val) |
444 | outb(value: 0x00, port: dev->io + 1); |
445 | else |
446 | outb(value: 0x20, port: dev->io + 1); |
447 | return 0; |
448 | } |
449 | return -EINVAL; |
450 | } |
451 | |
452 | static int cadet_open(struct file *file) |
453 | { |
454 | struct cadet *dev = video_drvdata(file); |
455 | int err; |
456 | |
457 | mutex_lock(&dev->lock); |
458 | err = v4l2_fh_open(filp: file); |
459 | if (err) |
460 | goto fail; |
461 | if (v4l2_fh_is_singular_file(filp: file)) |
462 | init_waitqueue_head(&dev->read_queue); |
463 | fail: |
464 | mutex_unlock(lock: &dev->lock); |
465 | return err; |
466 | } |
467 | |
468 | static int cadet_release(struct file *file) |
469 | { |
470 | struct cadet *dev = video_drvdata(file); |
471 | |
472 | mutex_lock(&dev->lock); |
473 | if (v4l2_fh_is_singular_file(filp: file) && dev->rdsstat) { |
474 | del_timer_sync(timer: &dev->readtimer); |
475 | dev->rdsstat = 0; |
476 | } |
477 | v4l2_fh_release(filp: file); |
478 | mutex_unlock(lock: &dev->lock); |
479 | return 0; |
480 | } |
481 | |
482 | static __poll_t cadet_poll(struct file *file, struct poll_table_struct *wait) |
483 | { |
484 | struct cadet *dev = video_drvdata(file); |
485 | __poll_t req_events = poll_requested_events(p: wait); |
486 | __poll_t res = v4l2_ctrl_poll(file, wait); |
487 | |
488 | poll_wait(filp: file, wait_address: &dev->read_queue, p: wait); |
489 | if (dev->rdsstat == 0 && (req_events & (EPOLLIN | EPOLLRDNORM))) { |
490 | mutex_lock(&dev->lock); |
491 | if (dev->rdsstat == 0) |
492 | cadet_start_rds(dev); |
493 | mutex_unlock(lock: &dev->lock); |
494 | } |
495 | if (cadet_has_rds_data(dev)) |
496 | res |= EPOLLIN | EPOLLRDNORM; |
497 | return res; |
498 | } |
499 | |
500 | |
501 | static const struct v4l2_file_operations cadet_fops = { |
502 | .owner = THIS_MODULE, |
503 | .open = cadet_open, |
504 | .release = cadet_release, |
505 | .read = cadet_read, |
506 | .unlocked_ioctl = video_ioctl2, |
507 | .poll = cadet_poll, |
508 | }; |
509 | |
510 | static const struct v4l2_ioctl_ops cadet_ioctl_ops = { |
511 | .vidioc_querycap = vidioc_querycap, |
512 | .vidioc_g_tuner = vidioc_g_tuner, |
513 | .vidioc_s_tuner = vidioc_s_tuner, |
514 | .vidioc_g_frequency = vidioc_g_frequency, |
515 | .vidioc_s_frequency = vidioc_s_frequency, |
516 | .vidioc_enum_freq_bands = vidioc_enum_freq_bands, |
517 | .vidioc_log_status = v4l2_ctrl_log_status, |
518 | .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, |
519 | .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
520 | }; |
521 | |
522 | static const struct v4l2_ctrl_ops cadet_ctrl_ops = { |
523 | .s_ctrl = cadet_s_ctrl, |
524 | }; |
525 | |
526 | #ifdef CONFIG_PNP |
527 | |
528 | static const struct pnp_device_id cadet_pnp_devices[] = { |
529 | /* ADS Cadet AM/FM Radio Card */ |
530 | {.id = "MSM0c24" , .driver_data = 0}, |
531 | {.id = "" } |
532 | }; |
533 | |
534 | MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices); |
535 | |
536 | static int cadet_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) |
537 | { |
538 | if (!dev) |
539 | return -ENODEV; |
540 | /* only support one device */ |
541 | if (io > 0) |
542 | return -EBUSY; |
543 | |
544 | if (!pnp_port_valid(dev, bar: 0)) |
545 | return -ENODEV; |
546 | |
547 | io = pnp_port_start(dev, bar: 0); |
548 | |
549 | printk(KERN_INFO "radio-cadet: PnP reports device at %#x\n" , io); |
550 | |
551 | return io; |
552 | } |
553 | |
554 | static struct pnp_driver cadet_pnp_driver = { |
555 | .name = "radio-cadet" , |
556 | .id_table = cadet_pnp_devices, |
557 | .probe = cadet_pnp_probe, |
558 | .remove = NULL, |
559 | }; |
560 | |
561 | #else |
562 | static struct pnp_driver cadet_pnp_driver; |
563 | #endif |
564 | |
565 | static void cadet_probe(struct cadet *dev) |
566 | { |
567 | static int iovals[8] = { 0x330, 0x332, 0x334, 0x336, 0x338, 0x33a, 0x33c, 0x33e }; |
568 | int i; |
569 | |
570 | for (i = 0; i < 8; i++) { |
571 | dev->io = iovals[i]; |
572 | if (request_region(dev->io, 2, "cadet-probe" )) { |
573 | cadet_setfreq(dev, freq: bands[1].rangelow); |
574 | if (cadet_getfreq(dev) == bands[1].rangelow) { |
575 | release_region(dev->io, 2); |
576 | return; |
577 | } |
578 | release_region(dev->io, 2); |
579 | } |
580 | } |
581 | dev->io = -1; |
582 | } |
583 | |
584 | /* |
585 | * io should only be set if the user has used something like |
586 | * isapnp (the userspace program) to initialize this card for us |
587 | */ |
588 | |
589 | static int __init cadet_init(void) |
590 | { |
591 | struct cadet *dev = &cadet_card; |
592 | struct v4l2_device *v4l2_dev = &dev->v4l2_dev; |
593 | struct v4l2_ctrl_handler *hdl; |
594 | int res = -ENODEV; |
595 | |
596 | strscpy(v4l2_dev->name, "cadet" , sizeof(v4l2_dev->name)); |
597 | mutex_init(&dev->lock); |
598 | |
599 | /* If a probe was requested then probe ISAPnP first (safest) */ |
600 | if (io < 0) |
601 | pnp_register_driver(drv: &cadet_pnp_driver); |
602 | dev->io = io; |
603 | |
604 | /* If that fails then probe unsafely if probe is requested */ |
605 | if (dev->io < 0) |
606 | cadet_probe(dev); |
607 | |
608 | /* Else we bail out */ |
609 | if (dev->io < 0) { |
610 | #ifdef MODULE |
611 | v4l2_err(v4l2_dev, "you must set an I/O address with io=0x330, 0x332, 0x334,\n" ); |
612 | v4l2_err(v4l2_dev, "0x336, 0x338, 0x33a, 0x33c or 0x33e\n" ); |
613 | #endif |
614 | goto fail; |
615 | } |
616 | if (!request_region(dev->io, 2, "cadet" )) |
617 | goto fail; |
618 | |
619 | res = v4l2_device_register(NULL, v4l2_dev); |
620 | if (res < 0) { |
621 | release_region(dev->io, 2); |
622 | v4l2_err(v4l2_dev, "could not register v4l2_device\n" ); |
623 | goto fail; |
624 | } |
625 | |
626 | hdl = &dev->ctrl_handler; |
627 | v4l2_ctrl_handler_init(hdl, 2); |
628 | v4l2_ctrl_new_std(hdl, ops: &cadet_ctrl_ops, |
629 | V4L2_CID_AUDIO_MUTE, min: 0, max: 1, step: 1, def: 1); |
630 | v4l2_dev->ctrl_handler = hdl; |
631 | if (hdl->error) { |
632 | res = hdl->error; |
633 | v4l2_err(v4l2_dev, "Could not register controls\n" ); |
634 | goto err_hdl; |
635 | } |
636 | |
637 | dev->is_fm_band = true; |
638 | dev->curfreq = bands[dev->is_fm_band].rangelow; |
639 | cadet_setfreq(dev, freq: dev->curfreq); |
640 | strscpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); |
641 | dev->vdev.v4l2_dev = v4l2_dev; |
642 | dev->vdev.fops = &cadet_fops; |
643 | dev->vdev.ioctl_ops = &cadet_ioctl_ops; |
644 | dev->vdev.release = video_device_release_empty; |
645 | dev->vdev.lock = &dev->lock; |
646 | dev->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | |
647 | V4L2_CAP_READWRITE | V4L2_CAP_RDS_CAPTURE; |
648 | video_set_drvdata(vdev: &dev->vdev, data: dev); |
649 | |
650 | res = video_register_device(vdev: &dev->vdev, type: VFL_TYPE_RADIO, nr: radio_nr); |
651 | if (res < 0) |
652 | goto err_hdl; |
653 | v4l2_info(v4l2_dev, "ADS Cadet Radio Card at 0x%x\n" , dev->io); |
654 | return 0; |
655 | err_hdl: |
656 | v4l2_ctrl_handler_free(hdl); |
657 | v4l2_device_unregister(v4l2_dev); |
658 | release_region(dev->io, 2); |
659 | fail: |
660 | pnp_unregister_driver(drv: &cadet_pnp_driver); |
661 | return res; |
662 | } |
663 | |
664 | static void __exit cadet_exit(void) |
665 | { |
666 | struct cadet *dev = &cadet_card; |
667 | |
668 | video_unregister_device(vdev: &dev->vdev); |
669 | v4l2_ctrl_handler_free(hdl: &dev->ctrl_handler); |
670 | v4l2_device_unregister(v4l2_dev: &dev->v4l2_dev); |
671 | outb(value: 7, port: dev->io); /* Mute */ |
672 | outb(value: 0x00, port: dev->io + 1); |
673 | release_region(dev->io, 2); |
674 | pnp_unregister_driver(drv: &cadet_pnp_driver); |
675 | } |
676 | |
677 | module_init(cadet_init); |
678 | module_exit(cadet_exit); |
679 | |
680 | |