V4L/DVB (5948): Adding support for the S5H1409/CX24227 8VSB/QAM demodulator.

This patch adds support for the Samsung S5H1409 demodulator, also known
as the Conexant CX24227 demodulator. 8VSB mode has been tested and QAM
has been implemented based on the spec, although it's untested.

The S5H1409 / CX24227 appears on various Hauppauge boards.

Signed-off-by: Steven Toth <stoth@hauppauge.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
This commit is contained in:
Steven Toth 2007-07-28 19:34:52 -03:00 committed by Mauro Carvalho Chehab
parent f47623a04d
commit 89885558ad
4 changed files with 792 additions and 0 deletions

View file

@ -283,6 +283,14 @@ config DVB_LGDT330X
An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
to support this frontend.
config DVB_S5H1409
tristate "Samsung S5H1409 based"
depends on DVB_CORE && I2C
default m if DVB_FE_CUSTOMISE
help
An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
to support this frontend.
comment "Tuners/PLL support"
depends on DVB_CORE

View file

@ -44,3 +44,4 @@ obj-$(CONFIG_DVB_TUNER_MT2266) += mt2266.o
obj-$(CONFIG_DVB_TUNER_QT1010) += qt1010.o
obj-$(CONFIG_DVB_TUA6100) += tua6100.o
obj-$(CONFIG_DVB_TUNER_MT2131) += mt2131.o
obj-$(CONFIG_DVB_S5H1409) += s5h1409.o

View file

