blob: c382f9160ffc79aab7083e0850b92c36d1ba1337 [file] [log] [blame]
Michael Hopecd92dd12017-11-22 22:53:29 +01001/*
2 * Copyright (c) 2018 Google LLC.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
Anas Nashifffb75da2018-10-08 18:19:56 -04007#define LOG_LEVEL CONFIG_FLASH_LOG_LEVEL
8#include <logging/log.h>
9LOG_MODULE_REGISTER(flash_sam0);
Michael Hopecd92dd12017-11-22 22:53:29 +010010
11#include <device.h>
12#include <flash.h>
13#include <init.h>
14#include <kernel.h>
15#include <soc.h>
16#include <string.h>
17
18/*
19 * Zephyr and the SAM0 series use different and conflicting names for
20 * the erasable units and programmable units:
21 *
22 * The erase unit is a row, which is a 'page' in Zephyr terms.
23 * The program unit is a page, which is a 'write_block' in Zephyr.
24 *
25 * This file uses the SAM0 names internally and the Zephyr names in
26 * any error messages.
27 */
28
29/*
30 * Number of lock regions. The number is fixed and the region size
31 * grows with the flash size.
32 */
33#define LOCK_REGIONS 16
34#define LOCK_REGION_SIZE (FLASH_SIZE / LOCK_REGIONS)
35
36#define PAGES_PER_ROW 4
37#define ROW_SIZE (FLASH_PAGE_SIZE * PAGES_PER_ROW)
38
39#define FLASH_MEM(_a) ((u32_t *)((u8_t *)((_a) + CONFIG_FLASH_BASE_ADDRESS)))
40
41struct flash_sam0_data {
42#if CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES
43 u8_t buf[ROW_SIZE];
44 off_t offset;
45#endif
46
47 struct k_sem sem;
48};
49
Henrik Brix Andersen8c5b16c2018-04-12 22:41:42 +020050#if CONFIG_FLASH_PAGE_LAYOUT
Michael Hopecd92dd12017-11-22 22:53:29 +010051static const struct flash_pages_layout flash_sam0_pages_layout = {
52 .pages_count = CONFIG_FLASH_SIZE * 1024 / ROW_SIZE,
53 .pages_size = ROW_SIZE,
54};
Henrik Brix Andersen8c5b16c2018-04-12 22:41:42 +020055#endif
Michael Hopecd92dd12017-11-22 22:53:29 +010056
57static inline void flash_sam0_sem_take(struct device *dev)
58{
59 struct flash_sam0_data *ctx = dev->driver_data;
60
61 k_sem_take(&ctx->sem, K_FOREVER);
62}
63
64static inline void flash_sam0_sem_give(struct device *dev)
65{
66 struct flash_sam0_data *ctx = dev->driver_data;
67
68 k_sem_give(&ctx->sem);
69}
70
71static int flash_sam0_valid_range(off_t offset, size_t len)
72{
73 if (offset < 0) {
Anas Nashifffb75da2018-10-08 18:19:56 -040074 LOG_WRN("%x: before start of flash", offset);
Michael Hopecd92dd12017-11-22 22:53:29 +010075 return -EINVAL;
76 }
77 if ((offset + len) > CONFIG_FLASH_SIZE * 1024) {
Anas Nashifffb75da2018-10-08 18:19:56 -040078 LOG_WRN("%x: ends past the end of flash", offset);
Michael Hopecd92dd12017-11-22 22:53:29 +010079 return -EINVAL;
80 }
81
82 return 0;
83}
84
85static void flash_sam0_wait_ready(void)
86{
87 while (NVMCTRL->INTFLAG.bit.READY == 0) {
88 }
89}
90
91static int flash_sam0_check_status(off_t offset)
92{
93 NVMCTRL_STATUS_Type status;
94
95 flash_sam0_wait_ready();
96
97 status = NVMCTRL->STATUS;
98 /* Clear any flags */
99 NVMCTRL->STATUS = status;
100
101 if (status.bit.PROGE) {
Anas Nashifffb75da2018-10-08 18:19:56 -0400102 LOG_ERR("programming error at 0x%x", offset);
Michael Hopecd92dd12017-11-22 22:53:29 +0100103 return -EIO;
104 } else if (status.bit.LOCKE) {
Anas Nashifffb75da2018-10-08 18:19:56 -0400105 LOG_ERR("lock error at 0x%x", offset);
Michael Hopecd92dd12017-11-22 22:53:29 +0100106 return -EROFS;
107 } else if (status.bit.NVME) {
Anas Nashifffb75da2018-10-08 18:19:56 -0400108 LOG_ERR("NVM error at 0x%x", offset);
Michael Hopecd92dd12017-11-22 22:53:29 +0100109 return -EIO;
110 }
111
112 return 0;
113}
114
115static int flash_sam0_write_page(struct device *dev, off_t offset,
116 const void *data)
117{
118 const u32_t *src = data;
119 const u32_t *end = src + FLASH_PAGE_SIZE / sizeof(*src);
120 u32_t *dst = FLASH_MEM(offset);
121 int err;
122
123 NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_PBC | NVMCTRL_CTRLA_CMDEX_KEY;
124 flash_sam0_wait_ready();
125
126 /* Ensure writes happen 32 bits at a time. */
127 for (; src != end; src++, dst++) {
128 *dst = *src;
129 }
130
131 NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_WP | NVMCTRL_CTRLA_CMDEX_KEY;
132
133 err = flash_sam0_check_status(offset);
134 if (err != 0) {
135 return err;
136 }
137
138 if (memcmp(data, FLASH_MEM(offset), FLASH_PAGE_SIZE) != 0) {
Anas Nashifffb75da2018-10-08 18:19:56 -0400139 LOG_ERR("verify error at offset 0x%x", offset);
Michael Hopecd92dd12017-11-22 22:53:29 +0100140 return -EIO;
141 }
142
143 return 0;
144}
145
146static int flash_sam0_erase_row(struct device *dev, off_t offset)
147{
Patrik Flykt8ff96b52018-11-29 11:12:22 -0800148 *FLASH_MEM(offset) = 0U;
Michael Hopecd92dd12017-11-22 22:53:29 +0100149 NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_ER | NVMCTRL_CTRLA_CMDEX_KEY;
150
151 return flash_sam0_check_status(offset);
152}
153
154#if CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES
155
156static int flash_sam0_commit(struct device *dev)
157{
158 struct flash_sam0_data *ctx = dev->driver_data;
159 int err;
160 int page;
161 off_t offset = ctx->offset;
162
163 ctx->offset = 0;
164
165 if (offset == 0) {
166 return 0;
167 }
168
169 err = flash_sam0_erase_row(dev, offset);
170 if (err != 0) {
171 return err;
172 }
173
174 for (page = 0; page < PAGES_PER_ROW; page++) {
175 err = flash_sam0_write_page(
176 dev, offset + page * FLASH_PAGE_SIZE,
177 &ctx->buf[page * FLASH_PAGE_SIZE]);
178 if (err != 0) {
179 return err;
180 }
181 }
182
183 return 0;
184}
185
186static int flash_sam0_write(struct device *dev, off_t offset,
187 const void *data, size_t len)
188{
189 struct flash_sam0_data *ctx = dev->driver_data;
190 const u8_t *pdata = data;
191 off_t addr;
192 int err;
193
Anas Nashifffb75da2018-10-08 18:19:56 -0400194 LOG_DBG("%x: len %u", offset, len);
Michael Hopecd92dd12017-11-22 22:53:29 +0100195
196 err = flash_sam0_valid_range(offset, len);
197 if (err != 0) {
198 return err;
199 }
200
201 flash_sam0_sem_take(dev);
202
203 for (addr = offset; addr < offset + len; addr++) {
204 off_t base = addr & ~(ROW_SIZE - 1);
205
206 if (base != ctx->offset) {
207 /* Started a new row. Flush any pending ones. */
208 flash_sam0_commit(dev);
209 memcpy(ctx->buf, (void *)base, sizeof(ctx->buf));
210 ctx->offset = base;
211 }
212
213 ctx->buf[addr % ROW_SIZE] = *pdata++;
214 }
215
216 flash_sam0_commit(dev);
217 flash_sam0_sem_give(dev);
218
219 return 0;
220}
221
222#else /* CONFIG_SOC_FLASH_SAM0_EMULATE_BYTE_PAGES */
223
224static int flash_sam0_write(struct device *dev, off_t offset,
225 const void *data, size_t len)
226{
227 const u8_t *pdata = data;
228 int err;
229 size_t idx;
230
231 err = flash_sam0_valid_range(offset, len);
232 if (err != 0) {
233 return err;
234 }
235
236 if ((offset % FLASH_PAGE_SIZE) != 0) {
Anas Nashifffb75da2018-10-08 18:19:56 -0400237 LOG_WRN("%x: not on a write block boundrary", offset);
Michael Hopecd92dd12017-11-22 22:53:29 +0100238 return -EINVAL;
239 }
240
241 if ((len % FLASH_PAGE_SIZE) != 0) {
Anas Nashifffb75da2018-10-08 18:19:56 -0400242 LOG_WRN("%x: not a integer number of write blocks", len);
Michael Hopecd92dd12017-11-22 22:53:29 +0100243 return -EINVAL;
244 }
245
246 flash_sam0_sem_take(dev);
247
248 for (idx = 0; idx < len; idx += FLASH_PAGE_SIZE) {
249 err = flash_sam0_write_page(dev, offset + idx, &pdata[idx]);
250 if (err != 0) {
251 goto done;
252 }
253 }
254
255done:
256 flash_sam0_sem_give(dev);
257
258 return err;
259}
260
261#endif
262
263static int flash_sam0_read(struct device *dev, off_t offset, void *data,
264 size_t len)
265{
266 int err;
267
268 err = flash_sam0_valid_range(offset, len);
269 if (err != 0) {
270 return err;
271 }
272
273 memcpy(data, (u8_t *)CONFIG_FLASH_BASE_ADDRESS + offset, len);
274
275 return 0;
276}
277
278static int flash_sam0_erase(struct device *dev, off_t offset, size_t size)
279{
280 int err;
281
282 err = flash_sam0_valid_range(offset, ROW_SIZE);
283 if (err != 0) {
284 return err;
285 }
286
287 if ((offset % ROW_SIZE) != 0) {
Anas Nashifffb75da2018-10-08 18:19:56 -0400288 LOG_WRN("%x: not on a page boundrary", offset);
Michael Hopecd92dd12017-11-22 22:53:29 +0100289 return -EINVAL;
290 }
291
292 if ((size % ROW_SIZE) != 0) {
Anas Nashifffb75da2018-10-08 18:19:56 -0400293 LOG_WRN("%x: not a integer number of pages", size);
Michael Hopecd92dd12017-11-22 22:53:29 +0100294 return -EINVAL;
295 }
296
297 flash_sam0_sem_take(dev);
298
299 for (size_t addr = offset; addr < offset + size; addr += ROW_SIZE) {
300 err = flash_sam0_erase_row(dev, addr);
301 if (err != 0) {
302 goto done;
303 }
304 }
305
306done:
307 flash_sam0_sem_give(dev);
308
309 return err;
310}
311
312static int flash_sam0_write_protection(struct device *dev, bool enable)
313{
314 off_t offset;
315 int err;
316
317 flash_sam0_sem_take(dev);
318
319 for (offset = 0; offset < CONFIG_FLASH_SIZE * 1024;
320 offset += LOCK_REGION_SIZE) {
Patrik Flykt8ff96b52018-11-29 11:12:22 -0800321 *FLASH_MEM(offset) = 0U;
Michael Hopecd92dd12017-11-22 22:53:29 +0100322
323 if (enable) {
324 NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_LR |
325 NVMCTRL_CTRLA_CMDEX_KEY;
326 } else {
327 NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_UR |
328 NVMCTRL_CTRLA_CMDEX_KEY;
329 }
330 err = flash_sam0_check_status(offset);
331 if (err != 0) {
332 goto done;
333 }
334 }
335
336done:
337 flash_sam0_sem_give(dev);
338
339 return err;
340}
341
Henrik Brix Andersen8c5b16c2018-04-12 22:41:42 +0200342#if CONFIG_FLASH_PAGE_LAYOUT
Michael Hopecd92dd12017-11-22 22:53:29 +0100343void flash_sam0_page_layout(struct device *dev,
344 const struct flash_pages_layout **layout,
345 size_t *layout_size)
346{
347 *layout = &flash_sam0_pages_layout;
348 *layout_size = 1;
349}
Henrik Brix Andersen8c5b16c2018-04-12 22:41:42 +0200350#endif
Michael Hopecd92dd12017-11-22 22:53:29 +0100351
352static int flash_sam0_init(struct device *dev)
353{
354 struct flash_sam0_data *ctx = dev->driver_data;
355
356 k_sem_init(&ctx->sem, 1, 1);
357
358 /* Ensure the clock is on. */
359 PM->APBBMASK.bit.NVMCTRL_ = 1;
360 /* Require an explicit write command */
361 NVMCTRL->CTRLB.bit.MANW = 1;
362
363 return flash_sam0_write_protection(dev, false);
364}
365
366static const struct flash_driver_api flash_sam0_api = {
367 .write_protection = flash_sam0_write_protection,
368 .erase = flash_sam0_erase,
369 .write = flash_sam0_write,
370 .read = flash_sam0_read,
371#ifdef CONFIG_FLASH_PAGE_LAYOUT
372 .page_layout = flash_sam0_page_layout,
373#endif
374 .write_block_size = FLASH_PAGE_SIZE,
375};
376
377static struct flash_sam0_data flash_sam0_data_0;
378
Andrzej Głąbek20202902018-11-13 15:15:23 +0100379DEVICE_AND_API_INIT(flash_sam0, DT_FLASH_DEV_NAME,
Michael Hopecd92dd12017-11-22 22:53:29 +0100380 flash_sam0_init, &flash_sam0_data_0, NULL, POST_KERNEL,
381 CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &flash_sam0_api);