// 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 const struct si2157_tuner_info si2157_tuners[] = {
{ SI2141, 0x60, false, SI2141_60_FIRMWARE, SI2141_A10_FIRMWARE },
{ SI2141, 0x61, false, SI2141_61_FIRMWARE, SI2141_A10_FIRMWARE },
{ SI2146, 0x11, false, SI2146_11_FIRMWARE, NULL },
{ SI2147, 0x50, false, SI2147_50_FIRMWARE, NULL },
{ SI2148, 0x32, true, SI2148_32_FIRMWARE, SI2158_A20_FIRMWARE },
{ SI2148, 0x33, true, SI2148_33_FIRMWARE, SI2158_A20_FIRMWARE },
{ SI2157, 0x50, false, SI2157_50_FIRMWARE, SI2157_A30_FIRMWARE },
{ SI2158, 0x50, false, SI2158_50_FIRMWARE, SI2158_A20_FIRMWARE },
{ SI2158, 0x51, false, SI2158_51_FIRMWARE, SI2158_A20_FIRMWARE },
{ SI2177, 0x50, false, SI2177_50_FIRMWARE, SI2157_A30_FIRMWARE },
};
static int si2157_load_firmware(struct dvb_frontend *fe,
const char *fw_name)
{
struct i2c_client *client = fe->tuner_priv;
const struct firmware *fw;
int ret, len, remaining;
struct si2157_cmd cmd;
/* request the firmware, this will block and timeout */
ret = firmware_request_nowarn(&fw, fw_name, &client->dev);
if (ret)
return ret;
/* firmware should be n chunks of 17 bytes */
if (fw->size % 17 != 0) {
dev_err(&client->dev, "firmware file '%s' is invalid\n",
fw_name);
ret = -EINVAL;
goto err_release_firmware;
}
dev_info(&client->dev, "downloading firmware from file '%s'\n",
fw_name);
for (remaining = fw->size; remaining > 0; remaining -= 17) {
len = fw->data[fw->size - remaining];
if (len > SI2157_ARGLEN) {
dev_err(&client->dev, "Bad firmware length\n");
ret = -EINVAL;
goto err_release_firmware;
}
memcpy(cmd.args, &fw->data[(fw->size - remaining) + 1], len);
cmd.wlen = len;
cmd.rlen = 1;
ret = si2157_cmd_execute(client, &cmd);
if (ret) {
dev_err(&client->dev, "firmware download failed %d\n",
ret);
goto err_release_firmware;
}
}
err_release_firmware:
release_firmware(fw);
return ret;
}
static int si2157_find_and_load_firmware(struct dvb_frontend *fe)
{
struct i2c_client *client = fe->tuner_priv;
struct si2157_dev *dev = i2c_get_clientdata(client);
const char *fw_alt_name = NULL;
unsigned char part_id, rom_id;
const char *fw_name = NULL;
struct si2157_cmd cmd;
bool required = true;
int ret, i;
if (dev->dont_load_firmware) {
dev_info(&client->dev,
"device is buggy, skipping firmware download\n");
return 0;
}
/* query chip revision */
memcpy(cmd.