2019-05-27 14:55:06 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2014-04-15 08:55:12 +08:00
|
|
|
/*
|
|
|
|
* Silicon Labs Si2168 DVB-T/T2/C demodulator driver
|
|
|
|
*
|
|
|
|
* Copyright (C) 2014 Antti Palosaari <crope@iki.fi>
|
|
|
|
*/
|
|
|
|
|
2017-12-12 08:51:53 +08:00
|
|
|
#include <linux/delay.h>
|
|
|
|
|
2014-04-11 09:00:50 +08:00
|
|
|
#include "si2168_priv.h"
|
|
|
|
|
|
|
|
static const struct dvb_frontend_ops si2168_ops;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
static void cmd_init(struct si2168_cmd *cmd, const u8 *buf, int wlen, int rlen)
|
|
|
|
{
|
|
|
|
memcpy(cmd->args, buf, wlen);
|
|
|
|
cmd->wlen = wlen;
|
|
|
|
cmd->rlen = rlen;
|
|
|
|
}
|
|
|
|
|
2014-04-11 09:00:50 +08:00
|
|
|
/* execute firmware command */
|
2016-05-05 04:15:32 +08:00
|
|
|
static int si2168_cmd_execute(struct i2c_client *client, struct si2168_cmd *cmd)
|
2014-04-11 09:00:50 +08:00
|
|
|
{
|
2016-05-05 04:15:32 +08:00
|
|
|
struct si2168_dev *dev = i2c_get_clientdata(client);
|
2014-04-11 09:00:50 +08:00
|
|
|
int ret;
|
|
|
|
unsigned long timeout;
|
|
|
|
|
2016-05-05 04:15:32 +08:00
|
|
|
mutex_lock(&dev->i2c_mutex);
|
|
|
|
|
2014-04-11 09:00:50 +08:00
|
|
|
if (cmd->wlen) {
|
|
|
|
/* write cmd and args for firmware */
|
2016-05-05 04:15:32 +08:00
|
|
|
ret = i2c_master_send(client, cmd->args, cmd->wlen);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret < 0) {
|
2016-05-05 04:15:32 +08:00
|
|
|
goto err_mutex_unlock;
|
2014-04-11 09:00:50 +08:00
|
|
|
} else if (ret != cmd->wlen) {
|
|
|
|
ret = -EREMOTEIO;
|
2016-05-05 04:15:32 +08:00
|
|
|
goto err_mutex_unlock;
|
2014-04-11 09:00:50 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmd->rlen) {
|
|
|
|
/* wait cmd execution terminate */
|
2014-12-08 16:30:44 +08:00
|
|
|
#define TIMEOUT 70
|
2014-04-11 09:00:50 +08:00
|
|
|
timeout = jiffies + msecs_to_jiffies(TIMEOUT);
|
|
|
|
while (!time_after(jiffies, timeout)) {
|
2016-05-05 04:15:32 +08:00
|
|
|
ret = i2c_master_recv(client, cmd->args, cmd->rlen);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret < 0) {
|
2016-05-05 04:15:32 +08:00
|
|
|
goto err_mutex_unlock;
|
2014-04-11 09:00:50 +08:00
|
|
|
} else if (ret != cmd->rlen) {
|
|
|
|
ret = -EREMOTEIO;
|
2016-05-05 04:15:32 +08:00
|
|
|
goto err_mutex_unlock;
|
2014-04-11 09:00:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* firmware ready? */
|
|
|
|
if ((cmd->args[0] >> 7) & 0x01)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_dbg(&client->dev, "cmd execution took %d ms\n",
|
2014-04-11 09:00:50 +08:00
|
|
|
jiffies_to_msecs(jiffies) -
|
|
|
|
(jiffies_to_msecs(timeout) - TIMEOUT));
|
|
|
|
|
2015-05-06 00:54:16 +08:00
|
|
|
/* error bit set? */
|
|
|
|
if ((cmd->args[0] >> 6) & 0x01) {
|
|
|
|
ret = -EREMOTEIO;
|
2016-05-05 04:15:32 +08:00
|
|
|
goto err_mutex_unlock;
|
2015-05-06 00:54:16 +08:00
|
|
|
}
|
|
|
|
|
2014-06-13 22:08:25 +08:00
|
|
|
if (!((cmd->args[0] >> 7) & 0x01)) {
|
2014-04-11 09:00:50 +08:00
|
|
|
ret = -ETIMEDOUT;
|
2016-05-05 04:15:32 +08:00
|
|
|
goto err_mutex_unlock;
|
2014-04-11 09:00:50 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-05 04:15:32 +08:00
|
|
|
mutex_unlock(&dev->i2c_mutex);
|
2014-12-06 01:30:44 +08:00
|
|
|
return 0;
|
2016-05-05 04:15:32 +08:00
|
|
|
err_mutex_unlock:
|
|
|
|
mutex_unlock(&dev->i2c_mutex);
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_dbg(&client->dev, "failed=%d\n", ret);
|
2014-04-11 09:00:50 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-02-13 04:19:04 +08:00
|
|
|
static int si2168_ts_bus_ctrl(struct dvb_frontend *fe, int acquire)
|
|
|
|
{
|
|
|
|
struct i2c_client *client = fe->demodulator_priv;
|
|
|
|
struct si2168_dev *dev = i2c_get_clientdata(client);
|
|
|
|
struct si2168_cmd cmd;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
dev_dbg(&client->dev, "%s acquire: %d\n", __func__, acquire);
|
|
|
|
|
2019-07-18 08:36:54 +08:00
|
|
|
/* set manual value */
|
|
|
|
if (dev->ts_mode & SI2168_TS_CLK_MANUAL) {
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x0d\x10\xe8\x03", 6, 4);
|
2019-07-18 08:36:54 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
2018-02-13 04:19:04 +08:00
|
|
|
/* set TS_MODE property */
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x01\x10\x10\x00", 6, 4);
|
2019-07-18 08:36:54 +08:00
|
|
|
if (dev->ts_mode & SI2168_TS_CLK_MANUAL)
|
|
|
|
cmd.args[4] = SI2168_TS_CLK_MANUAL;
|
2018-02-13 04:19:04 +08:00
|
|
|
if (acquire)
|
|
|
|
cmd.args[4] |= dev->ts_mode;
|
|
|
|
else
|
|
|
|
cmd.args[4] |= SI2168_TS_TRISTATE;
|
|
|
|
if (dev->ts_clock_gapped)
|
|
|
|
cmd.args[4] |= 0x40;
|
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-06-08 01:53:52 +08:00
|
|
|
static int si2168_read_status(struct dvb_frontend *fe, enum fe_status *status)
|
2014-04-11 09:00:50 +08:00
|
|
|
{
|
2014-11-26 04:31:43 +08:00
|
|
|
struct i2c_client *client = fe->demodulator_priv;
|
|
|
|
struct si2168_dev *dev = i2c_get_clientdata(client);
|
2014-04-12 07:35:27 +08:00
|
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
2016-12-09 08:19:19 +08:00
|
|
|
int ret, i;
|
|
|
|
unsigned int utmp, utmp1, utmp2;
|
2014-04-11 09:00:50 +08:00
|
|
|
struct si2168_cmd cmd;
|
|
|
|
|
|
|
|
*status = 0;
|
|
|
|
|
2014-11-26 03:46:16 +08:00
|
|
|
if (!dev->active) {
|
2014-04-11 09:00:50 +08:00
|
|
|
ret = -EAGAIN;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2014-04-12 07:35:27 +08:00
|
|
|
switch (c->delivery_system) {
|
|
|
|
case SYS_DVBT:
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\xa0\x01", 2, 13);
|
2014-04-12 07:35:27 +08:00
|
|
|
break;
|
2014-04-12 12:58:32 +08:00
|
|
|
case SYS_DVBC_ANNEX_A:
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x90\x01", 2, 9);
|
2014-04-12 12:58:32 +08:00
|
|
|
break;
|
2014-04-12 07:35:27 +08:00
|
|
|
case SYS_DVBT2:
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x50\x01", 2, 14);
|
2014-04-12 07:35:27 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2014-04-23 08:36:32 +08:00
|
|
|
switch ((cmd.args[2] >> 1) & 0x03) {
|
|
|
|
case 0x01:
|
2014-04-11 09:00:50 +08:00
|
|
|
*status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
|
|
|
|
break;
|
2014-04-23 08:36:32 +08:00
|
|
|
case 0x03:
|
2014-04-11 09:00:50 +08:00
|
|
|
*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI |
|
|
|
|
FE_HAS_SYNC | FE_HAS_LOCK;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-11-26 03:46:16 +08:00
|
|
|
dev->fe_status = *status;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-07-10 07:22:27 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_dbg(&client->dev, "status=%02x args=%*ph\n",
|
2014-08-05 19:54:08 +08:00
|
|
|
*status, cmd.rlen, cmd.args);
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2016-12-09 08:19:19 +08:00
|
|
|
/* BER */
|
|
|
|
if (*status & FE_HAS_VITERBI) {
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x82\x00", 2, 3);
|
2016-12-09 08:19:19 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Firmware returns [0, 255] mantissa and [0, 8] exponent.
|
|
|
|
* Convert to DVB API: mantissa * 10^(8 - exponent) / 10^8
|
|
|
|
*/
|
|
|
|
utmp = clamp(8 - cmd.args[1], 0, 8);
|
|
|
|
for (i = 0, utmp1 = 1; i < utmp; i++)
|
|
|
|
utmp1 = utmp1 * 10;
|
|
|
|
|
|
|
|
utmp1 = cmd.args[2] * utmp1;
|
|
|
|
utmp2 = 100000000; /* 10^8 */
|
|
|
|
|
|
|
|
dev_dbg(&client->dev,
|
|
|
|
"post_bit_error=%u post_bit_count=%u ber=%u*10^-%u\n",
|
|
|
|
utmp1, utmp2, cmd.args[2], cmd.args[1]);
|
|
|
|
|
|
|
|
c->post_bit_error.stat[0].scale = FE_SCALE_COUNTER;
|
|
|
|
c->post_bit_error.stat[0].uvalue += utmp1;
|
|
|
|
c->post_bit_count.stat[0].scale = FE_SCALE_COUNTER;
|
|
|
|
c->post_bit_count.stat[0].uvalue += utmp2;
|
|
|
|
} else {
|
|
|
|
c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
|
|
c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
|
|
}
|
|
|
|
|
2016-12-09 21:11:32 +08:00
|
|
|
/* UCB */
|
|
|
|
if (*status & FE_HAS_SYNC) {
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x84\x01", 2, 3);
|
2016-12-09 21:11:32 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
utmp1 = cmd.args[2] << 8 | cmd.args[1] << 0;
|
|
|
|
dev_dbg(&client->dev, "block_error=%u\n", utmp1);
|
|
|
|
|
|
|
|
/* Sometimes firmware returns bogus value */
|
|
|
|
if (utmp1 == 0xffff)
|
|
|
|
utmp1 = 0;
|
|
|
|
|
|
|
|
c->block_error.stat[0].scale = FE_SCALE_COUNTER;
|
|
|
|
c->block_error.stat[0].uvalue += utmp1;
|
|
|
|
} else {
|
|
|
|
c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
|
|
|
}
|
|
|
|
|
2014-04-11 09:00:50 +08:00
|
|
|
return 0;
|
|
|
|
err:
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_dbg(&client->dev, "failed=%d\n", ret);
|
2014-04-11 09:00:50 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int si2168_set_frontend(struct dvb_frontend *fe)
|
|
|
|
{
|
2014-11-26 04:31:43 +08:00
|
|
|
struct i2c_client *client = fe->demodulator_priv;
|
|
|
|
struct si2168_dev *dev = i2c_get_clientdata(client);
|
2014-04-11 09:00:50 +08:00
|
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
|
|
|
int ret;
|
|
|
|
struct si2168_cmd cmd;
|
2014-04-12 07:35:27 +08:00
|
|
|
u8 bandwidth, delivery_system;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_dbg(&client->dev,
|
2014-12-06 02:57:03 +08:00
|
|
|
"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);
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-11-26 03:46:16 +08:00
|
|
|
if (!dev->active) {
|
2014-04-11 09:00:50 +08:00
|
|
|
ret = -EAGAIN;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2014-04-12 07:35:27 +08:00
|
|
|
switch (c->delivery_system) {
|
|
|
|
case SYS_DVBT:
|
|
|
|
delivery_system = 0x20;
|
|
|
|
break;
|
2014-04-12 12:58:32 +08:00
|
|
|
case SYS_DVBC_ANNEX_A:
|
|
|
|
delivery_system = 0x30;
|
|
|
|
break;
|
2014-04-12 07:35:27 +08:00
|
|
|
case SYS_DVBT2:
|
|
|
|
delivery_system = 0x70;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2015-01-16 20:35:19 +08:00
|
|
|
if (c->bandwidth_hz == 0) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err;
|
2015-01-16 20:35:20 +08:00
|
|
|
} else if (c->bandwidth_hz <= 2000000)
|
|
|
|
bandwidth = 0x02;
|
|
|
|
else if (c->bandwidth_hz <= 5000000)
|
2014-04-12 07:35:27 +08:00
|
|
|
bandwidth = 0x05;
|
2014-04-12 12:58:32 +08:00
|
|
|
else if (c->bandwidth_hz <= 6000000)
|
2014-04-12 07:35:27 +08:00
|
|
|
bandwidth = 0x06;
|
2014-04-12 12:58:32 +08:00
|
|
|
else if (c->bandwidth_hz <= 7000000)
|
2014-04-12 07:35:27 +08:00
|
|
|
bandwidth = 0x07;
|
2014-04-12 12:58:32 +08:00
|
|
|
else if (c->bandwidth_hz <= 8000000)
|
2014-04-12 07:35:27 +08:00
|
|
|
bandwidth = 0x08;
|
2014-04-12 12:58:32 +08:00
|
|
|
else if (c->bandwidth_hz <= 9000000)
|
|
|
|
bandwidth = 0x09;
|
|
|
|
else if (c->bandwidth_hz <= 10000000)
|
|
|
|
bandwidth = 0x0a;
|
|
|
|
else
|
|
|
|
bandwidth = 0x0f;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
|
|
|
/* program tuner */
|
|
|
|
if (fe->ops.tuner_ops.set_params) {
|
|
|
|
ret = fe->ops.tuner_ops.set_params(fe);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x88\x02\x02\x02\x02", 5, 5);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2014-04-12 07:35:27 +08:00
|
|
|
/* that has no big effect */
|
|
|
|
if (c->delivery_system == SYS_DVBT)
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x89\x21\x06\x11\xff\x98", 6, 3);
|
2014-04-12 12:58:32 +08:00
|
|
|
else if (c->delivery_system == SYS_DVBC_ANNEX_A)
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x89\x21\x06\x11\x89\xf0", 6, 3);
|
2014-04-12 07:35:27 +08:00
|
|
|
else if (c->delivery_system == SYS_DVBT2)
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x89\x21\x06\x11\x89\x20", 6, 3);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2014-08-17 05:33:14 +08:00
|
|
|
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;
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-08-17 05:33:14 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x51\x03", 2, 12);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x12\x08\x04", 3, 3);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x0c\x10\x12\x00", 6, 4);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x06\x10\x24\x00", 6, 4);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x07\x10\x00\x24", 6, 4);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x0a\x10\x00\x00", 6, 4);
|
2014-04-12 07:35:27 +08:00
|
|
|
cmd.args[4] = delivery_system | bandwidth;
|
2018-01-18 06:31:58 +08:00
|
|
|
if (dev->spectral_inversion)
|
|
|
|
cmd.args[5] |= 1;
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2014-07-18 01:31:28 +08:00
|
|
|
/* set DVB-C symbol rate */
|
|
|
|
if (c->delivery_system == SYS_DVBC_ANNEX_A) {
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x02\x11\x00\x00", 6, 4);
|
2014-12-06 01:54:14 +08:00
|
|
|
cmd.args[4] = ((c->symbol_rate / 1000) >> 0) & 0xff;
|
2014-07-18 01:31:28 +08:00
|
|
|
cmd.args[5] = ((c->symbol_rate / 1000) >> 8) & 0xff;
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-07-18 01:31:28 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x0f\x10\x10\x00", 6, 4);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x09\x10\xe3\x08", 6, 4);
|
2014-11-26 03:46:16 +08:00
|
|
|
cmd.args[5] |= dev->ts_clock_inv ? 0x00 : 0x10;
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x08\x10\xd7\x05", 6, 4);
|
2014-11-26 03:46:16 +08:00
|
|
|
cmd.args[5] |= dev->ts_clock_inv ? 0x00 : 0x10;
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x01\x12\x00\x00", 6, 4);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x14\x00\x01\x03\x0c\x00", 6, 4);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-07-18 02:43:27 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x85", 1, 1);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2014-11-26 03:46:16 +08:00
|
|
|
dev->delivery_system = c->delivery_system;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2018-02-13 04:19:04 +08:00
|
|
|
/* enable ts bus */
|
|
|
|
ret = si2168_ts_bus_ctrl(fe, 1);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2014-04-11 09:00:50 +08:00
|
|
|
return 0;
|
|
|
|
err:
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_dbg(&client->dev, "failed=%d\n", ret);
|
2014-04-11 09:00:50 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int si2168_init(struct dvb_frontend *fe)
|
|
|
|
{
|
2014-11-26 04:31:43 +08:00
|
|
|
struct i2c_client *client = fe->demodulator_priv;
|
|
|
|
struct si2168_dev *dev = i2c_get_clientdata(client);
|
2016-12-09 08:19:19 +08:00
|
|
|
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
|
2014-04-11 09:00:50 +08:00
|
|
|
int ret, len, remaining;
|
2014-12-06 04:22:42 +08:00
|
|
|
const struct firmware *fw;
|
2014-04-11 09:00:50 +08:00
|
|
|
struct si2168_cmd cmd;
|
|
|
|
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_dbg(&client->dev, "\n");
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-08-26 02:07:04 +08:00
|
|
|
/* initialize */
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\xc0\x12\x00\x0c\x00\x0d\x16\x00\x00\x00\x00\x00\x00",
|
|
|
|
13, 0);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2016-06-30 07:38:17 +08:00
|
|
|
if (dev->warm) {
|
2014-08-26 02:07:04 +08:00
|
|
|
/* resume */
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\xc0\x06\x08\x0f\x00\x20\x21\x01", 8, 1);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-08-26 02:07:04 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2017-12-12 08:51:53 +08:00
|
|
|
udelay(100);
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x85", 1, 1);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-08-26 02:07:04 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
goto warm;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* power up */
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\xc0\x06\x01\x0f\x00\x20\x20\x01", 8, 1);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
/* request the firmware, this will block and timeout */
|
2016-06-30 07:38:17 +08:00
|
|
|
ret = request_firmware(&fw, dev->firmware_name, &client->dev);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret) {
|
media: si2168: drop support for old firmware file name for si2168 B40
The si2168 B40 firmware file name has been changed in or before 2014.
During initialization, the new file name is preferred, but the old file
name is used as a fallback when request_firmware with the new file name
fails. Once reading the old file name has been attempted, only this name
will be used on further firmware loading attempts. During resume,
firmware reading with the new file name can (and likely will) fail even
when it actually exists. So this permanent switch to the fallback
firmware name happens even when not desired.
Any system using a recent kernel version can be expected to have the
firmware under the new name. The major distributions are either using
the dvb firmware collection from LibreELEC, which has the new firmware
file name, or do not package the firmware file but have documentation
pointing towards a manual installation of the firmware file under the
new name. If the firmware is available under the old name, it is
severely outdated. If the switch to the old file name is performed,
further firmware loading will either permanently fail (if it is not
available) or an outdated firmware version will be used.
Drop support for the fallback firmware file name and fail directly if
the firmware is not available under its current name. On following
attempts, the firmware read will then be retried with the correct
current name instead of the old name.
As reasoned above, there should be no negative effects of this change,
while simplifying code (the B40 variant will be handled identical
compared to the other variants of the si2168) and at the same time
fixing possible problems if firmware loading fails on resume.
Link: https://lore.kernel.org/linux-media/20210418161544.58858-1-kernel@tuxforce.de
Signed-off-by: Lukas Middendorf <kernel@tuxforce.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
2021-04-19 00:15:44 +08:00
|
|
|
dev_err(&client->dev,
|
|
|
|
"firmware file '%s' not found\n",
|
|
|
|
dev->firmware_name);
|
|
|
|
goto err_release_firmware;
|
2014-04-11 09:00:50 +08:00
|
|
|
}
|
|
|
|
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_info(&client->dev, "downloading firmware from file '%s'\n",
|
2016-06-30 07:38:17 +08:00
|
|
|
dev->firmware_name);
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-11-28 03:42:23 +08:00
|
|
|
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];
|
2015-09-30 08:10:09 +08:00
|
|
|
if (len > SI2168_ARGLEN) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, &fw->data[(fw->size - remaining) + 1],
|
|
|
|
len, 1);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-12-06 04:08:22 +08:00
|
|
|
if (ret)
|
|
|
|
break;
|
2014-11-28 03:42:23 +08:00
|
|
|
}
|
2014-12-06 04:08:22 +08:00
|
|
|
} else if (fw->size % 8 == 0) {
|
2014-11-28 03:42:23 +08:00
|
|
|
/* firmware is in the old format */
|
2014-12-06 04:08:22 +08:00
|
|
|
for (remaining = fw->size; remaining > 0; remaining -= 8) {
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, &fw->data[fw->size - remaining], 8, 1);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-12-06 04:08:22 +08:00
|
|
|
if (ret)
|
|
|
|
break;
|
2014-04-11 09:00:50 +08:00
|
|
|
}
|
2014-12-06 04:08:22 +08:00
|
|
|
} else {
|
|
|
|
/* bad or unknown firmware format */
|
|
|
|
ret = -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
dev_err(&client->dev, "firmware download failed %d\n", ret);
|
|
|
|
goto err_release_firmware;
|
2014-04-11 09:00:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
release_firmware(fw);
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x01\x01", 2, 1);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-04-11 09:00:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2014-11-28 03:42:22 +08:00
|
|
|
/* query firmware version */
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x11", 1, 10);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-11-28 03:42:22 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2016-06-30 07:38:17 +08:00
|
|
|
dev->version = (cmd.args[9] + '@') << 24 | (cmd.args[6] - '0') << 16 |
|
|
|
|
(cmd.args[7] - '0') << 8 | (cmd.args[8]) << 0;
|
|
|
|
dev_info(&client->dev, "firmware version: %c %d.%d.%d\n",
|
|
|
|
dev->version >> 24 & 0xff, dev->version >> 16 & 0xff,
|
|
|
|
dev->version >> 8 & 0xff, dev->version >> 0 & 0xff);
|
2014-11-28 03:42:22 +08:00
|
|
|
|
2018-02-13 04:19:04 +08:00
|
|
|
/* set ts mode */
|
2018-01-13 00:19:39 +08:00
|
|
|
ret = si2168_ts_bus_ctrl(fe, 1);
|
2014-08-12 03:58:10 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2016-06-30 07:38:17 +08:00
|
|
|
dev->warm = true;
|
2021-04-18 08:12:04 +08:00
|
|
|
dev->initialized = true;
|
2014-11-04 05:28:39 +08:00
|
|
|
warm:
|
2016-12-09 08:19:19 +08:00
|
|
|
/* Init stats here to indicate which stats are supported */
|
|
|
|
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;
|
2016-12-09 21:11:32 +08:00
|
|
|
c->block_error.len = 1;
|
|
|
|
c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
|
2016-12-09 08:19:19 +08:00
|
|
|
|
2014-11-26 03:46:16 +08:00
|
|
|
dev->active = true;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
|
|
|
return 0;
|
2014-12-06 01:54:14 +08:00
|
|
|
err_release_firmware:
|
2014-11-20 06:23:15 +08:00
|
|
|
release_firmware(fw);
|
|
|
|
err:
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_dbg(&client->dev, "failed=%d\n", ret);
|
2014-04-11 09:00:50 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-04-18 08:12:04 +08:00
|
|
|
static int si2168_resume(struct dvb_frontend *fe)
|
|
|
|
{
|
|
|
|
struct i2c_client *client = fe->demodulator_priv;
|
|
|
|
struct si2168_dev *dev = i2c_get_clientdata(client);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* check whether si2168_init() has been called successfully
|
|
|
|
* outside of a resume cycle. Only call it (and load firmware)
|
|
|
|
* in this case. si2168_init() is only called during resume
|
|
|
|
* once the device has actually been used. Otherwise, leave the
|
|
|
|
* device untouched.
|
|
|
|
*/
|
|
|
|
if (dev->initialized) {
|
2021-11-23 17:06:45 +08:00
|
|
|
dev_dbg(&client->dev, "previously initialized, call si2168_init()\n");
|
2021-04-18 08:12:04 +08:00
|
|
|
return si2168_init(fe);
|
|
|
|
}
|
|
|
|
dev_dbg(&client->dev, "not initialized yet, skipping init on resume\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-04-11 09:00:50 +08:00
|
|
|
static int si2168_sleep(struct dvb_frontend *fe)
|
|
|
|
{
|
2014-11-26 04:31:43 +08:00
|
|
|
struct i2c_client *client = fe->demodulator_priv;
|
|
|
|
struct si2168_dev *dev = i2c_get_clientdata(client);
|
2014-07-10 05:45:31 +08:00
|
|
|
int ret;
|
|
|
|
struct si2168_cmd cmd;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_dbg(&client->dev, "\n");
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-11-26 03:46:16 +08:00
|
|
|
dev->active = false;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2018-01-13 00:19:39 +08:00
|
|
|
/* tri-state data bus */
|
2018-02-13 04:19:04 +08:00
|
|
|
ret = si2168_ts_bus_ctrl(fe, 0);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
2018-01-13 00:19:39 +08:00
|
|
|
|
2018-03-07 21:57:05 +08:00
|
|
|
/* Firmware later than B 4.0-11 loses warm state during sleep */
|
2016-06-30 07:38:17 +08:00
|
|
|
if (dev->version > ('B' << 24 | 4 << 16 | 0 << 8 | 11 << 0))
|
|
|
|
dev->warm = false;
|
|
|
|
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x13", 1, 0);
|
2014-11-26 04:31:43 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2014-07-10 05:45:31 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2014-04-11 09:00:50 +08:00
|
|
|
return 0;
|
2014-07-10 05:45:31 +08:00
|
|
|
err:
|
2014-11-26 04:53:21 +08:00
|
|
|
dev_dbg(&client->dev, "failed=%d\n", ret);
|
2014-07-10 05:45:31 +08:00
|
|
|
return ret;
|
2014-04-11 09:00:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int si2168_get_tune_settings(struct dvb_frontend *fe,
|
|
|
|
struct dvb_frontend_tune_settings *s)
|
|
|
|
{
|
|
|
|
s->min_delay_ms = 900;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-04-20 14:41:36 +08:00
|
|
|
static int si2168_select(struct i2c_mux_core *muxc, u32 chan)
|
2014-04-11 09:00:50 +08:00
|
|
|
{
|
2016-04-20 14:41:36 +08:00
|
|
|
struct i2c_client *client = i2c_mux_priv(muxc);
|
2014-04-11 09:00:50 +08:00
|
|
|
int ret;
|
2015-05-30 03:42:33 +08:00
|
|
|
struct si2168_cmd cmd;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2015-05-30 03:42:33 +08:00
|
|
|
/* open I2C gate */
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\xc0\x0d\x01", 3, 0);
|
2016-05-05 04:15:32 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2015-05-30 03:42:33 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2015-05-30 03:42:33 +08:00
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
dev_dbg(&client->dev, "failed=%d\n", ret);
|
2014-04-11 09:00:50 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-04-20 14:41:36 +08:00
|
|
|
static int si2168_deselect(struct i2c_mux_core *muxc, u32 chan)
|
2014-04-11 09:00:50 +08:00
|
|
|
{
|
2016-04-20 14:41:36 +08:00
|
|
|
struct i2c_client *client = i2c_mux_priv(muxc);
|
2014-04-11 09:00:50 +08:00
|
|
|
int ret;
|
2015-05-30 03:42:33 +08:00
|
|
|
struct si2168_cmd cmd;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2015-05-30 03:42:33 +08:00
|
|
|
/* close I2C gate */
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\xc0\x0d\x00", 3, 0);
|
2016-05-05 04:15:32 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
2015-05-30 03:42:33 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2015-05-30 03:42:33 +08:00
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
dev_dbg(&client->dev, "failed=%d\n", ret);
|
2014-04-11 09:00:50 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dvb_frontend_ops si2168_ops = {
|
2014-04-12 12:58:32 +08:00
|
|
|
.delsys = {SYS_DVBT, SYS_DVBT2, SYS_DVBC_ANNEX_A},
|
2014-04-11 09:00:50 +08:00
|
|
|
.info = {
|
|
|
|
.name = "Silicon Labs Si2168",
|
2018-12-21 02:54:44 +08:00
|
|
|
.frequency_min_hz = 48 * MHz,
|
|
|
|
.frequency_max_hz = 870 * MHz,
|
|
|
|
.frequency_stepsize_hz = 62500,
|
|
|
|
.symbol_rate_min = 1000000,
|
|
|
|
.symbol_rate_max = 7200000,
|
2014-04-11 09:00:50 +08:00
|
|
|
.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 |
|
2014-09-24 00:53:09 +08:00
|
|
|
FE_CAN_2G_MODULATION |
|
|
|
|
FE_CAN_MULTISTREAM
|
2014-04-11 09:00:50 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
.get_tune_settings = si2168_get_tune_settings,
|
|
|
|
|
|
|
|
.init = si2168_init,
|
|
|
|
.sleep = si2168_sleep,
|
2021-04-18 08:12:04 +08:00
|
|
|
.resume = si2168_resume,
|
2014-04-11 09:00:50 +08:00
|
|
|
|
|
|
|
.set_frontend = si2168_set_frontend,
|
|
|
|
|
|
|
|
.read_status = si2168_read_status,
|
|
|
|
};
|
|
|
|
|
2022-11-19 06:40:50 +08:00
|
|
|
static int si2168_probe(struct i2c_client *client)
|
2014-04-11 09:00:50 +08:00
|
|
|
{
|
|
|
|
struct si2168_config *config = client->dev.platform_data;
|
2014-11-26 03:46:16 +08:00
|
|
|
struct si2168_dev *dev;
|
2014-04-11 09:00:50 +08:00
|
|
|
int ret;
|
2016-06-30 07:38:17 +08:00
|
|
|
struct si2168_cmd cmd;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-08-05 19:54:08 +08:00
|
|
|
dev_dbg(&client->dev, "\n");
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-11-26 03:46:16 +08:00
|
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
|
|
|
if (!dev) {
|
2014-04-11 09:00:50 +08:00
|
|
|
ret = -ENOMEM;
|
2014-12-06 03:02:42 +08:00
|
|
|
goto err;
|
2014-04-11 09:00:50 +08:00
|
|
|
}
|
|
|
|
|
2016-06-30 07:38:17 +08:00
|
|
|
i2c_set_clientdata(client, dev);
|
2016-05-05 04:15:32 +08:00
|
|
|
mutex_init(&dev->i2c_mutex);
|
|
|
|
|
2016-06-30 07:38:17 +08:00
|
|
|
/* Initialize */
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\xc0\x12\x00\x0c\x00\x0d\x16\x00\x00\x00\x00\x00\x00",
|
|
|
|
13, 0);
|
2016-06-30 07:38:17 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
|
|
|
if (ret)
|
|
|
|
goto err_kfree;
|
|
|
|
|
|
|
|
/* Power up */
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\xc0\x06\x01\x0f\x00\x20\x20\x01", 8, 1);
|
2016-06-30 07:38:17 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
|
|
|
if (ret)
|
|
|
|
goto err_kfree;
|
|
|
|
|
|
|
|
/* Query chip revision */
|
2019-07-15 17:50:29 +08:00
|
|
|
cmd_init(&cmd, "\x02", 1, 13);
|
2016-06-30 07:38:17 +08:00
|
|
|
ret = si2168_cmd_execute(client, &cmd);
|
|
|
|
if (ret)
|
|
|
|
goto err_kfree;
|
|
|
|
|
|
|
|
dev->chip_id = cmd.args[1] << 24 | cmd.args[2] << 16 |
|
|
|
|
cmd.args[3] << 8 | cmd.args[4] << 0;
|
|
|
|
|
|
|
|
switch (dev->chip_id) {
|
|
|
|
case SI2168_CHIP_ID_A20:
|
|
|
|
dev->firmware_name = SI2168_A20_FIRMWARE;
|
|
|
|
break;
|
|
|
|
case SI2168_CHIP_ID_A30:
|
|
|
|
dev->firmware_name = SI2168_A30_FIRMWARE;
|
|
|
|
break;
|
|
|
|
case SI2168_CHIP_ID_B40:
|
|
|
|
dev->firmware_name = SI2168_B40_FIRMWARE;
|
|
|
|
break;
|
2017-02-08 05:33:47 +08:00
|
|
|
case SI2168_CHIP_ID_D60:
|
|
|
|
dev->firmware_name = SI2168_D60_FIRMWARE;
|
|
|
|
break;
|
2016-06-30 07:38:17 +08:00
|
|
|
default:
|
|
|
|
dev_dbg(&client->dev, "unknown chip version Si21%d-%c%c%c\n",
|
|
|
|
cmd.args[2], cmd.args[1], cmd.args[3], cmd.args[4]);
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto err_kfree;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev->version = (cmd.args[1]) << 24 | (cmd.args[3] - '0') << 16 |
|
|
|
|
(cmd.args[4] - '0') << 8 | (cmd.args[5]) << 0;
|
|
|
|
|
2014-04-11 09:00:50 +08:00
|
|
|
/* create mux i2c adapter for tuner */
|
2016-04-20 14:41:36 +08:00
|
|
|
dev->muxc = i2c_mux_alloc(client->adapter, &client->dev,
|
2016-05-05 04:15:32 +08:00
|
|
|
1, 0, I2C_MUX_LOCKED,
|
2016-04-20 14:41:36 +08:00
|
|
|
si2168_select, si2168_deselect);
|
|
|
|
if (!dev->muxc) {
|
|
|
|
ret = -ENOMEM;
|
2014-12-06 01:54:14 +08:00
|
|
|
goto err_kfree;
|
2014-07-18 03:38:08 +08:00
|
|
|
}
|
2016-04-20 14:41:36 +08:00
|
|
|
dev->muxc->priv = client;
|
|
|
|
ret = i2c_mux_add_adapter(dev->muxc, 0, 0, 0);
|
|
|
|
if (ret)
|
|
|
|
goto err_kfree;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
|
|
|
/* create dvb_frontend */
|
2014-11-26 03:46:16 +08:00
|
|
|
memcpy(&dev->fe.ops, &si2168_ops, sizeof(struct dvb_frontend_ops));
|
2014-11-26 04:31:43 +08:00
|
|
|
dev->fe.demodulator_priv = client;
|
2016-04-20 14:41:36 +08:00
|
|
|
*config->i2c_adapter = dev->muxc->adapter[0];
|
2014-11-26 03:46:16 +08:00
|
|
|
*config->fe = &dev->fe;
|
|
|
|
dev->ts_mode = config->ts_mode;
|
|
|
|
dev->ts_clock_inv = config->ts_clock_inv;
|
2015-05-06 00:54:14 +08:00
|
|
|
dev->ts_clock_gapped = config->ts_clock_gapped;
|
2018-01-18 06:31:58 +08:00
|
|
|
dev->spectral_inversion = config->spectral_inversion;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2016-06-30 07:38:17 +08:00
|
|
|
dev_info(&client->dev, "Silicon Labs Si2168-%c%d%d successfully identified\n",
|
|
|
|
dev->version >> 24 & 0xff, dev->version >> 16 & 0xff,
|
|
|
|
dev->version >> 8 & 0xff);
|
|
|
|
dev_info(&client->dev, "firmware version: %c %d.%d.%d\n",
|
|
|
|
dev->version >> 24 & 0xff, dev->version >> 16 & 0xff,
|
|
|
|
dev->version >> 8 & 0xff, dev->version >> 0 & 0xff);
|
2014-04-11 09:00:50 +08:00
|
|
|
|
|
|
|
return 0;
|
2014-12-06 01:54:14 +08:00
|
|
|
err_kfree:
|
2014-11-26 03:46:16 +08:00
|
|
|
kfree(dev);
|
2014-12-06 03:02:42 +08:00
|
|
|
err:
|
2018-01-13 00:19:40 +08:00
|
|
|
dev_warn(&client->dev, "probe failed = %d\n", ret);
|
2014-04-11 09:00:50 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-08-15 16:02:30 +08:00
|
|
|
static void si2168_remove(struct i2c_client *client)
|
2014-04-11 09:00:50 +08:00
|
|
|
{
|
2014-11-26 03:46:16 +08:00
|
|
|
struct si2168_dev *dev = i2c_get_clientdata(client);
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-08-05 19:54:08 +08:00
|
|
|
dev_dbg(&client->dev, "\n");
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2016-04-20 14:41:36 +08:00
|
|
|
i2c_mux_del_adapters(dev->muxc);
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-11-26 03:46:16 +08:00
|
|
|
dev->fe.ops.release = NULL;
|
|
|
|
dev->fe.demodulator_priv = NULL;
|
2014-04-11 09:00:50 +08:00
|
|
|
|
2014-11-26 03:46:16 +08:00
|
|
|
kfree(dev);
|
2014-04-11 09:00:50 +08:00
|
|
|
}
|
|
|
|
|
2014-12-06 01:54:14 +08:00
|
|
|
static const struct i2c_device_id si2168_id_table[] = {
|
2014-04-11 09:00:50 +08:00
|
|
|
{"si2168", 0},
|
|
|
|
{}
|
|
|
|
};
|
2014-12-06 01:54:14 +08:00
|
|
|
MODULE_DEVICE_TABLE(i2c, si2168_id_table);
|
2014-04-11 09:00:50 +08:00
|
|
|
|
|
|
|
static struct i2c_driver si2168_driver = {
|
|
|
|
.driver = {
|
2016-06-30 07:38:18 +08:00
|
|
|
.name = "si2168",
|
|
|
|
.suppress_bind_attrs = true,
|
2014-04-11 09:00:50 +08:00
|
|
|
},
|
2023-05-14 20:04:07 +08:00
|
|
|
.probe = si2168_probe,
|
2014-04-11 09:00:50 +08:00
|
|
|
.remove = si2168_remove,
|
2014-12-06 01:54:14 +08:00
|
|
|
.id_table = si2168_id_table,
|
2014-04-11 09:00:50 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
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");
|
2014-07-18 06:43:37 +08:00
|
|
|
MODULE_FIRMWARE(SI2168_A20_FIRMWARE);
|
2014-07-14 03:09:03 +08:00
|
|
|
MODULE_FIRMWARE(SI2168_A30_FIRMWARE);
|
2014-07-13 21:52:18 +08:00
|
|
|
MODULE_FIRMWARE(SI2168_B40_FIRMWARE);
|
2017-02-08 05:33:47 +08:00
|
|
|
MODULE_FIRMWARE(SI2168_D60_FIRMWARE);
|