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 
918fdc25bfSMauro Carvalho Chehab static int tda665x_get_frequency(struct dvb_frontend *fe, u32 *frequency)
928fdc25bfSMauro Carvalho Chehab {
938fdc25bfSMauro Carvalho Chehab 	struct tda665x_state *state = fe->tuner_priv;
948fdc25bfSMauro Carvalho Chehab 
958fdc25bfSMauro Carvalho Chehab 	*frequency = state->frequency;
968fdc25bfSMauro Carvalho Chehab 
978fdc25bfSMauro Carvalho Chehab 	return 0;
988fdc25bfSMauro Carvalho Chehab }
998fdc25bfSMauro Carvalho Chehab 
1009a0bf528SMauro Carvalho Chehab static int tda665x_get_status(struct dvb_frontend *fe, u32 *status)
1019a0bf528SMauro Carvalho Chehab {
1029a0bf528SMauro Carvalho Chehab 	struct tda665x_state *state = fe->tuner_priv;
1039a0bf528SMauro Carvalho Chehab 	u8 result = 0;
1049a0bf528SMauro Carvalho Chehab 	int err = 0;
1059a0bf528SMauro Carvalho Chehab 
1069a0bf528SMauro Carvalho Chehab 	*status = 0;
1079a0bf528SMauro Carvalho Chehab 
1089a0bf528SMauro Carvalho Chehab 	err = tda665x_read(state, &result);
1099a0bf528SMauro Carvalho Chehab 	if (err < 0)
1109a0bf528SMauro Carvalho Chehab 		goto exit;
1119a0bf528SMauro Carvalho Chehab 
1129a0bf528SMauro Carvalho Chehab 	if ((result >> 6) & 0x01) {
1139a0bf528SMauro Carvalho Chehab 		printk(KERN_DEBUG "%s: Tuner Phase Locked\n", __func__);
1149a0bf528SMauro Carvalho Chehab 		*status = 1;
1159a0bf528SMauro Carvalho Chehab 	}
1169a0bf528SMauro Carvalho Chehab 
1179a0bf528SMauro Carvalho Chehab 	return err;
1189a0bf528SMauro Carvalho Chehab exit:
1199a0bf528SMauro Carvalho Chehab 	printk(KERN_ERR "%s: I/O Error\n", __func__);
1209a0bf528SMauro Carvalho Chehab 	return err;
1219a0bf528SMauro Carvalho Chehab }
1229a0bf528SMauro Carvalho Chehab 
1238e6c4be3SMauro Carvalho Chehab static int tda665x_set_frequency(struct dvb_frontend *fe,
1248e6c4be3SMauro Carvalho Chehab 				 u32 new_frequency)
1259a0bf528SMauro Carvalho Chehab {
1269a0bf528SMauro Carvalho Chehab 	struct tda665x_state *state = fe->tuner_priv;
1279a0bf528SMauro Carvalho Chehab 	const struct tda665x_config *config = state->config;
1289a0bf528SMauro Carvalho Chehab 	u32 frequency, status = 0;
1299a0bf528SMauro Carvalho Chehab 	u8 buf[4];
1309a0bf528SMauro Carvalho Chehab 	int err = 0;
1319a0bf528SMauro Carvalho Chehab 
1328e6c4be3SMauro Carvalho Chehab 	if ((new_frequency < config->frequency_max)
1338e6c4be3SMauro Carvalho Chehab 	    || (new_frequency > config->frequency_min)) {
1348e6c4be3SMauro Carvalho Chehab 		printk(KERN_ERR "%s: Frequency beyond limits, frequency=%d\n",
1358e6c4be3SMauro Carvalho Chehab 		       __func__, new_frequency);
1369a0bf528SMauro Carvalho Chehab 		return -EINVAL;
1379a0bf528SMauro Carvalho Chehab 	}
1389a0bf528SMauro Carvalho Chehab 
1398e6c4be3SMauro Carvalho Chehab 	frequency = new_frequency;
1408e6c4be3SMauro Carvalho Chehab 
1419a0bf528SMauro Carvalho Chehab 	frequency += config->frequency_offst;
1429a0bf528SMauro Carvalho Chehab 	frequency *= config->ref_multiplier;
1439a0bf528SMauro Carvalho Chehab 	frequency += config->ref_divider >> 1;
1449a0bf528SMauro Carvalho Chehab 	frequency /= config->ref_divider;
1459a0bf528SMauro Carvalho Chehab 
1469a0bf528SMauro Carvalho Chehab 	buf[0] = (u8) ((frequency & 0x7f00) >> 8);
1479a0bf528SMauro Carvalho Chehab 	buf[1] = (u8) (frequency & 0x00ff) >> 0;
1489a0bf528SMauro Carvalho Chehab 	buf[2] = 0x80 | 0x40 | 0x02;
1499a0bf528SMauro Carvalho Chehab 	buf[3] = 0x00;
1509a0bf528SMauro Carvalho Chehab 
1519a0bf528SMauro Carvalho Chehab 	/* restore frequency */
1528e6c4be3SMauro Carvalho Chehab 	frequency = new_frequency;
1539a0bf528SMauro Carvalho Chehab 
1549a0bf528SMauro Carvalho Chehab 	if (frequency < 153000000) {
1559a0bf528SMauro Carvalho Chehab 		/* VHF-L */
1569a0bf528SMauro Carvalho Chehab 		buf[3] |= 0x01; /* fc, Low Band, 47 - 153 MHz */
1579a0bf528SMauro Carvalho Chehab 		if (frequency < 68000000)
1589a0bf528SMauro Carvalho Chehab 			buf[3] |= 0x40; /* 83uA */
1599a0bf528SMauro Carvalho Chehab 		if (frequency < 1040000000)
1609a0bf528SMauro Carvalho Chehab 			buf[3] |= 0x60; /* 122uA */
1619a0bf528SMauro Carvalho Chehab 		if (frequency < 1250000000)
1629a0bf528SMauro Carvalho Chehab 			buf[3] |= 0x80; /* 163uA */
1639a0bf528SMauro Carvalho Chehab 		else
1649a0bf528SMauro Carvalho Chehab 			buf[3] |= 0xa0; /* 254uA */
1659a0bf528SMauro Carvalho Chehab 	} else if (frequency < 438000000) {
1669a0bf528SMauro Carvalho Chehab 		/* VHF-H */
1679a0bf528SMauro Carvalho Chehab 		buf[3] |= 0x02; /* fc, Mid Band, 153 - 438 MHz */
1689a0bf528SMauro Carvalho Chehab 		if (frequency < 230000000)
1699a0bf528SMauro Carvalho Chehab 			buf[3] |= 0x40;
1709a0bf528SMauro Carvalho Chehab 		if (frequency < 300000000)
1719a0bf528SMauro Carvalho Chehab 			buf[3] |= 0x60;
1729a0bf528SMauro Carvalho Chehab 		else
1739a0bf528SMauro Carvalho Chehab 			buf[3] |= 0x80;
1749a0bf528SMauro Carvalho Chehab 	} else {
1759a0bf528SMauro Carvalho Chehab 		/* UHF */
1769a0bf528SMauro Carvalho Chehab 		buf[3] |= 0x04; /* fc, High Band, 438 - 862 MHz */
1779a0bf528SMauro Carvalho Chehab 		if (frequency < 470000000)
1789a0bf528SMauro Carvalho Chehab 			buf[3] |= 0x60;
1799a0bf528SMauro Carvalho Chehab 		if (frequency < 526000000)
1809a0bf528SMauro Carvalho Chehab 			buf[3] |= 0x80;
1819a0bf528SMauro Carvalho Chehab 		else
1829a0bf528SMauro Carvalho Chehab 			buf[3] |= 0xa0;
1839a0bf528SMauro Carvalho Chehab 	}
1849a0bf528SMauro Carvalho Chehab 
1859a0bf528SMauro Carvalho Chehab 	/* Set params */
1869a0bf528SMauro Carvalho Chehab 	err = tda665x_write(state, buf, 5);
1879a0bf528SMauro Carvalho Chehab 	if (err < 0)
1889a0bf528SMauro Carvalho Chehab 		goto exit;
1899a0bf528SMauro Carvalho Chehab 
1909a0bf528SMauro Carvalho Chehab 	/* sleep for some time */
1919a0bf528SMauro Carvalho Chehab 	printk(KERN_DEBUG "%s: Waiting to Phase LOCK\n", __func__);
1929a0bf528SMauro Carvalho Chehab 	msleep(20);
1939a0bf528SMauro Carvalho Chehab 	/* check status */
1949a0bf528SMauro Carvalho Chehab 	err = tda665x_get_status(fe, &status);
1959a0bf528SMauro Carvalho Chehab 	if (err < 0)
1969a0bf528SMauro Carvalho Chehab 		goto exit;
1979a0bf528SMauro Carvalho Chehab 
1989a0bf528SMauro Carvalho Chehab 	if (status == 1) {
1998e6c4be3SMauro Carvalho Chehab 		printk(KERN_DEBUG "%s: Tuner Phase locked: status=%d\n",
2008e6c4be3SMauro Carvalho Chehab 		       __func__, status);
2019a0bf528SMauro Carvalho Chehab 		state->frequency = frequency; /* cache successful state */
2029a0bf528SMauro Carvalho Chehab 	} else {
2038e6c4be3SMauro Carvalho Chehab 		printk(KERN_ERR "%s: No Phase lock: status=%d\n",
2048e6c4be3SMauro Carvalho Chehab 		       __func__, status);
2059a0bf528SMauro Carvalho Chehab 	}
2069a0bf528SMauro Carvalho Chehab 
2079a0bf528SMauro Carvalho Chehab 	return 0;
2089a0bf528SMauro Carvalho Chehab exit:
2099a0bf528SMauro Carvalho Chehab 	printk(KERN_ERR "%s: I/O Error\n", __func__);
2109a0bf528SMauro Carvalho Chehab 	return err;
2119a0bf528SMauro Carvalho Chehab }
2129a0bf528SMauro Carvalho Chehab 
2138fdc25bfSMauro Carvalho Chehab static int tda665x_set_params(struct dvb_frontend *fe)
2148fdc25bfSMauro Carvalho Chehab {
2158fdc25bfSMauro Carvalho Chehab 	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
2168fdc25bfSMauro Carvalho Chehab 
2178fdc25bfSMauro Carvalho Chehab 	tda665x_set_frequency(fe, c->frequency);
2188fdc25bfSMauro Carvalho Chehab 
2198fdc25bfSMauro Carvalho Chehab 	return 0;
2208fdc25bfSMauro Carvalho Chehab }
2218fdc25bfSMauro Carvalho Chehab 
2228e6c4be3SMauro Carvalho Chehab static int tda665x_set_state(struct dvb_frontend *fe,
2238e6c4be3SMauro Carvalho Chehab 			     enum tuner_param param,
2248e6c4be3SMauro Carvalho Chehab 			     struct tuner_state *tstate)
2258e6c4be3SMauro Carvalho Chehab {
2268e6c4be3SMauro Carvalho Chehab 	if (param & DVBFE_TUNER_FREQUENCY)
2278e6c4be3SMauro Carvalho Chehab 		return  tda665x_set_frequency(fe, tstate->frequency);
2288e6c4be3SMauro Carvalho Chehab 
2298e6c4be3SMauro Carvalho Chehab 	printk(KERN_ERR "%s: Unknown parameter (param=%d)\n", __func__, param);
2308e6c4be3SMauro Carvalho Chehab 	return -EINVAL;
2318e6c4be3SMauro Carvalho Chehab }
2328e6c4be3SMauro Carvalho Chehab 
2339a0bf528SMauro Carvalho Chehab static int tda665x_release(struct dvb_frontend *fe)
2349a0bf528SMauro Carvalho Chehab {
2359a0bf528SMauro Carvalho Chehab 	struct tda665x_state *state = fe->tuner_priv;
2369a0bf528SMauro Carvalho Chehab 
2379a0bf528SMauro Carvalho Chehab 	fe->tuner_priv = NULL;
2389a0bf528SMauro Carvalho Chehab 	kfree(state);
2399a0bf528SMauro Carvalho Chehab 	return 0;
2409a0bf528SMauro Carvalho Chehab }
2419a0bf528SMauro Carvalho Chehab 
2429a0bf528SMauro Carvalho Chehab static struct dvb_tuner_ops tda665x_ops = {
2439a0bf528SMauro Carvalho Chehab 
2449a0bf528SMauro Carvalho Chehab 	.set_state	= tda665x_set_state,
2459a0bf528SMauro Carvalho Chehab 	.get_state	= tda665x_get_state,
2469a0bf528SMauro Carvalho Chehab 	.get_status	= tda665x_get_status,
2478fdc25bfSMauro Carvalho Chehab 	.set_params	= tda665x_set_params,
2488fdc25bfSMauro Carvalho Chehab 	.get_frequency	= tda665x_get_frequency,
2499a0bf528SMauro Carvalho Chehab 	.release	= tda665x_release
2509a0bf528SMauro Carvalho Chehab };
2519a0bf528SMauro Carvalho Chehab 
2529a0bf528SMauro Carvalho Chehab struct dvb_frontend *tda665x_attach(struct dvb_frontend *fe,
2539a0bf528SMauro Carvalho Chehab 				    const struct tda665x_config *config,
2549a0bf528SMauro Carvalho Chehab 				    struct i2c_adapter *i2c)
2559a0bf528SMauro Carvalho Chehab {
2569a0bf528SMauro Carvalho Chehab 	struct tda665x_state *state = NULL;
2579a0bf528SMauro Carvalho Chehab 	struct dvb_tuner_info *info;
2589a0bf528SMauro Carvalho Chehab 
2599a0bf528SMauro Carvalho Chehab 	state = kzalloc(sizeof(struct tda665x_state), GFP_KERNEL);
2600df10427SPeter Senna Tschudin 	if (!state)
2610df10427SPeter Senna Tschudin 		return NULL;
2629a0bf528SMauro Carvalho Chehab 
2639a0bf528SMauro Carvalho Chehab 	state->config		= config;
2649a0bf528SMauro Carvalho Chehab 	state->i2c		= i2c;
2659a0bf528SMauro Carvalho Chehab 	state->fe		= fe;
2669a0bf528SMauro Carvalho Chehab 	fe->tuner_priv		= state;
2679a0bf528SMauro Carvalho Chehab 	fe->ops.tuner_ops	= tda665x_ops;
2689a0bf528SMauro Carvalho Chehab 	info			 = &fe->ops.tuner_ops.info;
2699a0bf528SMauro Carvalho Chehab 
2709a0bf528SMauro Carvalho Chehab 	memcpy(info->name, config->name, sizeof(config->name));
2719a0bf528SMauro Carvalho Chehab 	info->frequency_min	= config->frequency_min;
2729a0bf528SMauro Carvalho Chehab 	info->frequency_max	= config->frequency_max;
2739a0bf528SMauro Carvalho Chehab 	info->frequency_step	= config->frequency_offst;
2749a0bf528SMauro Carvalho Chehab 
2759a0bf528SMauro Carvalho Chehab 	printk(KERN_DEBUG "%s: Attaching TDA665x (%s) tuner\n", __func__, info->name);
2769a0bf528SMauro Carvalho Chehab 
2779a0bf528SMauro Carvalho Chehab 	return fe;
2789a0bf528SMauro Carvalho Chehab }
2799a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(tda665x_attach);
2809a0bf528SMauro Carvalho Chehab 
2819a0bf528SMauro Carvalho Chehab MODULE_DESCRIPTION("TDA665x driver");
2829a0bf528SMauro Carvalho Chehab MODULE_AUTHOR("Manu Abraham");
2839a0bf528SMauro Carvalho Chehab MODULE_LICENSE("GPL");
284