/*
* Toshiba TC90522 Demodulator
*
* Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
*
* 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.
*/
/*
* NOTICE:
* This driver is incomplete and lacks init/config of the chips,
* as the necessary info is not disclosed.
* It assumes that users of this driver (such as a PCI bridge of
* DTV receiver cards) properly init and configure the chip
* via I2C *before* calling this driver's init() function.
*
* Currently, PT3 driver is the only one that uses this driver,
* and contains init/config code in its firmware.
* Thus some part of the code might be dependent on PT3 specific config.
*/
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/dvb/frontend.h>
#include "dvb_math.h"
#include "tc90522.h"
#define TC90522_I2C_THRU_REG 0xfe
#define TC90522_MODULE_IDX(addr) (((u8)(addr) & 0x02U) >> 1)
struct tc90522_state {
struct tc90522_config cfg;
struct dvb_frontend fe;
struct i2c_client *i2c_client;
struct i2c_adapter tuner_i2c;
bool lna;
};
struct reg_val {
u8 reg;
u8 val;
};
static int
reg_write(struct tc90522_state *state, const struct reg_val *regs, int num)
{
int i, ret;
struct i2c_msg msg;
ret = 0;
msg.addr = state->i2c_client->addr;
msg.flags = 0;
msg.len = 2;
for (i = 0; i < num; i++) {
msg.buf = (u8 *)®s[i];
ret = i2c_transfer(state->i2c_client->adapter, &msg, 1);
if (ret == 0)
ret = -EIO;
if (ret < 0)
return ret;
}
return 0;
}
static int reg_read(struct tc90522_state *state, u8 reg, u8 *val, u8 len)
{
struct i2c_msg msgs[2] = {
{
.addr = state->i2c_client->addr,
.flags = 0,
.buf = ®,
.len = 1,
},
{
.addr = state->i2c_client->addr,
.flags = I2C_M_RD,
.buf = val,
.len = len,
},
};
int ret;
ret = i2c_transfer(state->i2c_client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret == ARRAY_SIZE(msgs))
ret = 0;
else if (ret >= 0)
ret = -EIO;
return ret;
}
static struct tc90522_state *cfg_to_state(struct tc90522_config *c)
{
return container_of(c, struct tc90522_state, cfg);
}
static int tc90522s_set_tsid(struct dvb_frontend *fe)
{
struct reg_val set_tsid[] = {
{ 0x8f, 00 },
{ 0x90, 00 }
};
set_tsid[0].val = (fe->dtv_property_cache.stream_id & 0xff00) >> 8;
set_tsid[1].val = fe->dtv_property_cache.stream_id & 0xff;
return reg_write(fe->demodulator_priv, set_tsid, ARRAY_SIZE(set_tsid));
}
static int tc90522t_set_layers(struct dvb_frontend *fe)
{
struct reg_val rv;
u8 laysel;
laysel = ~fe->dtv_property_cache.isdbt_layer_enabled & 0x07;
laysel = (laysel & 0x01) << 2 | (laysel & 0x02) | (laysel & 0x04) >> 2;
rv.reg = 0x71;
rv.val = laysel;
return reg_write(fe->demodulator_priv, &rv, 1);
}
/* frontend ops */
static int tc90522s_read_status(struct dvb_frontend *fe, fe_status_t *status)
{
struct tc90522_state *state;
int ret;
u8 reg;
state = fe->demodulator_priv;
ret = reg_read(state, 0xc3, ®, 1);
if (ret < 0)
return ret;
*status = 0;
if (reg & 0x80) /* input level under min ? */
return 0;
*status |= FE_HAS_SIGNAL;
if (reg & 0x60) /* carrier? */
return 0;
*status |= FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC;
if (reg & 0x10)
return 0;
if (reg_read(state, 0xc5, ®, 1) < 0 || !(reg & 0x03))
return 0;
*status |= FE_HAS_LOCK;
return 0;
}
static int tc90522t_read_status(struct dvb_frontend *fe, fe_status_t *status)
{
struct tc90522_state *state;
int ret;
u8 reg;
state = fe->demodulator_priv;
ret = reg_read(state, 0x96, ®, 1);
if (ret < 0)
return ret;
*status = 0;