18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci TDA8261 8PSK/QPSK tuner driver 48c2ecf20Sopenharmony_ci Copyright (C) Manu Abraham (abraham.manu@gmail.com) 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci*/ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/init.h> 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <media/dvb_frontend.h> 158c2ecf20Sopenharmony_ci#include "tda8261.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistruct tda8261_state { 188c2ecf20Sopenharmony_ci struct dvb_frontend *fe; 198c2ecf20Sopenharmony_ci struct i2c_adapter *i2c; 208c2ecf20Sopenharmony_ci const struct tda8261_config *config; 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci /* state cache */ 238c2ecf20Sopenharmony_ci u32 frequency; 248c2ecf20Sopenharmony_ci u32 bandwidth; 258c2ecf20Sopenharmony_ci}; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic int tda8261_read(struct tda8261_state *state, u8 *buf) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci const struct tda8261_config *config = state->config; 308c2ecf20Sopenharmony_ci int err = 0; 318c2ecf20Sopenharmony_ci struct i2c_msg msg = { .addr = config->addr, .flags = I2C_M_RD,.buf = buf, .len = 1 }; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) 348c2ecf20Sopenharmony_ci pr_err("%s: read error, err=%d\n", __func__, err); 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci return err; 378c2ecf20Sopenharmony_ci} 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic int tda8261_write(struct tda8261_state *state, u8 *buf) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci const struct tda8261_config *config = state->config; 428c2ecf20Sopenharmony_ci int err = 0; 438c2ecf20Sopenharmony_ci struct i2c_msg msg = { .addr = config->addr, .flags = 0, .buf = buf, .len = 4 }; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) 468c2ecf20Sopenharmony_ci pr_err("%s: write error, err=%d\n", __func__, err); 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci return err; 498c2ecf20Sopenharmony_ci} 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic int tda8261_get_status(struct dvb_frontend *fe, u32 *status) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci struct tda8261_state *state = fe->tuner_priv; 548c2ecf20Sopenharmony_ci u8 result = 0; 558c2ecf20Sopenharmony_ci int err = 0; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci *status = 0; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci if ((err = tda8261_read(state, &result)) < 0) { 608c2ecf20Sopenharmony_ci pr_err("%s: I/O Error\n", __func__); 618c2ecf20Sopenharmony_ci return err; 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci if ((result >> 6) & 0x01) { 648c2ecf20Sopenharmony_ci pr_debug("%s: Tuner Phase Locked\n", __func__); 658c2ecf20Sopenharmony_ci *status = 1; 668c2ecf20Sopenharmony_ci } 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci return err; 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic const u32 div_tab[] = { 2000, 1000, 500, 250, 125 }; /* kHz */ 728c2ecf20Sopenharmony_cistatic const u8 ref_div[] = { 0x00, 0x01, 0x02, 0x05, 0x07 }; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic int tda8261_get_frequency(struct dvb_frontend *fe, u32 *frequency) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci struct tda8261_state *state = fe->tuner_priv; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci *frequency = state->frequency; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci return 0; 818c2ecf20Sopenharmony_ci} 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic int tda8261_set_params(struct dvb_frontend *fe) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci struct dtv_frontend_properties *c = &fe->dtv_property_cache; 868c2ecf20Sopenharmony_ci struct tda8261_state *state = fe->tuner_priv; 878c2ecf20Sopenharmony_ci const struct tda8261_config *config = state->config; 888c2ecf20Sopenharmony_ci u32 frequency, N, status = 0; 898c2ecf20Sopenharmony_ci u8 buf[4]; 908c2ecf20Sopenharmony_ci int err = 0; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci /* 938c2ecf20Sopenharmony_ci * N = Max VCO Frequency / Channel Spacing 948c2ecf20Sopenharmony_ci * Max VCO Frequency = VCO frequency + (channel spacing - 1) 958c2ecf20Sopenharmony_ci * (to account for half channel spacing on either side) 968c2ecf20Sopenharmony_ci */ 978c2ecf20Sopenharmony_ci frequency = c->frequency; 988c2ecf20Sopenharmony_ci if ((frequency < 950000) || (frequency > 2150000)) { 998c2ecf20Sopenharmony_ci pr_warn("%s: Frequency beyond limits, frequency=%d\n", 1008c2ecf20Sopenharmony_ci __func__, frequency); 1018c2ecf20Sopenharmony_ci return -EINVAL; 1028c2ecf20Sopenharmony_ci } 1038c2ecf20Sopenharmony_ci N = (frequency + (div_tab[config->step_size] - 1)) / div_tab[config->step_size]; 1048c2ecf20Sopenharmony_ci pr_debug("%s: Step size=%d, Divider=%d, PG=0x%02x (%d)\n", 1058c2ecf20Sopenharmony_ci __func__, config->step_size, div_tab[config->step_size], N, N); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci buf[0] = (N >> 8) & 0xff; 1088c2ecf20Sopenharmony_ci buf[1] = N & 0xff; 1098c2ecf20Sopenharmony_ci buf[2] = (0x01 << 7) | ((ref_div[config->step_size] & 0x07) << 1); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (frequency < 1450000) 1128c2ecf20Sopenharmony_ci buf[3] = 0x00; 1138c2ecf20Sopenharmony_ci else if (frequency < 2000000) 1148c2ecf20Sopenharmony_ci buf[3] = 0x40; 1158c2ecf20Sopenharmony_ci else if (frequency < 2150000) 1168c2ecf20Sopenharmony_ci buf[3] = 0x80; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci /* Set params */ 1198c2ecf20Sopenharmony_ci err = tda8261_write(state, buf); 1208c2ecf20Sopenharmony_ci if (err < 0) { 1218c2ecf20Sopenharmony_ci pr_err("%s: I/O Error\n", __func__); 1228c2ecf20Sopenharmony_ci return err; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci /* sleep for some time */ 1258c2ecf20Sopenharmony_ci pr_debug("%s: Waiting to Phase LOCK\n", __func__); 1268c2ecf20Sopenharmony_ci msleep(20); 1278c2ecf20Sopenharmony_ci /* check status */ 1288c2ecf20Sopenharmony_ci if ((err = tda8261_get_status(fe, &status)) < 0) { 1298c2ecf20Sopenharmony_ci pr_err("%s: I/O Error\n", __func__); 1308c2ecf20Sopenharmony_ci return err; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci if (status == 1) { 1338c2ecf20Sopenharmony_ci pr_debug("%s: Tuner Phase locked: status=%d\n", __func__, 1348c2ecf20Sopenharmony_ci status); 1358c2ecf20Sopenharmony_ci state->frequency = frequency; /* cache successful state */ 1368c2ecf20Sopenharmony_ci } else { 1378c2ecf20Sopenharmony_ci pr_debug("%s: No Phase lock: status=%d\n", __func__, status); 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci return 0; 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic void tda8261_release(struct dvb_frontend *fe) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci struct tda8261_state *state = fe->tuner_priv; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci fe->tuner_priv = NULL; 1488c2ecf20Sopenharmony_ci kfree(state); 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic const struct dvb_tuner_ops tda8261_ops = { 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci .info = { 1548c2ecf20Sopenharmony_ci .name = "TDA8261", 1558c2ecf20Sopenharmony_ci .frequency_min_hz = 950 * MHz, 1568c2ecf20Sopenharmony_ci .frequency_max_hz = 2150 * MHz, 1578c2ecf20Sopenharmony_ci }, 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci .set_params = tda8261_set_params, 1608c2ecf20Sopenharmony_ci .get_frequency = tda8261_get_frequency, 1618c2ecf20Sopenharmony_ci .get_status = tda8261_get_status, 1628c2ecf20Sopenharmony_ci .release = tda8261_release 1638c2ecf20Sopenharmony_ci}; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistruct dvb_frontend *tda8261_attach(struct dvb_frontend *fe, 1668c2ecf20Sopenharmony_ci const struct tda8261_config *config, 1678c2ecf20Sopenharmony_ci struct i2c_adapter *i2c) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci struct tda8261_state *state = NULL; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci if ((state = kzalloc(sizeof (struct tda8261_state), GFP_KERNEL)) == NULL) 1728c2ecf20Sopenharmony_ci goto exit; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci state->config = config; 1758c2ecf20Sopenharmony_ci state->i2c = i2c; 1768c2ecf20Sopenharmony_ci state->fe = fe; 1778c2ecf20Sopenharmony_ci fe->tuner_priv = state; 1788c2ecf20Sopenharmony_ci fe->ops.tuner_ops = tda8261_ops; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci fe->ops.tuner_ops.info.frequency_step_hz = div_tab[config->step_size] * kHz; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci pr_info("%s: Attaching TDA8261 8PSK/QPSK tuner\n", __func__); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci return fe; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ciexit: 1878c2ecf20Sopenharmony_ci kfree(state); 1888c2ecf20Sopenharmony_ci return NULL; 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(tda8261_attach); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ciMODULE_AUTHOR("Manu Abraham"); 1948c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TDA8261 8PSK/QPSK Tuner"); 1958c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 196