@ -0,0 +1,715 @@
/*
Samsung S5H1409 VSB/QAM demodulator driver
Copyright (C) 2006 Steven Toth <stoth@hauppauge.com>
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.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include "dvb_frontend.h"
#include "dvb-pll.h"
#include "s5h1409.h"
struct s5h1409_state {
struct i2c_adapter* i2c;
/* configuration settings */
const struct s5h1409_config* config;
struct dvb_frontend frontend;
/* previous uncorrected block counter */
fe_modulation_t current_modulation;
u32 current_frequency;
};
static int debug = 0;
#define dprintk if (debug) printk
/* Register values to initialise the demod, this will set VSB by default */
static struct init_tab {
u8 reg;
u16 data;
} init_tab[] = {
{ 0x00, 0x0071, },
{ 0x01, 0x3213, },
{ 0x09, 0x0025, },
{ 0x1c, 0x001d, },
{ 0x1f, 0x002d, },
{ 0x20, 0x001d, },
{ 0x22, 0x0022, },
{ 0x23, 0x0020, },
{ 0x29, 0x110f, },
{ 0x2a, 0x10b4, },
{ 0x2b, 0x10ae, },
{ 0x2c, 0x0031, },
{ 0x31, 0x010d, },
{ 0x32, 0x0100, },
{ 0x44, 0x0510, },
{ 0x54, 0x0104, },
{ 0x58, 0x2222, },
{ 0x59, 0x1162, },
{ 0x5a, 0x3211, },
{ 0x5d, 0x0370, },
{ 0x5e, 0x0296, },
{ 0x61, 0x0010, },
{ 0x63, 0x4a00, },
{ 0x65, 0x0800, },
{ 0x71, 0x0003, },
{ 0x72, 0x0470, },
{ 0x81, 0x0002, },
{ 0x82, 0x0600, },
{ 0x86, 0x0002, },
{ 0x8a, 0x2c38, },
{ 0x8b, 0x2a37, },
{ 0x92, 0x302f, },
{ 0x93, 0x3332, },
{ 0x96, 0x000c, },
{ 0x99, 0x0101, },
{ 0x9c, 0x2e37, },
{ 0x9d, 0x2c37, },
{ 0x9e, 0x2c37, },
{ 0xab, 0x0100, },
{ 0xac, 0x1003, },
{ 0xad, 0x103f, },
{ 0xe2, 0x0100, },
{ 0x28, 0x1010, },
{ 0xb1, 0x000e, },
};
/* VSB SNR lookup table */
static struct vsb_snr_tab {
u16 val;
u16 data;
} vsb_snr_tab[] = {
{ 1023, 770, },
{ 923, 300, },
{ 918, 295, },
{ 915, 290, },
{ 911, 285, },
{ 906, 280, },
{ 901, 275, },
{ 896, 270, },
{ 891, 265, },
{ 885, 260, },
{ 879, 255, },
{ 873, 250, },
{ 864, 245, },
{ 858, 240, },
{ 850, 235, },
{ 841, 230, },
{ 832, 225, },
{ 823, 220, },
{ 812, 215, },
{ 802, 210, },
{ 788, 205, },
{ 778, 200, },
{ 767, 195, },
{ 753, 190, },
{ 740, 185, },
{ 725, 180, },
{ 707, 175, },
{ 689, 170, },
{ 671, 165, },
{ 656, 160, },
{ 637, 155, },
{ 616, 150, },
{ 542, 145, },
{ 519, 140, },
{ 507, 135, },
{ 497, 130, },
{ 492, 125, },
{ 474, 120, },
{ 300, 111, },
{ 0, 0, },
};
/* QAM64 SNR lookup table */
static struct qam64_snr_tab {
u16 val;
u16 data;
} qam64_snr_tab[] = {
{ 12, 300, },
{ 15, 290, },
{ 18, 280, },
{ 22, 270, },
{ 23, 268, },
{ 24, 266, },
{ 25, 264, },
{ 27, 262, },
{ 28, 260, },
{ 29, 258, },
{ 30, 256, },
{ 32, 254, },
{ 33, 252, },
{ 34, 250, },
{ 35, 249, },
{ 36, 248, },
{ 37, 247, },
{ 38, 246, },
{ 39, 245, },
{ 40, 244, },
{ 41, 243, },
{ 42, 241, },
{ 43, 240, },
{ 44, 239, },
{ 45, 238, },
{ 46, 237, },
{ 47, 236, },
{ 48, 235, },
{ 49, 234, },
{ 50, 233, },
{ 51, 232, },
{ 52, 231, },
{ 53, 230, },
{ 55, 229, },
{ 56, 228, },
{ 57, 227, },
{ 58, 226, },
{ 59, 225, },
{ 60, 224, },
{ 62, 223, },
{ 63, 222, },
{ 65, 221, },
{ 66, 220, },
{ 68, 219, },
{ 69, 218, },
{ 70, 217, },
{ 72, 216, },
{ 73, 215, },
{ 75, 214, },
{ 76, 213, },
{ 78, 212, },
{ 80, 211, },
{ 81, 210, },
{ 83, 209, },
{ 84, 208, },
{ 85, 207, },
{ 87, 206, },
{ 89, 205, },
{ 91, 204, },
{ 93, 203, },
{ 95, 202, },
{ 96, 201, },
{ 104, 200, },
};
/* QAM256 SNR lookup table */
static struct qam256_snr_tab {
u16 val;
u16 data;
} qam256_snr_tab[] = {
{ 12, 400, },
{ 13, 390, },
{ 15, 380, },
{ 17, 360, },
{ 19, 350, },
{ 22, 348, },
{ 23, 346, },
{ 24, 344, },
{ 25, 342, },
{ 26, 340, },
{ 27, 336, },
{ 28, 334, },
{ 29, 332, },
{ 30, 330, },
{ 31, 328, },
{ 32, 326, },
{ 33, 325, },
{ 34, 322, },
{ 35, 320, },
{ 37, 318, },
{ 39, 316, },
{ 40, 314, },
{ 41, 312, },
{ 42, 310, },
{ 43, 308, },
{ 46, 306, },
{ 47, 304, },
{ 49, 302, },
{ 51, 300, },
{ 53, 298, },
{ 54, 297, },
{ 55, 296, },
{ 56, 295, },
{ 57, 294, },
{ 59, 293, },
{ 60, 292, },
{ 61, 291, },
{ 63, 290, },
{ 64, 289, },
{ 65, 288, },
{ 66, 287, },
{ 68, 286, },
{ 69, 285, },
{ 71, 284, },
{ 72, 283, },
{ 74, 282, },
{ 75, 281, },
{ 76, 280, },
{ 77, 279, },
{ 78, 278, },
{ 81, 277, },
{ 83, 276, },
{ 84, 275, },
{ 86, 274, },
{ 87, 273, },
{ 89, 272, },
{ 90, 271, },
{ 92, 270, },
{ 93, 269, },
{ 95, 268, },
{ 96, 267, },
{ 98, 266, },
{ 100, 265, },
{ 102, 264, },
{ 104, 263, },
{ 105, 262, },
{ 106, 261, },
{ 110, 260, },
};
/* 8 bit registers, 16 bit values */
static int s5h1409_writereg(struct s5h1409_state* state, u8 reg, u16 data)
{
int ret;
u8 buf [] = { reg, data >> 8, data & 0xff };
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",
__FUNCTION__, reg, data, ret);
return (ret != 1) ? -1 : 0;
}
static u16 s5h1409_readreg(struct s5h1409_state* state, u8 reg)
{
int ret;
u8 b0 [] = { reg };
u8 b1 [] = { 0, 0 };
struct i2c_msg msg [] = {
{ .addr = state->config->demod_address, .flags = 0, .buf = b0, .len = 1 },
{ .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = b1, .len = 2 } };
ret = i2c_transfer(state->i2c, msg, 2);
if (ret != 2)
printk("%s: readreg error (ret == %i)\n", __FUNCTION__, ret)
;
return (b1[0] << 8) | b1[1];
}
static int s5h1409_softreset(struct dvb_frontend* fe)
{
struct s5h1409_state* state = fe->demodulator_priv;
dprintk("%s()\n", __FUNCTION__);
s5h1409_writereg(state, 0xf5, 0);
s5h1409_writereg(state, 0xf5, 1);
return 0;
}
static int s5h1409_set_if_freq(struct dvb_frontend* fe, int KHz)
{
struct s5h1409_state* state = fe->demodulator_priv;
int ret = 0;
dprintk("%s(%d KHz)\n", __FUNCTION__, KHz);
if( (KHz == 44000) || (KHz == 5380) )
{
s5h1409_writereg(state, 0x87, 0x01be);
s5h1409_writereg(state, 0x88, 0x0436);
s5h1409_writereg(state, 0x89, 0x054d);
} else {
printk("%s() Invalid arg = %d KHz\n", __FUNCTION__, KHz);
ret = -1;
}
return ret;
}
static int s5h1409_set_spectralinversion(struct dvb_frontend* fe, int inverted)
{
struct s5h1409_state* state = fe->demodulator_priv;
dprintk("%s()\n", __FUNCTION__);
if(inverted == 1)
return s5h1409_writereg(state, 0x1b, 0x1101); /* Inverted */
else
return s5h1409_writereg(state, 0x1b, 0x0110); /* Normal */
}
static int s5h1409_enable_modulation(struct dvb_frontend* fe, fe_modulation_t m)
{
struct s5h1409_state* state = fe->demodulator_priv;
dprintk("%s(0x%08x)\n", __FUNCTION__, m);
switch(m) {
case VSB_8:
dprintk("%s() VSB_8\n", __FUNCTION__);
s5h1409_writereg(state, 0xf4, 0);
break;
case QAM_64:
dprintk("%s() QAM_64\n", __FUNCTION__);
s5h1409_writereg(state, 0xf4, 1);
s5h1409_writereg(state, 0x85, 0x100);
break;
case QAM_256:
dprintk("%s() QAM_256\n", __FUNCTION__);
s5h1409_writereg(state, 0xf4, 1);
s5h1409_writereg(state, 0x85, 0x101);
break;
default:
dprintk("%s() Invalid modulation\n", __FUNCTION__);
return -EINVAL;
}
state->current_modulation = m;
s5h1409_softreset(fe);
return 0;
}
static int s5h1409_i2c_gate_ctrl(struct dvb_frontend* fe, int enable)
{
struct s5h1409_state* state = fe->demodulator_priv;
dprintk("%s(%d)\n", __FUNCTION__, enable);
if (enable)
return s5h1409_writereg(state, 0xf3, 1);
else
return s5h1409_writereg(state, 0xf3, 0);
}
static int s5h1409_set_gpio(struct dvb_frontend* fe, int enable)
{
struct s5h1409_state* state = fe->demodulator_priv;
dprintk("%s(%d)\n", __FUNCTION__, enable);
if (enable)
return s5h1409_writereg(state, 0xe3, 0x1100);
else
return s5h1409_writereg(state, 0xe3, 0);
}
static int s5h1409_sleep(struct dvb_frontend* fe, int enable)
{
struct s5h1409_state* state = fe->demodulator_priv;
dprintk("%s(%d)\n", __FUNCTION__, enable);
return s5h1409_writereg(state, 0xf2, enable);
}
static int s5h1409_register_reset(struct dvb_frontend* fe)
{
struct s5h1409_state* state = fe->demodulator_priv;
dprintk("%s()\n", __FUNCTION__);
return s5h1409_writereg(state, 0xfa, 0);
}
/* Talk to the demod, set the FEC, GUARD, QAM settings etc */
static int s5h1409_set_frontend (struct dvb_frontend* fe, struct dvb_frontend_parameters *p)
{
struct s5h1409_state* state = fe->demodulator_priv;
dprintk("%s(frequency=%d)\n", __FUNCTION__, p->frequency);
s5h1409_softreset(fe);
state->current_frequency = p->frequency;
s5h1409_enable_modulation(fe, p->u.vsb.modulation);
if (fe->ops.tuner_ops.set_params) {
if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1);
fe->ops.tuner_ops.set_params(fe, p);
if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 0);
}
return 0;
}
/* Reset the demod hardware and reset all of the configuration registers
to a default state. */
static int s5h1409_init (struct dvb_frontend* fe)
{
int i;
struct s5h1409_state* state = fe->demodulator_priv;
dprintk("%s()\n", __FUNCTION__);
s5h1409_sleep(fe, 0);
s5h1409_register_reset(fe);
for (i=0; i<sizeof(init_tab) / sizeof (struct init_tab); i++)
s5h1409_writereg(state, init_tab[i].reg, init_tab[i].data);
/* The datasheet says that after initialisation, VSB is default */
state->current_modulation = VSB_8;
if (state->config->output_mode == S5H1409_SERIAL_OUTPUT)
s5h1409_writereg(state, 0xab, 0x100); /* Serial */
else
s5h1409_writereg(state, 0xab, 0x0); /* Parallel */
s5h1409_set_spectralinversion(fe, state->config->inversion);
s5h1409_set_if_freq(fe, state->config->if_freq);
s5h1409_set_gpio(fe, state->config->gpio);
s5h1409_softreset(fe);
/* Note: Leaving the I2C gate open here. */
s5h1409_i2c_gate_ctrl(fe, 1);
return 0;
}
static int s5h1409_read_status(struct dvb_frontend* fe, fe_status_t* status)
{
struct s5h1409_state* state = fe->demodulator_priv;
u16 reg;
u32 tuner_status = 0;
*status = 0;
/* Get the demodulator status */
reg = s5h1409_readreg(state, 0xf1);
if(reg & 0x1000)
*status |= FE_HAS_VITERBI;
if(reg & 0x8000)
*status |= FE_HAS_LOCK | FE_HAS_SYNC;
switch(state->config->status_mode) {
case S5H1409_DEMODLOCKING:
if (*status & FE_HAS_VITERBI)
*status |= FE_HAS_CARRIER | FE_HAS_SIGNAL;
break;
case S5H1409_TUNERLOCKING:
/* Get the tuner status */
if (fe->ops.tuner_ops.get_status) {
if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1);
fe->ops.tuner_ops.get_status(fe, &tuner_status);
if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 0);
}
if (tuner_status)
*status |= FE_HAS_CARRIER | FE_HAS_SIGNAL;
break;
}
dprintk("%s() status 0x%08x\n", __FUNCTION__, *status);
return 0;
}
static int s5h1409_qam256_lookup_snr(struct dvb_frontend* fe, u16* snr, u16 v)
{
int i, ret = -EINVAL;
dprintk("%s()\n", __FUNCTION__);
for (i=0; i<sizeof(qam256_snr_tab) / (sizeof(struct qam256_snr_tab)); i++) {
if (v < qam256_snr_tab[i].val) {
*snr = qam256_snr_tab[i].data;
ret = 0;
break;
}
}
return ret;
}
static int s5h1409_qam64_lookup_snr(struct dvb_frontend* fe, u16* snr, u16 v)
{
int i, ret = -EINVAL;
dprintk("%s()\n", __FUNCTION__);
for (i=0; i<sizeof(qam64_snr_tab) / (sizeof(struct qam64_snr_tab)); i++) {
if (v < qam64_snr_tab[i].val) {
*snr = qam64_snr_tab[i].data;
ret = 0;
break;
}
}
return ret;
}
static int s5h1409_vsb_lookup_snr(struct dvb_frontend* fe, u16* snr, u16 v)
{
int i, ret = -EINVAL;
dprintk("%s()\n", __FUNCTION__);
for (i=0; i<sizeof(vsb_snr_tab) / (sizeof(struct vsb_snr_tab)); i++) {
if (v > vsb_snr_tab[i].val) {
*snr = vsb_snr_tab[i].data;
ret = 0;
break;
}
}
dprintk("%s() snr=%d\n", __FUNCTION__, *snr);
return ret;
}
static int s5h1409_read_snr(struct dvb_frontend* fe, u16* snr)
{
struct s5h1409_state* state = fe->demodulator_priv;
u16 reg;
dprintk("%s()\n", __FUNCTION__);
reg = s5h1409_readreg(state, 0xf1) & 0x1ff;
switch(state->current_modulation) {
case QAM_64:
return s5h1409_qam64_lookup_snr(fe, snr, reg);
case QAM_256:
return s5h1409_qam256_lookup_snr(fe, snr, reg);
case VSB_8:
return s5h1409_vsb_lookup_snr(fe, snr, reg);
default:
break;
}
return -EINVAL;
}
static int s5h1409_read_signal_strength(struct dvb_frontend* fe, u16* signal_strength)
{
return s5h1409_read_snr(fe, signal_strength);
}
static int s5h1409_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks)
{
struct s5h1409_state* state = fe->demodulator_priv;
*ucblocks = s5h1409_readreg(state, 0xb5);
return 0;
}
static int s5h1409_read_ber(struct dvb_frontend* fe, u32* ber)
{
return s5h1409_read_ucblocks(fe, ber);
}
static int s5h1409_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p)
{
struct s5h1409_state* state = fe->demodulator_priv;
p->frequency = state->current_frequency;
p->u.vsb.modulation = state->current_modulation;
return 0;
}
static int s5h1409_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *tune)
{
tune->min_delay_ms = 1000;
return 0;
}
static void s5h1409_release(struct dvb_frontend* fe)
{
struct s5h1409_state* state = fe->demodulator_priv;
kfree(state);
}
static struct dvb_frontend_ops s5h1409_ops;
struct dvb_frontend* s5h1409_attach(const struct s5h1409_config* config,
struct i2c_adapter* i2c)
{
struct s5h1409_state* state = NULL;
/* allocate memory for the internal state */
state = kmalloc(sizeof(struct s5h1409_state), GFP_KERNEL);
if (state == NULL)
goto error;
/* setup the state */
state->config = config;
state->i2c = i2c;
state->current_modulation = 0;
/* check if the demod exists */
if (s5h1409_readreg(state, 0x04) != 0x0066)
goto error;
/* create dvb_frontend */
memcpy(&state->frontend.ops, &s5h1409_ops, sizeof(struct dvb_frontend_ops));
state->frontend.demodulator_priv = state;
/* Note: Leaving the I2C gate open here. */
s5h1409_writereg(state, 0xf3, 1);
return &state->frontend;
error:
kfree(state);
return NULL;
}
static struct dvb_frontend_ops s5h1409_ops = {
.info = {
.name = "Samsung S5H1409 QAM/8VSB Frontend",
.type = FE_ATSC,
.frequency_min = 54000000,
.frequency_max = 858000000,
.frequency_stepsize = 62500,
.caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB
},
.init = s5h1409_init,
.i2c_gate_ctrl = s5h1409_i2c_gate_ctrl,
.set_frontend = s5h1409_set_frontend,
.get_frontend = s5h1409_get_frontend,
.get_tune_settings = s5h1409_get_tune_settings,
.read_status = s5h1409_read_status,
.read_ber = s5h1409_read_ber,
.read_signal_strength = s5h1409_read_signal_strength,
.read_snr = s5h1409_read_snr,
.read_ucblocks = s5h1409_read_ucblocks,
.release = s5h1409_release,
};
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "Enable verbose debug messages");
MODULE_DESCRIPTION("Samsung S5H1409 QAM-B/ATSC Demodulator driver");
MODULE_AUTHOR("Steven Toth");
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(s5h1409_attach);

