radio-tea5777.c (16182B)
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 24MODULE_AUTHOR("Hans de Goede <perex@perex.cz>"); 25MODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips"); 26MODULE_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 136enum { BAND_FM, BAND_AM }; 137 138static 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 162static 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 173int 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 224static 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 254static 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 return 0; 264} 265 266static int vidioc_enum_freq_bands(struct file *file, void *priv, 267 struct v4l2_frequency_band *band) 268{ 269 struct radio_tea5777 *tea = video_drvdata(file); 270 271 if (band->tuner != 0 || band->index >= ARRAY_SIZE(bands) || 272 (!tea->has_am && band->index == BAND_AM)) 273 return -EINVAL; 274 275 *band = bands[band->index]; 276 return 0; 277} 278 279static int vidioc_g_tuner(struct file *file, void *priv, 280 struct v4l2_tuner *v) 281{ 282 struct radio_tea5777 *tea = video_drvdata(file); 283 int res; 284 285 if (v->index > 0) 286 return -EINVAL; 287 288 res = radio_tea5777_update_read_reg(tea, 0); 289 if (res) 290 return res; 291 292 memset(v, 0, sizeof(*v)); 293 if (tea->has_am) 294 strscpy(v->name, "AM/FM", sizeof(v->name)); 295 else 296 strscpy(v->name, "FM", sizeof(v->name)); 297 v->type = V4L2_TUNER_RADIO; 298 v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 299 V4L2_TUNER_CAP_FREQ_BANDS | 300 V4L2_TUNER_CAP_HWSEEK_BOUNDED | 301 V4L2_TUNER_CAP_HWSEEK_PROG_LIM; 302 v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : 303 bands[BAND_FM].rangelow; 304 v->rangehigh = bands[BAND_FM].rangehigh; 305 if (tea->band == BAND_FM && 306 (tea->read_reg & TEA5777_R_FM_STEREO_MASK)) 307 v->rxsubchans = V4L2_TUNER_SUB_STEREO; 308 else 309 v->rxsubchans = V4L2_TUNER_SUB_MONO; 310 v->audmode = tea->audmode; 311 /* shift - 12 to convert 4-bits (0-15) scale to 16-bits (0-65535) */ 312 v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >> 313 (TEA5777_R_LEVEL_SHIFT - 12); 314 315 /* Invalidate read_reg, so that next call we return up2date signal */ 316 tea->read_reg = -1; 317 318 return 0; 319} 320 321static int vidioc_s_tuner(struct file *file, void *priv, 322 const struct v4l2_tuner *v) 323{ 324 struct radio_tea5777 *tea = video_drvdata(file); 325 u32 orig_audmode = tea->audmode; 326 327 if (v->index) 328 return -EINVAL; 329 330 tea->audmode = v->audmode; 331 if (tea->audmode > V4L2_TUNER_MODE_STEREO) 332 tea->audmode = V4L2_TUNER_MODE_STEREO; 333 334 if (tea->audmode != orig_audmode && tea->band == BAND_FM) 335 return radio_tea5777_set_freq(tea); 336 337 return 0; 338} 339 340static int vidioc_g_frequency(struct file *file, void *priv, 341 struct v4l2_frequency *f) 342{ 343 struct radio_tea5777 *tea = video_drvdata(file); 344 345 if (f->tuner != 0) 346 return -EINVAL; 347 f->type = V4L2_TUNER_RADIO; 348 f->frequency = tea->freq; 349 return 0; 350} 351 352static int vidioc_s_frequency(struct file *file, void *priv, 353 const struct v4l2_frequency *f) 354{ 355 struct radio_tea5777 *tea = video_drvdata(file); 356 357 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 358 return -EINVAL; 359 360 if (tea->has_am && f->frequency < (20000 * 16)) 361 tea->band = BAND_AM; 362 else 363 tea->band = BAND_FM; 364 365 tea->freq = f->frequency; 366 return radio_tea5777_set_freq(tea); 367} 368 369static int vidioc_s_hw_freq_seek(struct file *file, void *fh, 370 const struct v4l2_hw_freq_seek *a) 371{ 372 struct radio_tea5777 *tea = video_drvdata(file); 373 unsigned long timeout; 374 u32 rangelow = a->rangelow; 375 u32 rangehigh = a->rangehigh; 376 int i, res, spacing; 377 u32 orig_freq; 378 379 if (a->tuner || a->wrap_around) 380 return -EINVAL; 381 382 if (file->f_flags & O_NONBLOCK) 383 return -EWOULDBLOCK; 384 385 if (rangelow || rangehigh) { 386 for (i = 0; i < ARRAY_SIZE(bands); i++) { 387 if (i == BAND_AM && !tea->has_am) 388 continue; 389 if (bands[i].rangelow >= rangelow && 390 bands[i].rangehigh <= rangehigh) 391 break; 392 } 393 if (i == ARRAY_SIZE(bands)) 394 return -EINVAL; /* No matching band found */ 395 396 tea->band = i; 397 if (tea->freq < rangelow || tea->freq > rangehigh) { 398 tea->freq = clamp(tea->freq, rangelow, 399 rangehigh); 400 res = radio_tea5777_set_freq(tea); 401 if (res) 402 return res; 403 } 404 } else { 405 rangelow = bands[tea->band].rangelow; 406 rangehigh = bands[tea->band].rangehigh; 407 } 408 409 spacing = (tea->band == BAND_AM) ? (5 * 16) : (200 * 16); /* kHz */ 410 orig_freq = tea->freq; 411 412 tea->write_reg |= TEA5777_W_PROGBLIM_MASK; 413 if (tea->seek_rangelow != rangelow) { 414 tea->write_reg &= ~TEA5777_W_UPDWN_MASK; 415 tea->freq = rangelow; 416 res = radio_tea5777_set_freq(tea); 417 if (res) 418 goto leave; 419 tea->seek_rangelow = rangelow; 420 } 421 if (tea->seek_rangehigh != rangehigh) { 422 tea->write_reg |= TEA5777_W_UPDWN_MASK; 423 tea->freq = rangehigh; 424 res = radio_tea5777_set_freq(tea); 425 if (res) 426 goto leave; 427 tea->seek_rangehigh = rangehigh; 428 } 429 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; 430 431 tea->write_reg |= TEA5777_W_SEARCH_MASK; 432 if (a->seek_upward) { 433 tea->write_reg |= TEA5777_W_UPDWN_MASK; 434 tea->freq = orig_freq + spacing; 435 } else { 436 tea->write_reg &= ~TEA5777_W_UPDWN_MASK; 437 tea->freq = orig_freq - spacing; 438 } 439 res = radio_tea5777_set_freq(tea); 440 if (res) 441 goto leave; 442 443 timeout = jiffies + msecs_to_jiffies(5000); 444 for (;;) { 445 if (time_after(jiffies, timeout)) { 446 res = -ENODATA; 447 break; 448 } 449 450 res = radio_tea5777_update_read_reg(tea, 100); 451 if (res) 452 break; 453 454 /* 455 * Note we use tea->freq to track how far we've searched sofar 456 * this is necessary to ensure we continue seeking at the right 457 * point, in the write_before_read case. 458 */ 459 tea->freq = (tea->read_reg & TEA5777_R_FM_PLL_MASK); 460 tea->freq = tea5777_freq_to_v4l2_freq(tea, tea->freq); 461 462 if ((tea->read_reg & TEA5777_R_SFOUND_MASK)) { 463 tea->write_reg &= ~TEA5777_W_SEARCH_MASK; 464 return 0; 465 } 466 467 if (tea->read_reg & TEA5777_R_BLIM_MASK) { 468 res = -ENODATA; 469 break; 470 } 471 472 /* Force read_reg update */ 473 tea->read_reg = -1; 474 } 475leave: 476 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; 477 tea->write_reg &= ~TEA5777_W_SEARCH_MASK; 478 tea->freq = orig_freq; 479 radio_tea5777_set_freq(tea); 480 return res; 481} 482 483static int tea575x_s_ctrl(struct v4l2_ctrl *c) 484{ 485 struct radio_tea5777 *tea = 486 container_of(c->handler, struct radio_tea5777, ctrl_handler); 487 488 switch (c->id) { 489 case V4L2_CID_AUDIO_MUTE: 490 if (c->val) 491 tea->write_reg |= TEA5777_W_MUTE_MASK; 492 else 493 tea->write_reg &= ~TEA5777_W_MUTE_MASK; 494 495 return radio_tea5777_set_freq(tea); 496 } 497 498 return -EINVAL; 499} 500 501static const struct v4l2_file_operations tea575x_fops = { 502 .unlocked_ioctl = video_ioctl2, 503 .open = v4l2_fh_open, 504 .release = v4l2_fh_release, 505 .poll = v4l2_ctrl_poll, 506}; 507 508static const struct v4l2_ioctl_ops tea575x_ioctl_ops = { 509 .vidioc_querycap = vidioc_querycap, 510 .vidioc_g_tuner = vidioc_g_tuner, 511 .vidioc_s_tuner = vidioc_s_tuner, 512 .vidioc_g_frequency = vidioc_g_frequency, 513 .vidioc_s_frequency = vidioc_s_frequency, 514 .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, 515 .vidioc_enum_freq_bands = vidioc_enum_freq_bands, 516 .vidioc_log_status = v4l2_ctrl_log_status, 517 .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 518 .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 519}; 520 521static const struct video_device tea575x_radio = { 522 .ioctl_ops = &tea575x_ioctl_ops, 523 .release = video_device_release_empty, 524}; 525 526static const struct v4l2_ctrl_ops tea575x_ctrl_ops = { 527 .s_ctrl = tea575x_s_ctrl, 528}; 529 530int radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner) 531{ 532 int res; 533 534 tea->write_reg = (1LL << TEA5777_W_IFCE_SHIFT) | 535 (1LL << TEA5777_W_IFW_SHIFT) | 536 (1LL << TEA5777_W_INTEXT_SHIFT) | 537 (1LL << TEA5777_W_CHP0_SHIFT) | 538 (1LL << TEA5777_W_SLEV_SHIFT); 539 tea->freq = 90500 * 16; /* 90.5Mhz default */ 540 tea->audmode = V4L2_TUNER_MODE_STEREO; 541 res = radio_tea5777_set_freq(tea); 542 if (res) { 543 v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res); 544 return res; 545 } 546 547 tea->vd = tea575x_radio; 548 video_set_drvdata(&tea->vd, tea); 549 mutex_init(&tea->mutex); 550 strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name)); 551 tea->vd.lock = &tea->mutex; 552 tea->vd.v4l2_dev = tea->v4l2_dev; 553 tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | 554 V4L2_CAP_HW_FREQ_SEEK; 555 tea->fops = tea575x_fops; 556 tea->fops.owner = owner; 557 tea->vd.fops = &tea->fops; 558 559 tea->vd.ctrl_handler = &tea->ctrl_handler; 560 v4l2_ctrl_handler_init(&tea->ctrl_handler, 1); 561 v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops, 562 V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 563 res = tea->ctrl_handler.error; 564 if (res) { 565 v4l2_err(tea->v4l2_dev, "can't initialize controls\n"); 566 v4l2_ctrl_handler_free(&tea->ctrl_handler); 567 return res; 568 } 569 v4l2_ctrl_handler_setup(&tea->ctrl_handler); 570 571 res = video_register_device(&tea->vd, VFL_TYPE_RADIO, -1); 572 if (res) { 573 v4l2_err(tea->v4l2_dev, "can't register video device!\n"); 574 v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 575 return res; 576 } 577 578 return 0; 579} 580EXPORT_SYMBOL_GPL(radio_tea5777_init); 581 582void radio_tea5777_exit(struct radio_tea5777 *tea) 583{ 584 video_unregister_device(&tea->vd); 585 v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 586} 587EXPORT_SYMBOL_GPL(radio_tea5777_exit);