c1cee3f876e75e95b493e24866cd41e48a8cfee9
[fw/altos] / src / samd21 / ao_flash_samd21.c
1 /*
2  * Copyright © 2019 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 #include <ao.h>
20 #include <ao_flash.h>
21 #include <stdio.h>
22
23 /* Erase rows are four pages */
24 static uint32_t
25 samd21_nvmctrl_row_size(void)
26 {
27         return samd21_nvmctrl_page_size() * 4;
28 }
29
30 /* size of a lock region. That's just total flash size / 16 */
31 static uint32_t
32 samd21_nvmctrl_lock_region(void)
33 {
34         return samd21_flash_size() >> 4;
35 }
36
37 /* Find the bit index of an address within the lock word */
38 static uint8_t
39 ao_flash_lock_region_bit(void *addr)
40 {
41         return (uint8_t) (((uintptr_t) addr) / samd21_nvmctrl_lock_region());
42 }
43
44 static uint8_t
45 ao_flash_is_locked(void *addr)
46 {
47         return (samd21_nvmctrl.lock >> ao_flash_lock_region_bit(addr)) & 1;
48 }
49
50 /* Execute a single flash operation, waiting for it to complete. This
51  * bit of code must be in ram
52  */
53 static void __attribute__ ((section(".sdata2.flash"), noinline))
54 _ao_flash_execute(uint16_t cmd)
55 {
56         while ((samd21_nvmctrl.intflag & (1 << SAMD21_NVMCTRL_INTFLAG_READY)) == 0)
57                 ;
58         samd21_nvmctrl.ctrla = ((cmd << SAMD21_NVMCTRL_CTRLA_CMD) |
59                                 (SAMD21_NVMCTRL_CTRLA_CMDEX_KEY << SAMD21_NVMCTRL_CTRLA_CMDEX));
60         while ((samd21_nvmctrl.intflag & (1 << SAMD21_NVMCTRL_INTFLAG_READY)) == 0)
61                 ;
62         samd21_nvmctrl.intflag = ((1 << SAMD21_NVMCTRL_INTFLAG_READY) |
63                                   (1 << SAMD21_NVMCTRL_INTFLAG_ERROR));
64 }
65
66 /* Set the address of the next flash operation */
67 static void
68 _ao_flash_set_addr(void *addr)
69 {
70         while ((samd21_nvmctrl.intflag & (1 << SAMD21_NVMCTRL_INTFLAG_READY)) == 0)
71                 ;
72         samd21_nvmctrl.addr = ((uint32_t) addr) >> 1;
73         while ((samd21_nvmctrl.intflag & (1 << SAMD21_NVMCTRL_INTFLAG_READY)) == 0)
74                 ;
75 }
76
77 /* Unlock a region of flash */
78 static void
79 _ao_flash_unlock(void *addr)
80 {
81         if (!ao_flash_is_locked(addr))
82                 return;
83
84         _ao_flash_set_addr(addr);
85         _ao_flash_execute(SAMD21_NVMCTRL_CTRLA_CMD_UR);
86 }
87
88 /* Erase a row of flash */
89 static void
90 _ao_flash_erase_row(void *row)
91 {
92         _ao_flash_unlock(row);
93         _ao_flash_set_addr(row);
94         _ao_flash_execute(SAMD21_NVMCTRL_CTRLA_CMD_ER);
95 }
96
97 void
98 ao_flash_erase_page(uint32_t *page)
99 {
100         uint8_t *row = (uint8_t *) page;
101         uint32_t row_size = samd21_nvmctrl_row_size();
102         uint32_t rows = (row_size + 255) / 256;
103
104         ao_arch_block_interrupts();
105
106         if (((uintptr_t) row & (row_size - 1)) == 0) {
107                 while (rows--) {
108                         _ao_flash_erase_row(row);
109                         row += row_size;
110                 }
111         }
112
113         ao_arch_release_interrupts();
114 }
115
116 void
117 ao_flash_page(uint32_t *page, uint32_t *src)
118 {
119         uint32_t        page_size = samd21_nvmctrl_page_size();
120         uint32_t        pages = 256 / page_size;
121         uint32_t        i;
122         uint32_t        per_page = page_size / sizeof(uint32_t);
123
124         ao_arch_block_interrupts();
125
126         while(pages--) {
127                 /* Clear write buffer */
128                 _ao_flash_execute(SAMD21_NVMCTRL_CTRLA_CMD_PBC);
129                 _ao_flash_set_addr(page);
130                 for (i = 0; i < per_page; i++)
131                         *page++ = *src++;
132                 _ao_flash_execute(SAMD21_NVMCTRL_CTRLA_CMD_WP);
133         }
134
135         ao_arch_release_interrupts();
136 }
137