diff options
Diffstat (limited to 'drivers/dpll/zl3073x/flash.c')
-rw-r--r-- | drivers/dpll/zl3073x/flash.c | 666 |
1 files changed, 666 insertions, 0 deletions
diff --git a/drivers/dpll/zl3073x/flash.c b/drivers/dpll/zl3073x/flash.c new file mode 100644 index 000000000000..83452a77e3e9 --- /dev/null +++ b/drivers/dpll/zl3073x/flash.c @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#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__) + +/** + * zl3073x_flash_download - Download image block to device memory + * @zldev: zl3073x device structure + * @component: name of the component to be downloaded + * @addr: device memory target address + * @data: pointer to data to download + * @size: size of data to download + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +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 /* Check for interrupt each 5 seconds */ + 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) { + /* Write current word to HW memory */ + 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); + } + + /* Report status each 1 kB block */ + 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; +} + +/** + * zl3073x_flash_error_check - Check for flash utility errors + * @zldev: zl3073x device structure + * @extack: netlink extack pointer to report errors + * + * The function checks for errors detected by the flash utility and + * reports them if any were found. + * + * Return: 0 on success, -EIO when errors are detected + */ +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; /* No error */ + + rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_CAUSE, &cause); + if (rc) + return rc; + + /* Report errors */ + ZL_FLASH_ERR_MSG(extack, + "utility error occurred: count=%u cause=0x%x", count, + cause); + + return -EIO; +} + +/** + * zl3073x_flash_wait_ready - Check or wait for utility to be ready to flash + * @zldev: zl3073x device structure + * @timeout_ms: timeout for the waiting + * + * Return: 0 on success, <0 on error + */ +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; + + /* Check for interrupt each 1s */ + 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; /* Successfully done */ + + msleep(ZL_FLASH_POLL_DELAY_MS); + } + + return -ETIMEDOUT; +} + +/** + * zl3073x_flash_cmd_wait - Perform flash operation and wait for finish + * @zldev: zl3073x device structure + * @operation: operation to perform + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_cmd_wait(struct zl3073x_dev *zldev, u32 operation, + struct netlink_ext_ack *extack) +{ +#define ZL_FLASH_PHASE1_TIMEOUT_MS 60000 /* up to 1 minute */ +#define ZL_FLASH_PHASE2_TIMEOUT_MS 120000 /* up to 2 minutes */ + 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; + + /* Issue the requested operation */ + 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; + + /* Wait for command completion */ + rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE2_TIMEOUT_MS); + if (rc) + return rc; + + return zl3073x_flash_error_check(zldev, extack); +} + +/** + * zl3073x_flash_get_sector_size - Get flash sector size + * @zldev: zl3073x device structure + * @sector_size: sector size returned by the function + * + * The function reads the flash sector size detected by flash utility and + * stores it into @sector_size. + * + * Return: 0 on success, <0 on error + */ +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; +} + +/** + * zl3073x_flash_block - Download and flash memory block + * @zldev: zl3073x device structure + * @component: component name + * @operation: flash operation to perform + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function downloads the memory block given by the @data pointer and + * the size @size and flashes it into internal memory on flash page @page. + * The internal flash operation performed by the firmware is specified by + * the @operation parameter. + * + * Return: 0 on success, <0 on error + */ +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; + + /* Download block to device memory */ + rc = zl3073x_flash_download(zldev, component, addr, data, size, extack); + if (rc) + return rc; + + /* Set address to flash from */ + rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_START_ADDR, addr); + if (rc) + return rc; + + /* Set size of block to flash */ + rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_SIZE, size); + if (rc) + return rc; + + /* Set destination page to flash */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, page); + if (rc) + return rc; + + /* Set filling pattern */ + 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); + + /* Execute sectors flash operation */ + rc = zl3073x_flash_cmd_wait(zldev, operation, extack); + if (rc) + return rc; + + zl3073x_devlink_flash_notify(zldev, "Flashing image", component, size, + size); + + return 0; +} + +/** + * zl3073x_flash_sectors - Flash sectors + * @zldev: zl3073x device structure + * @component: component name + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function flashes given @data with size of @size to the internal flash + * memory block starting from page @page. The function uses sector flash + * method and has to take into account the flash sector size reported by + * flashing utility. Input data are spliced into blocks according this + * sector size and each block is flashed separately. + * + * Return: 0 on success, <0 on error + */ +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; + + /* Get flash sector size */ + rc = zl3073x_flash_get_sector_size(zldev, §or_size); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "Failed to get flash sector size"); + return rc; + } + + /* Determine max block size depending on sector size */ + 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); + + /* Add suffix '-partN' if the requested component size is + * greater than max_block_size. + */ + 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); + + /* Flash the memory block */ + rc = zl3073x_flash_block(zldev, comp_str, + ZL_WRITE_FLASH_OP_SECTORS, page, addr, + ptr, block_size, extack); + if (rc) + goto finish; + + /* Move to next page */ + page += block_size / ZL_FLASH_PAGE_SIZE; + } + +finish: + zl3073x_devlink_flash_notify(zldev, + rc ? "Flashing failed" : "Flashing done", + component, 0, 0); + + return rc; +} + +/** + * zl3073x_flash_page - Flash page + * @zldev: zl3073x device structure + * @component: component name + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function flashes given @data with size of @size to the internal flash + * memory block starting with page @page. + * + * Return: 0 on success, <0 on error + */ +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; + + /* Flash the memory block */ + 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; +} + +/** + * zl3073x_flash_page_copy - Copy flash page + * @zldev: zl3073x device structure + * @component: component name + * @src_page: source page to copy + * @dst_page: destination page + * @extack: netlink extack pointer to report errors + * + * The function copies one flash page specified by @src_page into the flash + * page specified by @dst_page. + * + * Return: 0 on success, <0 on error + */ +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; + + /* Set source page to be copied */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_READ, src_page); + if (rc) + return rc; + + /* Set destination page for the copy */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, dst_page); + if (rc) + return rc; + + /* Perform copy operation */ + 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; +} + +/** + * zl3073x_flash_mode_verify - Check flash utility + * @zldev: zl3073x device structure + * + * Return: 0 if the flash utility is ready, <0 on error + */ +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 success for correct family */ + return (family == 0x21) ? 0 : -ENODEV; +} + +static int +zl3073x_flash_host_ctrl_enable(struct zl3073x_dev *zldev) +{ + u8 host_ctrl; + int rc; + + /* Enable host control */ + 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); +} + +/** + * zl3073x_flash_mode_enter - Switch the device to flash mode + * @zldev: zl3073x device structure + * @util_ptr: buffer with flash utility + * @util_size: size of buffer with flash utility + * @extack: netlink extack pointer to report errors + * + * The function prepares and switches the device into flash mode. + * + * The procedure: + * 1) Stop device CPU by specific HW register sequence + * 2) Download flash utility to device memory + * 3) Resume device CPU by specific HW register sequence + * 4) Check communication with flash utility + * 5) Enable host control necessary to access flash API + * 6) Check for potential error detected by the utility + * + * The API provided by normal firmware is not available in flash mode + * so the caller has to ensure that this API is not used in this mode. + * + * After performing flash operation the caller should call + * @zl3073x_flash_mode_leave to return back to normal operation. + * + * Return: 0 on success, <0 on error. + */ +int zl3073x_flash_mode_enter(struct zl3073x_dev *zldev, const void *util_ptr, + size_t util_size, struct netlink_ext_ack *extack) +{ + /* Sequence to be written prior utility download */ + 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), + }; + /* Sequence to be written after utility download */ + 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); + + /* Execure pre-load sequence */ + 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; + } + + /* Download utility image to device memory */ + 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; + } + + /* Execute post-load sequence */ + 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; + } + + /* Check that utility identifies itself correctly */ + rc = zl3073x_flash_mode_verify(zldev); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "flash utility check failed"); + goto error; + } + + /* Enable host control */ + 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; +} + +/** + * zl3073x_flash_mode_leave - Leave flash mode + * @zldev: zl3073x device structure + * @extack: netlink extack pointer to report errors + * + * The function instructs the device to leave the flash mode and + * to return back to normal operation. + * + * The procedure: + * 1) Set reset flag + * 2) Reset the device CPU by specific HW register sequence + * 3) Wait for the device to be ready + * 4) Check the reset flag was cleared + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_mode_leave(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack) +{ + /* Sequence to be written after flash */ + 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); + + /* Read reset status register */ + rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status); + if (rc) + return rc; + + /* Set reset bit */ + reset_status |= ZL_REG_RESET_STATUS_RESET; + + /* Update reset status register */ + rc = zl3073x_write_u8(zldev, ZL_REG_RESET_STATUS, reset_status); + if (rc) + return rc; + + /* We do not check the return value here as the sequence resets + * the device CPU and the last write always return an error. + */ + zl3073x_write_hwreg_seq(zldev, fw_reset_seq, ARRAY_SIZE(fw_reset_seq)); + + /* Wait for the device to be ready */ + msleep(500); + + /* Read again the reset status register */ + rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status); + if (rc) + return rc; + + /* Check the reset bit was cleared */ + 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; +} |