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