[media] Add fc0011 tuner driver
authorMichael Büsch <m@bues.ch>
Mon, 2 Apr 2012 15:14:32 +0000 (12:14 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Mon, 9 Apr 2012 17:49:32 +0000 (14:49 -0300)
This adds support for the Fitipower fc0011 DVB-t tuner.

Signed-off-by: Michael Buesch <m@bues.ch>
Signed-off-by: Antti Palosaari <crope@iki.fi>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
MAINTAINERS
drivers/media/common/tuners/Kconfig
drivers/media/common/tuners/Makefile
drivers/media/common/tuners/fc0011.c [new file with mode: 0644]
drivers/media/common/tuners/fc0011.h [new file with mode: 0644]

index 6995469965590f0e576514a277b74666368b1080..9e05ceb2921f75b8d7669954365c4a8e401eb188 100644 (file)
@@ -2692,6 +2692,13 @@ S:       Maintained
 F:     Documentation/hwmon/f71805f
 F:     drivers/hwmon/f71805f.c
 
+FC0011 TUNER DRIVER
+M:     Michael Buesch <m@bues.ch>
+L:     linux-media@vger.kernel.org
+S:     Maintained
+F:     drivers/media/common/tuners/fc0011.h
+F:     drivers/media/common/tuners/fc0011.c
+
 FANOTIFY
 M:     Eric Paris <eparis@redhat.com>
 S:     Maintained
index ae8ebcfa6fa28481c9a9192e81de056714895bf8..0fd15d925e1540f25bd151246b6b9a33a8dd570d 100644 (file)
@@ -204,6 +204,13 @@ config MEDIA_TUNER_TDA18218
        help
          NXP TDA18218 silicon tuner driver.
 
+config MEDIA_TUNER_FC0011
+       tristate "Fitipower FC0011 silicon tuner"
+       depends on VIDEO_MEDIA && I2C
+       default m if MEDIA_TUNER_CUSTOMISE
+       help
+         Fitipower FC0011 silicon tuner driver.
+
 config MEDIA_TUNER_TDA18212
        tristate "NXP TDA18212 silicon tuner"
        depends on VIDEO_MEDIA && I2C
index 6c3040501c4507fc03f378998152397b7490fb68..64ee06fa83f14df95be9f39d5d25b696a5e803e2 100644 (file)
@@ -29,6 +29,7 @@ obj-$(CONFIG_MEDIA_TUNER_MAX2165) += max2165.o
 obj-$(CONFIG_MEDIA_TUNER_TDA18218) += tda18218.o
 obj-$(CONFIG_MEDIA_TUNER_TDA18212) += tda18212.o
 obj-$(CONFIG_MEDIA_TUNER_TUA9001) += tua9001.o
+obj-$(CONFIG_MEDIA_TUNER_FC0011) += fc0011.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb/frontends
diff --git a/drivers/media/common/tuners/fc0011.c b/drivers/media/common/tuners/fc0011.c
new file mode 100644 (file)
index 0000000..7842a4e
--- /dev/null
@@ -0,0 +1,524 @@
+/*
+ * Fitipower FC0011 tuner driver
+ *
+ * Copyright (C) 2012 Michael Buesch <m@bues.ch>
+ *
+ * Derived from FC0012 tuner driver:
+ * Copyright (C) 2012 Hans-Frieder Vogt <hfvogt@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "fc0011.h"
+
+
+/* Tuner registers */
+enum {
+       FC11_REG_0,
+       FC11_REG_FA,            /* FA */
+       FC11_REG_FP,            /* FP */
+       FC11_REG_XINHI,         /* XIN high 8 bit */
+       FC11_REG_XINLO,         /* XIN low 8 bit */
+       FC11_REG_VCO,           /* VCO */
+       FC11_REG_VCOSEL,        /* VCO select */
+       FC11_REG_7,             /* Unknown tuner reg 7 */
+       FC11_REG_8,             /* Unknown tuner reg 8 */
+       FC11_REG_9,
+       FC11_REG_10,            /* Unknown tuner reg 10 */
+       FC11_REG_11,            /* Unknown tuner reg 11 */
+       FC11_REG_12,
+       FC11_REG_RCCAL,         /* RC calibrate */
+       FC11_REG_VCOCAL,        /* VCO calibrate */
+       FC11_REG_15,
+       FC11_REG_16,            /* Unknown tuner reg 16 */
+       FC11_REG_17,
+
+       FC11_NR_REGS,           /* Number of registers */
+};
+
+enum FC11_REG_VCOSEL_bits {
+       FC11_VCOSEL_2           = 0x08, /* VCO select 2 */
+       FC11_VCOSEL_1           = 0x10, /* VCO select 1 */
+       FC11_VCOSEL_CLKOUT      = 0x20, /* Fix clock out */
+       FC11_VCOSEL_BW7M        = 0x40, /* 7MHz bw */
+       FC11_VCOSEL_BW6M        = 0x80, /* 6MHz bw */
+};
+
+enum FC11_REG_RCCAL_bits {
+       FC11_RCCAL_FORCE        = 0x10, /* force */
+};
+
+enum FC11_REG_VCOCAL_bits {
+       FC11_VCOCAL_RUN         = 0,    /* VCO calibration run */
+       FC11_VCOCAL_VALUEMASK   = 0x3F, /* VCO calibration value mask */
+       FC11_VCOCAL_OK          = 0x40, /* VCO calibration Ok */
+       FC11_VCOCAL_RESET       = 0x80, /* VCO calibration reset */
+};
+
+
+struct fc0011_priv {
+       struct i2c_adapter *i2c;
+       u8 addr;
+
+       u32 frequency;
+       u32 bandwidth;
+};
+
+
+static int fc0011_writereg(struct fc0011_priv *priv, u8 reg, u8 val)
+{
+       u8 buf[2] = { reg, val };
+       struct i2c_msg msg = { .addr = priv->addr,
+               .flags = 0, .buf = buf, .len = 2 };
+
+       if (i2c_transfer(priv->i2c, &msg, 1) != 1) {
+               dev_err(&priv->i2c->dev,
+                       "I2C write reg failed, reg: %02x, val: %02x\n",
+                       reg, val);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int fc0011_readreg(struct fc0011_priv *priv, u8 reg, u8 *val)
+{
+       u8 dummy;
+       struct i2c_msg msg[2] = {
+               { .addr = priv->addr,
+                 .flags = 0, .buf = &reg, .len = 1 },
+               { .addr = priv->addr,
+                 .flags = I2C_M_RD, .buf = val ? : &dummy, .len = 1 },
+       };
+
+       if (i2c_transfer(priv->i2c, msg, 2) != 2) {
+               dev_err(&priv->i2c->dev,
+                       "I2C read failed, reg: %02x\n", reg);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int fc0011_release(struct dvb_frontend *fe)
+{
+       kfree(fe->tuner_priv);
+       fe->tuner_priv = NULL;
+
+       return 0;
+}
+
+static int fc0011_init(struct dvb_frontend *fe)
+{
+       struct fc0011_priv *priv = fe->tuner_priv;
+       int err;
+
+       if (WARN_ON(!fe->callback))
+               return -EINVAL;
+
+       err = fe->callback(priv->i2c, DVB_FRONTEND_COMPONENT_TUNER,
+                          FC0011_FE_CALLBACK_POWER, priv->addr);
+       if (err) {
+               dev_err(&priv->i2c->dev, "Power-on callback failed\n");
+               return err;
+       }
+       err = fe->callback(priv->i2c, DVB_FRONTEND_COMPONENT_TUNER,
+                          FC0011_FE_CALLBACK_RESET, priv->addr);
+       if (err) {
+               dev_err(&priv->i2c->dev, "Reset callback failed\n");
+               return err;
+       }
+
+       return 0;
+}
+
+/* Initiate VCO calibration */
+static int fc0011_vcocal_trigger(struct fc0011_priv *priv)
+{
+       int err;
+
+       err = fc0011_writereg(priv, FC11_REG_VCOCAL, FC11_VCOCAL_RESET);
+       if (err)
+               return err;
+       err = fc0011_writereg(priv, FC11_REG_VCOCAL, FC11_VCOCAL_RUN);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+/* Read VCO calibration value */
+static int fc0011_vcocal_read(struct fc0011_priv *priv, u8 *value)
+{
+       int err;
+
+       err = fc0011_writereg(priv, FC11_REG_VCOCAL, FC11_VCOCAL_RUN);
+       if (err)
+               return err;
+       msleep(10);
+       err = fc0011_readreg(priv, FC11_REG_VCOCAL, value);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+static int fc0011_set_params(struct dvb_frontend *fe)
+{
+       struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+       struct fc0011_priv *priv = fe->tuner_priv;
+       int err;
+       unsigned int i, vco_retries;
+       u32 freq = p->frequency / 1000;
+       u32 bandwidth = p->bandwidth_hz / 1000;
+       u32 fvco, xin, xdiv, xdivr;
+       u16 frac;
+       u8 fa, fp, vco_sel, vco_cal;
+       u8 regs[FC11_NR_REGS] = { };
+
+       regs[FC11_REG_7] = 0x0F;
+       regs[FC11_REG_8] = 0x3E;
+       regs[FC11_REG_10] = 0xB8;
+       regs[FC11_REG_11] = 0x80;
+       regs[FC11_REG_RCCAL] = 0x04;
+       err = fc0011_writereg(priv, FC11_REG_7, regs[FC11_REG_7]);
+       err |= fc0011_writereg(priv, FC11_REG_8, regs[FC11_REG_8]);
+       err |= fc0011_writereg(priv, FC11_REG_10, regs[FC11_REG_10]);
+       err |= fc0011_writereg(priv, FC11_REG_11, regs[FC11_REG_11]);
+       err |= fc0011_writereg(priv, FC11_REG_RCCAL, regs[FC11_REG_RCCAL]);
+       if (err)
+               return -EIO;
+
+       /* Set VCO freq and VCO div */
+       if (freq < 54000) {
+               fvco = freq * 64;
+               regs[FC11_REG_VCO] = 0x82;
+       } else if (freq < 108000) {
+               fvco = freq * 32;
+               regs[FC11_REG_VCO] = 0x42;
+       } else if (freq < 216000) {
+               fvco = freq * 16;
+               regs[FC11_REG_VCO] = 0x22;
+       } else if (freq < 432000) {
+               fvco = freq * 8;
+               regs[FC11_REG_VCO] = 0x12;
+       } else {
+               fvco = freq * 4;
+               regs[FC11_REG_VCO] = 0x0A;
+       }
+
+       /* Calc XIN. The PLL reference frequency is 18 MHz. */
+       xdiv = fvco / 18000;
+       frac = fvco - xdiv * 18000;
+       frac = (frac << 15) / 18000;
+       if (frac >= 16384)
+               frac += 32786;
+       if (!frac)
+               xin = 0;
+       else if (frac < 511)
+               xin = 512;
+       else if (frac < 65026)
+               xin = frac;
+       else
+               xin = 65024;
+       regs[FC11_REG_XINHI] = xin >> 8;
+       regs[FC11_REG_XINLO] = xin;
+
+       /* Calc FP and FA */
+       xdivr = xdiv;
+       if (fvco - xdiv * 18000 >= 9000)
+               xdivr += 1; /* round */
+       fp = xdivr / 8;
+       fa = xdivr - fp * 8;
+       if (fa < 2) {
+               fp -= 1;
+               fa += 8;
+       }
+       if (fp > 0x1F) {
+               fp &= 0x1F;
+               fa &= 0xF;
+       }
+       if (fa >= fp) {
+               dev_warn(&priv->i2c->dev,
+                        "fa %02X >= fp %02X, but trying to continue\n",
+                        (unsigned int)(u8)fa, (unsigned int)(u8)fp);
+       }
+       regs[FC11_REG_FA] = fa;
+       regs[FC11_REG_FP] = fp;
+
+       /* Select bandwidth */
+       switch (bandwidth) {
+       case 8000:
+               break;
+       case 7000:
+               regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_BW7M;
+               break;
+       default:
+               dev_warn(&priv->i2c->dev, "Unsupported bandwidth %u kHz. "
+                        "Using 6000 kHz.\n",
+                        bandwidth);
+               bandwidth = 6000;
+               /* fallthrough */
+       case 6000:
+               regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_BW6M;
+               break;
+       }
+
+       /* Pre VCO select */
+       if (fvco < 2320000) {
+               vco_sel = 0;
+               regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2);
+       } else if (fvco < 3080000) {
+               vco_sel = 1;
+               regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2);
+               regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_1;
+       } else {
+               vco_sel = 2;
+               regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2);
+               regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_2;
+       }
+
+       /* Fix for low freqs */
+       if (freq < 45000) {
+               regs[FC11_REG_FA] = 0x6;
+               regs[FC11_REG_FP] = 0x11;
+       }
+
+       /* Clock out fix */
+       regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_CLKOUT;
+
+       /* Write the cached registers */
+       for (i = FC11_REG_FA; i <= FC11_REG_VCOSEL; i++) {
+               err = fc0011_writereg(priv, i, regs[i]);
+               if (err)
+                       return err;
+       }
+
+       /* VCO calibration */
+       err = fc0011_vcocal_trigger(priv);
+       if (err)
+               return err;
+       err = fc0011_vcocal_read(priv, &vco_cal);
+       if (err)
+               return err;
+       vco_retries = 0;
+       while (!(vco_cal & FC11_VCOCAL_OK) && vco_retries < 6) {
+               /* Reset the tuner and try again */
+               err = fe->callback(priv->i2c, DVB_FRONTEND_COMPONENT_TUNER,
+                                  FC0011_FE_CALLBACK_RESET, priv->addr);
+               if (err) {
+                       dev_err(&priv->i2c->dev, "Failed to reset tuner\n");
+                       return err;
+               }
+               /* Reinit tuner config */
+               err = 0;
+               for (i = FC11_REG_FA; i <= FC11_REG_VCOSEL; i++)
+                       err |= fc0011_writereg(priv, i, regs[i]);
+               err |= fc0011_writereg(priv, FC11_REG_7, regs[FC11_REG_7]);
+               err |= fc0011_writereg(priv, FC11_REG_8, regs[FC11_REG_8]);
+               err |= fc0011_writereg(priv, FC11_REG_10, regs[FC11_REG_10]);
+               err |= fc0011_writereg(priv, FC11_REG_11, regs[FC11_REG_11]);
+               err |= fc0011_writereg(priv, FC11_REG_RCCAL, regs[FC11_REG_RCCAL]);
+               if (err)
+                       return -EIO;
+               /* VCO calibration */
+               err = fc0011_vcocal_trigger(priv);
+               if (err)
+                       return err;
+               err = fc0011_vcocal_read(priv, &vco_cal);
+               if (err)
+                       return err;
+               vco_retries++;
+       }
+       if (!(vco_cal & FC11_VCOCAL_OK)) {
+               dev_err(&priv->i2c->dev,
+                       "Failed to read VCO calibration value (got %02X)\n",
+                       (unsigned int)vco_cal);
+               return -EIO;
+       }
+       vco_cal &= FC11_VCOCAL_VALUEMASK;
+
+       switch (vco_sel) {
+       case 0:
+               if (vco_cal < 8) {
+                       regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2);
+                       regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_1;
+                       err = fc0011_writereg(priv, FC11_REG_VCOSEL,
+                                             regs[FC11_REG_VCOSEL]);
+                       if (err)
+                               return err;
+                       err = fc0011_vcocal_trigger(priv);
+                       if (err)
+                               return err;
+               } else {
+                       regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2);
+                       err = fc0011_writereg(priv, FC11_REG_VCOSEL,
+                                             regs[FC11_REG_VCOSEL]);
+                       if (err)
+                               return err;
+               }
+               break;
+       case 1:
+               if (vco_cal < 5) {
+                       regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2);
+                       regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_2;
+                       err = fc0011_writereg(priv, FC11_REG_VCOSEL,
+                                             regs[FC11_REG_VCOSEL]);
+                       if (err)
+                               return err;
+                       err = fc0011_vcocal_trigger(priv);
+                       if (err)
+                               return err;
+               } else if (vco_cal <= 48) {
+                       regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2);
+                       regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_1;
+                       err = fc0011_writereg(priv, FC11_REG_VCOSEL,
+                                             regs[FC11_REG_VCOSEL]);
+                       if (err)
+                               return err;
+               } else {
+                       regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2);
+                       err = fc0011_writereg(priv, FC11_REG_VCOSEL,
+                                             regs[FC11_REG_VCOSEL]);
+                       if (err)
+                               return err;
+                       err = fc0011_vcocal_trigger(priv);
+                       if (err)
+                               return err;
+               }
+               break;
+       case 2:
+               if (vco_cal > 53) {
+                       regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2);
+                       regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_1;
+                       err = fc0011_writereg(priv, FC11_REG_VCOSEL,
+                                             regs[FC11_REG_VCOSEL]);
+                       if (err)
+                               return err;
+                       err = fc0011_vcocal_trigger(priv);
+                       if (err)
+                               return err;
+               } else {
+                       regs[FC11_REG_VCOSEL] &= ~(FC11_VCOSEL_1 | FC11_VCOSEL_2);
+                       regs[FC11_REG_VCOSEL] |= FC11_VCOSEL_2;
+                       err = fc0011_writereg(priv, FC11_REG_VCOSEL,
+                                             regs[FC11_REG_VCOSEL]);
+                       if (err)
+                               return err;
+               }
+               break;
+       }
+       err = fc0011_vcocal_read(priv, NULL);
+       if (err)
+               return err;
+       msleep(10);
+
+       err = fc0011_readreg(priv, FC11_REG_RCCAL, &regs[FC11_REG_RCCAL]);
+       if (err)
+               return err;
+       regs[FC11_REG_RCCAL] |= FC11_RCCAL_FORCE;
+       err = fc0011_writereg(priv, FC11_REG_RCCAL, regs[FC11_REG_RCCAL]);
+       if (err)
+               return err;
+       err = fc0011_writereg(priv, FC11_REG_16, 0xB);
+       if (err)
+               return err;
+
+       dev_dbg(&priv->i2c->dev, "Tuned to "
+               "fa=%02X fp=%02X xin=%02X%02X vco=%02X vcosel=%02X "
+               "vcocal=%02X(%u) bw=%u\n",
+               (unsigned int)regs[FC11_REG_FA],
+               (unsigned int)regs[FC11_REG_FP],
+               (unsigned int)regs[FC11_REG_XINHI],
+               (unsigned int)regs[FC11_REG_XINLO],
+               (unsigned int)regs[FC11_REG_VCO],
+               (unsigned int)regs[FC11_REG_VCOSEL],
+               (unsigned int)vco_cal, vco_retries,
+               (unsigned int)bandwidth);
+
+       priv->frequency = p->frequency;
+       priv->bandwidth = p->bandwidth_hz;
+
+       return 0;
+}
+
+static int fc0011_get_frequency(struct dvb_frontend *fe, u32 *frequency)
+{
+       struct fc0011_priv *priv = fe->tuner_priv;
+
+       *frequency = priv->frequency;
+
+       return 0;
+}
+
+static int fc0011_get_if_frequency(struct dvb_frontend *fe, u32 *frequency)
+{
+       *frequency = 0;
+
+       return 0;
+}
+
+static int fc0011_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
+{
+       struct fc0011_priv *priv = fe->tuner_priv;
+
+       *bandwidth = priv->bandwidth;
+
+       return 0;
+}
+
+static const struct dvb_tuner_ops fc0011_tuner_ops = {
+       .info = {
+               .name           = "Fitipower FC0011",
+
+               .frequency_min  = 45000000,
+               .frequency_max  = 1000000000,
+       },
+
+       .release                = fc0011_release,
+       .init                   = fc0011_init,
+
+       .set_params             = fc0011_set_params,
+
+       .get_frequency          = fc0011_get_frequency,
+       .get_if_frequency       = fc0011_get_if_frequency,
+       .get_bandwidth          = fc0011_get_bandwidth,
+};
+
+struct dvb_frontend *fc0011_attach(struct dvb_frontend *fe,
+                                  struct i2c_adapter *i2c,
+                                  const struct fc0011_config *config)
+{
+       struct fc0011_priv *priv;
+
+       priv = kzalloc(sizeof(struct fc0011_priv), GFP_KERNEL);
+       if (!priv)
+               return NULL;
+
+       priv->i2c = i2c;
+       priv->addr = config->i2c_address;
+
+       fe->tuner_priv = priv;
+       fe->ops.tuner_ops = fc0011_tuner_ops;
+
+       dev_info(&priv->i2c->dev, "Fitipower FC0011 tuner attached\n");
+
+       return fe;
+}
+EXPORT_SYMBOL(fc0011_attach);
+
+MODULE_DESCRIPTION("Fitipower FC0011 silicon tuner driver");
+MODULE_AUTHOR("Michael Buesch <m@bues.ch>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/common/tuners/fc0011.h b/drivers/media/common/tuners/fc0011.h
new file mode 100644 (file)
index 0000000..0ee581f
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef LINUX_FC0011_H_
+#define LINUX_FC0011_H_
+
+#include "dvb_frontend.h"
+
+
+/** struct fc0011_config - fc0011 hardware config
+ *
+ * @i2c_address: I2C bus address.
+ */
+struct fc0011_config {
+       u8 i2c_address;
+};
+
+/** enum fc0011_fe_callback_commands - Frontend callbacks
+ *
+ * @FC0011_FE_CALLBACK_POWER: Power on tuner hardware.
+ * @FC0011_FE_CALLBACK_RESET: Request a tuner reset.
+ */
+enum fc0011_fe_callback_commands {
+       FC0011_FE_CALLBACK_POWER,
+       FC0011_FE_CALLBACK_RESET,
+};
+
+#if defined(CONFIG_MEDIA_TUNER_FC0011) ||\
+    defined(CONFIG_MEDIA_TUNER_FC0011_MODULE)
+struct dvb_frontend *fc0011_attach(struct dvb_frontend *fe,
+                                  struct i2c_adapter *i2c,
+                                  const struct fc0011_config *config);
+#else
+static inline
+struct dvb_frontend *fc0011_attach(struct dvb_frontend *fe,
+                                  struct i2c_adapter *i2c,
+                                  const struct fc0011_config *config)
+{
+       dev_err(&i2c->dev, "fc0011 driver disabled in Kconfig\n");
+       return NULL;
+}
+#endif
+
+#endif /* LINUX_FC0011_H_ */