| // 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; | |
| FIELD_MODIFY(ZL_WRITE_FLASH_OP, &value, 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; | |
| } |