#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/errno.h>
#include <linux/jiffies.h>
#include <linux/minmax.h>
#include <linux/netlink.h>
#include <linux/sched/signal.h>
#include <linux/sizes.h>
#include <linux/sprintf.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#include <net/devlink.h>
#include "core.h"
#include "devlink.h"
#include "flash.h"
#define ZL_FLASH_ERR_PFX "FW update failed: "
#define ZL_FLASH_ERR_MSG(_extack, _msg, ...) \
NL_SET_ERR_MSG_FMT_MOD((_extack), ZL_FLASH_ERR_PFX _msg, \
## __VA_ARGS__)
static int
zl3073x_flash_download(struct zl3073x_dev *zldev, const char *component,
u32 addr, const void *data, size_t size,
struct netlink_ext_ack *extack)
{
#define ZL_CHECK_DELAY 5000
unsigned long check_time;
const void *ptr, *end;
int rc = 0;
dev_dbg(zldev->dev, "Downloading %zu bytes to device memory at 0x%0x\n",
size, addr);
check_time = jiffies + msecs_to_jiffies(ZL_CHECK_DELAY);
for (ptr = data, end = data + size; ptr < end; ptr += 4, addr += 4) {
rc = zl3073x_write_hwreg(zldev, addr,
get_unaligned((u32 *)ptr));
if (rc) {
ZL_FLASH_ERR_MSG(extack,
"failed to write to memory at 0x%0x",
addr);
return rc;
}
if (time_is_before_jiffies(check_time)) {
if (signal_pending(current)) {
ZL_FLASH_ERR_MSG(extack,
"Flashing interrupted");
return -EINTR;
}
check_time = jiffies + msecs_to_jiffies(ZL_CHECK_DELAY);
}
if ((ptr - data) % 1024 == 0)
zl3073x_devlink_flash_notify(zldev, "Downloading image",
component, ptr - data,
size);
}
zl3073x_devlink_flash_notify(zldev, "Downloading image", component,
ptr - data, size);
dev_dbg(zldev->dev, "%zu bytes downloaded to device memory\n", size);
return rc;
}
static int
zl3073x_flash_error_check(struct zl3073x_dev *zldev,
struct netlink_ext_ack *extack)
{
u32 count, cause;
int rc;
rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_COUNT, &count);
if (rc)
return rc;
else if (!count)
return 0;
rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_CAUSE, &cause);
if (rc)
return rc;
ZL_FLASH_ERR_MSG(extack,
"utility error occurred: count=%u cause=0x%x", count,
cause);
return -EIO;
}
static int
zl3073x_flash_wait_ready(struct zl3073x_dev *zldev, unsigned int timeout_ms)
{
#define ZL_FLASH_POLL_DELAY_MS 100
unsigned long timeout;
int rc, i;
dev_dbg(zldev->dev, "Waiting for flashing to be ready\n");
timeout = jiffies + msecs_to_jiffies(timeout_ms);
for (i = 0; time_is_after_jiffies(timeout); i++) {
u8 value;
if (i > 9) {
if (signal_pending(current))
return -EINTR;
i = 0;
}
rc = zl3073x_read_u8(zldev, ZL_REG_WRITE_FLASH, &value);
if (rc)
return rc;
value = FIELD_GET(ZL_WRITE_FLASH_OP, value);
if (value == ZL_WRITE_FLASH_OP_DONE)
return 0;
msleep(ZL_FLASH_POLL_DELAY_MS);
}
return -ETIMEDOUT;
}
static int
zl3073x_flash_cmd_wait(struct zl3073x_dev *zldev, u32 operation,
struct netlink_ext_ack *extack)
{
#define ZL_FLASH_PHASE1_TIMEOUT_MS 60000
#define ZL_FLASH_PHASE2_TIMEOUT_MS 120000
u8 value;
int rc;
dev_dbg(zldev->dev, "Sending flash command: 0x%x\n", operation);
rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE1_TIMEOUT_MS);
if (rc)
return rc;
rc = zl3073x_read_u8(zldev, ZL_REG_WRITE_FLASH, &value);
if (rc)
return rc;
value &= ~ZL_WRITE_FLASH_OP;
value |= FIELD_PREP(ZL_WRITE_FLASH_OP, operation);
rc = zl3073x_write_u8(zldev, ZL_REG_WRITE_FLASH, value);
if (rc)
return rc;
rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE2_TIMEOUT_MS);
if (rc)
return rc;
return zl3073x_flash_error_check(zldev, extack);
}
static int
zl3073x_flash_get_sector_size(struct zl3073x_dev *zldev, size_t *sector_size)
{
u8 flash_info;
int rc;
rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_INFO, &flash_info);
if (rc)
return rc;
switch (FIELD_GET(ZL_FLASH_INFO_SECTOR_SIZE, flash_info)) {
case ZL_FLASH_INFO_SECTOR_4K:
*sector_size = SZ_4K;
break;
case ZL_FLASH_INFO_SECTOR_64K:
*sector_size = SZ_64K;
break;
default:
rc = -EINVAL;
break;
}
return rc;
}
static int
zl3073x_flash_block(struct zl3073x_dev *zldev, const char *component,
u32 operation, u32 page, u32 addr, const void *data,
size_t size, struct netlink_ext_ack *extack)
{
int rc;
rc = zl3073x_flash_download(zldev, component, addr, data, size, extack);
if (rc)
return rc;
rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_START_ADDR, addr);
if (rc)
return rc;
rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_SIZE, size);
if (rc)
return rc;
rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, page);
if (rc)
return rc;
rc = zl3073x_write_u32(zldev, ZL_REG_FILL_PATTERN, U32_MAX);
if (rc)
return rc;
zl3073x_devlink_flash_notify(zldev, "Flashing image", component, 0,
size);
dev_dbg(zldev->dev, "Flashing %zu bytes to page %u\n", size, page);
rc = zl3073x_flash_cmd_wait(zldev, operation, extack);
if (rc)
return rc;
zl3073x_devlink_flash_notify(zldev, "Flashing image", component, size,
size);
return 0;
}
int zl3073x_flash_sectors(struct zl3073x_dev *zldev, const char *component,
u32 page, u32 addr, const void *data, size_t size,
struct netlink_ext_ack *extack)
{
#define ZL_FLASH_MAX_BLOCK_SIZE 0x0001E000
#define ZL_FLASH_PAGE_SIZE 256
size_t max_block_size, block_size, sector_size;
const void *ptr, *end;
int rc;
rc = zl3073x_flash_get_sector_size(zldev, §or_size);
if (rc) {
ZL_FLASH_ERR_MSG(extack, "Failed to get flash sector size");
return rc;
}
max_block_size = ALIGN_DOWN(ZL_FLASH_MAX_BLOCK_SIZE, sector_size);
for (ptr = data, end = data + size; ptr < end; ptr += block_size) {
char comp_str[32];
block_size = min_t(size_t, max_block_size, end - ptr);
if (max_block_size < size)
snprintf(comp_str, sizeof(comp_str), "%s-part%zu",
component, (ptr - data) / max_block_size + 1);
else
strscpy(comp_str, component);
rc = zl3073x_flash_block(zldev, comp_str,
ZL_WRITE_FLASH_OP_SECTORS, page, addr,
ptr, block_size, extack);
if (rc)
goto finish;
page += block_size / ZL_FLASH_PAGE_SIZE;
}
finish:
zl3073x_devlink_flash_notify(zldev,
rc ? "Flashing failed" : "Flashing done",
component, 0, 0);
return rc;
}
int zl3073x_flash_page(struct zl3073x_dev *zldev, const char *component,
u32 page, u32 addr, const void *data, size_t size,
struct netlink_ext_ack *extack)
{
int rc;
rc = zl3073x_flash_block(zldev, component, ZL_WRITE_FLASH_OP_PAGE, page,
addr, data, size, extack);
zl3073x_devlink_flash_notify(zldev,
rc ? "Flashing failed" : "Flashing done",
component, 0, 0);
return rc;
}
int zl3073x_flash_page_copy(struct zl3073x_dev *zldev, const char *component,
u32 src_page, u32 dst_page,
struct netlink_ext_ack *extack)
{
int rc;
rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_READ, src_page);
if (rc)
return rc;
rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, dst_page);
if (rc)
return rc;
rc = zl3073x_flash_cmd_wait(zldev, ZL_WRITE_FLASH_OP_COPY_PAGE, extack);
if (rc)
ZL_FLASH_ERR_MSG(extack, "Failed to copy page %u to page %u",
src_page, dst_page);
return rc;
}
static int
zl3073x_flash_mode_verify(struct zl3073x_dev *zldev)
{
u8 family, release;
u32 hash;
int rc;
rc = zl3073x_read_u32(zldev, ZL_REG_FLASH_HASH, &hash);
if (rc)
return rc;
rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_FAMILY, &family);
if (rc)
return rc;
rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_RELEASE, &release);
if (rc)
return rc;
dev_dbg(zldev->dev,
"Flash utility check: hash 0x%08x, fam 0x%02x, rel 0x%02x\n",
hash, family, release);
return (family == 0x21) ? 0 : -ENODEV;
}
static int
zl3073x_flash_host_ctrl_enable(struct zl3073x_dev *zldev)
{
u8 host_ctrl;
int rc;
rc = zl3073x_read_u8(zldev, ZL_REG_HOST_CONTROL, &host_ctrl);
if (rc)
return rc;
host_ctrl |= ZL_HOST_CONTROL_ENABLE;
return zl3073x_write_u8(zldev, ZL_REG_HOST_CONTROL, host_ctrl);
}
int zl3073x_flash_mode_enter(struct zl3073x_dev *zldev, const void *util_ptr,
size_t util_size, struct netlink_ext_ack *extack)
{
static const struct zl3073x_hwreg_seq_item pre_seq[] = {
HWREG_SEQ_ITEM(0x80000400, 1, BIT(0), 0),
HWREG_SEQ_ITEM(0x80206340, 1, BIT(4), 0),
HWREG_SEQ_ITEM(0x10000000, 1, BIT(2), 0),
HWREG_SEQ_ITEM(0x10000024, 0x00000001, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10000020, 0x00000001, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10000000, 1, BIT(10), 1000),
};
static const struct zl3073x_hwreg_seq_item post_seq[] = {
HWREG_SEQ_ITEM(0x10400004, 0x000000C0, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10400008, 0x00000000, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10400010, 0x20000000, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10400014, 0x20000004, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10000000, 1, GENMASK(10, 9), 0),
HWREG_SEQ_ITEM(0x10000020, 0x00000000, U32_MAX, 0),
HWREG_SEQ_ITEM(0x10000000, 0, BIT(0), 1000),
};
int rc;
zl3073x_devlink_flash_notify(zldev, "Prepare flash mode", "utility",
0, 0);
rc = zl3073x_write_hwreg_seq(zldev, pre_seq, ARRAY_SIZE(pre_seq));
if (rc) {
ZL_FLASH_ERR_MSG(extack, "cannot execute pre-load sequence");
goto error;
}
rc = zl3073x_flash_download(zldev, "utility", 0x20000000, util_ptr,
util_size, extack);
if (rc) {
ZL_FLASH_ERR_MSG(extack, "cannot download flash utility");
goto error;
}
rc = zl3073x_write_hwreg_seq(zldev, post_seq, ARRAY_SIZE(post_seq));
if (rc) {
ZL_FLASH_ERR_MSG(extack, "cannot execute post-load sequence");
goto error;
}
rc = zl3073x_flash_mode_verify(zldev);
if (rc) {
ZL_FLASH_ERR_MSG(extack, "flash utility check failed");
goto error;
}
rc = zl3073x_flash_host_ctrl_enable(zldev);
if (rc) {
ZL_FLASH_ERR_MSG(extack, "cannot enable host control");
goto error;
}
zl3073x_devlink_flash_notify(zldev, "Flash mode enabled", "utility",
0, 0);
return 0;
error:
zl3073x_flash_mode_leave(zldev, extack);
return rc;
}
int zl3073x_flash_mode_leave(struct zl3073x_dev *zldev,
struct netlink_ext_ack *extack)
{
static const struct zl3073x_hwreg_seq_item fw_reset_seq[] = {
HWREG_SEQ_ITEM(0x80000404, 1, BIT(0), 0),
HWREG_SEQ_ITEM(0x80000410, 1, BIT(0), 0),
};
u8 reset_status;
int rc;
zl3073x_devlink_flash_notify(zldev, "Leaving flash mode", "utility",
0, 0);
rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status);
if (rc)
return rc;
reset_status |= ZL_REG_RESET_STATUS_RESET;
rc = zl3073x_write_u8(zldev, ZL_REG_RESET_STATUS, reset_status);
if (rc)
return rc;
zl3073x_write_hwreg_seq(zldev, fw_reset_seq, ARRAY_SIZE(fw_reset_seq));
msleep(500);
rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status);
if (rc)
return rc;
if (reset_status & ZL_REG_RESET_STATUS_RESET) {
dev_err(zldev->dev,
"Reset not confirmed after switch to normal mode\n");
return -EINVAL;
}
return 0;
}