1 /*
2  * Copyright (c) 2022 BrainCo Inc.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "flash_gd32.h"
8 
9 #include <zephyr/logging/log.h>
10 #include <zephyr/kernel.h>
11 #include <gd32_fmc.h>
12 
13 LOG_MODULE_DECLARE(flash_gd32);
14 
15 #define GD32_NV_FLASH_V1_NODE		DT_INST(0, gd_gd32_nv_flash_v1)
16 #define GD32_NV_FLASH_V1_TIMEOUT	DT_PROP(GD32_NV_FLASH_V1_NODE, max_erase_time_ms)
17 #define GD32_NV_FLASH_V1_PAGE_SIZE	DT_PROP(GD32_NV_FLASH_V1_NODE, page_size)
18 
19 #if defined(CONFIG_SOC_SERIES_GD32E10X) || \
20 	defined(CONFIG_SOC_SERIES_GD32E50X)
21 /* Some GD32 FMC v1 series require offset and len to word aligned. */
22 #define GD32_FMC_V1_WORK_ALIGNED
23 #endif
24 
25 #ifdef FLASH_GD32_FMC_WORK_ALIGNED
26 #define GD32_FMC_V1_WRITE_ERR	(FMC_STAT_PGERR | FMC_STAT_WPERR | FMC_STAT_PGAERR)
27 #else
28 #define GD32_FMC_V1_WRITE_ERR	(FMC_STAT_PGERR | FMC_STAT_WPERR)
29 #endif
30 #define GD32_FMC_V1_ERASE_ERR	FMC_STAT_WPERR
31 
32 #ifdef CONFIG_FLASH_PAGE_LAYOUT
33 static const struct flash_pages_layout gd32_fmc_v1_layout[] = {
34 	{
35 	.pages_size = GD32_NV_FLASH_V1_PAGE_SIZE,
36 	.pages_count = SOC_NV_FLASH_SIZE / GD32_NV_FLASH_V1_PAGE_SIZE
37 	}
38 };
39 #endif
40 
gd32_fmc_v1_unlock(void)41 static inline void gd32_fmc_v1_unlock(void)
42 {
43 	FMC_KEY = UNLOCK_KEY0;
44 	FMC_KEY = UNLOCK_KEY1;
45 }
46 
gd32_fmc_v1_lock(void)47 static inline void gd32_fmc_v1_lock(void)
48 {
49 	FMC_CTL |= FMC_CTL_LK;
50 }
51 
gd32_fmc_v1_wait_idle(void)52 static int gd32_fmc_v1_wait_idle(void)
53 {
54 	const int64_t expired_time = k_uptime_get() + GD32_NV_FLASH_V1_TIMEOUT;
55 
56 	while (FMC_STAT & FMC_STAT_BUSY) {
57 		if (k_uptime_get() > expired_time) {
58 			return -ETIMEDOUT;
59 		}
60 	}
61 
62 	return 0;
63 }
64 
flash_gd32_valid_range(off_t offset,uint32_t len,bool write)65 bool flash_gd32_valid_range(off_t offset, uint32_t len, bool write)
66 {
67 	if ((offset > SOC_NV_FLASH_SIZE) ||
68 	    ((offset + len) > SOC_NV_FLASH_SIZE)) {
69 		return false;
70 	}
71 
72 	if (write) {
73 		/* Check offset and len is flash_prg_t aligned. */
74 		if ((offset % sizeof(flash_prg_t)) ||
75 		    (len % sizeof(flash_prg_t))) {
76 			return false;
77 		}
78 
79 #ifdef FLASH_GD32_FMC_WORK_ALIGNED
80 		/* Check offset and len is word aligned. */
81 		if ((offset % sizeof(uint32_t)) ||
82 		    (len % sizeof(uint32_t))) {
83 			return false;
84 		}
85 #endif
86 
87 	} else {
88 		if ((offset % GD32_NV_FLASH_V1_PAGE_SIZE) ||
89 		    (len % GD32_NV_FLASH_V1_PAGE_SIZE)) {
90 			return false;
91 		}
92 	}
93 
94 	return true;
95 }
96 
flash_gd32_write_range(off_t offset,const void * data,size_t len)97 int flash_gd32_write_range(off_t offset, const void *data, size_t len)
98 {
99 	flash_prg_t *prg_flash = (flash_prg_t *)((uint8_t *)SOC_NV_FLASH_ADDR + offset);
100 	flash_prg_t *prg_data = (flash_prg_t *)data;
101 	int ret = 0;
102 
103 	gd32_fmc_v1_unlock();
104 
105 	if (FMC_STAT & FMC_STAT_BUSY) {
106 		return -EBUSY;
107 	}
108 
109 	FMC_CTL |= FMC_CTL_PG;
110 
111 	for (size_t i = 0U; i < (len / sizeof(flash_prg_t)); i++) {
112 		*prg_flash++ = *prg_data++;
113 	}
114 
115 	ret = gd32_fmc_v1_wait_idle();
116 	if (ret < 0) {
117 		goto expired_out;
118 	}
119 
120 	if (FMC_STAT & GD32_FMC_V1_WRITE_ERR) {
121 		ret = -EIO;
122 		FMC_STAT |= GD32_FMC_V1_WRITE_ERR;
123 		LOG_ERR("FMC programming failed");
124 	}
125 
126 expired_out:
127 	FMC_CTL &= ~FMC_CTL_PG;
128 
129 	gd32_fmc_v1_lock();
130 
131 	return ret;
132 }
133 
gd32_fmc_v1_page_erase(uint32_t page_addr)134 static int gd32_fmc_v1_page_erase(uint32_t page_addr)
135 {
136 	int ret = 0;
137 
138 	gd32_fmc_v1_unlock();
139 
140 	if (FMC_STAT & FMC_STAT_BUSY) {
141 		return -EBUSY;
142 	}
143 
144 	FMC_CTL |= FMC_CTL_PER;
145 
146 	FMC_ADDR = page_addr;
147 
148 	FMC_CTL |= FMC_CTL_START;
149 
150 	ret = gd32_fmc_v1_wait_idle();
151 	if (ret < 0) {
152 		goto expired_out;
153 	}
154 
155 	if (FMC_STAT & GD32_FMC_V1_ERASE_ERR) {
156 		ret = -EIO;
157 		FMC_STAT |= GD32_FMC_V1_ERASE_ERR;
158 		LOG_ERR("FMC page %u erase failed", page_addr);
159 	}
160 
161 expired_out:
162 	FMC_CTL &= ~FMC_CTL_PER;
163 
164 	gd32_fmc_v1_lock();
165 
166 	return ret;
167 }
168 
flash_gd32_erase_block(off_t offset,size_t size)169 int flash_gd32_erase_block(off_t offset, size_t size)
170 {
171 	uint32_t page_addr = SOC_NV_FLASH_ADDR + offset;
172 	int ret = 0;
173 
174 	while (size > 0U) {
175 		ret = gd32_fmc_v1_page_erase(page_addr);
176 		if (ret < 0) {
177 			return ret;
178 		}
179 
180 		size -= GD32_NV_FLASH_V1_PAGE_SIZE;
181 		page_addr += GD32_NV_FLASH_V1_PAGE_SIZE;
182 	}
183 
184 	return 0;
185 }
186 
187 #ifdef CONFIG_FLASH_PAGE_LAYOUT
flash_gd32_pages_layout(const struct device * dev,const struct flash_pages_layout ** layout,size_t * layout_size)188 void flash_gd32_pages_layout(const struct device *dev,
189 			     const struct flash_pages_layout **layout,
190 			     size_t *layout_size)
191 {
192 	ARG_UNUSED(dev);
193 
194 	*layout = gd32_fmc_v1_layout;
195 	*layout_size = ARRAY_SIZE(gd32_fmc_v1_layout);
196 }
197 #endif /* CONFIG_FLASH_PAGE_LAYOUT */
198