On Wed, Aug 2, 2017 at 9:00 AM, <olli.salonen@xxxxxx> wrote: > From: Olli Salonen <olli.salonen@xxxxxx> > > NXP TDA18250 silicon tuner driver. > > Version 3 removes regmap_exit as I was pointed out that device > management code automatically frees the regmap. > > Signed-off-by: Olli Salonen <olli.salonen@xxxxxx> Reviewed-by: Michael Ira Krufky <mkrufky@xxxxxxxxxxx> > --- > drivers/media/tuners/Kconfig | 7 + > drivers/media/tuners/Makefile | 1 + > drivers/media/tuners/tda18250.c | 921 +++++++++++++++++++++++++++++++++++ > drivers/media/tuners/tda18250.h | 51 ++ > drivers/media/tuners/tda18250_priv.h | 153 ++++++ > 5 files changed, 1133 insertions(+) > create mode 100644 drivers/media/tuners/tda18250.c > create mode 100644 drivers/media/tuners/tda18250.h > create mode 100644 drivers/media/tuners/tda18250_priv.h Olli, Pardon the multiple copies of this email -- I had some trouble with my mailer (and forgot to keep the mailing list cc'd) Thank you for your contribution. Things look good, I just have a question, see inline below: > > diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig > index 05998f0..6687514 100644 > --- a/drivers/media/tuners/Kconfig > +++ b/drivers/media/tuners/Kconfig > @@ -26,6 +26,13 @@ config MEDIA_TUNER_SIMPLE > help > Say Y here to include support for various simple tuners. > > +config MEDIA_TUNER_TDA18250 > + tristate "NXP TDA18250 silicon tuner" > + depends on MEDIA_SUPPORT && I2C > + default m if !MEDIA_SUBDRV_AUTOSELECT > + help > + Say Y here to include support for TDA18250 tuner. > + > config MEDIA_TUNER_TDA8290 > tristate "TDA 8290/8295 + 8275(a)/18271 tuner combo" > depends on MEDIA_SUPPORT && I2C > diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile > index 06a9ab6..4b9444b 100644 > --- a/drivers/media/tuners/Makefile > +++ b/drivers/media/tuners/Makefile > @@ -41,6 +41,7 @@ obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o > obj-$(CONFIG_MEDIA_TUNER_MXL301RF) += mxl301rf.o > obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o > obj-$(CONFIG_MEDIA_TUNER_M88RS6000T) += m88rs6000t.o > +obj-$(CONFIG_MEDIA_TUNER_TDA18250) += tda18250.o > > ccflags-y += -I$(srctree)/drivers/media/dvb-core > ccflags-y += -I$(srctree)/drivers/media/dvb-frontends > diff --git a/drivers/media/tuners/tda18250.c b/drivers/media/tuners/tda18250.c > new file mode 100644 > index 0000000..3a10bd1 > --- /dev/null > +++ b/drivers/media/tuners/tda18250.c > @@ -0,0 +1,921 @@ > +/* > + * NXP TDA18250 silicon tuner driver > + * > + * Copyright (C) 2017 Olli Salonen <olli.salonen@xxxxxx> > + * > + * 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. > + * > + */ > + > +#include "tda18250_priv.h" > +#include <linux/regmap.h> > + > +static const struct dvb_tuner_ops tda18250_ops; > + > +static int tda18250_power_control(struct dvb_frontend *fe, > + unsigned int power_state) > +{ > + struct i2c_client *client = fe->tuner_priv; > + struct tda18250_dev *dev = i2c_get_clientdata(client); > + int ret; > + unsigned int utmp; > + > + dev_dbg(&client->dev, "power state: %d", power_state); > + > + switch (power_state) { > + case TDA18250_POWER_NORMAL: > + ret = regmap_write_bits(dev->regmap, R06_POWER2, 0x07, 0x00); > + if (ret) > + goto err; > + ret = regmap_write_bits(dev->regmap, R25_REF, 0xc0, 0xc0); > + if (ret) > + goto err; > + break; > + case TDA18250_POWER_STANDBY: > + if (dev->loopthrough) { > + ret = regmap_write_bits(dev->regmap, R25_REF, 0xc0, 0x80); > + if (ret) > + goto err; > + ret = regmap_write_bits(dev->regmap, R06_POWER2, 0x07, 0x02); > + if (ret) > + goto err; > + ret = regmap_write_bits(dev->regmap, R10_LT1, 0x80, 0x00); > + if (ret) > + goto err; > + } else { > + ret = regmap_write_bits(dev->regmap, R25_REF, 0xc0, 0x80); > + if (ret) > + goto err; > + ret = regmap_write_bits(dev->regmap, R06_POWER2, 0x07, 0x01); > + if (ret) > + goto err; > + ret = regmap_read(dev->regmap, R0D_AGC12, &utmp); > + if (ret) > + goto err; > + ret = regmap_write_bits(dev->regmap, R0D_AGC12, 0x03, 0x03); > + if (ret) > + goto err; > + ret = regmap_write_bits(dev->regmap, R10_LT1, 0x80, 0x80); > + if (ret) > + goto err; > + ret = regmap_write_bits(dev->regmap, R0D_AGC12, 0x03, utmp & 0x03); > + if (ret) > + goto err; > + } > + break; > + default: > + ret = -EINVAL; > + goto err; > + } > + > + return 0; > +err: > + return ret; > +} > + > +static int tda18250_wait_for_irq(struct dvb_frontend *fe, > + int maxwait, int step, u8 irq) > +{ > + struct i2c_client *client = fe->tuner_priv; > + struct tda18250_dev *dev = i2c_get_clientdata(client); > + int ret; > + unsigned long timeout; > + bool triggered; > + unsigned int utmp; > + > + triggered = false; > + timeout = jiffies + msecs_to_jiffies(maxwait); > + while (!time_after(jiffies, timeout)) { > + // check for the IRQ > + ret = regmap_read(dev->regmap, R08_IRQ1, &utmp); > + if (ret) > + goto err; > + if ((utmp & irq) == irq) { > + triggered = true; > + break; > + } > + msleep(step); > + } > + > + dev_dbg(&client->dev, "waited IRQ (0x%02x) %d ms, triggered: %s", irq, > + jiffies_to_msecs(jiffies) - > + (jiffies_to_msecs(timeout) - maxwait), > + triggered ? "true" : "false"); > + > + if (!triggered) > + return -ETIMEDOUT; > + > + return 0; > +err: > + return ret; > +} > + > +static int tda18250_init(struct dvb_frontend *fe) > +{ > + struct i2c_client *client = fe->tuner_priv; > + struct tda18250_dev *dev = i2c_get_clientdata(client); > + int ret, i; > + > + /* default values for various regs */ > + static const u8 init_regs[][2] = { > + { R0C_AGC11, 0xc7 }, > + { R0D_AGC12, 0x5d }, > + { R0E_AGC13, 0x40 }, > + { R0F_AGC14, 0x0e }, > + { R10_LT1, 0x47 }, > + { R11_LT2, 0x4e }, > + { R12_AGC21, 0x26 }, > + { R13_AGC22, 0x60 }, > + { R18_AGC32, 0x37 }, > + { R19_AGC33, 0x09 }, > + { R1A_AGCK, 0x00 }, > + { R1E_WI_FI, 0x29 }, > + { R1F_RF_BPF, 0x06 }, > + { R20_IR_MIX, 0xc6 }, > + { R21_IF_AGC, 0x00 }, > + { R2C_PS1, 0x75 }, > + { R2D_PS2, 0x06 }, > + { R2E_PS3, 0x07 }, > + { R30_RSSI2, 0x0e }, > + { R31_IRQ_CTRL, 0x00 }, > + { R39_SD5, 0x00 }, > + { R3B_REGU, 0x55 }, > + { R3C_RCCAL1, 0xa7 }, > + { R3F_IRCAL2, 0x85 }, > + { R40_IRCAL3, 0x87 }, > + { R41_IRCAL4, 0xc0 }, > + { R43_PD1, 0x40 }, > + { R44_PD2, 0xc0 }, > + { R46_CPUMP, 0x0c }, > + { R47_LNAPOL, 0x64 }, > + { R4B_XTALOSC1, 0x30 }, > + { R59_AGC2_UP2, 0x05 }, > + { R5B_AGC_AUTO, 0x07 }, > + { R5C_AGC_DEBUG, 0x00 }, > + }; > + > + /* crystal related regs depend on frequency */ > + static const u8 xtal_regs[][5] = { > + /* reg: 4d 4e 4f 50 51 */ > + [TDA18250_XTAL_FREQ_16MHZ] = { 0x3e, 0x80, 0x50, 0x00, 0x20 }, > + [TDA18250_XTAL_FREQ_24MHZ] = { 0x5d, 0xc0, 0xec, 0x00, 0x18 }, > + [TDA18250_XTAL_FREQ_25MHZ] = { 0x61, 0xa8, 0xec, 0x80, 0x19 }, > + [TDA18250_XTAL_FREQ_27MHZ] = { 0x69, 0x78, 0x8d, 0x80, 0x1b }, > + [TDA18250_XTAL_FREQ_30MHZ] = { 0x75, 0x30, 0x8f, 0x00, 0x1e }, > + }; > + > + dev_dbg(&client->dev, "\n"); > + > + ret = tda18250_power_control(fe, TDA18250_POWER_NORMAL); > + if (ret) > + goto err; > + > + msleep(20); > + > + if (dev->warm) > + goto warm; > + > + /* set initial register values */ > + for (i = 0; i < ARRAY_SIZE(init_regs); i++) { > + ret = regmap_write(dev->regmap, init_regs[i][0], > + init_regs[i][1]); > + if (ret) > + goto err; > + } > + > + /* set xtal related regs */ > + ret = regmap_bulk_write(dev->regmap, R4D_XTALFLX1, > + xtal_regs[dev->xtal_freq], 5); > + if (ret) > + goto err; > + > + ret = regmap_write_bits(dev->regmap, R10_LT1, 0x80, > + dev->loopthrough ? 0x00 : 0x80); > + if (ret) > + goto err; > + > + /* clear IRQ */ > + ret = regmap_write(dev->regmap, R0A_IRQ3, TDA18250_IRQ_HW_INIT); > + if (ret) > + goto err; > + > + /* start HW init */ > + ret = regmap_write(dev->regmap, R2A_MSM1, 0x70); > + if (ret) > + goto err; > + > + ret = regmap_write(dev->regmap, R2B_MSM2, 0x01); > + if (ret) > + goto err; > + > + ret = tda18250_wait_for_irq(fe, 500, 10, TDA18250_IRQ_HW_INIT); > + if (ret) > + goto err; > + > + /* tuner calibration */ > + ret = regmap_write(dev->regmap, R2A_MSM1, 0x02); > + if (ret) > + goto err; > + > + ret = regmap_write(dev->regmap, R2B_MSM2, 0x01); > + if (ret) > + goto err; > + > + ret = tda18250_wait_for_irq(fe, 500, 10, TDA18250_IRQ_CAL); > + if (ret) > + goto err; > + > + dev->warm = true; > + > +warm: > + /* power up LNA */ > + ret = regmap_write_bits(dev->regmap, R0C_AGC11, 0x80, 0x00); > + if (ret) > + goto err; > + > + return 0; > +err: > + dev_dbg(&client->dev, "failed=%d", ret); > + return ret; > +} > + > +static int tda18250_set_agc(struct dvb_frontend *fe) > +{ > + struct i2c_client *client = fe->tuner_priv; > + struct tda18250_dev *dev = i2c_get_clientdata(client); > + struct dtv_frontend_properties *c = &fe->dtv_property_cache; > + int ret; > + u8 utmp, utmp2; > + > + dev_dbg(&client->dev, "\n"); > + > + ret = regmap_write_bits(dev->regmap, R1F_RF_BPF, 0x87, 0x06); > + if (ret) > + goto err; > + > + utmp = ((c->frequency < 100000000) && > + ((c->delivery_system == SYS_DVBC_ANNEX_A) || > + (c->delivery_system == SYS_DVBC_ANNEX_C)) && > + (c->bandwidth_hz == 6000000)) ? 0x80 : 0x00; > + ret = regmap_write(dev->regmap, R5A_H3H5, utmp); > + if (ret) > + goto err; > + > + /* AGC1 */ > + switch (c->delivery_system) { > + case SYS_ATSC: > + case SYS_DVBT: > + case SYS_DVBT2: > + utmp = 4; > + break; > + default: /* DVB-C/QAM */ > + switch (c->bandwidth_hz) { > + case 6000000: > + utmp = (c->frequency < 800000000) ? 6 : 4; > + break; > + default: /* 7.935 and 8 MHz */ > + utmp = (c->frequency < 100000000) ? 2 : 3; > + break; > + } > + break; > + } > + > + ret = regmap_write_bits(dev->regmap, R0C_AGC11, 0x07, utmp); > + if (ret) > + goto err; > + > + /* AGC2 */ > + switch (c->delivery_system) { > + case SYS_ATSC: > + case SYS_DVBT: > + case SYS_DVBT2: > + utmp = (c->frequency < 320000000) ? 20 : 16; > + utmp2 = (c->frequency < 320000000) ? 22 : 18; > + break; > + default: /* DVB-C/QAM */ > + switch (c->bandwidth_hz) { > + case 6000000: > + if (c->frequency < 600000000) { > + utmp = 18; > + utmp2 = 22; > + } else if (c->frequency < 800000000) { > + utmp = 16; > + utmp2 = 20; > + } else { > + utmp = 14; > + utmp2 = 16; > + } > + break; > + default: /* 7.935 and 8 MHz */ > + utmp = (c->frequency < 320000000) ? 16 : 18; > + utmp2 = (c->frequency < 320000000) ? 18 : 20; > + break; > + } > + break; > + } > + ret = regmap_write_bits(dev->regmap, R58_AGC2_UP1, 0x1f, utmp2+8); > + if (ret) > + goto err; > + ret = regmap_write_bits(dev->regmap, R13_AGC22, 0x1f, utmp); > + if (ret) > + goto err; > + ret = regmap_write_bits(dev->regmap, R14_AGC23, 0x1f, utmp2); > + if (ret) > + goto err; > + > + switch (c->delivery_system) { > + case SYS_ATSC: > + case SYS_DVBT: > + case SYS_DVBT2: > + utmp = 98; > + break; > + default: /* DVB-C/QAM */ > + utmp = 90; > + break; > + } > + ret = regmap_write_bits(dev->regmap, R16_AGC25, 0xf8, utmp); > + if (ret) > + goto err; > + > + ret = regmap_write_bits(dev->regmap, R12_AGC21, 0x60, > + (c->frequency > 800000000) ? 0x40 : 0x20); > + if (ret) > + goto err; > + > + /* AGC3 */ > + switch (c->delivery_system) { > + case SYS_ATSC: > + case SYS_DVBT: > + case SYS_DVBT2: > + utmp = (c->frequency < 320000000) ? 5 : 7; > + utmp2 = (c->frequency < 320000000) ? 10 : 12; > + break; > + default: /* DVB-C/QAM */ > + utmp = 7; > + utmp2 = 12; > + break; > + } > + ret = regmap_write(dev->regmap, R17_AGC31, (utmp << 4) | utmp2); > + if (ret) > + goto err; > + > + /* S2D */ > + switch (c->delivery_system) { > + case SYS_ATSC: > + case SYS_DVBT: > + case SYS_DVBT2: > + if (c->bandwidth_hz == 8000000) > + utmp = 0x04; > + else > + utmp = (c->frequency < 320000000) ? 0x04 : 0x02; > + break; > + default: /* DVB-C/QAM */ > + if (c->bandwidth_hz == 6000000) > + utmp = ((c->frequency > 172544000) && > + (c->frequency < 320000000)) ? 0x04 : 0x02; > + else /* 7.935 and 8 MHz */ > + utmp = ((c->frequency > 320000000) && > + (c->frequency < 600000000)) ? 0x02 : 0x04; > + break; > + } > + ret = regmap_write_bits(dev->regmap, R20_IR_MIX, 0x06, utmp); > + if (ret) > + goto err; > + > + switch (c->delivery_system) { > + case SYS_ATSC: > + case SYS_DVBT: > + case SYS_DVBT2: > + utmp = 0; > + break; > + default: /* DVB-C/QAM */ > + utmp = (c->frequency < 600000000) ? 0 : 3; > + break; > + } > + ret = regmap_write_bits(dev->regmap, R16_AGC25, 0x03, utmp); > + if (ret) > + goto err; > + > + utmp = 0x09; > + switch (c->delivery_system) { > + case SYS_ATSC: > + case SYS_DVBT: > + case SYS_DVBT2: > + if (c->bandwidth_hz == 8000000) > + utmp = 0x0c; > + break; > + default: /* DVB-C/QAM */ > + utmp = 0x0c; > + break; > + } > + ret = regmap_write_bits(dev->regmap, R0F_AGC14, 0x3f, utmp); > + if (ret) > + goto err; > + > + return 0; > +err: > + dev_dbg(&client->dev, "failed=%d", ret); > + return ret; > +} > + > +static int tda18250_pll_calc(struct dvb_frontend *fe, u8 *rdiv, > + u8 *ndiv, u8 *icp) > +{ > + struct i2c_client *client = fe->tuner_priv; > + struct tda18250_dev *dev = i2c_get_clientdata(client); > + struct dtv_frontend_properties *c = &fe->dtv_property_cache; > + int ret; > + unsigned int uval, exp, lopd, scale; > + unsigned long fvco; > + > + ret = regmap_read(dev->regmap, R34_MD1, &uval); > + if (ret) > + goto err; > + > + exp = (uval & 0x70) >> 4; > + if (exp > 5) > + exp = 0; > + lopd = 1 << (exp - 1); > + scale = uval & 0x0f; > + fvco = lopd * scale * ((c->frequency / 1000) + dev->if_frequency); > + > + switch (dev->xtal_freq) { > + case TDA18250_XTAL_FREQ_16MHZ: > + *rdiv = 1; > + *ndiv = 0; > + *icp = (fvco < 6622000) ? 0x05 : 0x02; > + break; > + case TDA18250_XTAL_FREQ_24MHZ: > + case TDA18250_XTAL_FREQ_25MHZ: > + *rdiv = 3; > + *ndiv = 1; > + *icp = (fvco < 6622000) ? 0x05 : 0x02; > + break; > + case TDA18250_XTAL_FREQ_27MHZ: > + if (fvco < 6643000) { > + *rdiv = 2; > + *ndiv = 0; > + *icp = 0x05; > + } else if (fvco < 6811000) { > + *rdiv = 2; > + *ndiv = 0; > + *icp = 0x06; > + } else { > + *rdiv = 3; > + *ndiv = 1; > + *icp = 0x02; > + } > + break; > + case TDA18250_XTAL_FREQ_30MHZ: > + *rdiv = 2; > + *ndiv = 0; > + *icp = (fvco < 6811000) ? 0x05 : 0x02; > + break; > + default: > + return -EINVAL; > + } > + > + dev_dbg(&client->dev, > + "lopd=%d scale=%u fvco=%lu, rdiv=%d ndiv=%d icp=%d", > + lopd, scale, fvco, *rdiv, *ndiv, *icp); > + return 0; > +err: > + return ret; > +} > + > +static int tda18250_set_params(struct dvb_frontend *fe) > +{ > + struct i2c_client *client = fe->tuner_priv; > + struct tda18250_dev *dev = i2c_get_clientdata(client); > + struct dtv_frontend_properties *c = &fe->dtv_property_cache; > + u32 if_khz; > + int ret; > + unsigned int i, j; > + u8 utmp; > + u8 buf[3]; > + > + #define REG 0 > + #define MASK 1 > + #define DVBT_6 2 > + #define DVBT_7 3 > + #define DVBT_8 4 > + #define DVBC_6 5 > + #define DVBC_8 6 > + #define ATSC 7 > + > + static const u8 delsys_params[][16] = { > + [REG] = { 0x22, 0x23, 0x24, 0x21, 0x0d, 0x0c, 0x0f, 0x14, > + 0x0e, 0x12, 0x58, 0x59, 0x1a, 0x19, 0x1e, 0x30 }, > + [MASK] = { 0x77, 0xff, 0xff, 0x87, 0xf0, 0x78, 0x07, 0xe0, > + 0x60, 0x0f, 0x60, 0x0f, 0x33, 0x30, 0x80, 0x06 }, > + [DVBT_6] = { 0x51, 0x03, 0x83, 0x82, 0x40, 0x48, 0x01, 0xe0, > + 0x60, 0x0f, 0x60, 0x05, 0x03, 0x10, 0x00, 0x04 }, > + [DVBT_7] = { 0x52, 0x03, 0x85, 0x82, 0x40, 0x48, 0x01, 0xe0, > + 0x60, 0x0f, 0x60, 0x05, 0x03, 0x10, 0x00, 0x04 }, > + [DVBT_8] = { 0x53, 0x03, 0x87, 0x82, 0x40, 0x48, 0x06, 0xe0, > + 0x60, 0x07, 0x60, 0x05, 0x03, 0x10, 0x00, 0x04 }, > + [DVBC_6] = { 0x32, 0x05, 0x86, 0x82, 0x50, 0x00, 0x06, 0x60, > + 0x40, 0x0e, 0x60, 0x05, 0x33, 0x10, 0x00, 0x04 }, > + [DVBC_8] = { 0x53, 0x03, 0x88, 0x82, 0x50, 0x00, 0x06, 0x60, > + 0x40, 0x0e, 0x60, 0x05, 0x33, 0x10, 0x00, 0x04 }, > + [ATSC] = { 0x51, 0x03, 0x83, 0x82, 0x40, 0x48, 0x01, 0xe0, > + 0x40, 0x0e, 0x60, 0x05, 0x03, 0x00, 0x80, 0x04 }, > + }; > + > + dev_dbg(&client->dev, > + "delivery_system=%d frequency=%u bandwidth_hz=%u", > + c->delivery_system, c->frequency, c->bandwidth_hz); > + > + > + switch (c->delivery_system) { > + case SYS_ATSC: > + j = ATSC; > + if_khz = dev->if_atsc; > + break; > + case SYS_DVBT: > + case SYS_DVBT2: > + if (c->bandwidth_hz == 0) { > + ret = -EINVAL; > + goto err; > + } else if (c->bandwidth_hz <= 6000000) { > + j = DVBT_6; > + if_khz = dev->if_dvbt_6; > + } else if (c->bandwidth_hz <= 7000000) { > + j = DVBT_7; > + if_khz = dev->if_dvbt_7; > + } else if (c->bandwidth_hz <= 8000000) { > + j = DVBT_8; > + if_khz = dev->if_dvbt_8; > + } else { > + ret = -EINVAL; > + goto err; > + } > + break; > + case SYS_DVBC_ANNEX_A: > + case SYS_DVBC_ANNEX_C: > + if (c->bandwidth_hz == 0) { > + ret = -EINVAL; > + goto err; > + } else if (c->bandwidth_hz <= 6000000) { > + j = DVBC_6; > + if_khz = dev->if_dvbc_6; > + } else if (c->bandwidth_hz <= 8000000) { > + j = DVBC_8; > + if_khz = dev->if_dvbc_8; > + } else { > + ret = -EINVAL; > + goto err; > + } > + break; > + default: > + ret = -EINVAL; > + dev_err(&client->dev, "unsupported delivery system=%d", > + c->delivery_system); > + goto err; > + } > + > + /* set delivery system dependent registers */ > + for (i = 0; i < 16; i++) { > + ret = regmap_write_bits(dev->regmap, delsys_params[REG][i], > + delsys_params[MASK][i], delsys_params[j][i]); > + if (ret) > + goto err; > + } > + > + /* set IF if needed */ > + if (dev->if_frequency != if_khz) { > + utmp = DIV_ROUND_CLOSEST(if_khz, 50); > + ret = regmap_write(dev->regmap, R26_IF, utmp); > + if (ret) > + goto err; > + dev->if_frequency = if_khz; > + dev_dbg(&client->dev, "set IF=%u kHz", if_khz); > + > + } > + > + ret = tda18250_set_agc(fe); > + if (ret) > + goto err; > + > + ret = regmap_write_bits(dev->regmap, R1A_AGCK, 0x03, 0x01); > + if (ret) > + goto err; > + > + ret = regmap_write_bits(dev->regmap, R14_AGC23, 0x40, 0x00); > + if (ret) > + goto err; > + > + /* set frequency */ > + buf[0] = ((c->frequency / 1000) >> 16) & 0xff; > + buf[1] = ((c->frequency / 1000) >> 8) & 0xff; > + buf[2] = ((c->frequency / 1000) >> 0) & 0xff; > + ret = regmap_bulk_write(dev->regmap, R27_RF1, buf, 3); > + if (ret) > + goto err; > + > + ret = regmap_write(dev->regmap, R0A_IRQ3, TDA18250_IRQ_TUNE); > + if (ret) > + goto err; > + > + /* initial tune */ > + ret = regmap_write(dev->regmap, R2A_MSM1, 0x01); > + if (ret) > + goto err; > + > + ret = regmap_write(dev->regmap, R2B_MSM2, 0x01); > + if (ret) > + goto err; > + > + ret = tda18250_wait_for_irq(fe, 500, 10, TDA18250_IRQ_TUNE); > + if (ret) > + goto err; > + > + /* calc ndiv and rdiv */ > + ret = tda18250_pll_calc(fe, &buf[0], &buf[1], &buf[2]); > + if (ret) > + goto err; > + > + ret = regmap_write_bits(dev->regmap, R4F_XTALFLX3, 0xe0, > + (buf[0] << 6) | (buf[1] << 5)); > + if (ret) > + goto err; > + > + /* clear IRQ */ > + ret = regmap_write(dev->regmap, R0A_IRQ3, TDA18250_IRQ_TUNE); > + if (ret) > + goto err; > + > + ret = regmap_write_bits(dev->regmap, R46_CPUMP, 0x07, 0x00); > + if (ret) > + goto err; > + > + ret = regmap_write_bits(dev->regmap, R39_SD5, 0x03, 0x00); > + if (ret) > + goto err; > + > + /* tune again */ > + ret = regmap_write(dev->regmap, R2A_MSM1, 0x01); /* tune */ > + if (ret) > + goto err; > + > + ret = regmap_write(dev->regmap, R2B_MSM2, 0x01); /* go */ > + if (ret) > + goto err; > + > + ret = tda18250_wait_for_irq(fe, 500, 10, TDA18250_IRQ_TUNE); > + if (ret) > + goto err; > + > + /* pll locking */ > + msleep(5); > + > + ret = regmap_write_bits(dev->regmap, R2B_MSM2, 0x04, 0x04); > + if (ret) > + goto err; > + > + msleep(20); > + > + /* restore AGCK */ > + ret = regmap_write_bits(dev->regmap, R1A_AGCK, 0x03, 0x03); > + if (ret) > + goto err; > + > + ret = regmap_write_bits(dev->regmap, R14_AGC23, 0x40, 0x40); > + if (ret) > + goto err; > + > + /* charge pump */ > + ret = regmap_write_bits(dev->regmap, R46_CPUMP, 0x07, buf[2]); > + > + return 0; > +err: > + return ret; > +} > + > +static int tda18250_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) > +{ > + struct i2c_client *client = fe->tuner_priv; > + struct tda18250_dev *dev = i2c_get_clientdata(client); > + > + *frequency = dev->if_frequency * 1000; > + return 0; > +} > + > +static int tda18250_sleep(struct dvb_frontend *fe) > +{ > + struct i2c_client *client = fe->tuner_priv; > + struct tda18250_dev *dev = i2c_get_clientdata(client); > + int ret; > + > + dev_dbg(&client->dev, "\n"); > + > + /* power down LNA */ > + ret = regmap_write_bits(dev->regmap, R0C_AGC11, 0x80, 0x00); > + if (ret) > + return ret; > + > + ret = tda18250_power_control(fe, TDA18250_POWER_STANDBY); > + return ret; > +} Do we know for sure if the IF_FREQUENCY is preserved after returning from a sleep? It might be a good idea to set `dev->if_frequency = 0` within `tda18250_sleep` to be sure that it gets set again on the next tune, but you may want to check the specification first, if its available. This is not a show-stopper -- We can merge this as-is and this can be handled in a follow-up patch. > + > +static const struct dvb_tuner_ops tda18250_ops = { > + .info = { > + .name = "NXP TDA18250", > + .frequency_min = 42000000, > + .frequency_max = 870000000, > + }, > + > + .init = tda18250_init, > + .set_params = tda18250_set_params, > + .get_if_frequency = tda18250_get_if_frequency, > + .sleep = tda18250_sleep, > +}; > + > +static int tda18250_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct tda18250_config *cfg = client->dev.platform_data; > + struct dvb_frontend *fe = cfg->fe; > + struct tda18250_dev *dev; > + int ret; > + unsigned char chip_id[3]; > + > + /* some registers are always read from HW */ > + static const struct regmap_range tda18250_yes_ranges[] = { > + regmap_reg_range(R05_POWER1, R0B_IRQ4), > + regmap_reg_range(R21_IF_AGC, R21_IF_AGC), > + regmap_reg_range(R2A_MSM1, R2B_MSM2), > + regmap_reg_range(R2F_RSSI1, R31_IRQ_CTRL), > + }; > + > + static const struct regmap_access_table tda18250_volatile_table = { > + .yes_ranges = tda18250_yes_ranges, > + .n_yes_ranges = ARRAY_SIZE(tda18250_yes_ranges), > + }; > + > + static const struct regmap_config tda18250_regmap_config = { > + .reg_bits = 8, > + .val_bits = 8, > + .max_register = TDA18250_NUM_REGS - 1, > + .volatile_table = &tda18250_volatile_table, > + }; > + > + dev = kzalloc(sizeof(*dev), GFP_KERNEL); > + if (!dev) { > + ret = -ENOMEM; > + goto err; > + } > + > + i2c_set_clientdata(client, dev); > + > + dev->fe = cfg->fe; > + dev->loopthrough = cfg->loopthrough; > + if (cfg->xtal_freq < TDA18250_XTAL_FREQ_MAX) { > + dev->xtal_freq = cfg->xtal_freq; > + } else { > + ret = -EINVAL; > + dev_err(&client->dev, "xtal_freq invalid=%d", cfg->xtal_freq); > + goto err_kfree; > + } > + dev->if_dvbt_6 = cfg->if_dvbt_6; > + dev->if_dvbt_7 = cfg->if_dvbt_7; > + dev->if_dvbt_8 = cfg->if_dvbt_8; > + dev->if_dvbc_6 = cfg->if_dvbc_6; > + dev->if_dvbc_8 = cfg->if_dvbc_8; > + dev->if_atsc = cfg->if_atsc; > + > + dev->if_frequency = 0; > + dev->warm = false; > + > + dev->regmap = devm_regmap_init_i2c(client, &tda18250_regmap_config); > + if (IS_ERR(dev->regmap)) { > + ret = PTR_ERR(dev->regmap); > + goto err_kfree; > + } > + > + /* read the three chip ID registers */ > + regmap_bulk_read(dev->regmap, R00_ID1, &chip_id, 3); > + dev_dbg(&client->dev, "chip_id=%02x:%02x:%02x", > + chip_id[0], chip_id[1], chip_id[2]); > + > + switch (chip_id[0]) { > + case 0xc7: > + dev->slave = false; > + break; > + case 0x47: > + dev->slave = true; > + break; > + default: > + ret = -ENODEV; > + goto err_kfree; > + } > + > + if (chip_id[1] != 0x4a) { > + ret = -ENODEV; > + goto err_kfree; > + } > + > + switch (chip_id[2]) { > + case 0x20: > + dev_info(&client->dev, > + "NXP TDA18250AHN/%s successfully identified", > + dev->slave ? "S" : "M"); > + break; > + case 0x21: > + dev_info(&client->dev, > + "NXP TDA18250BHN/%s successfully identified", > + dev->slave ? "S" : "M"); > + break; > + default: > + ret = -ENODEV; > + goto err_kfree; > + } > + > + fe->tuner_priv = client; > + memcpy(&fe->ops.tuner_ops, &tda18250_ops, > + sizeof(struct dvb_tuner_ops)); > + > + /* media controller */ > +#ifdef CONFIG_MEDIA_CONTROLLER > + if (cfg->mdev) { > + dev->mdev = cfg->mdev; > + > + dev->ent.name = KBUILD_MODNAME; > + dev->ent.function = MEDIA_ENT_F_TUNER; > + > + dev->pad[TUNER_PAD_RF_INPUT].flags = MEDIA_PAD_FL_SINK; > + dev->pad[TUNER_PAD_OUTPUT].flags = MEDIA_PAD_FL_SOURCE; > + dev->pad[TUNER_PAD_AUD_OUT].flags = MEDIA_PAD_FL_SOURCE; > + > + ret = media_entity_pads_init(&dev->ent, TUNER_NUM_PADS, > + &dev->pad[0]); > + > + if (ret) > + goto err_kfree; > + > + ret = media_device_register_entity(cfg->mdev, &dev->ent); > + if (ret) { > + media_entity_cleanup(&dev->ent); > + goto err_kfree; > + } > + } > +#endif > + > + /* put the tuner in standby */ > + tda18250_power_control(fe, TDA18250_POWER_STANDBY); > + > + return 0; > +err_kfree: > + kfree(dev); > +err: > + dev_dbg(&client->dev, "failed=%d", ret); > + return ret; > +} > + > +static int tda18250_remove(struct i2c_client *client) > +{ > + struct tda18250_dev *dev = i2c_get_clientdata(client); > + struct dvb_frontend *fe = dev->fe; > + > + dev_dbg(&client->dev, "\n"); > + > +#ifdef CONFIG_MEDIA_CONTROLLER_DVB > + if (dev->mdev) > + media_device_unregister_entity(&dev->ent); > +#endif > + > + memset(&fe->ops.tuner_ops, 0, sizeof(struct dvb_tuner_ops)); > + fe->tuner_priv = NULL; > + kfree(dev); > + > + return 0; > +} > + > +static const struct i2c_device_id tda18250_id_table[] = { > + {"tda18250", 0}, > + {} > +}; > +MODULE_DEVICE_TABLE(i2c, tda18250_id_table); > + > +static struct i2c_driver tda18250_driver = { > + .driver = { > + .name = "tda18250", > + }, > + .probe = tda18250_probe, > + .remove = tda18250_remove, > + .id_table = tda18250_id_table, > +}; > + > +module_i2c_driver(tda18250_driver); > + > +MODULE_DESCRIPTION("NXP TDA18250 silicon tuner driver"); > +MODULE_AUTHOR("Olli Salonen <olli.salonen@xxxxxx>"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/media/tuners/tda18250.h b/drivers/media/tuners/tda18250.h > new file mode 100644 > index 0000000..fb56906 > --- /dev/null > +++ b/drivers/media/tuners/tda18250.h > @@ -0,0 +1,51 @@ > +/* > + * NXP TDA18250BHN silicon tuner driver > + * > + * Copyright (C) 2017 Olli Salonen <olli.salonen@xxxxxx> > + * > + * 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. > + */ > + > +#ifndef TDA18250_H > +#define TDA18250_H > + > +#include <linux/kconfig.h> > +#include <media/media-device.h> > +#include "dvb_frontend.h" > + > +#define TDA18250_XTAL_FREQ_16MHZ 0 > +#define TDA18250_XTAL_FREQ_24MHZ 1 > +#define TDA18250_XTAL_FREQ_25MHZ 2 > +#define TDA18250_XTAL_FREQ_27MHZ 3 > +#define TDA18250_XTAL_FREQ_30MHZ 4 > +#define TDA18250_XTAL_FREQ_MAX 5 > + > +struct tda18250_config { > + u16 if_dvbt_6; > + u16 if_dvbt_7; > + u16 if_dvbt_8; > + u16 if_dvbc_6; > + u16 if_dvbc_8; > + u16 if_atsc; > + u8 xtal_freq; > + bool loopthrough; > + > + /* > + * frontend > + */ > + struct dvb_frontend *fe; > + > +#if defined(CONFIG_MEDIA_CONTROLLER) > + struct media_device *mdev; > +#endif > +}; > + > +#endif > diff --git a/drivers/media/tuners/tda18250_priv.h b/drivers/media/tuners/tda18250_priv.h > new file mode 100644 > index 0000000..feef991 > --- /dev/null > +++ b/drivers/media/tuners/tda18250_priv.h > @@ -0,0 +1,153 @@ > +/* > + * NXP TDA18250BHN silicon tuner driver > + * > + * Copyright (C) 2017 Olli Salonen <olli.salonen@xxxxxx> > + * > + * 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. > + */ > + > +#ifndef TDA18250_PRIV_H > +#define TDA18250_PRIV_H > + > +#include "tda18250.h" > +#include <media/v4l2-mc.h> > + > +#define R00_ID1 0x00 /* ID byte 1 */ > +#define R01_ID2 0x01 /* ID byte 2 */ > +#define R02_ID3 0x02 /* ID byte 3 */ > +#define R03_THERMO1 0x03 /* Thermo byte 1 */ > +#define R04_THERMO2 0x04 /* Thermo byte 2 */ > +#define R05_POWER1 0x05 /* Power byte 1 */ > +#define R06_POWER2 0x06 /* Power byte 2 */ > +#define R07_GPIO 0x07 /* GPIO */ > +#define R08_IRQ1 0x08 /* IRQ */ > +#define R09_IRQ2 0x09 /* IRQ */ > +#define R0A_IRQ3 0x0a /* IRQ */ > +#define R0B_IRQ4 0x0b /* IRQ */ > +#define R0C_AGC11 0x0c /* AGC1 byte 1 */ > +#define R0D_AGC12 0x0d /* AGC1 byte 2 */ > +#define R0E_AGC13 0x0e /* AGC1 byte 3 */ > +#define R0F_AGC14 0x0f /* AGC1 byte 4 */ > +#define R10_LT1 0x10 /* LT byte 1 */ > +#define R11_LT2 0x11 /* LT byte 2 */ > +#define R12_AGC21 0x12 /* AGC2 byte 1 */ > +#define R13_AGC22 0x13 /* AGC2 byte 2 */ > +#define R14_AGC23 0x14 /* AGC2 byte 3 */ > +#define R15_AGC24 0x15 /* AGC2 byte 4 */ > +#define R16_AGC25 0x16 /* AGC2 byte 5 */ > +#define R17_AGC31 0x17 /* AGC3 byte 1 */ > +#define R18_AGC32 0x18 /* AGC3 byte 2 */ > +#define R19_AGC33 0x19 /* AGC3 byte 3 */ > +#define R1A_AGCK 0x1a > +#define R1B_GAIN1 0x1b > +#define R1C_GAIN2 0x1c > +#define R1D_GAIN3 0x1d > +#define R1E_WI_FI 0x1e /* Wireless Filter */ > +#define R1F_RF_BPF 0x1f /* RF Band Pass Filter */ > +#define R20_IR_MIX 0x20 /* IR Mixer */ > +#define R21_IF_AGC 0x21 > +#define R22_IF1 0x22 /* IF byte 1 */ > +#define R23_IF2 0x23 /* IF byte 2 */ > +#define R24_IF3 0x24 /* IF byte 3 */ > +#define R25_REF 0x25 /* reference byte */ > +#define R26_IF 0x26 /* IF frequency */ > +#define R27_RF1 0x27 /* RF frequency byte 1 */ > +#define R28_RF2 0x28 /* RF frequency byte 2 */ > +#define R29_RF3 0x29 /* RF frequency byte 3 */ > +#define R2A_MSM1 0x2a > +#define R2B_MSM2 0x2b > +#define R2C_PS1 0x2c /* power saving mode byte 1 */ > +#define R2D_PS2 0x2d /* power saving mode byte 2 */ > +#define R2E_PS3 0x2e /* power saving mode byte 3 */ > +#define R2F_RSSI1 0x2f > +#define R30_RSSI2 0x30 > +#define R31_IRQ_CTRL 0x31 > +#define R32_DUMMY 0x32 > +#define R33_TEST 0x33 > +#define R34_MD1 0x34 > +#define R35_SD1 0x35 > +#define R36_SD2 0x36 > +#define R37_SD3 0x37 > +#define R38_SD4 0x38 > +#define R39_SD5 0x39 > +#define R3A_SD_TEST 0x3a > +#define R3B_REGU 0x3b > +#define R3C_RCCAL1 0x3c > +#define R3D_RCCAL2 0x3d > +#define R3E_IRCAL1 0x3e > +#define R3F_IRCAL2 0x3f > +#define R40_IRCAL3 0x40 > +#define R41_IRCAL4 0x41 > +#define R42_IRCAL5 0x42 > +#define R43_PD1 0x43 /* power down byte 1 */ > +#define R44_PD2 0x44 /* power down byte 2 */ > +#define R45_PD 0x45 /* power down */ > +#define R46_CPUMP 0x46 /* charge pump */ > +#define R47_LNAPOL 0x47 /* LNA polar casc */ > +#define R48_SMOOTH1 0x48 /* smooth test byte 1 */ > +#define R49_SMOOTH2 0x49 /* smooth test byte 2 */ > +#define R4A_SMOOTH3 0x4a /* smooth test byte 3 */ > +#define R4B_XTALOSC1 0x4b > +#define R4C_XTALOSC2 0x4c > +#define R4D_XTALFLX1 0x4d > +#define R4E_XTALFLX2 0x4e > +#define R4F_XTALFLX3 0x4f > +#define R50_XTALFLX4 0x50 > +#define R51_XTALFLX5 0x51 > +#define R52_IRLOOP0 0x52 > +#define R53_IRLOOP1 0x53 > +#define R54_IRLOOP2 0x54 > +#define R55_IRLOOP3 0x55 > +#define R56_IRLOOP4 0x56 > +#define R57_PLL_LOG 0x57 > +#define R58_AGC2_UP1 0x58 > +#define R59_AGC2_UP2 0x59 > +#define R5A_H3H5 0x5a > +#define R5B_AGC_AUTO 0x5b > +#define R5C_AGC_DEBUG 0x5c > + > +#define TDA18250_NUM_REGS 93 > + > +#define TDA18250_POWER_STANDBY 0 > +#define TDA18250_POWER_NORMAL 1 > + > +#define TDA18250_IRQ_CAL 0x81 > +#define TDA18250_IRQ_HW_INIT 0x82 > +#define TDA18250_IRQ_TUNE 0x88 > + > +struct tda18250_dev { > + struct mutex i2c_mutex; > + struct dvb_frontend *fe; > + struct i2c_adapter *i2c; > + struct regmap *regmap; > + u8 xtal_freq; > + /* IF in kHz */ > + u16 if_dvbt_6; > + u16 if_dvbt_7; > + u16 if_dvbt_8; > + u16 if_dvbc_6; > + u16 if_dvbc_8; > + u16 if_atsc; > + u16 if_frequency; > + bool slave; > + bool loopthrough; > + bool warm; > + u8 regs[TDA18250_NUM_REGS]; > + > +#if defined(CONFIG_MEDIA_CONTROLLER) > + struct media_device *mdev; > + struct media_entity ent; > + struct media_pad pad[TUNER_NUM_PADS]; > +#endif > + > +}; > + > +#endif > -- > 2.7.4 >