|
From: openocd-gerrit <ope...@us...> - 2023-06-10 17:00:06
|
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Main OpenOCD repository".
The branch, master has been updated
via 370bf43fb1a89d4bf3887cba63318e5e1711478b (commit)
from 24b656bff5889350b0c95d791d47e479d9fbd7f9 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 370bf43fb1a89d4bf3887cba63318e5e1711478b
Author: iosabi <io...@pr...>
Date: Thu Apr 9 22:00:58 2020 +0000
flash/nor: add support for NXP QN908x
This patch adds support for the NXP QN908x family of Bluetooth
microcontrollers, such as the QN9080. This chip features a Cortex-M4F
with 512 KiB of flash on all the available versions, although the
documentation suggests that there might be 256 kB versions as well.
The initial support allows to read, erase and write the whole user flash
area. Three new sub-commands under the new "qn908x" command are added
in this patch as well: disable_wdog to disabled the watchdog,
mass_erase to perform a mass erase and allow_brick to allow programming
images that disable the SWD interface.
Disabling the watchdog is required after a "reset halt" in order to run
the CRC algorithm from RAM when verifying the chip. However, this is not
done automatically on probing or other initialization since disabling
the watchdog might interfere with debugging real applications.
The "mass_erase" command allows to erase the whole flash without
probing it, since in some scenarios the chip can be locked such that no
flash or ram can be accessed from the SWD interface, allowing only to
run a mass_erase to be able to flash the program.
The flashing process allows to compute a checksum, similar to the
lpc2000 driver "calc_checksum" but done over a different region of the
memory. This checksum is required to be present for the QN908x
bootloader ROM to boot, and otherwise is useless. As with the lpc2000
design, verification when using "calc_checksum" is expected to fail if
the checksum was not valid in the image being verified.
This was manually tested on a QN9080, including the scan-view,
AddressSanitizer/UBSan and test coverage configurations.
Change-Id: Ibd6d8f3608654294795085fcaaffb448b77cc58b
Co-developed-by: Marian Buschsieweke <mar...@ov...>
Signed-off-by: Marian Buschsieweke <mar...@ov...>
Signed-off-by: iosabi <io...@pr...>
Reviewed-on: https://review.openocd.org/c/openocd/+/5584
Tested-by: jenkins
Reviewed-by: Antonio Borneo <bor...@gm...>
Reviewed-by: Tomas Vanek <va...@fb...>
diff --git a/doc/openocd.texi b/doc/openocd.texi
index 9b485c5e1..832047f0e 100644
--- a/doc/openocd.texi
+++ b/doc/openocd.texi
@@ -7341,6 +7341,116 @@ Note: only Main and Work flash regions support Erase operation.
@end deffn
@end deffn
+@deffn {Flash Driver} {qn908x}
+The NXP QN908x microcontrollers feature a Cortex-M4F with integrated Bluetooth
+LE 5 support and an internal flash of up to 512 KiB. These chips only support
+the SWD interface.
+
+The @var{qn908x} driver uses the internal "Flash Memory Controller" block via
+SWD to erase, program and read the internal flash. This driver does not
+support the ISP (In-System Programming) mode which is an alternate way to
+program the flash via UART, SPI or USB.
+
+The internal flash is 512 KiB in size in all released chips and it starts at
+the address 0x01000000, although it can be mapped to address 0 and it is
+aliased to other addresses. This driver only recognizes the bank starting at
+address 0x01000000.
+
+The internal bootloader stored in ROM is in charge of loading and verifying
+the image from flash, or enter ISP mode. The programmed image must start at
+the beginning of the flash and contain a valid header and a matching CRC32
+checksum. Additionally, the image header contains a "Code Read Protection"
+(CRP) word which indicates whether SWD access is enabled, as well as whether
+ISP mode is enabled. Therefore, it is possible to program an image that
+disables SWD and ISP making it impossible to program another image in the
+future through these interfaces, or even debug the current image. While this is
+a valid use case for production deployments where the chips are locked down, by
+default this driver doesn't allow such images that disable the SWD interface.
+To program such images see the @command{qn908x allow_brick} command.
+
+Apart from the CRP field which is located in the image header, the last page
+of the flash memory contains a "Flash lock and protect" descriptor which allows
+to individually protect each 2 KiB page, as well as disabling SWD access to the
+flash and RAM. If this access is disabled it is not possible to read, erase or
+program individual pages from the SWD interface or even access the read-only
+"Flash information page" with information about the bootloader version and
+flash size. However when this protection is in place, it is still possible to
+mass erase the whole chip and then program a new image, for which you can use
+the @command{qn908x mass_erase}.
+
+Example:
+@example
+flash bank $FLASHNAME qn908x 0x01000000 0 0 0 $TARGETNAME calc_checksum
+@end example
+
+Parameters:
+@itemize
+@item @option{calc_checksum} optional parameter to compute the required
+checksum of the first bytes in the vector table.
+@quotation Note
+If the checksum in the header of your image is invalid and you don't provide the
+@option{calc_checksum} option the boot ROM will not boot your image and it may
+render the flash inaccessible. On the other hand, if you use this option to
+compute the checksum keep in mind that @command{verify_image} will fail on
+those four bytes of the checksum since those bytes in the flash will have the
+updated checksum.
+@end quotation
+@end itemize
+
+@deffn {Command} {qn908x allow_brick}
+Allow the qn908x driver to program images with a "Code Read Protection" byte
+that disables the SWD access. Programming such image will cause OpenOCD to
+not be able to reach the target over SWD anymore after the new image is
+programmed and its configuration takes effect, e.g. after a reboot. After
+executing @command{qn908x allow_brick} these images will be allowed to be
+programmed when writing to the flash.
+@end deffn
+
+@deffn {Command} {qn908x disable_wdog}
+Disable the watchdog timer (WDT) by resetting its CTRL field. The WDT starts
+enabled after a @command{reset halt} and it doesn't run while the target is
+halted. However, the verification process in this driver uses the generic
+Cortex-M verification process which executes a payload in RAM and thus
+requires the watchdog to be disabled before running @command{verify_image}
+after a reset halt or any other condition where the watchdog is running.
+Note that this is not done automatically and you must run this command in
+those scenarios.
+@end deffn
+
+@deffn {Command} {qn908x mass_erase}
+Erases the complete flash using the mass_erase method. Mass erase is only
+allowed if enabled in the Lock Status Register 8 (LOCK_STAT_8) which is read
+from the last sector of the flash on boot. However, this mass_erase lock
+protection can be bypassed and this command does so automatically.
+
+In the same LOCK_STAT_8 the flash and RAM access from SWD can be disabled by
+setting two bits in this register. After a mass_erase, all the bits of the
+flash would be set, making it the default to restrict SWD access to the flash
+and RAM regions. This new after erase LOCK_STAT_8 value only takes effect after
+being read from flash on the next reboot for example. After a mass_erase the
+LOCK_STAT_8 register is changed by the hardware to allow access to flash and
+RAM regardless of the value on flash, but only right after a mass_erase and
+until the next boot. Therefore it is possible to perform a mass_erase, program
+a new image, verify it and then reboot to a valid image that's locked from the
+SWD access.
+
+The @command{qn908x mass_erase} command clears the bits that would be loaded
+from the flash into LOCK_STAT_8 after erasing the whole chip to allow SWD
+access for debugging or re-flashing an image without a mass_erase by default.
+If the image being programmed also programs the last page of the flash with its
+own settings, this mass_erase behavior will interfere with that write since a
+new erase of at least the last page would need to be performed before writing
+to it again. For this reason the optional @option{keep_lock} argument can be
+used to leave the flash and RAM lock set. For development environments, the
+default behavior is desired.
+
+The mass erase locking mechanism is independent from the individual page
+locking bits, so it is possible that you can't erase a given page that is
+locked and you can't unprotect that page because the locking bits are also
+locked, but can still mass erase the whole flash.
+@end deffn
+@end deffn
+
@deffn {Flash Driver} {rp2040}
Supports RP2040 "Raspberry Pi Pico" microcontroller.
RP2040 is a dual-core device with two CM0+ cores. Both cores share the same
diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am
index f04f0d206..534a7a804 100644
--- a/src/flash/nor/Makefile.am
+++ b/src/flash/nor/Makefile.am
@@ -54,6 +54,7 @@ NOR_DRIVERS = \
%D%/psoc4.c \
%D%/psoc5lp.c \
%D%/psoc6.c \
+ %D%/qn908x.c \
%D%/renesas_rpchf.c \
%D%/rp2040.c \
%D%/rsl10.c \
diff --git a/src/flash/nor/driver.h b/src/flash/nor/driver.h
index 889a811e3..a63b72c8f 100644
--- a/src/flash/nor/driver.h
+++ b/src/flash/nor/driver.h
@@ -284,6 +284,7 @@ extern const struct flash_driver psoc5lp_eeprom_flash;
extern const struct flash_driver psoc5lp_flash;
extern const struct flash_driver psoc5lp_nvl_flash;
extern const struct flash_driver psoc6_flash;
+extern const struct flash_driver qn908x_flash;
extern const struct flash_driver renesas_rpchf_flash;
extern const struct flash_driver rp2040_flash;
extern const struct flash_driver rsl10_flash;
diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c
index 92476987e..3157bd329 100644
--- a/src/flash/nor/drivers.c
+++ b/src/flash/nor/drivers.c
@@ -61,6 +61,7 @@ static const struct flash_driver * const flash_drivers[] = {
&psoc5lp_eeprom_flash,
&psoc5lp_nvl_flash,
&psoc6_flash,
+ &qn908x_flash,
&renesas_rpchf_flash,
&rp2040_flash,
&sh_qspi_flash,
diff --git a/src/flash/nor/qn908x.c b/src/flash/nor/qn908x.c
new file mode 100644
index 000000000..8cd7a2f04
--- /dev/null
+++ b/src/flash/nor/qn908x.c
@@ -0,0 +1,1197 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/***************************************************************************
+ * Copyright (C) 2020 iosabi *
+ * iosabi <io...@pr...> *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "imp.h"
+
+#include <helper/binarybuffer.h>
+#include <helper/bits.h>
+#include <helper/crc32.h>
+#include <helper/time_support.h>
+#include <helper/types.h>
+
+/* The QN908x has two flash regions, one is the main flash region holding the
+ * user code and the second one is a small (0x800 bytes) "Flash information
+ * page" that can't be written to by the user. This page contains information
+ * programmed at the factory.
+ *
+ * The main flash region is normally 512 KiB, there's a field in the "Flash
+ * information page" that allows to specify for 256 KiB size chips. However, at
+ * the time of writing, none of the variants in the market have 256 KiB.
+ *
+ * The flash is divided into blocks of 256 KiB each, therefore containing two
+ * blocks. A block is subdivided into pages, of 2048 bytes. A page is the
+ * smallest region that can be erased or protected independently, although it
+ * is also possible to erase a whole block or both blocks. A page is subdivided
+ * into 8 rows of 64 words (32-bit words). The word subdivision is only
+ * relevant because DMA can write multiple words in the same row in the same
+ * flash program operation.
+ *
+ * For the Flash information page we are only interested in the last
+ * 0x100 bytes which contain a CRC-32 checksum of that 0x100 bytes long region
+ * and a field stating the size of the flash. This is also a good check that
+ * we are dealing with the right chip/flash configuration and is used in the
+ * probe() function.
+ */
+#define QN908X_FLASH_BASE 0x01000000
+
+#define QN908X_FLASH_PAGE_SIZE 2048
+#define QN908X_FLASH_PAGES_PER_BLOCK 128
+#define QN908X_FLASH_MAX_BLOCKS 2
+#define QN908X_FLASH_BLOCK_SIZE \
+ (QN908X_FLASH_PAGES_PER_BLOCK * QN908X_FLASH_PAGE_SIZE)
+#define QN908X_FLASH_IRQ_VECTOR_CHECKSUM_POS 0x1c
+#define QN908X_FLASH_IRQ_VECTOR_CHECKSUM_SIZE 4
+#define QN908X_FLASH_IRQ_VECTOR_CHECKSUM_END \
+ (QN908X_FLASH_IRQ_VECTOR_CHECKSUM_POS + QN908X_FLASH_IRQ_VECTOR_CHECKSUM_SIZE)
+
+
+/* Flash information page memory fields. */
+#define QN908X_INFO_PAGE_BASE 0x210b0000u
+#define QN908X_INFO_PAGE_CRC32 (QN908X_INFO_PAGE_BASE + 0x700)
+#define QN908X_INFO_PAGE_CRC_START (QN908X_INFO_PAGE_BASE + 0x704)
+#define QN908X_INFO_PAGE_BOOTLOADER_VER (QN908X_INFO_PAGE_BASE + 0x704)
+#define QN908X_INFO_PAGE_FLASH_SIZE (QN908X_INFO_PAGE_BASE + 0x708)
+#define QN908X_INFO_PAGE_BLUETOOTH_ADDR (QN908X_INFO_PAGE_BASE + 0x7fa)
+#define QN908X_INFO_PAGE_CRC_END (QN908X_INFO_PAGE_BASE + 0x800)
+
+
+/* Possible values of the QN908X_INFO_PAGE_FLASH_SIZE field. */
+enum qn908x_info_page_flash_size {
+ QN908X_FLASH_SIZE_512K = 0xfffff0ff,
+ QN908X_FLASH_SIZE_256K = 0xffffe0ff,
+};
+
+/* QN908x "Flash memory controller", described in section 28 of the user
+ * manual. In the NXP SDK this peripheral is called "FLASH", however we use the
+ * name "FMC" (Flash Memory Controller) here when referring to the controller
+ * to avoid confusion with other "flash" terms in OpenOCD. */
+#define QN908X_FMC_BASE 0x40081000u
+#define QN908X_FMC_INI_RD_EN (QN908X_FMC_BASE + 0x00)
+#define QN908X_FMC_ERASE_CTRL (QN908X_FMC_BASE + 0x04)
+#define QN908X_FMC_ERASE_TIME (QN908X_FMC_BASE + 0x08)
+#define QN908X_FMC_TIME_CTRL (QN908X_FMC_BASE + 0x0c)
+#define QN908X_FMC_SMART_CTRL (QN908X_FMC_BASE + 0x10)
+#define QN908X_FMC_INT_STAT (QN908X_FMC_BASE + 0x18)
+#define QN908X_FMC_LOCK_STAT_0 (QN908X_FMC_BASE + 0x20)
+#define QN908X_FMC_LOCK_STAT_1 (QN908X_FMC_BASE + 0x24)
+#define QN908X_FMC_LOCK_STAT_2 (QN908X_FMC_BASE + 0x28)
+#define QN908X_FMC_LOCK_STAT_3 (QN908X_FMC_BASE + 0x2c)
+#define QN908X_FMC_LOCK_STAT_4 (QN908X_FMC_BASE + 0x30)
+#define QN908X_FMC_LOCK_STAT_5 (QN908X_FMC_BASE + 0x34)
+#define QN908X_FMC_LOCK_STAT_6 (QN908X_FMC_BASE + 0x38)
+#define QN908X_FMC_LOCK_STAT_7 (QN908X_FMC_BASE + 0x3c)
+#define QN908X_FMC_LOCK_STAT_8 (QN908X_FMC_BASE + 0x40)
+#define QN908X_FMC_STATUS1 (QN908X_FMC_BASE + 0x48)
+#define QN908X_FMC_DEBUG_PASSWORD (QN908X_FMC_BASE + 0xa8)
+#define QN908X_FMC_ERASE_PASSWORD (QN908X_FMC_BASE + 0xac)
+
+#define QN908X_FMC_INI_RD_EN_INI_RD_EN_MASK BIT(0)
+
+#define QN908X_FMC_STATUS1_FSH_ERA_BUSY_L_MASK BIT(9)
+#define QN908X_FMC_STATUS1_FSH_WR_BUSY_L_MASK BIT(10)
+#define QN908X_FMC_STATUS1_FSH_ERA_BUSY_H_MASK BIT(12)
+#define QN908X_FMC_STATUS1_FSH_WR_BUSY_H_MASK BIT(13)
+#define QN908X_FMC_STATUS1_INI_RD_DONE_MASK BIT(15)
+#define QN908X_FMC_STATUS1_FSH_STA_MASK BIT(26)
+
+#define QN908X_FMC_ERASE_CTRL_PAGE_IDXL_SHIFT 0
+#define QN908X_FMC_ERASE_CTRL_PAGE_IDXH_SHIFT 8
+#define QN908X_FMC_ERASE_CTRL_HALF_ERASEL_EN_SHIFT 28
+#define QN908X_FMC_ERASE_CTRL_HALF_ERASEH_EN_SHIFT 29
+#define QN908X_FMC_ERASE_CTRL_PAGE_ERASEL_EN_SHIFT 30
+#define QN908X_FMC_ERASE_CTRL_PAGE_ERASEH_EN_SHIFT 31
+
+#define QN908X_FMC_INT_STAT_AHBL_INT_MASK BIT(0)
+#define QN908X_FMC_INT_STAT_LOCKL_INT_MASK BIT(1)
+#define QN908X_FMC_INT_STAT_ERASEL_INT_MASK BIT(2)
+#define QN908X_FMC_INT_STAT_WRITEL_INT_MASK BIT(3)
+#define QN908X_FMC_INT_STAT_WR_BUFL_INT_MASK BIT(4)
+#define QN908X_FMC_INT_STAT_WRITE_FAIL_L_INT_MASK BIT(5)
+#define QN908X_FMC_INT_STAT_ERASE_FAIL_L_INT_MASK BIT(6)
+#define QN908X_FMC_INT_STAT_AHBH_INT_MASK BIT(8)
+#define QN908X_FMC_INT_STAT_LOCKH_INT_MASK BIT(9)
+#define QN908X_FMC_INT_STAT_ERASEH_INT_MASK BIT(10)
+#define QN908X_FMC_INT_STAT_WRITEH_INT_MASK BIT(11)
+#define QN908X_FMC_INT_STAT_WR_BUFH_INT_MASK BIT(12)
+#define QN908X_FMC_INT_STAT_WRITE_FAIL_H_INT_MASK BIT(13)
+#define QN908X_FMC_INT_STAT_ERASE_FAIL_H_INT_MASK BIT(14)
+
+#define QN908X_FMC_SMART_CTRL_PRGML_EN_MASK BIT(0)
+#define QN908X_FMC_SMART_CTRL_PRGMH_EN_MASK BIT(1)
+#define QN908X_FMC_SMART_CTRL_SMART_WRITEL_EN_MASK BIT(2)
+#define QN908X_FMC_SMART_CTRL_SMART_WRITEH_EN_MASK BIT(3)
+#define QN908X_FMC_SMART_CTRL_SMART_ERASEL_EN_MASK BIT(4)
+#define QN908X_FMC_SMART_CTRL_SMART_ERASEH_EN_MASK BIT(5)
+#define QN908X_FMC_SMART_CTRL_MAX_WRITE_MASK 0xf00u
+#define QN908X_FMC_SMART_CTRL_MAX_WRITE_SHIFT 8u
+#define QN908X_FMC_SMART_CTRL_MAX_WRITE(x) \
+ (((uint32_t)(((uint32_t)(x)) << QN908X_FMC_SMART_CTRL_MAX_WRITE_SHIFT)) \
+ & QN908X_FMC_SMART_CTRL_MAX_WRITE_MASK)
+#define QN908X_FMC_SMART_CTRL_MAX_ERASE_MASK 0x3f000u
+#define QN908X_FMC_SMART_CTRL_MAX_ERASE_SHIFT 12u
+#define QN908X_FMC_SMART_CTRL_MAX_ERASE(x) \
+ (((uint32_t)(((uint32_t)(x)) << QN908X_FMC_SMART_CTRL_MAX_ERASE_SHIFT)) \
+ & QN908X_FMC_SMART_CTRL_MAX_ERASE_MASK)
+
+#define QN908X_FMC_SMART_CTRL_MAX_ERASE_RETRIES 9
+#define QN908X_FMC_SMART_CTRL_MAX_WRITE_RETRIES 9
+
+#define QN908X_FMC_TIME_CTRL_PRGM_CYCLE_MASK 0xfffu
+#define QN908X_FMC_TIME_CTRL_PRGM_CYCLE_SHIFT 0u
+#define QN908X_FMC_TIME_CTRL_PRGM_CYCLE(x) \
+ (((uint32_t)(((uint32_t)(x)) << QN908X_FMC_TIME_CTRL_PRGM_CYCLE_SHIFT)) \
+ & QN908X_FMC_TIME_CTRL_PRGM_CYCLE_MASK)
+#define QN908X_FMC_TIME_CTRL_TIME_BASE_MASK 0xff000u
+#define QN908X_FMC_TIME_CTRL_TIME_BASE_SHIFT 12u
+#define QN908X_FMC_TIME_CTRL_TIME_BASE(x) \
+ (((uint32_t)(((uint32_t)(x)) << QN908X_FMC_TIME_CTRL_TIME_BASE_SHIFT)) \
+ & QN908X_FMC_TIME_CTRL_TIME_BASE_MASK)
+
+#define QN908X_FMC_LOCK_STAT_8_MASS_ERASE_LOCK_EN BIT(0)
+#define QN908X_FMC_LOCK_STAT_8_FSH_PROTECT_EN BIT(1)
+#define QN908X_FMC_LOCK_STAT_8_MEM_PROTECT_EN BIT(2)
+#define QN908X_FMC_LOCK_STAT_8_PROTECT_ANY (BIT(1) | BIT(2))
+
+/* See Table 418 "Flash lock and protect description" in the user manual */
+#define QN908X_FLASH_LOCK_ADDR (QN908X_FLASH_BASE + 0x7f820)
+/* Allow mass erase */
+#define QN908X_FLASH_LOCK_ENABLE_MASS_ERASE BIT(0)
+/* disallow flash access from SWD */
+#define QN908X_FLASH_LOCK_ENABLE_FLASH_PROTECTION BIT(1)
+/* disallow SRAM access from SWD */
+#define QN908X_FLASH_LOCK_ENABLE_MEMORY_PROTECTION BIT(2)
+
+/* Page lock information located at the beginning of the last page. */
+struct qn908x_flash_page_lock {
+ uint8_t bits[QN908X_FLASH_MAX_BLOCKS * QN908X_FLASH_PAGES_PER_BLOCK / 8];
+ uint8_t protection;
+ uint8_t _reserved[3];
+ /* nvds_size is unused here, but we need to preserve it across erases
+ * when locking and unlocking pages. */
+ uint8_t nvds_size[4];
+} __attribute__ ((packed));
+
+/* Clock configuration is stored in the SYSCON. */
+#define QN908X_SYSCON_BASE 0x40000000u
+#define QN908X_SYSCON_CLK_EN (QN908X_SYSCON_BASE + 0x00cu)
+#define QN908X_SYSCON_CLK_CTRL (QN908X_SYSCON_BASE + 0x010u)
+#define QN908X_SYSCON_CHIP_ID (QN908X_SYSCON_BASE + 0x108u)
+#define QN908X_SYSCON_XTAL_CTRL (QN908X_SYSCON_BASE + 0x180u)
+
+/* Internal 16MHz / 8MHz clock used by the erase operation. */
+#define QN908X_SYSCON_CLK_EN_CLK_DP_EN_MASK BIT(21)
+
+#define SYSCON_XTAL_CTRL_XTAL_DIV_MASK BIT(31)
+
+#define SYSCON_CLK_CTRL_AHB_DIV_MASK 0x1FFF0u
+#define SYSCON_CLK_CTRL_AHB_DIV_SHIFT 4u
+#define SYSCON_CLK_CTRL_CLK_XTAL_SEL_MASK BIT(19)
+#define SYSCON_CLK_CTRL_CLK_OSC32M_DIV_MASK BIT(20)
+#define SYSCON_CLK_CTRL_SYS_CLK_SEL_MASK 0xC0000000u
+#define SYSCON_CLK_CTRL_SYS_CLK_SEL_SHIFT 30u
+
+#define CLOCK_16MHZ 16000000u
+#define CLOCK_32MHZ 32000000u
+#define CLOCK_32KHZ 32000u
+
+/* Watchdog block registers */
+#define QN908X_WDT_BASE 0x40001000u
+#define QN908X_WDT_CTRL (QN908X_WDT_BASE + 0x08u)
+#define QN908X_WDT_LOCK (QN908X_WDT_BASE + 0x20u)
+
+struct qn908x_flash_bank {
+ /* The number of flash blocks. Initially set to zero until the flash
+ * is probed. This determines the size of the flash. */
+ unsigned int num_blocks;
+
+ unsigned int user_bank_size;
+ bool calc_checksum;
+
+ /* Whether we allow to flash an image that disables SWD access, potentially
+ * bricking the device since the image can't be reflashed from SWD. */
+ bool allow_swd_disabled;
+
+ bool page_lock_loaded;
+ struct qn908x_flash_page_lock page_lock;
+};
+
+/* 500 ms timeout. */
+#define QN908X_DEFAULT_TIMEOUT_MS 500
+
+/* Forward declaration of commands. */
+static int qn908x_probe(struct flash_bank *bank);
+static int qn908x_write(struct flash_bank *bank, const uint8_t *buffer,
+ uint32_t offset, uint32_t count);
+
+/* Update the value of a register with a mask. This helper allows to read a
+ * register, modify a subset of the bits and write back the value, which is a
+ * common operation when modifying only a bit filed in a register. */
+static int qn908x_update_reg(struct target *target, target_addr_t reg,
+ uint32_t mask, uint32_t value)
+{
+ uint32_t orig_value = 0;
+ uint32_t new_value;
+ int retval;
+ if (mask != 0xffffffff) {
+ /* No need to read the old value if we request a mask of 32 bits. */
+ retval = target_read_u32(target, reg, &orig_value);
+ if (retval != ERROR_OK) {
+ LOG_DEBUG("Error reading reg at " TARGET_ADDR_FMT
+ ": %d", reg, retval);
+ return retval;
+ }
+ }
+ new_value = (orig_value & ~mask) | (value & mask);
+ retval = target_write_u32(target, reg, new_value);
+ if (retval != ERROR_OK) {
+ LOG_DEBUG("Error writing reg at " TARGET_ADDR_FMT " with 0x%08"
+ PRIx32 ": %d", reg, new_value, retval);
+ return retval;
+ }
+ if (mask == 0xffffffff) {
+ LOG_DEBUG("Updated reg at " TARGET_ADDR_FMT ": ?? -> 0x%.08"
+ PRIx32 "", reg, new_value);
+ } else {
+ LOG_DEBUG("Updated reg at " TARGET_ADDR_FMT ": 0x%.08" PRIx32
+ " -> 0x%.08" PRIx32, reg, orig_value, new_value);
+ }
+ return ERROR_OK;
+}
+
+/* Load lock bit and protection bit and load redundancy page info.
+ * This populates the LOCK_STAT_n registers with the values from the lock page,
+ * making protection bit changes to the last page effective. */
+static int qn908x_load_lock_stat(struct target *target)
+{
+ int retval = target_write_u32(target, QN908X_FMC_INI_RD_EN,
+ QN908X_FMC_INI_RD_EN_INI_RD_EN_MASK);
+ if (retval != ERROR_OK)
+ return retval;
+
+ uint32_t status1;
+ const uint32_t status_mask = QN908X_FMC_STATUS1_FSH_STA_MASK
+ | QN908X_FMC_STATUS1_INI_RD_DONE_MASK;
+ do {
+ retval = target_read_u32(target, QN908X_FMC_STATUS1, &status1);
+ if (retval != ERROR_OK)
+ return retval;
+ } while ((status1 & status_mask) != QN908X_FMC_STATUS1_INI_RD_DONE_MASK);
+
+ for (int i = 0; i <= 8; i++) {
+ uint32_t addr = QN908X_FMC_LOCK_STAT_0 + i * 4;
+ uint32_t lock_stat;
+ if (target_read_u32(target, addr, &lock_stat) == ERROR_OK)
+ LOG_DEBUG("LOCK_STAT_%d = 0x%08" PRIx32, i, lock_stat);
+ }
+ return ERROR_OK;
+}
+
+/* Initializes the FMC controller registers for allowing writing. */
+static int qn908x_init_flash(struct target *target)
+{
+ /* Determine the current clock configuration. */
+ uint32_t clk_ctrl;
+ int retval = target_read_u32(target, QN908X_SYSCON_CLK_CTRL, &clk_ctrl);
+ if (retval != ERROR_OK)
+ return retval;
+
+ uint32_t clk_sel = (clk_ctrl & SYSCON_CLK_CTRL_SYS_CLK_SEL_MASK)
+ >> SYSCON_CLK_CTRL_SYS_CLK_SEL_SHIFT;
+ LOG_DEBUG("Clock clk_sel=0x%08" PRIu32, clk_sel);
+
+ /* Core clock frequency. */
+ uint32_t core_freq = 0;
+ switch (clk_sel) {
+ case 0: /* RCO 32 MHz */
+ core_freq = (clk_ctrl & SYSCON_CLK_CTRL_CLK_OSC32M_DIV_MASK) ?
+ CLOCK_16MHZ : CLOCK_32MHZ;
+ break;
+ case 1: /* Xin frequency */
+ {
+ uint32_t clk_xtal;
+ retval = target_read_u32(target, QN908X_SYSCON_XTAL_CTRL, &clk_xtal);
+ if (retval != ERROR_OK)
+ return retval;
+ core_freq = (clk_ctrl & SYSCON_CLK_CTRL_CLK_XTAL_SEL_MASK)
+ && (clk_xtal & SYSCON_XTAL_CTRL_XTAL_DIV_MASK)
+ ? CLOCK_32MHZ : CLOCK_16MHZ;
+ }
+ break;
+ case 2: /* 32 Kz */
+ core_freq = CLOCK_32KHZ;
+ break;
+ default:
+ return ERROR_FAIL;
+ }
+
+ uint32_t ahb_div = (clk_ctrl & SYSCON_CLK_CTRL_AHB_DIV_MASK)
+ >> SYSCON_CLK_CTRL_AHB_DIV_SHIFT;
+ uint32_t ahb_freq = core_freq / (ahb_div + 1);
+
+ LOG_DEBUG("Core freq: %" PRIu32 " Hz | AHB freq: %" PRIu32 " Hz",
+ core_freq, ahb_freq);
+
+ /* TIME_BASE is 2uS at the current AHB clock speed. */
+ retval = target_write_u32(target, QN908X_FMC_TIME_CTRL,
+ QN908X_FMC_TIME_CTRL_TIME_BASE(2 * ahb_freq / 1000000) |
+ QN908X_FMC_TIME_CTRL_PRGM_CYCLE(30));
+ if (retval != ERROR_OK)
+ return retval;
+
+ return qn908x_load_lock_stat(target);
+}
+
+/* flash bank qn908x <base> <size> 0 0 <target#> [calc_checksum] */
+FLASH_BANK_COMMAND_HANDLER(qn908x_flash_bank_command)
+{
+ struct qn908x_flash_bank *qn908x_info;
+
+ if (CMD_ARGC < 6 || CMD_ARGC > 7)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ if (bank->base != QN908X_FLASH_BASE) {
+ LOG_ERROR("Address " TARGET_ADDR_FMT
+ " is an invalid bank address (try 0x%08" PRIx32 ")",
+ bank->base, QN908X_FLASH_BASE);
+ return ERROR_COMMAND_ARGUMENT_INVALID;
+ }
+
+ qn908x_info = malloc(sizeof(struct qn908x_flash_bank));
+
+ if (!qn908x_info)
+ return ERROR_FAIL;
+
+ bank->driver_priv = qn908x_info;
+ qn908x_info->num_blocks = 0;
+ qn908x_info->user_bank_size = bank->size;
+ qn908x_info->page_lock_loaded = false;
+ qn908x_info->allow_swd_disabled = false;
+
+ qn908x_info->calc_checksum = false;
+ if (CMD_ARGC == 7) {
+ if (strcmp(CMD_ARGV[6], "calc_checksum")) {
+ free(qn908x_info);
+ return ERROR_COMMAND_ARGUMENT_INVALID;
+ }
+ qn908x_info->calc_checksum = true;
+ }
+
+ return ERROR_OK;
+}
+
+static int qn908x_read_page_lock(struct flash_bank *bank)
+{
+ struct qn908x_flash_bank *qn908x_info = bank->driver_priv;
+
+ if (bank->target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ /* The last page of the flash contains the "Flash lock and protect"
+ * information. It is not clear where this is located on chips with only
+ * one block. */
+ uint32_t prot_offset = qn908x_info->num_blocks * QN908X_FLASH_BLOCK_SIZE
+ - QN908X_FLASH_PAGE_SIZE;
+
+ int retval = target_read_memory(bank->target, bank->base + prot_offset, 4,
+ sizeof(qn908x_info->page_lock) / 4,
+ (void *)(&qn908x_info->page_lock));
+ if (retval != ERROR_OK)
+ return retval;
+ LOG_DEBUG("Flash protection = 0x%02" PRIx8,
+ qn908x_info->page_lock.protection);
+
+ qn908x_info->page_lock_loaded = true;
+ return ERROR_OK;
+}
+
+static int qn908x_busy_check(struct target *target)
+{
+ uint32_t status1;
+ int retval = target_read_u32(target, QN908X_FMC_STATUS1, &status1);
+ if (retval != ERROR_OK)
+ return retval;
+
+ if ((status1 & (QN908X_FMC_STATUS1_FSH_ERA_BUSY_L_MASK
+ | QN908X_FMC_STATUS1_FSH_WR_BUSY_L_MASK
+ | QN908X_FMC_STATUS1_FSH_ERA_BUSY_H_MASK
+ | QN908X_FMC_STATUS1_FSH_WR_BUSY_H_MASK)))
+ return ERROR_FLASH_BUSY;
+ return ERROR_OK;
+}
+
+static int qn908x_status_check(struct target *target)
+{
+ uint32_t int_stat;
+ int retval = target_read_u32(target, QN908X_FMC_INT_STAT, &int_stat);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* The error bits for block 0 and block 1 have the exact same layout, only
+ * that block 1 error bits are shifted by 8 bits. We use this fact to
+ * loop over the blocks */
+ for (unsigned int block = 0; block <= 1; block++) {
+ unsigned int shift = (block) ? 8 : 0;
+ if (int_stat & (QN908X_FMC_INT_STAT_AHBL_INT_MASK << shift)) {
+ LOG_ERROR("AHB error on block %u", block);
+ return ERROR_FAIL;
+ }
+
+ if (int_stat & (QN908X_FMC_INT_STAT_LOCKL_INT_MASK << shift)) {
+ LOG_ERROR("Locked page being accessed error on block %u", block);
+ return ERROR_FAIL;
+ }
+
+ if (int_stat & (QN908X_FMC_INT_STAT_WRITE_FAIL_L_INT_MASK << shift)) {
+ LOG_ERROR("Smart write on block %u failed", block);
+ return ERROR_FAIL;
+ }
+
+ if ((int_stat & (QN908X_FMC_INT_STAT_ERASE_FAIL_L_INT_MASK << shift))
+ || (int_stat & (QN908X_FMC_INT_STAT_ERASE_FAIL_H_INT_MASK << shift))) {
+ LOG_ERROR("Smart erase on block %u failed", block);
+ return ERROR_FAIL;
+ }
+ }
+
+ return ERROR_OK;
+}
+
+static int qn908x_wait_for_idle(struct target *target, int64_t timeout_ms)
+{
+ int64_t ms_start = timeval_ms();
+
+ int busy = ERROR_FLASH_BUSY;
+ while (busy != ERROR_OK) {
+ busy = qn908x_busy_check(target);
+ if (busy != ERROR_OK && busy != ERROR_FLASH_BUSY)
+ return busy;
+ if (timeval_ms() - ms_start > timeout_ms) {
+ LOG_ERROR("Timeout waiting to be idle.");
+ return ERROR_TIMEOUT_REACHED;
+ }
+ }
+ return ERROR_OK;
+}
+
+/* Set up the chip to perform an erase (page or block) operation. */
+static int qn908x_setup_erase(struct target *target)
+{
+ int retval;
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ /* Enable 8MHz clock. */
+ retval = qn908x_update_reg(target, QN908X_SYSCON_CLK_EN,
+ QN908X_SYSCON_CLK_EN_CLK_DP_EN_MASK,
+ QN908X_SYSCON_CLK_EN_CLK_DP_EN_MASK);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Set ERASE_TIME to 2ms for smart erase. */
+ retval = qn908x_update_reg(target, QN908X_FMC_ERASE_TIME,
+ (1u << 20) - 1,
+ 2000 * 8); /* 2000 uS * 8 MHz = x cycles */
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Set up smart erase. SWD can only perform smart erase. */
+ uint32_t ctrl_val = QN908X_FMC_SMART_CTRL_SMART_ERASEH_EN_MASK
+ | QN908X_FMC_SMART_CTRL_SMART_ERASEL_EN_MASK
+ | QN908X_FMC_SMART_CTRL_MAX_ERASE(QN908X_FMC_SMART_CTRL_MAX_ERASE_RETRIES)
+ | QN908X_FMC_SMART_CTRL_MAX_WRITE(QN908X_FMC_SMART_CTRL_MAX_WRITE_RETRIES);
+ retval = targ...
[truncated message content] |