19a0bf528SMauro Carvalho Chehab /* 29a0bf528SMauro Carvalho Chehab TDA665x tuner driver 39a0bf528SMauro Carvalho Chehab Copyright (C) Manu Abraham (abraham.manu@gmail.com) 49a0bf528SMauro Carvalho Chehab 59a0bf528SMauro Carvalho Chehab This program is free software; you can redistribute it and/or modify 69a0bf528SMauro Carvalho Chehab it under the terms of the GNU General Public License as published by 79a0bf528SMauro Carvalho Chehab the Free Software Foundation; either version 2 of the License, or 89a0bf528SMauro Carvalho Chehab (at your option) any later version. 99a0bf528SMauro Carvalho Chehab 109a0bf528SMauro Carvalho Chehab This program is distributed in the hope that it will be useful, 119a0bf528SMauro Carvalho Chehab but WITHOUT ANY WARRANTY; without even the implied warranty of 129a0bf528SMauro Carvalho Chehab MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 139a0bf528SMauro Carvalho Chehab GNU General Public License for more details. 149a0bf528SMauro Carvalho Chehab 159a0bf528SMauro Carvalho Chehab You should have received a copy of the GNU General Public License 169a0bf528SMauro Carvalho Chehab along with this program; if not, write to the Free Software 179a0bf528SMauro Carvalho Chehab Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 189a0bf528SMauro Carvalho Chehab */ 199a0bf528SMauro Carvalho Chehab 209a0bf528SMauro Carvalho Chehab #include <linux/init.h> 219a0bf528SMauro Carvalho Chehab #include <linux/kernel.h> 229a0bf528SMauro Carvalho Chehab #include <linux/module.h> 239a0bf528SMauro Carvalho Chehab #include <linux/slab.h> 249a0bf528SMauro Carvalho Chehab 259a0bf528SMauro Carvalho Chehab #include "dvb_frontend.h" 269a0bf528SMauro Carvalho Chehab #include "tda665x.h" 279a0bf528SMauro Carvalho Chehab 289a0bf528SMauro Carvalho Chehab struct tda665x_state { 299a0bf528SMauro Carvalho Chehab struct dvb_frontend *fe; 309a0bf528SMauro Carvalho Chehab struct i2c_adapter *i2c; 319a0bf528SMauro Carvalho Chehab const struct tda665x_config *config; 329a0bf528SMauro Carvalho Chehab 339a0bf528SMauro Carvalho Chehab u32 frequency; 349a0bf528SMauro Carvalho Chehab u32 bandwidth; 359a0bf528SMauro Carvalho Chehab }; 369a0bf528SMauro Carvalho Chehab 379a0bf528SMauro Carvalho Chehab static int tda665x_read(struct tda665x_state *state, u8 *buf) 389a0bf528SMauro Carvalho Chehab { 399a0bf528SMauro Carvalho Chehab const struct tda665x_config *config = state->config; 409a0bf528SMauro Carvalho Chehab int err = 0; 419a0bf528SMauro Carvalho Chehab struct i2c_msg msg = { .addr = config->addr, .flags = I2C_M_RD, .buf = buf, .len = 2 }; 429a0bf528SMauro Carvalho Chehab 439a0bf528SMauro Carvalho Chehab err = i2c_transfer(state->i2c, &msg, 1); 449a0bf528SMauro Carvalho Chehab if (err != 1) 459a0bf528SMauro Carvalho Chehab goto exit; 469a0bf528SMauro Carvalho Chehab 479a0bf528SMauro Carvalho Chehab return err; 489a0bf528SMauro Carvalho Chehab exit: 499a0bf528SMauro Carvalho Chehab printk(KERN_ERR "%s: I/O Error err=<%d>\n", __func__, err); 509a0bf528SMauro Carvalho Chehab return err; 519a0bf528SMauro Carvalho Chehab } 529a0bf528SMauro Carvalho Chehab 539a0bf528SMauro Carvalho Chehab static int tda665x_write(struct tda665x_state *state, u8 *buf, u8 length) 549a0bf528SMauro Carvalho Chehab { 559a0bf528SMauro Carvalho Chehab const struct tda665x_config *config = state->config; 569a0bf528SMauro Carvalho Chehab int err = 0; 579a0bf528SMauro Carvalho Chehab struct i2c_msg msg = { .addr = config->addr, .flags = 0, .buf = buf, .len = length }; 589a0bf528SMauro Carvalho Chehab 599a0bf528SMauro Carvalho Chehab err = i2c_transfer(state->i2c, &msg, 1); 609a0bf528SMauro Carvalho Chehab if (err != 1) 619a0bf528SMauro Carvalho Chehab goto exit; 629a0bf528SMauro Carvalho Chehab 639a0bf528SMauro Carvalho Chehab return err; 649a0bf528SMauro Carvalho Chehab exit: 659a0bf528SMauro Carvalho Chehab printk(KERN_ERR "%s: I/O Error err=<%d>\n", __func__, err); 669a0bf528SMauro Carvalho Chehab return err; 679a0bf528SMauro Carvalho Chehab } 689a0bf528SMauro Carvalho Chehab 699a0bf528SMauro Carvalho Chehab static int tda665x_get_state(struct dvb_frontend *fe, 709a0bf528SMauro Carvalho Chehab enum tuner_param param, 719a0bf528SMauro Carvalho Chehab struct tuner_state *tstate) 729a0bf528SMauro Carvalho Chehab { 739a0bf528SMauro Carvalho Chehab struct tda665x_state *state = fe->tuner_priv; 749a0bf528SMauro Carvalho Chehab int err = 0; 759a0bf528SMauro Carvalho Chehab 769a0bf528SMauro Carvalho Chehab switch (param) { 779a0bf528SMauro Carvalho Chehab case DVBFE_TUNER_FREQUENCY: 789a0bf528SMauro Carvalho Chehab tstate->frequency = state->frequency; 799a0bf528SMauro Carvalho Chehab break; 809a0bf528SMauro Carvalho Chehab case DVBFE_TUNER_BANDWIDTH: 819a0bf528SMauro Carvalho Chehab break; 829a0bf528SMauro Carvalho Chehab default: 839a0bf528SMauro Carvalho Chehab printk(KERN_ERR "%s: Unknown parameter (param=%d)\n", __func__, param); 849a0bf528SMauro Carvalho Chehab err = -EINVAL; 859a0bf528SMauro Carvalho Chehab break; 869a0bf528SMauro Carvalho Chehab } 879a0bf528SMauro Carvalho Chehab 889a0bf528SMauro Carvalho Chehab return err; 899a0bf528SMauro Carvalho Chehab } 909a0bf528SMauro Carvalho Chehab 919a0bf528SMauro Carvalho Chehab static int tda665x_get_status(struct dvb_frontend *fe, u32 *status) 929a0bf528SMauro Carvalho Chehab { 939a0bf528SMauro Carvalho Chehab struct tda665x_state *state = fe->tuner_priv; 949a0bf528SMauro Carvalho Chehab u8 result = 0; 959a0bf528SMauro Carvalho Chehab int err = 0; 969a0bf528SMauro Carvalho Chehab 979a0bf528SMauro Carvalho Chehab *status = 0; 989a0bf528SMauro Carvalho Chehab 999a0bf528SMauro Carvalho Chehab err = tda665x_read(state, &result); 1009a0bf528SMauro Carvalho Chehab if (err < 0) 1019a0bf528SMauro Carvalho Chehab goto exit; 1029a0bf528SMauro Carvalho Chehab 1039a0bf528SMauro Carvalho Chehab if ((result >> 6) & 0x01) { 1049a0bf528SMauro Carvalho Chehab printk(KERN_DEBUG "%s: Tuner Phase Locked\n", __func__); 1059a0bf528SMauro Carvalho Chehab *status = 1; 1069a0bf528SMauro Carvalho Chehab } 1079a0bf528SMauro Carvalho Chehab 1089a0bf528SMauro Carvalho Chehab return err; 1099a0bf528SMauro Carvalho Chehab exit: 1109a0bf528SMauro Carvalho Chehab printk(KERN_ERR "%s: I/O Error\n", __func__); 1119a0bf528SMauro Carvalho Chehab return err; 1129a0bf528SMauro Carvalho Chehab } 1139a0bf528SMauro Carvalho Chehab 1148e6c4be3SMauro Carvalho Chehab static int tda665x_set_frequency(struct dvb_frontend *fe, 1158e6c4be3SMauro Carvalho Chehab u32 new_frequency) 1169a0bf528SMauro Carvalho Chehab { 1179a0bf528SMauro Carvalho Chehab struct tda665x_state *state = fe->tuner_priv; 1189a0bf528SMauro Carvalho Chehab const struct tda665x_config *config = state->config; 1199a0bf528SMauro Carvalho Chehab u32 frequency, status = 0; 1209a0bf528SMauro Carvalho Chehab u8 buf[4]; 1219a0bf528SMauro Carvalho Chehab int err = 0; 1229a0bf528SMauro Carvalho Chehab 1238e6c4be3SMauro Carvalho Chehab if ((new_frequency < config->frequency_max) 1248e6c4be3SMauro Carvalho Chehab || (new_frequency > config->frequency_min)) { 1258e6c4be3SMauro Carvalho Chehab printk(KERN_ERR "%s: Frequency beyond limits, frequency=%d\n", 1268e6c4be3SMauro Carvalho Chehab __func__, new_frequency); 1279a0bf528SMauro Carvalho Chehab return -EINVAL; 1289a0bf528SMauro Carvalho Chehab } 1299a0bf528SMauro Carvalho Chehab 1308e6c4be3SMauro Carvalho Chehab frequency = new_frequency; 1318e6c4be3SMauro Carvalho Chehab 1329a0bf528SMauro Carvalho Chehab frequency += config->frequency_offst; 1339a0bf528SMauro Carvalho Chehab frequency *= config->ref_multiplier; 1349a0bf528SMauro Carvalho Chehab frequency += config->ref_divider >> 1; 1359a0bf528SMauro Carvalho Chehab frequency /= config->ref_divider; 1369a0bf528SMauro Carvalho Chehab 1379a0bf528SMauro Carvalho Chehab buf[0] = (u8) ((frequency & 0x7f00) >> 8); 1389a0bf528SMauro Carvalho Chehab buf[1] = (u8) (frequency & 0x00ff) >> 0; 1399a0bf528SMauro Carvalho Chehab buf[2] = 0x80 | 0x40 | 0x02; 1409a0bf528SMauro Carvalho Chehab buf[3] = 0x00; 1419a0bf528SMauro Carvalho Chehab 1429a0bf528SMauro Carvalho Chehab /* restore frequency */ 1438e6c4be3SMauro Carvalho Chehab frequency = new_frequency; 1449a0bf528SMauro Carvalho Chehab 1459a0bf528SMauro Carvalho Chehab if (frequency < 153000000) { 1469a0bf528SMauro Carvalho Chehab /* VHF-L */ 1479a0bf528SMauro Carvalho Chehab buf[3] |= 0x01; /* fc, Low Band, 47 - 153 MHz */ 1489a0bf528SMauro Carvalho Chehab if (frequency < 68000000) 1499a0bf528SMauro Carvalho Chehab buf[3] |= 0x40; /* 83uA */ 1509a0bf528SMauro Carvalho Chehab if (frequency < 1040000000) 1519a0bf528SMauro Carvalho Chehab buf[3] |= 0x60; /* 122uA */ 1529a0bf528SMauro Carvalho Chehab if (frequency < 1250000000) 1539a0bf528SMauro Carvalho Chehab buf[3] |= 0x80; /* 163uA */ 1549a0bf528SMauro Carvalho Chehab else 1559a0bf528SMauro Carvalho Chehab buf[3] |= 0xa0; /* 254uA */ 1569a0bf528SMauro Carvalho Chehab } else if (frequency < 438000000) { 1579a0bf528SMauro Carvalho Chehab /* VHF-H */ 1589a0bf528SMauro Carvalho Chehab buf[3] |= 0x02; /* fc, Mid Band, 153 - 438 MHz */ 1599a0bf528SMauro Carvalho Chehab if (frequency < 230000000) 1609a0bf528SMauro Carvalho Chehab buf[3] |= 0x40; 1619a0bf528SMauro Carvalho Chehab if (frequency < 300000000) 1629a0bf528SMauro Carvalho Chehab buf[3] |= 0x60; 1639a0bf528SMauro Carvalho Chehab else 1649a0bf528SMauro Carvalho Chehab buf[3] |= 0x80; 1659a0bf528SMauro Carvalho Chehab } else { 1669a0bf528SMauro Carvalho Chehab /* UHF */ 1679a0bf528SMauro Carvalho Chehab buf[3] |= 0x04; /* fc, High Band, 438 - 862 MHz */ 1689a0bf528SMauro Carvalho Chehab if (frequency < 470000000) 1699a0bf528SMauro Carvalho Chehab buf[3] |= 0x60; 1709a0bf528SMauro Carvalho Chehab if (frequency < 526000000) 1719a0bf528SMauro Carvalho Chehab buf[3] |= 0x80; 1729a0bf528SMauro Carvalho Chehab else 1739a0bf528SMauro Carvalho Chehab buf[3] |= 0xa0; 1749a0bf528SMauro Carvalho Chehab } 1759a0bf528SMauro Carvalho Chehab 1769a0bf528SMauro Carvalho Chehab /* Set params */ 1779a0bf528SMauro Carvalho Chehab err = tda665x_write(state, buf, 5); 1789a0bf528SMauro Carvalho Chehab if (err < 0) 1799a0bf528SMauro Carvalho Chehab goto exit; 1809a0bf528SMauro Carvalho Chehab 1819a0bf528SMauro Carvalho Chehab /* sleep for some time */ 1829a0bf528SMauro Carvalho Chehab printk(KERN_DEBUG "%s: Waiting to Phase LOCK\n", __func__); 1839a0bf528SMauro Carvalho Chehab msleep(20); 1849a0bf528SMauro Carvalho Chehab /* check status */ 1859a0bf528SMauro Carvalho Chehab err = tda665x_get_status(fe, &status); 1869a0bf528SMauro Carvalho Chehab if (err < 0) 1879a0bf528SMauro Carvalho Chehab goto exit; 1889a0bf528SMauro Carvalho Chehab 1899a0bf528SMauro Carvalho Chehab if (status == 1) { 1908e6c4be3SMauro Carvalho Chehab printk(KERN_DEBUG "%s: Tuner Phase locked: status=%d\n", 1918e6c4be3SMauro Carvalho Chehab __func__, status); 1929a0bf528SMauro Carvalho Chehab state->frequency = frequency; /* cache successful state */ 1939a0bf528SMauro Carvalho Chehab } else { 1948e6c4be3SMauro Carvalho Chehab printk(KERN_ERR "%s: No Phase lock: status=%d\n", 1958e6c4be3SMauro Carvalho Chehab __func__, status); 1969a0bf528SMauro Carvalho Chehab } 1979a0bf528SMauro Carvalho Chehab 1989a0bf528SMauro Carvalho Chehab return 0; 1999a0bf528SMauro Carvalho Chehab exit: 2009a0bf528SMauro Carvalho Chehab printk(KERN_ERR "%s: I/O Error\n", __func__); 2019a0bf528SMauro Carvalho Chehab return err; 2029a0bf528SMauro Carvalho Chehab } 2039a0bf528SMauro Carvalho Chehab 2048e6c4be3SMauro Carvalho Chehab static int tda665x_set_state(struct dvb_frontend *fe, 2058e6c4be3SMauro Carvalho Chehab enum tuner_param param, 2068e6c4be3SMauro Carvalho Chehab struct tuner_state *tstate) 2078e6c4be3SMauro Carvalho Chehab { 2088e6c4be3SMauro Carvalho Chehab if (param & DVBFE_TUNER_FREQUENCY) 2098e6c4be3SMauro Carvalho Chehab return tda665x_set_frequency(fe, tstate->frequency); 2108e6c4be3SMauro Carvalho Chehab 2118e6c4be3SMauro Carvalho Chehab printk(KERN_ERR "%s: Unknown parameter (param=%d)\n", __func__, param); 2128e6c4be3SMauro Carvalho Chehab return -EINVAL; 2138e6c4be3SMauro Carvalho Chehab } 2148e6c4be3SMauro Carvalho Chehab 2159a0bf528SMauro Carvalho Chehab static int tda665x_release(struct dvb_frontend *fe) 2169a0bf528SMauro Carvalho Chehab { 2179a0bf528SMauro Carvalho Chehab struct tda665x_state *state = fe->tuner_priv; 2189a0bf528SMauro Carvalho Chehab 2199a0bf528SMauro Carvalho Chehab fe->tuner_priv = NULL; 2209a0bf528SMauro Carvalho Chehab kfree(state); 2219a0bf528SMauro Carvalho Chehab return 0; 2229a0bf528SMauro Carvalho Chehab } 2239a0bf528SMauro Carvalho Chehab 2249a0bf528SMauro Carvalho Chehab static struct dvb_tuner_ops tda665x_ops = { 2259a0bf528SMauro Carvalho Chehab 2269a0bf528SMauro Carvalho Chehab .set_state = tda665x_set_state, 2279a0bf528SMauro Carvalho Chehab .get_state = tda665x_get_state, 2289a0bf528SMauro Carvalho Chehab .get_status = tda665x_get_status, 2299a0bf528SMauro Carvalho Chehab .release = tda665x_release 2309a0bf528SMauro Carvalho Chehab }; 2319a0bf528SMauro Carvalho Chehab 2329a0bf528SMauro Carvalho Chehab struct dvb_frontend *tda665x_attach(struct dvb_frontend *fe, 2339a0bf528SMauro Carvalho Chehab const struct tda665x_config *config, 2349a0bf528SMauro Carvalho Chehab struct i2c_adapter *i2c) 2359a0bf528SMauro Carvalho Chehab { 2369a0bf528SMauro Carvalho Chehab struct tda665x_state *state = NULL; 2379a0bf528SMauro Carvalho Chehab struct dvb_tuner_info *info; 2389a0bf528SMauro Carvalho Chehab 2399a0bf528SMauro Carvalho Chehab state = kzalloc(sizeof(struct tda665x_state), GFP_KERNEL); 2400df10427SPeter Senna Tschudin if (!state) 2410df10427SPeter Senna Tschudin return NULL; 2429a0bf528SMauro Carvalho Chehab 2439a0bf528SMauro Carvalho Chehab state->config = config; 2449a0bf528SMauro Carvalho Chehab state->i2c = i2c; 2459a0bf528SMauro Carvalho Chehab state->fe = fe; 2469a0bf528SMauro Carvalho Chehab fe->tuner_priv = state; 2479a0bf528SMauro Carvalho Chehab fe->ops.tuner_ops = tda665x_ops; 2489a0bf528SMauro Carvalho Chehab info = &fe->ops.tuner_ops.info; 2499a0bf528SMauro Carvalho Chehab 2509a0bf528SMauro Carvalho Chehab memcpy(info->name, config->name, sizeof(config->name)); 2519a0bf528SMauro Carvalho Chehab info->frequency_min = config->frequency_min; 2529a0bf528SMauro Carvalho Chehab info->frequency_max = config->frequency_max; 2539a0bf528SMauro Carvalho Chehab info->frequency_step = config->frequency_offst; 2549a0bf528SMauro Carvalho Chehab 2559a0bf528SMauro Carvalho Chehab printk(KERN_DEBUG "%s: Attaching TDA665x (%s) tuner\n", __func__, info->name); 2569a0bf528SMauro Carvalho Chehab 2579a0bf528SMauro Carvalho Chehab return fe; 2589a0bf528SMauro Carvalho Chehab } 2599a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(tda665x_attach); 2609a0bf528SMauro Carvalho Chehab 2619a0bf528SMauro Carvalho Chehab MODULE_DESCRIPTION("TDA665x driver"); 2629a0bf528SMauro Carvalho Chehab MODULE_AUTHOR("Manu Abraham"); 2639a0bf528SMauro Carvalho Chehab MODULE_LICENSE("GPL"); 264