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
tda665x_read(struct tda665x_state * state,u8 * buf)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
tda665x_write(struct tda665x_state * state,u8 * buf,u8 length)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
tda665x_get_frequency(struct dvb_frontend * fe,u32 * frequency)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
tda665x_get_status(struct dvb_frontend * fe,u32 * status)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
tda665x_set_frequency(struct dvb_frontend * fe,u32 new_frequency)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
tda665x_set_params(struct dvb_frontend * fe)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
tda665x_release(struct dvb_frontend * fe)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
tda665x_attach(struct dvb_frontend * fe,const struct tda665x_config * config,struct i2c_adapter * i2c)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 }
230*86495af1SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(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