mirror of
https://github.com/torvalds/linux.git
synced 2024-12-01 00:21:32 +00:00
74ba9207e1
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 675 mass ave cambridge ma 02139 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 441 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Michael Ellerman <mpe@ellerman.id.au> (powerpc) Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190520071858.739733335@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
267 lines
6.8 KiB
C
267 lines
6.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
Auvitek AU8522 QAM/8VSB demodulator driver
|
|
|
|
Copyright (C) 2008 Steven Toth <stoth@linuxtv.org>
|
|
Copyright (C) 2008 Devin Heitmueller <dheitmueller@linuxtv.org>
|
|
Copyright (C) 2005-2008 Auvitek International, Ltd.
|
|
Copyright (C) 2012 Michael Krufky <mkrufky@linuxtv.org>
|
|
|
|
|
|
*/
|
|
|
|
#include <linux/i2c.h>
|
|
#include <media/dvb_frontend.h>
|
|
#include "au8522_priv.h"
|
|
|
|
static int debug;
|
|
|
|
#define dprintk(arg...)\
|
|
do { if (debug)\
|
|
printk(arg);\
|
|
} while (0)
|
|
|
|
/* Despite the name "hybrid_tuner", the framework works just as well for
|
|
hybrid demodulators as well... */
|
|
static LIST_HEAD(hybrid_tuner_instance_list);
|
|
static DEFINE_MUTEX(au8522_list_mutex);
|
|
|
|
/* 16 bit registers, 8 bit values */
|
|
int au8522_writereg(struct au8522_state *state, u16 reg, u8 data)
|
|
{
|
|
int ret;
|
|
u8 buf[] = { (reg >> 8) | 0x80, reg & 0xff, data };
|
|
|
|
struct i2c_msg msg = { .addr = state->config.demod_address,
|
|
.flags = 0, .buf = buf, .len = 3 };
|
|
|
|
ret = i2c_transfer(state->i2c, &msg, 1);
|
|
|
|
if (ret != 1)
|
|
printk("%s: writereg error (reg == 0x%02x, val == 0x%04x, ret == %i)\n",
|
|
__func__, reg, data, ret);
|
|
|
|
return (ret != 1) ? -1 : 0;
|
|
}
|
|
EXPORT_SYMBOL(au8522_writereg);
|
|
|
|
u8 au8522_readreg(struct au8522_state *state, u16 reg)
|
|
{
|
|
int ret;
|
|
u8 b0[] = { (reg >> 8) | 0x40, reg & 0xff };
|
|
u8 b1[] = { 0 };
|
|
|
|
struct i2c_msg msg[] = {
|
|
{ .addr = state->config.demod_address, .flags = 0,
|
|
.buf = b0, .len = 2 },
|
|
{ .addr = state->config.demod_address, .flags = I2C_M_RD,
|
|
.buf = b1, .len = 1 } };
|
|
|
|
ret = i2c_transfer(state->i2c, msg, 2);
|
|
|
|
if (ret != 2)
|
|
printk(KERN_ERR "%s: readreg error (ret == %i)\n",
|
|
__func__, ret);
|
|
return b1[0];
|
|
}
|
|
EXPORT_SYMBOL(au8522_readreg);
|
|
|
|
int au8522_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
|
|
{
|
|
struct au8522_state *state = fe->demodulator_priv;
|
|
|
|
dprintk("%s(%d)\n", __func__, enable);
|
|
|
|
if (state->operational_mode == AU8522_ANALOG_MODE) {
|
|
/* We're being asked to manage the gate even though we're
|
|
not in digital mode. This can occur if we get switched
|
|
over to analog mode before the dvb_frontend kernel thread
|
|
has completely shutdown */
|
|
return 0;
|
|
}
|
|
|
|
if (enable)
|
|
return au8522_writereg(state, 0x106, 1);
|
|
else
|
|
return au8522_writereg(state, 0x106, 0);
|
|
}
|
|
EXPORT_SYMBOL(au8522_i2c_gate_ctrl);
|
|
|
|
int au8522_analog_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
|
|
{
|
|
struct au8522_state *state = fe->demodulator_priv;
|
|
|
|
dprintk("%s(%d)\n", __func__, enable);
|
|
|
|
if (enable)
|
|
return au8522_writereg(state, 0x106, 1);
|
|
else
|
|
return au8522_writereg(state, 0x106, 0);
|
|
}
|
|
EXPORT_SYMBOL(au8522_analog_i2c_gate_ctrl);
|
|
|
|
/* Reset the demod hardware and reset all of the configuration registers
|
|
to a default state. */
|
|
int au8522_get_state(struct au8522_state **state, struct i2c_adapter *i2c,
|
|
u8 client_address)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&au8522_list_mutex);
|
|
ret = hybrid_tuner_request_state(struct au8522_state, (*state),
|
|
hybrid_tuner_instance_list,
|
|
i2c, client_address, "au8522");
|
|
mutex_unlock(&au8522_list_mutex);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(au8522_get_state);
|
|
|
|
void au8522_release_state(struct au8522_state *state)
|
|
{
|
|
mutex_lock(&au8522_list_mutex);
|
|
if (state != NULL)
|
|
hybrid_tuner_release_state(state);
|
|
mutex_unlock(&au8522_list_mutex);
|
|
}
|
|
EXPORT_SYMBOL(au8522_release_state);
|
|
|
|
static int au8522_led_gpio_enable(struct au8522_state *state, int onoff)
|
|
{
|
|
struct au8522_led_config *led_config = state->config.led_cfg;
|
|
u8 val;
|
|
|
|
/* bail out if we can't control an LED */
|
|
if (!led_config || !led_config->gpio_output ||
|
|
!led_config->gpio_output_enable || !led_config->gpio_output_disable)
|
|
return 0;
|
|
|
|
val = au8522_readreg(state, 0x4000 |
|
|
(led_config->gpio_output & ~0xc000));
|
|
if (onoff) {
|
|
/* enable GPIO output */
|
|
val &= ~((led_config->gpio_output_enable >> 8) & 0xff);
|
|
val |= (led_config->gpio_output_enable & 0xff);
|
|
} else {
|
|
/* disable GPIO output */
|
|
val &= ~((led_config->gpio_output_disable >> 8) & 0xff);
|
|
val |= (led_config->gpio_output_disable & 0xff);
|
|
}
|
|
return au8522_writereg(state, 0x8000 |
|
|
(led_config->gpio_output & ~0xc000), val);
|
|
}
|
|
|
|
/* led = 0 | off
|
|
* led = 1 | signal ok
|
|
* led = 2 | signal strong
|
|
* led < 0 | only light led if leds are currently off
|
|
*/
|
|
int au8522_led_ctrl(struct au8522_state *state, int led)
|
|
{
|
|
struct au8522_led_config *led_config = state->config.led_cfg;
|
|
int i, ret = 0;
|
|
|
|
/* bail out if we can't control an LED */
|
|
if (!led_config || !led_config->gpio_leds ||
|
|
!led_config->num_led_states || !led_config->led_states)
|
|
return 0;
|
|
|
|
if (led < 0) {
|
|
/* if LED is already lit, then leave it as-is */
|
|
if (state->led_state)
|
|
return 0;
|
|
else
|
|
led *= -1;
|
|
}
|
|
|
|
/* toggle LED if changing state */
|
|
if (state->led_state != led) {
|
|
u8 val;
|
|
|
|
dprintk("%s: %d\n", __func__, led);
|
|
|
|
au8522_led_gpio_enable(state, 1);
|
|
|
|
val = au8522_readreg(state, 0x4000 |
|
|
(led_config->gpio_leds & ~0xc000));
|
|
|
|
/* start with all leds off */
|
|
for (i = 0; i < led_config->num_led_states; i++)
|
|
val &= ~led_config->led_states[i];
|
|
|
|
/* set selected LED state */
|
|
if (led < led_config->num_led_states)
|
|
val |= led_config->led_states[led];
|
|
else if (led_config->num_led_states)
|
|
val |=
|
|
led_config->led_states[led_config->num_led_states - 1];
|
|
|
|
ret = au8522_writereg(state, 0x8000 |
|
|
(led_config->gpio_leds & ~0xc000), val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
state->led_state = led;
|
|
|
|
if (led == 0)
|
|
au8522_led_gpio_enable(state, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(au8522_led_ctrl);
|
|
|
|
int au8522_init(struct dvb_frontend *fe)
|
|
{
|
|
struct au8522_state *state = fe->demodulator_priv;
|
|
dprintk("%s()\n", __func__);
|
|
|
|
state->operational_mode = AU8522_DIGITAL_MODE;
|
|
|
|
/* Clear out any state associated with the digital side of the
|
|
chip, so that when it gets powered back up it won't think
|
|
that it is already tuned */
|
|
state->current_frequency = 0;
|
|
state->current_modulation = VSB_8;
|
|
|
|
au8522_writereg(state, 0xa4, 1 << 5);
|
|
|
|
au8522_i2c_gate_ctrl(fe, 1);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(au8522_init);
|
|
|
|
int au8522_sleep(struct dvb_frontend *fe)
|
|
{
|
|
struct au8522_state *state = fe->demodulator_priv;
|
|
dprintk("%s()\n", __func__);
|
|
|
|
/* Only power down if the digital side is currently using the chip */
|
|
if (state->operational_mode == AU8522_ANALOG_MODE) {
|
|
/* We're not in one of the expected power modes, which means
|
|
that the DVB thread is probably telling us to go to sleep
|
|
even though the analog frontend has already started using
|
|
the chip. So ignore the request */
|
|
return 0;
|
|
}
|
|
|
|
/* turn off led */
|
|
au8522_led_ctrl(state, 0);
|
|
|
|
/* Power down the chip */
|
|
au8522_writereg(state, 0xa4, 1 << 5);
|
|
|
|
state->current_frequency = 0;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(au8522_sleep);
|
|
|
|
module_param(debug, int, 0644);
|
|
MODULE_PARM_DESC(debug, "Enable verbose debug messages");
|
|
|
|
MODULE_DESCRIPTION("Auvitek AU8522 QAM-B/ATSC Demodulator driver");
|
|
MODULE_AUTHOR("Steven Toth");
|
|
MODULE_LICENSE("GPL");
|