174ba9207SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 29a0bf528SMauro Carvalho Chehab /* 39a0bf528SMauro Carvalho Chehab TDA665x tuner driver 49a0bf528SMauro Carvalho Chehab Copyright (C) Manu Abraham (abraham.manu@gmail.com) 59a0bf528SMauro Carvalho Chehab 69a0bf528SMauro Carvalho Chehab */ 79a0bf528SMauro Carvalho Chehab 89a0bf528SMauro Carvalho Chehab #include <linux/init.h> 99a0bf528SMauro Carvalho Chehab #include <linux/kernel.h> 109a0bf528SMauro Carvalho Chehab #include <linux/module.h> 119a0bf528SMauro Carvalho Chehab #include <linux/slab.h> 129a0bf528SMauro Carvalho Chehab 13fada1935SMauro Carvalho Chehab #include <media/dvb_frontend.h> 149a0bf528SMauro Carvalho Chehab #include "tda665x.h" 159a0bf528SMauro Carvalho Chehab 169a0bf528SMauro Carvalho Chehab struct tda665x_state { 179a0bf528SMauro Carvalho Chehab struct dvb_frontend *fe; 189a0bf528SMauro Carvalho Chehab struct i2c_adapter *i2c; 199a0bf528SMauro Carvalho Chehab const struct tda665x_config *config; 209a0bf528SMauro Carvalho Chehab 219a0bf528SMauro Carvalho Chehab u32 frequency; 229a0bf528SMauro Carvalho Chehab u32 bandwidth; 239a0bf528SMauro Carvalho Chehab }; 249a0bf528SMauro Carvalho Chehab 259a0bf528SMauro Carvalho Chehab static int tda665x_read(struct tda665x_state *state, u8 *buf) 269a0bf528SMauro Carvalho Chehab { 279a0bf528SMauro Carvalho Chehab const struct tda665x_config *config = state->config; 289a0bf528SMauro Carvalho Chehab int err = 0; 299a0bf528SMauro Carvalho Chehab struct i2c_msg msg = { .addr = config->addr, .flags = I2C_M_RD, .buf = buf, .len = 2 }; 309a0bf528SMauro Carvalho Chehab 319a0bf528SMauro Carvalho Chehab err = i2c_transfer(state->i2c, &msg, 1); 329a0bf528SMauro Carvalho Chehab if (err != 1) 339a0bf528SMauro Carvalho Chehab goto exit; 349a0bf528SMauro Carvalho Chehab 359a0bf528SMauro Carvalho Chehab return err; 369a0bf528SMauro Carvalho Chehab exit: 379a0bf528SMauro Carvalho Chehab printk(KERN_ERR "%s: I/O Error err=<%d>\n", __func__, err); 389a0bf528SMauro Carvalho Chehab return err; 399a0bf528SMauro Carvalho Chehab } 409a0bf528SMauro Carvalho Chehab 419a0bf528SMauro Carvalho Chehab static int tda665x_write(struct tda665x_state *state, u8 *buf, u8 length) 429a0bf528SMauro Carvalho Chehab { 439a0bf528SMauro Carvalho Chehab const struct tda665x_config *config = state->config; 449a0bf528SMauro Carvalho Chehab int err = 0; 459a0bf528SMauro Carvalho Chehab struct i2c_msg msg = { .addr = config->addr, .flags = 0, .buf = buf, .len = length }; 469a0bf528SMauro Carvalho Chehab 479a0bf528SMauro Carvalho Chehab err = i2c_transfer(state->i2c, &msg, 1); 489a0bf528SMauro Carvalho Chehab if (err != 1) 499a0bf528SMauro Carvalho Chehab goto exit; 509a0bf528SMauro Carvalho Chehab 519a0bf528SMauro Carvalho Chehab return err; 529a0bf528SMauro Carvalho Chehab exit: 539a0bf528SMauro Carvalho Chehab printk(KERN_ERR "%s: I/O Error err=<%d>\n", __func__, err); 549a0bf528SMauro Carvalho Chehab return err; 559a0bf528SMauro Carvalho Chehab } 569a0bf528SMauro Carvalho Chehab 578fdc25bfSMauro Carvalho Chehab static int tda665x_get_frequency(struct dvb_frontend *fe, u32 *frequency) 588fdc25bfSMauro Carvalho Chehab { 598fdc25bfSMauro Carvalho Chehab struct tda665x_state *state = fe->tuner_priv; 608fdc25bfSMauro Carvalho Chehab 618fdc25bfSMauro Carvalho Chehab *frequency = state->frequency; 628fdc25bfSMauro Carvalho Chehab 638fdc25bfSMauro Carvalho Chehab return 0; 648fdc25bfSMauro Carvalho Chehab } 658fdc25bfSMauro Carvalho Chehab 669a0bf528SMauro Carvalho Chehab static int tda665x_get_status(struct dvb_frontend *fe, u32 *status) 679a0bf528SMauro Carvalho Chehab { 689a0bf528SMauro Carvalho Chehab struct tda665x_state *state = fe->tuner_priv; 699a0bf528SMauro Carvalho Chehab u8 result = 0; 709a0bf528SMauro Carvalho Chehab int err = 0; 719a0bf528SMauro Carvalho Chehab 729a0bf528SMauro Carvalho Chehab *status = 0; 739a0bf528SMauro Carvalho Chehab 749a0bf528SMauro Carvalho Chehab err = tda665x_read(state, &result); 759a0bf528SMauro Carvalho Chehab if (err < 0) 769a0bf528SMauro Carvalho Chehab goto exit; 779a0bf528SMauro Carvalho Chehab 789a0bf528SMauro Carvalho Chehab if ((result >> 6) & 0x01) { 799a0bf528SMauro Carvalho Chehab printk(KERN_DEBUG "%s: Tuner Phase Locked\n", __func__); 809a0bf528SMauro Carvalho Chehab *status = 1; 819a0bf528SMauro Carvalho Chehab } 829a0bf528SMauro Carvalho Chehab 839a0bf528SMauro Carvalho Chehab return err; 849a0bf528SMauro Carvalho Chehab exit: 859a0bf528SMauro Carvalho Chehab printk(KERN_ERR "%s: I/O Error\n", __func__); 869a0bf528SMauro Carvalho Chehab return err; 879a0bf528SMauro Carvalho Chehab } 889a0bf528SMauro Carvalho Chehab 898e6c4be3SMauro Carvalho Chehab static int tda665x_set_frequency(struct dvb_frontend *fe, 908e6c4be3SMauro Carvalho Chehab u32 new_frequency) 919a0bf528SMauro Carvalho Chehab { 929a0bf528SMauro Carvalho Chehab struct tda665x_state *state = fe->tuner_priv; 939a0bf528SMauro Carvalho Chehab const struct tda665x_config *config = state->config; 949a0bf528SMauro Carvalho Chehab u32 frequency, status = 0; 959a0bf528SMauro Carvalho Chehab u8 buf[4]; 969a0bf528SMauro Carvalho Chehab int err = 0; 979a0bf528SMauro Carvalho Chehab 988e6c4be3SMauro Carvalho Chehab if ((new_frequency < config->frequency_max) 998e6c4be3SMauro Carvalho Chehab || (new_frequency > config->frequency_min)) { 1008e6c4be3SMauro Carvalho Chehab printk(KERN_ERR "%s: Frequency beyond limits, frequency=%d\n", 1018e6c4be3SMauro Carvalho Chehab __func__, new_frequency); 1029a0bf528SMauro Carvalho Chehab return -EINVAL; 1039a0bf528SMauro Carvalho Chehab } 1049a0bf528SMauro Carvalho Chehab 1058e6c4be3SMauro Carvalho Chehab frequency = new_frequency; 1068e6c4be3SMauro Carvalho Chehab 1079a0bf528SMauro Carvalho Chehab frequency += config->frequency_offst; 1089a0bf528SMauro Carvalho Chehab frequency *= config->ref_multiplier; 1099a0bf528SMauro Carvalho Chehab frequency += config->ref_divider >> 1; 1109a0bf528SMauro Carvalho Chehab frequency /= config->ref_divider; 1119a0bf528SMauro Carvalho Chehab 1129a0bf528SMauro Carvalho Chehab buf[0] = (u8) ((frequency & 0x7f00) >> 8); 1139a0bf528SMauro Carvalho Chehab buf[1] = (u8) (frequency & 0x00ff) >> 0; 1149a0bf528SMauro Carvalho Chehab buf[2] = 0x80 | 0x40 | 0x02; 1159a0bf528SMauro Carvalho Chehab buf[3] = 0x00; 1169a0bf528SMauro Carvalho Chehab 1179a0bf528SMauro Carvalho Chehab /* restore frequency */ 1188e6c4be3SMauro Carvalho Chehab frequency = new_frequency; 1199a0bf528SMauro Carvalho Chehab 1209a0bf528SMauro Carvalho Chehab if (frequency < 153000000) { 1219a0bf528SMauro Carvalho Chehab /* VHF-L */ 1229a0bf528SMauro Carvalho Chehab buf[3] |= 0x01; /* fc, Low Band, 47 - 153 MHz */ 1239a0bf528SMauro Carvalho Chehab if (frequency < 68000000) 1249a0bf528SMauro Carvalho Chehab buf[3] |= 0x40; /* 83uA */ 1259a0bf528SMauro Carvalho Chehab if (frequency < 1040000000) 1269a0bf528SMauro Carvalho Chehab buf[3] |= 0x60; /* 122uA */ 1279a0bf528SMauro Carvalho Chehab if (frequency < 1250000000) 1289a0bf528SMauro Carvalho Chehab buf[3] |= 0x80; /* 163uA */ 1299a0bf528SMauro Carvalho Chehab else 1309a0bf528SMauro Carvalho Chehab buf[3] |= 0xa0; /* 254uA */ 1319a0bf528SMauro Carvalho Chehab } else if (frequency < 438000000) { 1329a0bf528SMauro Carvalho Chehab /* VHF-H */ 1339a0bf528SMauro Carvalho Chehab buf[3] |= 0x02; /* fc, Mid Band, 153 - 438 MHz */ 1349a0bf528SMauro Carvalho Chehab if (frequency < 230000000) 1359a0bf528SMauro Carvalho Chehab buf[3] |= 0x40; 1369a0bf528SMauro Carvalho Chehab if (frequency < 300000000) 1379a0bf528SMauro Carvalho Chehab buf[3] |= 0x60; 1389a0bf528SMauro Carvalho Chehab else 1399a0bf528SMauro Carvalho Chehab buf[3] |= 0x80; 1409a0bf528SMauro Carvalho Chehab } else { 1419a0bf528SMauro Carvalho Chehab /* UHF */ 1429a0bf528SMauro Carvalho Chehab buf[3] |= 0x04; /* fc, High Band, 438 - 862 MHz */ 1439a0bf528SMauro Carvalho Chehab if (frequency < 470000000) 1449a0bf528SMauro Carvalho Chehab buf[3] |= 0x60; 1459a0bf528SMauro Carvalho Chehab if (frequency < 526000000) 1469a0bf528SMauro Carvalho Chehab buf[3] |= 0x80; 1479a0bf528SMauro Carvalho Chehab else 1489a0bf528SMauro Carvalho Chehab buf[3] |= 0xa0; 1499a0bf528SMauro Carvalho Chehab } 1509a0bf528SMauro Carvalho Chehab 1519a0bf528SMauro Carvalho Chehab /* Set params */ 1529a0bf528SMauro Carvalho Chehab err = tda665x_write(state, buf, 5); 1539a0bf528SMauro Carvalho Chehab if (err < 0) 1549a0bf528SMauro Carvalho Chehab goto exit; 1559a0bf528SMauro Carvalho Chehab 1569a0bf528SMauro Carvalho Chehab /* sleep for some time */ 1579a0bf528SMauro Carvalho Chehab printk(KERN_DEBUG "%s: Waiting to Phase LOCK\n", __func__); 1589a0bf528SMauro Carvalho Chehab msleep(20); 1599a0bf528SMauro Carvalho Chehab /* check status */ 1609a0bf528SMauro Carvalho Chehab err = tda665x_get_status(fe, &status); 1619a0bf528SMauro Carvalho Chehab if (err < 0) 1629a0bf528SMauro Carvalho Chehab goto exit; 1639a0bf528SMauro Carvalho Chehab 1649a0bf528SMauro Carvalho Chehab if (status == 1) { 1658e6c4be3SMauro Carvalho Chehab printk(KERN_DEBUG "%s: Tuner Phase locked: status=%d\n", 1668e6c4be3SMauro Carvalho Chehab __func__, status); 1679a0bf528SMauro Carvalho Chehab state->frequency = frequency; /* cache successful state */ 1689a0bf528SMauro Carvalho Chehab } else { 1698e6c4be3SMauro Carvalho Chehab printk(KERN_ERR "%s: No Phase lock: status=%d\n", 1708e6c4be3SMauro Carvalho Chehab __func__, status); 1719a0bf528SMauro Carvalho Chehab } 1729a0bf528SMauro Carvalho Chehab 1739a0bf528SMauro Carvalho Chehab return 0; 1749a0bf528SMauro Carvalho Chehab exit: 1759a0bf528SMauro Carvalho Chehab printk(KERN_ERR "%s: I/O Error\n", __func__); 1769a0bf528SMauro Carvalho Chehab return err; 1779a0bf528SMauro Carvalho Chehab } 1789a0bf528SMauro Carvalho Chehab 1798fdc25bfSMauro Carvalho Chehab static int tda665x_set_params(struct dvb_frontend *fe) 1808fdc25bfSMauro Carvalho Chehab { 1818fdc25bfSMauro Carvalho Chehab struct dtv_frontend_properties *c = &fe->dtv_property_cache; 1828fdc25bfSMauro Carvalho Chehab 1838fdc25bfSMauro Carvalho Chehab tda665x_set_frequency(fe, c->frequency); 1848fdc25bfSMauro Carvalho Chehab 1858fdc25bfSMauro Carvalho Chehab return 0; 1868fdc25bfSMauro Carvalho Chehab } 1878fdc25bfSMauro Carvalho Chehab 188f2709c20SMauro Carvalho Chehab static void tda665x_release(struct dvb_frontend *fe) 189f2709c20SMauro Carvalho Chehab { 190f2709c20SMauro Carvalho Chehab struct tda665x_state *state = fe->tuner_priv; 191f2709c20SMauro Carvalho Chehab 192f2709c20SMauro Carvalho Chehab fe->tuner_priv = NULL; 193f2709c20SMauro Carvalho Chehab kfree(state); 194f2709c20SMauro Carvalho Chehab } 195f2709c20SMauro Carvalho Chehab 19614c4bf3cSJulia Lawall static const struct dvb_tuner_ops tda665x_ops = { 1979a0bf528SMauro Carvalho Chehab .get_status = tda665x_get_status, 1988fdc25bfSMauro Carvalho Chehab .set_params = tda665x_set_params, 1998fdc25bfSMauro Carvalho Chehab .get_frequency = tda665x_get_frequency, 200f2709c20SMauro Carvalho Chehab .release = tda665x_release 2019a0bf528SMauro Carvalho Chehab }; 2029a0bf528SMauro Carvalho Chehab 2039a0bf528SMauro Carvalho Chehab struct dvb_frontend *tda665x_attach(struct dvb_frontend *fe, 2049a0bf528SMauro Carvalho Chehab const struct tda665x_config *config, 2059a0bf528SMauro Carvalho Chehab struct i2c_adapter *i2c) 2069a0bf528SMauro Carvalho Chehab { 2079a0bf528SMauro Carvalho Chehab struct tda665x_state *state = NULL; 2089a0bf528SMauro Carvalho Chehab struct dvb_tuner_info *info; 2099a0bf528SMauro Carvalho Chehab 2109a0bf528SMauro Carvalho Chehab state = kzalloc(sizeof(struct tda665x_state), GFP_KERNEL); 2110df10427SPeter Senna Tschudin if (!state) 2120df10427SPeter Senna Tschudin return NULL; 2139a0bf528SMauro Carvalho Chehab 2149a0bf528SMauro Carvalho Chehab state->config = config; 2159a0bf528SMauro Carvalho Chehab state->i2c = i2c; 2169a0bf528SMauro Carvalho Chehab state->fe = fe; 2179a0bf528SMauro Carvalho Chehab fe->tuner_priv = state; 2189a0bf528SMauro Carvalho Chehab fe->ops.tuner_ops = tda665x_ops; 2199a0bf528SMauro Carvalho Chehab info = &fe->ops.tuner_ops.info; 2209a0bf528SMauro Carvalho Chehab 2219a0bf528SMauro Carvalho Chehab memcpy(info->name, config->name, sizeof(config->name)); 222a3f90c75SMauro Carvalho Chehab info->frequency_min_hz = config->frequency_min; 223a3f90c75SMauro Carvalho Chehab info->frequency_max_hz = config->frequency_max; 224a3f90c75SMauro Carvalho Chehab info->frequency_step_hz = config->frequency_offst; 2259a0bf528SMauro Carvalho Chehab 2269a0bf528SMauro Carvalho Chehab printk(KERN_DEBUG "%s: Attaching TDA665x (%s) tuner\n", __func__, info->name); 2279a0bf528SMauro Carvalho Chehab 2289a0bf528SMauro Carvalho Chehab return fe; 2299a0bf528SMauro Carvalho Chehab } 2309a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(tda665x_attach); 2319a0bf528SMauro Carvalho Chehab 2329a0bf528SMauro Carvalho Chehab MODULE_DESCRIPTION("TDA665x driver"); 2339a0bf528SMauro Carvalho Chehab MODULE_AUTHOR("Manu Abraham"); 2349a0bf528SMauro Carvalho Chehab MODULE_LICENSE("GPL"); 235