]> git.sur5r.net Git - openocd/blobdiff - src/flash/nor/at91samd.c
flash/nor: at91samd modified to use real erase sector size
[openocd] / src / flash / nor / at91samd.c
index 2cf577805937a87aebb826d02ffc798d457fda4c..ad88c5143dbe8e544187224cf475dc0c5790a133 100644 (file)
@@ -13,9 +13,7 @@
  *   GNU General Public License for more details.                          *
  *                                                                         *
  *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, write to the                         *
- *   Free Software Foundation, Inc.,                                       *
- *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.           *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
  ***************************************************************************/
 
 #ifdef HAVE_CONFIG_H
@@ -27,7 +25,7 @@
 
 #include <target/cortex_m.h>
 
-#define SAMD_NUM_SECTORS       16
+#define SAMD_NUM_PROT_BLOCKS   16
 #define SAMD_PAGE_SIZE_MAX     1024
 
 #define SAMD_FLASH                     ((uint32_t)0x00000000)  /* physical Flash memory */
@@ -38,6 +36,7 @@
 
 #define SAMD_DSU_STATUSA        1               /* DSU status register */
 #define SAMD_DSU_DID           0x18            /* Device ID register */
+#define SAMD_DSU_CTRL_EXT      0x100           /* CTRL register, external access */
 
 #define SAMD_NVMCTRL_CTRLA             0x00    /* NVM control A register */
 #define SAMD_NVMCTRL_CTRLB             0x04    /* NVM control B register */
@@ -76,6 +75,7 @@
 #define SAMD_SERIES_22         0x02
 #define SAMD_SERIES_10         0x02
 #define SAMD_SERIES_11         0x03
+#define SAMD_SERIES_09         0x04
 
 /* Device ID macros */
 #define SAMD_GET_PROCESSOR(id) (id >> 28)
@@ -90,6 +90,13 @@ struct samd_part {
        uint32_t ram_kb;
 };
 
