0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 #include <linux/delay.h>
0013 #include <linux/init.h>
0014 #include <linux/module.h>
0015 #include <linux/sched.h>
0016 #include <linux/slab.h>
0017 #include <media/v4l2-device.h>
0018 #include <media/v4l2-dev.h>
0019 #include <media/v4l2-fh.h>
0020 #include <media/v4l2-ioctl.h>
0021 #include <media/v4l2-event.h>
0022 #include "radio-tea5777.h"
0023
0024 MODULE_AUTHOR("Hans de Goede <perex@perex.cz>");
0025 MODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips");
0026 MODULE_LICENSE("GPL");
0027
0028 #define TEA5777_FM_IF 150
0029 #define TEA5777_FM_FREQ_STEP 50
0030
0031 #define TEA5777_AM_IF 21
0032 #define TEA5777_AM_FREQ_STEP 1
0033
0034
0035 #define TEA5777_W_MUTE_MASK (1LL << 47)
0036 #define TEA5777_W_MUTE_SHIFT 47
0037 #define TEA5777_W_AM_FM_MASK (1LL << 46)
0038 #define TEA5777_W_AM_FM_SHIFT 46
0039 #define TEA5777_W_STB_MASK (1LL << 45)
0040 #define TEA5777_W_STB_SHIFT 45
0041
0042 #define TEA5777_W_IFCE_MASK (1LL << 29)
0043 #define TEA5777_W_IFCE_SHIFT 29
0044 #define TEA5777_W_IFW_MASK (1LL << 28)
0045 #define TEA5777_W_IFW_SHIFT 28
0046 #define TEA5777_W_HILO_MASK (1LL << 27)
0047 #define TEA5777_W_HILO_SHIFT 27
0048 #define TEA5777_W_DBUS_MASK (1LL << 26)
0049 #define TEA5777_W_DBUS_SHIFT 26
0050
0051 #define TEA5777_W_INTEXT_MASK (1LL << 24)
0052 #define TEA5777_W_INTEXT_SHIFT 24
0053 #define TEA5777_W_P1_MASK (1LL << 23)
0054 #define TEA5777_W_P1_SHIFT 23
0055 #define TEA5777_W_P0_MASK (1LL << 22)
0056 #define TEA5777_W_P0_SHIFT 22
0057 #define TEA5777_W_PEN1_MASK (1LL << 21)
0058 #define TEA5777_W_PEN1_SHIFT 21
0059 #define TEA5777_W_PEN0_MASK (1LL << 20)
0060 #define TEA5777_W_PEN0_SHIFT 20
0061
0062 #define TEA5777_W_CHP0_MASK (1LL << 18)
0063 #define TEA5777_W_CHP0_SHIFT 18
0064 #define TEA5777_W_DEEM_MASK (1LL << 17)
0065 #define TEA5777_W_DEEM_SHIFT 17
0066
0067 #define TEA5777_W_SEARCH_MASK (1LL << 7)
0068 #define TEA5777_W_SEARCH_SHIFT 7
0069 #define TEA5777_W_PROGBLIM_MASK (1LL << 6)
0070 #define TEA5777_W_PROGBLIM_SHIFT 6
0071 #define TEA5777_W_UPDWN_MASK (1LL << 5)
0072 #define TEA5777_W_UPDWN_SHIFT 5
0073 #define TEA5777_W_SLEV_MASK (3LL << 3)
0074 #define TEA5777_W_SLEV_SHIFT 3
0075
0076
0077 #define TEA5777_W_FM_PLL_MASK (0x1fffLL << 32)
0078 #define TEA5777_W_FM_PLL_SHIFT 32
0079 #define TEA5777_W_FM_FREF_MASK (0x03LL << 30)
0080 #define TEA5777_W_FM_FREF_SHIFT 30
0081 #define TEA5777_W_FM_FREF_VALUE 0LL
0082
0083 #define TEA5777_W_FM_FORCEMONO_MASK (1LL << 15)
0084 #define TEA5777_W_FM_FORCEMONO_SHIFT 15
0085 #define TEA5777_W_FM_SDSOFF_MASK (1LL << 14)
0086 #define TEA5777_W_FM_SDSOFF_SHIFT 14
0087 #define TEA5777_W_FM_DOFF_MASK (1LL << 13)
0088 #define TEA5777_W_FM_DOFF_SHIFT 13
0089
0090 #define TEA5777_W_FM_STEP_MASK (3LL << 1)
0091 #define TEA5777_W_FM_STEP_SHIFT 1
0092
0093
0094 #define TEA5777_W_AM_PLL_MASK (0x7ffLL << 34)
0095 #define TEA5777_W_AM_PLL_SHIFT 34
0096 #define TEA5777_W_AM_AGCRF_MASK (1LL << 33)
0097 #define TEA5777_W_AM_AGCRF_SHIFT 33
0098 #define TEA5777_W_AM_AGCIF_MASK (1LL << 32)
0099 #define TEA5777_W_AM_AGCIF_SHIFT 32
0100 #define TEA5777_W_AM_MWLW_MASK (1LL << 31)
0101 #define TEA5777_W_AM_MWLW_SHIFT 31
0102 #define TEA5777_W_AM_LW 0LL
0103 #define TEA5777_W_AM_MW 1LL
0104 #define TEA5777_W_AM_LNA_MASK (1LL << 30)
0105 #define TEA5777_W_AM_LNA_SHIFT 30
0106
0107 #define TEA5777_W_AM_PEAK_MASK (1LL << 25)
0108 #define TEA5777_W_AM_PEAK_SHIFT 25
0109
0110 #define TEA5777_W_AM_RFB_MASK (1LL << 16)
0111 #define TEA5777_W_AM_RFB_SHIFT 16
0112 #define TEA5777_W_AM_CALLIGN_MASK (1LL << 15)
0113 #define TEA5777_W_AM_CALLIGN_SHIFT 15
0114 #define TEA5777_W_AM_CBANK_MASK (0x7fLL << 8)
0115 #define TEA5777_W_AM_CBANK_SHIFT 8
0116
0117 #define TEA5777_W_AM_DELAY_MASK (1LL << 2)
0118 #define TEA5777_W_AM_DELAY_SHIFT 2
0119 #define TEA5777_W_AM_STEP_MASK (1LL << 1)
0120 #define TEA5777_W_AM_STEP_SHIFT 1
0121
0122
0123 #define TEA5777_R_LEVEL_MASK (0x0f << 17)
0124 #define TEA5777_R_LEVEL_SHIFT 17
0125 #define TEA5777_R_SFOUND_MASK (0x01 << 16)
0126 #define TEA5777_R_SFOUND_SHIFT 16
0127 #define TEA5777_R_BLIM_MASK (0x01 << 15)
0128 #define TEA5777_R_BLIM_SHIFT 15
0129
0130
0131 #define TEA5777_R_FM_STEREO_MASK (0x01 << 21)
0132 #define TEA5777_R_FM_STEREO_SHIFT 21
0133 #define TEA5777_R_FM_PLL_MASK 0x1fff
0134 #define TEA5777_R_FM_PLL_SHIFT 0
0135
0136 enum { BAND_FM, BAND_AM };
0137
0138 static const struct v4l2_frequency_band bands[] = {
0139 {
0140 .type = V4L2_TUNER_RADIO,
0141 .index = 0,
0142 .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
0143 V4L2_TUNER_CAP_FREQ_BANDS |
0144 V4L2_TUNER_CAP_HWSEEK_BOUNDED |
0145 V4L2_TUNER_CAP_HWSEEK_PROG_LIM,
0146 .rangelow = 76000 * 16,
0147 .rangehigh = 108000 * 16,
0148 .modulation = V4L2_BAND_MODULATION_FM,
0149 },
0150 {
0151 .type = V4L2_TUNER_RADIO,
0152 .index = 1,
0153 .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS |
0154 V4L2_TUNER_CAP_HWSEEK_BOUNDED |
0155 V4L2_TUNER_CAP_HWSEEK_PROG_LIM,
0156 .rangelow = 530 * 16,
0157 .rangehigh = 1710 * 16,
0158 .modulation = V4L2_BAND_MODULATION_AM,
0159 },
0160 };
0161
0162 static u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq)
0163 {
0164 switch (tea->band) {
0165 case BAND_FM:
0166 return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16;
0167 case BAND_AM:
0168 return (freq * TEA5777_AM_FREQ_STEP + TEA5777_AM_IF) * 16;
0169 }
0170 return 0;
0171 }
0172
0173 int radio_tea5777_set_freq(struct radio_tea5777 *tea)
0174 {
0175 u32 freq;
0176 int res;
0177
0178 freq = clamp(tea->freq, bands[tea->band].rangelow,
0179 bands[tea->band].rangehigh);
0180 freq = (freq + 8) / 16;
0181
0182 switch (tea->band) {
0183 case BAND_FM:
0184 tea->write_reg &= ~TEA5777_W_AM_FM_MASK;
0185 freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP;
0186 tea->write_reg &= ~TEA5777_W_FM_PLL_MASK;
0187 tea->write_reg |= (u64)freq << TEA5777_W_FM_PLL_SHIFT;
0188 tea->write_reg &= ~TEA5777_W_FM_FREF_MASK;
0189 tea->write_reg |= TEA5777_W_FM_FREF_VALUE <<
0190 TEA5777_W_FM_FREF_SHIFT;
0191 tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK;
0192 if (tea->audmode == V4L2_TUNER_MODE_MONO)
0193 tea->write_reg |= 1LL << TEA5777_W_FM_FORCEMONO_SHIFT;
0194 break;
0195 case BAND_AM:
0196 tea->write_reg &= ~TEA5777_W_AM_FM_MASK;
0197 tea->write_reg |= (1LL << TEA5777_W_AM_FM_SHIFT);
0198 freq = (freq - TEA5777_AM_IF) / TEA5777_AM_FREQ_STEP;
0199 tea->write_reg &= ~TEA5777_W_AM_PLL_MASK;
0200 tea->write_reg |= (u64)freq << TEA5777_W_AM_PLL_SHIFT;
0201 tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK;
0202 tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK;
0203 tea->write_reg &= ~TEA5777_W_AM_MWLW_MASK;
0204 tea->write_reg |= TEA5777_W_AM_MW << TEA5777_W_AM_MWLW_SHIFT;
0205 tea->write_reg &= ~TEA5777_W_AM_LNA_MASK;
0206 tea->write_reg |= 1LL << TEA5777_W_AM_LNA_SHIFT;
0207 tea->write_reg &= ~TEA5777_W_AM_PEAK_MASK;
0208 tea->write_reg |= 1LL << TEA5777_W_AM_PEAK_SHIFT;
0209 tea->write_reg &= ~TEA5777_W_AM_CALLIGN_MASK;
0210 break;
0211 }
0212
0213 res = tea->ops->write_reg(tea, tea->write_reg);
0214 if (res)
0215 return res;
0216
0217 tea->needs_write = false;
0218 tea->read_reg = -1;
0219 tea->freq = tea5777_freq_to_v4l2_freq(tea, freq);
0220
0221 return 0;
0222 }
0223
0224 static int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait)
0225 {
0226 int res;
0227
0228 if (tea->read_reg != -1)
0229 return 0;
0230
0231 if (tea->write_before_read && tea->needs_write) {
0232 res = radio_tea5777_set_freq(tea);
0233 if (res)
0234 return res;
0235 }
0236
0237 if (wait) {
0238 if (schedule_timeout_interruptible(msecs_to_jiffies(wait)))
0239 return -ERESTARTSYS;
0240 }
0241
0242 res = tea->ops->read_reg(tea, &tea->read_reg);
0243 if (res)
0244 return res;
0245
0246 tea->needs_write = true;
0247 return 0;
0248 }
0249
0250
0251
0252
0253
0254 static int vidioc_querycap(struct file *file, void *priv,
0255 struct v4l2_capability *v)
0256 {
0257 struct radio_tea5777 *tea = video_drvdata(file);
0258
0259 strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver));
0260 strscpy(v->card, tea->card, sizeof(v->card));
0261 strlcat(v->card, " TEA5777", sizeof(v->card));
0262 strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info));
0263 return 0;
0264 }
0265
0266 static int vidioc_enum_freq_bands(struct file *file, void *priv,
0267 struct v4l2_frequency_band *band)
0268 {
0269 struct radio_tea5777 *tea = video_drvdata(file);
0270
0271 if (band->tuner != 0 || band->index >= ARRAY_SIZE(bands) ||
0272 (!tea->has_am && band->index == BAND_AM))
0273 return -EINVAL;
0274
0275 *band = bands[band->index];
0276 return 0;
0277 }
0278
0279 static int vidioc_g_tuner(struct file *file, void *priv,
0280 struct v4l2_tuner *v)
0281 {
0282 struct radio_tea5777 *tea = video_drvdata(file);
0283 int res;
0284
0285 if (v->index > 0)
0286 return -EINVAL;
0287
0288 res = radio_tea5777_update_read_reg(tea, 0);
0289 if (res)
0290 return res;
0291
0292 memset(v, 0, sizeof(*v));
0293 if (tea->has_am)
0294 strscpy(v->name, "AM/FM", sizeof(v->name));
0295 else
0296 strscpy(v->name, "FM", sizeof(v->name));
0297 v->type = V4L2_TUNER_RADIO;
0298 v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
0299 V4L2_TUNER_CAP_FREQ_BANDS |
0300 V4L2_TUNER_CAP_HWSEEK_BOUNDED |
0301 V4L2_TUNER_CAP_HWSEEK_PROG_LIM;
0302 v->rangelow = tea->has_am ? bands[BAND_AM].rangelow :
0303 bands[BAND_FM].rangelow;
0304 v->rangehigh = bands[BAND_FM].rangehigh;
0305 if (tea->band == BAND_FM &&
0306 (tea->read_reg & TEA5777_R_FM_STEREO_MASK))
0307 v->rxsubchans = V4L2_TUNER_SUB_STEREO;
0308 else
0309 v->rxsubchans = V4L2_TUNER_SUB_MONO;
0310 v->audmode = tea->audmode;
0311
0312 v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >>
0313 (TEA5777_R_LEVEL_SHIFT - 12);
0314
0315
0316 tea->read_reg = -1;
0317
0318 return 0;
0319 }
0320
0321 static int vidioc_s_tuner(struct file *file, void *priv,
0322 const struct v4l2_tuner *v)
0323 {
0324 struct radio_tea5777 *tea = video_drvdata(file);
0325 u32 orig_audmode = tea->audmode;
0326
0327 if (v->index)
0328 return -EINVAL;
0329
0330 tea->audmode = v->audmode;
0331 if (tea->audmode > V4L2_TUNER_MODE_STEREO)
0332 tea->audmode = V4L2_TUNER_MODE_STEREO;
0333
0334 if (tea->audmode != orig_audmode && tea->band == BAND_FM)
0335 return radio_tea5777_set_freq(tea);
0336
0337 return 0;
0338 }
0339
0340 static int vidioc_g_frequency(struct file *file, void *priv,
0341 struct v4l2_frequency *f)
0342 {
0343 struct radio_tea5777 *tea = video_drvdata(file);
0344
0345 if (f->tuner != 0)
0346 return -EINVAL;
0347 f->type = V4L2_TUNER_RADIO;
0348 f->frequency = tea->freq;
0349 return 0;
0350 }
0351
0352 static int vidioc_s_frequency(struct file *file, void *priv,
0353 const struct v4l2_frequency *f)
0354 {
0355 struct radio_tea5777 *tea = video_drvdata(file);
0356
0357 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
0358 return -EINVAL;
0359
0360 if (tea->has_am && f->frequency < (20000 * 16))
0361 tea->band = BAND_AM;
0362 else
0363 tea->band = BAND_FM;
0364
0365 tea->freq = f->frequency;
0366 return radio_tea5777_set_freq(tea);
0367 }
0368
0369 static int vidioc_s_hw_freq_seek(struct file *file, void *fh,
0370 const struct v4l2_hw_freq_seek *a)
0371 {
0372 struct radio_tea5777 *tea = video_drvdata(file);
0373 unsigned long timeout;
0374 u32 rangelow = a->rangelow;
0375 u32 rangehigh = a->rangehigh;
0376 int i, res, spacing;
0377 u32 orig_freq;
0378
0379 if (a->tuner || a->wrap_around)
0380 return -EINVAL;
0381
0382 if (file->f_flags & O_NONBLOCK)
0383 return -EWOULDBLOCK;
0384
0385 if (rangelow || rangehigh) {
0386 for (i = 0; i < ARRAY_SIZE(bands); i++) {
0387 if (i == BAND_AM && !tea->has_am)
0388 continue;
0389 if (bands[i].rangelow >= rangelow &&
0390 bands[i].rangehigh <= rangehigh)
0391 break;
0392 }
0393 if (i == ARRAY_SIZE(bands))
0394 return -EINVAL;
0395
0396 tea->band = i;
0397 if (tea->freq < rangelow || tea->freq > rangehigh) {
0398 tea->freq = clamp(tea->freq, rangelow,
0399 rangehigh);
0400 res = radio_tea5777_set_freq(tea);
0401 if (res)
0402 return res;
0403 }
0404 } else {
0405 rangelow = bands[tea->band].rangelow;
0406 rangehigh = bands[tea->band].rangehigh;
0407 }
0408
0409 spacing = (tea->band == BAND_AM) ? (5 * 16) : (200 * 16);
0410 orig_freq = tea->freq;
0411
0412 tea->write_reg |= TEA5777_W_PROGBLIM_MASK;
0413 if (tea->seek_rangelow != rangelow) {
0414 tea->write_reg &= ~TEA5777_W_UPDWN_MASK;
0415 tea->freq = rangelow;
0416 res = radio_tea5777_set_freq(tea);
0417 if (res)
0418 goto leave;
0419 tea->seek_rangelow = rangelow;
0420 }
0421 if (tea->seek_rangehigh != rangehigh) {
0422 tea->write_reg |= TEA5777_W_UPDWN_MASK;
0423 tea->freq = rangehigh;
0424 res = radio_tea5777_set_freq(tea);
0425 if (res)
0426 goto leave;
0427 tea->seek_rangehigh = rangehigh;
0428 }
0429 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK;
0430
0431 tea->write_reg |= TEA5777_W_SEARCH_MASK;
0432 if (a->seek_upward) {
0433 tea->write_reg |= TEA5777_W_UPDWN_MASK;
0434 tea->freq = orig_freq + spacing;
0435 } else {
0436 tea->write_reg &= ~TEA5777_W_UPDWN_MASK;
0437 tea->freq = orig_freq - spacing;
0438 }
0439 res = radio_tea5777_set_freq(tea);
0440 if (res)
0441 goto leave;
0442
0443 timeout = jiffies + msecs_to_jiffies(5000);
0444 for (;;) {
0445 if (time_after(jiffies, timeout)) {
0446 res = -ENODATA;
0447 break;
0448 }
0449
0450 res = radio_tea5777_update_read_reg(tea, 100);
0451 if (res)
0452 break;
0453
0454
0455
0456
0457
0458
0459 tea->freq = (tea->read_reg & TEA5777_R_FM_PLL_MASK);
0460 tea->freq = tea5777_freq_to_v4l2_freq(tea, tea->freq);
0461
0462 if ((tea->read_reg & TEA5777_R_SFOUND_MASK)) {
0463 tea->write_reg &= ~TEA5777_W_SEARCH_MASK;
0464 return 0;
0465 }
0466
0467 if (tea->read_reg & TEA5777_R_BLIM_MASK) {
0468 res = -ENODATA;
0469 break;
0470 }
0471
0472
0473 tea->read_reg = -1;
0474 }
0475 leave:
0476 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK;
0477 tea->write_reg &= ~TEA5777_W_SEARCH_MASK;
0478 tea->freq = orig_freq;
0479 radio_tea5777_set_freq(tea);
0480 return res;
0481 }
0482
0483 static int tea575x_s_ctrl(struct v4l2_ctrl *c)
0484 {
0485 struct radio_tea5777 *tea =
0486 container_of(c->handler, struct radio_tea5777, ctrl_handler);
0487
0488 switch (c->id) {
0489 case V4L2_CID_AUDIO_MUTE:
0490 if (c->val)
0491 tea->write_reg |= TEA5777_W_MUTE_MASK;
0492 else
0493 tea->write_reg &= ~TEA5777_W_MUTE_MASK;
0494
0495 return radio_tea5777_set_freq(tea);
0496 }
0497
0498 return -EINVAL;
0499 }
0500
0501 static const struct v4l2_file_operations tea575x_fops = {
0502 .unlocked_ioctl = video_ioctl2,
0503 .open = v4l2_fh_open,
0504 .release = v4l2_fh_release,
0505 .poll = v4l2_ctrl_poll,
0506 };
0507
0508 static const struct v4l2_ioctl_ops tea575x_ioctl_ops = {
0509 .vidioc_querycap = vidioc_querycap,
0510 .vidioc_g_tuner = vidioc_g_tuner,
0511 .vidioc_s_tuner = vidioc_s_tuner,
0512 .vidioc_g_frequency = vidioc_g_frequency,
0513 .vidioc_s_frequency = vidioc_s_frequency,
0514 .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek,
0515 .vidioc_enum_freq_bands = vidioc_enum_freq_bands,
0516 .vidioc_log_status = v4l2_ctrl_log_status,
0517 .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
0518 .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
0519 };
0520
0521 static const struct video_device tea575x_radio = {
0522 .ioctl_ops = &tea575x_ioctl_ops,
0523 .release = video_device_release_empty,
0524 };
0525
0526 static const struct v4l2_ctrl_ops tea575x_ctrl_ops = {
0527 .s_ctrl = tea575x_s_ctrl,
0528 };
0529
0530 int radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner)
0531 {
0532 int res;
0533
0534 tea->write_reg = (1LL << TEA5777_W_IFCE_SHIFT) |
0535 (1LL << TEA5777_W_IFW_SHIFT) |
0536 (1LL << TEA5777_W_INTEXT_SHIFT) |
0537 (1LL << TEA5777_W_CHP0_SHIFT) |
0538 (1LL << TEA5777_W_SLEV_SHIFT);
0539 tea->freq = 90500 * 16;
0540 tea->audmode = V4L2_TUNER_MODE_STEREO;
0541 res = radio_tea5777_set_freq(tea);
0542 if (res) {
0543 v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res);
0544 return res;
0545 }
0546
0547 tea->vd = tea575x_radio;
0548 video_set_drvdata(&tea->vd, tea);
0549 mutex_init(&tea->mutex);
0550 strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name));
0551 tea->vd.lock = &tea->mutex;
0552 tea->vd.v4l2_dev = tea->v4l2_dev;
0553 tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
0554 V4L2_CAP_HW_FREQ_SEEK;
0555 tea->fops = tea575x_fops;
0556 tea->fops.owner = owner;
0557 tea->vd.fops = &tea->fops;
0558
0559 tea->vd.ctrl_handler = &tea->ctrl_handler;
0560 v4l2_ctrl_handler_init(&tea->ctrl_handler, 1);
0561 v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops,
0562 V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
0563 res = tea->ctrl_handler.error;
0564 if (res) {
0565 v4l2_err(tea->v4l2_dev, "can't initialize controls\n");
0566 v4l2_ctrl_handler_free(&tea->ctrl_handler);
0567 return res;
0568 }
0569 v4l2_ctrl_handler_setup(&tea->ctrl_handler);
0570
0571 res = video_register_device(&tea->vd, VFL_TYPE_RADIO, -1);
0572 if (res) {
0573 v4l2_err(tea->v4l2_dev, "can't register video device!\n");
0574 v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
0575 return res;
0576 }
0577
0578 return 0;
0579 }
0580 EXPORT_SYMBOL_GPL(radio_tea5777_init);
0581
0582 void radio_tea5777_exit(struct radio_tea5777 *tea)
0583 {
0584 video_unregister_device(&tea->vd);
0585 v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
0586 }
0587 EXPORT_SYMBOL_GPL(radio_tea5777_exit);