// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Silicon Labs Si2146/2147/2148/2157/2158 silicon tuner driver
*
* Copyright (C) 2014 Antti Palosaari <crope@iki.fi>
*/
#include "si2157_priv.h"
static const struct dvb_tuner_ops si2157_ops;
static int tuner_lock_debug;
module_param(tuner_lock_debug, int, 0644);
MODULE_PARM_DESC(tuner_lock_debug, "if set, signal lock is briefly waited on after setting params");
/* execute firmware command */
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;
mutex_lock(&dev->i2c_mutex);
if (cmd->wlen) {
/* write cmd and args for firmware */
ret = i2c_master_send(client, cmd->args, cmd->wlen);
if (ret < 0) {
goto err_mutex_unlock;
} else if (ret != cmd->wlen) {
ret = -EREMOTEIO;
goto err_mutex_unlock;
}
}
if (cmd->rlen) {
/* wait cmd execution terminate */
#define TIMEOUT 80
timeout = jiffies + msecs_to_jiffies(TIMEOUT);
while (!time_after(jiffies, timeout)) {
ret = i2c_master_recv(client, cmd->args, cmd->rlen);
if (ret < 0) {
goto err_mutex_unlock;
} else if (ret != cmd->rlen) {
ret = -EREMOTEIO;
goto err_mutex_unlock;
}
/* firmware ready? */
if ((cmd->args[0] >> 7) & 0x01)
break;
}
dev_dbg(&client->dev, "cmd execution took %d ms, status=%x\n",
jiffies_to_msecs(jiffies) -
(jiffies_to_msecs(timeout) - TIMEOUT),
cmd->args[0]);
if (!((cmd->args[0] >> 7) & 0x01)) {
ret = -ETIMEDOUT;
goto err_mutex_unlock;
}
/* check error status bit */
if (cmd->args[0] & 0x40) {
ret = -EAGAIN;
goto err_mutex_unlock;
}
}
mutex_unlock(&dev->i2c_mutex);
return 0;
err_mutex_unlock:
mutex_unlock(&dev->i2c_mutex);
dev_dbg(&client->dev, "failed=%d\n", ret);
return ret;
}
static int si2157_init(struct dvb_frontend *fe)
{
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, xtal_trim;
dev_dbg(&client->dev, "\n");
/* Try to get Xtal trim property, to verify tuner still running */
memcpy(cmd.args, "\x15\x00\x04\x02", 4);
cmd.wlen = 4;
cmd.rlen = 4;
ret = si2157_cmd_execute(client, &cmd);
xtal_trim = cmd.args[2] | (cmd.args[3] << 8);
if (ret == 0 && xtal_trim < 16)
goto warm;
dev->if_frequency = 0; /* we no longer know current tuner state */
/* power up */
if (dev->chiptype == SI2157_CHIPTYPE_SI2146) {
memcpy(cmd.args, "\xc0\x05\x01\x00\x00\x0b\x00\x00\x01", 9);
cmd.wlen = 9;
} else if (dev->chiptype == SI2157_CHIPTYPE_SI2141) {
memcpy(cmd.args, "\xc0\x00\x0d\x0e\x00\x01\x01\x01\x01\x03", 10);
cmd.wlen = 10;
} else {
memcpy(cmd.args, "\xc0\x00\x0c\x00\x00\x01\x01\x01\x01\x01\x01\x02\x00\x00\x01", 15);
cmd.wlen = 15;
}
cmd.rlen = 1;
ret = si2157_cmd_execute(client, &cmd);
if (ret && (dev->chiptype != SI2157_CHIPTYPE_SI2141 || ret != -EAGAIN))
goto err;
/* Si2141 needs a second command before it answers the revision query */
if (dev->chiptype == SI2157_CHIPTYPE_SI2141) {
memcpy(cmd.args, "\xc0\x08\x01\x02\x00\x00\x01", 7);
cmd.wlen = 7;
ret = si2157_cmd_execute(client, &cmd);
if