174ba9207SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
29a0bf528SMauro Carvalho Chehab /*
39a0bf528SMauro Carvalho Chehab     Auvitek AU8522 QAM/8VSB demodulator driver
49a0bf528SMauro Carvalho Chehab 
59a0bf528SMauro Carvalho Chehab     Copyright (C) 2008 Steven Toth <stoth@linuxtv.org>
69a0bf528SMauro Carvalho Chehab     Copyright (C) 2008 Devin Heitmueller <dheitmueller@linuxtv.org>
79a0bf528SMauro Carvalho Chehab     Copyright (C) 2005-2008 Auvitek International, Ltd.
89a0bf528SMauro Carvalho Chehab     Copyright (C) 2012 Michael Krufky <mkrufky@linuxtv.org>
99a0bf528SMauro Carvalho Chehab 
109a0bf528SMauro Carvalho Chehab 
119a0bf528SMauro Carvalho Chehab */
129a0bf528SMauro Carvalho Chehab 
139a0bf528SMauro Carvalho Chehab #include <linux/i2c.h>
14fada1935SMauro Carvalho Chehab #include <media/dvb_frontend.h>
159a0bf528SMauro Carvalho Chehab #include "au8522_priv.h"
169a0bf528SMauro Carvalho Chehab 
179a0bf528SMauro Carvalho Chehab static int debug;
189a0bf528SMauro Carvalho Chehab 
199a0bf528SMauro Carvalho Chehab #define dprintk(arg...)\
209a0bf528SMauro Carvalho Chehab   do { if (debug)\
219a0bf528SMauro Carvalho Chehab 	 printk(arg);\
229a0bf528SMauro Carvalho Chehab   } while (0)
239a0bf528SMauro Carvalho Chehab 
249a0bf528SMauro Carvalho Chehab /* Despite the name "hybrid_tuner", the framework works just as well for
259a0bf528SMauro Carvalho Chehab    hybrid demodulators as well... */
269a0bf528SMauro Carvalho Chehab static LIST_HEAD(hybrid_tuner_instance_list);
279a0bf528SMauro Carvalho Chehab static DEFINE_MUTEX(au8522_list_mutex);
289a0bf528SMauro Carvalho Chehab 
299a0bf528SMauro Carvalho Chehab /* 16 bit registers, 8 bit values */
au8522_writereg(struct au8522_state * state,u16 reg,u8 data)309a0bf528SMauro Carvalho Chehab int au8522_writereg(struct au8522_state *state, u16 reg, u8 data)
319a0bf528SMauro Carvalho Chehab {
329a0bf528SMauro Carvalho Chehab 	int ret;
339a0bf528SMauro Carvalho Chehab 	u8 buf[] = { (reg >> 8) | 0x80, reg & 0xff, data };
349a0bf528SMauro Carvalho Chehab 
35aa37763fSMauro Carvalho Chehab 	struct i2c_msg msg = { .addr = state->config.demod_address,
369a0bf528SMauro Carvalho Chehab 			       .flags = 0, .buf = buf, .len = 3 };
379a0bf528SMauro Carvalho Chehab 
389a0bf528SMauro Carvalho Chehab 	ret = i2c_transfer(state->i2c, &msg, 1);
399a0bf528SMauro Carvalho Chehab 
409a0bf528SMauro Carvalho Chehab 	if (ret != 1)
414bd69e7bSMauro Carvalho Chehab 		printk("%s: writereg error (reg == 0x%02x, val == 0x%04x, ret == %i)\n",
424bd69e7bSMauro Carvalho Chehab 		       __func__, reg, data, ret);
439a0bf528SMauro Carvalho Chehab 
449a0bf528SMauro Carvalho Chehab 	return (ret != 1) ? -1 : 0;
459a0bf528SMauro Carvalho Chehab }
469a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(au8522_writereg);
479a0bf528SMauro Carvalho Chehab 
au8522_readreg(struct au8522_state * state,u16 reg)489a0bf528SMauro Carvalho Chehab u8 au8522_readreg(struct au8522_state *state, u16 reg)
499a0bf528SMauro Carvalho Chehab {
509a0bf528SMauro Carvalho Chehab 	int ret;
519a0bf528SMauro Carvalho Chehab 	u8 b0[] = { (reg >> 8) | 0x40, reg & 0xff };
529a0bf528SMauro Carvalho Chehab 	u8 b1[] = { 0 };
539a0bf528SMauro Carvalho Chehab 
549a0bf528SMauro Carvalho Chehab 	struct i2c_msg msg[] = {
55aa37763fSMauro Carvalho Chehab 		{ .addr = state->config.demod_address, .flags = 0,
569a0bf528SMauro Carvalho Chehab 		  .buf = b0, .len = 2 },
57aa37763fSMauro Carvalho Chehab 		{ .addr = state->config.demod_address, .flags = I2C_M_RD,
589a0bf528SMauro Carvalho Chehab 		  .buf = b1, .len = 1 } };
599a0bf528SMauro Carvalho Chehab 
609a0bf528SMauro Carvalho Chehab 	ret = i2c_transfer(state->i2c, msg, 2);
619a0bf528SMauro Carvalho Chehab 
629a0bf528SMauro Carvalho Chehab 	if (ret != 2)
639a0bf528SMauro Carvalho Chehab 		printk(KERN_ERR "%s: readreg error (ret == %i)\n",
649a0bf528SMauro Carvalho Chehab 		       __func__, ret);
659a0bf528SMauro Carvalho Chehab 	return b1[0];
669a0bf528SMauro Carvalho Chehab }
679a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(au8522_readreg);
689a0bf528SMauro Carvalho Chehab 
au8522_i2c_gate_ctrl(struct dvb_frontend * fe,int enable)699a0bf528SMauro Carvalho Chehab int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
709a0bf528SMauro Carvalho Chehab {
719a0bf528SMauro Carvalho Chehab 	struct au8522_state *state = fe->demodulator_priv;
729a0bf528SMauro Carvalho Chehab 
739a0bf528SMauro Carvalho Chehab 	dprintk("%s(%d)\n", __func__, enable);
749a0bf528SMauro Carvalho Chehab 
759a0bf528SMauro Carvalho Chehab 	if (state->operational_mode == AU8522_ANALOG_MODE) {
769a0bf528SMauro Carvalho Chehab 		/* We're being asked to manage the gate even though we're
779a0bf528SMauro Carvalho Chehab 		   not in digital mode.  This can occur if we get switched
789a0bf528SMauro Carvalho Chehab 		   over to analog mode before the dvb_frontend kernel thread
799a0bf528SMauro Carvalho Chehab 		   has completely shutdown */
809a0bf528SMauro Carvalho Chehab 		return 0;
819a0bf528SMauro Carvalho Chehab 	}
829a0bf528SMauro Carvalho Chehab 
839a0bf528SMauro Carvalho Chehab 	if (enable)
849a0bf528SMauro Carvalho Chehab 		return au8522_writereg(state, 0x106, 1);
859a0bf528SMauro Carvalho Chehab 	else
869a0bf528SMauro Carvalho Chehab 		return au8522_writereg(state, 0x106, 0);
879a0bf528SMauro Carvalho Chehab }
889a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(au8522_i2c_gate_ctrl);
899a0bf528SMauro Carvalho Chehab 
au8522_analog_i2c_gate_ctrl(struct dvb_frontend * fe,int enable)909a0bf528SMauro Carvalho Chehab int au8522_analog_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
919a0bf528SMauro Carvalho Chehab {
929a0bf528SMauro Carvalho Chehab 	struct au8522_state *state = fe->demodulator_priv;
939a0bf528SMauro Carvalho Chehab 
949a0bf528SMauro Carvalho Chehab 	dprintk("%s(%d)\n", __func__, enable);
959a0bf528SMauro Carvalho Chehab 
969a0bf528SMauro Carvalho Chehab 	if (enable)
979a0bf528SMauro Carvalho Chehab 		return au8522_writereg(state, 0x106, 1);
989a0bf528SMauro Carvalho Chehab 	else
999a0bf528SMauro Carvalho Chehab 		return au8522_writereg(state, 0x106, 0);
1009a0bf528SMauro Carvalho Chehab }
1019a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(au8522_analog_i2c_gate_ctrl);
1029a0bf528SMauro Carvalho Chehab 
1039a0bf528SMauro Carvalho Chehab /* Reset the demod hardware and reset all of the configuration registers
1049a0bf528SMauro Carvalho Chehab    to a default state. */
au8522_get_state(struct au8522_state ** state,struct i2c_adapter * i2c,u8 client_address)1059a0bf528SMauro Carvalho Chehab int au8522_get_state(struct au8522_state **state, struct i2c_adapter *i2c,
1069a0bf528SMauro Carvalho Chehab 		     u8 client_address)
1079a0bf528SMauro Carvalho Chehab {
1089a0bf528SMauro Carvalho Chehab 	int ret;
1099a0bf528SMauro Carvalho Chehab 
1109a0bf528SMauro Carvalho Chehab 	mutex_lock(&au8522_list_mutex);
1119a0bf528SMauro Carvalho Chehab 	ret = hybrid_tuner_request_state(struct au8522_state, (*state),
1129a0bf528SMauro Carvalho Chehab 					 hybrid_tuner_instance_list,
1139a0bf528SMauro Carvalho Chehab 					 i2c, client_address, "au8522");
1149a0bf528SMauro Carvalho Chehab 	mutex_unlock(&au8522_list_mutex);
1159a0bf528SMauro Carvalho Chehab 
1169a0bf528SMauro Carvalho Chehab 	return ret;
1179a0bf528SMauro Carvalho Chehab }
1189a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(au8522_get_state);
1199a0bf528SMauro Carvalho Chehab 
au8522_release_state(struct au8522_state * state)1209a0bf528SMauro Carvalho Chehab void au8522_release_state(struct au8522_state *state)
1219a0bf528SMauro Carvalho Chehab {
1229a0bf528SMauro Carvalho Chehab 	mutex_lock(&au8522_list_mutex);
1239a0bf528SMauro Carvalho Chehab 	if (state != NULL)
1249a0bf528SMauro Carvalho Chehab 		hybrid_tuner_release_state(state);
1259a0bf528SMauro Carvalho Chehab 	mutex_unlock(&au8522_list_mutex);
1269a0bf528SMauro Carvalho Chehab }
1279a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(au8522_release_state);
1289a0bf528SMauro Carvalho Chehab 
au8522_led_gpio_enable(struct au8522_state * state,int onoff)1299a0bf528SMauro Carvalho Chehab static int au8522_led_gpio_enable(struct au8522_state *state, int onoff)
1309a0bf528SMauro Carvalho Chehab {
131aa37763fSMauro Carvalho Chehab 	struct au8522_led_config *led_config = state->config.led_cfg;
1329a0bf528SMauro Carvalho Chehab 	u8 val;
1339a0bf528SMauro Carvalho Chehab 
1349a0bf528SMauro Carvalho Chehab 	/* bail out if we can't control an LED */
1359a0bf528SMauro Carvalho Chehab 	if (!led_config || !led_config->gpio_output ||
1369a0bf528SMauro Carvalho Chehab 	    !led_config->gpio_output_enable || !led_config->gpio_output_disable)
1379a0bf528SMauro Carvalho Chehab 		return 0;
1389a0bf528SMauro Carvalho Chehab 
1399a0bf528SMauro Carvalho Chehab 	val = au8522_readreg(state, 0x4000 |
1409a0bf528SMauro Carvalho Chehab 			     (led_config->gpio_output & ~0xc000));
1419a0bf528SMauro Carvalho Chehab 	if (onoff) {
1429a0bf528SMauro Carvalho Chehab 		/* enable GPIO output */
1439a0bf528SMauro Carvalho Chehab 		val &= ~((led_config->gpio_output_enable >> 8) & 0xff);
1449a0bf528SMauro Carvalho Chehab 		val |=  (led_config->gpio_output_enable & 0xff);
1459a0bf528SMauro Carvalho Chehab 	} else {
1469a0bf528SMauro Carvalho Chehab 		/* disable GPIO output */
1479a0bf528SMauro Carvalho Chehab 		val &= ~((led_config->gpio_output_disable >> 8) & 0xff);
1489a0bf528SMauro Carvalho Chehab 		val |=  (led_config->gpio_output_disable & 0xff);
1499a0bf528SMauro Carvalho Chehab 	}
1509a0bf528SMauro Carvalho Chehab 	return au8522_writereg(state, 0x8000 |
1519a0bf528SMauro Carvalho Chehab 			       (led_config->gpio_output & ~0xc000), val);
1529a0bf528SMauro Carvalho Chehab }
1539a0bf528SMauro Carvalho Chehab 
1549a0bf528SMauro Carvalho Chehab /* led = 0 | off
1559a0bf528SMauro Carvalho Chehab  * led = 1 | signal ok
1569a0bf528SMauro Carvalho Chehab  * led = 2 | signal strong
1579a0bf528SMauro Carvalho Chehab  * led < 0 | only light led if leds are currently off
1589a0bf528SMauro Carvalho Chehab  */
au8522_led_ctrl(struct au8522_state * state,int led)1599a0bf528SMauro Carvalho Chehab int au8522_led_ctrl(struct au8522_state *state, int led)
1609a0bf528SMauro Carvalho Chehab {
161aa37763fSMauro Carvalho Chehab 	struct au8522_led_config *led_config = state->config.led_cfg;
1629a0bf528SMauro Carvalho Chehab 	int i, ret = 0;
1639a0bf528SMauro Carvalho Chehab 
1649a0bf528SMauro Carvalho Chehab 	/* bail out if we can't control an LED */
1659a0bf528SMauro Carvalho Chehab 	if (!led_config || !led_config->gpio_leds ||
1669a0bf528SMauro Carvalho Chehab 	    !led_config->num_led_states || !led_config->led_states)
1679a0bf528SMauro Carvalho Chehab 		return 0;
1689a0bf528SMauro Carvalho Chehab 
1699a0bf528SMauro Carvalho Chehab 	if (led < 0) {
1709a0bf528SMauro Carvalho Chehab 		/* if LED is already lit, then leave it as-is */
1719a0bf528SMauro Carvalho Chehab 		if (state->led_state)
1729a0bf528SMauro Carvalho Chehab 			return 0;
1739a0bf528SMauro Carvalho Chehab 		else
1749a0bf528SMauro Carvalho Chehab 			led *= -1;
1759a0bf528SMauro Carvalho Chehab 	}
1769a0bf528SMauro Carvalho Chehab 
1779a0bf528SMauro Carvalho Chehab 	/* toggle LED if changing state */
1789a0bf528SMauro Carvalho Chehab 	if (state->led_state != led) {
1799a0bf528SMauro Carvalho Chehab 		u8 val;
1809a0bf528SMauro Carvalho Chehab 
1819a0bf528SMauro Carvalho Chehab 		dprintk("%s: %d\n", __func__, led);
1829a0bf528SMauro Carvalho Chehab 
1839a0bf528SMauro Carvalho Chehab 		au8522_led_gpio_enable(state, 1);
1849a0bf528SMauro Carvalho Chehab 
1859a0bf528SMauro Carvalho Chehab 		val = au8522_readreg(state, 0x4000 |
1869a0bf528SMauro Carvalho Chehab 				     (led_config->gpio_leds & ~0xc000));
1879a0bf528SMauro Carvalho Chehab 
1889a0bf528SMauro Carvalho Chehab 		/* start with all leds off */
1899a0bf528SMauro Carvalho Chehab 		for (i = 0; i < led_config->num_led_states; i++)
1909a0bf528SMauro Carvalho Chehab 			val &= ~led_config->led_states[i];
1919a0bf528SMauro Carvalho Chehab 
1929a0bf528SMauro Carvalho Chehab 		/* set selected LED state */
1939a0bf528SMauro Carvalho Chehab 		if (led < led_config->num_led_states)
1949a0bf528SMauro Carvalho Chehab 			val |= led_config->led_states[led];
1959a0bf528SMauro Carvalho Chehab 		else if (led_config->num_led_states)
1969a0bf528SMauro Carvalho Chehab 			val |=
1979a0bf528SMauro Carvalho Chehab 			led_config->led_states[led_config->num_led_states - 1];
1989a0bf528SMauro Carvalho Chehab 
1999a0bf528SMauro Carvalho Chehab 		ret = au8522_writereg(state, 0x8000 |
2009a0bf528SMauro Carvalho Chehab 				      (led_config->gpio_leds & ~0xc000), val);
2019a0bf528SMauro Carvalho Chehab 		if (ret < 0)
2029a0bf528SMauro Carvalho Chehab 			return ret;
2039a0bf528SMauro Carvalho Chehab 
2049a0bf528SMauro Carvalho Chehab 		state->led_state = led;
2059a0bf528SMauro Carvalho Chehab 
2069a0bf528SMauro Carvalho Chehab 		if (led == 0)
2079a0bf528SMauro Carvalho Chehab 			au8522_led_gpio_enable(state, 0);
2089a0bf528SMauro Carvalho Chehab 	}
2099a0bf528SMauro Carvalho Chehab 
2109a0bf528SMauro Carvalho Chehab 	return 0;
2119a0bf528SMauro Carvalho Chehab }
2129a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(au8522_led_ctrl);
2139a0bf528SMauro Carvalho Chehab 
au8522_init(struct dvb_frontend * fe)2149a0bf528SMauro Carvalho Chehab int au8522_init(struct dvb_frontend *fe)
2159a0bf528SMauro Carvalho Chehab {
2169a0bf528SMauro Carvalho Chehab 	struct au8522_state *state = fe->demodulator_priv;
2179a0bf528SMauro Carvalho Chehab 	dprintk("%s()\n", __func__);
2189a0bf528SMauro Carvalho Chehab 
2199a0bf528SMauro Carvalho Chehab 	state->operational_mode = AU8522_DIGITAL_MODE;
2209a0bf528SMauro Carvalho Chehab 
2219a0bf528SMauro Carvalho Chehab 	/* Clear out any state associated with the digital side of the
2229a0bf528SMauro Carvalho Chehab 	   chip, so that when it gets powered back up it won't think
2239a0bf528SMauro Carvalho Chehab 	   that it is already tuned */
2249a0bf528SMauro Carvalho Chehab 	state->current_frequency = 0;
225d7590b57SDevin Heitmueller 	state->current_modulation = VSB_8;
2269a0bf528SMauro Carvalho Chehab 
2279a0bf528SMauro Carvalho Chehab 	au8522_writereg(state, 0xa4, 1 << 5);
2289a0bf528SMauro Carvalho Chehab 
2299a0bf528SMauro Carvalho Chehab 	au8522_i2c_gate_ctrl(fe, 1);
2309a0bf528SMauro Carvalho Chehab 
2319a0bf528SMauro Carvalho Chehab 	return 0;
2329a0bf528SMauro Carvalho Chehab }
2339a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(au8522_init);
2349a0bf528SMauro Carvalho Chehab 
au8522_sleep(struct dvb_frontend * fe)2359a0bf528SMauro Carvalho Chehab int au8522_sleep(struct dvb_frontend *fe)
2369a0bf528SMauro Carvalho Chehab {
2379a0bf528SMauro Carvalho Chehab 	struct au8522_state *state = fe->demodulator_priv;
2389a0bf528SMauro Carvalho Chehab 	dprintk("%s()\n", __func__);
2399a0bf528SMauro Carvalho Chehab 
2409a0bf528SMauro Carvalho Chehab 	/* Only power down if the digital side is currently using the chip */
2419a0bf528SMauro Carvalho Chehab 	if (state->operational_mode == AU8522_ANALOG_MODE) {
2429a0bf528SMauro Carvalho Chehab 		/* We're not in one of the expected power modes, which means
2439a0bf528SMauro Carvalho Chehab 		   that the DVB thread is probably telling us to go to sleep
2449a0bf528SMauro Carvalho Chehab 		   even though the analog frontend has already started using
2459a0bf528SMauro Carvalho Chehab 		   the chip.  So ignore the request */
2469a0bf528SMauro Carvalho Chehab 		return 0;
2479a0bf528SMauro Carvalho Chehab 	}
2489a0bf528SMauro Carvalho Chehab 
2499a0bf528SMauro Carvalho Chehab 	/* turn off led */
2509a0bf528SMauro Carvalho Chehab 	au8522_led_ctrl(state, 0);
2519a0bf528SMauro Carvalho Chehab 
2529a0bf528SMauro Carvalho Chehab 	/* Power down the chip */
2539a0bf528SMauro Carvalho Chehab 	au8522_writereg(state, 0xa4, 1 << 5);
2549a0bf528SMauro Carvalho Chehab 
2559a0bf528SMauro Carvalho Chehab 	state->current_frequency = 0;
2569a0bf528SMauro Carvalho Chehab 
2579a0bf528SMauro Carvalho Chehab 	return 0;
2589a0bf528SMauro Carvalho Chehab }
2599a0bf528SMauro Carvalho Chehab EXPORT_SYMBOL(au8522_sleep);
2609a0bf528SMauro Carvalho Chehab 
2619a0bf528SMauro Carvalho Chehab module_param(debug, int, 0644);
2629a0bf528SMauro Carvalho Chehab MODULE_PARM_DESC(debug, "Enable verbose debug messages");
2639a0bf528SMauro Carvalho Chehab 
2649a0bf528SMauro Carvalho Chehab MODULE_DESCRIPTION("Auvitek AU8522 QAM-B/ATSC Demodulator driver");
2659a0bf528SMauro Carvalho Chehab MODULE_AUTHOR("Steven Toth");
2669a0bf528SMauro Carvalho Chehab MODULE_LICENSE("GPL");
267