cregit-Linux how code gets into the kernel

Release 4.7 drivers/media/tuners/fc0011.c

/*
 * 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; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch98100.00%1100.00%
Total98100.00%1100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch121100.00%1100.00%
Total121100.00%1100.00%


static int fc0011_release(struct dvb_frontend *fe) { kfree(fe->tuner_priv); fe->tuner_priv = NULL; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch27100.00%1100.00%
Total27100.00%1100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch120100.00%1100.00%
Total120100.00%1100.00%

/* 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; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch53100.00%1100.00%
Total53100.00%1100.00%

/* 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; usleep_range(10000, 20000); err = fc0011_readreg(priv, FC11_REG_VCOCAL, value); if (err) return err; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch64100.00%2100.00%
Total64100.00%2100.00%


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, frac, xdiv, xdivr; 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; WARN_ON(xdiv > 0xFF); frac = fvco - xdiv * 18000; frac = (frac << 15) / 18000; if (frac >= 16384) frac += 32786; if (!frac) xin = 0; else xin = clamp_t(u32, frac, 512, 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 < 3) { /* 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) { default: WARN_ON(1); 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; usleep_range(10000, 50000); 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; regs[FC11_REG_16] = 0xB; err = fc0011_writereg(priv, FC11_REG_16, regs[FC11_REG_16]); 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; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch1570100.00%6100.00%
Total1570100.00%6100.00%


static int fc0011_get_frequency(struct dvb_frontend *fe, u32 *frequency) { struct fc0011_priv *priv = fe->tuner_priv; *frequency = priv->frequency; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch34100.00%1100.00%
Total34100.00%1100.00%


static int fc0011_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) { *frequency = 0; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch23100.00%1100.00%
Total23100.00%1100.00%


static int fc0011_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth) { struct fc0011_priv *priv = fe->tuner_priv; *bandwidth = priv->bandwidth; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch34100.00%1100.00%
Total34100.00%1100.00%

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; }

Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch92100.00%1100.00%
Total92100.00%1100.00%

EXPORT_SYMBOL(fc0011_attach); MODULE_DESCRIPTION("Fitipower FC0011 silicon tuner driver"); MODULE_AUTHOR("Michael Buesch <m@bues.ch>"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
michael buschmichael busch2461100.00%6100.00%
Total2461100.00%6100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
{% endraw %}