Merge branch 'android-x86_64-fugu-3.10-security-next' into android-x86_64-fugu-3.10 Security February 2017.1
diff --git a/arch/x86/configs/fugu_defconfig b/arch/x86/configs/fugu_defconfig index 0cfde13..c0bdb54 100644 --- a/arch/x86/configs/fugu_defconfig +++ b/arch/x86/configs/fugu_defconfig
@@ -44,6 +44,9 @@ # CONFIG_SCHED_OMIT_FRAME_POINTER is not set CONFIG_MSLM=y CONFIG_NR_CPUS=4 +CONFIG_SCHED_SMT=y +CONFIG_CPU_CONCURRENCY=y +CONFIG_WORKLOAD_CONSOLIDATION=y CONFIG_PREEMPT=y # CONFIG_X86_MCE_AMD is not set CONFIG_X86_MSR=y @@ -260,6 +263,7 @@ # CONFIG_HW_RANDOM_VIA is not set CONFIG_NVRAM=y CONFIG_I2C_CHARDEV=y +CONFIG_I2C_MUX=y CONFIG_I2C_GPIO=y CONFIG_PTP_1588_CLOCK=y CONFIG_GPIOLIB=y @@ -294,6 +298,9 @@ CONFIG_VIDEO_CX231XX=y # CONFIG_VIDEO_CX231XX_RC is not set CONFIG_VIDEO_CX231XX_DVB=y +CONFIG_VIDEO_EM28XX=y +CONFIG_VIDEO_EM28XX_DVB=y +# CONFIG_VIDEO_EM28XX_RC is not set CONFIG_DRM=y CONFIG_INTEL_NO_FB_PANIC_NOTIFY=y CONFIG_VIDEO_OUTPUT_CONTROL=y
diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig index dea17b0c..f268934 100644 --- a/drivers/media/dvb-frontends/Kconfig +++ b/drivers/media/dvb-frontends/Kconfig
@@ -432,6 +432,13 @@ help Say Y when you want to support this frontend. +config DVB_SI2168 + tristate "Silicon Labs Si2168" + depends on DVB_CORE && I2C && I2C_MUX + default m if !MEDIA_SUBDRV_AUTOSELECT + help + Say Y when you want to support this frontend. + comment "DVB-C (cable) frontends" depends on DVB_CORE
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile index 3b79d38..92bb882 100644 --- a/drivers/media/dvb-frontends/Makefile +++ b/drivers/media/dvb-frontends/Makefile
@@ -104,4 +104,5 @@ obj-$(CONFIG_DVB_RTL2832) += rtl2832.o obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o obj-$(CONFIG_DVB_AF9033) += af9033.o +obj-$(CONFIG_DVB_SI2168) += si2168.o
diff --git a/drivers/media/dvb-frontends/lgdt3306a.c b/drivers/media/dvb-frontends/lgdt3306a.c index 73ee1cc..9c7e412 100644 --- a/drivers/media/dvb-frontends/lgdt3306a.c +++ b/drivers/media/dvb-frontends/lgdt3306a.c
@@ -16,7 +16,6 @@ * GNU General Public License for more details. */ -#undef pr_fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <asm/div64.h> @@ -59,11 +58,12 @@ struct lgdt3306a_state { struct i2c_adapter *i2c_adap; + struct lgdt3306a_config config; const struct lgdt3306a_config *cfg; - + struct i2c_client *client; struct dvb_frontend frontend; - fe_modulation_t current_modulation; + enum fe_modulation current_modulation; u32 current_frequency; u32 snr; }; @@ -118,6 +118,7 @@ static int lgdt3306a_write_reg(struct lgdt3306a_state *state, u16 reg, u8 val) { int ret; + struct i2c_adapter *i2c_adapter; u8 buf[] = { reg >> 8, reg & 0xff, val }; struct i2c_msg msg = { .addr = state->cfg->i2c_addr, .flags = 0, @@ -126,8 +127,11 @@ dbg_reg("reg: 0x%04x, val: 0x%02x\n", reg, val); - ret = i2c_transfer(state->i2c_adap, &msg, 1); - + if (state->cfg->has_tuner_i2c_adapter) + i2c_adapter = state->client->adapter; + else + i2c_adapter = state->i2c_adap; + ret = i2c_transfer(i2c_adapter, &msg, 1); if (ret != 1) { pr_err("error (addr %02x %02x <- %02x, err = %i)\n", msg.buf[0], msg.buf[1], msg.buf[2], ret); @@ -142,6 +146,7 @@ static int lgdt3306a_read_reg(struct lgdt3306a_state *state, u16 reg, u8 *val) { int ret; + struct i2c_adapter *i2c_adapter; u8 reg_buf[] = { reg >> 8, reg & 0xff }; struct i2c_msg msg[] = { { .addr = state->cfg->i2c_addr, @@ -150,8 +155,12 @@ .flags = I2C_M_RD, .buf = val, .len = 1 }, }; - ret = i2c_transfer(state->i2c_adap, msg, 2); + if (state->cfg->has_tuner_i2c_adapter) + i2c_adapter = state->client->adapter; + else + i2c_adapter = state->i2c_adap; + ret = i2c_transfer(i2c_adapter, msg, 2); if (ret != 2) { pr_err("error (addr %02x reg %04x error (ret == %i)\n", state->cfg->i2c_addr, reg, ret); @@ -622,6 +631,9 @@ case QAM_256: ret = lgdt3306a_set_qam(state, QAM_256); break; + case QAM_AUTO: + ret = lgdt3306a_set_qam(state, QAM_64); + break; default: return -EINVAL; } @@ -647,6 +659,7 @@ break; case QAM_64: case QAM_256: + case QAM_AUTO: break; default: return -EINVAL; @@ -701,6 +714,7 @@ break; case QAM_64: case QAM_256: + case QAM_AUTO: /* Auto ok for QAM */ ret = lgdt3306a_set_inversion_auto(state, 1); break; @@ -724,6 +738,7 @@ break; case QAM_64: case QAM_256: + case QAM_AUTO: if_freq_khz = state->cfg->qam_if_khz; break; default: @@ -1559,7 +1574,8 @@ return LG3306_UNLOCK; } -static int lgdt3306a_read_status(struct dvb_frontend *fe, fe_status_t *status) +static int lgdt3306a_read_status(struct dvb_frontend *fe, + enum fe_status *status) { struct lgdt3306a_state *state = fe->demodulator_priv; u16 strength = 0; @@ -1581,6 +1597,7 @@ switch (state->current_modulation) { case QAM_256: case QAM_64: + case QAM_AUTO: if (lgdt3306a_qam_lock_poll(state) == LG3306_LOCK) { *status |= FE_HAS_VITERBI; *status |= FE_HAS_SYNC; @@ -1641,6 +1658,9 @@ case QAM_256: ref_snr = 2800; /* 28dB */ break; + case QAM_AUTO: + ref_snr = 2200; /* 22dB */ + break; default: return -EINVAL; } @@ -1706,7 +1726,7 @@ static int lgdt3306a_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, - fe_status_t *status) + enum fe_status *status) { int ret = 0; struct lgdt3306a_state *state = fe->demodulator_priv; @@ -1736,7 +1756,7 @@ static int lgdt3306a_search(struct dvb_frontend *fe) { - fe_status_t status = 0; + enum fe_status status = 0; int i = 0, ret; unsigned long timeout; @@ -2111,7 +2131,144 @@ } #endif /* DBG_DUMP */ +/* + * I2C gate logic + * We must use unlocked I2C I/O because I2C adapter lock is already taken + * by the caller (usually tuner driver). + */ +static int lgdt3306a_select(struct i2c_adapter *adap, void *mux_priv, u32 chan) +{ + struct lgdt3306a_state *state = mux_priv; + struct i2c_client *client = state->client; + int ret; + u8 val; + u8 buf[3]; + + struct i2c_msg read_msg_1 = { + .addr = client->addr, + .flags = 0, + .buf = "\x00\x02", + .len = 2, + }; + struct i2c_msg read_msg_2 = { + .addr = client->addr, + .flags = I2C_M_RD, + .buf = &val, + .len = 1, + }; + + struct i2c_msg write_msg = { + .addr = client->addr, + .flags = 0, + .buf = buf, + .len = 3, + }; + + ret = __i2c_transfer(client->adapter, &read_msg_1, 1); + if (ret != 1) { + pr_err("error (addr %02x reg 0x002 error (ret == %i)\n", + client->addr, ret); + if (ret < 0) + return ret; + else + return -EREMOTEIO; + } + + ret = __i2c_transfer(client->adapter, &read_msg_2, 1); + if (ret != 1) { + pr_err("error (addr %02x reg 0x002 error (ret == %i)\n", + client->addr, ret); + if (ret < 0) + return ret; + else + return -EREMOTEIO; + } + + buf[0] = 0x00; + buf[1] = 0x02; + val &= 0x7F; + val |= LG3306_TUNERI2C_ON; + buf[2] = val; + ret = __i2c_transfer(client->adapter, &write_msg, 1); + if (ret != 1) { + pr_err("error (addr %02x %02x <- %02x, err = %i)\n", + write_msg.buf[0], write_msg.buf[1], write_msg.buf[2], + ret); + if (ret < 0) + return ret; + else + return -EREMOTEIO; + } + return 0; +} + +static int lgdt3306a_deselect(struct i2c_adapter *adap, void *mux_priv, + u32 chan) +{ + struct lgdt3306a_state *state = mux_priv; + struct i2c_client *client = state->client; + int ret; + u8 val; + u8 buf[3]; + + struct i2c_msg read_msg_1 = { + .addr = client->addr, + .flags = 0, + .buf = "\x00\x02", + .len = 2, + }; + struct i2c_msg read_msg_2 = { + .addr = client->addr, + .flags = I2C_M_RD, + .buf = &val, + .len = 1, + }; + + struct i2c_msg write_msg = { + .addr = client->addr, + .flags = 0, + .len = 3, + .buf = buf, + }; + + ret = __i2c_transfer(client->adapter, &read_msg_1, 1); + if (ret != 1) { + pr_err("error (addr %02x reg 0x002 error (ret == %i)\n", + client->addr, ret); + if (ret < 0) + return ret; + else + return -EREMOTEIO; + } + + ret = __i2c_transfer(client->adapter, &read_msg_2, 1); + if (ret != 1) { + pr_err("error (addr %02x reg 0x002 error (ret == %i)\n", + client->addr, ret); + if (ret < 0) + return ret; + else + return -EREMOTEIO; + } + + buf[0] = 0x00; + buf[1] = 0x02; + val &= 0x7F; + val |= LG3306_TUNERI2C_OFF; + buf[2] = val; + ret = __i2c_transfer(client->adapter, &write_msg, 1); + if (ret != 1) { + pr_err("error (addr %02x %02x <- %02x, err = %i)\n", + write_msg.buf[0], write_msg.buf[1], write_msg.buf[2], + ret); + if (ret < 0) + return ret; + else + return -EREMOTEIO; + } + return 0; +} static struct dvb_frontend_ops lgdt3306a_ops = { .delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B }, @@ -2141,6 +2298,135 @@ .search = lgdt3306a_search, }; +static int lgdt3306a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lgdt3306a_config *config = client->dev.platform_data; + struct lgdt3306a_state *state; + u8 val; + int ret; + + dev_dbg(&client->dev, "\n"); + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) { + ret = -ENOMEM; + dev_err(&client->dev, "kzalloc() failed\n"); + goto err; + } + + state->client = client; + state->config.i2c_addr = config->i2c_addr; + state->config.qam_if_khz = config->qam_if_khz; + state->config.vsb_if_khz = config->vsb_if_khz; + state->config.deny_i2c_rptr = config->deny_i2c_rptr; + state->config.spectral_inversion = config->spectral_inversion; + state->config.mpeg_mode = config->mpeg_mode; + state->config.tpclk_edge = config->tpclk_edge; + state->config.tpvalid_polarity = config->tpvalid_polarity; + state->config.xtalMHz = config->xtalMHz; + state->config.has_tuner_i2c_adapter = config->has_tuner_i2c_adapter; + state->cfg = &state->config; + + /* create mux i2c adapter for tuner */ + state->i2c_adap = i2c_add_mux_adapter(client->adapter, &client->dev, + state, 0, 0, 0, lgdt3306a_select, lgdt3306a_deselect); + if (state->i2c_adap == NULL) { + ret = -ENODEV; + goto err_kfree; + } + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &lgdt3306a_ops, + sizeof(state->frontend.ops)); + state->frontend.demodulator_priv = state; + *config->i2c_adapter = state->i2c_adap; + *config->fe = &state->frontend; + + /* verify that we're talking to a lg3306a */ + /* FGR - NOTE - there is no obvious ChipId to check; we check + * some "known" bits after reset, but it's still just a guess */ + ret = lgdt3306a_read_reg(state, 0x0000, &val); + if (lg_chkerr(ret)) + goto err_kfree; + if ((val & 0x74) != 0x74) { + pr_warn("expected 0x74, got 0x%x\n", (val & 0x74)); +#if 0 + /* FIXME - re-enable when we know this is right */ + goto err_kfree; +#endif + } + ret = lgdt3306a_read_reg(state, 0x0001, &val); + if (lg_chkerr(ret)) + goto err_kfree; + if ((val & 0xf6) != 0xc6) { + pr_warn("expected 0xc6, got 0x%x\n", (val & 0xf6)); +#if 0 + /* FIXME - re-enable when we know this is right */ + goto err_kfree; +#endif + } + ret = lgdt3306a_read_reg(state, 0x0002, &val); + if (lg_chkerr(ret)) + goto err_kfree; + if ((val & 0x73) != 0x03) { + pr_warn("expected 0x03, got 0x%x\n", (val & 0x73)); +#if 0 + /* FIXME - re-enable when we know this is right */ + goto err_kfree; +#endif + } + + state->current_frequency = -1; + state->current_modulation = -1; + + lgdt3306a_sleep(state); + + i2c_set_clientdata(client, state); + + dev_info(&client->dev, "LG Electronics LGDT3306A successfully attached\n"); + return 0; +err_kfree: + kfree(state); +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int lgdt3306a_remove(struct i2c_client *client) +{ + struct lgdt3306a_state *state = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "\n"); + + i2c_del_mux_adapter(state->i2c_adap); + + state->frontend.ops.release = NULL; + state->frontend.demodulator_priv = NULL; + + kfree(state); + + return 0; +} + +static const struct i2c_device_id lgdt3306a_id_table[] = { + {"lgdt3306a", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, lgdt3306a_id_table); + +static struct i2c_driver lgdt3306a_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "lgdt3306a", + }, + .probe = lgdt3306a_probe, + .remove = lgdt3306a_remove, + .id_table = lgdt3306a_id_table, +}; + +module_i2c_driver(lgdt3306a_driver); + MODULE_DESCRIPTION("LG Electronics LGDT3306A ATSC/QAM-B Demodulator Driver"); MODULE_AUTHOR("Fred Richter <frichter@hauppauge.com>"); MODULE_LICENSE("GPL");
diff --git a/drivers/media/dvb-frontends/lgdt3306a.h b/drivers/media/dvb-frontends/lgdt3306a.h index ed8aa3e..b54e7e5 100644 --- a/drivers/media/dvb-frontends/lgdt3306a.h +++ b/drivers/media/dvb-frontends/lgdt3306a.h
@@ -19,8 +19,11 @@ #define _LGDT3306A_H_ #include <linux/i2c.h> +#include <linux/i2c-mux.h> #include "dvb_frontend.h" +#define LG3306_TUNERI2C_ON 0x00 +#define LG3306_TUNERI2C_OFF 0x80 enum lgdt3306a_mpeg_mode { LGDT3306A_MPEG_PARALLEL = 0, @@ -40,6 +43,18 @@ struct lgdt3306a_config { u8 i2c_addr; + /* + * frontend + * returned by driver + */ + struct dvb_frontend **fe; + + /* + * tuner I2C adapter + * returned by driver + */ + struct i2c_adapter **i2c_adapter; + /* user defined IF frequency in KHz */ u16 qam_if_khz; u16 vsb_if_khz; @@ -56,6 +71,8 @@ /* demod clock freq in MHz; 24 or 25 supported */ int xtalMHz; + + int has_tuner_i2c_adapter; }; #if IS_ENABLED(CONFIG_DVB_LGDT3306A)
diff --git a/drivers/media/dvb-frontends/s921.h b/drivers/media/dvb-frontends/s921.h index 8d5e2a6..9a58f01 100644 --- a/drivers/media/dvb-frontends/s921.h +++ b/drivers/media/dvb-frontends/s921.h
@@ -36,7 +36,7 @@ printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return NULL; } -static struct i2c_adapter * +static inline struct i2c_adapter * s921_get_tuner_i2c_adapter(struct dvb_frontend *fe) { printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
diff --git a/drivers/media/dvb-frontends/si2168.c b/drivers/media/dvb-frontends/si2168.c new file mode 100644 index 0000000..d40090b --- /dev/null +++ b/drivers/media/dvb-frontends/si2168.c
@@ -0,0 +1,777 @@ +/* + * Silicon Labs Si2168 DVB-T/T2/C demodulator driver + * + * Copyright (C) 2014 Antti Palosaari <crope@iki.fi> + * + * 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. + */ + +#include "si2168_priv.h" + +static const struct dvb_frontend_ops si2168_ops; + +/* Own I2C adapter locking is needed because of I2C gate logic. */ +static int si2168_i2c_master_send_unlocked(const struct i2c_client *client, + const char *buf, int count) +{ + int ret; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = count, + .buf = (char *)buf, + }; + + ret = __i2c_transfer(client->adapter, &msg, 1); + return (ret == 1) ? count : ret; +} + +static int si2168_i2c_master_recv_unlocked(const struct i2c_client *client, + char *buf, int count) +{ + int ret; + struct i2c_msg msg = { + .addr = client->addr, + .flags = I2C_M_RD, + .len = count, + .buf = buf, + }; + + ret = __i2c_transfer(client->adapter, &msg, 1); + return (ret == 1) ? count : ret; +} + +/* execute firmware command */ +static int si2168_cmd_execute_unlocked(struct i2c_client *client, + struct si2168_cmd *cmd) +{ + int ret; + unsigned long timeout; + + if (cmd->wlen) { + /* write cmd and args for firmware */ + ret = si2168_i2c_master_send_unlocked(client, cmd->args, + cmd->wlen); + if (ret < 0) { + goto err; + } else if (ret != cmd->wlen) { + ret = -EREMOTEIO; + goto err; + } + } + + if (cmd->rlen) { + /* wait cmd execution terminate */ + timeout = jiffies + msecs_to_jiffies(70); + while (!time_after(jiffies, timeout)) { + ret = si2168_i2c_master_recv_unlocked(client, cmd->args, + cmd->rlen); + if (ret < 0) { + goto err; + } else if (ret != cmd->rlen) { + ret = -EREMOTEIO; + goto err; + } + + /* firmware ready? */ + if ((cmd->args[0] >> 7) & 0x01) + break; + } + + dev_dbg(&client->dev, "cmd execution took %d ms\n", + jiffies_to_msecs(jiffies) - + (jiffies_to_msecs(timeout) - 70)); + + /* error bit set? */ + if ((cmd->args[0] >> 6) & 0x01) { + ret = -EREMOTEIO; + goto err; + } + + if (!((cmd->args[0] >> 7) & 0x01)) { + ret = -ETIMEDOUT; + goto err; + } + } + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_cmd_execute(struct i2c_client *client, struct si2168_cmd *cmd) +{ + int ret; + + i2c_lock_adapter(client->adapter); + ret = si2168_cmd_execute_unlocked(client, cmd); + i2c_unlock_adapter(client->adapter); + + return ret; +} + +static int si2168_read_status(struct dvb_frontend *fe, enum fe_status *status) +{ + struct i2c_client *client = fe->demodulator_priv; + struct si2168_dev *dev = i2c_get_clientdata(client); + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + struct si2168_cmd cmd; + + *status = 0; + + if (!dev->active) { + ret = -EAGAIN; + goto err; + } + + switch (c->delivery_system) { + case SYS_DVBT: + memcpy(cmd.args, "\xa0\x01", 2); + cmd.wlen = 2; + cmd.rlen = 13; + break; + case SYS_DVBC_ANNEX_A: + memcpy(cmd.args, "\x90\x01", 2); + cmd.wlen = 2; + cmd.rlen = 9; + break; + case SYS_DVBT2: + memcpy(cmd.args, "\x50\x01", 2); + cmd.wlen = 2; + cmd.rlen = 14; + break; + default: + ret = -EINVAL; + goto err; + } + + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + switch ((cmd.args[2] >> 1) & 0x03) { + case 0x01: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; + break; + case 0x03: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | + FE_HAS_SYNC | FE_HAS_LOCK; + break; + } + + dev->fe_status = *status; + + if (*status & FE_HAS_LOCK) { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + c->cnr.stat[0].svalue = cmd.args[3] * 1000 / 4; + } else { + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + } + + dev_dbg(&client->dev, "status=%02x args=%*ph\n", + *status, cmd.rlen, cmd.args); + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_set_frontend(struct dvb_frontend *fe) +{ + struct i2c_client *client = fe->demodulator_priv; + struct si2168_dev *dev = i2c_get_clientdata(client); + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + struct si2168_cmd cmd; + u8 bandwidth, delivery_system; + + dev_dbg(&client->dev, + "delivery_system=%u modulation=%u frequency=%u " + "bandwidth_hz=%u symbol_rate=%u inversion=%u " + "stream_id=%u\n", + c->delivery_system, c->modulation, c->frequency, + c->bandwidth_hz, c->symbol_rate, c->inversion, + c->stream_id); + + if (!dev->active) { + ret = -EAGAIN; + goto err; + } + + switch (c->delivery_system) { + case SYS_DVBT: + delivery_system = 0x20; + break; + case SYS_DVBC_ANNEX_A: + delivery_system = 0x30; + break; + case SYS_DVBT2: + delivery_system = 0x70; + break; + default: + ret = -EINVAL; + goto err; + } + + if (c->bandwidth_hz == 0) { + ret = -EINVAL; + goto err; + } else if (c->bandwidth_hz <= 2000000) + bandwidth = 0x02; + else if (c->bandwidth_hz <= 5000000) + bandwidth = 0x05; + else if (c->bandwidth_hz <= 6000000) + bandwidth = 0x06; + else if (c->bandwidth_hz <= 7000000) + bandwidth = 0x07; + else if (c->bandwidth_hz <= 8000000) + bandwidth = 0x08; + else if (c->bandwidth_hz <= 9000000) + bandwidth = 0x09; + else if (c->bandwidth_hz <= 10000000) + bandwidth = 0x0a; + else + bandwidth = 0x0f; + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) { + ret = fe->ops.tuner_ops.set_params(fe); + if (ret) + goto err; + } + + memcpy(cmd.args, "\x88\x02\x02\x02\x02", 5); + cmd.wlen = 5; + cmd.rlen = 5; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* that has no big effect */ + if (c->delivery_system == SYS_DVBT) + memcpy(cmd.args, "\x89\x21\x06\x11\xff\x98", 6); + else if (c->delivery_system == SYS_DVBC_ANNEX_A) + memcpy(cmd.args, "\x89\x21\x06\x11\x89\xf0", 6); + else if (c->delivery_system == SYS_DVBT2) + memcpy(cmd.args, "\x89\x21\x06\x11\x89\x20", 6); + cmd.wlen = 6; + cmd.rlen = 3; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + if (c->delivery_system == SYS_DVBT2) { + /* select PLP */ + cmd.args[0] = 0x52; + cmd.args[1] = c->stream_id & 0xff; + cmd.args[2] = c->stream_id == NO_STREAM_ID_FILTER ? 0 : 1; + cmd.wlen = 3; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + } + + memcpy(cmd.args, "\x51\x03", 2); + cmd.wlen = 2; + cmd.rlen = 12; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x12\x08\x04", 3); + cmd.wlen = 3; + cmd.rlen = 3; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x0c\x10\x12\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x06\x10\x24\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x07\x10\x00\x24", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x0a\x10\x00\x00", 6); + cmd.args[4] = delivery_system | bandwidth; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* set DVB-C symbol rate */ + if (c->delivery_system == SYS_DVBC_ANNEX_A) { + memcpy(cmd.args, "\x14\x00\x02\x11", 4); + cmd.args[4] = ((c->symbol_rate / 1000) >> 0) & 0xff; + cmd.args[5] = ((c->symbol_rate / 1000) >> 8) & 0xff; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + } + + memcpy(cmd.args, "\x14\x00\x0f\x10\x10\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x09\x10\xe3\x08", 6); + cmd.args[5] |= dev->ts_clock_inv ? 0x00 : 0x10; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x08\x10\xd7\x05", 6); + cmd.args[5] |= dev->ts_clock_inv ? 0x00 : 0x10; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x01\x12\x00\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x14\x00\x01\x03\x0c\x00", 6); + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x85", 1); + cmd.wlen = 1; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + dev->delivery_system = c->delivery_system; + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_init(struct dvb_frontend *fe) +{ + struct i2c_client *client = fe->demodulator_priv; + struct si2168_dev *dev = i2c_get_clientdata(client); + int ret, len, remaining; + const struct firmware *fw; + const char *fw_name; + struct si2168_cmd cmd; + unsigned int chip_id; + enum si2168_chip_id { + SI2168_A20 = ('A' << 24 | 68 << 16 | '2' << 8 | '0' << 0), + SI2168_A30 = ('A' << 24 | 68 << 16 | '3' << 8 | '0' << 0), + SI2168_B40 = ('B' << 24 | 68 << 16 | '4' << 8 | '0' << 0) + }; + + dev_dbg(&client->dev, "\n"); + + /* initialize */ + memcpy(cmd.args, "\xc0\x12\x00\x0c\x00\x0d\x16\x00\x00\x00\x00\x00\x00", 13); + cmd.wlen = 13; + cmd.rlen = 0; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + if (dev->fw_loaded) { + /* resume */ + memcpy(cmd.args, "\xc0\x06\x08\x0f\x00\x20\x21\x01", 8); + cmd.wlen = 8; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + memcpy(cmd.args, "\x85", 1); + cmd.wlen = 1; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + goto warm; + } + + /* power up */ + memcpy(cmd.args, "\xc0\x06\x01\x0f\x00\x20\x20\x01", 8); + cmd.wlen = 8; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* query chip revision */ + memcpy(cmd.args, "\x02", 1); + cmd.wlen = 1; + cmd.rlen = 13; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + chip_id = cmd.args[1] << 24 | cmd.args[2] << 16 | cmd.args[3] << 8 | + cmd.args[4] << 0; + + switch (chip_id) { + case SI2168_A20: + fw_name = SI2168_A20_FIRMWARE; + break; + case SI2168_A30: + fw_name = SI2168_A30_FIRMWARE; + break; + case SI2168_B40: + fw_name = SI2168_B40_FIRMWARE; + break; + default: + dev_err(&client->dev, "unknown chip version Si21%d-%c%c%c\n", + cmd.args[2], cmd.args[1], + cmd.args[3], cmd.args[4]); + ret = -EINVAL; + goto err; + } + + dev_info(&client->dev, "found a 'Silicon Labs Si21%d-%c%c%c'\n", + cmd.args[2], cmd.args[1], cmd.args[3], cmd.args[4]); + + /* request the firmware, this will block and timeout */ + ret = request_firmware(&fw, fw_name, &client->dev); + if (ret) { + /* fallback mechanism to handle old name for Si2168 B40 fw */ + if (chip_id == SI2168_B40) { + fw_name = SI2168_B40_FIRMWARE_FALLBACK; + ret = request_firmware(&fw, fw_name, &client->dev); + } + + if (ret == 0) { + dev_notice(&client->dev, + "please install firmware file '%s'\n", + SI2168_B40_FIRMWARE); + } else { + dev_err(&client->dev, + "firmware file '%s' not found\n", + fw_name); + goto err_release_firmware; + } + } + + dev_info(&client->dev, "downloading firmware from file '%s'\n", + fw_name); + + if ((fw->size % 17 == 0) && (fw->data[0] > 5)) { + /* firmware is in the new format */ + for (remaining = fw->size; remaining > 0; remaining -= 17) { + len = fw->data[fw->size - remaining]; + memcpy(cmd.args, &fw->data[(fw->size - remaining) + 1], len); + cmd.wlen = len; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + break; + } + } else if (fw->size % 8 == 0) { + /* firmware is in the old format */ + for (remaining = fw->size; remaining > 0; remaining -= 8) { + len = 8; + memcpy(cmd.args, &fw->data[fw->size - remaining], len); + cmd.wlen = len; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + break; + } + } else { + /* bad or unknown firmware format */ + ret = -EINVAL; + } + + if (ret) { + dev_err(&client->dev, "firmware download failed %d\n", ret); + goto err_release_firmware; + } + + release_firmware(fw); + + memcpy(cmd.args, "\x01\x01", 2); + cmd.wlen = 2; + cmd.rlen = 1; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + /* query firmware version */ + memcpy(cmd.args, "\x11", 1); + cmd.wlen = 1; + cmd.rlen = 10; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + dev_info(&client->dev, "firmware version: %c.%c.%d\n", + cmd.args[6], cmd.args[7], cmd.args[8]); + + /* set ts mode */ + memcpy(cmd.args, "\x14\x00\x01\x10\x10\x00", 6); + cmd.args[4] |= dev->ts_mode; + if (dev->ts_clock_gapped) + cmd.args[4] |= 0x40; + cmd.wlen = 6; + cmd.rlen = 4; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + dev->fw_loaded = true; +warm: + dev->active = true; + + return 0; + +err_release_firmware: + release_firmware(fw); +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_sleep(struct dvb_frontend *fe) +{ + struct i2c_client *client = fe->demodulator_priv; + struct si2168_dev *dev = i2c_get_clientdata(client); + int ret; + struct si2168_cmd cmd; + + dev_dbg(&client->dev, "\n"); + + dev->active = false; + + memcpy(cmd.args, "\x13", 1); + cmd.wlen = 1; + cmd.rlen = 0; + ret = si2168_cmd_execute(client, &cmd); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 900; + + return 0; +} + +/* + * I2C gate logic + * We must use unlocked I2C I/O because I2C adapter lock is already taken + * by the caller (usually tuner driver). + */ +static int si2168_select(struct i2c_adapter *adap, void *mux_priv, u32 chan) +{ + struct i2c_client *client = mux_priv; + int ret; + struct si2168_cmd cmd; + + /* open I2C gate */ + memcpy(cmd.args, "\xc0\x0d\x01", 3); + cmd.wlen = 3; + cmd.rlen = 0; + ret = si2168_cmd_execute_unlocked(client, &cmd); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_deselect(struct i2c_adapter *adap, void *mux_priv, u32 chan) +{ + struct i2c_client *client = mux_priv; + int ret; + struct si2168_cmd cmd; + + /* close I2C gate */ + memcpy(cmd.args, "\xc0\x0d\x00", 3); + cmd.wlen = 3; + cmd.rlen = 0; + ret = si2168_cmd_execute_unlocked(client, &cmd); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static const struct dvb_frontend_ops si2168_ops = { + .delsys = {SYS_DVBT, SYS_DVBT2, SYS_DVBC_ANNEX_A}, + .info = { + .name = "Silicon Labs Si2168", + .symbol_rate_min = 1000000, + .symbol_rate_max = 7200000, + .caps = FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_QAM_16 | + FE_CAN_QAM_32 | + FE_CAN_QAM_64 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_MUTE_TS | + FE_CAN_2G_MODULATION | + FE_CAN_MULTISTREAM + }, + + .get_tune_settings = si2168_get_tune_settings, + + .init = si2168_init, + .sleep = si2168_sleep, + + .set_frontend = si2168_set_frontend, + + .read_status = si2168_read_status, +}; + +static int si2168_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct si2168_config *config = client->dev.platform_data; + struct si2168_dev *dev; + int ret; + + dev_dbg(&client->dev, "\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + dev_err(&client->dev, "kzalloc() failed\n"); + goto err; + } + + /* create mux i2c adapter for tuner */ + dev->adapter = i2c_add_mux_adapter(client->adapter, &client->dev, + client, 0, 0, 0, si2168_select, si2168_deselect); + if (dev->adapter == NULL) { + ret = -ENODEV; + goto err_kfree; + } + + /* create dvb_frontend */ + memcpy(&dev->fe.ops, &si2168_ops, sizeof(struct dvb_frontend_ops)); + dev->fe.demodulator_priv = client; + *config->i2c_adapter = dev->adapter; + *config->fe = &dev->fe; + dev->ts_mode = config->ts_mode; + dev->ts_clock_inv = config->ts_clock_inv; + dev->ts_clock_gapped = config->ts_clock_gapped; + dev->fw_loaded = false; + + i2c_set_clientdata(client, dev); + + dev_info(&client->dev, "Silicon Labs Si2168 successfully attached\n"); + return 0; +err_kfree: + kfree(dev); +err: + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; +} + +static int si2168_remove(struct i2c_client *client) +{ + struct si2168_dev *dev = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "\n"); + + i2c_del_mux_adapter(dev->adapter); + + dev->fe.ops.release = NULL; + dev->fe.demodulator_priv = NULL; + + kfree(dev); + + return 0; +} + +static const struct i2c_device_id si2168_id_table[] = { + {"si2168", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, si2168_id_table); + +static struct i2c_driver si2168_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "si2168", + }, + .probe = si2168_probe, + .remove = si2168_remove, + .id_table = si2168_id_table, +}; + +module_i2c_driver(si2168_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Silicon Labs Si2168 DVB-T/T2/C demodulator driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(SI2168_A20_FIRMWARE); +MODULE_FIRMWARE(SI2168_A30_FIRMWARE); +MODULE_FIRMWARE(SI2168_B40_FIRMWARE);
diff --git a/drivers/media/dvb-frontends/si2168.h b/drivers/media/dvb-frontends/si2168.h new file mode 100644 index 0000000..3225d0c --- /dev/null +++ b/drivers/media/dvb-frontends/si2168.h
@@ -0,0 +1,50 @@ +/* + * Silicon Labs Si2168 DVB-T/T2/C demodulator driver + * + * Copyright (C) 2014 Antti Palosaari <crope@iki.fi> + * + * 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. + */ + +#ifndef SI2168_H +#define SI2168_H + +#include <linux/dvb/frontend.h> +/* + * I2C address + * 0x64 + */ +struct si2168_config { + /* + * frontend + * returned by driver + */ + struct dvb_frontend **fe; + + /* + * tuner I2C adapter + * returned by driver + */ + struct i2c_adapter **i2c_adapter; + + /* TS mode */ +#define SI2168_TS_PARALLEL 0x06 +#define SI2168_TS_SERIAL 0x03 + u8 ts_mode; + + /* TS clock inverted */ + bool ts_clock_inv; + + /* TS clock gapped */ + bool ts_clock_gapped; +}; + +#endif
diff --git a/drivers/media/dvb-frontends/si2168_priv.h b/drivers/media/dvb-frontends/si2168_priv.h new file mode 100644 index 0000000..c07e6fe --- /dev/null +++ b/drivers/media/dvb-frontends/si2168_priv.h
@@ -0,0 +1,51 @@ +/* + * Silicon Labs Si2168 DVB-T/T2/C demodulator driver + * + * Copyright (C) 2014 Antti Palosaari <crope@iki.fi> + * + * 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. + */ + +#ifndef SI2168_PRIV_H +#define SI2168_PRIV_H + +#include "si2168.h" +#include "dvb_frontend.h" +#include <linux/firmware.h> +#include <linux/i2c-mux.h> + +#define SI2168_A20_FIRMWARE "dvb-demod-si2168-a20-01.fw" +#define SI2168_A30_FIRMWARE "dvb-demod-si2168-a30-01.fw" +#define SI2168_B40_FIRMWARE "dvb-demod-si2168-b40-01.fw" +#define SI2168_B40_FIRMWARE_FALLBACK "dvb-demod-si2168-02.fw" + +/* state struct */ +struct si2168_dev { + struct i2c_adapter *adapter; + struct dvb_frontend fe; + enum fe_delivery_system delivery_system; + enum fe_status fe_status; + bool active; + bool fw_loaded; + u8 ts_mode; + bool ts_clock_inv; + bool ts_clock_gapped; +}; + +/* firmware command struct */ +#define SI2168_ARGLEN 30 +struct si2168_cmd { + u8 args[SI2168_ARGLEN]; + unsigned wlen; + unsigned rlen; +}; + +#endif
diff --git a/drivers/media/tuners/si2157.c b/drivers/media/tuners/si2157.c index 7c2f621..942ab21 100644 --- a/drivers/media/tuners/si2157.c +++ b/drivers/media/tuners/si2157.c
@@ -18,51 +18,10 @@ static const struct dvb_tuner_ops si2157_ops; -/* si2157_i2c_master_send - issue a single I2C message in master transmit mode */ -static int si2157_i2c_master_send(struct i2c_adapter *adap, u8 i2c_addr, - const char *buf, int count) -{ - int ret; - struct i2c_msg msg; - - msg.addr = i2c_addr; - msg.flags = 0; - msg.len = count; - msg.buf = (char *)buf; - - ret = i2c_transfer(adap, &msg, 1); - - /* - * If everything went ok (i.e. 1 msg transmitted), return #bytes - * transmitted, else error code. - */ - return (ret == 1) ? count : ret; -} - -/* si2157_i2c_master_recv - issue a single I2C message in master receive mode */ -static int si2157_i2c_master_recv(struct i2c_adapter *adap, u8 i2c_addr, - char *buf, int count) -{ - struct i2c_msg msg; - int ret; - - msg.addr = i2c_addr; - msg.flags = I2C_M_RD; - msg.len = count; - msg.buf = buf; - - ret = i2c_transfer(adap, &msg, 1); - - /* - * If everything went ok (i.e. 1 msg received), return #bytes received, - * else error code. - */ - return (ret == 1) ? count : ret; -} - /* execute firmware command */ -static int si2157_cmd_execute(struct si2157_dev *dev, struct si2157_cmd *cmd) +static int si2157_cmd_execute(struct i2c_client *client, struct si2157_cmd *cmd) { + struct si2157_dev *dev = i2c_get_clientdata(client); int ret; unsigned long timeout; @@ -70,8 +29,7 @@ if (cmd->wlen) { /* write cmd and args for firmware */ - ret = si2157_i2c_master_send(dev->i2c_adap, dev->i2c_addr, - cmd->args, cmd->wlen); + ret = i2c_master_send(client, cmd->args, cmd->wlen); if (ret < 0) { goto err_mutex_unlock; } else if (ret != cmd->wlen) { @@ -85,8 +43,7 @@ #define TIMEOUT 80 timeout = jiffies + msecs_to_jiffies(TIMEOUT); while (!time_after(jiffies, timeout)) { - ret = si2157_i2c_master_recv(dev->i2c_adap, dev->i2c_addr, - cmd->args, cmd->rlen); + ret = i2c_master_recv(client, cmd->args, cmd->rlen); if (ret < 0) { goto err_mutex_unlock; } else if (ret != cmd->rlen) { @@ -99,7 +56,7 @@ break; } - dev_dbg(&dev->i2c_adap->dev, "cmd execution took %d ms\n", + dev_dbg(&client->dev, "cmd execution took %d ms\n", jiffies_to_msecs(jiffies) - (jiffies_to_msecs(timeout) - TIMEOUT)); @@ -114,20 +71,22 @@ err_mutex_unlock: mutex_unlock(&dev->i2c_mutex); - dev_dbg(&dev->i2c_adap->dev, "failed=%d\n", ret); + dev_dbg(&client->dev, "failed=%d\n", ret); return ret; } static int si2157_init(struct dvb_frontend *fe) { - struct si2157_dev *dev = fe->tuner_priv; + struct i2c_client *client = fe->tuner_priv; + struct si2157_dev *dev = i2c_get_clientdata(client); + struct dtv_frontend_properties *c = &fe->dtv_property_cache; int ret, len, remaining; struct si2157_cmd cmd; const struct firmware *fw; const char *fw_name; unsigned int chip_id; - dev_dbg(&dev->i2c_adap->dev, "\n"); + dev_dbg(&client->dev, "\n"); if (dev->fw_loaded) goto warm; @@ -141,7 +100,7 @@ cmd.wlen = 15; } cmd.rlen = 1; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) goto err; @@ -149,7 +108,7 @@ memcpy(cmd.args, "\x02", 1); cmd.wlen = 1; cmd.rlen = 13; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) goto err; @@ -173,36 +132,36 @@ fw_name = NULL; break; default: - dev_err(&dev->i2c_adap->dev, - "unknown chip version Si21%d-%c%c%c\n", cmd.args[2], - cmd.args[1], cmd.args[3], cmd.args[4]); + dev_err(&client->dev, "unknown chip version Si21%d-%c%c%c\n", + cmd.args[2], cmd.args[1], + cmd.args[3], cmd.args[4]); ret = -EINVAL; goto err; } - dev_info(&dev->i2c_adap->dev, "found a 'Silicon Labs Si21%d-%c%c%c'\n", + dev_info(&client->dev, "found a 'Silicon Labs Si21%d-%c%c%c'\n", cmd.args[2], cmd.args[1], cmd.args[3], cmd.args[4]); if (fw_name == NULL) goto skip_fw_download; /* request the firmware, this will block and timeout */ - ret = request_firmware(&fw, fw_name, &dev->i2c_adap->dev); + ret = request_firmware(&fw, fw_name, &client->dev); if (ret) { - dev_err(&dev->i2c_adap->dev, "firmware file '%s' not found\n", + dev_err(&client->dev, "firmware file '%s' not found\n", fw_name); goto err; } /* firmware should be n chunks of 17 bytes */ if (fw->size % 17 != 0) { - dev_err(&dev->i2c_adap->dev, "firmware file '%s' is invalid\n", + dev_err(&client->dev, "firmware file '%s' is invalid\n", fw_name); ret = -EINVAL; goto err_release_firmware; } - dev_info(&dev->i2c_adap->dev, "downloading firmware from file '%s'\n", + dev_info(&client->dev, "downloading firmware from file '%s'\n", fw_name); for (remaining = fw->size; remaining > 0; remaining -= 17) { @@ -210,9 +169,9 @@ memcpy(cmd.args, &fw->data[(fw->size - remaining) + 1], len); cmd.wlen = len; cmd.rlen = 1; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) { - dev_err(&dev->i2c_adap->dev, "firmware download failed %d\n", + dev_err(&client->dev, "firmware download failed %d\n", ret); goto err_release_firmware; } @@ -225,7 +184,7 @@ memcpy(cmd.args, "\x01\x01", 2); cmd.wlen = 2; cmd.rlen = 1; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) goto err; @@ -233,60 +192,70 @@ memcpy(cmd.args, "\x11", 1); cmd.wlen = 1; cmd.rlen = 10; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) goto err; - dev_info(&dev->i2c_adap->dev, "firmware version: %c.%c.%d\n", + dev_info(&client->dev, "firmware version: %c.%c.%d\n", cmd.args[6], cmd.args[7], cmd.args[8]); dev->fw_loaded = true; warm: + /* init statistics in order signal app which are supported */ + c->strength.len = 1; + c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + /* start statistics polling */ + schedule_delayed_work(&dev->stat_work, msecs_to_jiffies(1000)); + dev->active = true; return 0; - err_release_firmware: release_firmware(fw); err: - dev_dbg(&dev->i2c_adap->dev, "failed=%d\n", ret); + dev_dbg(&client->dev, "failed=%d\n", ret); return ret; } static int si2157_sleep(struct dvb_frontend *fe) { - struct si2157_dev *dev = fe->tuner_priv; + struct i2c_client *client = fe->tuner_priv; + struct si2157_dev *dev = i2c_get_clientdata(client); int ret; struct si2157_cmd cmd; - dev_dbg(&dev->i2c_adap->dev, "\n"); + dev_dbg(&client->dev, "\n"); dev->active = false; + /* stop statistics polling */ + cancel_delayed_work_sync(&dev->stat_work); + /* standby */ memcpy(cmd.args, "\x16\x00", 2); cmd.wlen = 2; cmd.rlen = 1; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) goto err; return 0; err: - dev_dbg(&dev->i2c_adap->dev, "failed=%d\n", ret); + dev_dbg(&client->dev, "failed=%d\n", ret); return ret; } static int si2157_set_params(struct dvb_frontend *fe) { - struct si2157_dev *dev = fe->tuner_priv; + struct i2c_client *client = fe->tuner_priv; + struct si2157_dev *dev = i2c_get_clientdata(client); struct dtv_frontend_properties *c = &fe->dtv_property_cache; int ret; struct si2157_cmd cmd; u8 bandwidth, delivery_system; u32 if_frequency = 5000000; - dev_dbg(&dev->i2c_adap->dev, + dev_dbg(&client->dev, "delivery_system=%d frequency=%u bandwidth_hz=%u\n", c->delivery_system, c->frequency, c->bandwidth_hz); @@ -331,17 +300,18 @@ cmd.args[5] = 0x01; cmd.wlen = 6; cmd.rlen = 4; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) goto err; if (dev->chiptype == SI2157_CHIPTYPE_SI2146) memcpy(cmd.args, "\x14\x00\x02\x07\x00\x01", 6); else - memcpy(cmd.args, "\x14\x00\x02\x07\x01\x00", 6); + memcpy(cmd.args, "\x14\x00\x02\x07\x00\x00", 6); + cmd.args[4] = dev->if_port; cmd.wlen = 6; cmd.rlen = 4; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) goto err; @@ -352,7 +322,7 @@ cmd.args[5] = ((if_frequency / 1000) >> 8) & 0xff; cmd.wlen = 6; cmd.rlen = 4; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) goto err; @@ -367,99 +337,145 @@ cmd.args[7] = (c->frequency >> 24) & 0xff; cmd.wlen = 8; cmd.rlen = 1; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) goto err; return 0; err: - dev_dbg(&dev->i2c_adap->dev, "failed=%d\n", ret); + dev_dbg(&client->dev, "failed=%d\n", ret); return ret; } static int si2157_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) { - struct si2157_dev *dev = fe->tuner_priv; + struct i2c_client *client = fe->tuner_priv; + struct si2157_dev *dev = i2c_get_clientdata(client); *frequency = dev->if_frequency; return 0; } -static int si2157_release(struct dvb_frontend *fe) -{ - struct si2157_dev *dev = fe->tuner_priv; - - dev_dbg(&dev->i2c_adap->dev, "%s:\n", __func__); - - kfree(fe->tuner_priv); - - return 0; -} - static const struct dvb_tuner_ops si2157_ops = { .info = { .name = "Silicon Labs Si2146/2147/2148/2157/2158", - .frequency_min = 55000000, - .frequency_max = 862000000, + .frequency_min = 42000000, + .frequency_max = 870000000, }, - .release = si2157_release, .init = si2157_init, .sleep = si2157_sleep, .set_params = si2157_set_params, .get_if_frequency = si2157_get_if_frequency, }; -struct dvb_frontend *si2157_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, const struct si2157_config *cfg) +static void si2157_stat_work(struct work_struct *work) { + struct si2157_dev *dev = container_of(work, struct si2157_dev, stat_work.work); + struct dvb_frontend *fe = dev->fe; + struct i2c_client *client = fe->tuner_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct si2157_cmd cmd; + int ret; + + dev_dbg(&client->dev, "\n"); + + memcpy(cmd.args, "\x42\x00", 2); + cmd.wlen = 2; + cmd.rlen = 12; + ret = si2157_cmd_execute(client, &cmd); + if (ret) + goto err; + + c->strength.stat[0].scale = FE_SCALE_DECIBEL; + c->strength.stat[0].svalue = ((s8) cmd.args[3]) * 1000; + + schedule_delayed_work(&dev->stat_work, msecs_to_jiffies(2000)); + return; +err: + c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + dev_dbg(&client->dev, "failed=%d\n", ret); +} + +static int si2157_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct si2157_config *cfg = client->dev.platform_data; + struct dvb_frontend *fe = cfg->fe; struct si2157_dev *dev; struct si2157_cmd cmd; int ret; - if (!cfg) { - dev_err(&i2c->dev, "no configuration submitted\n"); - goto err; - } - dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { ret = -ENOMEM; - dev_err(&i2c->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME); + dev_err(&client->dev, "kzalloc() failed\n"); goto err; } - dev->i2c_adap = i2c; - dev->i2c_addr = cfg->i2c_addr; + i2c_set_clientdata(client, dev); + dev->fe = cfg->fe; dev->inversion = cfg->inversion; + dev->if_port = cfg->if_port; dev->fw_loaded = false; - dev->chiptype = SI2157_CHIPTYPE_SI2157; /* (u8)id->driver_data; */ + dev->chiptype = (u8)id->driver_data; dev->if_frequency = 5000000; /* default value of property 0x0706 */ mutex_init(&dev->i2c_mutex); + INIT_DELAYED_WORK(&dev->stat_work, si2157_stat_work); /* check if the tuner is there */ cmd.wlen = 0; cmd.rlen = 1; - ret = si2157_cmd_execute(dev, &cmd); + ret = si2157_cmd_execute(client, &cmd); if (ret) goto err_kfree; memcpy(&fe->ops.tuner_ops, &si2157_ops, sizeof(struct dvb_tuner_ops)); - fe->tuner_priv = dev; + fe->tuner_priv = client; - dev_info(&i2c->dev, "%s: Silicon Labs %s successfully attached\n", - KBUILD_MODNAME, dev->chiptype == SI2157_CHIPTYPE_SI2146 ? + dev_info(&client->dev, "Silicon Labs %s successfully attached\n", + dev->chiptype == SI2157_CHIPTYPE_SI2146 ? "Si2146" : "Si2147/2148/2157/2158"); - return fe; + return 0; err_kfree: kfree(dev); err: - dev_dbg(&i2c->dev, "failed=%d\n", ret); - return NULL; + dev_dbg(&client->dev, "failed=%d\n", ret); + return ret; } -EXPORT_SYMBOL(si2157_attach); + +static int si2157_remove(struct i2c_client *client) +{ + struct si2157_dev *dev = i2c_get_clientdata(client); + struct dvb_frontend *fe = dev->fe; + + memset(&fe->ops.tuner_ops, 0, sizeof(fe->ops.tuner_ops)); + fe->tuner_priv = NULL; + kfree(dev); + + return 0; +} + +static const struct i2c_device_id si2157_id_table[] = { + {"si2157", SI2157_CHIPTYPE_SI2157}, + {"si2146", SI2157_CHIPTYPE_SI2146}, + {} +}; +MODULE_DEVICE_TABLE(i2c, si2157_id_table); + +static struct i2c_driver si2157_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "si2157", + }, + .probe = si2157_probe, + .remove = si2157_remove, + .id_table = si2157_id_table, +}; + +module_i2c_driver(si2157_driver); MODULE_DESCRIPTION("Silicon Labs Si2146/2147/2148/2157/2158 silicon tuner driver"); MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
diff --git a/drivers/media/tuners/si2157.h b/drivers/media/tuners/si2157.h index 1755ba3..4db97ab 100644 --- a/drivers/media/tuners/si2157.h +++ b/drivers/media/tuners/si2157.h
@@ -20,28 +20,26 @@ #include <linux/kconfig.h> #include "dvb_frontend.h" +/* + * I2C address + * 0x60 + */ struct si2157_config { /* - * I2C address + * frontend */ - u8 i2c_addr; + struct dvb_frontend *fe; /* * Spectral Inversion */ bool inversion; -}; -#if IS_ENABLED(CONFIG_MEDIA_TUNER_SI2157) -extern struct dvb_frontend *si2157_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, const struct si2157_config *cfg); -#else -static inline struct dvb_frontend *si2157_attach(struct dvb_frontend *fe, - struct i2c_adapter *i2c, const struct si2157_config *cfg) -{ - pr_warn("%s: driver disabled by Kconfig\n", __func__); - return NULL; -} -#endif + /* + * Port selection + * Select the RF interface to use (pins 9+11 or 12+13) + */ + u8 if_port; +}; #endif
diff --git a/drivers/media/tuners/si2157_priv.h b/drivers/media/tuners/si2157_priv.h index 7830ccd..ecc463d 100644 --- a/drivers/media/tuners/si2157_priv.h +++ b/drivers/media/tuners/si2157_priv.h
@@ -23,13 +23,14 @@ /* state struct */ struct si2157_dev { struct mutex i2c_mutex; - struct i2c_adapter *i2c_adap; - u8 i2c_addr; + struct dvb_frontend *fe; bool active; bool fw_loaded; bool inversion; u8 chiptype; + u8 if_port; u32 if_frequency; + struct delayed_work stat_work; }; #define SI2157_CHIPTYPE_SI2157 0
diff --git a/drivers/media/usb/cx231xx/cx231xx-dvb.c b/drivers/media/usb/cx231xx/cx231xx-dvb.c index 66340b5..773306c 100644 --- a/drivers/media/usb/cx231xx/cx231xx-dvb.c +++ b/drivers/media/usb/cx231xx/cx231xx-dvb.c
@@ -69,6 +69,7 @@ struct dmx_frontend fe_hw; struct dmx_frontend fe_mem; struct dvb_net net; + struct i2c_client *i2c_client_tuner; int power_on; }; @@ -183,11 +184,6 @@ .xtalMHz = 25, }; -static struct si2157_config si2157_config = { - .i2c_addr = 0x60, - .inversion = true, -}; - static inline void print_err_status(struct cx231xx *dev, int packet, int status) { char *errmsg = "Unknown"; @@ -608,11 +604,18 @@ static void unregister_dvb(struct cx231xx_dvb *dvb) { + struct i2c_client *client; dvb_net_release(&dvb->net); dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem); dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw); dvb_dmxdev_release(&dvb->dmxdev); dvb_dmx_release(&dvb->demux); + client = dvb->i2c_client_tuner; + /* remove I2C tuner */ + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } dvb_unregister_frontend(dvb->frontend); dvb_frontend_detach(dvb->frontend); dvb_unregister_adapter(&dvb->adapter); @@ -769,7 +772,11 @@ &hcw_tda18271_config); break; - case CX231XX_BOARD_HAUPPAUGE_955Q: + case CX231XX_BOARD_HAUPPAUGE_955Q: { + + struct i2c_client *client; + struct i2c_board_info info; + struct si2157_config si2157_config; printk(KERN_INFO "%s: looking for tuner / demod on i2c bus: %d\n", __func__, i2c_adapter_id(&dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap)); @@ -788,11 +795,38 @@ /* define general-purpose callback pointer */ dvb->frontend->callback = cx231xx_tuner_callback; - dvb_attach(si2157_attach, dev->dvb->frontend, - &dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap, - &si2157_config); - break; + /* attach tuner */ + memset(&info, 0, sizeof(info)); + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = dev->dvb->frontend; + si2157_config.if_port = 1; + si2157_config.inversion = true; + strlcpy(info.type, "si2157", I2C_NAME_SIZE); + info.addr = 0x60; + info.platform_data = &si2157_config; + request_module("si2157"); + client = i2c_new_device( + &dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap, + &info); + if (client == NULL || client->dev.driver == NULL) { + dvb_frontend_detach(dev->dvb->frontend); + result = -ENODEV; + goto out_free; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + dvb_frontend_detach(dev->dvb->frontend); + result = -ENODEV; + goto out_free; + } + + dev->cx231xx_reset_analog_tuner = NULL; + + dev->dvb->i2c_client_tuner = client; + break; + } case CX231XX_BOARD_PV_PLAYTV_USB_HYBRID: case CX231XX_BOARD_KWORLD_UB430_USB_HYBRID:
diff --git a/drivers/media/usb/em28xx/Kconfig b/drivers/media/usb/em28xx/Kconfig index ca5ee6a..45ff31e 100644 --- a/drivers/media/usb/em28xx/Kconfig +++ b/drivers/media/usb/em28xx/Kconfig
@@ -35,6 +35,7 @@ depends on VIDEO_EM28XX && DVB_CORE select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT select DVB_LGDT3305 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT3306A if MEDIA_SUBDRV_AUTOSELECT select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT select DVB_S921 if MEDIA_SUBDRV_AUTOSELECT @@ -47,8 +48,10 @@ select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT + select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT ---help--- This adds support for DVB cards based on the Empiatech em28xx chips.
diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c index 83bfbe4..6804b11 100644 --- a/drivers/media/usb/em28xx/em28xx-cards.c +++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -411,6 +411,29 @@ { -1, -1, -1, -1}, }; +/* 2040:0265 Hauppauge WinTV-dualHD DVB + * reg 0x80/0x84: + * GPIO_0: Yellow LED tuner 1, 0=on, 1=off + * GPIO_1: Green LED tuner 1, 0=on, 1=off + * GPIO_2: Yellow LED tuner 2, 0=on, 1=off + * GPIO_3: Green LED tuner 2, 0=on, 1=off + * GPIO_5: Reset #2, 0=active + * GPIO_6: Reset #1, 0=active + */ +static struct em28xx_reg_seq hauppauge_dualhd_dvb[] = { + {EM2874_R80_GPIO, 0xff, 0xff, 0}, + {0x0d, 0xff, 0xff, 200}, + {0x50, 0x04, 0xff, 300}, + {EM2874_R80_GPIO, 0xbf, 0xff, 100}, /* demod 1 reset */ + {EM2874_R80_GPIO, 0xff, 0xff, 100}, + {EM2874_R80_GPIO, 0xdf, 0xff, 100}, /* demod 2 reset */ + {EM2874_R80_GPIO, 0xff, 0xff, 100}, + {EM2874_R5F_TS_ENABLE, 0x44, 0xff, 50}, + {EM2874_R5D_TS1_PKT_SIZE, 0x05, 0xff, 50}, + {EM2874_R5E_TS2_PKT_SIZE, 0x05, 0xff, 50}, + { -1, -1, -1, -1}, +}; + /* * Board definitions */ @@ -2017,6 +2040,30 @@ .i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ, }, + /* 2040:0265 Hauppauge WinTV-dualHD (DVB version). + * Empia EM28274, 2x Silicon Labs Si2168, 2x Silicon Labs Si2157 */ + [EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB] = { + .name = "Hauppauge WinTV-dualHD DVB", + .def_i2c_bus = 1, + .i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ, + .tuner_type = TUNER_ABSENT, + .tuner_gpio = hauppauge_dualhd_dvb, + .has_dvb = 1, + .has_dual_ts = 1, + .ir_codes = RC_MAP_HAUPPAUGE, + }, + /* 2040:026D Hauppauge WinTV-dualHD (DVB version). + * Empia EM28274, 2x LGDT3306A, 2x Silicon Labs Si2157 */ + [EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC] = { + .name = "Hauppauge WinTV-dualHD ATSC", + .def_i2c_bus = 1, + .i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ, + .tuner_type = TUNER_ABSENT, + .tuner_gpio = hauppauge_dualhd_dvb, + .has_dvb = 1, + .has_dual_ts = 1, + .ir_codes = RC_MAP_HAUPPAUGE, + }, }; const unsigned int em28xx_bcount = ARRAY_SIZE(em28xx_boards); @@ -2178,6 +2225,14 @@ .driver_info = EM2884_BOARD_PCTV_510E }, { USB_DEVICE(0x2013, 0x0251), .driver_info = EM2884_BOARD_PCTV_520E }, + { USB_DEVICE(0x2040, 0x0265), /* ISOC */ + .driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB }, + { USB_DEVICE(0x2040, 0x8265), /* BULK */ + .driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB }, + { USB_DEVICE(0x2040, 0x026D), /* ISOC */ + .driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC }, + { USB_DEVICE(0x2040, 0x826D), /* BULK */ + .driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC }, { }, }; MODULE_DEVICE_TABLE(usb, em28xx_id_table); @@ -2638,6 +2693,8 @@ case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850: case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950: case EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C: + case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB: + case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC: { struct tveeprom tv; @@ -2847,7 +2904,8 @@ v4l2_device_unregister(&dev->v4l2_dev); - usb_put_dev(dev->udev); + if (dev->ts == PRIMARY_TS) + usb_put_dev(dev->udev); /* Mark device as unused */ clear_bit(dev->devno, &em28xx_devused); @@ -3115,6 +3173,21 @@ /* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */ #define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) +int em28xx_duplicate_dev(struct em28xx *dev) +{ + struct em28xx *sec_dev = NULL; + int nr; + sec_dev = kzalloc(sizeof(struct em28xx), GFP_KERNEL); + memcpy(sec_dev, dev, sizeof(struct em28xx)); + nr = find_first_zero_bit(&em28xx_devused, EM28XX_MAXBOARDS); + test_and_set_bit(nr, &em28xx_devused); + sec_dev->devno = nr; + snprintf(sec_dev->name, 28, "em28xx #%d", nr); + dev->dev_next = sec_dev; + sec_dev->dev_next = NULL; + return 0; +} + /* * em28xx_usb_probe() * checks for supported devices @@ -3125,7 +3198,7 @@ struct usb_device *udev; struct em28xx *dev = NULL; int retval; - bool has_audio = false, has_video = false, has_dvb = false; + bool has_audio = false, has_video = false, has_dvb = false, has_dvb_ts2 = false; int i, nr, try_bulk; const int ifnum = interface->altsetting[0].desc.bInterfaceNumber; char *speed; @@ -3232,6 +3305,19 @@ } } break; + case 0x85: + if (usb_endpoint_xfer_isoc(e)) { + if (size > dev->dvb_max_pkt_size_isoc_ts2) { + has_dvb_ts2 = true; /* see NOTE (~) */ + dev->dvb_ep_isoc_ts2 = e->bEndpointAddress; + dev->dvb_max_pkt_size_isoc_ts2 = size; + dev->dvb_alt_isoc = i; + } + } else { + has_dvb_ts2 = true; + dev->dvb_ep_bulk_ts2 = e->bEndpointAddress; + } + break; } } /* NOTE: @@ -3313,6 +3399,8 @@ dev->is_audio_only = has_audio && !(has_video || has_dvb); dev->has_alsa_audio = has_audio; dev->audio_ifnum = ifnum; + dev->ts = PRIMARY_TS; + dev->dev_next = NULL; /* Checks if audio is provided by some interface */ for (i = 0; i < udev->config->desc.bNumInterfaces; i++) { @@ -3382,26 +3470,49 @@ em28xx_info("dvb set to %s mode.\n", dev->dvb_xfer_bulk ? "bulk" : "isoc"); + } - /* pre-allocate DVB usb transfer buffers */ - if (dev->dvb_xfer_bulk) { - retval = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE, - dev->dvb_xfer_bulk, - EM28XX_DVB_NUM_BUFS, - 512, - EM28XX_DVB_BULK_PACKET_MULTIPLIER); + if (dev->board.has_dual_ts) { + em28xx_duplicate_dev(dev); + dev->dev_next->ts = SECONDARY_TS; + dev->dev_next->alt = -1; + dev->dev_next->is_audio_only = has_audio && !(has_video || has_dvb); + dev->dev_next->has_video = false; + dev->dev_next->ifnum = ifnum; + dev->dev_next->model = id->driver_info; + + mutex_init(&dev->dev_next->lock); + mutex_lock(&dev->dev_next->lock); + retval = em28xx_init_dev(dev->dev_next, udev, interface, dev->dev_next->devno); + if (retval) + goto err_free; + + if (usb_xfer_mode < 0) { + if (dev->dev_next->board.is_webcam) + try_bulk = 1; + else + try_bulk = 0; } else { - retval = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE, - dev->dvb_xfer_bulk, - EM28XX_DVB_NUM_BUFS, - dev->dvb_max_pkt_size_isoc, - EM28XX_DVB_NUM_ISOC_PACKETS); + try_bulk = usb_xfer_mode > 0; } - if (retval) { - printk(DRIVER_NAME - ": Failed to pre-allocate USB transfer buffers for DVB.\n"); - goto unlock_and_free; + + /* Select USB transfer types to use */ + if (has_dvb) { + if (!dev->dvb_ep_isoc_ts2 || (try_bulk && dev->dvb_ep_bulk_ts2)) + dev->dev_next->dvb_xfer_bulk = 1; + em28xx_info("dvb ts2 set to %s mode.\n", + dev->dev_next->dvb_xfer_bulk ? "bulk" : "isoc"); } + + dev->dev_next->dvb_ep_isoc = dev->dvb_ep_isoc_ts2; + dev->dev_next->dvb_max_pkt_size_isoc = dev->dvb_max_pkt_size_isoc_ts2; + dev->dev_next->dvb_alt_isoc = dev->dvb_alt_isoc; + + /* Configure hardware to support TS2*/ + em28xx_write_reg(dev, 0x0b, 0x96); + mdelay(100); + em28xx_write_reg(dev, 0x0b, 0x82); + mdelay(100); } request_modules(dev); @@ -3410,6 +3521,8 @@ open the device before fully initializing it */ mutex_unlock(&dev->lock); + if (dev->dev_next != NULL) + mutex_unlock(&dev->dev_next->lock); return 0; @@ -3422,6 +3535,8 @@ err: clear_bit(nr, &em28xx_devused); + if (dev->dev_next != NULL) + clear_bit(dev->dev_next->devno, &em28xx_devused); err_no_slot: usb_put_dev(udev); @@ -3443,6 +3558,14 @@ if (!dev) return; + if (dev->dev_next != NULL) { + dev->dev_next->disconnected = 1; + + em28xx_info("Disconnecting %s\n", dev->dev_next->name); + + flush_request_modules(dev->dev_next); + } + dev->disconnected = 1; if (dev->is_audio_only) { @@ -3471,11 +3594,20 @@ em28xx_close_extension(dev); /* NOTE: must be called BEFORE the resources are released */ + if ((!dev->users) && (dev->dev_next != NULL)) + em28xx_release_resources(dev->dev_next); + if (!dev->users) em28xx_release_resources(dev); mutex_unlock(&dev->lock); + if ((!dev->users) && (dev->dev_next != NULL)) { + kfree(dev->dev_next->alt_max_pkt_size_isoc); + kfree(dev->dev_next); + dev->dev_next = NULL; + } + if (!dev->users) { kfree(dev->alt_max_pkt_size_isoc); kfree(dev);
diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c index a802128..e9efc92 100644 --- a/drivers/media/usb/em28xx/em28xx-core.c +++ b/drivers/media/usb/em28xx/em28xx-core.c
@@ -249,6 +249,24 @@ EXPORT_SYMBOL_GPL(em28xx_write_reg_bits); /* + * em28xx_toggle_reg_bits() + * toggles/inverts the bits (specified by bitmask) of a register + */ +static int em28xx_toggle_reg_bits(struct em28xx *dev, u16 reg, u8 bitmask) +{ + int oldval; + u8 newval; + + oldval = em28xx_read_reg(dev, reg); + if (oldval < 0) + return oldval; + + newval = (~oldval & bitmask) | (oldval & ~bitmask); + + return em28xx_write_reg(dev, reg, newval); +} + +/* * em28xx_is_ac97_ready() * Checks if ac97 is ready */ @@ -631,18 +649,32 @@ dev->chip_id == CHIP_ID_EM2884 || dev->chip_id == CHIP_ID_EM28174) { /* The Transport Stream Enable Register moved in em2874 */ - if (!start) { + if (dev->board.has_dual_ts) { + if (start) { + rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE, + (EM2874_TS1_CAPTURE_ENABLE | EM2874_TS2_CAPTURE_ENABLE), + (EM2874_TS1_CAPTURE_ENABLE | EM2874_TS2_CAPTURE_ENABLE)); + } else { + if (dev->ts == PRIMARY_TS) + rc = em28xx_toggle_reg_bits(dev, EM2874_R5F_TS_ENABLE, EM2874_TS1_CAPTURE_ENABLE); + else + rc = em28xx_toggle_reg_bits(dev, EM2874_R5F_TS_ENABLE, EM2874_TS2_CAPTURE_ENABLE); + } + return rc; + } else { + if (!start) { + rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE, + 0x00, + EM2874_TS1_CAPTURE_ENABLE); + return rc; + } + + /* Enable Transport Stream */ rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE, - 0x00, + EM2874_TS1_CAPTURE_ENABLE, EM2874_TS1_CAPTURE_ENABLE); return rc; } - - /* Enable Transport Stream */ - rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE, - EM2874_TS1_CAPTURE_ENABLE, - EM2874_TS1_CAPTURE_ENABLE); - return rc; } @@ -1293,6 +1325,8 @@ list_add_tail(&ops->next, &em28xx_extension_devlist); list_for_each_entry(dev, &em28xx_devlist, devlist) { ops->init(dev); + if (dev->dev_next != NULL) + ops->init(dev->dev_next); } mutex_unlock(&em28xx_devlist_mutex); printk(KERN_INFO "Em28xx: Initialized (%s) extension\n", ops->name); @@ -1307,6 +1341,8 @@ mutex_lock(&em28xx_devlist_mutex); list_for_each_entry(dev, &em28xx_devlist, devlist) { ops->fini(dev); + if (dev->dev_next != NULL) + ops->fini(dev->dev_next); } list_del(&ops->next); mutex_unlock(&em28xx_devlist_mutex); @@ -1323,6 +1359,8 @@ list_for_each_entry(ops, &em28xx_extension_devlist, next) { if (ops->init) ops->init(dev); + if (dev->dev_next != NULL) + ops->init(dev->dev_next); } mutex_unlock(&em28xx_devlist_mutex); } @@ -1335,6 +1373,8 @@ list_for_each_entry(ops, &em28xx_extension_devlist, next) { if (ops->fini) ops->fini(dev); + if (dev->dev_next != NULL) + ops->fini(dev->dev_next); } list_del(&dev->devlist); mutex_unlock(&em28xx_devlist_mutex);
diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c index b22f8fe..c902f9d 100644 --- a/drivers/media/usb/em28xx/em28xx-dvb.c +++ b/drivers/media/usb/em28xx/em28xx-dvb.c
@@ -36,6 +36,7 @@ #include "lgdt330x.h" #include "lgdt3305.h" +#include "lgdt3306a.h" #include "zl10353.h" #include "s5h1409.h" #include "mt352.h" @@ -51,6 +52,8 @@ #include "a8293.h" #include "qt1010.h" #include "mb86a20s.h" +#include "si2168.h" +#include "si2157.h" MODULE_DESCRIPTION("driver for em28xx based DVB cards"); MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>"); @@ -87,6 +90,9 @@ struct semaphore pll_mutex; bool dont_attach_fe1; int lna_gpio; + struct i2c_client *i2c_client_demod; + struct i2c_client *i2c_client_tuner; + struct i2c_client *i2c_client_sec; }; @@ -198,7 +204,8 @@ dvb_alt = dev->dvb_alt_isoc; } - usb_set_interface(dev->udev, 0, dvb_alt); + /* moved to em28xx_dvb_init*/ + /* usb_set_interface(dev->udev, 0, dvb_alt);*/ rc = em28xx_set_mode(dev, EM28XX_DIGITAL_MODE); if (rc < 0) return rc; @@ -956,7 +963,7 @@ static int em28xx_dvb_init(struct em28xx *dev) { - int result = 0, mfe_shared = 0; + int result = 0, mfe_shared = 0, dvb_alt = 0; struct em28xx_dvb *dvb; if (!dev->board.has_dvb) { @@ -974,6 +981,26 @@ dev->dvb = dvb; dvb->fe[0] = dvb->fe[1] = NULL; + /* pre-allocate DVB usb transfer buffers */ + if (dev->dvb_xfer_bulk) { + result = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE, + dev->dvb_xfer_bulk, + EM28XX_DVB_NUM_BUFS, + 512, + EM28XX_DVB_BULK_PACKET_MULTIPLIER); + } else { + result = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE, + dev->dvb_xfer_bulk, + EM28XX_DVB_NUM_BUFS, + dev->dvb_max_pkt_size_isoc, + EM28XX_DVB_NUM_ISOC_PACKETS); + } + if (result) { + printk(KERN_INFO "em28xx_dvb: Failed to pre-allocate USB transfer buffers for DVB.\n"); + kfree(dvb); + dev->dvb = NULL; + return result; + } mutex_lock(&dev->lock); em28xx_set_mode(dev, EM28XX_DIGITAL_MODE); /* init frontend */ @@ -1296,6 +1323,153 @@ goto out_free; } break; + case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB: + { + struct i2c_adapter *adapter; + struct i2c_client *client; + struct i2c_board_info info; + struct si2168_config si2168_config; + struct si2157_config si2157_config; + + /* attach demod */ + memset(&si2168_config, 0, sizeof(si2168_config)); + si2168_config.i2c_adapter = &adapter; + si2168_config.fe = &dvb->fe[0]; + si2168_config.ts_mode = SI2168_TS_SERIAL; + memset(&info, 0, sizeof(info)); + strlcpy(info.type, "si2168", I2C_NAME_SIZE); + if (dev->ts == PRIMARY_TS) + info.addr = 0x64; + else + info.addr = 0x67; + info.platform_data = &si2168_config; + request_module(info.type); + client = i2c_new_device(&dev->i2c_adap[dev->def_i2c_bus], &info); + if (client == NULL || client->dev.driver == NULL) { + result = -ENODEV; + goto out_free; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + result = -ENODEV; + goto out_free; + } + + dvb->i2c_client_demod = client; + em28xx_info("demod si2168 successfully loaded\n"); + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = dvb->fe[0]; + si2157_config.if_port = 1; + memset(&info, 0, sizeof(info)); + strlcpy(info.type, "si2157", I2C_NAME_SIZE); + if (dev->ts == PRIMARY_TS) + info.addr = 0x60; + else + info.addr = 0x63; + info.platform_data = &si2157_config; + request_module(info.type); + client = i2c_new_device(adapter, &info); + if (client == NULL || client->dev.driver == NULL) { + module_put(dvb->i2c_client_demod->dev.driver->owner); + i2c_unregister_device(dvb->i2c_client_demod); + result = -ENODEV; + goto out_free; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + module_put(dvb->i2c_client_demod->dev.driver->owner); + i2c_unregister_device(dvb->i2c_client_demod); + result = -ENODEV; + goto out_free; + } + + dvb->i2c_client_tuner = client; + } + break; + case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC: + { + struct i2c_adapter *adapter; + struct i2c_client *client; + struct i2c_board_info info; + struct lgdt3306a_config lgdt3306a_config; + struct si2157_config si2157_config; + + /* attach demod */ + memset(&lgdt3306a_config, 0, sizeof(lgdt3306a_config)); + lgdt3306a_config.i2c_adapter = &adapter; + lgdt3306a_config.fe = &dvb->fe[0]; + if (dev->ts == PRIMARY_TS) + lgdt3306a_config.i2c_addr = 0x59; + else + lgdt3306a_config.i2c_addr = 0x0e; + lgdt3306a_config.qam_if_khz = 4000; + lgdt3306a_config.vsb_if_khz = 3250; + lgdt3306a_config.deny_i2c_rptr = 1; + lgdt3306a_config.spectral_inversion = 1; + lgdt3306a_config.mpeg_mode = LGDT3306A_MPEG_SERIAL; + lgdt3306a_config.tpclk_edge = LGDT3306A_TPCLK_RISING_EDGE; + lgdt3306a_config.tpvalid_polarity = LGDT3306A_TP_VALID_HIGH; + lgdt3306a_config.xtalMHz = 25; + lgdt3306a_config.has_tuner_i2c_adapter = 1; + memset(&info, 0, sizeof(info)); + strlcpy(info.type, "lgdt3306a", I2C_NAME_SIZE); + if (dev->ts == PRIMARY_TS) + info.addr = 0x59; + else + info.addr = 0x0e; + info.platform_data = &lgdt3306a_config; + request_module(info.type); + client = i2c_new_device(&dev->i2c_adap[dev->def_i2c_bus], &info); + if (client == NULL || client->dev.driver == NULL) { + result = -ENODEV; + goto out_free; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + result = -ENODEV; + goto out_free; + } + + dvb->fe[0]->ops.i2c_gate_ctrl = NULL; + dvb->i2c_client_demod = client; + + /* attach tuner */ + memset(&si2157_config, 0, sizeof(si2157_config)); + si2157_config.fe = dvb->fe[0]; + si2157_config.if_port = 1; + si2157_config.inversion = true; + memset(&info, 0, sizeof(info)); + strlcpy(info.type, "si2157", I2C_NAME_SIZE); + if (dev->ts == PRIMARY_TS) + info.addr = 0x60; + else + info.addr = 0x62; + info.platform_data = &si2157_config; + request_module(info.type); + client = i2c_new_device(adapter, &info); + if (client == NULL || client->dev.driver == NULL) { + module_put(dvb->i2c_client_demod->dev.driver->owner); + i2c_unregister_device(dvb->i2c_client_demod); + result = -ENODEV; + goto out_free; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + module_put(dvb->i2c_client_demod->dev.driver->owner); + i2c_unregister_device(dvb->i2c_client_demod); + result = -ENODEV; + goto out_free; + } + + dvb->i2c_client_tuner = client; + } + break; default: em28xx_errdev("/2: The frontend of your DVB/ATSC card" " isn't supported yet\n"); @@ -1320,6 +1494,12 @@ /* MFE lock */ dvb->adapter.mfe_shared = mfe_shared; + if (dev->dvb_xfer_bulk) { + dvb_alt = 0; + } else { /* isoc */ + dvb_alt = dev->dvb_alt_isoc; + } + usb_set_interface(dev->udev, dev->ifnum, dvb_alt); em28xx_info("Successfully loaded em28xx-dvb\n"); ret: em28xx_set_mode(dev, EM28XX_SUSPEND); @@ -1341,6 +1521,8 @@ static int em28xx_dvb_fini(struct em28xx *dev) { + struct i2c_client *client; + if (!dev->board.has_dvb) { /* This device does not support the extension */ return 0; @@ -1358,6 +1540,27 @@ prevent_sleep(&dvb->fe[1]->ops); } + /* remove I2C SEC */ + client = dvb->i2c_client_sec; + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + + /* remove I2C tuner */ + client = dvb->i2c_client_tuner; + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + + /* remove I2C demod */ + client = dvb->i2c_client_demod; + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + em28xx_unregister_dvb(dvb); kfree(dvb); dev->dvb = NULL;
diff --git a/drivers/media/usb/em28xx/em28xx-reg.h b/drivers/media/usb/em28xx/em28xx-reg.h index 622871d..18e715d 100644 --- a/drivers/media/usb/em28xx/em28xx-reg.h +++ b/drivers/media/usb/em28xx/em28xx-reg.h
@@ -192,6 +192,18 @@ /* em2874 registers */ #define EM2874_R50_IR_CONFIG 0x50 #define EM2874_R51_IR 0x51 +#define EM2874_R5D_TS1_PKT_SIZE 0x5d +#define EM2874_R5E_TS2_PKT_SIZE 0x5e + /* + * For both TS1 and TS2, In isochronous mode: + * 0x01 188 bytes + * 0x02 376 bytes + * 0x03 564 bytes + * 0x04 752 bytes + * 0x05 940 bytes + * In bulk mode: + * 0x01..0xff total packet count in 188-byte + */ #define EM2874_R5F_TS_ENABLE 0x5f #define EM2874_R80_GPIO 0x80
diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h index a9323b6..e488113 100644 --- a/drivers/media/usb/em28xx/em28xx.h +++ b/drivers/media/usb/em28xx/em28xx.h
@@ -130,6 +130,8 @@ #define EM2884_BOARD_PCTV_520E 86 #define EM2884_BOARD_TERRATEC_HTC_USB_XS 87 #define EM2884_BOARD_C3TECH_DIGITAL_DUO 88 +#define EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB 89 +#define EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC 90 /* Limits minimum and default number of buffers */ #define EM28XX_MIN_BUF 4 @@ -179,6 +181,10 @@ /* time in msecs to wait for i2c writes to finish */ #define EM2800_I2C_XFER_TIMEOUT 20 +#define PRIMARY_TS 0 + +#define SECONDARY_TS 1 + enum em28xx_mode { EM28XX_SUSPEND, EM28XX_ANALOG_MODE, @@ -393,6 +399,7 @@ unsigned int mts_firmware:1; unsigned int max_range_640_480:1; unsigned int has_dvb:1; + unsigned int has_dual_ts:1; unsigned int has_snapshot_button:1; unsigned int is_webcam:1; unsigned int valid:1; @@ -506,6 +513,7 @@ unsigned int has_audio_class:1; unsigned int has_alsa_audio:1; + unsigned int has_video:1; unsigned int is_audio_only:1; /* Controls audio streaming */ @@ -605,10 +613,13 @@ /* usb transfer */ struct usb_device *udev; /* the usb device */ + u8 ifnum; /* number of the assigned usb interface */ u8 analog_ep_isoc; /* address of isoc endpoint for analog */ u8 analog_ep_bulk; /* address of bulk endpoint for analog */ u8 dvb_ep_isoc; /* address of isoc endpoint for DVB */ u8 dvb_ep_bulk; /* address of bulk endpoint for DVB */ + u8 dvb_ep_isoc_ts2; /* address of isoc endpoint for DVB TS2*/ + u8 dvb_ep_bulk_ts2; /* address of bulk endpoint for DVB TS2*/ int alt; /* alternate setting */ int max_pkt_size; /* max packet size of the selected ep at alt */ int packet_multiplier; /* multiplier for wMaxPacketSize, used for @@ -620,6 +631,8 @@ int dvb_alt_isoc; /* alternate setting for DVB isoc transfers */ unsigned int dvb_max_pkt_size_isoc; /* isoc max packet size of the selected DVB ep at dvb_alt */ + unsigned int dvb_max_pkt_size_isoc_ts2; /* isoc max packet size of the + selected DVB ep at dvb_alt */ unsigned int dvb_xfer_bulk:1; /* use bulk instead of isoc transfers for DVB */ char urb_buf[URB_MAX_CTRL_SIZE]; /* urb control msg buffer */ @@ -648,6 +661,8 @@ struct delayed_work sbutton_query_work; struct em28xx_dvb *dvb; + struct em28xx *dev_next; + int ts; }; struct em28xx_ops {