View file

@ -0,0 +1,68 @@
/*
Samsung S5H1409 VSB/QAM demodulator driver
Copyright (C) 2006 Steven Toth <stoth@hauppauge.com>
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.
*/
#ifndef S5H1409_H
#define S5H1409_H
#include <linux/dvb/frontend.h>
struct s5h1409_config
{
/* the demodulator's i2c address */
u8 demod_address;
/* serial/parallel output */
#define S5H1409_PARALLEL_OUTPUT 0
#define S5H1409_SERIAL_OUTPUT 1
u8 output_mode;
/* GPIO Setting */
#define S5H1409_GPIO_OFF 0
#define S5H1409_GPIO_ON 1
u8 gpio;
/* IF Freq in KHz */
u16 if_freq;
/* Spectral Inversion */
#define S5H1409_INVERSION_OFF 0
#define S5H1409_INVERSION_ON 1
u8 inversion;
/* Return lock status based on tuner lock, or demod lock */
#define S5H1409_TUNERLOCKING 0
#define S5H1409_DEMODLOCKING 1
u8 status_mode;
};
#if defined(CONFIG_DVB_S5H1409) || defined(CONFIG_DVB_S5H1409_MODULE)
extern struct dvb_frontend* s5h1409_attach(const struct s5h1409_config* config,
struct i2c_adapter* i2c);
#else
static inline struct dvb_frontend* s5h1409_attach(const struct s5h1409_config* config,
struct i2c_adapter* i2c)
{
printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __FUNCTION__);
return NULL;
}
#endif // CONFIG_DVB_S5H1409
#endif // S5H1409_H