You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

at91samd.c 17 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. /***************************************************************************
  2. * Copyright (C) 2013 by Andrey Yurovsky *
  3. * Andrey Yurovsky <yurovsky@gmail.com> *
  4. * *
  5. * This program is free software; you can redistribute it and/or modify *
  6. * it under the terms of the GNU General Public License as published by *
  7. * the Free Software Foundation; either version 2 of the License, or *
  8. * (at your option) any later version. *
  9. * *
  10. * This program is distributed in the hope that it will be useful, *
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  13. * GNU General Public License for more details. *
  14. * *
  15. * You should have received a copy of the GNU General Public License *
  16. * along with this program; if not, write to the *
  17. * Free Software Foundation, Inc., *
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
  19. ***************************************************************************/
  20. #ifdef HAVE_CONFIG_H
  21. #include "config.h"
  22. #endif
  23. #include "imp.h"
  24. #define SAMD_NUM_SECTORS 16
  25. #define SAMD_FLASH 0x00000000 /* physical Flash memory */
  26. #define SAMD_DSU 0x41002000 /* Device Service Unit */
  27. #define SAMD_NVMCTRL 0x41004000 /* Non-volatile memory controller */
  28. #define SAMD_DSU_DID 0x18 /* Device ID register */
  29. #define SAMD_NVMCTRL_CTRLA 0x00 /* NVM control A register */
  30. #define SAMD_NVMCTRL_CTRLB 0x04 /* NVM control B register */
  31. #define SAMD_NVMCTRL_PARAM 0x08 /* NVM parameters register */
  32. #define SAMD_NVMCTRL_INTFLAG 0x18 /* NVM Interupt Flag Status & Clear */
  33. #define SAMD_NVMCTRL_STATUS 0x18 /* NVM status register */
  34. #define SAMD_NVMCTRL_ADDR 0x1C /* NVM address register */
  35. #define SAMD_NVMCTRL_LOCK 0x20 /* NVM Lock section register */
  36. #define SAMD_CMDEX_KEY 0xA5UL
  37. #define SAMD_NVM_CMD(n) ((SAMD_CMDEX_KEY << 8) | (n & 0x7F))
  38. /* NVMCTRL commands. See Table 20-4 in 42129F–SAM–10/2013 */
  39. #define SAMD_NVM_CMD_ER 0x02 /* Erase Row */
  40. #define SAMD_NVM_CMD_WP 0x04 /* Write Page */
  41. #define SAMD_NVM_CMD_EAR 0x05 /* Erase Auxilary Row */
  42. #define SAMD_NVM_CMD_WAP 0x06 /* Write Auxilary Page */
  43. #define SAMD_NVM_CMD_LR 0x40 /* Lock Region */
  44. #define SAMD_NVM_CMD_UR 0x41 /* Unlock Region */
  45. #define SAMD_NVM_CMD_SPRM 0x42 /* Set Power Reduction Mode */
  46. #define SAMD_NVM_CMD_CPRM 0x43 /* Clear Power Reduction Mode */
  47. #define SAMD_NVM_CMD_PBC 0x44 /* Page Buffer Clear */
  48. #define SAMD_NVM_CMD_SSB 0x45 /* Set Security Bit */
  49. #define SAMD_NVM_CMD_INVALL 0x46 /* Invalidate all caches */
  50. /* Known identifiers */
  51. #define SAMD_PROCESSOR_M0 0x01
  52. #define SAMD_FAMILY_D 0x00
  53. #define SAMD_SERIES_20 0x00
  54. #define SAMD_SERIES_21 0x01
  55. struct samd_part {
  56. uint8_t id;
  57. const char *name;
  58. uint32_t flash_kb;
  59. uint32_t ram_kb;
  60. };
  61. /* Known SAMD20 parts. See Table 12-8 in 42129F–SAM–10/2013 */
  62. static const struct samd_part samd20_parts[] = {
  63. { 0x0, "SAMD20J18A", 256, 32 },
  64. { 0x1, "SAMD20J17A", 128, 16 },
  65. { 0x2, "SAMD20J16A", 64, 8 },
  66. { 0x3, "SAMD20J15A", 32, 4 },
  67. { 0x4, "SAMD20J14A", 16, 2 },
  68. { 0x5, "SAMD20G18A", 256, 32 },
  69. { 0x6, "SAMD20G17A", 128, 16 },
  70. { 0x7, "SAMD20G16A", 64, 8 },
  71. { 0x8, "SAMD20G15A", 32, 4 },
  72. { 0x9, "SAMD20G14A", 16, 2 },
  73. { 0xB, "SAMD20E17A", 128, 16 },
  74. { 0xC, "SAMD20E16A", 64, 8 },
  75. { 0xD, "SAMD20E15A", 32, 4 },
  76. { 0xE, "SAMD20E14A", 16, 2 },
  77. };
  78. /* Known SAMD21 parts. */
  79. static const struct samd_part samd21_parts[] = {
  80. { 0x0, "SAMD21J18A", 256, 32 },
  81. { 0x1, "SAMD21J17A", 128, 16 },
  82. { 0x2, "SAMD21J16A", 64, 8 },
  83. { 0x3, "SAMD21J15A", 32, 4 },
  84. { 0x4, "SAMD21J14A", 16, 2 },
  85. { 0x5, "SAMD21G18A", 256, 32 },
  86. { 0x6, "SAMD21G17A", 128, 16 },
  87. { 0x7, "SAMD21G16A", 64, 8 },
  88. { 0x8, "SAMD21G15A", 32, 4 },
  89. { 0x9, "SAMD21G14A", 16, 2 },
  90. { 0xA, "SAMD21E18A", 256, 32 },
  91. { 0xB, "SAMD21E17A", 128, 16 },
  92. { 0xC, "SAMD21E16A", 64, 8 },
  93. { 0xD, "SAMD21E15A", 32, 4 },
  94. { 0xE, "SAMD21E14A", 16, 2 },
  95. };
  96. /* Each family of parts contains a parts table in the DEVSEL field of DID. The
  97. * processor ID, family ID, and series ID are used to determine which exact
  98. * family this is and then we can use the corresponding table. */
  99. struct samd_family {
  100. uint8_t processor;
  101. uint8_t family;
  102. uint8_t series;
  103. const struct samd_part *parts;
  104. size_t num_parts;
  105. };
  106. /* Known SAMD families */
  107. static const struct samd_family samd_families[] = {
  108. { SAMD_PROCESSOR_M0, SAMD_FAMILY_D, SAMD_SERIES_20,
  109. samd20_parts, ARRAY_SIZE(samd20_parts) },
  110. { SAMD_PROCESSOR_M0, SAMD_FAMILY_D, SAMD_SERIES_21,
  111. samd21_parts, ARRAY_SIZE(samd21_parts) },
  112. };
  113. struct samd_info {
  114. uint32_t page_size;
  115. int num_pages;
  116. int sector_size;
  117. bool probed;
  118. struct target *target;
  119. struct samd_info *next;
  120. };
  121. static struct samd_info *samd_chips;
  122. static const struct samd_part *samd_find_part(uint32_t id)
  123. {
  124. uint8_t processor = (id >> 28);
  125. uint8_t family = (id >> 24) & 0x0F;
  126. uint8_t series = (id >> 16) & 0xFF;
  127. uint8_t devsel = id & 0xFF;
  128. for (unsigned i = 0; i < ARRAY_SIZE(samd_families); i++) {
  129. if (samd_families[i].processor == processor &&
  130. samd_families[i].series == series &&
  131. samd_families[i].family == family) {
  132. for (unsigned j = 0; j < samd_families[i].num_parts; j++) {
  133. if (samd_families[i].parts[j].id == devsel)
  134. return &samd_families[i].parts[j];
  135. }
  136. }
  137. }
  138. return NULL;
  139. }
  140. static int samd_protect_check(struct flash_bank *bank)
  141. {
  142. int res;
  143. uint16_t lock;
  144. res = target_read_u16(bank->target,
  145. SAMD_NVMCTRL + SAMD_NVMCTRL_LOCK, &lock);
  146. if (res != ERROR_OK)
  147. return res;
  148. /* Lock bits are active-low */
  149. for (int i = 0; i < bank->num_sectors; i++)
  150. bank->sectors[i].is_protected = !(lock & (1<<i));
  151. return ERROR_OK;
  152. }
  153. static int samd_probe(struct flash_bank *bank)
  154. {
  155. uint32_t id, param;
  156. int res;
  157. struct samd_info *chip = (struct samd_info *)bank->driver_priv;
  158. const struct samd_part *part;
  159. if (chip->probed)
  160. return ERROR_OK;
  161. res = target_read_u32(bank->target, SAMD_DSU + SAMD_DSU_DID, &id);
  162. if (res != ERROR_OK) {
  163. LOG_ERROR("Couldn't read Device ID register");
  164. return res;
  165. }
  166. part = samd_find_part(id);
  167. if (part == NULL) {
  168. LOG_ERROR("Couldn't find part correspoding to DID %08" PRIx32, id);
  169. return ERROR_FAIL;
  170. }
  171. res = target_read_u32(bank->target,
  172. SAMD_NVMCTRL + SAMD_NVMCTRL_PARAM, &param);
  173. if (res != ERROR_OK) {
  174. LOG_ERROR("Couldn't read NVM Parameters register");
  175. return res;
  176. }
  177. bank->size = part->flash_kb * 1024;
  178. chip->sector_size = bank->size / SAMD_NUM_SECTORS;
  179. /* The PSZ field (bits 18:16) indicate the page size bytes as 2^(3+n) so
  180. * 0 is 8KB and 7 is 1024KB. */
  181. chip->page_size = (8 << ((param >> 16) & 0x7));
  182. /* The NVMP field (bits 15:0) indicates the total number of pages */
  183. chip->num_pages = param & 0xFFFF;
  184. /* Sanity check: the total flash size in the DSU should match the page size
  185. * multiplied by the number of pages. */
  186. if (bank->size != chip->num_pages * chip->page_size) {
  187. LOG_WARNING("SAMD: bank size doesn't match NVM parameters. "
  188. "Identified %uKB Flash but NVMCTRL reports %u %uB pages",
  189. part->flash_kb, chip->num_pages, chip->page_size);
  190. }
  191. /* Allocate the sector table */
  192. bank->num_sectors = SAMD_NUM_SECTORS;
  193. bank->sectors = calloc(bank->num_sectors, sizeof((bank->sectors)[0]));
  194. if (!bank->sectors)
  195. return ERROR_FAIL;
  196. /* Fill out the sector information: all SAMD sectors are the same size and
  197. * there is always a fixed number of them. */
  198. for (int i = 0; i < bank->num_sectors; i++) {
  199. bank->sectors[i].size = chip->sector_size;
  200. bank->sectors[i].offset = i * chip->sector_size;
  201. /* mark as unknown */
  202. bank->sectors[i].is_erased = -1;
  203. bank->sectors[i].is_protected = -1;
  204. }
  205. samd_protect_check(bank);
  206. /* Done */
  207. chip->probed = true;
  208. LOG_INFO("SAMD MCU: %s (%uKB Flash, %uKB RAM)", part->name,
  209. part->flash_kb, part->ram_kb);
  210. return ERROR_OK;
  211. }
  212. static int samd_protect(struct flash_bank *bank, int set, int first, int last)
  213. {
  214. int res;
  215. struct samd_info *chip = (struct samd_info *)bank->driver_priv;
  216. for (int s = first; s <= last; s++) {
  217. /* Load an address that is within this sector (we use offset 0) */
  218. res = target_write_u32(bank->target, SAMD_NVMCTRL + SAMD_NVMCTRL_ADDR,
  219. s * chip->sector_size);
  220. if (res != ERROR_OK)
  221. return res;
  222. /* Tell the controller to lock that sector */
  223. res = target_write_u16(bank->target,
  224. SAMD_NVMCTRL + SAMD_NVMCTRL_CTRLA,
  225. SAMD_NVM_CMD(SAMD_NVM_CMD_LR));
  226. if (res != ERROR_OK)
  227. return res;
  228. }
  229. samd_protect_check(bank);
  230. return ERROR_OK;
  231. }
  232. static bool samd_check_error(struct flash_bank *bank)
  233. {
  234. int ret;
  235. bool error;
  236. uint16_t status;
  237. ret = target_read_u16(bank->target,
  238. SAMD_NVMCTRL + SAMD_NVMCTRL_STATUS, &status);
  239. if (ret != ERROR_OK) {
  240. LOG_ERROR("Can't read NVM status");
  241. return true;
  242. }
  243. if (status & 0x001C) {
  244. if (status & (1 << 4)) /* NVME */
  245. LOG_ERROR("SAMD: NVM Error");
  246. if (status & (1 << 3)) /* LOCKE */
  247. LOG_ERROR("SAMD: NVM lock error");
  248. if (status & (1 << 2)) /* PROGE */
  249. LOG_ERROR("SAMD: NVM programming error");
  250. error = true;
  251. } else {
  252. error = false;
  253. }
  254. /* Clear the error conditions by writing a one to them */
  255. ret = target_write_u16(bank->target,
  256. SAMD_NVMCTRL + SAMD_NVMCTRL_STATUS, status);
  257. if (ret != ERROR_OK)
  258. LOG_ERROR("Can't clear NVM error conditions");
  259. return error;
  260. }
  261. static int samd_erase_row(struct flash_bank *bank, uint32_t address)
  262. {
  263. int res;
  264. bool error = false;
  265. /* Set an address contained in the row to be erased */
  266. res = target_write_u32(bank->target,
  267. SAMD_NVMCTRL + SAMD_NVMCTRL_ADDR, address >> 1);
  268. if (res == ERROR_OK) {
  269. /* Issue the Erase Row command to erase that row */
  270. res = target_write_u16(bank->target,
  271. SAMD_NVMCTRL + SAMD_NVMCTRL_CTRLA,
  272. SAMD_NVM_CMD(SAMD_NVM_CMD_ER));
  273. /* Check (and clear) error conditions */
  274. error = samd_check_error(bank);
  275. }
  276. if (res != ERROR_OK || error) {
  277. LOG_ERROR("Failed to erase row containing %08X" PRIx32, address);
  278. return ERROR_FAIL;
  279. }
  280. return ERROR_OK;
  281. }
  282. static int samd_erase(struct flash_bank *bank, int first, int last)
  283. {
  284. int res;
  285. int rows_in_sector;
  286. struct samd_info *chip = (struct samd_info *)bank->driver_priv;
  287. if (bank->target->state != TARGET_HALTED) {
  288. LOG_ERROR("Target not halted");
  289. return ERROR_TARGET_NOT_HALTED;
  290. }
  291. if (!chip->probed) {
  292. if (samd_probe(bank) != ERROR_OK)
  293. return ERROR_FLASH_BANK_NOT_PROBED;
  294. }
  295. /* The SAMD NVM has row erase granularity. There are four pages in a row
  296. * and the number of rows in a sector depends on the sector size, which in
  297. * turn depends on the Flash capacity as there is a fixed number of
  298. * sectors. */
  299. rows_in_sector = chip->sector_size / (chip->page_size * 4);
  300. /* For each sector to be erased */
  301. for (int s = first; s <= last; s++) {
  302. /* For each row in that sector */
  303. for (int r = s * rows_in_sector; r < (s + 1) * rows_in_sector; r++) {
  304. res = samd_erase_row(bank, r * chip->page_size * 4);
  305. if (res != ERROR_OK) {
  306. LOG_ERROR("SAMD: failed to erase sector %d", s);
  307. return res;
  308. }
  309. }
  310. bank->sectors[s].is_erased = 1;
  311. }
  312. return ERROR_OK;
  313. }
  314. static struct flash_sector *samd_find_sector_by_address(struct flash_bank *bank, uint32_t address)
  315. {
  316. struct samd_info *chip = (struct samd_info *)bank->driver_priv;
  317. for (int i = 0; i < bank->num_sectors; i++) {
  318. if (bank->sectors[i].offset <= address &&
  319. address < bank->sectors[i].offset + chip->sector_size)
  320. return &bank->sectors[i];
  321. }
  322. return NULL;
  323. }
  324. /* Write an entire row (four pages) from host buffer 'buf' to row-aligned
  325. * 'address' in the Flash. */
  326. static int samd_write_row(struct flash_bank *bank, uint32_t address,
  327. const uint8_t *buf)
  328. {
  329. int res;
  330. struct samd_info *chip = (struct samd_info *)bank->driver_priv;
  331. struct flash_sector *sector = samd_find_sector_by_address(bank, address);
  332. if (!sector) {
  333. LOG_ERROR("Can't find sector corresponding to address 0x%08" PRIx32, address);
  334. return ERROR_FLASH_OPERATION_FAILED;
  335. }
  336. if (sector->is_protected) {
  337. LOG_ERROR("Trying to write to a protected sector at 0x%08" PRIx32, address);
  338. return ERROR_FLASH_OPERATION_FAILED;
  339. }
  340. /* Erase the row that we'll be writing to */
  341. res = samd_erase_row(bank, address);
  342. if (res != ERROR_OK)
  343. return res;
  344. /* Now write the pages in this row. */
  345. for (unsigned int i = 0; i < 4; i++) {
  346. bool error;
  347. /* Write the page contents to the target's page buffer. A page write
  348. * is issued automatically once the last location is written in the
  349. * page buffer (ie: a complete page has been written out). */
  350. res = target_write_memory(bank->target, address, 4,
  351. chip->page_size / 4, buf);
  352. if (res != ERROR_OK) {
  353. LOG_ERROR("%s: %d", __func__, __LINE__);
  354. return res;
  355. }
  356. error = samd_check_error(bank);
  357. if (error)
  358. return ERROR_FAIL;
  359. /* Next page */
  360. address += chip->page_size;
  361. buf += chip->page_size;
  362. }
  363. sector->is_erased = 0;
  364. return res;
  365. }
  366. /* Write partial contents into row-aligned 'address' on the Flash from host
  367. * buffer 'buf' by writing 'nb' of 'buf' at 'row_offset' into the Flash row. */
  368. static int samd_write_row_partial(struct flash_bank *bank, uint32_t address,
  369. const uint8_t *buf, uint32_t row_offset, uint32_t nb)
  370. {
  371. int res;
  372. struct samd_info *chip = (struct samd_info *)bank->driver_priv;
  373. uint32_t row_size = chip->page_size * 4;
  374. uint8_t *rb = malloc(row_size);
  375. if (!rb)
  376. return ERROR_FAIL;
  377. assert(row_offset + nb < row_size);
  378. assert((address % row_size) == 0);
  379. /* Retrieve the full row contents from Flash */
  380. res = target_read_memory(bank->target, address, 4, row_size / 4, rb);
  381. if (res != ERROR_OK) {
  382. free(rb);
  383. return res;
  384. }
  385. /* Insert our partial row over the data from Flash */
  386. memcpy(rb + (row_offset % row_size), buf, nb);
  387. /* Write the row back out */
  388. res = samd_write_row(bank, address, rb);
  389. free(rb);
  390. return res;
  391. }
  392. static int samd_write(struct flash_bank *bank, const uint8_t *buffer,
  393. uint32_t offset, uint32_t count)
  394. {
  395. int res;
  396. uint32_t address;
  397. uint32_t nb = 0;
  398. struct samd_info *chip = (struct samd_info *)bank->driver_priv;
  399. uint32_t row_size = chip->page_size * 4;
  400. if (bank->target->state != TARGET_HALTED) {
  401. LOG_ERROR("Target not halted");
  402. return ERROR_TARGET_NOT_HALTED;
  403. }
  404. if (!chip->probed) {
  405. if (samd_probe(bank) != ERROR_OK)
  406. return ERROR_FLASH_BANK_NOT_PROBED;
  407. }
  408. if (offset % row_size) {
  409. /* We're starting at an unaligned offset so we'll write a partial row
  410. * comprising that offset and up to the end of that row. */
  411. nb = row_size - (offset % row_size);
  412. if (nb > count)
  413. nb = count;
  414. } else if (count < row_size) {
  415. /* We're writing an aligned but partial row. */
  416. nb = count;
  417. }
  418. address = (offset / row_size) * row_size + bank->base;
  419. if (nb > 0) {
  420. res = samd_write_row_partial(bank, address, buffer,
  421. offset % row_size, nb);
  422. if (res != ERROR_OK)
  423. return res;
  424. /* We're done with the row contents */
  425. count -= nb;
  426. offset += nb;
  427. buffer += row_size;
  428. }
  429. /* There's at least one aligned row to write out. */
  430. if (count >= row_size) {
  431. int nr = count / row_size + ((count % row_size) ? 1 : 0);
  432. unsigned int r = 0;
  433. for (unsigned int i = address / row_size;
  434. (i < (address / row_size) + nr) && count > 0; i++) {
  435. address = (i * row_size) + bank->base;
  436. if (count >= row_size) {
  437. res = samd_write_row(bank, address, buffer + (r * row_size));
  438. /* Advance one row */
  439. offset += row_size;
  440. count -= row_size;
  441. } else {
  442. res = samd_write_row_partial(bank, address,
  443. buffer + (r * row_size), 0, count);
  444. /* We're done after this. */
  445. offset += count;
  446. count = 0;
  447. }
  448. r++;
  449. if (res != ERROR_OK)
  450. return res;
  451. }
  452. }
  453. return ERROR_OK;
  454. }
  455. FLASH_BANK_COMMAND_HANDLER(samd_flash_bank_command)
  456. {
  457. struct samd_info *chip = samd_chips;
  458. while (chip) {
  459. if (chip->target == bank->target)
  460. break;
  461. chip = chip->next;
  462. }
  463. if (!chip) {
  464. /* Create a new chip */
  465. chip = calloc(1, sizeof(*chip));
  466. if (!chip)
  467. return ERROR_FAIL;
  468. chip->target = bank->target;
  469. chip->probed = false;
  470. bank->driver_priv = chip;
  471. /* Insert it into the chips list (at head) */
  472. chip->next = samd_chips;
  473. samd_chips = chip;
  474. }
  475. if (bank->base != SAMD_FLASH) {
  476. LOG_ERROR("Address 0x%08" PRIx32 " invalid bank address (try 0x%08" PRIx32
  477. "[at91samd series] )",
  478. bank->base, SAMD_FLASH);
  479. return ERROR_FAIL;
  480. }
  481. return ERROR_OK;
  482. }
  483. COMMAND_HANDLER(samd_handle_info_command)
  484. {
  485. return ERROR_OK;
  486. }
  487. static const struct command_registration at91samd_exec_command_handlers[] = {
  488. {
  489. .name = "info",
  490. .handler = samd_handle_info_command,
  491. .mode = COMMAND_EXEC,
  492. .help = "Print information about the current at91samd chip"
  493. "and its flash configuration.",
  494. },
  495. COMMAND_REGISTRATION_DONE
  496. };
  497. static const struct command_registration at91samd_command_handlers[] = {
  498. {
  499. .name = "at91samd",
  500. .mode = COMMAND_ANY,
  501. .help = "at91samd flash command group",
  502. .usage = "",
  503. .chain = at91samd_exec_command_handlers,
  504. },
  505. COMMAND_REGISTRATION_DONE
  506. };
  507. struct flash_driver at91samd_flash = {
  508. .name = "at91samd",
  509. .commands = at91samd_command_handlers,
  510. .flash_bank_command = samd_flash_bank_command,
  511. .erase = samd_erase,
  512. .protect = samd_protect,
  513. .write = samd_write,
  514. .read = default_flash_read,
  515. .probe = samd_probe,
  516. .auto_probe = samd_probe,
  517. .erase_check = default_flash_blank_check,
  518. .protect_check = samd_protect_check,
  519. };