From: Буди Романто, AreMa Inc <knightrider@xxxxxx> Signed-off-by: Буди Романто, AreMa Inc <knightrider@xxxxxx> --- drivers/media/dvb-frontends/tc90522.c | 283 ++++++++++++++++++++++++++++++++++ drivers/media/dvb-frontends/tc90522.h | 18 +++ 2 files changed, 301 insertions(+) create mode 100644 drivers/media/dvb-frontends/tc90522.c create mode 100644 drivers/media/dvb-frontends/tc90522.h diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c new file mode 100644 index 0000000..97d18ba --- /dev/null +++ b/drivers/media/dvb-frontends/tc90522.c @@ -0,0 +1,283 @@ +/* + * Toshiba TC90522XBG 2ch OFDM(ISDB-T) + 2ch 8PSK(ISDB-S) demodulator + * + * Copyright (C) Budi Rachmanto, AreMa Inc. <info@xxxxxx> + * + * Supported cards: + * Earthsoft PT3 + * PLEX PX-Q3PE + * + * 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. + */ + +#include <linux/pci.h> +#include "dvb_math.h" +#include "dvb_frontend.h" +#include "tc90522.h" + +struct tc90522 { + struct i2c_adapter *i2c; + fe_status_t stat; +}; + +bool tc90522_r(struct dvb_frontend *fe, u8 slvadr, u8 *buf, u8 len) +{ + struct tc90522 *d = fe->demodulator_priv; + struct i2c_msg msg[] = { + {.addr = fe->id, .flags = 0, .buf = &slvadr, .len = 1,}, + {.addr = fe->id, .flags = I2C_M_RD, .buf = buf, .len = len,}, + }; + return i2c_transfer(d->i2c, msg, 2) == 2; +} + +bool tc90522_w(struct dvb_frontend *fe, u8 slvadr, u8 dat) +{ + struct tc90522 *d = fe->demodulator_priv; + u8 buf[] = {slvadr, dat}; + struct i2c_msg msg[] = { + {.addr = fe->id, .flags = 0, .buf = buf, .len = 2,}, + }; + return i2c_transfer(d->i2c, msg, 1) == 1; +} + +u64 tc90522_n2int(const u8 *data, u8 n) /* convert n_bytes data from stream (network byte order) to integer */ +{ /* can't use <arpa/inet.h>'s ntoh*() as sometimes n = 3,5,... */ + u32 i, val = 0; + + for (i = 0; i < n; i++) { + val <<= 8; + val |= data[i]; + } + return val; +} + +int tc90522_cn_raw(struct dvb_frontend *fe, u16 *raw) /* for DVBv3 compatibility */ +{ + u8 buf[3], + len = fe->dtv_property_cache.delivery_system == SYS_ISDBS ? 2 : 3, + adr = fe->dtv_property_cache.delivery_system == SYS_ISDBS ? 0xbc : 0x8b; + bool ok = tc90522_r(fe, adr, buf, len); + int cn = tc90522_n2int(buf, len); + + if (!ok) + return -EIO; + *raw = cn; + return cn; +} + +int tc90522_status(struct dvb_frontend *fe, fe_status_t *stat) +{ + struct tc90522 *d = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u16 v16; + s64 raw = tc90522_cn_raw(fe, &v16), + x, + y; + + s64 cn_s(void) /* @ .0001 dB */ + { + raw -= 3000; + if (raw < 0) + raw = 0; + x = int_sqrt(raw << 20); + y = 16346ll * x - (143410ll << 16); + y = ((x * y) >> 16) + (502590ll << 16); + y = ((x * y) >> 16) - (889770ll << 16); + y = ((x * y) >> 16) + (895650ll << 16); + y = (588570ll << 16) - ((x * y) >> 16); + return y < 0 ? 0 : y >> 16; + } + + s64 cn_t(void) /* @ .0001 dB */ + { + if (!raw) + return 0; + x = (1130911733ll - 10ll * intlog10(raw)) >> 2; + y = (x >> 2) - (x >> 6) + (x >> 8) + (x >> 9) - (x >> 10) + (x >> 11) + (x >> 12) - (16ll << 22); + y = ((x * y) >> 22) + (398ll << 22); + y = ((x * y) >> 22) + (5491ll << 22); + y = ((x * y) >> 22) + (30965ll << 22); + return y >> 22; + } + + c->cnr.len = 1; + c->cnr.stat[0].svalue = fe->dtv_property_cache.delivery_system == SYS_ISDBS ? cn_s() : cn_t(); + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + *stat = d->stat; + return d->stat; +} + +int tc90522_get_frontend_algo(struct dvb_frontend *fe) +{ + return DVBFE_ALGO_HW; +} + +int tc90522_tune(struct dvb_frontend *fe, bool retune, u32 mode_flags, u32 *delay, fe_status_t *stat) +{ + u32 fno2kHz(u32 fno) + { + u32 chno; + + if (fno < 12) { + chno = 1 + 2 * fno; /* BS */ + return 1049480 + 38360 * fno; + } else if (fno < 24) { + fno -= 12; + chno = 2 + 2 * fno; /* CS110 right */ + return 1613000 + 40000 * fno; + } + fno -= 24; + chno = 1 + 2 * fno; /* CS110 left */ + return 1593000 + 40000 * fno; + } + + void s_kHz(u32 *f) + { + *f = *f > 3000000 ? fno2kHz(14) : /* CNN etc. */ + *f >= 1049480 ? *f : + *f > 48 ? fno2kHz(4) : /* BS11 etc. */ + fno2kHz(*f - 1); + } + + u32 fno2Hz(u32 fno) + { + return (fno > 112 ? 557 : 93 + 6 * fno + (fno < 12 ? 0 : fno < 17 ? 2 : fno < 63 ? 0 : 2)) * 1000000 + 142857; + } + + void t_Hz(u32 *f) + { + *f = *f >= 90000000 ? *f : /* real_freq Hz */ + *f > 255 ? fno2Hz(77) : /* NHK */ + *f > 127 ? fno2Hz(*f - 128) : /* freqno (IO#) */ + *f > 63 ? (*f -= 64, /* CATV */ + *f > 22 ? fno2Hz(*f - 1) : /* C23-C62 */ + *f > 12 ? fno2Hz(*f - 10) : /* C13-C22 */ + fno2Hz(77)) : + *f > 62 ? fno2Hz(77) : + *f > 12 ? fno2Hz(*f + 50) : /* 13-62 */ + *f > 3 ? fno2Hz(*f + 9) : /* 4-12 */ + *f ? fno2Hz(*f - 1) : /* 1-3 */ + fno2Hz(77); + } + struct tc90522 *d = fe->demodulator_priv; + u16 stream_id = fe->dtv_property_cache.stream_id, + i = 0xFFFF; + u8 data[16]; + + if (!retune) /* once is enough */ + return 0; + d->stat = 0; + if (fe->dtv_property_cache.delivery_system == SYS_ISDBT) + goto ISDBT; + + s_kHz(&fe->dtv_property_cache.frequency); + if (fe->ops.tuner_ops.set_params(fe)) + return -EIO; + while (i--) { + if ((tc90522_r(fe, 0xC3, data, 1), !(data[0] & 0x10)) && /* locked */ + (tc90522_r(fe, 0xCE, data, 2), *(u16 *)data != 0) && /* valid TSID */ + tc90522_r(fe, 0xC3, data, 1) && + tc90522_r(fe, 0xCE, data, 16)) + break; + msleep_interruptible(1); + } + if (!i) + return -ETIMEDOUT; + for (i = 0; i < 8; i++) { + u16 tsid = tc90522_n2int(data + i*2, 2); + + if ((tsid == stream_id || stream_id == i) && + tc90522_w(fe, 0x8F, tsid >> 8) && + tc90522_w(fe, 0x90, tsid & 0xFF) && + tc90522_r(fe, 0xE6, data, 2) && + tc90522_n2int(data, 2) == tsid) + goto LOCK; + } + goto ERR; +ISDBT: + t_Hz(&fe->dtv_property_cache.frequency); + if (fe->ops.tuner_ops.set_params(fe)) + return -EIO; + while (i--) { + bool retryov, + lock0, + lock1; + if (!tc90522_r(fe, 0x80, data, 1) || !tc90522_r(fe, 0xB0, data + 1, 1)) + break; + retryov = data[0] & 0b10000000 ? true : false; + lock0 = data[0] & 0b00001000 ? false : true; + lock1 = data[1] & 0b00001000 ? true : false; + if (lock0 && lock1) { +LOCK: + d->stat = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK; + *stat = d->stat; + return 0; + } + if (retryov) + break; + msleep_interruptible(1); + } +ERR: + *stat = d->stat; + return -ETIMEDOUT; +} + +static struct dvb_frontend_ops tc90522_ops = { + .info = { + .name = TC90522_MODNAME, + .caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO, + .frequency_min = 1, + .frequency_max = 770000000, + }, + .get_frontend_algo = tc90522_get_frontend_algo, + .read_snr = tc90522_cn_raw, + .read_status = tc90522_status, + .tune = tc90522_tune, +}; + +int tc90522_probe(struct i2c_client *c, const struct i2c_device_id *id) +{ + struct tc90522 *d = kzalloc(sizeof(struct tc90522), GFP_KERNEL); + struct dvb_frontend *fe = c->dev.platform_data; + + if (!d) + return -ENOMEM; + d->i2c = c->adapter; + fe->demodulator_priv = d; + i2c_set_clientdata(c, d); + memcpy(&fe->ops, &tc90522_ops, sizeof(struct dvb_frontend_ops)); + fe->ops.delsys[0] = fe->dtv_property_cache.delivery_system; + return 0; +} + +int tc90522_remove(struct i2c_client *c) +{ + kfree(i2c_get_clientdata(c)); + return 0; +} + +static struct i2c_device_id tc90522_id[] = { + {TC90522_MODNAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, tc90522_id); + +static struct i2c_driver tc90522_driver = { + .driver = { + .owner = THIS_MODULE, + .name = tc90522_id->name, + }, + .probe = tc90522_probe, + .remove = tc90522_remove, + .id_table = tc90522_id, +}; +module_i2c_driver(tc90522_driver); + +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>"); +MODULE_DESCRIPTION("Toshiba TC90522 8PSK(ISDB-S)/OFDM(ISDB-T) quad demodulator"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h new file mode 100644 index 0000000..b6ee014 --- /dev/null +++ b/drivers/media/dvb-frontends/tc90522.h @@ -0,0 +1,18 @@ +/* + * Toshiba TC90522XBG 2ch OFDM(ISDB-T) + 2ch 8PSK(ISDB-S) demodulator + * + * Copyright (C) Budi Rachmanto, AreMa Inc. <info@xxxxxx> + * + * 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. + */ + +#ifndef TC90522_H +#define TC90522_H + +#define TC90522_MODNAME "tc90522" + +#endif + -- 2.3.10 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html