1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * v4l2 driver for TEA5777 Philips AM/FM radio tuner chips 4 * 5 * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> 6 * 7 * Based on the ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips: 8 * 9 * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz> 10 */ 11 12 #include <linux/delay.h> 13 #include <linux/init.h> 14 #include <linux/module.h> 15 #include <linux/sched.h> 16 #include <linux/slab.h> 17 #include <media/v4l2-device.h> 18 #include <media/v4l2-dev.h> 19 #include <media/v4l2-fh.h> 20 #include <media/v4l2-ioctl.h> 21 #include <media/v4l2-event.h> 22 #include "radio-tea5777.h" 23 24 MODULE_AUTHOR("Hans de Goede <perex@perex.cz>"); 25 MODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips"); 26 MODULE_LICENSE("GPL"); 27 28 #define TEA5777_FM_IF 150 /* kHz */ 29 #define TEA5777_FM_FREQ_STEP 50 /* kHz */ 30 31 #define TEA5777_AM_IF 21 /* kHz */ 32 #define TEA5777_AM_FREQ_STEP 1 /* kHz */ 33 34 /* Write reg, common bits */ 35 #define TEA5777_W_MUTE_MASK (1LL << 47) 36 #define TEA5777_W_MUTE_SHIFT 47 37 #define TEA5777_W_AM_FM_MASK (1LL << 46) 38 #define TEA5777_W_AM_FM_SHIFT 46 39 #define TEA5777_W_STB_MASK (1LL << 45) 40 #define TEA5777_W_STB_SHIFT 45 41 42 #define TEA5777_W_IFCE_MASK (1LL << 29) 43 #define TEA5777_W_IFCE_SHIFT 29 44 #define TEA5777_W_IFW_MASK (1LL << 28) 45 #define TEA5777_W_IFW_SHIFT 28 46 #define TEA5777_W_HILO_MASK (1LL << 27) 47 #define TEA5777_W_HILO_SHIFT 27 48 #define TEA5777_W_DBUS_MASK (1LL << 26) 49 #define TEA5777_W_DBUS_SHIFT 26 50 51 #define TEA5777_W_INTEXT_MASK (1LL << 24) 52 #define TEA5777_W_INTEXT_SHIFT 24 53 #define TEA5777_W_P1_MASK (1LL << 23) 54 #define TEA5777_W_P1_SHIFT 23 55 #define TEA5777_W_P0_MASK (1LL << 22) 56 #define TEA5777_W_P0_SHIFT 22 57 #define TEA5777_W_PEN1_MASK (1LL << 21) 58 #define TEA5777_W_PEN1_SHIFT 21 59 #define TEA5777_W_PEN0_MASK (1LL << 20) 60 #define TEA5777_W_PEN0_SHIFT 20 61 62 #define TEA5777_W_CHP0_MASK (1LL << 18) 63 #define TEA5777_W_CHP0_SHIFT 18 64 #define TEA5777_W_DEEM_MASK (1LL << 17) 65 #define TEA5777_W_DEEM_SHIFT 17 66 67 #define TEA5777_W_SEARCH_MASK (1LL << 7) 68 #define TEA5777_W_SEARCH_SHIFT 7 69 #define TEA5777_W_PROGBLIM_MASK (1LL << 6) 70 #define TEA5777_W_PROGBLIM_SHIFT 6 71 #define TEA5777_W_UPDWN_MASK (1LL << 5) 72 #define TEA5777_W_UPDWN_SHIFT 5 73 #define TEA5777_W_SLEV_MASK (3LL << 3) 74 #define TEA5777_W_SLEV_SHIFT 3 75 76 /* Write reg, FM specific bits */ 77 #define TEA5777_W_FM_PLL_MASK (0x1fffLL << 32) 78 #define TEA5777_W_FM_PLL_SHIFT 32 79 #define TEA5777_W_FM_FREF_MASK (0x03LL << 30) 80 #define TEA5777_W_FM_FREF_SHIFT 30 81 #define TEA5777_W_FM_FREF_VALUE 0LL /* 50k steps, 150k IF */ 82 83 #define TEA5777_W_FM_FORCEMONO_MASK (1LL << 15) 84 #define TEA5777_W_FM_FORCEMONO_SHIFT 15 85 #define TEA5777_W_FM_SDSOFF_MASK (1LL << 14) 86 #define TEA5777_W_FM_SDSOFF_SHIFT 14 87 #define TEA5777_W_FM_DOFF_MASK (1LL << 13) 88 #define TEA5777_W_FM_DOFF_SHIFT 13 89 90 #define TEA5777_W_FM_STEP_MASK (3LL << 1) 91 #define TEA5777_W_FM_STEP_SHIFT 1 92 93 /* Write reg, AM specific bits */ 94 #define TEA5777_W_AM_PLL_MASK (0x7ffLL << 34) 95 #define TEA5777_W_AM_PLL_SHIFT 34 96 #define TEA5777_W_AM_AGCRF_MASK (1LL << 33) 97 #define TEA5777_W_AM_AGCRF_SHIFT 33 98 #define TEA5777_W_AM_AGCIF_MASK (1LL << 32) 99 #define TEA5777_W_AM_AGCIF_SHIFT 32 100 #define TEA5777_W_AM_MWLW_MASK (1LL << 31) 101 #define TEA5777_W_AM_MWLW_SHIFT 31 102 #define TEA5777_W_AM_LW 0LL 103 #define TEA5777_W_AM_MW 1LL 104 #define TEA5777_W_AM_LNA_MASK (1LL << 30) 105 #define TEA5777_W_AM_LNA_SHIFT 30 106 107 #define TEA5777_W_AM_PEAK_MASK (1LL << 25) 108 #define TEA5777_W_AM_PEAK_SHIFT 25 109 110 #define TEA5777_W_AM_RFB_MASK (1LL << 16) 111 #define TEA5777_W_AM_RFB_SHIFT 16 112 #define TEA5777_W_AM_CALLIGN_MASK (1LL << 15) 113 #define TEA5777_W_AM_CALLIGN_SHIFT 15 114 #define TEA5777_W_AM_CBANK_MASK (0x7fLL << 8) 115 #define TEA5777_W_AM_CBANK_SHIFT 8 116 117 #define TEA5777_W_AM_DELAY_MASK (1LL << 2) 118 #define TEA5777_W_AM_DELAY_SHIFT 2 119 #define TEA5777_W_AM_STEP_MASK (1LL << 1) 120 #define TEA5777_W_AM_STEP_SHIFT 1 121 122 /* Read reg, common bits */ 123 #define TEA5777_R_LEVEL_MASK (0x0f << 17) 124 #define TEA5777_R_LEVEL_SHIFT 17 125 #define TEA5777_R_SFOUND_MASK (0x01 << 16) 126 #define TEA5777_R_SFOUND_SHIFT 16 127 #define TEA5777_R_BLIM_MASK (0x01 << 15) 128 #define TEA5777_R_BLIM_SHIFT 15 129 130 /* Read reg, FM specific bits */ 131 #define TEA5777_R_FM_STEREO_MASK (0x01 << 21) 132 #define TEA5777_R_FM_STEREO_SHIFT 21 133 #define TEA5777_R_FM_PLL_MASK 0x1fff 134 #define TEA5777_R_FM_PLL_SHIFT 0 135 136 enum { BAND_FM, BAND_AM }; 137 138 static const struct v4l2_frequency_band bands[] = { 139 { 140 .type = V4L2_TUNER_RADIO, 141 .index = 0, 142 .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 143 V4L2_TUNER_CAP_FREQ_BANDS | 144 V4L2_TUNER_CAP_HWSEEK_BOUNDED | 145 V4L2_TUNER_CAP_HWSEEK_PROG_LIM, 146 .rangelow = 76000 * 16, 147 .rangehigh = 108000 * 16, 148 .modulation = V4L2_BAND_MODULATION_FM, 149 }, 150 { 151 .type = V4L2_TUNER_RADIO, 152 .index = 1, 153 .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS | 154 V4L2_TUNER_CAP_HWSEEK_BOUNDED | 155 V4L2_TUNER_CAP_HWSEEK_PROG_LIM, 156 .rangelow = 530 * 16, 157 .rangehigh = 1710 * 16, 158 .modulation = V4L2_BAND_MODULATION_AM, 159 }, 160 }; 161 162 static u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq) 163 { 164 switch (tea->band) { 165 case BAND_FM: 166 return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16; 167 case BAND_AM: 168 return (freq * TEA5777_AM_FREQ_STEP + TEA5777_AM_IF) * 16; 169 } 170 return 0; /* Never reached */ 171 } 172 173 int radio_tea5777_set_freq(struct radio_tea5777 *tea) 174 { 175 u32 freq; 176 int res; 177 178 freq = clamp(tea->freq, bands[tea->band].rangelow, 179 bands[tea->band].rangehigh); 180 freq = (freq + 8) / 16; /* to kHz */ 181 182 switch (tea->band) { 183 case BAND_FM: 184 tea->write_reg &= ~TEA5777_W_AM_FM_MASK; 185 freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP; 186 tea->write_reg &= ~TEA5777_W_FM_PLL_MASK; 187 tea->write_reg |= (u64)freq << TEA5777_W_FM_PLL_SHIFT; 188 tea->write_reg &= ~TEA5777_W_FM_FREF_MASK; 189 tea->write_reg |= TEA5777_W_FM_FREF_VALUE << 190 TEA5777_W_FM_FREF_SHIFT; 191 tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK; 192 if (tea->audmode == V4L2_TUNER_MODE_MONO) 193 tea->write_reg |= 1LL << TEA5777_W_FM_FORCEMONO_SHIFT; 194 break; 195 case BAND_AM: 196 tea->write_reg &= ~TEA5777_W_AM_FM_MASK; 197 tea->write_reg |= (1LL << TEA5777_W_AM_FM_SHIFT); 198 freq = (freq - TEA5777_AM_IF) / TEA5777_AM_FREQ_STEP; 199 tea->write_reg &= ~TEA5777_W_AM_PLL_MASK; 200 tea->write_reg |= (u64)freq << TEA5777_W_AM_PLL_SHIFT; 201 tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK; 202 tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK; 203 tea->write_reg &= ~TEA5777_W_AM_MWLW_MASK; 204 tea->write_reg |= TEA5777_W_AM_MW << TEA5777_W_AM_MWLW_SHIFT; 205 tea->write_reg &= ~TEA5777_W_AM_LNA_MASK; 206 tea->write_reg |= 1LL << TEA5777_W_AM_LNA_SHIFT; 207 tea->write_reg &= ~TEA5777_W_AM_PEAK_MASK; 208 tea->write_reg |= 1LL << TEA5777_W_AM_PEAK_SHIFT; 209 tea->write_reg &= ~TEA5777_W_AM_CALLIGN_MASK; 210 break; 211 } 212 213 res = tea->ops->write_reg(tea, tea->write_reg); 214 if (res) 215 return res; 216 217 tea->needs_write = false; 218 tea->read_reg = -1; 219 tea->freq = tea5777_freq_to_v4l2_freq(tea, freq); 220 221 return 0; 222 } 223 224 static int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait) 225 { 226 int res; 227 228 if (tea->read_reg != -1) 229 return 0; 230 231 if (tea->write_before_read && tea->needs_write) { 232 res = radio_tea5777_set_freq(tea); 233 if (res) 234 return res; 235 } 236 237 if (wait) { 238 if (schedule_timeout_interruptible(msecs_to_jiffies(wait))) 239 return -ERESTARTSYS; 240 } 241 242 res = tea->ops->read_reg(tea, &tea->read_reg); 243 if (res) 244 return res; 245 246 tea->needs_write = true; 247 return 0; 248 } 249 250 /* 251 * Linux Video interface 252 */ 253 254 static int vidioc_querycap(struct file *file, void *priv, 255 struct v4l2_capability *v) 256 { 257 struct radio_tea5777 *tea = video_drvdata(file); 258 259 strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver)); 260 strscpy(v->card, tea->card, sizeof(v->card)); 261 strlcat(v->card, " TEA5777", sizeof(v->card)); 262 strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info)); 263 v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 264 v->device_caps |= V4L2_CAP_HW_FREQ_SEEK; 265 v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; 266 return 0; 267 } 268 269 static int vidioc_enum_freq_bands(struct file *file, void *priv, 270 struct v4l2_frequency_band *band) 271 { 272 struct radio_tea5777 *tea = video_drvdata(file); 273 274 if (band->tuner != 0 || band->index >= ARRAY_SIZE(bands) || 275 (!tea->has_am && band->index == BAND_AM)) 276 return -EINVAL; 277 278 *band = bands[band->index]; 279 return 0; 280 } 281 282 static int vidioc_g_tuner(struct file *file, void *priv, 283 struct v4l2_tuner *v) 284 { 285 struct radio_tea5777 *tea = video_drvdata(file); 286 int res; 287 288 if (v->index > 0) 289 return -EINVAL; 290 291 res = radio_tea5777_update_read_reg(tea, 0); 292 if (res) 293 return res; 294 295 memset(v, 0, sizeof(*v)); 296 if (tea->has_am) 297 strscpy(v->name, "AM/FM", sizeof(v->name)); 298 else 299 strscpy(v->name, "FM", sizeof(v->name)); 300 v->type = V4L2_TUNER_RADIO; 301 v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 302 V4L2_TUNER_CAP_FREQ_BANDS | 303 V4L2_TUNER_CAP_HWSEEK_BOUNDED | 304 V4L2_TUNER_CAP_HWSEEK_PROG_LIM; 305 v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : 306 bands[BAND_FM].rangelow; 307 v->rangehigh = bands[BAND_FM].rangehigh; 308 if (tea->band == BAND_FM && 309 (tea->read_reg & TEA5777_R_FM_STEREO_MASK)) 310 v->rxsubchans = V4L2_TUNER_SUB_STEREO; 311 else 312 v->rxsubchans = V4L2_TUNER_SUB_MONO; 313 v->audmode = tea->audmode; 314 /* shift - 12 to convert 4-bits (0-15) scale to 16-bits (0-65535) */ 315 v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >> 316 (TEA5777_R_LEVEL_SHIFT - 12); 317 318 /* Invalidate read_reg, so that next call we return up2date signal */ 319 tea->read_reg = -1; 320 321 return 0; 322 } 323 324 static int vidioc_s_tuner(struct file *file, void *priv, 325 const struct v4l2_tuner *v) 326 { 327 struct radio_tea5777 *tea = video_drvdata(file); 328 u32 orig_audmode = tea->audmode; 329 330 if (v->index) 331 return -EINVAL; 332 333 tea->audmode = v->audmode; 334 if (tea->audmode > V4L2_TUNER_MODE_STEREO) 335 tea->audmode = V4L2_TUNER_MODE_STEREO; 336 337 if (tea->audmode != orig_audmode && tea->band == BAND_FM) 338 return radio_tea5777_set_freq(tea); 339 340 return 0; 341 } 342 343 static int vidioc_g_frequency(struct file *file, void *priv, 344 struct v4l2_frequency *f) 345 { 346 struct radio_tea5777 *tea = video_drvdata(file); 347 348 if (f->tuner != 0) 349 return -EINVAL; 350 f->type = V4L2_TUNER_RADIO; 351 f->frequency = tea->freq; 352 return 0; 353 } 354 355 static int vidioc_s_frequency(struct file *file, void *priv, 356 const struct v4l2_frequency *f) 357 { 358 struct radio_tea5777 *tea = video_drvdata(file); 359 360 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 361 return -EINVAL; 362 363 if (tea->has_am && f->frequency < (20000 * 16)) 364 tea->band = BAND_AM; 365 else 366 tea->band = BAND_FM; 367 368 tea->freq = f->frequency; 369 return radio_tea5777_set_freq(tea); 370 } 371 372 static int vidioc_s_hw_freq_seek(struct file *file, void *fh, 373 const struct v4l2_hw_freq_seek *a) 374 { 375 struct radio_tea5777 *tea = video_drvdata(file); 376 unsigned long timeout; 377 u32 rangelow = a->rangelow; 378 u32 rangehigh = a->rangehigh; 379 int i, res, spacing; 380 u32 orig_freq; 381 382 if (a->tuner || a->wrap_around) 383 return -EINVAL; 384 385 if (file->f_flags & O_NONBLOCK) 386 return -EWOULDBLOCK; 387 388 if (rangelow || rangehigh) { 389 for (i = 0; i < ARRAY_SIZE(bands); i++) { 390 if (i == BAND_AM && !tea->has_am) 391 continue; 392 if (bands[i].rangelow >= rangelow && 393 bands[i].rangehigh <= rangehigh) 394 break; 395 } 396 if (i == ARRAY_SIZE(bands)) 397 return -EINVAL; /* No matching band found */ 398 399 tea->band = i; 400 if (tea->freq < rangelow || tea->freq > rangehigh) { 401 tea->freq = clamp(tea->freq, rangelow, 402 rangehigh); 403 res = radio_tea5777_set_freq(tea); 404 if (res) 405 return res; 406 } 407 } else { 408 rangelow = bands[tea->band].rangelow; 409 rangehigh = bands[tea->band].rangehigh; 410 } 411 412 spacing = (tea->band == BAND_AM) ? (5 * 16) : (200 * 16); /* kHz */ 413 orig_freq = tea->freq; 414 415 tea->write_reg |= TEA5777_W_PROGBLIM_MASK; 416 if (tea->seek_rangelow != rangelow) { 417 tea->write_reg &= ~TEA5777_W_UPDWN_MASK; 418 tea->freq = rangelow; 419 res = radio_tea5777_set_freq(tea); 420 if (res) 421 goto leave; 422 tea->seek_rangelow = rangelow; 423 } 424 if (tea->seek_rangehigh != rangehigh) { 425 tea->write_reg |= TEA5777_W_UPDWN_MASK; 426 tea->freq = rangehigh; 427 res = radio_tea5777_set_freq(tea); 428 if (res) 429 goto leave; 430 tea->seek_rangehigh = rangehigh; 431 } 432 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; 433 434 tea->write_reg |= TEA5777_W_SEARCH_MASK; 435 if (a->seek_upward) { 436 tea->write_reg |= TEA5777_W_UPDWN_MASK; 437 tea->freq = orig_freq + spacing; 438 } else { 439 tea->write_reg &= ~TEA5777_W_UPDWN_MASK; 440 tea->freq = orig_freq - spacing; 441 } 442 res = radio_tea5777_set_freq(tea); 443 if (res) 444 goto leave; 445 446 timeout = jiffies + msecs_to_jiffies(5000); 447 for (;;) { 448 if (time_after(jiffies, timeout)) { 449 res = -ENODATA; 450 break; 451 } 452 453 res = radio_tea5777_update_read_reg(tea, 100); 454 if (res) 455 break; 456 457 /* 458 * Note we use tea->freq to track how far we've searched sofar 459 * this is necessary to ensure we continue seeking at the right 460 * point, in the write_before_read case. 461 */ 462 tea->freq = (tea->read_reg & TEA5777_R_FM_PLL_MASK); 463 tea->freq = tea5777_freq_to_v4l2_freq(tea, tea->freq); 464 465 if ((tea->read_reg & TEA5777_R_SFOUND_MASK)) { 466 tea->write_reg &= ~TEA5777_W_SEARCH_MASK; 467 return 0; 468 } 469 470 if (tea->read_reg & TEA5777_R_BLIM_MASK) { 471 res = -ENODATA; 472 break; 473 } 474 475 /* Force read_reg update */ 476 tea->read_reg = -1; 477 } 478 leave: 479 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; 480 tea->write_reg &= ~TEA5777_W_SEARCH_MASK; 481 tea->freq = orig_freq; 482 radio_tea5777_set_freq(tea); 483 return res; 484 } 485 486 static int tea575x_s_ctrl(struct v4l2_ctrl *c) 487 { 488 struct radio_tea5777 *tea = 489 container_of(c->handler, struct radio_tea5777, ctrl_handler); 490 491 switch (c->id) { 492 case V4L2_CID_AUDIO_MUTE: 493 if (c->val) 494 tea->write_reg |= TEA5777_W_MUTE_MASK; 495 else 496 tea->write_reg &= ~TEA5777_W_MUTE_MASK; 497 498 return radio_tea5777_set_freq(tea); 499 } 500 501 return -EINVAL; 502 } 503 504 static const struct v4l2_file_operations tea575x_fops = { 505 .unlocked_ioctl = video_ioctl2, 506 .open = v4l2_fh_open, 507 .release = v4l2_fh_release, 508 .poll = v4l2_ctrl_poll, 509 }; 510 511 static const struct v4l2_ioctl_ops tea575x_ioctl_ops = { 512 .vidioc_querycap = vidioc_querycap, 513 .vidioc_g_tuner = vidioc_g_tuner, 514 .vidioc_s_tuner = vidioc_s_tuner, 515 .vidioc_g_frequency = vidioc_g_frequency, 516 .vidioc_s_frequency = vidioc_s_frequency, 517 .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, 518 .vidioc_enum_freq_bands = vidioc_enum_freq_bands, 519 .vidioc_log_status = v4l2_ctrl_log_status, 520 .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 521 .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 522 }; 523 524 static const struct video_device tea575x_radio = { 525 .ioctl_ops = &tea575x_ioctl_ops, 526 .release = video_device_release_empty, 527 }; 528 529 static const struct v4l2_ctrl_ops tea575x_ctrl_ops = { 530 .s_ctrl = tea575x_s_ctrl, 531 }; 532 533 int radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner) 534 { 535 int res; 536 537 tea->write_reg = (1LL << TEA5777_W_IFCE_SHIFT) | 538 (1LL << TEA5777_W_IFW_SHIFT) | 539 (1LL << TEA5777_W_INTEXT_SHIFT) | 540 (1LL << TEA5777_W_CHP0_SHIFT) | 541 (1LL << TEA5777_W_SLEV_SHIFT); 542 tea->freq = 90500 * 16; /* 90.5Mhz default */ 543 tea->audmode = V4L2_TUNER_MODE_STEREO; 544 res = radio_tea5777_set_freq(tea); 545 if (res) { 546 v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res); 547 return res; 548 } 549 550 tea->vd = tea575x_radio; 551 video_set_drvdata(&tea->vd, tea); 552 mutex_init(&tea->mutex); 553 strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name)); 554 tea->vd.lock = &tea->mutex; 555 tea->vd.v4l2_dev = tea->v4l2_dev; 556 tea->fops = tea575x_fops; 557 tea->fops.owner = owner; 558 tea->vd.fops = &tea->fops; 559 560 tea->vd.ctrl_handler = &tea->ctrl_handler; 561 v4l2_ctrl_handler_init(&tea->ctrl_handler, 1); 562 v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops, 563 V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 564 res = tea->ctrl_handler.error; 565 if (res) { 566 v4l2_err(tea->v4l2_dev, "can't initialize controls\n"); 567 v4l2_ctrl_handler_free(&tea->ctrl_handler); 568 return res; 569 } 570 v4l2_ctrl_handler_setup(&tea->ctrl_handler); 571 572 res = video_register_device(&tea->vd, VFL_TYPE_RADIO, -1); 573 if (res) { 574 v4l2_err(tea->v4l2_dev, "can't register video device!\n"); 575 v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 576 return res; 577 } 578 579 return 0; 580 } 581 EXPORT_SYMBOL_GPL(radio_tea5777_init); 582 583 void radio_tea5777_exit(struct radio_tea5777 *tea) 584 { 585 video_unregister_device(&tea->vd); 586 v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 587 } 588 EXPORT_SYMBOL_GPL(radio_tea5777_exit); 589