/****************************************************************************** * * @file ambiqmicro.c * * @brief Ambiq Micro flash driver. * *****************************************************************************/ /****************************************************************************** * Copyright (c) 2015, David Racine * * Copyright (c) 2016, Rick Foos * * Copyright (c) 2015-2016, Ambiq Micro, Inc. * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * *****************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "jtag/interface.h" #include "imp.h" #include "target/algorithm.h" #include "target/armv7m.h" #include "target/cortex_m.h" /** Check error, log error. */ #define CHECK_STATUS(rc, msg) { \ if (rc != ERROR_OK) { \ LOG_ERROR("status(%d):%s\n", rc, msg); } } /* * Address and Key defines. */ #define PROGRAM_KEY (0x12344321) #define OTP_PROGRAM_KEY (0x87655678) #define FLASH_PROGRAM_MAIN_FROM_SRAM 0x0800005d #define FLASH_PROGRAM_OTP_FROM_SRAM 0x08000061 #define FLASH_ERASE_LIST_MAIN_PAGES_FROM_SRAM 0x08000065 #define FLASH_MASS_ERASE_MAIN_PAGES_FROM_SRAM 0x08000069 static const uint32_t apollo_flash_size[] = { 1 << 15, 1 << 16, 1 << 17, 1 << 18, 1 << 19, 1 << 20, 1 << 21 }; static const uint32_t apollo_sram_size[] = { 1 << 15, 1 << 16, 1 << 17, 1 << 18, 1 << 19, 1 << 20, 1 << 21 }; struct ambiqmicro_flash_bank { /* chip id register */ uint32_t probed; const char *target_name; uint8_t target_class; uint32_t sramsiz; uint32_t flshsiz; /* flash geometry */ uint32_t num_pages; uint32_t pagesize; uint32_t pages_in_lockregion; /* nv memory bits */ uint16_t num_lockbits; /* main clock status */ uint32_t rcc; uint32_t rcc2; uint8_t mck_valid; uint8_t xtal_mask; uint32_t iosc_freq; uint32_t mck_freq; const char *iosc_desc; const char *mck_desc; }; static struct { uint8_t class; uint8_t partno; const char *partname; } ambiqmicroParts[6] = { {0xFF, 0x00, "Unknown"}, {0x01, 0x00, "Apollo"}, {0x02, 0x00, "Apollo2"}, {0x03, 0x00, "Unknown"}, {0x04, 0x00, "Unknown"}, {0x05, 0x00, "Apollo"}, }; static char *ambiqmicroClassname[6] = { "Unknown", "Apollo", "Apollo2", "Unknown", "Unknown", "Apollo" }; /*************************************************************************** * openocd command interface * ***************************************************************************/ /* flash_bank ambiqmicro 0 0 */ FLASH_BANK_COMMAND_HANDLER(ambiqmicro_flash_bank_command) { struct ambiqmicro_flash_bank *ambiqmicro_info; if (CMD_ARGC < 6) return ERROR_COMMAND_SYNTAX_ERROR; ambiqmicro_info = calloc(sizeof(struct ambiqmicro_flash_bank), 1); bank->driver_priv = ambiqmicro_info; ambiqmicro_info->target_name = "Unknown target"; /* part wasn't probed yet */ ambiqmicro_info->probed = 0; return ERROR_OK; } static int get_ambiqmicro_info(struct flash_bank *bank, char *buf, int buf_size) { struct ambiqmicro_flash_bank *ambiqmicro_info = bank->driver_priv; int printed; char *classname; if (ambiqmicro_info->probed == 0) { LOG_ERROR("Target not probed"); return ERROR_FLASH_BANK_NOT_PROBED; } /* Check class name in range. */ if (ambiqmicro_info->target_class < sizeof(ambiqmicroClassname)) classname = ambiqmicroClassname[ambiqmicro_info->target_class]; else classname = ambiqmicroClassname[0]; printed = snprintf(buf, buf_size, "\nAmbiq Micro information: Chip is " "class %d (%s) %s\n", ambiqmicro_info->target_class, classname, ambiqmicro_info->target_name); if ((printed < 0)) return ERROR_BUF_TOO_SMALL; return ERROR_OK; } /*************************************************************************** * chip identification and status * ***************************************************************************/ /* Fill in driver info structure */ static int ambiqmicro_read_part_info(struct flash_bank *bank) { struct ambiqmicro_flash_bank *ambiqmicro_info = bank->driver_priv; struct target *target = bank->target; uint32_t PartNum = 0; int retval; /* * Read Part Number. */ retval = target_read_u32(target, 0x40020000, &PartNum); if (retval != ERROR_OK) { LOG_ERROR("status(0x%x):Could not read PartNum.\n", retval); /* Set PartNum to default device */ PartNum = 0; } LOG_DEBUG("Part number: 0x%x", PartNum); /* * Determine device class. */ ambiqmicro_info->target_class = (PartNum & 0xFF000000) >> 24; switch (ambiqmicro_info->target_class) { case 1: /* 1 - Apollo */ case 5: /* 5 - Apollo Bootloader */ bank->base = bank->bank_number * 0x40000; ambiqmicro_info->pagesize = 2048; ambiqmicro_info->flshsiz = apollo_flash_size[(PartNum & 0x00F00000) >> 20]; ambiqmicro_info->sramsiz = apollo_sram_size[(PartNum & 0x000F0000) >> 16]; ambiqmicro_info->num_pages = ambiqmicro_info->flshsiz / ambiqmicro_info->pagesize; if (ambiqmicro_info->num_pages > 128) { ambiqmicro_info->num_pages = 128; ambiqmicro_info->flshsiz = 1024 * 256; } break; default: LOG_INFO("Unknown Class. Using Apollo-64 as default."); bank->base = bank->bank_number * 0x40000; ambiqmicro_info->pagesize = 2048; ambiqmicro_info->flshsiz = apollo_flash_size[1]; ambiqmicro_info->sramsiz = apollo_sram_size[0]; ambiqmicro_info->num_pages = ambiqmicro_info->flshsiz / ambiqmicro_info->pagesize; if (ambiqmicro_info->num_pages > 128) { ambiqmicro_info->num_pages = 128; ambiqmicro_info->flshsiz = 1024 * 256; } break; } if (ambiqmicro_info->target_class < (sizeof(ambiqmicroParts)/sizeof(ambiqmicroParts[0]))) ambiqmicro_info->target_name = ambiqmicroParts[ambiqmicro_info->target_class].partname; else ambiqmicro_info->target_name = ambiqmicroParts[0].partname; LOG_DEBUG("num_pages: %d, pagesize: %d, flash: %d, sram: %d", ambiqmicro_info->num_pages, ambiqmicro_info->pagesize, ambiqmicro_info->flshsiz, ambiqmicro_info->sramsiz); return ERROR_OK; } /*************************************************************************** * flash operations * ***************************************************************************/ static int ambiqmicro_protect_check(struct flash_bank *bank) { struct ambiqmicro_flash_bank *ambiqmicro = bank->driver_priv; int status = ERROR_OK; uint32_t i; if (ambiqmicro->probed == 0) { LOG_ERROR("Target not probed"); return ERROR_FLASH_BANK_NOT_PROBED; } for (i = 0; i < (unsigned) bank->num_sectors; i++) bank->sectors[i].is_protected = -1; return status; } /** Read flash status from bootloader. */ static int check_flash_status(struct target *target, uint32_t address) { uint32_t retflash; int rc; rc = target_read_u32(target, address, &retflash); /* target connection failed. */ if (rc != ERROR_OK) { LOG_DEBUG("%s:%d:%s(): status(0x%x)\n", __FILE__, __LINE__, __func__, rc); return rc; } /* target flash failed, unknown cause. */ if (retflash != 0) { LOG_ERROR("Flash not happy: status(0x%x)", retflash); return ERROR_FLASH_OPERATION_FAILED; } return ERROR_OK; } static int ambiqmicro_exec_command(struct target *target, uint32_t command, uint32_t flash_return_address) { int retval, retflash; retval = target_resume( target, false, command, true, true); CHECK_STATUS(retval, "error executing ambiqmicro command"); /* * Wait for halt. */ for (;; ) { target_poll(target); if (target->state == TARGET_HALTED) break; else if (target->state == TARGET_RUNNING || target->state == TARGET_DEBUG_RUNNING) { /* * Keep polling until target halts. */ target_poll(target); alive_sleep(100); LOG_DEBUG("state = %d", target->state); } else { LOG_ERROR("Target not halted or running %d", target->state); break; } } /* * Read return value, flash error takes precedence. */ retflash = check_flash_status(target, flash_return_address); if (retflash != ERROR_OK) retval = retflash; /* Return code from target_resume OR flash. */ return retval; } static int ambiqmicro_mass_erase(struct flash_bank *bank) { struct target *target = NULL; struct ambiqmicro_flash_bank *ambiqmicro_info = NULL; int retval = ERROR_OK; ambiqmicro_info = bank->driver_priv; target = bank->target; if (target->state != TARGET_HALTED) { LOG_ERROR("Target not halted"); return ERROR_TARGET_NOT_HALTED; } if (ambiqmicro_info->probed == 0) { LOG_ERROR("Target not probed"); return ERROR_FLASH_BANK_NOT_PROBED; } /* * Clear Bootloader bit. */ retval = target_write_u32(target, 0x400201a0, 0x0); CHECK_STATUS(retval, "error clearing bootloader bit."); /* * Set up the SRAM. */ /* * Bank. */ retval = target_write_u32(target, 0x10000000, bank->bank_number); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Write Key. */ retval = target_write_u32(target, 0x10000004, PROGRAM_KEY); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Breakpoint. */ retval = target_write_u32(target, 0x10000008, 0xfffffffe); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Erase the main array. */ LOG_INFO("Mass erase on bank %d.", bank->bank_number); /* * passed pc, addr = ROM function, handle breakpoints, not debugging. */ retval = ambiqmicro_exec_command(target, FLASH_MASS_ERASE_MAIN_PAGES_FROM_SRAM, 0x10000008); CHECK_STATUS(retval, "error executing ambiqmicro flash mass erase."); if (retval != ERROR_OK) return retval; /* * Set Bootloader bit, regardless of command execution. */ retval = target_write_u32(target, 0x400201a0, 0x1); CHECK_STATUS(retval, "error setting bootloader bit."); return retval; } static int ambiqmicro_erase(struct flash_bank *bank, int first, int last) { struct ambiqmicro_flash_bank *ambiqmicro_info = bank->driver_priv; struct target *target = bank->target; uint32_t retval = ERROR_OK; if (bank->target->state != TARGET_HALTED) { LOG_ERROR("Target not halted"); return ERROR_TARGET_NOT_HALTED; } if (ambiqmicro_info->probed == 0) { LOG_ERROR("Target not probed"); return ERROR_FLASH_BANK_NOT_PROBED; } /* * Check pages. * Fix num_pages for the device. */ if ((first < 0) || (last < first) || (last >= (int)ambiqmicro_info->num_pages)) return ERROR_FLASH_SECTOR_INVALID; /* * Just Mass Erase if all pages are given. * TODO: Fix num_pages for the device */ if ((first == 0) && (last == ((int)ambiqmicro_info->num_pages-1))) return ambiqmicro_mass_erase(bank); /* * Clear Bootloader bit. */ retval = target_write_u32(target, 0x400201a0, 0x0); CHECK_STATUS(retval, "error clearing bootloader bit."); /* * Set up the SRAM. */ /* * Bank. */ retval = target_write_u32(target, 0x10000000, bank->bank_number); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Number of pages to erase. */ retval = target_write_u32(target, 0x10000004, 1 + (last-first)); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Write Key. */ retval = target_write_u32(target, 0x10000008, PROGRAM_KEY); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Breakpoint. */ retval = target_write_u32(target, 0x1000000c, 0xfffffffe); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Pointer to flash address. */ retval = target_write_u32(target, 0x10000010, first); CHECK_STATUS(retval, "error writing target SRAM parameters."); if (retval != ERROR_OK) return retval; /* * Erase the pages. */ LOG_INFO("Erasing pages %d to %d on bank %d", first, last, bank->bank_number); /* * passed pc, addr = ROM function, handle breakpoints, not debugging. */ retval = ambiqmicro_exec_command(target, FLASH_ERASE_LIST_MAIN_PAGES_FROM_SRAM, 0x1000000C); CHECK_STATUS(retval, "error executing flash page erase"); if (retval != ERROR_OK) return retval; LOG_INFO("%d pages erased!", 1+(last-first)); if (first == 0) { /* * Set Bootloader bit. */ retval = target_write_u32(target, 0x400201a0, 0x1); CHECK_STATUS(retval, "error setting bootloader bit."); if (retval != ERROR_OK) return retval; } return retval; } static int ambiqmicro_protect(struct flash_bank *bank, int set, int first, int last) { /* struct ambiqmicro_flash_bank *ambiqmicro_info = bank->driver_priv; * struct target *target = bank->target; */ /* * TODO */ LOG_INFO("Not yet implemented"); if (bank->target->state != TARGET_HALTED) { LOG_ERROR("Target not halted"); return ERROR_TARGET_NOT_HALTED; } return ERROR_OK; } static int ambiqmicro_write_block(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count) { /* struct ambiqmicro_flash_bank *ambiqmicro_info = bank->driver_priv; */ struct target *target = bank->target; uint32_t address = bank->base + offset; uint32_t buffer_pointer = 0x10000010; uint32_t maxbuffer; uint32_t thisrun_count; int retval = ERROR_OK; if (((count%4) != 0) || ((offset%4) != 0)) { LOG_ERROR("write block must be multiple of 4 bytes in offset & length"); return ERROR_FAIL; } /* * Max buffer size for this device. * Hard code 6kB for the buffer. */ maxbuffer = 0x1800; LOG_INFO("Flashing main array"); while (count > 0) { if (count > maxbuffer) thisrun_count = maxbuffer; else thisrun_count = count; /* * Set up the SRAM. */ /* * Pointer to flash. */ retval = target_write_u32(target, 0x10000000, address); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Number of 32-bit words to program. */ retval = target_write_u32(target, 0x10000004, thisrun_count/4); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Write Key. */ retval = target_write_u32(target, 0x10000008, PROGRAM_KEY); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Breakpoint. */ retval = target_write_u32(target, 0x1000000c, 0xfffffffe); CHECK_STATUS(retval, "error writing target SRAM parameters."); /* * Write Buffer. */ retval = target_write_buffer(target, buffer_pointer, thisrun_count, buffer); if (retval != ERROR_OK) { CHECK_STATUS(retval, "error writing target SRAM parameters."); break; } LOG_DEBUG("address = 0x%08x", address); retval = ambiqmicro_exec_command(target, FLASH_PROGRAM_MAIN_FROM_SRAM, 0x1000000c); CHECK_STATUS(retval, "error executing ambiqmicro flash write algorithm"); if (retval != ERROR_OK) break; buffer += thisrun_count; address += thisrun_count; count -= thisrun_count; } LOG_INFO("Main array flashed"); /* * Clear Bootloader bit. */ retval = target_write_u32(target, 0x400201a0, 0x0); CHECK_STATUS(retval, "error clearing bootloader bit"); return retval; } static int ambiqmicro_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count) { uint32_t retval; /* try using a block write */ retval = ambiqmicro_write_block(bank, buffer, offset, count); if (retval != ERROR_OK) LOG_ERROR("write failed"); return retval; } static int ambiqmicro_probe(struct flash_bank *bank) { struct ambiqmicro_flash_bank *ambiqmicro_info = bank->driver_priv; uint32_t retval; /* If this is a ambiqmicro chip, it has flash; probe() is just * to figure out how much is present. Only do it once. */ if (ambiqmicro_info->probed == 1) { LOG_INFO("Target already probed"); return ERROR_OK; } /* ambiqmicro_read_part_info() already handled error checking and * reporting. Note that it doesn't write, so we don't care about * whether the target is halted or not. */ retval = ambiqmicro_read_part_info(bank); if (retval != ERROR_OK) return retval; if (bank->sectors) { free(bank->sectors); bank->sectors = NULL; } /* provide this for the benefit of the NOR flash framework */ bank->size = ambiqmicro_info->pagesize * ambiqmicro_info->num_pages; bank->num_sectors = ambiqmicro_info->num_pages; bank->sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors); for (int i = 0; i < bank->num_sectors; i++) { bank->sectors[i].offset = i * ambiqmicro_info->pagesize; bank->sectors[i].size = ambiqmicro_info->pagesize; bank->sectors[i].is_erased = -1; bank->sectors[i].is_protected = -1; } /* * Part has been probed. */ ambiqmicro_info->probed = 1; return retval; } static int ambiqmicro_otp_program(struct flash_bank *bank, uint32_t offset, uint32_t count) { struct target *target = NULL; struct ambiqmicro_flash_bank *ambiqmicro_info = NULL; uint32_t retval = ERROR_OK; ambiqmicro_info = bank->driver_priv; target = bank->target; if (target->state != TARGET_HALTED) { LOG_ERROR("Target not halted"); return ERROR_TARGET_NOT_HALTED; } if (ambiqmicro_info->probed == 0) { LOG_ERROR("Target not probed"); return ERROR_FLASH_BANK_NOT_PROBED; } if (count > 256) { LOG_ERROR("Count must be < 256"); return ERROR_FLASH_DST_BREAKS_ALIGNMENT; } /* * Clear Bootloader bit. */ retval = target_write_u32(target, 0x400201a0, 0x0); CHECK_STATUS(retval, "error clearing bootloader bit."); /* * Set up the SRAM. */ /* * Bank. */ retval = target_write_u32(target, 0x10000000, offset); CHECK_STATUS(retval, "error setting target SRAM parameters."); /* * Num of words to program. */ retval = target_write_u32(target, 0x10000004, count); CHECK_STATUS(retval, "error setting target SRAM parameters."); /* * Write Key. */ retval = target_write_u32(target, 0x10000008, OTP_PROGRAM_KEY); CHECK_STATUS(retval, "error setting target SRAM parameters."); /* * Breakpoint. */ retval = target_write_u32(target, 0x1000000c, 0xfffffffe); CHECK_STATUS(retval, "error setting target SRAM parameters."); if (retval != ERROR_OK) return retval; /* * Program OTP. */ LOG_INFO("Programming OTP offset 0x%08x", offset); /* * passed pc, addr = ROM function, handle breakpoints, not debugging. */ retval = ambiqmicro_exec_command(target, FLASH_PROGRAM_OTP_FROM_SRAM, 0x1000000C); CHECK_STATUS(retval, "error executing ambiqmicro otp program algorithm"); LOG_INFO("Programming OTP finished."); return retval; } COMMAND_HANDLER(ambiqmicro_handle_mass_erase_command) { int i; if (CMD_ARGC < 1) return ERROR_COMMAND_SYNTAX_ERROR; struct flash_bank *bank; uint32_t retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); if (ERROR_OK != retval) return retval; if (ambiqmicro_mass_erase(bank) == ERROR_OK) { /* set all sectors as erased */ for (i = 0; i < bank->num_sectors; i++) bank->sectors[i].is_erased = 1; command_print(CMD_CTX, "ambiqmicro mass erase complete"); } else command_print(CMD_CTX, "ambiqmicro mass erase failed"); return ERROR_OK; } COMMAND_HANDLER(ambiqmicro_handle_page_erase_command) { struct flash_bank *bank; uint32_t first, last; uint32_t retval; if (CMD_ARGC < 3) return ERROR_COMMAND_SYNTAX_ERROR; COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], first); COMMAND_PARSE_NUMBER(u32, CMD_ARGV[2], last); retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); if (ERROR_OK != retval) return retval; if (ambiqmicro_erase(bank, first, last) == ERROR_OK) command_print(CMD_CTX, "ambiqmicro page erase complete"); else command_print(CMD_CTX, "ambiqmicro page erase failed"); return ERROR_OK; } /** * Program the otp block. */ COMMAND_HANDLER(ambiqmicro_handle_program_otp_command) { struct flash_bank *bank; uint32_t offset, count; uint32_t retval; if (CMD_ARGC < 3) return ERROR_COMMAND_SYNTAX_ERROR; COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], offset); COMMAND_PARSE_NUMBER(u32, CMD_ARGV[2], count); command_print(CMD_CTX, "offset=0x%08x count=%d", offset, count); CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); retval = ambiqmicro_otp_program(bank, offset, count); if (retval != ERROR_OK) LOG_ERROR("error check log"); return ERROR_OK; } static const struct command_registration ambiqmicro_exec_command_handlers[] = { { .name = "mass_erase", .usage = "", .handler = ambiqmicro_handle_mass_erase_command, .mode = COMMAND_EXEC, .help = "Erase entire device", }, { .name = "page_erase", .usage = " ", .handler = ambiqmicro_handle_page_erase_command, .mode = COMMAND_EXEC, .help = "Erase device pages", }, { .name = "program_otp", .handler = ambiqmicro_handle_program_otp_command, .mode = COMMAND_EXEC, .usage = " ", .help = "Program OTP (assumes you have already written array starting at 0x10000010)", }, COMMAND_REGISTRATION_DONE }; static const struct command_registration ambiqmicro_command_handlers[] = { { .name = "ambiqmicro", .mode = COMMAND_EXEC, .help = "ambiqmicro flash command group", .usage = "Support for Ambiq Micro parts.", .chain = ambiqmicro_exec_command_handlers, }, COMMAND_REGISTRATION_DONE }; struct flash_driver ambiqmicro_flash = { .name = "ambiqmicro", .commands = ambiqmicro_command_handlers, .flash_bank_command = ambiqmicro_flash_bank_command, .erase = ambiqmicro_erase, .protect = ambiqmicro_protect, .write = ambiqmicro_write, .read = default_flash_read, .probe = ambiqmicro_probe, .auto_probe = ambiqmicro_probe, .erase_check = default_flash_blank_check, .protect_check = ambiqmicro_protect_check, .info = get_ambiqmicro_info, };