![Mauro Carvalho Chehab](/assets/img/avatar_default.png)
The DVB API was originally defined using typedefs. This is against Kernel CodingStyle, and there's no good usage here. While we can't remove its usage on userspace, we can avoid its usage in Kernelspace. So, let's do it. This patch was generated by this shell script: for j in $(grep typedef include/uapi/linux/dvb/frontend.h |cut -d' ' -f 3); do for i in $(find drivers/media -name '*.[ch]' -type f) $(find drivers/staging/media -name '*.[ch]' -type f); do sed "s,${j}_t,enum $j," <$i >a && mv a $i; done; done While here, make CodingStyle fixes on the affected lines. Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com> Acked-by: Stefan Richter <stefanr@s5r6.in-berlin.de> # for drivers/media/firewire/*
826 lines
19 KiB
C
826 lines
19 KiB
C
/*
|
|
Driver for M88RS2000 demodulator and tuner
|
|
|
|
Copyright (C) 2012 Malcolm Priestley (tvboxspy@gmail.com)
|
|
Beta Driver
|
|
|
|
Include various calculation code from DS3000 driver.
|
|
Copyright (C) 2009 Konstantin Dimitrov.
|
|
|
|
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/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/string.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
|
|
|
|
#include "dvb_frontend.h"
|
|
#include "m88rs2000.h"
|
|
|
|
struct m88rs2000_state {
|
|
struct i2c_adapter *i2c;
|
|
const struct m88rs2000_config *config;
|
|
struct dvb_frontend frontend;
|
|
u8 no_lock_count;
|
|
u32 tuner_frequency;
|
|
u32 symbol_rate;
|
|
enum fe_code_rate fec_inner;
|
|
u8 tuner_level;
|
|
int errmode;
|
|
};
|
|
|
|
static int m88rs2000_debug;
|
|
|
|
module_param_named(debug, m88rs2000_debug, int, 0644);
|
|
MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able)).");
|
|
|
|
#define dprintk(level, args...) do { \
|
|
if (level & m88rs2000_debug) \
|
|
printk(KERN_DEBUG "m88rs2000-fe: " args); \
|
|
} while (0)
|
|
|
|
#define deb_info(args...) dprintk(0x01, args)
|
|
#define info(format, arg...) \
|
|
printk(KERN_INFO "m88rs2000-fe: " format "\n" , ## arg)
|
|
|
|
static int m88rs2000_writereg(struct m88rs2000_state *state,
|
|
u8 reg, u8 data)
|
|
{
|
|
int ret;
|
|
u8 buf[] = { reg, data };
|
|
struct i2c_msg msg = {
|
|
.addr = state->config->demod_addr,
|
|
.flags = 0,
|
|
.buf = buf,
|
|
.len = 2
|
|
};
|
|
|
|
ret = i2c_transfer(state->i2c, &msg, 1);
|
|
|
|
if (ret != 1)
|
|
deb_info("%s: writereg error (reg == 0x%02x, val == 0x%02x, "
|
|
"ret == %i)\n", __func__, reg, data, ret);
|
|
|
|
return (ret != 1) ? -EREMOTEIO : 0;
|
|
}
|
|
|
|
static u8 m88rs2000_readreg(struct m88rs2000_state *state, u8 reg)
|
|
{
|
|
int ret;
|
|
u8 b0[] = { reg };
|
|
u8 b1[] = { 0 };
|
|
|
|
struct i2c_msg msg[] = {
|
|
{
|
|
.addr = state->config->demod_addr,
|
|
.flags = 0,
|
|
.buf = b0,
|
|
.len = 1
|
|
}, {
|
|
.addr = state->config->demod_addr,
|
|
.flags = I2C_M_RD,
|
|
.buf = b1,
|
|
.len = 1
|
|
}
|
|
};
|
|
|
|
ret = i2c_transfer(state->i2c, msg, 2);
|
|
|
|
if (ret != 2)
|
|
deb_info("%s: readreg error (reg == 0x%02x, ret == %i)\n",
|
|
__func__, reg, ret);
|
|
|
|
return b1[0];
|
|
}
|
|
|
|
static u32 m88rs2000_get_mclk(struct dvb_frontend *fe)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
u32 mclk;
|
|
u8 reg;
|
|
/* Must not be 0x00 or 0xff */
|
|
reg = m88rs2000_readreg(state, 0x86);
|
|
if (!reg || reg == 0xff)
|
|
return 0;
|
|
|
|
reg /= 2;
|
|
reg += 1;
|
|
|
|
mclk = (u32)(reg * RS2000_FE_CRYSTAL_KHZ + 28 / 2) / 28;
|
|
|
|
return mclk;
|
|
}
|
|
|
|
static int m88rs2000_set_carrieroffset(struct dvb_frontend *fe, s16 offset)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
u32 mclk;
|
|
s32 tmp;
|
|
u8 reg;
|
|
int ret;
|
|
|
|
mclk = m88rs2000_get_mclk(fe);
|
|
if (!mclk)
|
|
return -EINVAL;
|
|
|
|
tmp = (offset * 4096 + (s32)mclk / 2) / (s32)mclk;
|
|
if (tmp < 0)
|
|
tmp += 4096;
|
|
|
|
/* Carrier Offset */
|
|
ret = m88rs2000_writereg(state, 0x9c, (u8)(tmp >> 4));
|
|
|
|
reg = m88rs2000_readreg(state, 0x9d);
|
|
reg &= 0xf;
|
|
reg |= (u8)(tmp & 0xf) << 4;
|
|
|
|
ret |= m88rs2000_writereg(state, 0x9d, reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int m88rs2000_set_symbolrate(struct dvb_frontend *fe, u32 srate)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
int ret;
|
|
u64 temp;
|
|
u32 mclk;
|
|
u8 b[3];
|
|
|
|
if ((srate < 1000000) || (srate > 45000000))
|
|
return -EINVAL;
|
|
|
|
mclk = m88rs2000_get_mclk(fe);
|
|
if (!mclk)
|
|
return -EINVAL;
|
|
|
|
temp = srate / 1000;
|
|
temp *= 1 << 24;
|
|
|
|
do_div(temp, mclk);
|
|
|
|
b[0] = (u8) (temp >> 16) & 0xff;
|
|
b[1] = (u8) (temp >> 8) & 0xff;
|
|
b[2] = (u8) temp & 0xff;
|
|
|
|
ret = m88rs2000_writereg(state, 0x93, b[2]);
|
|
ret |= m88rs2000_writereg(state, 0x94, b[1]);
|
|
ret |= m88rs2000_writereg(state, 0x95, b[0]);
|
|
|
|
if (srate > 10000000)
|
|
ret |= m88rs2000_writereg(state, 0xa0, 0x20);
|
|
else
|
|
ret |= m88rs2000_writereg(state, 0xa0, 0x60);
|
|
|
|
ret |= m88rs2000_writereg(state, 0xa1, 0xe0);
|
|
|
|
if (srate > 12000000)
|
|
ret |= m88rs2000_writereg(state, 0xa3, 0x20);
|
|
else if (srate > 2800000)
|
|
ret |= m88rs2000_writereg(state, 0xa3, 0x98);
|
|
else
|
|
ret |= m88rs2000_writereg(state, 0xa3, 0x90);
|
|
|
|
deb_info("m88rs2000: m88rs2000_set_symbolrate\n");
|
|
return ret;
|
|
}
|
|
|
|
static int m88rs2000_send_diseqc_msg(struct dvb_frontend *fe,
|
|
struct dvb_diseqc_master_cmd *m)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
|
|
int i;
|
|
u8 reg;
|
|
deb_info("%s\n", __func__);
|
|
m88rs2000_writereg(state, 0x9a, 0x30);
|
|
reg = m88rs2000_readreg(state, 0xb2);
|
|
reg &= 0x3f;
|
|
m88rs2000_writereg(state, 0xb2, reg);
|
|
for (i = 0; i < m->msg_len; i++)
|
|
m88rs2000_writereg(state, 0xb3 + i, m->msg[i]);
|
|
|
|
reg = m88rs2000_readreg(state, 0xb1);
|
|
reg &= 0x87;
|
|
reg |= ((m->msg_len - 1) << 3) | 0x07;
|
|
reg &= 0x7f;
|
|
m88rs2000_writereg(state, 0xb1, reg);
|
|
|
|
for (i = 0; i < 15; i++) {
|
|
if ((m88rs2000_readreg(state, 0xb1) & 0x40) == 0x0)
|
|
break;
|
|
msleep(20);
|
|
}
|
|
|
|
reg = m88rs2000_readreg(state, 0xb1);
|
|
if ((reg & 0x40) > 0x0) {
|
|
reg &= 0x7f;
|
|
reg |= 0x40;
|
|
m88rs2000_writereg(state, 0xb1, reg);
|
|
}
|
|
|
|
reg = m88rs2000_readreg(state, 0xb2);
|
|
reg &= 0x3f;
|
|
reg |= 0x80;
|
|
m88rs2000_writereg(state, 0xb2, reg);
|
|
m88rs2000_writereg(state, 0x9a, 0xb0);
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_send_diseqc_burst(struct dvb_frontend *fe,
|
|
enum fe_sec_mini_cmd burst)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
u8 reg0, reg1;
|
|
deb_info("%s\n", __func__);
|
|
m88rs2000_writereg(state, 0x9a, 0x30);
|
|
msleep(50);
|
|
reg0 = m88rs2000_readreg(state, 0xb1);
|
|
reg1 = m88rs2000_readreg(state, 0xb2);
|
|
/* TODO complete this section */
|
|
m88rs2000_writereg(state, 0xb2, reg1);
|
|
m88rs2000_writereg(state, 0xb1, reg0);
|
|
m88rs2000_writereg(state, 0x9a, 0xb0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_set_tone(struct dvb_frontend *fe,
|
|
enum fe_sec_tone_mode tone)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
u8 reg0, reg1;
|
|
m88rs2000_writereg(state, 0x9a, 0x30);
|
|
reg0 = m88rs2000_readreg(state, 0xb1);
|
|
reg1 = m88rs2000_readreg(state, 0xb2);
|
|
|
|
reg1 &= 0x3f;
|
|
|
|
switch (tone) {
|
|
case SEC_TONE_ON:
|
|
reg0 |= 0x4;
|
|
reg0 &= 0xbc;
|
|
break;
|
|
case SEC_TONE_OFF:
|
|
reg1 |= 0x80;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
m88rs2000_writereg(state, 0xb2, reg1);
|
|
m88rs2000_writereg(state, 0xb1, reg0);
|
|
m88rs2000_writereg(state, 0x9a, 0xb0);
|
|
return 0;
|
|
}
|
|
|
|
struct inittab {
|
|
u8 cmd;
|
|
u8 reg;
|
|
u8 val;
|
|
};
|
|
|
|
static struct inittab m88rs2000_setup[] = {
|
|
{DEMOD_WRITE, 0x9a, 0x30},
|
|
{DEMOD_WRITE, 0x00, 0x01},
|
|
{WRITE_DELAY, 0x19, 0x00},
|
|
{DEMOD_WRITE, 0x00, 0x00},
|
|
{DEMOD_WRITE, 0x9a, 0xb0},
|
|
{DEMOD_WRITE, 0x81, 0xc1},
|
|
{DEMOD_WRITE, 0x81, 0x81},
|
|
{DEMOD_WRITE, 0x86, 0xc6},
|
|
{DEMOD_WRITE, 0x9a, 0x30},
|
|
{DEMOD_WRITE, 0xf0, 0x22},
|
|
{DEMOD_WRITE, 0xf1, 0xbf},
|
|
{DEMOD_WRITE, 0xb0, 0x45},
|
|
{DEMOD_WRITE, 0xb2, 0x01}, /* set voltage pin always set 1*/
|
|
{DEMOD_WRITE, 0x9a, 0xb0},
|
|
{0xff, 0xaa, 0xff}
|
|
};
|
|
|
|
static struct inittab m88rs2000_shutdown[] = {
|
|
{DEMOD_WRITE, 0x9a, 0x30},
|
|
{DEMOD_WRITE, 0xb0, 0x00},
|
|
{DEMOD_WRITE, 0xf1, 0x89},
|
|
{DEMOD_WRITE, 0x00, 0x01},
|
|
{DEMOD_WRITE, 0x9a, 0xb0},
|
|
{DEMOD_WRITE, 0x81, 0x81},
|
|
{0xff, 0xaa, 0xff}
|
|
};
|
|
|
|
static struct inittab fe_reset[] = {
|
|
{DEMOD_WRITE, 0x00, 0x01},
|
|
{DEMOD_WRITE, 0x20, 0x81},
|
|
{DEMOD_WRITE, 0x21, 0x80},
|
|
{DEMOD_WRITE, 0x10, 0x33},
|
|
{DEMOD_WRITE, 0x11, 0x44},
|
|
{DEMOD_WRITE, 0x12, 0x07},
|
|
{DEMOD_WRITE, 0x18, 0x20},
|
|
{DEMOD_WRITE, 0x28, 0x04},
|
|
{DEMOD_WRITE, 0x29, 0x8e},
|
|
{DEMOD_WRITE, 0x3b, 0xff},
|
|
{DEMOD_WRITE, 0x32, 0x10},
|
|
{DEMOD_WRITE, 0x33, 0x02},
|
|
{DEMOD_WRITE, 0x34, 0x30},
|
|
{DEMOD_WRITE, 0x35, 0xff},
|
|
{DEMOD_WRITE, 0x38, 0x50},
|
|
{DEMOD_WRITE, 0x39, 0x68},
|
|
{DEMOD_WRITE, 0x3c, 0x7f},
|
|
{DEMOD_WRITE, 0x3d, 0x0f},
|
|
{DEMOD_WRITE, 0x45, 0x20},
|
|
{DEMOD_WRITE, 0x46, 0x24},
|
|
{DEMOD_WRITE, 0x47, 0x7c},
|
|
{DEMOD_WRITE, 0x48, 0x16},
|
|
{DEMOD_WRITE, 0x49, 0x04},
|
|
{DEMOD_WRITE, 0x4a, 0x01},
|
|
{DEMOD_WRITE, 0x4b, 0x78},
|
|
{DEMOD_WRITE, 0X4d, 0xd2},
|
|
{DEMOD_WRITE, 0x4e, 0x6d},
|
|
{DEMOD_WRITE, 0x50, 0x30},
|
|
{DEMOD_WRITE, 0x51, 0x30},
|
|
{DEMOD_WRITE, 0x54, 0x7b},
|
|
{DEMOD_WRITE, 0x56, 0x09},
|
|
{DEMOD_WRITE, 0x58, 0x59},
|
|
{DEMOD_WRITE, 0x59, 0x37},
|
|
{DEMOD_WRITE, 0x63, 0xfa},
|
|
{0xff, 0xaa, 0xff}
|
|
};
|
|
|
|
static struct inittab fe_trigger[] = {
|
|
{DEMOD_WRITE, 0x97, 0x04},
|
|
{DEMOD_WRITE, 0x99, 0x77},
|
|
{DEMOD_WRITE, 0x9b, 0x64},
|
|
{DEMOD_WRITE, 0x9e, 0x00},
|
|
{DEMOD_WRITE, 0x9f, 0xf8},
|
|
{DEMOD_WRITE, 0x98, 0xff},
|
|
{DEMOD_WRITE, 0xc0, 0x0f},
|
|
{DEMOD_WRITE, 0x89, 0x01},
|
|
{DEMOD_WRITE, 0x00, 0x00},
|
|
{WRITE_DELAY, 0x0a, 0x00},
|
|
{DEMOD_WRITE, 0x00, 0x01},
|
|
{DEMOD_WRITE, 0x00, 0x00},
|
|
{DEMOD_WRITE, 0x9a, 0xb0},
|
|
{0xff, 0xaa, 0xff}
|
|
};
|
|
|
|
static int m88rs2000_tab_set(struct m88rs2000_state *state,
|
|
struct inittab *tab)
|
|
{
|
|
int ret = 0;
|
|
u8 i;
|
|
if (tab == NULL)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < 255; i++) {
|
|
switch (tab[i].cmd) {
|
|
case 0x01:
|
|
ret = m88rs2000_writereg(state, tab[i].reg,
|
|
tab[i].val);
|
|
break;
|
|
case 0x10:
|
|
if (tab[i].reg > 0)
|
|
mdelay(tab[i].reg);
|
|
break;
|
|
case 0xff:
|
|
if (tab[i].reg == 0xaa && tab[i].val == 0xff)
|
|
return 0;
|
|
case 0x00:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
if (ret < 0)
|
|
return -ENODEV;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_set_voltage(struct dvb_frontend *fe,
|
|
enum fe_sec_voltage volt)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
u8 data;
|
|
|
|
data = m88rs2000_readreg(state, 0xb2);
|
|
data |= 0x03; /* bit0 V/H, bit1 off/on */
|
|
|
|
switch (volt) {
|
|
case SEC_VOLTAGE_18:
|
|
data &= ~0x03;
|
|
break;
|
|
case SEC_VOLTAGE_13:
|
|
data &= ~0x03;
|
|
data |= 0x01;
|
|
break;
|
|
case SEC_VOLTAGE_OFF:
|
|
break;
|
|
}
|
|
|
|
m88rs2000_writereg(state, 0xb2, data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_init(struct dvb_frontend *fe)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
int ret;
|
|
|
|
deb_info("m88rs2000: init chip\n");
|
|
/* Setup frontend from shutdown/cold */
|
|
if (state->config->inittab)
|
|
ret = m88rs2000_tab_set(state,
|
|
(struct inittab *)state->config->inittab);
|
|
else
|
|
ret = m88rs2000_tab_set(state, m88rs2000_setup);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int m88rs2000_sleep(struct dvb_frontend *fe)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
int ret;
|
|
/* Shutdown the frondend */
|
|
ret = m88rs2000_tab_set(state, m88rs2000_shutdown);
|
|
return ret;
|
|
}
|
|
|
|
static int m88rs2000_read_status(struct dvb_frontend *fe,
|
|
enum fe_status *status)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
u8 reg = m88rs2000_readreg(state, 0x8c);
|
|
|
|
*status = 0;
|
|
|
|
if ((reg & 0xee) == 0xee) {
|
|
*status = FE_HAS_CARRIER | FE_HAS_SIGNAL | FE_HAS_VITERBI
|
|
| FE_HAS_SYNC | FE_HAS_LOCK;
|
|
if (state->config->set_ts_params)
|
|
state->config->set_ts_params(fe, CALL_IS_READ);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_read_ber(struct dvb_frontend *fe, u32 *ber)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
u8 tmp0, tmp1;
|
|
|
|
m88rs2000_writereg(state, 0x9a, 0x30);
|
|
tmp0 = m88rs2000_readreg(state, 0xd8);
|
|
if ((tmp0 & 0x10) != 0) {
|
|
m88rs2000_writereg(state, 0x9a, 0xb0);
|
|
*ber = 0xffffffff;
|
|
return 0;
|
|
}
|
|
|
|
*ber = (m88rs2000_readreg(state, 0xd7) << 8) |
|
|
m88rs2000_readreg(state, 0xd6);
|
|
|
|
tmp1 = m88rs2000_readreg(state, 0xd9);
|
|
m88rs2000_writereg(state, 0xd9, (tmp1 & ~7) | 4);
|
|
/* needs twice */
|
|
m88rs2000_writereg(state, 0xd8, (tmp0 & ~8) | 0x30);
|
|
m88rs2000_writereg(state, 0xd8, (tmp0 & ~8) | 0x30);
|
|
m88rs2000_writereg(state, 0x9a, 0xb0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_read_signal_strength(struct dvb_frontend *fe,
|
|
u16 *strength)
|
|
{
|
|
if (fe->ops.tuner_ops.get_rf_strength)
|
|
fe->ops.tuner_ops.get_rf_strength(fe, strength);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_read_snr(struct dvb_frontend *fe, u16 *snr)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
|
|
*snr = 512 * m88rs2000_readreg(state, 0x65);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
u8 tmp;
|
|
|
|
*ucblocks = (m88rs2000_readreg(state, 0xd5) << 8) |
|
|
m88rs2000_readreg(state, 0xd4);
|
|
tmp = m88rs2000_readreg(state, 0xd8);
|
|
m88rs2000_writereg(state, 0xd8, tmp & ~0x20);
|
|
/* needs two times */
|
|
m88rs2000_writereg(state, 0xd8, tmp | 0x20);
|
|
m88rs2000_writereg(state, 0xd8, tmp | 0x20);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_set_fec(struct m88rs2000_state *state,
|
|
enum fe_code_rate fec)
|
|
{
|
|
u8 fec_set, reg;
|
|
int ret;
|
|
|
|
switch (fec) {
|
|
case FEC_1_2:
|
|
fec_set = 0x8;
|
|
break;
|
|
case FEC_2_3:
|
|
fec_set = 0x10;
|
|
break;
|
|
case FEC_3_4:
|
|
fec_set = 0x20;
|
|
break;
|
|
case FEC_5_6:
|
|
fec_set = 0x40;
|
|
break;
|
|
case FEC_7_8:
|
|
fec_set = 0x80;
|
|
break;
|
|
case FEC_AUTO:
|
|
default:
|
|
fec_set = 0x0;
|
|
}
|
|
|
|
reg = m88rs2000_readreg(state, 0x70);
|
|
reg &= 0x7;
|
|
ret = m88rs2000_writereg(state, 0x70, reg | fec_set);
|
|
|
|
ret |= m88rs2000_writereg(state, 0x76, 0x8);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static enum fe_code_rate m88rs2000_get_fec(struct m88rs2000_state *state)
|
|
{
|
|
u8 reg;
|
|
m88rs2000_writereg(state, 0x9a, 0x30);
|
|
reg = m88rs2000_readreg(state, 0x76);
|
|
m88rs2000_writereg(state, 0x9a, 0xb0);
|
|
|
|
reg &= 0xf0;
|
|
reg >>= 5;
|
|
|
|
switch (reg) {
|
|
case 0x4:
|
|
return FEC_1_2;
|
|
case 0x3:
|
|
return FEC_2_3;
|
|
case 0x2:
|
|
return FEC_3_4;
|
|
case 0x1:
|
|
return FEC_5_6;
|
|
case 0x0:
|
|
return FEC_7_8;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return FEC_AUTO;
|
|
}
|
|
|
|
static int m88rs2000_set_frontend(struct dvb_frontend *fe)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
enum fe_status status;
|
|
int i, ret = 0;
|
|
u32 tuner_freq;
|
|
s16 offset = 0;
|
|
u8 reg;
|
|
|
|
state->no_lock_count = 0;
|
|
|
|
if (c->delivery_system != SYS_DVBS) {
|
|
deb_info("%s: unsupported delivery "
|
|
"system selected (%d)\n",
|
|
__func__, c->delivery_system);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* Set Tuner */
|
|
if (fe->ops.tuner_ops.set_params)
|
|
ret = fe->ops.tuner_ops.set_params(fe);
|
|
|
|
if (ret < 0)
|
|
return -ENODEV;
|
|
|
|
if (fe->ops.tuner_ops.get_frequency)
|
|
ret = fe->ops.tuner_ops.get_frequency(fe, &tuner_freq);
|
|
|
|
if (ret < 0)
|
|
return -ENODEV;
|
|
|
|
offset = (s16)((s32)tuner_freq - c->frequency);
|
|
|
|
/* default mclk value 96.4285 * 2 * 1000 = 192857 */
|
|
if (((c->frequency % 192857) >= (192857 - 3000)) ||
|
|
(c->frequency % 192857) <= 3000)
|
|
ret = m88rs2000_writereg(state, 0x86, 0xc2);
|
|
else
|
|
ret = m88rs2000_writereg(state, 0x86, 0xc6);
|
|
|
|
ret |= m88rs2000_set_carrieroffset(fe, offset);
|
|
if (ret < 0)
|
|
return -ENODEV;
|
|
|
|
/* Reset demod by symbol rate */
|
|
if (c->symbol_rate > 27500000)
|
|
ret = m88rs2000_writereg(state, 0xf1, 0xa4);
|
|
else
|
|
ret = m88rs2000_writereg(state, 0xf1, 0xbf);
|
|
|
|
ret |= m88rs2000_tab_set(state, fe_reset);
|
|
if (ret < 0)
|
|
return -ENODEV;
|
|
|
|
/* Set FEC */
|
|
ret = m88rs2000_set_fec(state, c->fec_inner);
|
|
ret |= m88rs2000_writereg(state, 0x85, 0x1);
|
|
ret |= m88rs2000_writereg(state, 0x8a, 0xbf);
|
|
ret |= m88rs2000_writereg(state, 0x8d, 0x1e);
|
|
ret |= m88rs2000_writereg(state, 0x90, 0xf1);
|
|
ret |= m88rs2000_writereg(state, 0x91, 0x08);
|
|
|
|
if (ret < 0)
|
|
return -ENODEV;
|
|
|
|
/* Set Symbol Rate */
|
|
ret = m88rs2000_set_symbolrate(fe, c->symbol_rate);
|
|
if (ret < 0)
|
|
return -ENODEV;
|
|
|
|
/* Set up Demod */
|
|
ret = m88rs2000_tab_set(state, fe_trigger);
|
|
if (ret < 0)
|
|
return -ENODEV;
|
|
|
|
for (i = 0; i < 25; i++) {
|
|
reg = m88rs2000_readreg(state, 0x8c);
|
|
if ((reg & 0xee) == 0xee) {
|
|
status = FE_HAS_LOCK;
|
|
break;
|
|
}
|
|
state->no_lock_count++;
|
|
if (state->no_lock_count == 15) {
|
|
reg = m88rs2000_readreg(state, 0x70);
|
|
reg ^= 0x4;
|
|
m88rs2000_writereg(state, 0x70, reg);
|
|
state->no_lock_count = 0;
|
|
}
|
|
msleep(20);
|
|
}
|
|
|
|
if (status & FE_HAS_LOCK) {
|
|
state->fec_inner = m88rs2000_get_fec(state);
|
|
/* Uknown suspect SNR level */
|
|
reg = m88rs2000_readreg(state, 0x65);
|
|
}
|
|
|
|
state->tuner_frequency = c->frequency;
|
|
state->symbol_rate = c->symbol_rate;
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_get_frontend(struct dvb_frontend *fe)
|
|
{
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
c->fec_inner = state->fec_inner;
|
|
c->frequency = state->tuner_frequency;
|
|
c->symbol_rate = state->symbol_rate;
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_get_tune_settings(struct dvb_frontend *fe,
|
|
struct dvb_frontend_tune_settings *tune)
|
|
{
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
|
|
if (c->symbol_rate > 3000000)
|
|
tune->min_delay_ms = 2000;
|
|
else
|
|
tune->min_delay_ms = 3000;
|
|
|
|
tune->step_size = c->symbol_rate / 16000;
|
|
tune->max_drift = c->symbol_rate / 2000;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88rs2000_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
|
|
if (enable)
|
|
m88rs2000_writereg(state, 0x81, 0x84);
|
|
else
|
|
m88rs2000_writereg(state, 0x81, 0x81);
|
|
udelay(10);
|
|
return 0;
|
|
}
|
|
|
|
static void m88rs2000_release(struct dvb_frontend *fe)
|
|
{
|
|
struct m88rs2000_state *state = fe->demodulator_priv;
|
|
kfree(state);
|
|
}
|
|
|
|
static struct dvb_frontend_ops m88rs2000_ops = {
|
|
.delsys = { SYS_DVBS },
|
|
.info = {
|
|
.name = "M88RS2000 DVB-S",
|
|
.frequency_min = 950000,
|
|
.frequency_max = 2150000,
|
|
.frequency_stepsize = 1000, /* kHz for QPSK frontends */
|
|
.frequency_tolerance = 5000,
|
|
.symbol_rate_min = 1000000,
|
|
.symbol_rate_max = 45000000,
|
|
.symbol_rate_tolerance = 500, /* ppm */
|
|
.caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
|
|
FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 |
|
|
FE_CAN_QPSK | FE_CAN_INVERSION_AUTO |
|
|
FE_CAN_FEC_AUTO
|
|
},
|
|
|
|
.release = m88rs2000_release,
|
|
.init = m88rs2000_init,
|
|
.sleep = m88rs2000_sleep,
|
|
.i2c_gate_ctrl = m88rs2000_i2c_gate_ctrl,
|
|
.read_status = m88rs2000_read_status,
|
|
.read_ber = m88rs2000_read_ber,
|
|
.read_signal_strength = m88rs2000_read_signal_strength,
|
|
.read_snr = m88rs2000_read_snr,
|
|
.read_ucblocks = m88rs2000_read_ucblocks,
|
|
.diseqc_send_master_cmd = m88rs2000_send_diseqc_msg,
|
|
.diseqc_send_burst = m88rs2000_send_diseqc_burst,
|
|
.set_tone = m88rs2000_set_tone,
|
|
.set_voltage = m88rs2000_set_voltage,
|
|
|
|
.set_frontend = m88rs2000_set_frontend,
|
|
.get_frontend = m88rs2000_get_frontend,
|
|
.get_tune_settings = m88rs2000_get_tune_settings,
|
|
};
|
|
|
|
struct dvb_frontend *m88rs2000_attach(const struct m88rs2000_config *config,
|
|
struct i2c_adapter *i2c)
|
|
{
|
|
struct m88rs2000_state *state = NULL;
|
|
|
|
/* allocate memory for the internal state */
|
|
state = kzalloc(sizeof(struct m88rs2000_state), GFP_KERNEL);
|
|
if (state == NULL)
|
|
goto error;
|
|
|
|
/* setup the state */
|
|
state->config = config;
|
|
state->i2c = i2c;
|
|
state->tuner_frequency = 0;
|
|
state->symbol_rate = 0;
|
|
state->fec_inner = 0;
|
|
|
|
/* create dvb_frontend */
|
|
memcpy(&state->frontend.ops, &m88rs2000_ops,
|
|
sizeof(struct dvb_frontend_ops));
|
|
state->frontend.demodulator_priv = state;
|
|
return &state->frontend;
|
|
|
|
error:
|
|
kfree(state);
|
|
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(m88rs2000_attach);
|
|
|
|
MODULE_DESCRIPTION("M88RS2000 DVB-S Demodulator driver");
|
|
MODULE_AUTHOR("Malcolm Priestley tvboxspy@gmail.com");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION("1.13");
|
|
|