// SPDX-License-Identifier: GPL-2.0-only
/*
* drivers/mfd/si476x-cmd.c -- Subroutines implementing command
* protocol of si476x series of chips
*
* Copyright (C) 2012 Innovative Converged Devices(ICD)
* Copyright (C) 2013 Andrey Smirnov
*
* Author: Andrey Smirnov <andrew.smirnov@gmail.com>
*/
#include <linux/module.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/i2c.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/videodev2.h>
#include <linux/mfd/si476x-core.h>
#include <asm/unaligned.h>
#define msb(x) ((u8)((u16) x >> 8))
#define lsb(x) ((u8)((u16) x & 0x00FF))
#define CMD_POWER_UP 0x01
#define CMD_POWER_UP_A10_NRESP 1
#define CMD_POWER_UP_A10_NARGS 5
#define CMD_POWER_UP_A20_NRESP 1
#define CMD_POWER_UP_A20_NARGS 5
#define POWER_UP_DELAY_MS 110
#define CMD_POWER_DOWN 0x11
#define CMD_POWER_DOWN_A10_NRESP 1
#define CMD_POWER_DOWN_A20_NRESP 1
#define CMD_POWER_DOWN_A20_NARGS 1
#define CMD_FUNC_INFO 0x12
#define CMD_FUNC_INFO_NRESP 7
#define CMD_SET_PROPERTY 0x13
#define CMD_SET_PROPERTY_NARGS 5
#define CMD_SET_PROPERTY_NRESP 1
#define CMD_GET_PROPERTY 0x14
#define CMD_GET_PROPERTY_NARGS 3
#define CMD_GET_PROPERTY_NRESP 4
#define CMD_AGC_STATUS 0x17
#define CMD_AGC_STATUS_NRESP_A10 2
#define CMD_AGC_STATUS_NRESP_A20 6
#define PIN_CFG_BYTE(x) (0x7F & (x))
#define CMD_DIG_AUDIO_PIN_CFG 0x18
#define CMD_DIG_AUDIO_PIN_CFG_NARGS 4
#define CMD_DIG_AUDIO_PIN_CFG_NRESP 5
#define CMD_ZIF_PIN_CFG 0x19
#define CMD_ZIF_PIN_CFG_NARGS 4
#define CMD_ZIF_PIN_CFG_NRESP 5
#define CMD_IC_LINK_GPO_CTL_PIN_CFG 0x1A
#define CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS 4
#define CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP 5
#define CMD_ANA_AUDIO_PIN_CFG 0x1B
#define CMD_ANA_AUDIO_PIN_CFG_NARGS 1
#define CMD_ANA_AUDIO_PIN_CFG_NRESP 2
#define CMD_INTB_PIN_CFG 0x1C
#define CMD_INTB_PIN_CFG_NARGS 2
#define CMD_INTB_PIN_CFG_A10_NRESP 6
#define CMD_INTB_PIN_CFG_A20_NRESP 3
#define CMD_FM_TUNE_FREQ 0x30
#define CMD_FM_TUNE_FREQ_A10_NARGS 5
#define CMD_FM_TUNE_FREQ_A20_NARGS 3
#define CMD_FM_TUNE_FREQ_NRESP 1
#define CMD_FM_RSQ_STATUS 0x32
#define CMD_FM_RSQ_STATUS_A10_NARGS 1
#define CMD_FM_RSQ_STATUS_A10_NRESP 17
#define CMD_FM_RSQ_STATUS_A30_NARGS 1
#define CMD_FM_RSQ_STATUS_A30_NRESP 23
#define CMD_FM_SEEK_START 0x31
#define CMD_FM_SEEK_START_NARGS 1
#define CMD_FM_SEEK_START_NRESP 1
#define CMD_FM_RDS_STATUS 0x36
#define CMD_FM_RDS_STATUS_NARGS 1
#define CMD_FM_RDS_STATUS_NRESP 16
#define CMD_FM_RDS_BLOCKCOUNT 0x37
#define CMD_FM_RDS_BLOCKCOUNT_NARGS 1
#define CMD_FM_RDS_BLOCKCOUNT_NRESP 8
#define CMD_FM_PHASE_DIVERSITY 0x38
#define CMD_FM_PHASE_DIVERSITY_NARGS 1
#define CMD_FM_PHASE_DIVERSITY_NRESP 1
#define CMD_FM_PHASE_DIV_STATUS 0x39
#define CMD_FM_PHASE_DIV_STATUS_NRESP 2
#define CMD_AM_TUNE_FREQ 0x40
#define CMD_AM_TUNE_FREQ_NARGS 3
#define CMD_AM_TUNE_FREQ_NRESP 1
#define CMD_AM_RSQ_STATUS 0x42
#define CMD_AM_RSQ_STATUS_NARGS 1
#define CMD_AM_RSQ_STATUS_NRESP 13
#define CMD_AM_SEEK_START 0x41
#define CMD_AM_SEEK_START_NARGS 1
#define CMD_AM_SEEK_START_NRESP 1
#define CMD_AM_ACF_STATUS 0x45
#define CMD_AM_ACF_STATUS_NRESP 6
#define CMD_AM_ACF_STATUS_NARGS 1
#define CMD_FM_ACF_STATUS 0x35
#define CMD_FM_ACF_STATUS_NRESP 8
#define CMD_FM_ACF_STATUS_NARGS 1
#define CMD_MAX_ARGS_COUNT (10)
enum si476x_acf_status_report_bits {
SI476X_ACF_BLEND_INT = (1 << 4),
SI476X_ACF_HIBLEND_INT = (1 << 3),
SI476X_ACF_HICUT_INT = (1 << 2),
SI476X_ACF_CHBW_INT = (1 << 1),
SI476X_ACF_SOFTMUTE_INT = (1 << 0),
SI476X_ACF_SMUTE = (1 << 0),
SI476X_ACF_SMATTN = 0x1f,
SI476X_ACF_PILOT = (1 << 7),
SI476X_ACF_STBLEND = ~SI476X_ACF_PILOT,
};
enum si476x_agc_status_report_bits {
SI476X_AGC_MXHI = (1 << 5),
SI476X_AGC_MXLO = (1 << 4),
SI476X_AGC_LNAHI = (1 << 3),
SI476X_AGC_LNALO = (1 << 2),
};
enum si476x_errors {
SI476X_ERR_BAD_COMMAND = 0x10,
SI476X_ERR_BAD_ARG1 = 0x11,
SI476X_ERR_BAD_ARG2 = 0x12,
SI476X_ERR_BAD_ARG3 = 0x13,
SI476X_ERR_BAD_ARG4 = 0x14,
SI476X_ERR_BUSY = 0x18,
SI476X_ERR_BAD_INTERNAL_MEMORY = 0x20,
SI476X_ERR_BAD_PATCH = 0x30,
SI476X_ERR_BAD_BOOT_MODE = 0x31,
SI476X_ERR_BAD_PROPERTY = 0x40,
};
static int si476x_core_parse_and_nag_about_error(struct si476x_core *core)
{
int err;
char *cause;
u8 buffer[2];
if (core->revision != SI476X_REVISION_A10) {
err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV,
buffer, sizeof(buffer));
if (err == sizeof(buffer)) {
switch (buffer[1]) {
case SI476X_ERR_BAD_COMMAND:
cause = "Bad command";
err = -EINVAL;
break;
case SI476X_ERR_BAD_ARG1:
cause = "Bad argument #1";
err = -EINVAL;
break;
case SI476X_ERR_BAD_ARG2:
cause = "Bad argument #2";
err = -EINVAL;
break;
case SI476X_ERR_BAD_ARG3:
cause = "Bad argument #3";
err = -EINVAL;
break;
case SI476X_ERR_BAD_ARG4:
cause = "Bad argument #4";
err = -EINVAL;
break;
case SI476X_ERR_BUSY:
cause = "Chip is busy";
err = -EBUSY;
break;
case SI476X_ERR_BAD_INTERNAL_MEMORY:
cause = "Bad internal memory";
err = -EIO;
break;
case SI476X_ERR_BAD_PATCH:
cause = "Bad patch";
err = -EINVAL;
break;
case SI476X_ERR_BAD_BOOT_MODE:
cause = "Bad boot mode";
err = -EINVAL;
break;
case SI476X_ERR_BAD_PROPERTY:
cause = "Bad property";
err = -EINVAL;
break;
default:
cause = "Unknown";
err = -EIO;
}
dev_err(&core->client->dev,
"[Chip error status]: %s\n", cause);
} else {
dev_err(&core->client->dev,
"Failed to fetch error code\n");
err = (err >= 0) ? -EIO : err;
}
} else {
err = -EIO;
}
return err;
}
/**
* si476x_core_send_command() - sends a command to si476x and waits its
* response
* @core: si476x_device structure for the device we are
* communicating with
* @command: command id
* @args: command arguments we are sending
* @argn: actual size of @args
* @resp: buffer to place the expected response from the device
* @respn: actual size of @resp
* @usecs: amount of time to wait before reading the response (in
* usecs)
*
* Function returns 0 on succsess and negative error code on
* failure
*/
static int si476x_core_send_command(struct si476x_core *core,
const u8 command,
const u8 args[],
const int argn,
u8 resp[],
const int respn,
const int usecs)
{
struct i2c_client *client = core->client;
int err;
u8 data[CMD_MAX_ARGS_COUNT + 1];
if (argn > CMD_MAX_ARGS_COUNT) {
err = -ENOMEM;
goto exit;
}
if (!client->adapter) {
err = -ENODEV;
goto exit;
}
/* First send the command and its arguments */
data[0] = command;
memcpy(&data[1], args, argn);
dev_dbg(&client->dev, "Command:\n %*ph\n", argn + 1, data);
err = si476x_core_i2c_xfer(core, SI476X_I2C_SEND,
(char *) data, argn + 1);
if (err != argn + 1) {
dev_err(&core->client->dev,
"Error while sending command 0x%02x\n",
command);
err = (err >= 0) ? -EIO : err;
goto exit;
}
/* Set CTS to zero only after the command is send to avoid
* possible racing conditions when working in polling mode */
atomic_set(&a
|