/*
Driver for Samsung S5H1420 QPSK Demodulator
Copyright (C) 2005 Andrew de Quincey <adq_dvb@lidskialf.net>
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/module.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <asm/div64.h>
#include "dvb_frontend.h"
#include "s5h1420.h"
#define TONE_FREQ 22000
struct s5h1420_state {
struct i2c_adapter* i2c;
const struct s5h1420_config* config;
struct dvb_frontend frontend;
u8 postlocked:1;
u32 fclk;
u32 tunedfreq;
fe_code_rate_t fec_inner;
u32 symbol_rate;
};
static u32 s5h1420_getsymbolrate(struct s5h1420_state* state);
static int s5h1420_get_tune_settings(struct dvb_frontend* fe,
struct dvb_frontend_tune_settings* fesettings);
static int debug = 0;
#define dprintk if (debug) printk
static int s5h1420_writereg (struct s5h1420_state* state, u8 reg, u8 data)
{
u8 buf [] = { reg, data };
struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = buf, .len = 2 };
int err;
if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) {
dprintk ("%s: writereg error (err == %i, reg == 0x%02x, data == 0x%02x)\n", __FUNCTION__, err, reg, data);
return -EREMOTEIO;
}
return 0;
}
static u8 s5h1420_readreg (struct s5h1420_state* state, u8 reg)
{
int ret;
u8 b0 [] = { reg };
u8 b1 [] = { 0 };
struct i2c_msg msg1 = { .addr = state->config->demod_address, .flags = 0, .buf = b0, .len = 1 };
struct i2c_msg msg2 = { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = b1, .len = 1 };
if ((ret = i2c_transfer (state->i2c, &msg1, 1)) != 1)
return ret;
if ((ret = i2c_transfer (state->i2c, &msg2, 1)) != 1)
return ret;
return b1[0];
}
static int s5h1420_set_voltage (struct dvb_frontend* fe, fe_sec_voltage_t voltage)
{
struct s5h1420_state* state = fe->demodulator_priv;
switch(voltage) {
case SEC_VOLTAGE_13:
s5h1420_writereg(state, 0x3c,
(s5h1420_readreg(state, 0x3c) & 0xfe) | 0x02);
break;
case SEC_VOLTAGE_18:
s5h1420_writereg(state, 0x3c, s5h1420_readreg(state, 0x3c) | 0x03);
break;
case SEC_VOLTAGE_OFF:
s5h1420_writereg(state, 0x3c, s5h1420_readreg(state, 0x3c) & 0xfd);
break;
}
return 0;
}
static int s5h1420_set_tone (struct dvb_frontend* fe, fe_sec_tone_mode_t tone)
{
struct s5h1420_state* state = fe->demodulator_priv;
switch(tone) {
case SEC_TONE_ON:
s5h1420_writereg(state, 0x3b,
(s5h1420_readreg(state, 0x3b) & 0x74) | 0x08);
break;
case SEC_TONE_OFF:
s5h1420_writereg(state, 0x3b,
(s5h1420_readreg(state, 0x3b) & 0x74) | 0x01);
break;
}
return 0;
}
static int s5h1420_send_master_cmd (struct dvb_frontend* fe,
struct dvb_diseqc_master_cmd* cmd)
{
struct s5h1420_state* state = fe->demodulator_priv;
u8 val;
int i;
unsigned long timeout;
int result = 0;
if (cmd->msg_len > 8)
return -EINVAL;
/* setup for DISEQC */
val = s5h1420_readreg(state, 0x3b);
s5h1420_writereg(state, 0x3b, 0x02);
msleep(15);
/* write the DISEQC command bytes */
for(i=0; i< cmd->msg_len; i++) {
s5h1420_writereg(state, 0x3d + i, cmd->msg[i]);
}
/* kick off transmission */
s5h1420_writereg(state, 0x3b, s5h1420_readreg(state, 0x3b) |
((cmd->msg_len-1) << 4) | 0x08);
/* wait for transmission to complete */
timeout = jiffies + ((100*HZ) / 1000);
while(time_before(jiffies, timeout)) {
if (!(s5h1420_readreg(state, 0x3b) & 0x08))
break;
msleep(5);
}
if (tim