18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci TDA665x tuner driver 48c2ecf20Sopenharmony_ci Copyright (C) Manu Abraham (abraham.manu@gmail.com) 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci*/ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/init.h> 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <media/dvb_frontend.h> 148c2ecf20Sopenharmony_ci#include "tda665x.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistruct tda665x_state { 178c2ecf20Sopenharmony_ci struct dvb_frontend *fe; 188c2ecf20Sopenharmony_ci struct i2c_adapter *i2c; 198c2ecf20Sopenharmony_ci const struct tda665x_config *config; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci u32 frequency; 228c2ecf20Sopenharmony_ci u32 bandwidth; 238c2ecf20Sopenharmony_ci}; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistatic int tda665x_read(struct tda665x_state *state, u8 *buf) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci const struct tda665x_config *config = state->config; 288c2ecf20Sopenharmony_ci int err = 0; 298c2ecf20Sopenharmony_ci struct i2c_msg msg = { .addr = config->addr, .flags = I2C_M_RD, .buf = buf, .len = 2 }; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci err = i2c_transfer(state->i2c, &msg, 1); 328c2ecf20Sopenharmony_ci if (err != 1) 338c2ecf20Sopenharmony_ci goto exit; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci return err; 368c2ecf20Sopenharmony_ciexit: 378c2ecf20Sopenharmony_ci printk(KERN_ERR "%s: I/O Error err=<%d>\n", __func__, err); 388c2ecf20Sopenharmony_ci return err; 398c2ecf20Sopenharmony_ci} 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic int tda665x_write(struct tda665x_state *state, u8 *buf, u8 length) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci const struct tda665x_config *config = state->config; 448c2ecf20Sopenharmony_ci int err = 0; 458c2ecf20Sopenharmony_ci struct i2c_msg msg = { .addr = config->addr, .flags = 0, .buf = buf, .len = length }; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci err = i2c_transfer(state->i2c, &msg, 1); 488c2ecf20Sopenharmony_ci if (err != 1) 498c2ecf20Sopenharmony_ci goto exit; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci return err; 528c2ecf20Sopenharmony_ciexit: 538c2ecf20Sopenharmony_ci printk(KERN_ERR "%s: I/O Error err=<%d>\n", __func__, err); 548c2ecf20Sopenharmony_ci return err; 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic int tda665x_get_frequency(struct dvb_frontend *fe, u32 *frequency) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci struct tda665x_state *state = fe->tuner_priv; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci *frequency = state->frequency; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci return 0; 648c2ecf20Sopenharmony_ci} 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic int tda665x_get_status(struct dvb_frontend *fe, u32 *status) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci struct tda665x_state *state = fe->tuner_priv; 698c2ecf20Sopenharmony_ci u8 result = 0; 708c2ecf20Sopenharmony_ci int err = 0; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci *status = 0; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci err = tda665x_read(state, &result); 758c2ecf20Sopenharmony_ci if (err < 0) 768c2ecf20Sopenharmony_ci goto exit; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci if ((result >> 6) & 0x01) { 798c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s: Tuner Phase Locked\n", __func__); 808c2ecf20Sopenharmony_ci *status = 1; 818c2ecf20Sopenharmony_ci } 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci return err; 848c2ecf20Sopenharmony_ciexit: 858c2ecf20Sopenharmony_ci printk(KERN_ERR "%s: I/O Error\n", __func__); 868c2ecf20Sopenharmony_ci return err; 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic int tda665x_set_frequency(struct dvb_frontend *fe, 908c2ecf20Sopenharmony_ci u32 new_frequency) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci struct tda665x_state *state = fe->tuner_priv; 938c2ecf20Sopenharmony_ci const struct tda665x_config *config = state->config; 948c2ecf20Sopenharmony_ci u32 frequency, status = 0; 958c2ecf20Sopenharmony_ci u8 buf[4]; 968c2ecf20Sopenharmony_ci int err = 0; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci if ((new_frequency < config->frequency_max) 998c2ecf20Sopenharmony_ci || (new_frequency > config->frequency_min)) { 1008c2ecf20Sopenharmony_ci printk(KERN_ERR "%s: Frequency beyond limits, frequency=%d\n", 1018c2ecf20Sopenharmony_ci __func__, new_frequency); 1028c2ecf20Sopenharmony_ci return -EINVAL; 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci frequency = new_frequency; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci frequency += config->frequency_offst; 1088c2ecf20Sopenharmony_ci frequency *= config->ref_multiplier; 1098c2ecf20Sopenharmony_ci frequency += config->ref_divider >> 1; 1108c2ecf20Sopenharmony_ci frequency /= config->ref_divider; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci buf[0] = (u8) ((frequency & 0x7f00) >> 8); 1138c2ecf20Sopenharmony_ci buf[1] = (u8) (frequency & 0x00ff) >> 0; 1148c2ecf20Sopenharmony_ci buf[2] = 0x80 | 0x40 | 0x02; 1158c2ecf20Sopenharmony_ci buf[3] = 0x00; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci /* restore frequency */ 1188c2ecf20Sopenharmony_ci frequency = new_frequency; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci if (frequency < 153000000) { 1218c2ecf20Sopenharmony_ci /* VHF-L */ 1228c2ecf20Sopenharmony_ci buf[3] |= 0x01; /* fc, Low Band, 47 - 153 MHz */ 1238c2ecf20Sopenharmony_ci if (frequency < 68000000) 1248c2ecf20Sopenharmony_ci buf[3] |= 0x40; /* 83uA */ 1258c2ecf20Sopenharmony_ci if (frequency < 1040000000) 1268c2ecf20Sopenharmony_ci buf[3] |= 0x60; /* 122uA */ 1278c2ecf20Sopenharmony_ci if (frequency < 1250000000) 1288c2ecf20Sopenharmony_ci buf[3] |= 0x80; /* 163uA */ 1298c2ecf20Sopenharmony_ci else 1308c2ecf20Sopenharmony_ci buf[3] |= 0xa0; /* 254uA */ 1318c2ecf20Sopenharmony_ci } else if (frequency < 438000000) { 1328c2ecf20Sopenharmony_ci /* VHF-H */ 1338c2ecf20Sopenharmony_ci buf[3] |= 0x02; /* fc, Mid Band, 153 - 438 MHz */ 1348c2ecf20Sopenharmony_ci if (frequency < 230000000) 1358c2ecf20Sopenharmony_ci buf[3] |= 0x40; 1368c2ecf20Sopenharmony_ci if (frequency < 300000000) 1378c2ecf20Sopenharmony_ci buf[3] |= 0x60; 1388c2ecf20Sopenharmony_ci else 1398c2ecf20Sopenharmony_ci buf[3] |= 0x80; 1408c2ecf20Sopenharmony_ci } else { 1418c2ecf20Sopenharmony_ci /* UHF */ 1428c2ecf20Sopenharmony_ci buf[3] |= 0x04; /* fc, High Band, 438 - 862 MHz */ 1438c2ecf20Sopenharmony_ci if (frequency < 470000000) 1448c2ecf20Sopenharmony_ci buf[3] |= 0x60; 1458c2ecf20Sopenharmony_ci if (frequency < 526000000) 1468c2ecf20Sopenharmony_ci buf[3] |= 0x80; 1478c2ecf20Sopenharmony_ci else 1488c2ecf20Sopenharmony_ci buf[3] |= 0xa0; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* Set params */ 1528c2ecf20Sopenharmony_ci err = tda665x_write(state, buf, 5); 1538c2ecf20Sopenharmony_ci if (err < 0) 1548c2ecf20Sopenharmony_ci goto exit; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci /* sleep for some time */ 1578c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s: Waiting to Phase LOCK\n", __func__); 1588c2ecf20Sopenharmony_ci msleep(20); 1598c2ecf20Sopenharmony_ci /* check status */ 1608c2ecf20Sopenharmony_ci err = tda665x_get_status(fe, &status); 1618c2ecf20Sopenharmony_ci if (err < 0) 1628c2ecf20Sopenharmony_ci goto exit; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci if (status == 1) { 1658c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s: Tuner Phase locked: status=%d\n", 1668c2ecf20Sopenharmony_ci __func__, status); 1678c2ecf20Sopenharmony_ci state->frequency = frequency; /* cache successful state */ 1688c2ecf20Sopenharmony_ci } else { 1698c2ecf20Sopenharmony_ci printk(KERN_ERR "%s: No Phase lock: status=%d\n", 1708c2ecf20Sopenharmony_ci __func__, status); 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci return 0; 1748c2ecf20Sopenharmony_ciexit: 1758c2ecf20Sopenharmony_ci printk(KERN_ERR "%s: I/O Error\n", __func__); 1768c2ecf20Sopenharmony_ci return err; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_cistatic int tda665x_set_params(struct dvb_frontend *fe) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci struct dtv_frontend_properties *c = &fe->dtv_property_cache; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci tda665x_set_frequency(fe, c->frequency); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci return 0; 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_cistatic void tda665x_release(struct dvb_frontend *fe) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci struct tda665x_state *state = fe->tuner_priv; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci fe->tuner_priv = NULL; 1938c2ecf20Sopenharmony_ci kfree(state); 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic const struct dvb_tuner_ops tda665x_ops = { 1978c2ecf20Sopenharmony_ci .get_status = tda665x_get_status, 1988c2ecf20Sopenharmony_ci .set_params = tda665x_set_params, 1998c2ecf20Sopenharmony_ci .get_frequency = tda665x_get_frequency, 2008c2ecf20Sopenharmony_ci .release = tda665x_release 2018c2ecf20Sopenharmony_ci}; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistruct dvb_frontend *tda665x_attach(struct dvb_frontend *fe, 2048c2ecf20Sopenharmony_ci const struct tda665x_config *config, 2058c2ecf20Sopenharmony_ci struct i2c_adapter *i2c) 2068c2ecf20Sopenharmony_ci{ 2078c2ecf20Sopenharmony_ci struct tda665x_state *state = NULL; 2088c2ecf20Sopenharmony_ci struct dvb_tuner_info *info; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci state = kzalloc(sizeof(struct tda665x_state), GFP_KERNEL); 2118c2ecf20Sopenharmony_ci if (!state) 2128c2ecf20Sopenharmony_ci return NULL; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci state->config = config; 2158c2ecf20Sopenharmony_ci state->i2c = i2c; 2168c2ecf20Sopenharmony_ci state->fe = fe; 2178c2ecf20Sopenharmony_ci fe->tuner_priv = state; 2188c2ecf20Sopenharmony_ci fe->ops.tuner_ops = tda665x_ops; 2198c2ecf20Sopenharmony_ci info = &fe->ops.tuner_ops.info; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci memcpy(info->name, config->name, sizeof(config->name)); 2228c2ecf20Sopenharmony_ci info->frequency_min_hz = config->frequency_min; 2238c2ecf20Sopenharmony_ci info->frequency_max_hz = config->frequency_max; 2248c2ecf20Sopenharmony_ci info->frequency_step_hz = config->frequency_offst; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s: Attaching TDA665x (%s) tuner\n", __func__, info->name); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci return fe; 2298c2ecf20Sopenharmony_ci} 2308c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(tda665x_attach); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TDA665x driver"); 2338c2ecf20Sopenharmony_ciMODULE_AUTHOR("Manu Abraham"); 2348c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 235