aboutsummaryrefslogtreecommitdiff
path: root/src/flash
diff options
context:
space:
mode:
authorTim Newsome <tim@sifive.com>2023-10-27 09:00:59 -0700
committerTim Newsome <tim@sifive.com>2023-10-27 09:00:59 -0700
commitf02fe0960cca39024afbb6edbf27490cee3afb1d (patch)
tree3427e34aa27e2340274f3d2855f697f70d492071 /src/flash
parent89260a5f1f76ef9490d602b004a862bd14681df5 (diff)
parent9f23a1d7c1e27c556ef9787b9d3f263f5c1ecf24 (diff)
downloadriscv-openocd-f02fe0960cca39024afbb6edbf27490cee3afb1d.zip
riscv-openocd-f02fe0960cca39024afbb6edbf27490cee3afb1d.tar.gz
riscv-openocd-f02fe0960cca39024afbb6edbf27490cee3afb1d.tar.bz2
Merge commit '9f23a1d7c1e27c556ef9787b9d3f263f5c1ecf24' into from_upstream
Conflicts: HACKING src/target/riscv/riscv-013.c Change-Id: I43ccb143cae8daa39212d66a8824ae3ad2af6fef
Diffstat (limited to 'src/flash')
-rw-r--r--src/flash/nand/core.h3
-rw-r--r--src/flash/nand/lpc32xx.c2
-rw-r--r--src/flash/nand/tcl.c3
-rw-r--r--src/flash/nor/Makefile.am1
-rw-r--r--src/flash/nor/core.h13
-rw-r--r--src/flash/nor/driver.h1
-rw-r--r--src/flash/nor/drivers.c1
-rw-r--r--src/flash/nor/jtagspi.c79
-rw-r--r--src/flash/nor/psoc4.c2
-rw-r--r--src/flash/nor/qn908x.c1197
-rw-r--r--src/flash/nor/rp2040.c4
-rw-r--r--src/flash/nor/stm32f2x.c8
-rw-r--r--src/flash/nor/stm32l4x.c43
-rw-r--r--src/flash/nor/stm32l4x.h2
-rw-r--r--src/flash/nor/tcl.c6
15 files changed, 1311 insertions, 54 deletions
diff --git a/src/flash/nand/core.h b/src/flash/nand/core.h
index 19c53d1..137298c 100644
--- a/src/flash/nand/core.h
+++ b/src/flash/nand/core.h
@@ -179,6 +179,7 @@ enum oob_formats {
NAND_OOB_YAFFS2 = 0x100,/* when writing, use YAFFS2 OOB layout */
};
+extern struct nand_device *nand_devices;
struct nand_device *get_nand_device_by_num(int num);
@@ -202,6 +203,8 @@ int nand_calculate_ecc(struct nand_device *nand,
const uint8_t *dat, uint8_t *ecc_code);
int nand_calculate_ecc_kw(struct nand_device *nand,
const uint8_t *dat, uint8_t *ecc_code);
+int nand_correct_data(struct nand_device *nand, u_char *dat,
+ u_char *read_ecc, u_char *calc_ecc);
int nand_register_commands(struct command_context *cmd_ctx);
diff --git a/src/flash/nand/lpc32xx.c b/src/flash/nand/lpc32xx.c
index f8b59b3..1fdae9f 100644
--- a/src/flash/nand/lpc32xx.c
+++ b/src/flash/nand/lpc32xx.c
@@ -24,8 +24,6 @@
static int lpc32xx_reset(struct nand_device *nand);
static int lpc32xx_controller_ready(struct nand_device *nand, int timeout);
static int lpc32xx_tc_ready(struct nand_device *nand, int timeout);
-extern int nand_correct_data(struct nand_device *nand, u_char *dat,
- u_char *read_ecc, u_char *calc_ecc);
/* These are offset with the working area in IRAM when using DMA to
* read/write data to the SLC controller.
diff --git a/src/flash/nand/tcl.c b/src/flash/nand/tcl.c
index 4bb15fa..67a6277 100644
--- a/src/flash/nand/tcl.c
+++ b/src/flash/nand/tcl.c
@@ -17,9 +17,6 @@
#include "fileio.h"
#include <target/target.h>
-/* to be removed */
-extern struct nand_device *nand_devices;
-
COMMAND_HANDLER(handle_nand_list_command)
{
struct nand_device *p;
diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am
index f04f0d2..534a7a8 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/core.h b/src/flash/nor/core.h
index 8c26ba0..80911f7 100644
--- a/src/flash/nor/core.h
+++ b/src/flash/nor/core.h
@@ -253,6 +253,19 @@ int get_flash_bank_by_num(unsigned int num, struct flash_bank **bank);
COMMAND_HELPER(flash_command_get_bank, unsigned name_index,
struct flash_bank **bank);
/**
+ * Retrieves @a bank from a command argument, reporting errors parsing
+ * the bank identifier or retrieving the specified bank. The bank
+ * may be identified by its bank number or by @c name.instance, where
+ * @a instance is driver-specific.
+ * @param name_index The index to the string in args containing the
+ * bank identifier.
+ * @param bank On output, contains a pointer to the bank or NULL.
+ * @param do_probe Does auto-probing when set, otherwise without probing.
+ * @returns ERROR_OK on success, or an error indicating the problem.
+ */
+COMMAND_HELPER(flash_command_get_bank_probe_optional, unsigned int name_index,
+ struct flash_bank **bank, bool do_probe);
+/**
* Returns the flash bank like get_flash_bank_by_num(), without probing.
* @param num The flash bank number.
* @returns A struct flash_bank for flash bank @a num, or NULL.
diff --git a/src/flash/nor/driver.h b/src/flash/nor/driver.h
index fcb9ea7..514fe8a 100644
--- a/src/flash/nor/driver.h
+++ b/src/flash/nor/driver.h
@@ -285,6 +285,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 a0e135f..02b1511 100644
--- a/src/flash/nor/drivers.c
+++ b/src/flash/nor/drivers.c
@@ -62,6 +62,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/jtagspi.c b/src/flash/nor/jtagspi.c
index c176ca8..6bb3af9 100644
--- a/src/flash/nor/jtagspi.c
+++ b/src/flash/nor/jtagspi.c
@@ -41,7 +41,11 @@ FLASH_BANK_COMMAND_HANDLER(jtagspi_flash_bank_command)
bank->sectors = NULL;
bank->driver_priv = info;
- info->tap = NULL;
+ if (!bank->target->tap) {
+ LOG_ERROR("Target has no JTAG tap");
+ return ERROR_FAIL;
+ }
+ info->tap = bank->target->tap;
info->probed = false;
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[6], info->ir);
@@ -161,7 +165,12 @@ COMMAND_HANDLER(jtagspi_handle_set)
return ERROR_COMMAND_SYNTAX_ERROR;
}
- retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
+ /* calling flash_command_get_bank without probing because handle_set is used
+ to set device parameters if not autodetected. So probing would fail
+ anyhow.
+ */
+ retval = CALL_COMMAND_HANDLER(flash_command_get_bank_probe_optional, 0,
+ &bank, false);
if (ERROR_OK != retval)
return retval;
info = bank->driver_priv;
@@ -292,52 +301,50 @@ COMMAND_HANDLER(jtagspi_handle_set)
COMMAND_HANDLER(jtagspi_handle_cmd)
{
struct flash_bank *bank;
- unsigned int index = 1;
- const int max = 21;
- uint8_t num_write, num_read, write_buffer[max], read_buffer[1 << CHAR_BIT];
- uint8_t data, *ptr;
- char temp[4], output[(2 + max + (1 << CHAR_BIT)) * 3 + 8];
- int retval;
+ const unsigned int max = 20;
+ uint8_t cmd_byte, num_read, write_buffer[max], read_buffer[1 << CHAR_BIT];
LOG_DEBUG("%s", __func__);
- if (CMD_ARGC < 3) {
- command_print(CMD, "jtagspi: not enough arguments");
+ if (CMD_ARGC < 3)
return ERROR_COMMAND_SYNTAX_ERROR;
- }
- num_write = CMD_ARGC - 2;
+ uint8_t num_write = CMD_ARGC - 3;
if (num_write > max) {
- LOG_ERROR("at most %d bytes may be send", max);
- return ERROR_COMMAND_SYNTAX_ERROR;
+ command_print(CMD, "at most %d bytes may be send", max);
+ return ERROR_COMMAND_ARGUMENT_INVALID;
}
- retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
- if (ERROR_OK != retval)
+ /* calling flash_command_get_bank without probing because we like to be
+ able to send commands before auto-probing occurred. For example sending
+ "release from power down" is needed before probing when flash is in
+ power down mode.
+ */
+ int retval = CALL_COMMAND_HANDLER(flash_command_get_bank_probe_optional, 0,
+ &bank, false);
+ if (retval != ERROR_OK)
return retval;
- COMMAND_PARSE_NUMBER(u8, CMD_ARGV[index++], num_read);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[1], num_read);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[2], cmd_byte);
- snprintf(output, sizeof(output), "spi: ");
- for (ptr = &write_buffer[0] ; index < CMD_ARGC; index++) {
- COMMAND_PARSE_NUMBER(u8, CMD_ARGV[index], data);
- *ptr++ = data;
- snprintf(temp, sizeof(temp), "%02" PRIx8 " ", data);
- strncat(output, temp, sizeof(output) - strlen(output) - 1);
- }
- strncat(output, "-> ", sizeof(output) - strlen(output) - 1);
+ for (unsigned int i = 0; i < num_write; i++)
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[i + 3], write_buffer[i]);
/* process command */
- ptr = &read_buffer[0];
- jtagspi_cmd(bank, write_buffer[0], &write_buffer[1], num_write - 1, ptr, -num_read);
+ retval = jtagspi_cmd(bank, cmd_byte, write_buffer, num_write, read_buffer, -num_read);
if (retval != ERROR_OK)
return retval;
- for ( ; num_read > 0; num_read--) {
- snprintf(temp, sizeof(temp), "%02" PRIx8 " ", *ptr++);
- strncat(output, temp, sizeof(output) - strlen(output) - 1);
- }
- command_print(CMD, "%s", output);
+ command_print_sameline(CMD, "spi: %02" PRIx8, cmd_byte);
+
+ for (unsigned int i = 0; i < num_write; i++)
+ command_print_sameline(CMD, " %02" PRIx8, write_buffer[i]);
+
+ command_print_sameline(CMD, " ->");
+
+ for (unsigned int i = 0; i < num_read; i++)
+ command_print_sameline(CMD, " %02" PRIx8, read_buffer[i]);
return ERROR_OK;
}
@@ -381,12 +388,6 @@ static int jtagspi_probe(struct flash_bank *bank)
}
info->probed = false;
- if (!bank->target->tap) {
- LOG_ERROR("Target has no JTAG tap");
- return ERROR_FAIL;
- }
- info->tap = bank->target->tap;
-
jtagspi_cmd(bank, SPIFLASH_READ_ID, NULL, 0, in_buf, -3);
/* the table in spi.c has the manufacturer byte (first) as the lsb */
id = le_to_h_u24(in_buf);
@@ -518,7 +519,7 @@ static int jtagspi_bulk_erase(struct flash_bank *bank)
if (retval != ERROR_OK)
return retval;
- jtagspi_cmd(bank, info->dev.chip_erase_cmd, NULL, 0, NULL, 0);
+ retval = jtagspi_cmd(bank, info->dev.chip_erase_cmd, NULL, 0, NULL, 0);
if (retval != ERROR_OK)
return retval;
diff --git a/src/flash/nor/psoc4.c b/src/flash/nor/psoc4.c
index fb462c1..1064fa9 100644
--- a/src/flash/nor/psoc4.c
+++ b/src/flash/nor/psoc4.c
@@ -779,7 +779,7 @@ static int psoc4_probe(struct flash_bank *bank)
flash_size_in_kb = psoc4_info->user_bank_size / 1024;
}
- char macros_txt[20] = "";
+ char macros_txt[22] = "";
if (num_macros > 1)
snprintf(macros_txt, sizeof(macros_txt), " in %" PRIu32 " macros", num_macros);
diff --git a/src/flash/nor/qn908x.c b/src/flash/nor/qn908x.c
new file mode 100644
index 0000000..8cd7a2f
--- /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 <iosabi@protonmail.com> *
+ ***************************************************************************/
+
+#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 = target_write_u32(target, QN908X_FMC_SMART_CTRL, ctrl_val);
+ if (retval != ERROR_OK)
+ return retval;
+
+ retval = qn908x_wait_for_idle(target, QN908X_DEFAULT_TIMEOUT_MS);
+ if (retval != ERROR_OK)
+ return retval;
+
+ return ERROR_OK;
+}
+
+static int qn908x_erase(struct flash_bank *bank, unsigned int first,
+ unsigned int last)
+{
+ struct qn908x_flash_bank *qn908x_info = bank->driver_priv;
+ int retval = ERROR_OK;
+
+ if (!qn908x_info->num_blocks) {
+ if (qn908x_probe(bank) != ERROR_OK)
+ return ERROR_FLASH_BANK_NOT_PROBED;
+ }
+
+ retval = qn908x_setup_erase(bank->target);
+ if (retval != ERROR_OK)
+ return retval;
+
+ for (unsigned int i = first; i <= last; i++) {
+ if (i >= bank->num_sectors)
+ return ERROR_FLASH_SECTOR_INVALID;
+ uint32_t block_idx = i / QN908X_FLASH_PAGES_PER_BLOCK;
+ uint32_t page_idx = i % QN908X_FLASH_PAGES_PER_BLOCK;
+ if (block_idx >= qn908x_info->num_blocks)
+ return ERROR_FLASH_SECTOR_INVALID;
+
+ LOG_DEBUG("Erasing page %" PRIu32 " of block %" PRIu32,
+ page_idx, block_idx);
+
+ /* Depending on the block the page we are erasing is located we
+ * need to use a different set of bits in the registers. */
+ uint32_t ctrl_page_idx_shift = block_idx ?
+ QN908X_FMC_ERASE_CTRL_PAGE_IDXH_SHIFT :
+ QN908X_FMC_ERASE_CTRL_PAGE_IDXL_SHIFT;
+ uint32_t ctrl_erase_en_shift = block_idx ?
+ QN908X_FMC_ERASE_CTRL_PAGE_ERASEH_EN_SHIFT :
+ QN908X_FMC_ERASE_CTRL_PAGE_ERASEL_EN_SHIFT;
+
+ retval = target_write_u32(bank->target, QN908X_FMC_ERASE_CTRL,
+ BIT(ctrl_erase_en_shift) | (page_idx << ctrl_page_idx_shift));
+ if (retval != ERROR_OK)
+ return retval;
+
+ retval = qn908x_wait_for_idle(bank->target, QN908X_DEFAULT_TIMEOUT_MS);
+ if (retval != ERROR_OK)
+ return retval;
+
+ retval = qn908x_status_check(bank->target);
+ if (retval != ERROR_OK)
+ return retval;
+ }
+
+ return retval;
+}
+
+static int qn908x_protect(struct flash_bank *bank, int set, unsigned int first,
+ unsigned int last)
+{
+ 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;
+ }
+
+ if (!qn908x_info->page_lock_loaded) {
+ int retval = qn908x_read_page_lock(bank);
+ if (retval != ERROR_OK)
+ return retval;
+ }
+
+ /* Use [first, last) interval open on the right side from now on. */
+ last++;
+ /* We use sectors as prot_blocks. */
+ bool needs_update = false;
+ for (unsigned int i = first; i < last; i++) {
+ if (set != (((qn908x_info->page_lock.bits[i / 8] >> (i % 8)) & 1) ^ 1))
+ needs_update = true;
+ }
+
+ /* Check that flash protection still allows SWD access to flash and RAM,
+ * otherwise we won't be able to re-flash this chip from SWD unless we do a
+ * mass erase. */
+ if (qn908x_info->page_lock.protection & QN908X_FMC_LOCK_STAT_8_PROTECT_ANY) {
+ LOG_WARNING("SWD flash/RAM access disabled in the Flash lock and "
+ "protect descriptor. You might need to issue a mass_erase to "
+ "regain SWD access to this chip after reboot.");
+ }
+
+ if (!needs_update)
+ return ERROR_OK;
+
+ int last_page = qn908x_info->num_blocks * QN908X_FLASH_PAGES_PER_BLOCK - 1;
+ int retval;
+
+ if (qn908x_info->page_lock.bits[sizeof(qn908x_info->page_lock.bits) - 1] & 0x80) {
+ /* A bit 1 in the MSB in the page_lock.bits array means that the last
+ * page is unlocked, so we can just erase it. */
+ retval = qn908x_erase(bank, last_page, last_page);
+ if (retval != ERROR_OK)
+ return retval;
+ } else {
+ /* TODO: The last page is locked and we can't erase unless we use the
+ * ERASE_PASSWORD from code running on the device. For this we need to
+ * copy a little program to RAM and execute the erase command from
+ * there since there's no way to override the page protection from
+ * SWD. */
+ LOG_ERROR("Unprotecting the last page is not supported. Issue a "
+ "\"qn908x mass_erase\" command to erase the whole flash, "
+ "including the last page and its protection.");
+ return ERROR_FAIL;
+ }
+
+ for (unsigned int i = first / 8; i < (last + 7) / 8; i++) {
+ /* first_mask contains a bit set if the bit corresponds to a block id
+ * that is larger or equal than first. This is basically 0xff in all
+ * cases except potentially the first iteration. */
+ uint8_t first_mask = (first <= i * 8)
+ ? 0xff : 0xff ^ ((1u << (first - i * 8)) - 1);
+ /* Similar to first_mask, this contains a bit set if the corresponding
+ * is smaller than last. */
+ uint8_t last_mask = (i * 8 + 8 <= last)
+ ? 0xff : ((1u << (last - i * 8)) - 1);
+
+ uint8_t mask = first_mask & last_mask;
+ LOG_DEBUG("protect set=%d bits[%d] with mask=0x%02x", set, i, mask);
+ /* To "set" the protection bit means to clear the bit in the page_lock
+ * bit array. */
+ if (set)
+ qn908x_info->page_lock.bits[i] &= ~mask;
+ else
+ qn908x_info->page_lock.bits[i] |= mask;
+ }
+
+ retval = qn908x_write(bank, (void *)(&qn908x_info->page_lock),
+ last_page * QN908X_FLASH_PAGE_SIZE, sizeof(qn908x_info->page_lock));
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Reload the lock_stat to make the changes effective. */
+ retval = qn908x_load_lock_stat(bank->target);
+ if (retval != ERROR_OK)
+ return retval;
+
+ for (unsigned int i = first; i < last; i++)
+ bank->sectors[i].is_protected = set;
+
+ return ERROR_OK;
+}
+
+static int qn908x_write(struct flash_bank *bank, const uint8_t *buffer,
+ uint32_t offset, uint32_t count)
+{
+ struct qn908x_flash_bank *qn908x_info = bank->driver_priv;
+ int retval = ERROR_OK;
+
+ if (bank->target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ /* The flash infrastructure was requested to align writes to 32 bit */
+ assert(((offset % 4) == 0) && ((count % 4) == 0));
+
+ /* Compute the calc_checksum even if it wasn't requested. */
+ uint32_t checksum = 0;
+ if (offset == 0 && count >= 0x20) {
+ for (int i = 0; i < 7; i++)
+ checksum += buf_get_u32(buffer + (i * 4), 0, 32);
+ checksum = 0 - checksum;
+ LOG_DEBUG("computed image checksum: 0x%8.8" PRIx32, checksum);
+ uint32_t stored_checksum = buf_get_u32(buffer + 7 * 4, 0, 32);
+ if (checksum != stored_checksum) {
+ LOG_WARNING("Image vector table checksum mismatch: expected 0x%08"
+ PRIx32 " but found 0x%08" PRIx32,
+ checksum, stored_checksum);
+ if (!qn908x_info->calc_checksum)
+ LOG_WARNING("This device will not boot, use calc_checksum in "
+ "the flash bank.");
+ else
+ LOG_WARNING("Updating checksum, verification will fail.");
+ }
+ }
+
+ /* Check the Code Read Protection (CRP) word for invalid values or not
+ * allowed ones. */
+ if (offset <= 0x20 && offset + count >= 0x24) {
+ uint32_t crp = buf_get_u32(buffer + 0x20 - offset, 0, 32);
+ /* 2-bit fields at bits 10, 12, 14, 16 and 18 must not be 00 or 11. */
+ for (int i = 10; i <= 18; i += 2) {
+ uint32_t field = (crp >> i) & 3;
+ if (field == 0 || field == 3) {
+ LOG_DEBUG("Code Read Protection = 0x%08" PRIx32, crp);
+ LOG_ERROR("The Code Read Protection (CRP) field at bit %d is "
+ "invalid (%" PRIu32 "). An invalid value could make "
+ "the flash inaccessible.", i, field);
+ return ERROR_FAIL;
+ }
+ }
+
+ uint32_t swd_allowed = (crp >> 18) & 3;
+ if (swd_allowed != 2) {
+ LOG_WARNING("The Code Read Protection (CRP) in this image "
+ "(0x%08" PRIx32 ") is disabling the SWD access, which is "
+ "currently used by OpenOCD to flash this device. After "
+ "reboot, this device will not be accessible to OpenOCD "
+ "anymore.", crp);
+ if (!qn908x_info->allow_swd_disabled) {
+ LOG_ERROR("Disabling SWD is not allowed, run "
+ "\"qn908x allow_brick\" before if you really want to "
+ "disable SWD. You won't be able to access this chip "
+ "anymore from OpenOCD.");
+ return ERROR_FAIL;
+ }
+ }
+ }
+
+ retval = qn908x_wait_for_idle(bank->target, QN908X_DEFAULT_TIMEOUT_MS);
+ if (retval != ERROR_OK)
+ return retval;
+
+ uint32_t smart_ctrl = QN908X_FMC_SMART_CTRL_SMART_WRITEL_EN_MASK
+ | QN908X_FMC_SMART_CTRL_PRGML_EN_MASK
+ | QN908X_FMC_SMART_CTRL_MAX_WRITE(QN908X_FMC_SMART_CTRL_MAX_WRITE_RETRIES);
+ if (qn908x_info->num_blocks > 1) {
+ smart_ctrl |= QN908X_FMC_SMART_CTRL_SMART_WRITEH_EN_MASK
+ | QN908X_FMC_SMART_CTRL_PRGMH_EN_MASK;
+ }
+ retval = target_write_u32(bank->target, QN908X_FMC_SMART_CTRL, smart_ctrl);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Write data page-wise, as suggested in the examples in section
+ * 28.5.2 "Flash write" of user manual UM11023 in revision 1.1
+ * (February 2018). */
+ while (count > 0) {
+ uint32_t next_offset = (offset & ~(QN908X_FLASH_PAGE_SIZE - 1)) + QN908X_FLASH_PAGE_SIZE;
+ uint32_t chunk_len = next_offset - offset;
+ if (chunk_len > count)
+ chunk_len = count;
+
+ if (offset == 0
+ && chunk_len >= QN908X_FLASH_IRQ_VECTOR_CHECKSUM_END
+ && qn908x_info->calc_checksum) {
+ /* write data prior to checksum */
+ retval = target_write_buffer(bank->target, bank->base,
+ QN908X_FLASH_IRQ_VECTOR_CHECKSUM_POS, buffer);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* write computed crc checksum instead of provided data */
+ retval = target_write_u32(bank->target, bank->base + QN908X_FLASH_IRQ_VECTOR_CHECKSUM_POS, checksum);
+ if (retval != ERROR_OK)
+ return retval;
+
+ retval = target_write_buffer(bank->target,
+ bank->base + QN908X_FLASH_IRQ_VECTOR_CHECKSUM_END,
+ chunk_len - QN908X_FLASH_IRQ_VECTOR_CHECKSUM_END,
+ buffer + QN908X_FLASH_IRQ_VECTOR_CHECKSUM_END);
+ } else {
+ retval = target_write_buffer(bank->target, bank->base + offset,
+ chunk_len, buffer);
+ }
+
+ if (retval != ERROR_OK)
+ return retval;
+
+ keep_alive();
+ buffer += chunk_len;
+ count -= chunk_len;
+ offset = next_offset;
+
+ /* Wait for FMC to complete write */
+ retval = qn908x_wait_for_idle(bank->target, QN908X_DEFAULT_TIMEOUT_MS);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Check if FMC reported any errors */
+ retval = qn908x_status_check(bank->target);
+ if (retval != ERROR_OK)
+ return retval;
+ }
+
+ return retval;
+}
+
+static int is_flash_protected(struct flash_bank *bank, bool *is_protected)
+{
+ int retval;
+ uint32_t lock_stat;
+ retval = target_read_u32(bank->target, QN908X_FMC_LOCK_STAT_8, &lock_stat);
+ if (retval)
+ return retval;
+
+ *is_protected = false;
+ if (lock_stat & QN908X_FMC_LOCK_STAT_8_PROTECT_ANY)
+ *is_protected = true;
+
+ return ERROR_OK;
+}
+
+static int qn908x_probe(struct flash_bank *bank)
+{
+ int retval;
+ struct qn908x_flash_bank *qn908x_info = bank->driver_priv;
+ uint8_t info_page[QN908X_INFO_PAGE_CRC_END - QN908X_INFO_PAGE_CRC_START];
+ qn908x_info->num_blocks = 0;
+
+ /* When the SWD access to the RAM is locked by the LOCK_STAT_8 register we
+ * can't access the info page to verify the chip/bank version and it will
+ * read all zeros. This situation prevents the bank from being initialized
+ * at all so no other operation can be performed. The only option to
+ * re-flash the chip is to perform a mass_erase from SWD, which can be
+ * performed even if the mass_erase operation is locked as well.
+ * We attempt to read the info page and redirect the user to perform a
+ * mass_erase if we detect this situation. */
+ retval = target_read_memory(bank->target, QN908X_INFO_PAGE_CRC_START,
+ sizeof(uint32_t), sizeof(info_page) / sizeof(uint32_t),
+ info_page);
+ if (retval != ERROR_OK)
+ return retval;
+
+ const uint32_t crc_seed = 0xffffffff;
+ /* The QN908x uses the standard little endian CRC32 polynomial and all ones
+ * as seed. The CRC32 is however finalized by one last xor operation that
+ * is not part of the common CRC32 implementation, so we do that by hand */
+ uint32_t computed_crc = crc32_le(CRC32_POLY_LE, crc_seed,
+ info_page, sizeof(info_page));
+ computed_crc ^= crc_seed;
+ uint32_t read_crc;
+ retval = target_read_u32(bank->target, QN908X_INFO_PAGE_CRC32, &read_crc);
+ if (retval != ERROR_OK)
+ return retval;
+
+ if (computed_crc != read_crc) {
+ uint32_t info_page_or = 0;
+ for (unsigned int i = 0; i < sizeof(info_page); i++)
+ info_page_or |= info_page[i];
+ bool is_protected;
+ retval = is_flash_protected(bank, &is_protected);
+ if (retval != ERROR_OK)
+ return retval;
+
+ if (info_page_or == 0 && is_protected) {
+ LOG_ERROR("The flash or memory in this chip is protected and "
+ "cannot be accessed from the SWD interface. However, a "
+ "\"qn908x mass_erase\" can erase the device and lift this "
+ "protection.");
+ return ERROR_FAIL;
+ }
+
+ LOG_ERROR("Flash information page CRC32 mismatch, found 0x%08"
+ PRIx32 " but computed 0x%08" PRIx32 ". Flash size unknown",
+ read_crc, computed_crc);
+ return ERROR_FAIL;
+ }
+
+ uint32_t flash_size_fld = target_buffer_get_u32(bank->target,
+ info_page + (QN908X_INFO_PAGE_FLASH_SIZE - QN908X_INFO_PAGE_CRC_START));
+
+ switch (flash_size_fld) {
+ case QN908X_FLASH_SIZE_512K:
+ qn908x_info->num_blocks = 2;
+ break;
+ case QN908X_FLASH_SIZE_256K:
+ qn908x_info->num_blocks = 1;
+ break;
+ default:
+ LOG_ERROR("Unknown Flash size field: 0x%08" PRIx32,
+ flash_size_fld);
+ return ERROR_FAIL;
+ }
+
+ bank->size = qn908x_info->num_blocks * QN908X_FLASH_BLOCK_SIZE;
+ bank->write_start_alignment = 4;
+ bank->write_end_alignment = 4;
+
+ /* The flash supports erasing and protecting individual pages. */
+ bank->num_sectors = qn908x_info->num_blocks *
+ QN908X_FLASH_PAGES_PER_BLOCK;
+ bank->sectors = alloc_block_array(0, QN908X_FLASH_PAGE_SIZE,
+ bank->num_sectors);
+ if (!bank->sectors)
+ return ERROR_FAIL;
+
+ retval = qn908x_init_flash(bank->target);
+ if (retval != ERROR_OK)
+ return retval;
+
+ LOG_INFO("Detected flash size: %d KiB", bank->size / 1024);
+
+ return ERROR_OK;
+}
+
+static int qn908x_auto_probe(struct flash_bank *bank)
+{
+ struct qn908x_flash_bank *qn908x_info = bank->driver_priv;
+ if (qn908x_info->num_blocks != 0)
+ return ERROR_OK;
+ LOG_DEBUG("auto_probe");
+ return qn908x_probe(bank);
+}
+
+static int qn908x_protect_check(struct flash_bank *bank)
+{
+ struct qn908x_flash_bank *qn908x_info = bank->driver_priv;
+
+ int retval = qn908x_read_page_lock(bank);
+ if (retval != ERROR_OK)
+ return retval;
+
+ for (uint32_t i = 0;
+ i < qn908x_info->num_blocks * QN908X_FLASH_PAGES_PER_BLOCK;
+ i++) {
+ /* A bit 0 in page_lock means page is locked. */
+ bank->sectors[i].is_protected =
+ ((qn908x_info->page_lock.bits[i / 8] >> (i % 8)) & 1) ^ 1;
+ }
+ return ERROR_OK;
+}
+
+static int qn908x_get_info(struct flash_bank *bank,
+ struct command_invocation *cmd)
+{
+ uint32_t bootloader_version;
+ uint32_t chip_id;
+ uint8_t bluetooth[6];
+ int retval;
+ struct qn908x_flash_bank *qn908x_info = bank->driver_priv;
+
+ retval = target_read_u32(bank->target, QN908X_SYSCON_CHIP_ID, &chip_id);
+ if (retval != ERROR_OK) {
+ command_print_sameline(cmd, "Cannot read QN908x chip ID.");
+ return retval;
+ }
+ retval = target_read_u32(bank->target, QN908X_INFO_PAGE_BOOTLOADER_VER,
+ &bootloader_version);
+ if (retval != ERROR_OK) {
+ command_print_sameline(cmd, "Cannot read from QN908x info page.");
+ return retval;
+ }
+
+ retval = target_read_memory(bank->target, QN908X_INFO_PAGE_BLUETOOTH_ADDR,
+ 1, sizeof(bluetooth), bluetooth);
+ if (retval != ERROR_OK) {
+ command_print_sameline(cmd, "Cannot read QN908x bluetooth L2 address.");
+ return retval;
+ }
+
+ command_print_sameline(cmd, "qn908x: chip id: 0x%" PRIx32, chip_id);
+
+ command_print_sameline(cmd, " bdaddr: "
+ "%02" PRIx8 ":%02" PRIx8 ":%02" PRIx8
+ ":%02" PRIx8 ":%02" PRIx8 ":%02" PRIx8,
+ bluetooth[0], bluetooth[1], bluetooth[2],
+ bluetooth[3], bluetooth[4], bluetooth[5]);
+
+ command_print_sameline(cmd, " bootloader: %08" PRIx32, bootloader_version);
+
+ command_print_sameline(cmd, " blocks: %" PRIu32, qn908x_info->num_blocks);
+
+ return ERROR_OK;
+}
+
+COMMAND_HANDLER(qn908x_handle_allow_brick_command)
+{
+ int retval;
+
+ struct target *target = get_current_target(CMD_CTX);
+ struct flash_bank *bank = NULL;
+
+ if (CMD_ARGC != 0)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ retval = get_flash_bank_by_addr(target, QN908X_FLASH_BASE, true, &bank);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* If get_flash_bank_by_addr() did not find the flash bank, it should have
+ * returned and error code instead of ERROR_OK */
+ assert(bank);
+ struct qn908x_flash_bank *qn908x_info = bank->driver_priv;
+
+ LOG_WARNING("Flashing images that disable SWD in qn908x is now allowed.");
+ qn908x_info->allow_swd_disabled = true;
+
+ return ERROR_OK;
+}
+
+COMMAND_HANDLER(qn908x_handle_disable_wdog_command)
+{
+ int retval;
+ struct target *target = get_current_target(CMD_CTX);
+
+ if (CMD_ARGC != 0)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ if (target->state != TARGET_HALTED) {
+ command_print(CMD, "Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ /* To change any value in the watchdog block (WDT) we need to first write
+ * 0x1ACCE551 to the LOCK register, and we can then set it back to any other
+ * value to prevent accidental changes to the watchdog. */
+ retval = target_write_u32(target, QN908X_WDT_LOCK, 0x1ACCE551);
+ if (retval != ERROR_OK)
+ return retval;
+
+ retval = target_write_u32(target, QN908X_WDT_CTRL, 0);
+ if (retval != ERROR_OK)
+ return retval;
+
+ return target_write_u32(target, QN908X_WDT_LOCK, 0);
+}
+
+COMMAND_HANDLER(qn908x_handle_mass_erase_command)
+{
+ int retval;
+ bool keep_lock = false;
+ if (CMD_ARGC > 1)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ if (CMD_ARGC == 1) {
+ if (strcmp("keep_lock", CMD_ARGV[0]))
+ return ERROR_COMMAND_ARGUMENT_INVALID;
+ keep_lock = true;
+ }
+
+ /* This operation can be performed without probing the bank since it is the
+ * only way to unlock a chip when the flash and ram have been locked. */
+ struct target *target = get_current_target(CMD_CTX);
+
+ retval = qn908x_setup_erase(target);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Check the mass-erase locking status for information purposes only. This
+ * lock applies to both the SWD and the code running in the core but can be
+ * bypassed in either case. */
+ uint32_t lock_stat_8;
+ retval = target_read_u32(target, QN908X_FMC_LOCK_STAT_8, &lock_stat_8);
+ LOG_DEBUG("LOCK_STAT_8 before erasing: 0x%" PRIx32, lock_stat_8);
+ if (retval != ERROR_OK)
+ return retval;
+ if ((lock_stat_8 & QN908X_FMC_LOCK_STAT_8_MASS_ERASE_LOCK_EN) == 0) {
+ LOG_INFO("mass_erase disabled by Flash lock and protection, forcing "
+ "mass_erase.");
+ }
+ /* Set the DEBUG_PASSWORD so we can force the mass erase from the SWD. We do
+ * this regardless of the lock status. */
+ retval = target_write_u32(target, QN908X_FMC_DEBUG_PASSWORD, 0xCA1E093F);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Erase both halves of the flash at the same time. These are actually done
+ * sequentially but we need to send the command to erase both blocks since
+ * doing so in a locked flash will change the LOCK_STAT_8 register to 0x01,
+ * allowing us to access the (now erase) flash an memory. Erasing only one
+ * block at a time does not reset the LOCK_STAT_8 register and therefore
+ * will not grant access to program the chip. */
+ uint32_t erase_cmd = (1u << QN908X_FMC_ERASE_CTRL_HALF_ERASEH_EN_SHIFT) |
+ (1u << QN908X_FMC_ERASE_CTRL_HALF_ERASEL_EN_SHIFT);
+ LOG_DEBUG("Erasing both blocks with command 0x%" PRIx32, erase_cmd);
+
+ retval = target_write_u32(target, QN908X_FMC_ERASE_CTRL, erase_cmd);
+ if (retval != ERROR_OK)
+ return retval;
+
+ retval = qn908x_wait_for_idle(target, QN908X_DEFAULT_TIMEOUT_MS);
+ if (retval != ERROR_OK)
+ return retval;
+
+ retval = qn908x_status_check(target);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Set the debug password back to 0 to avoid accidental mass_erase. */
+ retval = target_write_u32(target, QN908X_FMC_DEBUG_PASSWORD, 0);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* At this point the flash is erased and we are able to write to the flash
+ * since the LOCK_STAT_8 gets updated to 0x01 after the mass_erase. However,
+ * after a hard reboot this value will be realoaded from flash which after
+ * an erase is 0xff. This means that after flashing an image that doesn't
+ * set the protection bits we end up with a chip that we can't debug. We
+ * update this value to 0x01 unless "keep_lock" is passed to allow the SWD
+ * interface to debug the flash and RAM after a hard reset. */
+ if (keep_lock)
+ return retval;
+
+ retval = qn908x_init_flash(target);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Unlock access to RAM and FLASH in the last page of the flash and
+ * reloading */
+ retval = qn908x_wait_for_idle(target, QN908X_DEFAULT_TIMEOUT_MS);
+ if (retval != ERROR_OK)
+ return retval;
+
+ uint32_t smart_ctrl = QN908X_FMC_SMART_CTRL_SMART_WRITEH_EN_MASK |
+ QN908X_FMC_SMART_CTRL_PRGMH_EN_MASK;
+ retval = target_write_u32(target, QN908X_FMC_SMART_CTRL, smart_ctrl);
+ if (retval != ERROR_OK)
+ return retval;
+
+ retval = target_write_u32(target, QN908X_FLASH_LOCK_ADDR,
+ QN908X_FLASH_LOCK_ENABLE_MASS_ERASE);
+ if (retval != ERROR_OK)
+ return retval;
+
+ retval = qn908x_wait_for_idle(target, QN908X_DEFAULT_TIMEOUT_MS);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Force a page_lock reload after the mass_erase . */
+ retval = qn908x_load_lock_stat(target);
+ if (retval != ERROR_OK)
+ return retval;
+
+ return retval;
+}
+
+static const struct command_registration qn908x_exec_command_handlers[] = {
+ {
+ .name = "allow_brick",
+ .handler = qn908x_handle_allow_brick_command,
+ .mode = COMMAND_EXEC,
+ .help = "Allow writing images that disable SWD access in their "
+ "Code Read Protection (CRP) word. Warning: This can make your "
+ "chip inaccessible from OpenOCD or any other SWD debugger.",
+ .usage = "",
+ },
+ {
+ .name = "disable_wdog",
+ .handler = qn908x_handle_disable_wdog_command,
+ .mode = COMMAND_EXEC,
+ .help = "Disabled the watchdog (WDT).",
+ .usage = "",
+ },
+ {
+ .name = "mass_erase",
+ .handler = qn908x_handle_mass_erase_command,
+ .mode = COMMAND_EXEC,
+ .help = "Erase the whole flash chip.",
+ .usage = "[keep_lock]",
+ },
+ COMMAND_REGISTRATION_DONE
+};
+
+static const struct command_registration qn908x_command_handlers[] = {
+ {
+ .name = "qn908x",
+ .mode = COMMAND_ANY,
+ .help = "qn908x flash controller commands",
+ .usage = "",
+ .chain = qn908x_exec_command_handlers,
+ },
+ COMMAND_REGISTRATION_DONE
+};
+
+const struct flash_driver qn908x_flash = {
+ .name = "qn908x",
+ .commands = qn908x_command_handlers,
+ .flash_bank_command = qn908x_flash_bank_command,
+ .info = qn908x_get_info,
+ .erase = qn908x_erase,
+ .protect = qn908x_protect,
+ .write = qn908x_write,
+ .read = default_flash_read,
+ .probe = qn908x_probe,
+ .auto_probe = qn908x_auto_probe,
+ .erase_check = default_flash_blank_check,
+ .protect_check = qn908x_protect_check,
+ .free_driver_priv = default_flash_free_driver_priv,
+};
diff --git a/src/flash/nor/rp2040.c b/src/flash/nor/rp2040.c
index 6c18c7b..b2ebd9c 100644
--- a/src/flash/nor/rp2040.c
+++ b/src/flash/nor/rp2040.c
@@ -91,7 +91,7 @@ static uint32_t rp2040_lookup_symbol(struct target *target, uint32_t tag, uint16
}
static int rp2040_call_rom_func(struct target *target, struct rp2040_flash_bank *priv,
- uint16_t func_offset, uint32_t argdata[], unsigned int n_args, int timeout_ms)
+ uint16_t func_offset, uint32_t argdata[], unsigned int n_args, unsigned int timeout_ms)
{
char *regnames[4] = { "r0", "r1", "r2", "r3" };
@@ -312,7 +312,7 @@ static int rp2040_flash_erase(struct flash_bank *bank, unsigned int first, unsig
an optional larger "block" (size and command provided in args).
*/
- int timeout_ms = 2000 * (last - first) + 1000;
+ unsigned int timeout_ms = 2000 * (last - first) + 1000;
err = rp2040_call_rom_func(target, priv, priv->jump_flash_range_erase,
args, ARRAY_SIZE(args), timeout_ms);
diff --git a/src/flash/nor/stm32f2x.c b/src/flash/nor/stm32f2x.c
index 36b7a0d..dcaf260 100644
--- a/src/flash/nor/stm32f2x.c
+++ b/src/flash/nor/stm32f2x.c
@@ -230,11 +230,11 @@ static int stm32x_otp_enable(struct flash_bank *bank)
struct stm32x_flash_bank *stm32x_info = bank->driver_priv;
if (!stm32x_info->otp_unlocked) {
- LOG_INFO("OTP memory bank #%u is is enabled for write commands.",
+ LOG_INFO("OTP memory bank #%u is enabled for write commands.",
bank->bank_number);
stm32x_info->otp_unlocked = true;
} else {
- LOG_WARNING("OTP memory bank #%u is is already enabled for write commands.",
+ LOG_WARNING("OTP memory bank #%u is already enabled for write commands.",
bank->bank_number);
}
return ERROR_OK;
@@ -659,8 +659,10 @@ static int stm32x_protect(struct flash_bank *bank, int set, unsigned int first,
}
if (stm32x_is_otp(bank)) {
- if (!set)
+ if (!set) {
+ LOG_ERROR("OTP protection can only be enabled");
return ERROR_COMMAND_ARGUMENT_INVALID;
+ }
return stm32x_otp_protect(bank, first, last);
}
diff --git a/src/flash/nor/stm32l4x.c b/src/flash/nor/stm32l4x.c
index 02e737b..934deec 100644
--- a/src/flash/nor/stm32l4x.c
+++ b/src/flash/nor/stm32l4x.c
@@ -80,6 +80,12 @@
* http://www.st.com/resource/en/reference_manual/dm00451556.pdf
*/
+/* STM32C0xxx series for reference.
+ *
+ * RM0490 (STM32C0x1)
+ * http://www.st.com/resource/en/reference_manual/dm00781702.pdf
+ */
+
/* STM32G0xxx series for reference.
*
* RM0444 (STM32G0x1)
@@ -263,7 +269,7 @@ struct stm32l4_wrp {
};
/* human readable list of families this drivers supports (sorted alphabetically) */
-static const char *device_families = "STM32G0/G4/L4/L4+/L5/U5/WB/WL";
+static const char *device_families = "STM32C0/G0/G4/L4/L4+/L5/U5/WB/WL";
static const struct stm32l4_rev stm32l47_l48xx_revs[] = {
{ 0x1000, "1" }, { 0x1001, "2" }, { 0x1003, "3" }, { 0x1007, "4" }
@@ -273,6 +279,15 @@ static const struct stm32l4_rev stm32l43_l44xx_revs[] = {
{ 0x1000, "A" }, { 0x1001, "Z" }, { 0x2001, "Y" },
};
+
+static const struct stm32l4_rev stm32c01xx_revs[] = {
+ { 0x1000, "A" }, { 0x1001, "Z" },
+};
+
+static const struct stm32l4_rev stm32c03xx_revs[] = {
+ { 0x1000, "A" }, { 0x1001, "Z" },
+};
+
static const struct stm32l4_rev stm32g05_g06xx_revs[] = {
{ 0x1000, "A" },
};
@@ -372,6 +387,30 @@ static const struct stm32l4_part_info stm32l4_parts[] = {
.otp_size = 1024,
},
{
+ .id = DEVID_STM32C01XX,
+ .revs = stm32c01xx_revs,
+ .num_revs = ARRAY_SIZE(stm32c01xx_revs),
+ .device_str = "STM32C01xx",
+ .max_flash_size_kb = 32,
+ .flags = F_NONE,
+ .flash_regs_base = 0x40022000,
+ .fsize_addr = 0x1FFF75A0,
+ .otp_base = 0x1FFF7000,
+ .otp_size = 1024,
+ },
+ {
+ .id = DEVID_STM32C03XX,
+ .revs = stm32c03xx_revs,
+ .num_revs = ARRAY_SIZE(stm32c03xx_revs),
+ .device_str = "STM32C03xx",
+ .max_flash_size_kb = 32,
+ .flags = F_NONE,
+ .flash_regs_base = 0x40022000,
+ .fsize_addr = 0x1FFF75A0,
+ .otp_base = 0x1FFF7000,
+ .otp_size = 1024,
+ },
+ {
.id = DEVID_STM32G05_G06XX,
.revs = stm32g05_g06xx_revs,
.num_revs = ARRAY_SIZE(stm32g05_g06xx_revs),
@@ -1855,6 +1894,8 @@ static int stm32l4_probe(struct flash_bank *bank)
}
break;
case DEVID_STM32L43_L44XX:
+ case DEVID_STM32C01XX:
+ case DEVID_STM32C03XX:
case DEVID_STM32G05_G06XX:
case DEVID_STM32G07_G08XX:
case DEVID_STM32L45_L46XX:
diff --git a/src/flash/nor/stm32l4x.h b/src/flash/nor/stm32l4x.h
index 06cc66d..278038d 100644
--- a/src/flash/nor/stm32l4x.h
+++ b/src/flash/nor/stm32l4x.h
@@ -87,6 +87,8 @@
/* Supported device IDs */
#define DEVID_STM32L47_L48XX 0x415
#define DEVID_STM32L43_L44XX 0x435
+#define DEVID_STM32C01XX 0x443
+#define DEVID_STM32C03XX 0x453
#define DEVID_STM32G05_G06XX 0x456
#define DEVID_STM32G07_G08XX 0x460
#define DEVID_STM32L49_L4AXX 0x461
diff --git a/src/flash/nor/tcl.c b/src/flash/nor/tcl.c
index 7723a17..1c4e154 100644
--- a/src/flash/nor/tcl.c
+++ b/src/flash/nor/tcl.c
@@ -19,7 +19,7 @@
* Implements Tcl commands used to access NOR flash facilities.
*/
-static COMMAND_HELPER(flash_command_get_bank_maybe_probe, unsigned name_index,
+COMMAND_HELPER(flash_command_get_bank_probe_optional, unsigned int name_index,
struct flash_bank **bank, bool do_probe)
{
const char *name = CMD_ARGV[name_index];
@@ -51,7 +51,7 @@ static COMMAND_HELPER(flash_command_get_bank_maybe_probe, unsigned name_index,
COMMAND_HELPER(flash_command_get_bank, unsigned name_index,
struct flash_bank **bank)
{
- return CALL_COMMAND_HANDLER(flash_command_get_bank_maybe_probe,
+ return CALL_COMMAND_HANDLER(flash_command_get_bank_probe_optional,
name_index, bank, true);
}
@@ -157,7 +157,7 @@ COMMAND_HANDLER(handle_flash_probe_command)
if (CMD_ARGC != 1)
return ERROR_COMMAND_SYNTAX_ERROR;
- retval = CALL_COMMAND_HANDLER(flash_command_get_bank_maybe_probe, 0, &p, false);
+ retval = CALL_COMMAND_HANDLER(flash_command_get_bank_probe_optional, 0, &p, false);
if (retval != ERROR_OK)
return retval;