1/*
2    Auvitek AU8522 QAM/8VSB demodulator driver
3
4    Copyright (C) 2008 Steven Toth <stoth@linuxtv.org>
5    Copyright (C) 2008 Devin Heitmueller <dheitmueller@linuxtv.org>
6    Copyright (C) 2005-2008 Auvitek International, Ltd.
7    Copyright (C) 2012 Michael Krufky <mkrufky@linuxtv.org>
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22
23*/
24
25#include <linux/i2c.h>
26#include "dvb_frontend.h"
27#include "au8522_priv.h"
28
29static int debug;
30
31#define dprintk(arg...)\
32  do { if (debug)\
33	 printk(arg);\
34  } while (0)
35
36/* Despite the name "hybrid_tuner", the framework works just as well for
37   hybrid demodulators as well... */
38static LIST_HEAD(hybrid_tuner_instance_list);
39static DEFINE_MUTEX(au8522_list_mutex);
40
41/* 16 bit registers, 8 bit values */
42int au8522_writereg(struct au8522_state *state, u16 reg, u8 data)
43{
44	int ret;
45	u8 buf[] = { (reg >> 8) | 0x80, reg & 0xff, data };
46
47	struct i2c_msg msg = { .addr = state->config->demod_address,
48			       .flags = 0, .buf = buf, .len = 3 };
49
50	ret = i2c_transfer(state->i2c, &msg, 1);
51
52	if (ret != 1)
53		printk("%s: writereg error (reg == 0x%02x, val == 0x%04x, "
54		       "ret == %i)\n", __func__, reg, data, ret);
55
56	return (ret != 1) ? -1 : 0;
57}
58EXPORT_SYMBOL(au8522_writereg);
59
60u8 au8522_readreg(struct au8522_state *state, u16 reg)
61{
62	int ret;
63	u8 b0[] = { (reg >> 8) | 0x40, reg & 0xff };
64	u8 b1[] = { 0 };
65
66	struct i2c_msg msg[] = {
67		{ .addr = state->config->demod_address, .flags = 0,
68		  .buf = b0, .len = 2 },
69		{ .addr = state->config->demod_address, .flags = I2C_M_RD,
70		  .buf = b1, .len = 1 } };
71
72	ret = i2c_transfer(state->i2c, msg, 2);
73
74	if (ret != 2)
75		printk(KERN_ERR "%s: readreg error (ret == %i)\n",
76		       __func__, ret);
77	return b1[0];
78}
79EXPORT_SYMBOL(au8522_readreg);
80
81int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
82{
83	struct au8522_state *state = fe->demodulator_priv;
84
85	dprintk("%s(%d)\n", __func__, enable);
86
87	if (state->operational_mode == AU8522_ANALOG_MODE) {
88		/* We're being asked to manage the gate even though we're
89		   not in digital mode.  This can occur if we get switched
90		   over to analog mode before the dvb_frontend kernel thread
91		   has completely shutdown */
92		return 0;
93	}
94
95	if (enable)
96		return au8522_writereg(state, 0x106, 1);
97	else
98		return au8522_writereg(state, 0x106, 0);
99}
100EXPORT_SYMBOL(au8522_i2c_gate_ctrl);
101
102int au8522_analog_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
103{
104	struct au8522_state *state = fe->demodulator_priv;
105
106	dprintk("%s(%d)\n", __func__, enable);
107
108	if (enable)
109		return au8522_writereg(state, 0x106, 1);
110	else
111		return au8522_writereg(state, 0x106, 0);
112}
113EXPORT_SYMBOL(au8522_analog_i2c_gate_ctrl);
114
115/* Reset the demod hardware and reset all of the configuration registers
116   to a default state. */
117int au8522_get_state(struct au8522_state **state, struct i2c_adapter *i2c,
118		     u8 client_address)
119{
120	int ret;
121
122	mutex_lock(&au8522_list_mutex);
123	ret = hybrid_tuner_request_state(struct au8522_state, (*state),
124					 hybrid_tuner_instance_list,
125					 i2c, client_address, "au8522");
126	mutex_unlock(&au8522_list_mutex);
127
128	return ret;
129}
130EXPORT_SYMBOL(au8522_get_state);
131
132void au8522_release_state(struct au8522_state *state)
133{
134	mutex_lock(&au8522_list_mutex);
135	if (state != NULL)
136		hybrid_tuner_release_state(state);
137	mutex_unlock(&au8522_list_mutex);
138}
139EXPORT_SYMBOL(au8522_release_state);
140
141static int au8522_led_gpio_enable(struct au8522_state *state, int onoff)
142{
143	struct au8522_led_config *led_config = state->config->led_cfg;
144	u8 val;
145
146	/* bail out if we can't control an LED */
147	if (!led_config || !led_config->gpio_output ||
148	    !led_config->gpio_output_enable || !led_config->gpio_output_disable)
149		return 0;
150
151	val = au8522_readreg(state, 0x4000 |
152			     (led_config->gpio_output & ~0xc000));
153	if (onoff) {
154		/* enable GPIO output */
155		val &= ~((led_config->gpio_output_enable >> 8) & 0xff);
156		val |=  (led_config->gpio_output_enable & 0xff);
157	} else {
158		/* disable GPIO output */
159		val &= ~((led_config->gpio_output_disable >> 8) & 0xff);
160		val |=  (led_config->gpio_output_disable & 0xff);
161	}
162	return au8522_writereg(state, 0x8000 |
163			       (led_config->gpio_output & ~0xc000), val);
164}
165
166/* led = 0 | off
167 * led = 1 | signal ok
168 * led = 2 | signal strong
169 * led < 0 | only light led if leds are currently off
170 */
171int au8522_led_ctrl(struct au8522_state *state, int led)
172{
173	struct au8522_led_config *led_config = state->config->led_cfg;
174	int i, ret = 0;
175
176	/* bail out if we can't control an LED */
177	if (!led_config || !led_config->gpio_leds ||
178	    !led_config->num_led_states || !led_config->led_states)
179		return 0;
180
181	if (led < 0) {
182		/* if LED is already lit, then leave it as-is */
183		if (state->led_state)
184			return 0;
185		else
186			led *= -1;
187	}
188
189	/* toggle LED if changing state */
190	if (state->led_state != led) {
191		u8 val;
192
193		dprintk("%s: %d\n", __func__, led);
194
195		au8522_led_gpio_enable(state, 1);
196
197		val = au8522_readreg(state, 0x4000 |
198				     (led_config->gpio_leds & ~0xc000));
199
200		/* start with all leds off */
201		for (i = 0; i < led_config->num_led_states; i++)
202			val &= ~led_config->led_states[i];
203
204		/* set selected LED state */
205		if (led < led_config->num_led_states)
206			val |= led_config->led_states[led];
207		else if (led_config->num_led_states)
208			val |=
209			led_config->led_states[led_config->num_led_states - 1];
210
211		ret = au8522_writereg(state, 0x8000 |
212				      (led_config->gpio_leds & ~0xc000), val);
213		if (ret < 0)
214			return ret;
215
216		state->led_state = led;
217
218		if (led == 0)
219			au8522_led_gpio_enable(state, 0);
220	}
221
222	return 0;
223}
224EXPORT_SYMBOL(au8522_led_ctrl);
225
226int au8522_init(struct dvb_frontend *fe)
227{
228	struct au8522_state *state = fe->demodulator_priv;
229	dprintk("%s()\n", __func__);
230
231	state->operational_mode = AU8522_DIGITAL_MODE;
232
233	/* Clear out any state associated with the digital side of the
234	   chip, so that when it gets powered back up it won't think
235	   that it is already tuned */
236	state->current_frequency = 0;
237
238	au8522_writereg(state, 0xa4, 1 << 5);
239
240	au8522_i2c_gate_ctrl(fe, 1);
241
242	return 0;
243}
244EXPORT_SYMBOL(au8522_init);
245
246int au8522_sleep(struct dvb_frontend *fe)
247{
248	struct au8522_state *state = fe->demodulator_priv;
249	dprintk("%s()\n", __func__);
250
251	/* Only power down if the digital side is currently using the chip */
252	if (state->operational_mode == AU8522_ANALOG_MODE) {
253		/* We're not in one of the expected power modes, which means
254		   that the DVB thread is probably telling us to go to sleep
255		   even though the analog frontend has already started using
256		   the chip.  So ignore the request */
257		return 0;
258	}
259
260	/* turn off led */
261	au8522_led_ctrl(state, 0);
262
263	/* Power down the chip */
264	au8522_writereg(state, 0xa4, 1 << 5);
265
266	state->current_frequency = 0;
267
268	return 0;
269}
270EXPORT_SYMBOL(au8522_sleep);
271
272module_param(debug, int, 0644);
273MODULE_PARM_DESC(debug, "Enable verbose debug messages");
274
275MODULE_DESCRIPTION("Auvitek AU8522 QAM-B/ATSC Demodulator driver");
276MODULE_AUTHOR("Steven Toth");
277MODULE_LICENSE("GPL");
278