linux/drivers/media/radio/radio-tea5777.c
<<
>>
Prefs
   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);
 588