| // 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 */ | 
 | 		return; | 
 | 	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"); |