#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/usb/typec_dp.h>
#include <asm/unaligned.h>
#include "ucsi.h"
enum enum_fw_mode {
BOOT,
FW1,
FW2,
FW_INVALID,
};
#define CCGX_RAB_DEVICE_MODE 0x0000
#define CCGX_RAB_INTR_REG 0x0006
#define DEV_INT BIT(0)
#define PORT0_INT BIT(1)
#define PORT1_INT BIT(2)
#define UCSI_READ_INT BIT(7)
#define CCGX_RAB_JUMP_TO_BOOT 0x0007
#define TO_BOOT 'J'
#define TO_ALT_FW 'A'
#define CCGX_RAB_RESET_REQ 0x0008
#define RESET_SIG 'R'
#define CMD_RESET_I2C 0x0
#define CMD_RESET_DEV 0x1
#define CCGX_RAB_ENTER_FLASHING 0x000A
#define FLASH_ENTER_SIG 'P'
#define CCGX_RAB_VALIDATE_FW 0x000B
#define CCGX_RAB_FLASH_ROW_RW 0x000C
#define FLASH_SIG 'F'
#define FLASH_RD_CMD 0x0
#define FLASH_WR_CMD 0x1
#define FLASH_FWCT1_WR_CMD 0x2
#define FLASH_FWCT2_WR_CMD 0x3
#define FLASH_FWCT_SIG_WR_CMD 0x4
#define CCGX_RAB_READ_ALL_VER 0x0010
#define CCGX_RAB_READ_FW2_VER 0x0020
#define CCGX_RAB_UCSI_CONTROL 0x0039
#define CCGX_RAB_UCSI_CONTROL_START BIT(0)
#define CCGX_RAB_UCSI_CONTROL_STOP BIT(1)
#define CCGX_RAB_UCSI_DATA_BLOCK(offset) (0xf000 | ((offset) & 0xff))
#define REG_FLASH_RW_MEM 0x0200
#define DEV_REG_IDX CCGX_RAB_DEVICE_MODE
#define CCGX_RAB_PDPORT_ENABLE 0x002C
#define PDPORT_1 BIT(0)
#define PDPORT_2 BIT(1)
#define CCGX_RAB_RESPONSE 0x007E
#define ASYNC_EVENT BIT(7)
#define RESET_COMPLETE 0x80
#define EVENT_INDEX RESET_COMPLETE
#define PORT_CONNECT_DET 0x84
#define PORT_DISCONNECT_DET 0x85
#define ROLE_SWAP_COMPELETE 0x87
#define CYACD_LINE_SIZE 527
#define CCG4_ROW_SIZE 256
#define FW1_METADATA_ROW 0x1FF
#define FW2_METADATA_ROW 0x1FE
#define FW_CFG_TABLE_SIG_SIZE 256
static int secondary_fw_min_ver = 41;
enum enum_flash_mode {
SECONDARY_BL,
PRIMARY,
SECONDARY,
FLASH_NOT_NEEDED,
FLASH_INVALID,
};
static const char * const ccg_fw_names[] = {
"ccg_boot.cyacd",
"ccg_primary.cyacd",
"ccg_secondary.cyacd"
};
struct ccg_dev_info {
#define CCG_DEVINFO_FWMODE_SHIFT (0)
#define CCG_DEVINFO_FWMODE_MASK (0x3 << CCG_DEVINFO_FWMODE_SHIFT)
#define CCG_DEVINFO_PDPORTS_SHIFT (2)
#define CCG_DEVINFO_PDPORTS_MASK (0x3 << CCG_DEVINFO_PDPORTS_SHIFT)
u8 mode;
u8 bl_mode;
__le16 silicon_id;
__le16 bl_last_row;
} __packed;
struct version_format {
__le16 build;
u8 patch;
u8 ver;
#define CCG_VERSION_PATCH(x) ((x) << 16)
#define CCG_VERSION(x) ((x) << 24)
#define CCG_VERSION_MIN_SHIFT (0)
#define CCG_VERSION_MIN_MASK (0xf << CCG_VERSION_MIN_SHIFT)
#define CCG_VERSION_MAJ_SHIFT (4)
#define CCG_VERSION_MAJ_MASK (0xf << CCG_VERSION_MAJ_SHIFT)
} __packed;
#define CCG_FW_BUILD_NVIDIA (('n' << 8) | 'v')
#define CCG_OLD_FW_VERSION (CCG_VERSION(0x31) | CCG_VERSION_PATCH(10))
#define CCG_FW_BUILD_NVIDIA_TEGRA (('g' << 8) | 'n')
#define NVIDIA_FTB_DP_OFFSET (2)
#define NVIDIA_FTB_DBG_OFFSET (3)
struct version_info {
struct version_format base;
struct version_format app;
};
struct fw_config_table {
u32 identity;
u16 table_size;
u8 fwct_version;
u8 is_key_change;
u8 guid[16];
struct version_format base;
struct version_format app;
u8 primary_fw_digest[32];
u32 key_exp_length;
u8 key_modulus[256];
u8 key_exp[4];
};
enum ccg_resp_code {
CMD_NO_RESP = 0x00,
CMD_SUCCESS = 0x02,
FLASH_DATA_AVAILABLE = 0x03,
CMD_INVALID = 0x05,
FLASH_UPDATE_FAIL = 0x07,
INVALID_FW = 0x08,
INVALID_ARG = 0x09,
CMD_NOT_SUPPORT = 0x0A,
TRANSACTION_FAIL = 0x0C,
PD_CMD_FAIL = 0x0D,
UNDEF_ERROR = 0x0F,
INVALID_RESP = 0x10,
};
#define CCG_EVENT_MAX (EVENT_INDEX + 43)
struct ccg_cmd {
u16 reg;
u32 data;
int len;
u32 delay;
};
struct ccg_resp {
u8 code;
u8 length;
};
struct ucsi_ccg_altmode {
u16 svid;
u32 mid;
u8 linked_idx;
u8 active_idx;
#define UCSI_MULTI_DP_INDEX (0xff)
bool checked;
} __packed;
struct ucsi_ccg {
struct device *dev;
struct ucsi *ucsi;
struct i2c_client *client;
struct ccg_dev_info info;
struct version_info version[FW2 + 1];
u32 fw_version;
unsigned long flags;
#define RESET_PENDING 0
#define DEV_CMD_PENDING 1
struct ccg_resp dev_resp;
u8 cmd_resp;
int port_num;
int irq;
struct work_struct work;
struct mutex lock;
u16 fw_build;
struct work_struct pm_work;
struct completion complete;
u64 last_cmd_sent;
bool has_multiple_dp;
struct ucsi_ccg_altmode orig[UCSI_MAX_ALTMODES];
struct ucsi_ccg_altmode updated[UCSI_MAX_ALTMODES];
};
static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len)
{
struct i2c_client *client = uc->client;
const struct i2c_adapter_quirks *quirks = client->adapter->quirks;
unsigned char buf[2];
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0x0,
.len = sizeof(buf),
.buf = buf,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.buf = data,
},
};
u32 rlen, rem_len = len, max_read_len = len;
int status;
if (quirks && quirks->max_read_len)
max_read_len = quirks->max_read_len;
pm_runtime_get_sync(uc->dev);
while (rem_len > 0) {
msgs[1].buf = &data[len - rem_len];
rlen = min_t(u16, rem_len, max_read_len);
msgs[1].len = rlen;
put_unaligned_le16(rab, buf);
status = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (status < 0) {
dev_err(uc->dev, "i2c_transfer failed %d\n", status);
pm_runtime_put_sync(uc->dev);
return status;
}
rab += rlen;
rem_len -= rlen;
}
pm_runtime_put_sync(uc->dev);
return 0;
}
static int ccg_write(struct ucsi_ccg *uc, u16 rab, const u8 *data, u32 len)
{
struct i2c_client *client = uc->client;
unsigned char *buf;
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0x0,
}
};
int status;
buf = kzalloc(len + sizeof(rab), GFP_KERNEL);
if (!buf)
return -ENOMEM;
put_unaligned_le16(rab, buf);
memcpy(buf + sizeof(rab), data, len);
msgs[0].len = len + sizeof(rab);
msgs[0].buf = buf;
pm_runtime_get_sync(uc->dev);
status = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (status < 0) {
dev_err(uc->dev, "i2c_transfer failed %d\n", status);
pm_runtime_put_sync(uc->dev);
kfree(buf);
return status;
}
pm_runtime_put_sync(uc->dev);
kfree(buf);
return 0;
}
static int ucsi_ccg_init(struct ucsi_ccg *uc)
{
unsigned int count = 10;
u8 data;
int status;
data = CCGX_RAB_UCSI_CONTROL_STOP;
status = ccg_write(uc, CCGX_RAB_UCSI_CONTROL, &data, sizeof(data));
if (status < 0)
return status;
data = CCGX_RAB_UCSI_CONTROL_START;
status = ccg_write(uc, CCGX_RAB_UCSI_CONTROL, &data, sizeof(data));
if (status < 0)
return status;
do {
status = ccg_read(uc, CCGX_RAB_INTR_REG, &data, sizeof(data));
if (status < 0)
return status;
if (!(data & DEV_INT))
return 0;
status = ccg_write(uc, CCGX_RAB_INTR_REG, &data, sizeof(data));
if (status < 0)
return status;
usleep_range(10000, 11000);
} while (--count);
return -ETIMEDOUT;
}
static void ucsi_ccg_update_get_current_cam_cmd(struct ucsi_ccg *uc, u8 *data)
{
u8 cam, new_cam;
cam = data[0];
new_cam = uc->orig[cam].linked_idx;
uc->updated[new_cam].active_idx = cam;
data[0] = new_cam;
}
static bool ucsi_ccg_update_altmodes(struct ucsi *ucsi,
struct ucsi_altmode *orig,
struct ucsi_altmode *updated)
{
struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi);
struct ucsi_ccg_altmode *alt, *new_alt;
int i, j, k = 0;
bool found = false;
alt = uc->orig;
new_alt = uc->updated;
memset(uc->updated, 0, sizeof(uc->updated));
for (i = 0; i < UCSI_MAX_ALTMODES; i++) {
alt[i].svid = orig[i].svid;
alt[i].mid = orig[i].mid;
if (!alt[i].svid)
break;
}
for (i = 0; i < UCSI_MAX_ALTMODES; i++) {
if (!alt[i].svid)
break;
if (alt[i].checked)
continue;
if (!DP_CONF_GET_PIN_ASSIGN(alt[i].mid)) {
new_alt[k].svid = alt[i].svid;
new_alt[k].mid |= alt[i].mid;
new_alt[k].linked_idx = i;
alt[i].linked_idx = k;
updated[k].svid = new_alt[k].svid;
updated[k].mid = new_alt[k].mid;
k++;
continue;
}
for (j = i + 1; j < UCSI_MAX_ALTMODES; j++) {
if (alt[i].svid != alt[j].svid ||
!DP_CONF_GET_PIN_ASSIGN(alt[j].mid)) {
continue;
} else {
new_alt[k].svid = alt[i].svid;
new_alt[k].mid |= alt[i].mid | alt[j].mid;
new_alt[k].linked_idx = UCSI_MULTI_DP_INDEX;
alt[i].linked_idx = k;
alt[j].linked_idx = k;
alt[j].checked = true;
found = true;
}
}
if (found) {
uc->has_multiple_dp = true;
} else {
new_alt[k].svid = alt[i].svid;
new_alt[k].mid |= alt[i].mid;
new_alt[k].linked_idx = i;
alt[i].linked_idx = k;
}
updated[k].svid = new_alt[k].svid;
updated[k].mid = new_alt[k].mid;
k++;
}
return found;
}
static void ucsi_ccg_update_set_new_cam_cmd(struct ucsi_ccg *uc,
struct ucsi_connector *con,
u64 *cmd)
{
struct ucsi_ccg_altmode *new_port, *port;
struct typec_altmode *alt = NULL;
u8 new_cam, cam, pin;
bool enter_new_mode;
int i, j, k = 0xff;
port = uc->orig;
new_cam = UCSI_SET_NEW_CAM_GET_AM(*cmd);
new_port = &uc->updated[new_cam];
cam = new_port->linked_idx;
enter_new_mode = UCSI_SET_NEW_CAM_ENTER(*cmd);
if (cam == UCSI_MULTI_DP_INDEX) {
if (enter_new_mode) {
for (i = 0; con->partner_altmode[i]; i++) {
alt = con->partner_altmode[i];
if (alt->svid == new_port->svid)
break;
}
for (j = 0; port[j].svid; j++) {
pin = DP_CONF_GET_PIN_ASSIGN(port[j].mid);
if (alt && port[j].svid == alt->svid &&
(pin & DP_CONF_GET_PIN_ASSIGN(alt->vdo))) {
if (k == 0xff || (k != 0xff && pin >
DP_CONF_GET_PIN_ASSIGN(port[k].mid))
) {
k = j;
}
}
}
cam = k;
new_port->active_idx = cam;
} else {
cam = new_port->active_idx;
}
}
*cmd &= ~UCSI_SET_NEW_CAM_AM_MASK;
*cmd |= UCSI_SET_NEW_CAM_SET_AM(cam);
}
static void ucsi_ccg_nvidia_altmode(struct ucsi_ccg *uc,
struct ucsi_altmode *alt)
{
switch (UCSI_ALTMODE_OFFSET(uc->last_cmd_sent)) {
case NVIDIA_FTB_DP_OFFSET:
if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DBG_VDO)
alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DP_VDO |
DP_CAP_DP_SIGNALING | DP_CAP_USB |
DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E));
break;
case NVIDIA_FTB_DBG_OFFSET:
if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DP_VDO)
alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DBG_VDO;
break;
default:
break;
}
}
static int ucsi_ccg_read(struct ucsi *ucsi, unsigned int offset,
void *val, size_t val_len)
{
struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi);
u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(offset);
struct ucsi_capability *cap;
struct ucsi_altmode *alt;
int ret;
ret = ccg_read(uc, reg, val, val_len);
if (ret)
return ret;
if (offset != UCSI_MESSAGE_IN)
return ret;
switch (UCSI_COMMAND(uc->last_cmd_sent)) {
case UCSI_GET_CURRENT_CAM:
if (uc->has_multiple_dp)
ucsi_ccg_update_get_current_cam_cmd(uc, (u8 *)val);
break;
case UCSI_GET_ALTERNATE_MODES:
if (UCSI_ALTMODE_RECIPIENT(uc->last_cmd_sent) ==
UCSI_RECIPIENT_SOP) {
alt = val;
if (alt[0].svid == USB_TYPEC_NVIDIA_VLINK_SID)
ucsi_ccg_nvidia_altmode(uc, alt);
}
break;
case UCSI_GET_CAPABILITY:
if (uc->fw_build == CCG_FW_BUILD_NVIDIA_TEGRA) {
cap = val;
cap->features &= ~UCSI_CAP_ALT_MODE_DETAILS;
}
break;
default:
break;
}
uc->last_cmd_sent = 0;
return ret;
}
static int ucsi_ccg_async_write(struct ucsi *ucsi, unsigned int offset,
const void *val, size_t val_len)
{
u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(offset);
return ccg_write(ucsi_get_drvdata(ucsi), reg, val, val_len);
}
static int ucsi_ccg_sync_write(struct ucsi *ucsi, unsigned int offset,
const void *val, size_t val_len)
{
struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi);
struct ucsi_connector *con;
int con_index;
int ret;
mutex_lock(&uc->lock);
pm_runtime_get_sync(uc->dev);
set_bit(DEV_CMD_PENDING, &uc->flags);
if (offset == UCSI_CONTROL && val_len == sizeof(uc->last_cmd_sent)) {
uc->last_cmd_sent = *(u64 *)val;
if (UCSI_COMMAND(uc->last_cmd_sent) == UCSI_SET_NEW_CAM &&
uc->has_multiple_dp) {
con_index = (uc->last_cmd_sent >> 16) &
UCSI_CMD_CONNECTOR_MASK;
con = &uc->ucsi->connector[con_index - 1];
ucsi_ccg_update_set_new_cam_cmd(uc, con, (u64 *)val);
}
}
ret = ucsi_ccg_async_write(ucsi, offset, val, val_len);
if (ret)
goto err_clear_bit;
if (!wait_for_completion_timeout(&uc->complete, msecs_to_jiffies(5000)))
ret = -ETIMEDOUT;
err_clear_bit:
clear_bit(DEV_CMD_PENDING, &uc->flags);
pm_runtime_put_sync(uc->dev);
mutex_unlock(&uc->lock);
return ret;
}
static const struct ucsi_operations ucsi_ccg_ops = {
.read = ucsi_ccg_read,
.sync_write = ucsi_ccg_sync_write,
.async_write = ucsi_ccg_async_write,
.update_altmodes = ucsi_ccg_update_altmodes
};
static irqreturn_t ccg_irq_handler(int irq, void *data)
{
u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_CCI);
struct ucsi_ccg *uc = data;
u8 intr_reg;
u32 cci;
int ret;
ret = ccg_read(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg));
if (ret)
return ret;
ret = ccg_read(uc, reg, (void *)&cci, sizeof(cci));
if (ret)
goto err_clear_irq;
if (UCSI_CCI_CONNECTOR(cci))
ucsi_connector_change(uc->ucsi, UCSI_CCI_CONNECTOR(cci));
if (test_bit(DEV_CMD_PENDING, &uc->flags) &&
cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE))
complete(&uc->complete);
err_clear_irq:
ccg_write(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg));
return IRQ_HANDLED;
}
static int ccg_request_irq(struct ucsi_ccg *uc)
{
unsigned long flags = IRQF_ONESHOT;
if (!dev_fwnode(uc->dev))
flags |= IRQF_TRIGGER_HIGH;
return request_threaded_irq(uc->irq, NULL, ccg_irq_handler, flags, dev_name(uc->dev), uc);
}
static void ccg_pm_workaround_work(struct work_struct *pm_work)
{
ccg_irq_handler(0, container_of(pm_work, struct ucsi_ccg, pm_work));
}
static int get_fw_info(struct ucsi_ccg *uc)
{
int err;
err = ccg_read(uc, CCGX_RAB_READ_ALL_VER, (u8 *)(&uc->version),
sizeof(uc->version));
if (err < 0)
return err;
uc->fw_version = CCG_VERSION(uc->version[FW2].app.ver) |
CCG_VERSION_PATCH(uc->version[FW2].app.patch);
err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, (u8 *)(&uc->info),
sizeof(uc->info));
if (err < 0)
return err;
return 0;
}
static inline bool invalid_async_evt(int code)
{
return (code >= CCG_EVENT_MAX) || (code < EVENT_INDEX);
}
static void ccg_process_response(struct ucsi_ccg *uc)
{
struct device *dev = uc->dev;
if (uc->dev_resp.code & ASYNC_EVENT) {
if (uc->dev_resp.code == RESET_COMPLETE) {
if (test_bit(RESET_PENDING, &uc->flags))
uc->cmd_resp = uc->dev_resp.code;
get_fw_info(uc);
}
if (invalid_async_evt(uc->dev_resp.code))
dev_err(dev, "invalid async evt %d\n",
uc->dev_resp.code);
} else {
if (test_bit(DEV_CMD_PENDING, &uc->flags)) {
uc->cmd_resp = uc->dev_resp.code;
clear_bit(DEV_CMD_PENDING, &uc->flags);
} else {
dev_err(dev, "dev resp 0x%04x but no cmd pending\n",
uc->dev_resp.code);
}
}
}
static int ccg_read_response(struct ucsi_ccg *uc)
{
unsigned long target = jiffies + msecs_to_jiffies(1000);
struct device *dev = uc->dev;
u8 intval;
int status;
do {
status = ccg_read(uc, CCGX_RAB_INTR_REG, &intval,
sizeof(intval));
if (status < 0)
return status;
if (intval & DEV_INT)
break;
usleep_range(500, 600);
} while (time_is_after_jiffies(target));
if (time_is_before_jiffies(target)) {
dev_err(dev, "response timeout error\n");
return -ETIME;
}
status = ccg_read(uc, CCGX_RAB_RESPONSE, (u8 *)&uc->dev_resp,
sizeof(uc->dev_resp));
if (status < 0)
return status;
status = ccg_write(uc, CCGX_RAB_INTR_REG, &intval, sizeof(intval));
if (status < 0)
return status;
return 0;
}
static int ccg_send_command(struct ucsi_ccg *uc, struct ccg_cmd *cmd)
{
struct device *dev = uc->dev;
int ret;
switch (cmd->reg & 0xF000) {
case DEV_REG_IDX:
set_bit(DEV_CMD_PENDING, &uc->flags);
break;
default:
dev_err(dev, "invalid cmd register\n");
break;
}
ret = ccg_write(uc, cmd->reg, (u8 *)&cmd->data, cmd->len);
if (ret < 0)
return ret;
msleep(cmd->delay);
ret = ccg_read_response(uc);
if (ret < 0) {
dev_err(dev, "response read error\n");
switch (cmd->reg & 0xF000) {
case DEV_REG_IDX:
clear_bit(DEV_CMD_PENDING, &uc->flags);
break;
default:
dev_err(dev, "invalid cmd register\n");
break;
}
return -EIO;
}
ccg_process_response(uc);
return uc->cmd_resp;
}
static int ccg_cmd_enter_flashing(struct ucsi_ccg *uc)
{
struct ccg_cmd cmd;
int ret;
cmd.reg = CCGX_RAB_ENTER_FLASHING;
cmd.data = FLASH_ENTER_SIG;
cmd.len = 1;
cmd.delay = 50;
mutex_lock(&uc->lock);
ret = ccg_send_command(uc, &cmd);
mutex_unlock(&uc->lock);
if (ret != CMD_SUCCESS) {
dev_err(uc->dev, "enter flashing failed ret=%d\n", ret);
return ret;
}
return 0;
}
static int ccg_cmd_reset(struct ucsi_ccg *uc)
{
struct ccg_cmd cmd;
u8 *p;
int ret;
p = (u8 *)&cmd.data;
cmd.reg = CCGX_RAB_RESET_REQ;
p[0] = RESET_SIG;
p[1] = CMD_RESET_DEV;
cmd.len = 2;
cmd.delay = 5000;
mutex_lock(&uc->lock);
set_bit(RESET_PENDING, &uc->flags);
ret = ccg_send_command(uc, &cmd);
if (ret != RESET_COMPLETE)
goto err_clear_flag;
ret = 0;
err_clear_flag:
clear_bit(RESET_PENDING, &uc->flags);
mutex_unlock(&uc->lock);
return ret;
}
static int ccg_cmd_port_control(struct ucsi_ccg *uc, bool enable)
{
struct ccg_cmd cmd;
int ret;
cmd.reg = CCGX_RAB_PDPORT_ENABLE;
if (enable)
cmd.data = (uc->port_num == 1) ?
PDPORT_1 : (PDPORT_1 | PDPORT_2);
else
cmd.data = 0x0;
cmd.len = 1;
cmd.delay = 10;
mutex_lock(&uc->lock);
ret = ccg_send_command(uc, &cmd);
mutex_unlock(&uc->lock);
if (ret != CMD_SUCCESS) {
dev_err(uc->dev, "port control failed ret=%d\n", ret);
return ret;
}
return 0;
}
static int ccg_cmd_jump_boot_mode(struct ucsi_ccg *uc, int bl_mode)
{
struct ccg_cmd cmd;
int ret;
cmd.reg = CCGX_RAB_JUMP_TO_BOOT;
if (bl_mode)
cmd.data = TO_BOOT;
else
cmd.data = TO_ALT_FW;
cmd.len = 1;
cmd.delay = 100;
mutex_lock(&uc->lock);
set_bit(RESET_PENDING, &uc->flags);
ret = ccg_send_command(uc, &cmd);
if (ret != RESET_COMPLETE)
goto err_clear_flag;
ret = 0;
err_clear_flag:
clear_bit(RESET_PENDING, &uc->flags);
mutex_unlock(&uc->lock);
return ret;
}
static int
ccg_cmd_write_flash_row(struct ucsi_ccg *uc, u16 row,
const void *data, u8 fcmd)
{
struct i2c_client *client = uc->client;
struct ccg_cmd cmd;
u8 buf[CCG4_ROW_SIZE + 2];
u8 *p;
int ret;
put_unaligned_le16(REG_FLASH_RW_MEM, buf);
memcpy(buf + 2, data, CCG4_ROW_SIZE);
mutex_lock(&uc->lock);
ret = i2c_master_send(client, buf, CCG4_ROW_SIZE + 2);
if (ret != CCG4_ROW_SIZE + 2) {
dev_err(uc->dev, "REG_FLASH_RW_MEM write fail %d\n", ret);
mutex_unlock(&uc->lock);
return ret < 0 ? ret : -EIO;
}
p = (u8 *)&cmd.data;
cmd.reg = CCGX_RAB_FLASH_ROW_RW;
p[0] = FLASH_SIG;
p[1] = fcmd;
put_unaligned_le16(row, &p[2]);
cmd.len = 4;
cmd.delay = 50;
if (fcmd == FLASH_FWCT_SIG_WR_CMD)
cmd.delay += 400;
if (row == 510)
cmd.delay += 220;
ret = ccg_send_command(uc, &cmd);
mutex_unlock(&uc->lock);
if (ret != CMD_SUCCESS) {
dev_err(uc->dev, "write flash row failed ret=%d\n", ret);
return ret;
}
return 0;
}
static int ccg_cmd_validate_fw(struct ucsi_ccg *uc, unsigned int fwid)
{
struct ccg_cmd cmd;
int ret;
cmd.reg = CCGX_RAB_VALIDATE_FW;
cmd.data = fwid;
cmd.len = 1;
cmd.delay = 500;
mutex_lock(&uc->lock);
ret = ccg_send_command(uc, &cmd);
mutex_unlock(&uc->lock);
if (ret != CMD_SUCCESS)
return ret;
return 0;
}
static bool ccg_check_vendor_version(struct ucsi_ccg *uc,
struct version_format *app,
struct fw_config_table *fw_cfg)
{
struct device *dev = uc->dev;
if (le16_to_cpu(app->build) != uc->fw_build) {
dev_info(dev, "current fw is not from supported vendor\n");
return false;
}
if (le16_to_cpu(fw_cfg->app.build) != uc->fw_build) {
dev_info(dev, "new fw is not from supported vendor\n");
return false;
}
return true;
}
static bool ccg_check_fw_version(struct ucsi_ccg *uc, const char *fw_name,
struct version_format *app)
{
const struct firmware *fw = NULL;
struct device *dev = uc->dev;
struct fw_config_table fw_cfg;
u32 cur_version, new_version;
bool is_later = false;
if (request_firmware(&fw, fw_name, dev) != 0) {
dev_err(dev, "error: Failed to open cyacd file %s\n", fw_name);
return false;
}
if (fw->size < sizeof(fw_cfg) + FW_CFG_TABLE_SIG_SIZE)
goto out_release_firmware;
memcpy((uint8_t *)&fw_cfg, fw->data + fw->size -
sizeof(fw_cfg) - FW_CFG_TABLE_SIG_SIZE, sizeof(fw_cfg));
if (fw_cfg.identity != ('F' | 'W' << 8 | 'C' << 16 | 'T' << 24)) {
dev_info(dev, "not a signed image\n");
goto out_release_firmware;
}
cur_version = le16_to_cpu(app->build) | CCG_VERSION_PATCH(app->patch) |
CCG_VERSION(app->ver);
new_version = le16_to_cpu(fw_cfg.app.build) |
CCG_VERSION_PATCH(fw_cfg.app.patch) |
CCG_VERSION(fw_cfg.app.ver);
if (!ccg_check_vendor_version(uc, app, &fw_cfg))
goto out_release_firmware;
if (new_version > cur_version)
is_later = true;
out_release_firmware:
release_firmware(fw);
return is_later;
}
static int ccg_fw_update_needed(struct ucsi_ccg *uc,
enum enum_flash_mode *mode)
{
struct device *dev = uc->dev;
int err;
struct version_info version[3];
err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, (u8 *)(&uc->info),
sizeof(uc->info));
if (err) {
dev_err(dev, "read device mode failed\n");
return err;
}
err = ccg_read(uc, CCGX_RAB_READ_ALL_VER, (u8 *)version,
sizeof(version));
if (err) {
dev_err(dev, "read device mode failed\n");
return err;
}
if (memcmp(&version[FW1], "\0\0\0\0\0\0\0\0",
sizeof(struct version_info)) == 0) {
dev_info(dev, "secondary fw is not flashed\n");
*mode = SECONDARY_BL;
} else if (le16_to_cpu(version[FW1].base.build) <
secondary_fw_min_ver) {
dev_info(dev, "secondary fw version is too low (< %d)\n",
secondary_fw_min_ver);
*mode = SECONDARY;
} else if (memcmp(&version[FW2], "\0\0\0\0\0\0\0\0",
sizeof(struct version_info)) == 0) {
dev_info(dev, "primary fw is not flashed\n");
*mode = PRIMARY;
} else if (ccg_check_fw_version(uc, ccg_fw_names[PRIMARY],
&version[FW2].app)) {
dev_info(dev, "found primary fw with later version\n");
*mode = PRIMARY;
} else {
dev_info(dev, "secondary and primary fw are the latest\n");
*mode = FLASH_NOT_NEEDED;
}
return 0;
}
static int do_flash(struct ucsi_ccg *uc, enum enum_flash_mode mode)
{
struct device *dev = uc->dev;
const struct firmware *fw = NULL;
const char *p, *s;
const char *eof;
int err, row, len, line_sz, line_cnt = 0;
unsigned long start_time = jiffies;
struct fw_config_table fw_cfg;
u8 fw_cfg_sig[FW_CFG_TABLE_SIG_SIZE];
u8 *wr_buf;
err = request_firmware(&fw, ccg_fw_names[mode], dev);
if (err) {
dev_err(dev, "request %s failed err=%d\n",
ccg_fw_names[mode], err);
return err;
}
if (((uc->info.mode & CCG_DEVINFO_FWMODE_MASK) >>
CCG_DEVINFO_FWMODE_SHIFT) == FW2) {
err = ccg_cmd_port_control(uc, false);
if (err < 0)
goto release_fw;
err = ccg_cmd_jump_boot_mode(uc, 0);
if (err < 0)
goto release_fw;
}
eof = fw->data + fw->size;
if (fw->size < sizeof(fw_cfg) + sizeof(fw_cfg_sig))
goto not_signed_fw;
memcpy((uint8_t *)&fw_cfg, fw->data + fw->size -
sizeof(fw_cfg) - sizeof(fw_cfg_sig), sizeof(fw_cfg));
if (fw_cfg.identity != ('F' | ('W' << 8) | ('C' << 16) | ('T' << 24))) {
dev_info(dev, "not a signed image\n");
goto not_signed_fw;
}
eof = fw->data + fw->size - sizeof(fw_cfg) - sizeof(fw_cfg_sig);
memcpy((uint8_t *)&fw_cfg_sig,
fw->data + fw->size - sizeof(fw_cfg_sig), sizeof(fw_cfg_sig));
err = ccg_cmd_write_flash_row(uc, 0, (u8 *)&fw_cfg,
FLASH_FWCT1_WR_CMD);
if (err)
goto release_fw;
err = ccg_cmd_write_flash_row(uc, 0, (u8 *)&fw_cfg + CCG4_ROW_SIZE,
FLASH_FWCT2_WR_CMD);
if (err)
goto release_fw;
err = ccg_cmd_write_flash_row(uc, 0, &fw_cfg_sig,
FLASH_FWCT_SIG_WR_CMD);
if (err)
goto release_fw;
not_signed_fw:
wr_buf = kzalloc(CCG4_ROW_SIZE + 4, GFP_KERNEL);
if (!wr_buf) {
err = -ENOMEM;
goto release_fw;
}
err = ccg_cmd_enter_flashing(uc);
if (err)
goto release_mem;
p = strnchr(fw->data, fw->size, ':');
while (p < eof) {
s = strnchr(p + 1, eof - p - 1, ':');
if (!s)
s = eof;
line_sz = s - p;
if (line_sz != CYACD_LINE_SIZE) {
dev_err(dev, "Bad FW format line_sz=%d\n", line_sz);
err = -EINVAL;
goto release_mem;
}
if (hex2bin(wr_buf, p + 3, CCG4_ROW_SIZE + 4)) {
err = -EINVAL;
goto release_mem;
}
row = get_unaligned_be16(wr_buf);
len = get_unaligned_be16(&wr_buf[2]);
if (len != CCG4_ROW_SIZE) {
err = -EINVAL;
goto release_mem;
}
err = ccg_cmd_write_flash_row(uc, row, wr_buf + 4,
FLASH_WR_CMD);
if (err)
goto release_mem;
line_cnt++;
p = s;
}
dev_info(dev, "total %d row flashed. time: %dms\n",
line_cnt, jiffies_to_msecs(jiffies - start_time));
err = ccg_cmd_validate_fw(uc, (mode == PRIMARY) ? FW2 : FW1);
if (err)
dev_err(dev, "%s validation failed err=%d\n",
(mode == PRIMARY) ? "FW2" : "FW1", err);
else
dev_info(dev, "%s validated\n",
(mode == PRIMARY) ? "FW2" : "FW1");
err = ccg_cmd_port_control(uc, false);
if (err < 0)
goto release_mem;
err = ccg_cmd_reset(uc);
if (err < 0)
goto release_mem;
err = ccg_cmd_port_control(uc, true);
if (err < 0)
goto release_mem;
release_mem:
kfree(wr_buf);
release_fw:
release_firmware(fw);
return err;
}
static int ccg_fw_update(struct ucsi_ccg *uc, enum enum_flash_mode flash_mode)
{
int err = 0;
while (flash_mode != FLASH_NOT_NEEDED) {
err = do_flash(uc, flash_mode);
if (err < 0)
return err;
err = ccg_fw_update_needed(uc, &flash_mode);
if (err < 0)
return err;
}
dev_info(uc->dev, "CCG FW update successful\n");
return err;
}
static int ccg_restart(struct ucsi_ccg *uc)
{
struct device *dev = uc->dev;
int status;
status = ucsi_ccg_init(uc);
if (status < 0) {
dev_err(dev, "ucsi_ccg_start fail, err=%d\n", status);
return status;
}
status = ccg_request_irq(uc);
if (status < 0) {
dev_err(dev, "request_threaded_irq failed - %d\n", status);
return status;
}
status = ucsi_register(uc->ucsi);
if (status) {
dev_err(uc->dev, "failed to register the interface\n");
return status;
}
pm_runtime_enable(uc->dev);
return 0;
}
static void ccg_update_firmware(struct work_struct *work)
{
struct ucsi_ccg *uc = container_of(work, struct ucsi_ccg, work);
enum enum_flash_mode flash_mode;
int status;
status = ccg_fw_update_needed(uc, &flash_mode);
if (status < 0)
return;
if (flash_mode != FLASH_NOT_NEEDED) {
ucsi_unregister(uc->ucsi);
pm_runtime_disable(uc->dev);
free_irq(uc->irq, uc);
ccg_fw_update(uc, flash_mode);
ccg_restart(uc);
}
}
static ssize_t do_flash_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t n)
{
struct ucsi_ccg *uc = i2c_get_clientdata(to_i2c_client(dev));
bool flash;
if (kstrtobool(buf, &flash))
return -EINVAL;
if (!flash)
return n;
if (uc->fw_build == 0x0) {
dev_err(dev, "fail to flash FW due to missing FW build info\n");
return -EINVAL;
}
schedule_work(&uc->work);
return n;
}
static DEVICE_ATTR_WO(do_flash);
static struct attribute *ucsi_ccg_attrs[] = {
&dev_attr_do_flash.attr,
NULL,
};
ATTRIBUTE_GROUPS(ucsi_ccg);
static int ucsi_ccg_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct ucsi_ccg *uc;
const char *fw_name;
int status;
uc = devm_kzalloc(dev, sizeof(*uc), GFP_KERNEL);
if (!uc)
return -ENOMEM;
uc->dev = dev;
uc->client = client;
uc->irq = client->irq;
mutex_init(&uc->lock);
init_completion(&uc->complete);
INIT_WORK(&uc->work, ccg_update_firmware);
INIT_WORK(&uc->pm_work, ccg_pm_workaround_work);
status = device_property_read_string(dev, "firmware-name", &fw_name);
if (!status) {
if (!strcmp(fw_name, "nvidia,jetson-agx-xavier"))
uc->fw_build = CCG_FW_BUILD_NVIDIA_TEGRA;
else if (!strcmp(fw_name, "nvidia,gpu"))
uc->fw_build = CCG_FW_BUILD_NVIDIA;
}
if (!uc->fw_build)
dev_err(uc->dev, "failed to get FW build information\n");
status = ucsi_ccg_init(uc);
if (status < 0) {
dev_err(uc->dev, "ucsi_ccg_init failed - %d\n", status);
return status;
}
status = get_fw_info(uc);
if (status < 0) {
dev_err(uc->dev, "get_fw_info failed - %d\n", status);
return status;
}
uc->port_num = 1;
if (uc->info.mode & CCG_DEVINFO_PDPORTS_MASK)
uc->port_num++;
uc->ucsi = ucsi_create(dev, &ucsi_ccg_ops);
if (IS_ERR(uc->ucsi))
return PTR_ERR(uc->ucsi);
ucsi_set_drvdata(uc->ucsi, uc);
status = ccg_request_irq(uc);
if (status < 0) {
dev_err(uc->dev, "request_threaded_irq failed - %d\n", status);
goto out_ucsi_destroy;
}
status = ucsi_register(uc->ucsi);
if (status)
goto out_free_irq;
i2c_set_clientdata(client, uc);
pm_runtime_set_active(uc->dev);
pm_runtime_enable(uc->dev);
pm_runtime_use_autosuspend(uc->dev);
pm_runtime_set_autosuspend_delay(uc->dev, 5000);
pm_runtime_idle(uc->dev);
return 0;
out_free_irq:
free_irq(uc->irq, uc);
out_ucsi_destroy:
ucsi_destroy(uc->ucsi);
return status;
}
static void ucsi_ccg_remove(struct i2c_client *client)
{
struct ucsi_ccg *uc = i2c_get_clientdata(client);
cancel_work_sync(&uc->pm_work);
cancel_work_sync(&uc->work);
pm_runtime_disable(uc->dev);
ucsi_unregister(uc->ucsi);
ucsi_destroy(uc->ucsi);
free_irq(uc->irq, uc);
}
static const struct of_device_id ucsi_ccg_of_match_table[] = {
{ .compatible = "cypress,cypd4226", },
{ }
};
MODULE_DEVICE_TABLE(of, ucsi_ccg_of_match_table);
static const struct i2c_device_id ucsi_ccg_device_id[] = {
{"ccgx-ucsi", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, ucsi_ccg_device_id);
static const struct acpi_device_id amd_i2c_ucsi_match[] = {
{"AMDI0042"},
{}
};
MODULE_DEVICE_TABLE(acpi, amd_i2c_ucsi_match);
static int ucsi_ccg_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ucsi_ccg *uc = i2c_get_clientdata(client);
return ucsi_resume(uc->ucsi);
}
static int ucsi_ccg_runtime_suspend(struct device *dev)
{
return 0;
}
static int ucsi_ccg_runtime_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ucsi_ccg *uc = i2c_get_clientdata(client);
if (uc->fw_build == CCG_FW_BUILD_NVIDIA &&
uc->fw_version <= CCG_OLD_FW_VERSION)
schedule_work(&uc->pm_work);
return 0;
}
static const struct dev_pm_ops ucsi_ccg_pm = {
.resume = ucsi_ccg_resume,
.runtime_suspend = ucsi_ccg_runtime_suspend,
.runtime_resume = ucsi_ccg_runtime_resume,
};
static struct i2c_driver ucsi_ccg_driver = {
.driver = {
.name = "ucsi_ccg",
.pm = &ucsi_ccg_pm,
.dev_groups = ucsi_ccg_groups,
.acpi_match_table = amd_i2c_ucsi_match,
.of_match_table = ucsi_ccg_of_match_table,
},
.probe = ucsi_ccg_probe,
.remove = ucsi_ccg_remove,
.id_table = ucsi_ccg_device_id,
};
module_i2c_driver(ucsi_ccg_driver);
MODULE_AUTHOR("Ajay Gupta <ajayg@nvidia.com>");
MODULE_DESCRIPTION("UCSI driver for Cypress CCGx Type-C controller");
MODULE_LICENSE("GPL v2"