+/* Known SAMD09 parts. DID reset values missing in RM, see
+ * https://github.com/avrxml/asf/blob/master/sam0/utils/cmsis/samd09/include/ */
+static const struct samd_part samd09_parts[] = {
+       { 0x0, "SAMD09D14A", 16, 4 },
+       { 0x7, "SAMD09C13A", 8, 4 },
+};
+
 /* Known SAMD10 parts */
 static const struct samd_part samd10_parts[] = {
        { 0x0, "SAMD10D14AMU", 16, 4 },
@@ -152,7 +159,13 @@ static const struct samd_part samd21_parts[] = {
        { 0xC, "SAMD21E16A", 64, 8 },
        { 0xD, "SAMD21E15A", 32, 4 },
        { 0xE, "SAMD21E14A", 16, 2 },
+    /* Below are B Variants (Table 3-7 from rev I of datasheet) */
+       { 0x20, "SAMD21J16B", 64, 8 },
+       { 0x21, "SAMD21J15B", 32, 4 },
+       { 0x23, "SAMD21G16B", 64, 8 },
+       { 0x24, "SAMD21G15B", 32, 4 },
        { 0x26, "SAMD21E16B", 64, 8 },
+       { 0x27, "SAMD21E15B", 32, 4 },
 };
 
 /* Known SAMR21 parts. */
@@ -253,6 +266,8 @@ static const struct samd_family samd_families[] = {
                samd21_parts, ARRAY_SIZE(samd21_parts) },
        { SAMD_PROCESSOR_M0, SAMD_FAMILY_D, SAMD_SERIES_21,
                samr21_parts, ARRAY_SIZE(samr21_parts) },
+       { SAMD_PROCESSOR_M0, SAMD_FAMILY_D, SAMD_SERIES_09,
+               samd09_parts, ARRAY_SIZE(samd09_parts) },
        { SAMD_PROCESSOR_M0, SAMD_FAMILY_D, SAMD_SERIES_10,
                samd10_parts, ARRAY_SIZE(samd10_parts) },
        { SAMD_PROCESSOR_M0, SAMD_FAMILY_D, SAMD_SERIES_11,
@@ -271,6 +286,7 @@ struct samd_info {
        uint32_t page_size;
        int num_pages;
        int sector_size;
+       int prot_block_size;
 
        bool probed;
        struct target *target;
@@ -304,7 +320,7 @@ static const struct samd_part *samd_find_part(uint32_t id)
 
 static int samd_protect_check(struct flash_bank *bank)
 {
-       int res;
+       int res, prot_block;
        uint16_t lock;
 
        res = target_read_u16(bank->target,
@@ -313,8 +329,8 @@ static int samd_protect_check(struct flash_bank *bank)
                return res;
 
        /* Lock bits are active-low */
-       for (int i = 0; i < bank->num_sectors; i++)
-               bank->sectors[i].is_protected = !(lock & (1<<i));
+       for (prot_block = 0; prot_block < bank->num_prot_blocks; prot_block++)
+               bank->prot_blocks[prot_block].is_protected = !(lock & (1u<<prot_block));
 
        return ERROR_OK;
 }
@@ -365,8 +381,6 @@ static int samd_probe(struct flash_bank *bank)
 
        bank->size = part->flash_kb * 1024;
 
-       chip->sector_size = bank->size / SAMD_NUM_SECTORS;
-
        res = samd_get_flash_page_info(bank->target, &chip->page_size,
                        &chip->num_pages);
        if (res != ERROR_OK) {
@@ -382,21 +396,23 @@ static int samd_probe(struct flash_bank *bank)
                                part->flash_kb, chip->num_pages, chip->page_size);
        }
 
+       /* Erase granularity = 1 row = 4 pages */
+       chip->sector_size = chip->page_size * 4;
+
        /* Allocate the sector table */
-       bank->num_sectors = SAMD_NUM_SECTORS;
-       bank->sectors = calloc(bank->num_sectors, sizeof((bank->sectors)[0]));
+       bank->num_sectors = chip->num_pages / 4;
+       bank->sectors = alloc_block_array(0, chip->sector_size, bank->num_sectors);
        if (!bank->sectors)
                return ERROR_FAIL;
 
-       /* Fill out the sector information: all SAMD sectors are the same size and
-        * there is always a fixed number of them. */
-       for (int i = 0; i < bank->num_sectors; i++) {
-               bank->sectors[i].size = chip->sector_size;
-               bank->sectors[i].offset = i * chip->sector_size;
-               /* mark as unknown */
-               bank->sectors[i].is_erased = -1;
-               bank->sectors[i].is_protected = -1;
-       }
+       /* 16 protection blocks per device */
+       chip->prot_block_size = bank->size / SAMD_NUM_PROT_BLOCKS;
+
+       /* Allocate the table of protection blocks */
+       bank->num_prot_blocks = SAMD_NUM_PROT_BLOCKS;
+       bank->prot_blocks = alloc_block_array(0, chip->prot_block_size, bank->num_prot_blocks);
+       if (!bank->prot_blocks)
+               return ERROR_FAIL;
 
        samd_protect_check(bank);
 
@@ -409,39 +425,43 @@ static int samd_probe(struct flash_bank *bank)
        return ERROR_OK;
 }
 
-static bool samd_check_error(struct target *target)
+static int samd_check_error(struct target *target)
 {
-       int ret;
-       bool error;
+       int ret, ret2;
        uint16_t status;
 
        ret = target_read_u16(target,
                        SAMD_NVMCTRL + SAMD_NVMCTRL_STATUS, &status);
        if (ret != ERROR_OK) {
                LOG_ERROR("Can't read NVM status");
-               return true;
+               return ret;
        }
 
-       if (status & 0x001C) {
-               if (status & (1 << 4)) /* NVME */
-                       LOG_ERROR("SAMD: NVM Error");
-               if (status & (1 << 3)) /* LOCKE */
-                       LOG_ERROR("SAMD: NVM lock error");
-               if (status & (1 << 2)) /* PROGE */
-                       LOG_ERROR("SAMD: NVM programming error");
+       if ((status & 0x001C) == 0)
+               return ERROR_OK;
+
+       if (status & (1 << 4)) { /* NVME */
+               LOG_ERROR("SAMD: NVM Error");
+               ret = ERROR_FLASH_OPERATION_FAILED;
+       }
 
-               error = true;
-       } else {
-               error = false;
+       if (status & (1 << 3)) { /* LOCKE */
+               LOG_ERROR("SAMD: NVM lock error");
+               ret = ERROR_FLASH_PROTECTED;
+       }
+
+       if (status & (1 << 2)) { /* PROGE */
+               LOG_ERROR("SAMD: NVM programming error");
+               ret = ERROR_FLASH_OPER_UNSUPPORTED;
        }
 
        /* Clear the error conditions by writing a one to them */
-       ret = target_write_u16(target,
+       ret2 = target_write_u16(target,
                        SAMD_NVMCTRL + SAMD_NVMCTRL_STATUS, status);
-       if (ret != ERROR_OK)
+       if (ret2 != ERROR_OK)
                LOG_ERROR("Can't clear NVM error conditions");
 
-       return error;
+       return ret;
 }
 
 static int samd_issue_nvmctrl_command(struct target *target, uint16_t cmd)
@@ -460,10 +480,7 @@ static int samd_issue_nvmctrl_command(struct target *target, uint16_t cmd)
                return res;
 
        /* Check to see if the NVM command resulted in an error condition. */
-       if (samd_check_error(target))
-               return ERROR_FAIL;
-
-       return ERROR_OK;
+       return samd_check_error(target);
 }
 
 static int samd_erase_row(struct target *target, uint32_t address)
@@ -517,12 +534,19 @@ static int samd_modify_user_row(struct target *target, uint32_t value,
                uint8_t startb, uint8_t endb)
 {
        int res;
+       uint32_t nvm_ctrlb;
+       bool manual_wp = true;
 
        if (is_user_row_reserved_bit(startb) || is_user_row_reserved_bit(endb)) {
                LOG_ERROR("Can't modify bits in the requested range");
                return ERROR_FAIL;
        }
 
+       /* Check if we need to do manual page write commands */
+       res = target_read_u32(target, SAMD_NVMCTRL + SAMD_NVMCTRL_CTRLB, &nvm_ctrlb);
+       if (res == ERROR_OK)
+               manual_wp = (nvm_ctrlb & SAMD_NVM_CTRLB_MANW) != 0;
+
        /* Retrieve the MCU's page size, in bytes. This is also the size of the
         * entire User Row. */
        uint32_t page_size;
@@ -545,8 +569,8 @@ static int samd_modify_user_row(struct target *target, uint32_t value,
        if (!buf)
                return ERROR_FAIL;
 
-       /* Read the user row (comprising one page) by half-words. */
-       res = target_read_memory(target, SAMD_USER_ROW, 2, page_size / 2, buf);
+       /* Read the user row (comprising one page) by words. */
+       res = target_read_memory(target, SAMD_USER_ROW, 4, page_size / 4, buf);
        if (res != ERROR_OK)
                goto out_user_row;
 
@@ -565,29 +589,28 @@ static int samd_modify_user_row(struct target *target, uint32_t value,
        /* Modify */
        buf_set_u32(buf, startb, endb - startb + 1, value);
 
-       /* Write the page buffer back out to the target.  A Flash write will be
-        * triggered automatically. */
+       /* Write the page buffer back out to the target. */
        res = target_write_memory(target, SAMD_USER_ROW, 4, page_size / 4, buf);
        if (res != ERROR_OK)
                goto out_user_row;
 
-       if (samd_check_error(target)) {
-               res = ERROR_FAIL;
-               goto out_user_row;
+       if (manual_wp) {
+               /* Trigger flash write */
+               res = samd_issue_nvmctrl_command(target, SAMD_NVM_CMD_WAP);
+       } else {
+               res = samd_check_error(target);
        }
 
-       /* Success */
-       res = ERROR_OK;
-
 out_user_row:
        free(buf);
 
        return res;
 }
 
-static int samd_protect(struct flash_bank *bank, int set, int first, int last)
+static int samd_protect(struct flash_bank *bank, int set, int first_prot_bl, int last_prot_bl)
 {
-       struct samd_info *chip = (struct samd_info *)bank->driver_priv;
+       int res = ERROR_OK;
+       int prot_block;
 
        /* We can issue lock/unlock region commands with the target running but
         * the settings won't persist unless we're able to modify the LOCK regions
@@ -597,18 +620,16 @@ static int samd_protect(struct flash_bank *bank, int set, int first, int last)
                return ERROR_TARGET_NOT_HALTED;
        }
 
-       int res = ERROR_OK;
-
-       for (int s = first; s <= last; s++) {
-               if (set != bank->sectors[s].is_protected) {
-                       /* Load an address that is within this sector (we use offset 0) */
+       for (prot_block = first_prot_bl; prot_block <= last_prot_bl; prot_block++) {
+               if (set != bank->prot_blocks[prot_block].is_protected) {
+                       /* Load an address that is within this protection block (we use offset 0) */
                        res = target_write_u32(bank->target,
                                                        SAMD_NVMCTRL + SAMD_NVMCTRL_ADDR,
-                                                       ((s * chip->sector_size) >> 1));
+                                                       bank->prot_blocks[prot_block].offset >> 1);
                        if (res != ERROR_OK)
                                goto exit;
 
-                       /* Tell the controller to lock that sector */
+                       /* Tell the controller to lock that block */
                        res = samd_issue_nvmctrl_command(bank->target,
                                        set ? SAMD_NVM_CMD_LR : SAMD_NVM_CMD_UR);
                        if (res != ERROR_OK)
@@ -623,7 +644,7 @@ static int samd_protect(struct flash_bank *bank, int set, int first, int last)
         * locked.  See Table 9-3 in the SAMD20 datasheet for more details. */
 
        res = samd_modify_user_row(bank->target, set ? 0x0000 : 0xFFFF,
-                       48 + first, 48 + last);
+                       48 + first_prot_bl, 48 + last_prot_bl);
        if (res != ERROR_OK)
                LOG_WARNING("SAMD: protect settings were not made persistent!");
 
@@ -635,10 +656,9 @@ exit:
        return res;
 }
 
-static int samd_erase(struct flash_bank *bank, int first, int last)
+static int samd_erase(struct flash_bank *bank, int first_sect, int last_sect)
 {
-       int res;
-       int rows_in_sector;
+       int res, s;
        struct samd_info *chip = (struct samd_info *)bank->driver_priv;
 
        if (bank->target->state != TARGET_HALTED) {
@@ -652,26 +672,12 @@ static int samd_erase(struct flash_bank *bank, int first, int last)
                        return ERROR_FLASH_BANK_NOT_PROBED;
        }
 
-       /* The SAMD NVM has row erase granularity.  There are four pages in a row
-        * and the number of rows in a sector depends on the sector size, which in
-        * turn depends on the Flash capacity as there is a fixed number of
-        * sectors. */
-       rows_in_sector = chip->sector_size / (chip->page_size * 4);
-
        /* For each sector to be erased */
-       for (int s = first; s <= last; s++) {
-               if (bank->sectors[s].is_protected) {
-                       LOG_ERROR("SAMD: failed to erase sector %d. That sector is write-protected", s);
-                       return ERROR_FLASH_OPERATION_FAILED;
-               }
-
-               /* For each row in that sector */
-               for (int r = s * rows_in_sector; r < (s + 1) * rows_in_sector; r++) {
-                       res = samd_erase_row(bank->target, r * chip->page_size * 4);
-                       if (res != ERROR_OK) {
-                               LOG_ERROR("SAMD: failed to erase sector %d", s);
-                               return res;
-                       }
+       for (s = first_sect; s <= last_sect; s++) {
+               res = samd_erase_row(bank->target, bank->sectors[s].offset);
+               if (res != ERROR_OK) {
+                       LOG_ERROR("SAMD: failed to erase sector %d at 0x%08" PRIx32, s, bank->sectors[s].offset);
+                       return res;
                }
        }
 
@@ -770,18 +776,15 @@ static int samd_write(struct flash_bank *bank, const uint8_t *buffer,
                 * then issue CMD_WP always */
                if (manual_wp || pg_offset + 4 * nw < chip->page_size) {
                        res = samd_issue_nvmctrl_command(bank->target, SAMD_NVM_CMD_WP);
-                       if (res != ERROR_OK) {
-                               LOG_ERROR("%s: %d", __func__, __LINE__);
-                               goto free_pb;
-                       }
-               }
+               } else {
+                       /* Access through AHB is stalled while flash is being programmed */
+                       usleep(200);
 
-               /* Access through AHB is stalled while flash is being programmed */
-               usleep(200);
+                       res = samd_check_error(bank->target);
+               }
 
-               if (samd_check_error(bank->target)) {
+               if (res != ERROR_OK) {
                        LOG_ERROR("%s: write failed at address 0x%08" PRIx32, __func__, address);
-                       res = ERROR_FAIL;
                        goto free_pb;
                }
 
@@ -842,18 +845,23 @@ COMMAND_HANDLER(samd_handle_info_command)
 COMMAND_HANDLER(samd_handle_chip_erase_command)
 {
        struct target *target = get_current_target(CMD_CTX);
+       int res = ERROR_FAIL;
 
        if (target) {
                /* Enable access to the DSU by disabling the write protect bit */
                target_write_u32(target, SAMD_PAC1, (1<<1));
+               /* intentionally without error checking - not accessible on secured chip */
+
                /* Tell the DSU to perform a full chip erase.  It takes about 240ms to
                 * perform the erase. */
-               target_write_u8(target, SAMD_DSU, (1<<4));
-
-               command_print(CMD_CTX, "chip erased");
+               res = target_write_u8(target, SAMD_DSU + SAMD_DSU_CTRL_EXT, (1<<4));
+               if (res == ERROR_OK)
+                       command_print(CMD_CTX, "chip erase started");
+               else
+                       command_print(CMD_CTX, "write to DSU CTRL failed");
        }
 
-       return ERROR_OK;
+       return res;
 }
 
 COMMAND_HANDLER(samd_handle_set_security_command)
@@ -1004,10 +1012,15 @@ COMMAND_HANDLER(samd_handle_bootloader_command)
 COMMAND_HANDLER(samd_handle_reset_deassert)
 {
        struct target *target = get_current_target(CMD_CTX);
-       struct armv7m_common *armv7m = target_to_armv7m(target);
        int retval = ERROR_OK;
        enum reset_types jtag_reset_config = jtag_get_reset_config();
 
+       /* If the target has been unresponsive before, try to re-establish
+        * communication now - CPU is held in reset by DSU, DAP is working */
+       if (!target_was_examined(target))
+               target_examine_one(target);
+       target_poll(target);
+
        /* In case of sysresetreq, debug retains state set in cortex_m_assert_reset()
         * so we just release reset held by DSU
         *
@@ -1016,9 +1029,9 @@ COMMAND_HANDLER(samd_handle_reset_deassert)
         * After vectreset DSU release is not needed however makes no harm
         */
        if (target->reset_halt && (jtag_reset_config & RESET_HAS_SRST)) {
-               retval = mem_ap_write_u32(armv7m->debug_ap, DCB_DHCSR, DBGKEY | C_HALT | C_DEBUGEN);
+               retval = target_write_u32(target, DCB_DHCSR, DBGKEY | C_HALT | C_DEBUGEN);
                if (retval == ERROR_OK)
-                       retval = mem_ap_write_u32(armv7m->debug_ap, DCB_DEMCR,
+                       retval = target_write_u32(target, DCB_DEMCR,
                                TRCENA | VC_HARDERR | VC_BUSERR | VC_CORERESET);
                /* do not return on error here, releasing DSU reset is more important */
        }