mirror of
https://github.com/torvalds/linux.git
synced 2024-12-21 18:42:44 +00:00
c942fddf87
Based on 3 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 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 [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] 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 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 [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1589 lines
41 KiB
C
1589 lines
41 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
Conexant cx24120/cx24118 - DVBS/S2 Satellite demod/tuner driver
|
|
|
|
Copyright (C) 2008 Patrick Boettcher <pb@linuxtv.org>
|
|
Copyright (C) 2009 Sergey Tyurin <forum.free-x.de>
|
|
Updated 2012 by Jannis Achstetter <jannis_achstetter@web.de>
|
|
Copyright (C) 2015 Jemma Denson <jdenson@gmail.com>
|
|
April 2015
|
|
Refactored & simplified driver
|
|
Updated to work with delivery system supplied by DVBv5
|
|
Add frequency, fec & pilot to get_frontend
|
|
|
|
Cards supported: Technisat Skystar S2
|
|
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/init.h>
|
|
#include <linux/firmware.h>
|
|
#include <media/dvb_frontend.h>
|
|
#include "cx24120.h"
|
|
|
|
#define CX24120_SEARCH_RANGE_KHZ 5000
|
|
#define CX24120_FIRMWARE "dvb-fe-cx24120-1.20.58.2.fw"
|
|
|
|
/* cx24120 i2c registers */
|
|
#define CX24120_REG_CMD_START 0x00 /* write cmd_id */
|
|
#define CX24120_REG_CMD_ARGS 0x01 /* write command arguments */
|
|
#define CX24120_REG_CMD_END 0x1f /* write 0x01 for end */
|
|
|
|
#define CX24120_REG_MAILBOX 0x33
|
|
#define CX24120_REG_FREQ3 0x34 /* frequency */
|
|
#define CX24120_REG_FREQ2 0x35
|
|
#define CX24120_REG_FREQ1 0x36
|
|
|
|
#define CX24120_REG_FECMODE 0x39 /* FEC status */
|
|
#define CX24120_REG_STATUS 0x3a /* Tuner status */
|
|
#define CX24120_REG_SIGSTR_H 0x3a /* Signal strength high */
|
|
#define CX24120_REG_SIGSTR_L 0x3b /* Signal strength low byte */
|
|
#define CX24120_REG_QUALITY_H 0x40 /* SNR high byte */
|
|
#define CX24120_REG_QUALITY_L 0x41 /* SNR low byte */
|
|
|
|
#define CX24120_REG_BER_HH 0x47 /* BER high byte of high word */
|
|
#define CX24120_REG_BER_HL 0x48 /* BER low byte of high word */
|
|
#define CX24120_REG_BER_LH 0x49 /* BER high byte of low word */
|
|
#define CX24120_REG_BER_LL 0x4a /* BER low byte of low word */
|
|
|
|
#define CX24120_REG_UCB_H 0x50 /* UCB high byte */
|
|
#define CX24120_REG_UCB_L 0x51 /* UCB low byte */
|
|
|
|
#define CX24120_REG_CLKDIV 0xe6
|
|
#define CX24120_REG_RATEDIV 0xf0
|
|
|
|
#define CX24120_REG_REVISION 0xff /* Chip revision (ro) */
|
|
|
|
/* Command messages */
|
|
enum command_message_id {
|
|
CMD_VCO_SET = 0x10, /* cmd.len = 12; */
|
|
CMD_TUNEREQUEST = 0x11, /* cmd.len = 15; */
|
|
|
|
CMD_MPEG_ONOFF = 0x13, /* cmd.len = 4; */
|
|
CMD_MPEG_INIT = 0x14, /* cmd.len = 7; */
|
|
CMD_BANDWIDTH = 0x15, /* cmd.len = 12; */
|
|
CMD_CLOCK_READ = 0x16, /* read clock */
|
|
CMD_CLOCK_SET = 0x17, /* cmd.len = 10; */
|
|
|
|
CMD_DISEQC_MSG1 = 0x20, /* cmd.len = 11; */
|
|
CMD_DISEQC_MSG2 = 0x21, /* cmd.len = d->msg_len + 6; */
|
|
CMD_SETVOLTAGE = 0x22, /* cmd.len = 2; */
|
|
CMD_SETTONE = 0x23, /* cmd.len = 4; */
|
|
CMD_DISEQC_BURST = 0x24, /* cmd.len not used !!! */
|
|
|
|
CMD_READ_SNR = 0x1a, /* Read signal strength */
|
|
CMD_START_TUNER = 0x1b, /* ??? */
|
|
|
|
CMD_FWVERSION = 0x35,
|
|
|
|
CMD_BER_CTRL = 0x3c, /* cmd.len = 0x03; */
|
|
};
|
|
|
|
#define CX24120_MAX_CMD_LEN 30
|
|
|
|
/* pilot mask */
|
|
#define CX24120_PILOT_OFF 0x00
|
|
#define CX24120_PILOT_ON 0x40
|
|
#define CX24120_PILOT_AUTO 0x80
|
|
|
|
/* signal status */
|
|
#define CX24120_HAS_SIGNAL 0x01
|
|
#define CX24120_HAS_CARRIER 0x02
|
|
#define CX24120_HAS_VITERBI 0x04
|
|
#define CX24120_HAS_LOCK 0x08
|
|
#define CX24120_HAS_UNK1 0x10
|
|
#define CX24120_HAS_UNK2 0x20
|
|
#define CX24120_STATUS_MASK 0x0f
|
|
#define CX24120_SIGNAL_MASK 0xc0
|
|
|
|
/* ber window */
|
|
#define CX24120_BER_WINDOW 16
|
|
#define CX24120_BER_WSIZE ((1 << CX24120_BER_WINDOW) * 208 * 8)
|
|
|
|
#define info(args...) pr_info("cx24120: " args)
|
|
#define err(args...) pr_err("cx24120: ### ERROR: " args)
|
|
|
|
/* The Demod/Tuner can't easily provide these, we cache them */
|
|
struct cx24120_tuning {
|
|
u32 frequency;
|
|
u32 symbol_rate;
|
|
enum fe_spectral_inversion inversion;
|
|
enum fe_code_rate fec;
|
|
|
|
enum fe_delivery_system delsys;
|
|
enum fe_modulation modulation;
|
|
enum fe_pilot pilot;
|
|
|
|
/* Demod values */
|
|
u8 fec_val;
|
|
u8 fec_mask;
|
|
u8 clkdiv;
|
|
u8 ratediv;
|
|
u8 inversion_val;
|
|
u8 pilot_val;
|
|
};
|
|
|
|
/* Private state */
|
|
struct cx24120_state {
|
|
struct i2c_adapter *i2c;
|
|
const struct cx24120_config *config;
|
|
struct dvb_frontend frontend;
|
|
|
|
u8 cold_init;
|
|
u8 mpeg_enabled;
|
|
u8 need_clock_set;
|
|
|
|
/* current and next tuning parameters */
|
|
struct cx24120_tuning dcur;
|
|
struct cx24120_tuning dnxt;
|
|
|
|
enum fe_status fe_status;
|
|
|
|
/* dvbv5 stats calculations */
|
|
u32 bitrate;
|
|
u32 berw_usecs;
|
|
u32 ber_prev;
|
|
u32 ucb_offset;
|
|
unsigned long ber_jiffies_stats;
|
|
unsigned long per_jiffies_stats;
|
|
};
|
|
|
|
/* Command message to firmware */
|
|
struct cx24120_cmd {
|
|
u8 id;
|
|
u8 len;
|
|
u8 arg[CX24120_MAX_CMD_LEN];
|
|
};
|
|
|
|
/* Read single register */
|
|
static int cx24120_readreg(struct cx24120_state *state, u8 reg)
|
|
{
|
|
int ret;
|
|
u8 buf = 0;
|
|
struct i2c_msg msg[] = {
|
|
{
|
|
.addr = state->config->i2c_addr,
|
|
.flags = 0,
|
|
.len = 1,
|
|
.buf = ®
|
|
}, {
|
|
.addr = state->config->i2c_addr,
|
|
.flags = I2C_M_RD,
|
|
.len = 1,
|
|
.buf = &buf
|
|
}
|
|
};
|
|
|
|
ret = i2c_transfer(state->i2c, msg, 2);
|
|
if (ret != 2) {
|
|
err("Read error: reg=0x%02x, ret=%i)\n", reg, ret);
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(&state->i2c->dev, "reg=0x%02x; data=0x%02x\n", reg, buf);
|
|
|
|
return buf;
|
|
}
|
|
|
|
/* Write single register */
|
|
static int cx24120_writereg(struct cx24120_state *state, u8 reg, u8 data)
|
|
{
|
|
u8 buf[] = { reg, data };
|
|
struct i2c_msg msg = {
|
|
.addr = state->config->i2c_addr,
|
|
.flags = 0,
|
|
.buf = buf,
|
|
.len = 2
|
|
};
|
|
int ret;
|
|
|
|
ret = i2c_transfer(state->i2c, &msg, 1);
|
|
if (ret != 1) {
|
|
err("Write error: i2c_write error(err == %i, 0x%02x: 0x%02x)\n",
|
|
ret, reg, data);
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(&state->i2c->dev, "reg=0x%02x; data=0x%02x\n", reg, data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Write multiple registers in chunks of i2c_wr_max-sized buffers */
|
|
static int cx24120_writeregs(struct cx24120_state *state,
|
|
u8 reg, const u8 *values, u16 len, u8 incr)
|
|
{
|
|
int ret;
|
|
u16 max = state->config->i2c_wr_max > 0 ?
|
|
state->config->i2c_wr_max :
|
|
len;
|
|
|
|
struct i2c_msg msg = {
|
|
.addr = state->config->i2c_addr,
|
|
.flags = 0,
|
|
};
|
|
|
|
msg.buf = kmalloc(max + 1, GFP_KERNEL);
|
|
if (!msg.buf)
|
|
return -ENOMEM;
|
|
|
|
while (len) {
|
|
msg.buf[0] = reg;
|
|
msg.len = len > max ? max : len;
|
|
memcpy(&msg.buf[1], values, msg.len);
|
|
|
|
len -= msg.len; /* data length revers counter */
|
|
values += msg.len; /* incr data pointer */
|
|
|
|
if (incr)
|
|
reg += msg.len;
|
|
msg.len++; /* don't forget the addr byte */
|
|
|
|
ret = i2c_transfer(state->i2c, &msg, 1);
|
|
if (ret != 1) {
|
|
err("i2c_write error(err == %i, 0x%02x)\n", ret, reg);
|
|
goto out;
|
|
}
|
|
|
|
dev_dbg(&state->i2c->dev, "reg=0x%02x; data=%*ph\n",
|
|
reg, msg.len - 1, msg.buf + 1);
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
kfree(msg.buf);
|
|
return ret;
|
|
}
|
|
|
|
static const struct dvb_frontend_ops cx24120_ops;
|
|
|
|
struct dvb_frontend *cx24120_attach(const struct cx24120_config *config,
|
|
struct i2c_adapter *i2c)
|
|
{
|
|
struct cx24120_state *state;
|
|
int demod_rev;
|
|
|
|
info("Conexant cx24120/cx24118 - DVBS/S2 Satellite demod/tuner\n");
|
|
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
|
if (!state) {
|
|
err("Unable to allocate memory for cx24120_state\n");
|
|
goto error;
|
|
}
|
|
|
|
/* setup the state */
|
|
state->config = config;
|
|
state->i2c = i2c;
|
|
|
|
/* check if the demod is present and has proper type */
|
|
demod_rev = cx24120_readreg(state, CX24120_REG_REVISION);
|
|
switch (demod_rev) {
|
|
case 0x07:
|
|
info("Demod cx24120 rev. 0x07 detected.\n");
|
|
break;
|
|
case 0x05:
|
|
info("Demod cx24120 rev. 0x05 detected.\n");
|
|
break;
|
|
default:
|
|
err("Unsupported demod revision: 0x%x detected.\n", demod_rev);
|
|
goto error;
|
|
}
|
|
|
|
/* create dvb_frontend */
|
|
state->cold_init = 0;
|
|
memcpy(&state->frontend.ops, &cx24120_ops,
|
|
sizeof(struct dvb_frontend_ops));
|
|
state->frontend.demodulator_priv = state;
|
|
|
|
info("Conexant cx24120/cx24118 attached.\n");
|
|
return &state->frontend;
|
|
|
|
error:
|
|
kfree(state);
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(cx24120_attach);
|
|
|
|
static int cx24120_test_rom(struct cx24120_state *state)
|
|
{
|
|
int err, ret;
|
|
|
|
err = cx24120_readreg(state, 0xfd);
|
|
if (err & 4) {
|
|
ret = cx24120_readreg(state, 0xdf) & 0xfe;
|
|
err = cx24120_writereg(state, 0xdf, ret);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int cx24120_read_snr(struct dvb_frontend *fe, u16 *snr)
|
|
{
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
|
|
if (c->cnr.stat[0].scale != FE_SCALE_DECIBEL)
|
|
*snr = 0;
|
|
else
|
|
*snr = div_s64(c->cnr.stat[0].svalue, 100);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cx24120_read_ber(struct dvb_frontend *fe, u32 *ber)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
|
|
if (c->post_bit_error.stat[0].scale != FE_SCALE_COUNTER) {
|
|
*ber = 0;
|
|
return 0;
|
|
}
|
|
|
|
*ber = c->post_bit_error.stat[0].uvalue - state->ber_prev;
|
|
state->ber_prev = c->post_bit_error.stat[0].uvalue;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cx24120_msg_mpeg_output_global_config(struct cx24120_state *state,
|
|
u8 flag);
|
|
|
|
/* Check if we're running a command that needs to disable mpeg out */
|
|
static void cx24120_check_cmd(struct cx24120_state *state, u8 id)
|
|
{
|
|
switch (id) {
|
|
case CMD_TUNEREQUEST:
|
|
case CMD_CLOCK_READ:
|
|
case CMD_DISEQC_MSG1:
|
|
case CMD_DISEQC_MSG2:
|
|
case CMD_SETVOLTAGE:
|
|
case CMD_SETTONE:
|
|
case CMD_DISEQC_BURST:
|
|
cx24120_msg_mpeg_output_global_config(state, 0);
|
|
/* Old driver would do a msleep(100) here */
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Send a message to the firmware */
|
|
static int cx24120_message_send(struct cx24120_state *state,
|
|
struct cx24120_cmd *cmd)
|
|
{
|
|
int ficus;
|
|
|
|
if (state->mpeg_enabled) {
|
|
/* Disable mpeg out on certain commands */
|
|
cx24120_check_cmd(state, cmd->id);
|
|
}
|
|
|
|
cx24120_writereg(state, CX24120_REG_CMD_START, cmd->id);
|
|
cx24120_writeregs(state, CX24120_REG_CMD_ARGS, &cmd->arg[0],
|
|
cmd->len, 1);
|
|
cx24120_writereg(state, CX24120_REG_CMD_END, 0x01);
|
|
|
|
ficus = 1000;
|
|
while (cx24120_readreg(state, CX24120_REG_CMD_END)) {
|
|
msleep(20);
|
|
ficus -= 20;
|
|
if (ficus == 0) {
|
|
err("Error sending message to firmware\n");
|
|
return -EREMOTEIO;
|
|
}
|
|
}
|
|
dev_dbg(&state->i2c->dev, "sent message 0x%02x\n", cmd->id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Send a message and fill arg[] with the results */
|
|
static int cx24120_message_sendrcv(struct cx24120_state *state,
|
|
struct cx24120_cmd *cmd, u8 numreg)
|
|
{
|
|
int ret, i;
|
|
|
|
if (numreg > CX24120_MAX_CMD_LEN) {
|
|
err("Too many registers to read. cmd->reg = %d", numreg);
|
|
return -EREMOTEIO;
|
|
}
|
|
|
|
ret = cx24120_message_send(state, cmd);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
if (!numreg)
|
|
return 0;
|
|
|
|
/* Read numreg registers starting from register cmd->len */
|
|
for (i = 0; i < numreg; i++)
|
|
cmd->arg[i] = cx24120_readreg(state, (cmd->len + i + 1));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cx24120_read_signal_strength(struct dvb_frontend *fe,
|
|
u16 *signal_strength)
|
|
{
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
|
|
if (c->strength.stat[0].scale != FE_SCALE_RELATIVE)
|
|
*signal_strength = 0;
|
|
else
|
|
*signal_strength = c->strength.stat[0].uvalue;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cx24120_msg_mpeg_output_global_config(struct cx24120_state *state,
|
|
u8 enable)
|
|
{
|
|
struct cx24120_cmd cmd;
|
|
int ret;
|
|
|
|
cmd.id = CMD_MPEG_ONOFF;
|
|
cmd.len = 4;
|
|
cmd.arg[0] = 0x01;
|
|
cmd.arg[1] = 0x00;
|
|
cmd.arg[2] = enable ? 0 : (u8)(-1);
|
|
cmd.arg[3] = 0x01;
|
|
|
|
ret = cx24120_message_send(state, &cmd);
|
|
if (ret != 0) {
|
|
dev_dbg(&state->i2c->dev, "failed to %s MPEG output\n",
|
|
enable ? "enable" : "disable");
|
|
return ret;
|
|
}
|
|
|
|
state->mpeg_enabled = enable;
|
|
dev_dbg(&state->i2c->dev, "MPEG output %s\n",
|
|
enable ? "enabled" : "disabled");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cx24120_msg_mpeg_output_config(struct cx24120_state *state, u8 seq)
|
|
{
|
|
struct cx24120_cmd cmd;
|
|
struct cx24120_initial_mpeg_config i =
|
|
state->config->initial_mpeg_config;
|
|
|
|
cmd.id = CMD_MPEG_INIT;
|
|
cmd.len = 7;
|
|
cmd.arg[0] = seq; /* sequental number - can be 0,1,2 */
|
|
cmd.arg[1] = ((i.x1 & 0x01) << 1) | ((i.x1 >> 1) & 0x01);
|
|
cmd.arg[2] = 0x05;
|
|
cmd.arg[3] = 0x02;
|
|
cmd.arg[4] = ((i.x2 >> 1) & 0x01);
|
|
cmd.arg[5] = (i.x2 & 0xf0) | (i.x3 & 0x0f);
|
|
cmd.arg[6] = 0x10;
|
|
|
|
return cx24120_message_send(state, &cmd);
|
|
}
|
|
|
|
static int cx24120_diseqc_send_burst(struct dvb_frontend *fe,
|
|
enum fe_sec_mini_cmd burst)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
struct cx24120_cmd cmd;
|
|
|
|
dev_dbg(&state->i2c->dev, "\n");
|
|
|
|
/*
|
|
* Yes, cmd.len is set to zero. The old driver
|
|
* didn't specify any len, but also had a
|
|
* memset 0 before every use of the cmd struct
|
|
* which would have set it to zero.
|
|
* This quite probably needs looking into.
|
|
*/
|
|
cmd.id = CMD_DISEQC_BURST;
|
|
cmd.len = 0;
|
|
cmd.arg[0] = 0x00;
|
|
cmd.arg[1] = (burst == SEC_MINI_B) ? 0x01 : 0x00;
|
|
|
|
return cx24120_message_send(state, &cmd);
|
|
}
|
|
|
|
static int cx24120_set_tone(struct dvb_frontend *fe, enum fe_sec_tone_mode tone)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
struct cx24120_cmd cmd;
|
|
|
|
dev_dbg(&state->i2c->dev, "(%d)\n", tone);
|
|
|
|
if ((tone != SEC_TONE_ON) && (tone != SEC_TONE_OFF)) {
|
|
err("Invalid tone=%d\n", tone);
|
|
return -EINVAL;
|
|
}
|
|
|
|
cmd.id = CMD_SETTONE;
|
|
cmd.len = 4;
|
|
cmd.arg[0] = 0x00;
|
|
cmd.arg[1] = 0x00;
|
|
cmd.arg[2] = 0x00;
|
|
cmd.arg[3] = (tone == SEC_TONE_ON) ? 0x01 : 0x00;
|
|
|
|
return cx24120_message_send(state, &cmd);
|
|
}
|
|
|
|
static int cx24120_set_voltage(struct dvb_frontend *fe,
|
|
enum fe_sec_voltage voltage)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
struct cx24120_cmd cmd;
|
|
|
|
dev_dbg(&state->i2c->dev, "(%d)\n", voltage);
|
|
|
|
cmd.id = CMD_SETVOLTAGE;
|
|
cmd.len = 2;
|
|
cmd.arg[0] = 0x00;
|
|
cmd.arg[1] = (voltage == SEC_VOLTAGE_18) ? 0x01 : 0x00;
|
|
|
|
return cx24120_message_send(state, &cmd);
|
|
}
|
|
|
|
static int cx24120_send_diseqc_msg(struct dvb_frontend *fe,
|
|
struct dvb_diseqc_master_cmd *d)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
struct cx24120_cmd cmd;
|
|
int back_count;
|
|
|
|
dev_dbg(&state->i2c->dev, "\n");
|
|
|
|
cmd.id = CMD_DISEQC_MSG1;
|
|
cmd.len = 11;
|
|
cmd.arg[0] = 0x00;
|
|
cmd.arg[1] = 0x00;
|
|
cmd.arg[2] = 0x03;
|
|
cmd.arg[3] = 0x16;
|
|
cmd.arg[4] = 0x28;
|
|
cmd.arg[5] = 0x01;
|
|
cmd.arg[6] = 0x01;
|
|
cmd.arg[7] = 0x14;
|
|
cmd.arg[8] = 0x19;
|
|
cmd.arg[9] = 0x14;
|
|
cmd.arg[10] = 0x1e;
|
|
|
|
if (cx24120_message_send(state, &cmd)) {
|
|
err("send 1st message(0x%x) failed\n", cmd.id);
|
|
return -EREMOTEIO;
|
|
}
|
|
|
|
cmd.id = CMD_DISEQC_MSG2;
|
|
cmd.len = d->msg_len + 6;
|
|
cmd.arg[0] = 0x00;
|
|
cmd.arg[1] = 0x01;
|
|
cmd.arg[2] = 0x02;
|
|
cmd.arg[3] = 0x00;
|
|
cmd.arg[4] = 0x00;
|
|
cmd.arg[5] = d->msg_len;
|
|
|
|
memcpy(&cmd.arg[6], &d->msg, d->msg_len);
|
|
|
|
if (cx24120_message_send(state, &cmd)) {
|
|
err("send 2nd message(0x%x) failed\n", cmd.id);
|
|
return -EREMOTEIO;
|
|
}
|
|
|
|
back_count = 500;
|
|
do {
|
|
if (!(cx24120_readreg(state, 0x93) & 0x01)) {
|
|
dev_dbg(&state->i2c->dev, "diseqc sequence sent\n");
|
|
return 0;
|
|
}
|
|
msleep(20);
|
|
back_count -= 20;
|
|
} while (back_count);
|
|
|
|
err("Too long waiting for diseqc.\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static void cx24120_get_stats(struct cx24120_state *state)
|
|
{
|
|
struct dvb_frontend *fe = &state->frontend;
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
struct cx24120_cmd cmd;
|
|
int ret, cnr, msecs;
|
|
u16 sig, ucb;
|
|
u32 ber;
|
|
|
|
dev_dbg(&state->i2c->dev, "\n");
|
|
|
|
/* signal strength */
|
|
if (state->fe_status & FE_HAS_SIGNAL) {
|
|
cmd.id = CMD_READ_SNR;
|
|
cmd.len = 1;
|
|
cmd.arg[0] = 0x00;
|
|
|
|
ret = cx24120_message_send(state, &cmd);
|
|
if (ret != 0) {
|
|
err("error reading signal strength\n");
|
|
return;
|
|
}
|
|
|
|
/* raw */
|
|
sig = cx24120_readreg(state, CX24120_REG_SIGSTR_H) >> 6;
|
|
sig = sig << 8;
|
|
sig |= cx24120_readreg(state, CX24120_REG_SIGSTR_L);
|
|
dev_dbg(&state->i2c->dev,
|
|
"signal strength from firmware = 0x%x\n", sig);
|
|
|
|
/* cooked */
|
|
sig = -100 * sig + 94324;
|
|
|
|
c->strength.stat[0].scale = FE_SCALE_RELATIVE;
|
|
c->strength.stat[0].uvalue = sig;
|
|
} else {
|
|
c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
}
|
|
|
|
/* CNR */
|
|
if (state->fe_status & FE_HAS_VITERBI) {
|
|
cnr = cx24120_readreg(state, CX24120_REG_QUALITY_H) << 8;
|
|
cnr |= cx24120_readreg(state, CX24120_REG_QUALITY_L);
|
|
dev_dbg(&state->i2c->dev, "read SNR index = %d\n", cnr);
|
|
|
|
/* guessed - seems about right */
|
|
cnr = cnr * 100;
|
|
|
|
c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
|
|
c->cnr.stat[0].svalue = cnr;
|
|
} else {
|
|
c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
}
|
|
|
|
/* BER & UCB require lock */
|
|
if (!(state->fe_status & FE_HAS_LOCK)) {
|
|
c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
return;
|
|
}
|
|
|
|
/* BER */
|
|
if (time_after(jiffies, state->ber_jiffies_stats)) {
|
|
msecs = (state->berw_usecs + 500) / 1000;
|
|
state->ber_jiffies_stats = jiffies + msecs_to_jiffies(msecs);
|
|
|
|
ber = cx24120_readreg(state, CX24120_REG_BER_HH) << 24;
|
|
ber |= cx24120_readreg(state, CX24120_REG_BER_HL) << 16;
|
|
ber |= cx24120_readreg(state, CX24120_REG_BER_LH) << 8;
|
|
ber |= cx24120_readreg(state, CX24120_REG_BER_LL);
|
|
dev_dbg(&state->i2c->dev, "read BER index = %d\n", ber);
|
|
|
|
c->post_bit_error.stat[0].scale = FE_SCALE_COUNTER;
|
|
c->post_bit_error.stat[0].uvalue += ber;
|
|
|
|
c->post_bit_count.stat[0].scale = FE_SCALE_COUNTER;
|
|
c->post_bit_count.stat[0].uvalue += CX24120_BER_WSIZE;
|
|
}
|
|
|
|
/* UCB */
|
|
if (time_after(jiffies, state->per_jiffies_stats)) {
|
|
state->per_jiffies_stats = jiffies + msecs_to_jiffies(1000);
|
|
|
|
ucb = cx24120_readreg(state, CX24120_REG_UCB_H) << 8;
|
|
ucb |= cx24120_readreg(state, CX24120_REG_UCB_L);
|
|
dev_dbg(&state->i2c->dev, "ucblocks = %d\n", ucb);
|
|
|
|
/* handle reset */
|
|
if (ucb < state->ucb_offset)
|
|
state->ucb_offset = c->block_error.stat[0].uvalue;
|
|
|
|
c->block_error.stat[0].scale = FE_SCALE_COUNTER;
|
|
c->block_error.stat[0].uvalue = ucb + state->ucb_offset;
|
|
|
|
c->block_count.stat[0].scale = FE_SCALE_COUNTER;
|
|
c->block_count.stat[0].uvalue += state->bitrate / 8 / 208;
|
|
}
|
|
}
|
|
|
|
static void cx24120_set_clock_ratios(struct dvb_frontend *fe);
|
|
|
|
/* Read current tuning status */
|
|
static int cx24120_read_status(struct dvb_frontend *fe, enum fe_status *status)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
int lock;
|
|
|
|
lock = cx24120_readreg(state, CX24120_REG_STATUS);
|
|
|
|
dev_dbg(&state->i2c->dev, "status = 0x%02x\n", lock);
|
|
|
|
*status = 0;
|
|
|
|
if (lock & CX24120_HAS_SIGNAL)
|
|
*status = FE_HAS_SIGNAL;
|
|
if (lock & CX24120_HAS_CARRIER)
|
|
*status |= FE_HAS_CARRIER;
|
|
if (lock & CX24120_HAS_VITERBI)
|
|
*status |= FE_HAS_VITERBI | FE_HAS_SYNC;
|
|
if (lock & CX24120_HAS_LOCK)
|
|
*status |= FE_HAS_LOCK;
|
|
|
|
/*
|
|
* TODO: is FE_HAS_SYNC in the right place?
|
|
* Other cx241xx drivers have this slightly
|
|
* different
|
|
*/
|
|
|
|
state->fe_status = *status;
|
|
cx24120_get_stats(state);
|
|
|
|
/* Set the clock once tuned in */
|
|
if (state->need_clock_set && *status & FE_HAS_LOCK) {
|
|
/* Set clock ratios */
|
|
cx24120_set_clock_ratios(fe);
|
|
|
|
/* Old driver would do a msleep(200) here */
|
|
|
|
/* Renable mpeg output */
|
|
if (!state->mpeg_enabled)
|
|
cx24120_msg_mpeg_output_global_config(state, 1);
|
|
|
|
state->need_clock_set = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* FEC & modulation lookup table
|
|
* Used for decoding the REG_FECMODE register
|
|
* once tuned in.
|
|
*/
|
|
struct cx24120_modfec {
|
|
enum fe_delivery_system delsys;
|
|
enum fe_modulation mod;
|
|
enum fe_code_rate fec;
|
|
u8 val;
|
|
};
|
|
|
|
static const struct cx24120_modfec modfec_lookup_table[] = {
|
|
/*delsys mod fec val */
|
|
{ SYS_DVBS, QPSK, FEC_1_2, 0x01 },
|
|
{ SYS_DVBS, QPSK, FEC_2_3, 0x02 },
|
|
{ SYS_DVBS, QPSK, FEC_3_4, 0x03 },
|
|
{ SYS_DVBS, QPSK, FEC_4_5, 0x04 },
|
|
{ SYS_DVBS, QPSK, FEC_5_6, 0x05 },
|
|
{ SYS_DVBS, QPSK, FEC_6_7, 0x06 },
|
|
{ SYS_DVBS, QPSK, FEC_7_8, 0x07 },
|
|
|
|
{ SYS_DVBS2, QPSK, FEC_1_2, 0x04 },
|
|
{ SYS_DVBS2, QPSK, FEC_3_5, 0x05 },
|
|
{ SYS_DVBS2, QPSK, FEC_2_3, 0x06 },
|
|
{ SYS_DVBS2, QPSK, FEC_3_4, 0x07 },
|
|
{ SYS_DVBS2, QPSK, FEC_4_5, 0x08 },
|
|
{ SYS_DVBS2, QPSK, FEC_5_6, 0x09 },
|
|
{ SYS_DVBS2, QPSK, FEC_8_9, 0x0a },
|
|
{ SYS_DVBS2, QPSK, FEC_9_10, 0x0b },
|
|
|
|
{ SYS_DVBS2, PSK_8, FEC_3_5, 0x0c },
|
|
{ SYS_DVBS2, PSK_8, FEC_2_3, 0x0d },
|
|
{ SYS_DVBS2, PSK_8, FEC_3_4, 0x0e },
|
|
{ SYS_DVBS2, PSK_8, FEC_5_6, 0x0f },
|
|
{ SYS_DVBS2, PSK_8, FEC_8_9, 0x10 },
|
|
{ SYS_DVBS2, PSK_8, FEC_9_10, 0x11 },
|
|
};
|
|
|
|
/* Retrieve current fec, modulation & pilot values */
|
|
static int cx24120_get_fec(struct dvb_frontend *fe)
|
|
{
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
int idx;
|
|
int ret;
|
|
int fec;
|
|
|
|
ret = cx24120_readreg(state, CX24120_REG_FECMODE);
|
|
fec = ret & 0x3f; /* Lower 6 bits */
|
|
|
|
dev_dbg(&state->i2c->dev, "raw fec = %d\n", fec);
|
|
|
|
for (idx = 0; idx < ARRAY_SIZE(modfec_lookup_table); idx++) {
|
|
if (modfec_lookup_table[idx].delsys != state->dcur.delsys)
|
|
continue;
|
|
if (modfec_lookup_table[idx].val != fec)
|
|
continue;
|
|
|
|
break; /* found */
|
|
}
|
|
|
|
if (idx >= ARRAY_SIZE(modfec_lookup_table)) {
|
|
dev_dbg(&state->i2c->dev, "couldn't find fec!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* save values back to cache */
|
|
c->modulation = modfec_lookup_table[idx].mod;
|
|
c->fec_inner = modfec_lookup_table[idx].fec;
|
|
c->pilot = (ret & 0x80) ? PILOT_ON : PILOT_OFF;
|
|
|
|
dev_dbg(&state->i2c->dev, "mod(%d), fec(%d), pilot(%d)\n",
|
|
c->modulation, c->fec_inner, c->pilot);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Calculate ber window time */
|
|
static void cx24120_calculate_ber_window(struct cx24120_state *state, u32 rate)
|
|
{
|
|
struct dvb_frontend *fe = &state->frontend;
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
u64 tmp;
|
|
|
|
/*
|
|
* Calculate bitrate from rate in the clock ratios table.
|
|
* This isn't *exactly* right but close enough.
|
|
*/
|
|
tmp = (u64)c->symbol_rate * rate;
|
|
do_div(tmp, 256);
|
|
state->bitrate = tmp;
|
|
|
|
/* usecs per ber window */
|
|
tmp = 1000000ULL * CX24120_BER_WSIZE;
|
|
do_div(tmp, state->bitrate);
|
|
state->berw_usecs = tmp;
|
|
|
|
dev_dbg(&state->i2c->dev, "bitrate: %u, berw_usecs: %u\n",
|
|
state->bitrate, state->berw_usecs);
|
|
}
|
|
|
|
/*
|
|
* Clock ratios lookup table
|
|
*
|
|
* Values obtained from much larger table in old driver
|
|
* which had numerous entries which would never match.
|
|
*
|
|
* There's probably some way of calculating these but I
|
|
* can't determine the pattern
|
|
*/
|
|
struct cx24120_clock_ratios_table {
|
|
enum fe_delivery_system delsys;
|
|
enum fe_pilot pilot;
|
|
enum fe_modulation mod;
|
|
enum fe_code_rate fec;
|
|
u32 m_rat;
|
|
u32 n_rat;
|
|
u32 rate;
|
|
};
|
|
|
|
static const struct cx24120_clock_ratios_table clock_ratios_table[] = {
|
|
/*delsys pilot mod fec m_rat n_rat rate */
|
|
{ SYS_DVBS2, PILOT_OFF, QPSK, FEC_1_2, 273088, 254505, 274 },
|
|
{ SYS_DVBS2, PILOT_OFF, QPSK, FEC_3_5, 17272, 13395, 330 },
|
|
{ SYS_DVBS2, PILOT_OFF, QPSK, FEC_2_3, 24344, 16967, 367 },
|
|
{ SYS_DVBS2, PILOT_OFF, QPSK, FEC_3_4, 410788, 254505, 413 },
|
|
{ SYS_DVBS2, PILOT_OFF, QPSK, FEC_4_5, 438328, 254505, 440 },
|
|
{ SYS_DVBS2, PILOT_OFF, QPSK, FEC_5_6, 30464, 16967, 459 },
|
|
{ SYS_DVBS2, PILOT_OFF, QPSK, FEC_8_9, 487832, 254505, 490 },
|
|
{ SYS_DVBS2, PILOT_OFF, QPSK, FEC_9_10, 493952, 254505, 496 },
|
|
{ SYS_DVBS2, PILOT_OFF, PSK_8, FEC_3_5, 328168, 169905, 494 },
|
|
{ SYS_DVBS2, PILOT_OFF, PSK_8, FEC_2_3, 24344, 11327, 550 },
|
|
{ SYS_DVBS2, PILOT_OFF, PSK_8, FEC_3_4, 410788, 169905, 618 },
|
|
{ SYS_DVBS2, PILOT_OFF, PSK_8, FEC_5_6, 30464, 11327, 688 },
|
|
{ SYS_DVBS2, PILOT_OFF, PSK_8, FEC_8_9, 487832, 169905, 735 },
|
|
{ SYS_DVBS2, PILOT_OFF, PSK_8, FEC_9_10, 493952, 169905, 744 },
|
|
{ SYS_DVBS2, PILOT_ON, QPSK, FEC_1_2, 273088, 260709, 268 },
|
|
{ SYS_DVBS2, PILOT_ON, QPSK, FEC_3_5, 328168, 260709, 322 },
|
|
{ SYS_DVBS2, PILOT_ON, QPSK, FEC_2_3, 121720, 86903, 358 },
|
|
{ SYS_DVBS2, PILOT_ON, QPSK, FEC_3_4, 410788, 260709, 403 },
|
|
{ SYS_DVBS2, PILOT_ON, QPSK, FEC_4_5, 438328, 260709, 430 },
|
|
{ SYS_DVBS2, PILOT_ON, QPSK, FEC_5_6, 152320, 86903, 448 },
|
|
{ SYS_DVBS2, PILOT_ON, QPSK, FEC_8_9, 487832, 260709, 479 },
|
|
{ SYS_DVBS2, PILOT_ON, QPSK, FEC_9_10, 493952, 260709, 485 },
|
|
{ SYS_DVBS2, PILOT_ON, PSK_8, FEC_3_5, 328168, 173853, 483 },
|
|
{ SYS_DVBS2, PILOT_ON, PSK_8, FEC_2_3, 121720, 57951, 537 },
|
|
{ SYS_DVBS2, PILOT_ON, PSK_8, FEC_3_4, 410788, 173853, 604 },
|
|
{ SYS_DVBS2, PILOT_ON, PSK_8, FEC_5_6, 152320, 57951, 672 },
|
|
{ SYS_DVBS2, PILOT_ON, PSK_8, FEC_8_9, 487832, 173853, 718 },
|
|
{ SYS_DVBS2, PILOT_ON, PSK_8, FEC_9_10, 493952, 173853, 727 },
|
|
{ SYS_DVBS, PILOT_OFF, QPSK, FEC_1_2, 152592, 152592, 256 },
|
|
{ SYS_DVBS, PILOT_OFF, QPSK, FEC_2_3, 305184, 228888, 341 },
|
|
{ SYS_DVBS, PILOT_OFF, QPSK, FEC_3_4, 457776, 305184, 384 },
|
|
{ SYS_DVBS, PILOT_OFF, QPSK, FEC_5_6, 762960, 457776, 427 },
|
|
{ SYS_DVBS, PILOT_OFF, QPSK, FEC_7_8, 1068144, 610368, 448 },
|
|
};
|
|
|
|
/* Set clock ratio from lookup table */
|
|
static void cx24120_set_clock_ratios(struct dvb_frontend *fe)
|
|
{
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
struct cx24120_cmd cmd;
|
|
int ret, idx;
|
|
|
|
/* Find fec, modulation, pilot */
|
|
ret = cx24120_get_fec(fe);
|
|
if (ret != 0)
|
|
return;
|
|
|
|
/* Find the clock ratios in the lookup table */
|
|
for (idx = 0; idx < ARRAY_SIZE(clock_ratios_table); idx++) {
|
|
if (clock_ratios_table[idx].delsys != state->dcur.delsys)
|
|
continue;
|
|
if (clock_ratios_table[idx].mod != c->modulation)
|
|
continue;
|
|
if (clock_ratios_table[idx].fec != c->fec_inner)
|
|
continue;
|
|
if (clock_ratios_table[idx].pilot != c->pilot)
|
|
continue;
|
|
|
|
break; /* found */
|
|
}
|
|
|
|
if (idx >= ARRAY_SIZE(clock_ratios_table)) {
|
|
info("Clock ratio not found - data reception in danger\n");
|
|
return;
|
|
}
|
|
|
|
/* Read current values? */
|
|
cmd.id = CMD_CLOCK_READ;
|
|
cmd.len = 1;
|
|
cmd.arg[0] = 0x00;
|
|
ret = cx24120_message_sendrcv(state, &cmd, 6);
|
|
if (ret != 0)
|
|
return;
|
|
/* in cmd[0]-[5] - result */
|
|
|
|
dev_dbg(&state->i2c->dev, "m=%d, n=%d; idx: %d m=%d, n=%d, rate=%d\n",
|
|
cmd.arg[2] | (cmd.arg[1] << 8) | (cmd.arg[0] << 16),
|
|
cmd.arg[5] | (cmd.arg[4] << 8) | (cmd.arg[3] << 16),
|
|
idx,
|
|
clock_ratios_table[idx].m_rat,
|
|
clock_ratios_table[idx].n_rat,
|
|
clock_ratios_table[idx].rate);
|
|
|
|
/* Set the clock */
|
|
cmd.id = CMD_CLOCK_SET;
|
|
cmd.len = 10;
|
|
cmd.arg[0] = 0;
|
|
cmd.arg[1] = 0x10;
|
|
cmd.arg[2] = (clock_ratios_table[idx].m_rat >> 16) & 0xff;
|
|
cmd.arg[3] = (clock_ratios_table[idx].m_rat >> 8) & 0xff;
|
|
cmd.arg[4] = (clock_ratios_table[idx].m_rat >> 0) & 0xff;
|
|
cmd.arg[5] = (clock_ratios_table[idx].n_rat >> 16) & 0xff;
|
|
cmd.arg[6] = (clock_ratios_table[idx].n_rat >> 8) & 0xff;
|
|
cmd.arg[7] = (clock_ratios_table[idx].n_rat >> 0) & 0xff;
|
|
cmd.arg[8] = (clock_ratios_table[idx].rate >> 8) & 0xff;
|
|
cmd.arg[9] = (clock_ratios_table[idx].rate >> 0) & 0xff;
|
|
|
|
cx24120_message_send(state, &cmd);
|
|
|
|
/* Calculate ber window rates for stat work */
|
|
cx24120_calculate_ber_window(state, clock_ratios_table[idx].rate);
|
|
}
|
|
|
|
/* Set inversion value */
|
|
static int cx24120_set_inversion(struct cx24120_state *state,
|
|
enum fe_spectral_inversion inversion)
|
|
{
|
|
dev_dbg(&state->i2c->dev, "(%d)\n", inversion);
|
|
|
|
switch (inversion) {
|
|
case INVERSION_OFF:
|
|
state->dnxt.inversion_val = 0x00;
|
|
break;
|
|
case INVERSION_ON:
|
|
state->dnxt.inversion_val = 0x04;
|
|
break;
|
|
case INVERSION_AUTO:
|
|
state->dnxt.inversion_val = 0x0c;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
state->dnxt.inversion = inversion;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* FEC lookup table for tuning */
|
|
struct cx24120_modfec_table {
|
|
enum fe_delivery_system delsys;
|
|
enum fe_modulation mod;
|
|
enum fe_code_rate fec;
|
|
u8 val;
|
|
};
|
|
|
|
static const struct cx24120_modfec_table modfec_table[] = {
|
|
/*delsys mod fec val */
|
|
{ SYS_DVBS, QPSK, FEC_1_2, 0x2e },
|
|
{ SYS_DVBS, QPSK, FEC_2_3, 0x2f },
|
|
{ SYS_DVBS, QPSK, FEC_3_4, 0x30 },
|
|
{ SYS_DVBS, QPSK, FEC_5_6, 0x31 },
|
|
{ SYS_DVBS, QPSK, FEC_6_7, 0x32 },
|
|
{ SYS_DVBS, QPSK, FEC_7_8, 0x33 },
|
|
|
|
{ SYS_DVBS2, QPSK, FEC_1_2, 0x04 },
|
|
{ SYS_DVBS2, QPSK, FEC_3_5, 0x05 },
|
|
{ SYS_DVBS2, QPSK, FEC_2_3, 0x06 },
|
|
{ SYS_DVBS2, QPSK, FEC_3_4, 0x07 },
|
|
{ SYS_DVBS2, QPSK, FEC_4_5, 0x08 },
|
|
{ SYS_DVBS2, QPSK, FEC_5_6, 0x09 },
|
|
{ SYS_DVBS2, QPSK, FEC_8_9, 0x0a },
|
|
{ SYS_DVBS2, QPSK, FEC_9_10, 0x0b },
|
|
|
|
{ SYS_DVBS2, PSK_8, FEC_3_5, 0x0c },
|
|
{ SYS_DVBS2, PSK_8, FEC_2_3, 0x0d },
|
|
{ SYS_DVBS2, PSK_8, FEC_3_4, 0x0e },
|
|
{ SYS_DVBS2, PSK_8, FEC_5_6, 0x0f },
|
|
{ SYS_DVBS2, PSK_8, FEC_8_9, 0x10 },
|
|
{ SYS_DVBS2, PSK_8, FEC_9_10, 0x11 },
|
|
};
|
|
|
|
/* Set fec_val & fec_mask values from delsys, modulation & fec */
|
|
static int cx24120_set_fec(struct cx24120_state *state, enum fe_modulation mod,
|
|
enum fe_code_rate fec)
|
|
{
|
|
int idx;
|
|
|
|
dev_dbg(&state->i2c->dev, "(0x%02x,0x%02x)\n", mod, fec);
|
|
|
|
state->dnxt.fec = fec;
|
|
|
|
/* Lookup fec_val from modfec table */
|
|
for (idx = 0; idx < ARRAY_SIZE(modfec_table); idx++) {
|
|
if (modfec_table[idx].delsys != state->dnxt.delsys)
|
|
continue;
|
|
if (modfec_table[idx].mod != mod)
|
|
continue;
|
|
if (modfec_table[idx].fec != fec)
|
|
continue;
|
|
|
|
/* found */
|
|
state->dnxt.fec_mask = 0x00;
|
|
state->dnxt.fec_val = modfec_table[idx].val;
|
|
return 0;
|
|
}
|
|
|
|
if (state->dnxt.delsys == SYS_DVBS2) {
|
|
/* DVBS2 auto is 0x00/0x00 */
|
|
state->dnxt.fec_mask = 0x00;
|
|
state->dnxt.fec_val = 0x00;
|
|
} else {
|
|
/* Set DVB-S to auto */
|
|
state->dnxt.fec_val = 0x2e;
|
|
state->dnxt.fec_mask = 0xac;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set pilot */
|
|
static int cx24120_set_pilot(struct cx24120_state *state, enum fe_pilot pilot)
|
|
{
|
|
dev_dbg(&state->i2c->dev, "(%d)\n", pilot);
|
|
|
|
/* Pilot only valid in DVBS2 */
|
|
if (state->dnxt.delsys != SYS_DVBS2) {
|
|
state->dnxt.pilot_val = CX24120_PILOT_OFF;
|
|
return 0;
|
|
}
|
|
|
|
switch (pilot) {
|
|
case PILOT_OFF:
|
|
state->dnxt.pilot_val = CX24120_PILOT_OFF;
|
|
break;
|
|
case PILOT_ON:
|
|
state->dnxt.pilot_val = CX24120_PILOT_ON;
|
|
break;
|
|
case PILOT_AUTO:
|
|
default:
|
|
state->dnxt.pilot_val = CX24120_PILOT_AUTO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set symbol rate */
|
|
static int cx24120_set_symbolrate(struct cx24120_state *state, u32 rate)
|
|
{
|
|
dev_dbg(&state->i2c->dev, "(%d)\n", rate);
|
|
|
|
state->dnxt.symbol_rate = rate;
|
|
|
|
/* Check symbol rate */
|
|
if (rate > 31000000) {
|
|
state->dnxt.clkdiv = (-(rate < 31000001) & 3) + 2;
|
|
state->dnxt.ratediv = (-(rate < 31000001) & 6) + 4;
|
|
} else {
|
|
state->dnxt.clkdiv = 3;
|
|
state->dnxt.ratediv = 6;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Overwrite the current tuning params, we are about to tune */
|
|
static void cx24120_clone_params(struct dvb_frontend *fe)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
|
|
state->dcur = state->dnxt;
|
|
}
|
|
|
|
static int cx24120_set_frontend(struct dvb_frontend *fe)
|
|
{
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
struct cx24120_cmd cmd;
|
|
int ret;
|
|
|
|
switch (c->delivery_system) {
|
|
case SYS_DVBS2:
|
|
dev_dbg(&state->i2c->dev, "DVB-S2\n");
|
|
break;
|
|
case SYS_DVBS:
|
|
dev_dbg(&state->i2c->dev, "DVB-S\n");
|
|
break;
|
|
default:
|
|
dev_dbg(&state->i2c->dev,
|
|
"delivery system(%d) not supported\n",
|
|
c->delivery_system);
|
|
return -EINVAL;
|
|
}
|
|
|
|
state->dnxt.delsys = c->delivery_system;
|
|
state->dnxt.modulation = c->modulation;
|
|
state->dnxt.frequency = c->frequency;
|
|
state->dnxt.pilot = c->pilot;
|
|
|
|
ret = cx24120_set_inversion(state, c->inversion);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
ret = cx24120_set_fec(state, c->modulation, c->fec_inner);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
ret = cx24120_set_pilot(state, c->pilot);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
ret = cx24120_set_symbolrate(state, c->symbol_rate);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
/* discard the 'current' tuning parameters and prepare to tune */
|
|
cx24120_clone_params(fe);
|
|
|
|
dev_dbg(&state->i2c->dev,
|
|
"delsys = %d\n", state->dcur.delsys);
|
|
dev_dbg(&state->i2c->dev,
|
|
"modulation = %d\n", state->dcur.modulation);
|
|
dev_dbg(&state->i2c->dev,
|
|
"frequency = %d\n", state->dcur.frequency);
|
|
dev_dbg(&state->i2c->dev,
|
|
"pilot = %d (val = 0x%02x)\n",
|
|
state->dcur.pilot, state->dcur.pilot_val);
|
|
dev_dbg(&state->i2c->dev,
|
|
"symbol_rate = %d (clkdiv/ratediv = 0x%02x/0x%02x)\n",
|
|
state->dcur.symbol_rate,
|
|
state->dcur.clkdiv, state->dcur.ratediv);
|
|
dev_dbg(&state->i2c->dev,
|
|
"FEC = %d (mask/val = 0x%02x/0x%02x)\n",
|
|
state->dcur.fec, state->dcur.fec_mask, state->dcur.fec_val);
|
|
dev_dbg(&state->i2c->dev,
|
|
"Inversion = %d (val = 0x%02x)\n",
|
|
state->dcur.inversion, state->dcur.inversion_val);
|
|
|
|
/* Flag that clock needs to be set after tune */
|
|
state->need_clock_set = 1;
|
|
|
|
/* Tune in */
|
|
cmd.id = CMD_TUNEREQUEST;
|
|
cmd.len = 15;
|
|
cmd.arg[0] = 0;
|
|
cmd.arg[1] = (state->dcur.frequency & 0xff0000) >> 16;
|
|
cmd.arg[2] = (state->dcur.frequency & 0x00ff00) >> 8;
|
|
cmd.arg[3] = (state->dcur.frequency & 0x0000ff);
|
|
cmd.arg[4] = ((state->dcur.symbol_rate / 1000) & 0xff00) >> 8;
|
|
cmd.arg[5] = ((state->dcur.symbol_rate / 1000) & 0x00ff);
|
|
cmd.arg[6] = state->dcur.inversion;
|
|
cmd.arg[7] = state->dcur.fec_val | state->dcur.pilot_val;
|
|
cmd.arg[8] = CX24120_SEARCH_RANGE_KHZ >> 8;
|
|
cmd.arg[9] = CX24120_SEARCH_RANGE_KHZ & 0xff;
|
|
cmd.arg[10] = 0; /* maybe rolloff? */
|
|
cmd.arg[11] = state->dcur.fec_mask;
|
|
cmd.arg[12] = state->dcur.ratediv;
|
|
cmd.arg[13] = state->dcur.clkdiv;
|
|
cmd.arg[14] = 0;
|
|
|
|
/* Send tune command */
|
|
ret = cx24120_message_send(state, &cmd);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
/* Write symbol rate values */
|
|
ret = cx24120_writereg(state, CX24120_REG_CLKDIV, state->dcur.clkdiv);
|
|
ret = cx24120_readreg(state, CX24120_REG_RATEDIV);
|
|
ret &= 0xfffffff0;
|
|
ret |= state->dcur.ratediv;
|
|
ret = cx24120_writereg(state, CX24120_REG_RATEDIV, ret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set vco from config */
|
|
static int cx24120_set_vco(struct cx24120_state *state)
|
|
{
|
|
struct cx24120_cmd cmd;
|
|
u32 nxtal_khz, vco;
|
|
u64 inv_vco;
|
|
u32 xtal_khz = state->config->xtal_khz;
|
|
|
|
nxtal_khz = xtal_khz * 4;
|
|
vco = nxtal_khz * 10;
|
|
inv_vco = DIV_ROUND_CLOSEST_ULL(0x400000000ULL, vco);
|
|
|
|
dev_dbg(&state->i2c->dev, "xtal=%d, vco=%d, inv_vco=%lld\n",
|
|
xtal_khz, vco, inv_vco);
|
|
|
|
cmd.id = CMD_VCO_SET;
|
|
cmd.len = 12;
|
|
cmd.arg[0] = (vco >> 16) & 0xff;
|
|
cmd.arg[1] = (vco >> 8) & 0xff;
|
|
cmd.arg[2] = vco & 0xff;
|
|
cmd.arg[3] = (inv_vco >> 8) & 0xff;
|
|
cmd.arg[4] = (inv_vco) & 0xff;
|
|
cmd.arg[5] = 0x03;
|
|
cmd.arg[6] = (nxtal_khz >> 8) & 0xff;
|
|
cmd.arg[7] = nxtal_khz & 0xff;
|
|
cmd.arg[8] = 0x06;
|
|
cmd.arg[9] = 0x03;
|
|
cmd.arg[10] = (xtal_khz >> 16) & 0xff;
|
|
cmd.arg[11] = xtal_khz & 0xff;
|
|
|
|
return cx24120_message_send(state, &cmd);
|
|
}
|
|
|
|
static int cx24120_init(struct dvb_frontend *fe)
|
|
{
|
|
const struct firmware *fw;
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
struct cx24120_cmd cmd;
|
|
u8 reg;
|
|
int ret, i;
|
|
unsigned char vers[4];
|
|
|
|
if (state->cold_init)
|
|
return 0;
|
|
|
|
/* ???? */
|
|
cx24120_writereg(state, 0xea, 0x00);
|
|
cx24120_test_rom(state);
|
|
reg = cx24120_readreg(state, 0xfb) & 0xfe;
|
|
cx24120_writereg(state, 0xfb, reg);
|
|
reg = cx24120_readreg(state, 0xfc) & 0xfe;
|
|
cx24120_writereg(state, 0xfc, reg);
|
|
cx24120_writereg(state, 0xc3, 0x04);
|
|
cx24120_writereg(state, 0xc4, 0x04);
|
|
cx24120_writereg(state, 0xce, 0x00);
|
|
cx24120_writereg(state, 0xcf, 0x00);
|
|
reg = cx24120_readreg(state, 0xea) & 0xfe;
|
|
cx24120_writereg(state, 0xea, reg);
|
|
cx24120_writereg(state, 0xeb, 0x0c);
|
|
cx24120_writereg(state, 0xec, 0x06);
|
|
cx24120_writereg(state, 0xed, 0x05);
|
|
cx24120_writereg(state, 0xee, 0x03);
|
|
cx24120_writereg(state, 0xef, 0x05);
|
|
cx24120_writereg(state, 0xf3, 0x03);
|
|
cx24120_writereg(state, 0xf4, 0x44);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
cx24120_writereg(state, 0xf0 + i, 0x04);
|
|
cx24120_writereg(state, 0xe6 + i, 0x02);
|
|
}
|
|
|
|
cx24120_writereg(state, 0xea, (reg | 0x01));
|
|
for (i = 0; i < 6; i += 2) {
|
|
cx24120_writereg(state, 0xc5 + i, 0x00);
|
|
cx24120_writereg(state, 0xc6 + i, 0x00);
|
|
}
|
|
|
|
cx24120_writereg(state, 0xe4, 0x03);
|
|
cx24120_writereg(state, 0xeb, 0x0a);
|
|
|
|
dev_dbg(&state->i2c->dev, "requesting firmware (%s) to download...\n",
|
|
CX24120_FIRMWARE);
|
|
|
|
ret = state->config->request_firmware(fe, &fw, CX24120_FIRMWARE);
|
|
if (ret) {
|
|
err("Could not load firmware (%s): %d\n", CX24120_FIRMWARE,
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(&state->i2c->dev,
|
|
"Firmware found, size %d bytes (%02x %02x .. %02x %02x)\n",
|
|
(int)fw->size, /* firmware_size in bytes */
|
|
fw->data[0], /* fw 1st byte */
|
|
fw->data[1], /* fw 2d byte */
|
|
fw->data[fw->size - 2], /* fw before last byte */
|
|
fw->data[fw->size - 1]); /* fw last byte */
|
|
|
|
cx24120_test_rom(state);
|
|
reg = cx24120_readreg(state, 0xfb) & 0xfe;
|
|
cx24120_writereg(state, 0xfb, reg);
|
|
cx24120_writereg(state, 0xe0, 0x76);
|
|
cx24120_writereg(state, 0xf7, 0x81);
|
|
cx24120_writereg(state, 0xf8, 0x00);
|
|
cx24120_writereg(state, 0xf9, 0x00);
|
|
cx24120_writeregs(state, 0xfa, fw->data, (fw->size - 1), 0x00);
|
|
cx24120_writereg(state, 0xf7, 0xc0);
|
|
cx24120_writereg(state, 0xe0, 0x00);
|
|
reg = (fw->size - 2) & 0x00ff;
|
|
cx24120_writereg(state, 0xf8, reg);
|
|
reg = ((fw->size - 2) >> 8) & 0x00ff;
|
|
cx24120_writereg(state, 0xf9, reg);
|
|
cx24120_writereg(state, 0xf7, 0x00);
|
|
cx24120_writereg(state, 0xdc, 0x00);
|
|
cx24120_writereg(state, 0xdc, 0x07);
|
|
msleep(500);
|
|
|
|
/* Check final byte matches final byte of firmware */
|
|
reg = cx24120_readreg(state, 0xe1);
|
|
if (reg == fw->data[fw->size - 1]) {
|
|
dev_dbg(&state->i2c->dev, "Firmware uploaded successfully\n");
|
|
ret = 0;
|
|
} else {
|
|
err("Firmware upload failed. Last byte returned=0x%x\n", ret);
|
|
ret = -EREMOTEIO;
|
|
}
|
|
cx24120_writereg(state, 0xdc, 0x00);
|
|
release_firmware(fw);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
/* Start tuner */
|
|
cmd.id = CMD_START_TUNER;
|
|
cmd.len = 3;
|
|
cmd.arg[0] = 0x00;
|
|
cmd.arg[1] = 0x00;
|
|
cmd.arg[2] = 0x00;
|
|
|
|
if (cx24120_message_send(state, &cmd) != 0) {
|
|
err("Error tuner start! :(\n");
|
|
return -EREMOTEIO;
|
|
}
|
|
|
|
/* Set VCO */
|
|
ret = cx24120_set_vco(state);
|
|
if (ret != 0) {
|
|
err("Error set VCO! :(\n");
|
|
return ret;
|
|
}
|
|
|
|
/* set bandwidth */
|
|
cmd.id = CMD_BANDWIDTH;
|
|
cmd.len = 12;
|
|
cmd.arg[0] = 0x00;
|
|
cmd.arg[1] = 0x00;
|
|
cmd.arg[2] = 0x00;
|
|
cmd.arg[3] = 0x00;
|
|
cmd.arg[4] = 0x05;
|
|
cmd.arg[5] = 0x02;
|
|
cmd.arg[6] = 0x02;
|
|
cmd.arg[7] = 0x00;
|
|
cmd.arg[8] = 0x05;
|
|
cmd.arg[9] = 0x02;
|
|
cmd.arg[10] = 0x02;
|
|
cmd.arg[11] = 0x00;
|
|
|
|
if (cx24120_message_send(state, &cmd)) {
|
|
err("Error set bandwidth!\n");
|
|
return -EREMOTEIO;
|
|
}
|
|
|
|
reg = cx24120_readreg(state, 0xba);
|
|
if (reg > 3) {
|
|
dev_dbg(&state->i2c->dev, "Reset-readreg 0xba: %x\n", ret);
|
|
err("Error initialising tuner!\n");
|
|
return -EREMOTEIO;
|
|
}
|
|
|
|
dev_dbg(&state->i2c->dev, "Tuner initialised correctly.\n");
|
|
|
|
/* Initialise mpeg outputs */
|
|
cx24120_writereg(state, 0xeb, 0x0a);
|
|
if (cx24120_msg_mpeg_output_global_config(state, 0) ||
|
|
cx24120_msg_mpeg_output_config(state, 0) ||
|
|
cx24120_msg_mpeg_output_config(state, 1) ||
|
|
cx24120_msg_mpeg_output_config(state, 2)) {
|
|
err("Error initialising mpeg output. :(\n");
|
|
return -EREMOTEIO;
|
|
}
|
|
|
|
/* Set size of BER window */
|
|
cmd.id = CMD_BER_CTRL;
|
|
cmd.len = 3;
|
|
cmd.arg[0] = 0x00;
|
|
cmd.arg[1] = CX24120_BER_WINDOW;
|
|
cmd.arg[2] = CX24120_BER_WINDOW;
|
|
if (cx24120_message_send(state, &cmd)) {
|
|
err("Error setting ber window\n");
|
|
return -EREMOTEIO;
|
|
}
|
|
|
|
/* Firmware CMD 35: Get firmware version */
|
|
cmd.id = CMD_FWVERSION;
|
|
cmd.len = 1;
|
|
for (i = 0; i < 4; i++) {
|
|
cmd.arg[0] = i;
|
|
ret = cx24120_message_send(state, &cmd);
|
|
if (ret != 0)
|
|
return ret;
|
|
vers[i] = cx24120_readreg(state, CX24120_REG_MAILBOX);
|
|
}
|
|
info("FW version %i.%i.%i.%i\n", vers[0], vers[1], vers[2], vers[3]);
|
|
|
|
/* init stats here in order signal app which stats are supported */
|
|
c->strength.len = 1;
|
|
c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
c->cnr.len = 1;
|
|
c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
c->post_bit_error.len = 1;
|
|
c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
c->post_bit_count.len = 1;
|
|
c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
c->block_error.len = 1;
|
|
c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
c->block_count.len = 1;
|
|
c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
|
|
state->cold_init = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cx24120_tune(struct dvb_frontend *fe, bool re_tune,
|
|
unsigned int mode_flags, unsigned int *delay,
|
|
enum fe_status *status)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
int ret;
|
|
|
|
dev_dbg(&state->i2c->dev, "(%d)\n", re_tune);
|
|
|
|
/* TODO: Do we need to set delay? */
|
|
|
|
if (re_tune) {
|
|
ret = cx24120_set_frontend(fe);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return cx24120_read_status(fe, status);
|
|
}
|
|
|
|
static enum dvbfe_algo cx24120_get_algo(struct dvb_frontend *fe)
|
|
{
|
|
return DVBFE_ALGO_HW;
|
|
}
|
|
|
|
static int cx24120_sleep(struct dvb_frontend *fe)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int cx24120_get_frontend(struct dvb_frontend *fe,
|
|
struct dtv_frontend_properties *c)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
u8 freq1, freq2, freq3;
|
|
int status;
|
|
|
|
dev_dbg(&state->i2c->dev, "\n");
|
|
|
|
/* don't return empty data if we're not tuned in */
|
|
status = cx24120_readreg(state, CX24120_REG_STATUS);
|
|
if (!(status & CX24120_HAS_LOCK))
|
|
return 0;
|
|
|
|
/* Get frequency */
|
|
freq1 = cx24120_readreg(state, CX24120_REG_FREQ1);
|
|
freq2 = cx24120_readreg(state, CX24120_REG_FREQ2);
|
|
freq3 = cx24120_readreg(state, CX24120_REG_FREQ3);
|
|
c->frequency = (freq3 << 16) | (freq2 << 8) | freq1;
|
|
dev_dbg(&state->i2c->dev, "frequency = %d\n", c->frequency);
|
|
|
|
/* Get modulation, fec, pilot */
|
|
cx24120_get_fec(fe);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cx24120_release(struct dvb_frontend *fe)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
|
|
dev_dbg(&state->i2c->dev, "Clear state structure\n");
|
|
kfree(state);
|
|
}
|
|
|
|
static int cx24120_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
|
|
{
|
|
struct cx24120_state *state = fe->demodulator_priv;
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
|
|
if (c->block_error.stat[0].scale != FE_SCALE_COUNTER) {
|
|
*ucblocks = 0;
|
|
return 0;
|
|
}
|
|
|
|
*ucblocks = c->block_error.stat[0].uvalue - state->ucb_offset;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dvb_frontend_ops cx24120_ops = {
|
|
.delsys = { SYS_DVBS, SYS_DVBS2 },
|
|
.info = {
|
|
.name = "Conexant CX24120/CX24118",
|
|
.frequency_min_hz = 950 * MHz,
|
|
.frequency_max_hz = 2150 * MHz,
|
|
.frequency_stepsize_hz = 1011 * kHz,
|
|
.frequency_tolerance_hz = 5 * MHz,
|
|
.symbol_rate_min = 1000000,
|
|
.symbol_rate_max = 45000000,
|
|
.caps = FE_CAN_INVERSION_AUTO |
|
|
FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
|
|
FE_CAN_FEC_4_5 | FE_CAN_FEC_5_6 | FE_CAN_FEC_6_7 |
|
|
FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
|
|
FE_CAN_2G_MODULATION |
|
|
FE_CAN_QPSK | FE_CAN_RECOVER
|
|
},
|
|
.release = cx24120_release,
|
|
|
|
.init = cx24120_init,
|
|
.sleep = cx24120_sleep,
|
|
|
|
.tune = cx24120_tune,
|
|
.get_frontend_algo = cx24120_get_algo,
|
|
.set_frontend = cx24120_set_frontend,
|
|
|
|
.get_frontend = cx24120_get_frontend,
|
|
.read_status = cx24120_read_status,
|
|
.read_ber = cx24120_read_ber,
|
|
.read_signal_strength = cx24120_read_signal_strength,
|
|
.read_snr = cx24120_read_snr,
|
|
.read_ucblocks = cx24120_read_ucblocks,
|
|
|
|
.diseqc_send_master_cmd = cx24120_send_diseqc_msg,
|
|
|
|
.diseqc_send_burst = cx24120_diseqc_send_burst,
|
|
.set_tone = cx24120_set_tone,
|
|
.set_voltage = cx24120_set_voltage,
|
|
};
|
|
|
|
MODULE_DESCRIPTION("DVB Frontend module for Conexant CX24120/CX24118 hardware");
|
|
MODULE_AUTHOR("Jemma Denson");
|
|
MODULE_LICENSE("GPL");
|