samd21: Avoid divides in boot loader
[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         uint32_t lock_region = samd21_nvmctrl_lock_region();
42         uintptr_t a = (uintptr_t) addr;
43
44         while (lock_region) {
45                 a >>= 1;
46                 lock_region >>= 1;
47         }
48
49         return (uint8_t) a;
50 }
51
52 static uint8_t
53 ao_flash_is_locked(void *addr)
54 {
55         return (samd21_nvmctrl.lock >> ao_flash_lock_region_bit(addr)) & 1;
56 }
57
58 /* Execute a single flash operation, waiting for it to complete. This
59  * bit of code must be in ram
60  */
61 static void __attribute__ ((section(".sdata2.flash"), noinline))
62 _ao_flash_execute(uint16_t cmd)
63 {
64         while ((samd21_nvmctrl.intflag & (1 << SAMD21_NVMCTRL_INTFLAG_READY)) == 0)
65                 ;
66         samd21_nvmctrl.ctrla = ((cmd << SAMD21_NVMCTRL_CTRLA_CMD) |
67                                 (SAMD21_NVMCTRL_CTRLA_CMDEX_KEY << SAMD21_NVMCTRL_CTRLA_CMDEX));
68         while ((samd21_nvmctrl.intflag & (1 << SAMD21_NVMCTRL_INTFLAG_READY)) == 0)
69                 ;
70         samd21_nvmctrl.intflag = ((1 << SAMD21_NVMCTRL_INTFLAG_READY) |
71                                   (1 << SAMD21_NVMCTRL_INTFLAG_ERROR));
72 }
73
74 /* Set the address of the next flash operation */
75 static void
76 _ao_flash_set_addr(void *addr)
77 {
78         while ((samd21_nvmctrl.intflag & (1 << SAMD21_NVMCTRL_INTFLAG_READY)) == 0)
79                 ;
80         samd21_nvmctrl.addr = ((uint32_t) addr) >> 1;
81         while ((samd21_nvmctrl.intflag & (1 << SAMD21_NVMCTRL_INTFLAG_READY)) == 0)
82                 ;
83 }
84
85 /* Unlock a region of flash */
86 static void
87 _ao_flash_unlock(void *addr)
88 {
89         if (!ao_flash_is_locked(addr))
90                 return;
91
92         _ao_flash_set_addr(addr);
93         _ao_flash_execute(SAMD21_NVMCTRL_CTRLA_CMD_UR);
94 }
95
96 /* Erase a row of flash */
97 static void
98 _ao_flash_erase_row(void *row)
99 {
100         _ao_flash_unlock(row);
101         _ao_flash_set_addr(row);
102         _ao_flash_execute(SAMD21_NVMCTRL_CTRLA_CMD_ER);
103 }
104
105 void
106 ao_flash_erase_page(uint32_t *page)
107 {
108         uint8_t *row = (uint8_t *) page;
109         uint32_t row_size = samd21_nvmctrl_row_size();
110         uint32_t rows = (row_size + 255) / 256;
111
112         if ((uintptr_t) page & (row_size - 1))
113                 return;
114
115         ao_arch_block_interrupts();
116
117         if (((uintptr_t) row & (row_size - 1)) == 0) {
118                 while (rows--) {
119                         _ao_flash_erase_row(row);
120                         row += row_size;
121                 }
122         }
123
124         ao_arch_release_interrupts();
125 }
126
127 void
128 ao_flash_page(uint32_t *page, uint32_t *src)
129 {
130         uint32_t        page_shift = samd21_nvmctrl_page_shift();
131         uint32_t        pages = 256 >> page_shift;
132         uint32_t        i;
133         uint32_t        per_page = 1 << (page_shift - 2);
134
135         ao_flash_erase_page(page);
136
137         ao_arch_block_interrupts();
138
139         while(pages--) {
140                 /* Clear write buffer */
141                 _ao_flash_execute(SAMD21_NVMCTRL_CTRLA_CMD_PBC);
142                 _ao_flash_set_addr(page);
143                 for (i = 0; i < per_page; i++)
144                         *page++ = *src++;
145                 _ao_flash_execute(SAMD21_NVMCTRL_CTRLA_CMD_WP);
146         }
147
148         ao_arch_release_interrupts();
149 }
150