18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci    Auvitek AU8522 QAM/8VSB demodulator driver
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci    Copyright (C) 2008 Steven Toth <stoth@linuxtv.org>
68c2ecf20Sopenharmony_ci    Copyright (C) 2008 Devin Heitmueller <dheitmueller@linuxtv.org>
78c2ecf20Sopenharmony_ci    Copyright (C) 2005-2008 Auvitek International, Ltd.
88c2ecf20Sopenharmony_ci    Copyright (C) 2012 Michael Krufky <mkrufky@linuxtv.org>
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci*/
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/i2c.h>
148c2ecf20Sopenharmony_ci#include <media/dvb_frontend.h>
158c2ecf20Sopenharmony_ci#include "au8522_priv.h"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cistatic int debug;
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define dprintk(arg...)\
208c2ecf20Sopenharmony_ci  do { if (debug)\
218c2ecf20Sopenharmony_ci	 printk(arg);\
228c2ecf20Sopenharmony_ci  } while (0)
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/* Despite the name "hybrid_tuner", the framework works just as well for
258c2ecf20Sopenharmony_ci   hybrid demodulators as well... */
268c2ecf20Sopenharmony_cistatic LIST_HEAD(hybrid_tuner_instance_list);
278c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(au8522_list_mutex);
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci/* 16 bit registers, 8 bit values */
308c2ecf20Sopenharmony_ciint au8522_writereg(struct au8522_state *state, u16 reg, u8 data)
318c2ecf20Sopenharmony_ci{
328c2ecf20Sopenharmony_ci	int ret;
338c2ecf20Sopenharmony_ci	u8 buf[] = { (reg >> 8) | 0x80, reg & 0xff, data };
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	struct i2c_msg msg = { .addr = state->config.demod_address,
368c2ecf20Sopenharmony_ci			       .flags = 0, .buf = buf, .len = 3 };
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	ret = i2c_transfer(state->i2c, &msg, 1);
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	if (ret != 1)
418c2ecf20Sopenharmony_ci		printk("%s: writereg error (reg == 0x%02x, val == 0x%04x, ret == %i)\n",
428c2ecf20Sopenharmony_ci		       __func__, reg, data, ret);
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	return (ret != 1) ? -1 : 0;
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ciEXPORT_SYMBOL(au8522_writereg);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ciu8 au8522_readreg(struct au8522_state *state, u16 reg)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	int ret;
518c2ecf20Sopenharmony_ci	u8 b0[] = { (reg >> 8) | 0x40, reg & 0xff };
528c2ecf20Sopenharmony_ci	u8 b1[] = { 0 };
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	struct i2c_msg msg[] = {
558c2ecf20Sopenharmony_ci		{ .addr = state->config.demod_address, .flags = 0,
568c2ecf20Sopenharmony_ci		  .buf = b0, .len = 2 },
578c2ecf20Sopenharmony_ci		{ .addr = state->config.demod_address, .flags = I2C_M_RD,
588c2ecf20Sopenharmony_ci		  .buf = b1, .len = 1 } };
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	ret = i2c_transfer(state->i2c, msg, 2);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	if (ret != 2)
638c2ecf20Sopenharmony_ci		printk(KERN_ERR "%s: readreg error (ret == %i)\n",
648c2ecf20Sopenharmony_ci		       __func__, ret);
658c2ecf20Sopenharmony_ci	return b1[0];
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ciEXPORT_SYMBOL(au8522_readreg);
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ciint au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct au8522_state *state = fe->demodulator_priv;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	dprintk("%s(%d)\n", __func__, enable);
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	if (state->operational_mode == AU8522_ANALOG_MODE) {
768c2ecf20Sopenharmony_ci		/* We're being asked to manage the gate even though we're
778c2ecf20Sopenharmony_ci		   not in digital mode.  This can occur if we get switched
788c2ecf20Sopenharmony_ci		   over to analog mode before the dvb_frontend kernel thread
798c2ecf20Sopenharmony_ci		   has completely shutdown */
808c2ecf20Sopenharmony_ci		return 0;
818c2ecf20Sopenharmony_ci	}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	if (enable)
848c2ecf20Sopenharmony_ci		return au8522_writereg(state, 0x106, 1);
858c2ecf20Sopenharmony_ci	else
868c2ecf20Sopenharmony_ci		return au8522_writereg(state, 0x106, 0);
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ciEXPORT_SYMBOL(au8522_i2c_gate_ctrl);
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ciint au8522_analog_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	struct au8522_state *state = fe->demodulator_priv;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	dprintk("%s(%d)\n", __func__, enable);
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	if (enable)
978c2ecf20Sopenharmony_ci		return au8522_writereg(state, 0x106, 1);
988c2ecf20Sopenharmony_ci	else
998c2ecf20Sopenharmony_ci		return au8522_writereg(state, 0x106, 0);
1008c2ecf20Sopenharmony_ci}
1018c2ecf20Sopenharmony_ciEXPORT_SYMBOL(au8522_analog_i2c_gate_ctrl);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci/* Reset the demod hardware and reset all of the configuration registers
1048c2ecf20Sopenharmony_ci   to a default state. */
1058c2ecf20Sopenharmony_ciint au8522_get_state(struct au8522_state **state, struct i2c_adapter *i2c,
1068c2ecf20Sopenharmony_ci		     u8 client_address)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	int ret;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	mutex_lock(&au8522_list_mutex);
1118c2ecf20Sopenharmony_ci	ret = hybrid_tuner_request_state(struct au8522_state, (*state),
1128c2ecf20Sopenharmony_ci					 hybrid_tuner_instance_list,
1138c2ecf20Sopenharmony_ci					 i2c, client_address, "au8522");
1148c2ecf20Sopenharmony_ci	mutex_unlock(&au8522_list_mutex);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	return ret;
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ciEXPORT_SYMBOL(au8522_get_state);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_civoid au8522_release_state(struct au8522_state *state)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	mutex_lock(&au8522_list_mutex);
1238c2ecf20Sopenharmony_ci	if (state != NULL)
1248c2ecf20Sopenharmony_ci		hybrid_tuner_release_state(state);
1258c2ecf20Sopenharmony_ci	mutex_unlock(&au8522_list_mutex);
1268c2ecf20Sopenharmony_ci}
1278c2ecf20Sopenharmony_ciEXPORT_SYMBOL(au8522_release_state);
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_cistatic int au8522_led_gpio_enable(struct au8522_state *state, int onoff)
1308c2ecf20Sopenharmony_ci{
1318c2ecf20Sopenharmony_ci	struct au8522_led_config *led_config = state->config.led_cfg;
1328c2ecf20Sopenharmony_ci	u8 val;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	/* bail out if we can't control an LED */
1358c2ecf20Sopenharmony_ci	if (!led_config || !led_config->gpio_output ||
1368c2ecf20Sopenharmony_ci	    !led_config->gpio_output_enable || !led_config->gpio_output_disable)
1378c2ecf20Sopenharmony_ci		return 0;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	val = au8522_readreg(state, 0x4000 |
1408c2ecf20Sopenharmony_ci			     (led_config->gpio_output & ~0xc000));
1418c2ecf20Sopenharmony_ci	if (onoff) {
1428c2ecf20Sopenharmony_ci		/* enable GPIO output */
1438c2ecf20Sopenharmony_ci		val &= ~((led_config->gpio_output_enable >> 8) & 0xff);
1448c2ecf20Sopenharmony_ci		val |=  (led_config->gpio_output_enable & 0xff);
1458c2ecf20Sopenharmony_ci	} else {
1468c2ecf20Sopenharmony_ci		/* disable GPIO output */
1478c2ecf20Sopenharmony_ci		val &= ~((led_config->gpio_output_disable >> 8) & 0xff);
1488c2ecf20Sopenharmony_ci		val |=  (led_config->gpio_output_disable & 0xff);
1498c2ecf20Sopenharmony_ci	}
1508c2ecf20Sopenharmony_ci	return au8522_writereg(state, 0x8000 |
1518c2ecf20Sopenharmony_ci			       (led_config->gpio_output & ~0xc000), val);
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci/* led = 0 | off
1558c2ecf20Sopenharmony_ci * led = 1 | signal ok
1568c2ecf20Sopenharmony_ci * led = 2 | signal strong
1578c2ecf20Sopenharmony_ci * led < 0 | only light led if leds are currently off
1588c2ecf20Sopenharmony_ci */
1598c2ecf20Sopenharmony_ciint au8522_led_ctrl(struct au8522_state *state, int led)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	struct au8522_led_config *led_config = state->config.led_cfg;
1628c2ecf20Sopenharmony_ci	int i, ret = 0;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	/* bail out if we can't control an LED */
1658c2ecf20Sopenharmony_ci	if (!led_config || !led_config->gpio_leds ||
1668c2ecf20Sopenharmony_ci	    !led_config->num_led_states || !led_config->led_states)
1678c2ecf20Sopenharmony_ci		return 0;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	if (led < 0) {
1708c2ecf20Sopenharmony_ci		/* if LED is already lit, then leave it as-is */
1718c2ecf20Sopenharmony_ci		if (state->led_state)
1728c2ecf20Sopenharmony_ci			return 0;
1738c2ecf20Sopenharmony_ci		else
1748c2ecf20Sopenharmony_ci			led *= -1;
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	/* toggle LED if changing state */
1788c2ecf20Sopenharmony_ci	if (state->led_state != led) {
1798c2ecf20Sopenharmony_ci		u8 val;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci		dprintk("%s: %d\n", __func__, led);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci		au8522_led_gpio_enable(state, 1);
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci		val = au8522_readreg(state, 0x4000 |
1868c2ecf20Sopenharmony_ci				     (led_config->gpio_leds & ~0xc000));
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci		/* start with all leds off */
1898c2ecf20Sopenharmony_ci		for (i = 0; i < led_config->num_led_states; i++)
1908c2ecf20Sopenharmony_ci			val &= ~led_config->led_states[i];
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci		/* set selected LED state */
1938c2ecf20Sopenharmony_ci		if (led < led_config->num_led_states)
1948c2ecf20Sopenharmony_ci			val |= led_config->led_states[led];
1958c2ecf20Sopenharmony_ci		else if (led_config->num_led_states)
1968c2ecf20Sopenharmony_ci			val |=
1978c2ecf20Sopenharmony_ci			led_config->led_states[led_config->num_led_states - 1];
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci		ret = au8522_writereg(state, 0x8000 |
2008c2ecf20Sopenharmony_ci				      (led_config->gpio_leds & ~0xc000), val);
2018c2ecf20Sopenharmony_ci		if (ret < 0)
2028c2ecf20Sopenharmony_ci			return ret;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci		state->led_state = led;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci		if (led == 0)
2078c2ecf20Sopenharmony_ci			au8522_led_gpio_enable(state, 0);
2088c2ecf20Sopenharmony_ci	}
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	return 0;
2118c2ecf20Sopenharmony_ci}
2128c2ecf20Sopenharmony_ciEXPORT_SYMBOL(au8522_led_ctrl);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ciint au8522_init(struct dvb_frontend *fe)
2158c2ecf20Sopenharmony_ci{
2168c2ecf20Sopenharmony_ci	struct au8522_state *state = fe->demodulator_priv;
2178c2ecf20Sopenharmony_ci	dprintk("%s()\n", __func__);
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	state->operational_mode = AU8522_DIGITAL_MODE;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	/* Clear out any state associated with the digital side of the
2228c2ecf20Sopenharmony_ci	   chip, so that when it gets powered back up it won't think
2238c2ecf20Sopenharmony_ci	   that it is already tuned */
2248c2ecf20Sopenharmony_ci	state->current_frequency = 0;
2258c2ecf20Sopenharmony_ci	state->current_modulation = VSB_8;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	au8522_writereg(state, 0xa4, 1 << 5);
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	au8522_i2c_gate_ctrl(fe, 1);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	return 0;
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ciEXPORT_SYMBOL(au8522_init);
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ciint au8522_sleep(struct dvb_frontend *fe)
2368c2ecf20Sopenharmony_ci{
2378c2ecf20Sopenharmony_ci	struct au8522_state *state = fe->demodulator_priv;
2388c2ecf20Sopenharmony_ci	dprintk("%s()\n", __func__);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	/* Only power down if the digital side is currently using the chip */
2418c2ecf20Sopenharmony_ci	if (state->operational_mode == AU8522_ANALOG_MODE) {
2428c2ecf20Sopenharmony_ci		/* We're not in one of the expected power modes, which means
2438c2ecf20Sopenharmony_ci		   that the DVB thread is probably telling us to go to sleep
2448c2ecf20Sopenharmony_ci		   even though the analog frontend has already started using
2458c2ecf20Sopenharmony_ci		   the chip.  So ignore the request */
2468c2ecf20Sopenharmony_ci		return 0;
2478c2ecf20Sopenharmony_ci	}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	/* turn off led */
2508c2ecf20Sopenharmony_ci	au8522_led_ctrl(state, 0);
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	/* Power down the chip */
2538c2ecf20Sopenharmony_ci	au8522_writereg(state, 0xa4, 1 << 5);
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	state->current_frequency = 0;
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	return 0;
2588c2ecf20Sopenharmony_ci}
2598c2ecf20Sopenharmony_ciEXPORT_SYMBOL(au8522_sleep);
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cimodule_param(debug, int, 0644);
2628c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "Enable verbose debug messages");
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Auvitek AU8522 QAM-B/ATSC Demodulator driver");
2658c2ecf20Sopenharmony_ciMODULE_AUTHOR("Steven Toth");
2668c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
267