1 /*
2  * libtilemcore - Graphing calculator emulation library
3  *
4  * Copyright (C) 2001 Solignac Julien
5  * Copyright (C) 2004-2012 Benjamin Moody
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, see
19  * <http://www.gnu.org/licenses/>.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25 
26 #include <stdio.h>
27 #include "tilem.h"
28 
29 #define FLASH_READ 0
30 #define FLASH_AA   1
31 #define FLASH_55   2
32 #define FLASH_PROG 3
33 #define FLASH_ERASE 4
34 #define FLASH_ERAA 5
35 #define FLASH_ER55 6
36 #define FLASH_ERROR 7
37 #define FLASH_FASTMODE 8
38 #define FLASH_FASTPROG 9
39 #define FLASH_FASTEXIT 10
40 
41 #define FLASH_BUSY_PROGRAM 1
42 #define FLASH_BUSY_ERASE_WAIT 2
43 #define FLASH_BUSY_ERASE 3
44 
45 /* Still to do:
46    - autoselect
47    - erase suspend
48    - fast program
49    - CFI
50  */
51 
52 #define WARN(xxx) \
53 	tilem_warning(calc, "Flash error (" xxx ")")
54 #define WARN2(xxx, yyy, zzz) \
55 	tilem_warning(calc, "Flash error (" xxx ")", (yyy), (zzz))
56 
tilem_flash_reset(TilemCalc * calc)57 void tilem_flash_reset(TilemCalc* calc)
58 {
59 	calc->flash.unlock = 0;
60 	calc->flash.state = FLASH_READ;
61 	calc->flash.busy = 0;
62 }
63 
tilem_flash_delay_timer(TilemCalc * calc,void * data TILEM_ATTR_UNUSED)64 void tilem_flash_delay_timer(TilemCalc* calc, void* data TILEM_ATTR_UNUSED)
65 {
66 	if (calc->flash.busy == FLASH_BUSY_ERASE_WAIT) {
67 		calc->flash.busy = FLASH_BUSY_ERASE;
68 		tilem_z80_set_timer(calc, TILEM_TIMER_FLASH_DELAY,
69 				    200000, 0, 1);
70 	}
71 	else {
72 		calc->flash.busy = 0;
73 	}
74 }
75 
76 #ifdef DISABLE_FLASH_DELAY
77 # define set_busy(fl, bm, t)
78 #else
set_busy(TilemCalc * calc,int busymode,int time)79 static inline void set_busy(TilemCalc* calc, int busymode, int time)
80 {
81 	if (!(calc->flash.emuflags & TILEM_FLASH_REQUIRE_DELAY))
82 		return;
83 
84 	calc->flash.busy = busymode;
85 	tilem_z80_set_timer(calc, TILEM_TIMER_FLASH_DELAY,
86 			    time, 0, 1);
87 }
88 #endif
89 
program_byte(TilemCalc * calc,dword a,byte v)90 static inline void program_byte(TilemCalc* calc, dword a, byte v)
91 {
92 	calc->mem[a] &= v;
93 	calc->flash.progaddr = a;
94 	calc->flash.progbyte = v;
95 
96 	if (calc->mem[a] != v) {
97 		WARN2("bad program %02x over %02x", v, calc->mem[a]);
98 		calc->flash.state = FLASH_ERROR;
99 	}
100 	else {
101 		calc->flash.state = FLASH_READ;
102 	}
103 
104 	set_busy(calc, FLASH_BUSY_PROGRAM, 7);
105 }
106 
erase_sector(TilemCalc * calc,dword a,dword l)107 static inline void erase_sector(TilemCalc* calc, dword a, dword l)
108 {
109 	dword i;
110 
111 	calc->flash.progaddr = a;
112 	for (i = 0; i < l; i++)
113 		calc->mem[a + i]=0xFF;
114 	calc->flash.state = FLASH_READ;
115 
116 	set_busy(calc, FLASH_BUSY_ERASE_WAIT, 50);
117 }
118 
get_sector(TilemCalc * calc,dword pa)119 static const TilemFlashSector* get_sector(TilemCalc* calc, dword pa)
120 {
121 	int i;
122 	const TilemFlashSector* sec;
123 
124 	for (i = 0; i < calc->hw.nflashsectors; i++) {
125 		sec = &calc->hw.flashsectors[i];
126 		if (pa >= sec->start && pa < sec->start + sec->size)
127 			return sec;
128 	}
129 
130 	return NULL;
131 }
132 
sector_writable(TilemCalc * calc,const TilemFlashSector * sec)133 static int sector_writable(TilemCalc* calc, const TilemFlashSector* sec)
134 {
135 	return !(sec->protectgroup & ~calc->flash.overridegroup);
136 }
137 
tilem_flash_read_byte(TilemCalc * calc,dword pa)138 byte tilem_flash_read_byte(TilemCalc* calc, dword pa)
139 {
140 	byte value;
141 
142 	if (calc->flash.busy == FLASH_BUSY_PROGRAM) {
143 		if (pa != calc->flash.progaddr)
144 			WARN("reading from Flash while programming");
145 		value = (~calc->flash.progbyte & 0x80);
146 		value |= calc->flash.toggles;
147 		calc->flash.toggles ^= 0x40;
148 		return (value);
149 	}
150 	else if (calc->flash.busy == FLASH_BUSY_ERASE) {
151 		if ((pa >> 16) != (calc->flash.progaddr >> 16))
152 			WARN("reading from Flash while erasing");
153 		value = calc->flash.toggles | 0x08;
154 		calc->flash.toggles ^= 0x44;
155 		return (value);
156 	}
157 	else if (calc->flash.busy == FLASH_BUSY_ERASE_WAIT) {
158 		if ((pa >> 16) != (calc->flash.progaddr >> 16))
159 			WARN("reading from Flash while erasing");
160 		value = calc->flash.toggles;
161 		calc->flash.toggles ^= 0x44;
162 		return (value);
163 	}
164 
165         if (calc->flash.state == FLASH_ERROR) {
166 		value = ((~calc->flash.progbyte & 0x80) | 0x20);
167 		value |= calc->flash.toggles;
168 		calc->flash.toggles ^= 0x40;
169 		return (value);
170 	}
171 	else if (calc->flash.state == FLASH_FASTMODE) {
172 		return (calc->mem[pa]);
173 	}
174 	else if (calc->flash.state == FLASH_READ) {
175 		return (calc->mem[pa]);
176 	}
177 	else {
178 		WARN("reading during program/erase sequence");
179 		calc->flash.state = FLASH_READ;
180 		return (calc->mem[pa]);
181 	}
182 }
183 
tilem_flash_erase_address(TilemCalc * calc,dword pa)184 void tilem_flash_erase_address(TilemCalc* calc, dword pa)
185 {
186 	const TilemFlashSector* sec = get_sector(calc, pa);
187 
188 	if (sector_writable(calc, sec)) {
189 		tilem_message(calc, "Erasing Flash sector at %06x", pa);
190 		erase_sector(calc, sec->start, sec->size);
191 	}
192 	else {
193 		WARN("erasing protected sector");
194 	}
195 }
196 
tilem_flash_write_byte(TilemCalc * calc,dword pa,byte v)197 void tilem_flash_write_byte(TilemCalc* calc, dword pa, byte v)
198 {
199 	int oldstate;
200 	int i;
201 	const TilemFlashSector* sec;
202 
203 	if (!calc->flash.unlock)
204 		return;
205 
206 #ifndef DISABLE_FLASH_DELAY
207 	if (calc->flash.busy == FLASH_BUSY_PROGRAM
208 	    || calc->flash.busy == FLASH_BUSY_ERASE)
209 		return;
210 #endif
211 
212 	oldstate = calc->flash.state;
213 	calc->flash.state = FLASH_READ;
214 
215 	switch (oldstate) {
216 	case FLASH_READ:
217 		if (((pa&0xFFF) == 0xAAA) && (v == 0xAA))
218 			calc->flash.state = FLASH_AA;
219 		return;
220 
221 	case FLASH_AA:
222 		if (((pa&0xFFF) == 0x555) && (v == 0x55))
223 			calc->flash.state = FLASH_55;
224 		else if (v != 0xF0) {
225 			WARN2("undefined command %02x->%06x after AA", v, pa);
226 		}
227 		return;
228 
229 	case FLASH_55:
230 		if ((pa&0xFFF) == 0xAAA) {
231 			switch (v) {
232 			case 0x10:
233 			case 0x30:
234 				WARN("attempt to erase without pre-erase");
235 				return;
236 			case 0x20:
237 				//WARN("entering fast mode");
238 				calc->flash.state = FLASH_FASTMODE;
239 				return;
240 			case 0x80:
241 				calc->flash.state = FLASH_ERASE;
242 				return;
243 			case 0x90:
244 				WARN("autoselect is not implemented");
245 				return;
246 			case 0xA0:
247 				calc->flash.state = FLASH_PROG;
248 				return;
249 			}
250 		}
251 		if (v != 0xF0)
252 			WARN2("undefined command %02x->%06x after AA,55", v, pa);
253 		return;
254 
255 	case FLASH_PROG:
256 		sec = get_sector(calc, pa);
257 		if (!sector_writable(calc, sec))
258 			WARN("programming protected sector");
259 		else
260 			program_byte(calc, pa, v);
261 		return;
262 
263 	case FLASH_FASTMODE:
264 		//WARN2("fast mode cmd %02x->%06x", v, pa);
265 		if ( v == 0x90 )
266 			calc->flash.state = FLASH_FASTEXIT;
267 		else if ( v == 0xA0 )
268 			calc->flash.state = FLASH_FASTPROG;
269 		else
270 			// TODO : figure out whether mixing is allowed on real HW
271 			WARN2("mixing fast programming with regular programming : %02x->%06x", v, pa);
272 		return;
273 
274 	case FLASH_FASTPROG:
275 		//WARN2("fast prog %02x->%06x", v, pa);
276 		sec = get_sector(calc, pa);
277 		if (!sector_writable(calc, sec))
278 			WARN("programming protected sector");
279 		else
280 			program_byte(calc, pa, v);
281 		calc->flash.state = FLASH_FASTMODE;
282 		return;
283 
284 	case FLASH_FASTEXIT:
285 		//WARN("leaving fast mode");
286 		if ( v != 0xF0 )
287 		{
288 			WARN2("undefined command %02x->%06x after fast mode pre-exit 90", v, pa);
289 			// TODO : figure out whether fast mode remains in such a case
290 			calc->flash.state = FLASH_FASTMODE;
291 		}
292 		return;
293 
294 	case FLASH_ERASE:
295 		if (((pa&0xFFF) == 0xAAA) && (v == 0xAA))
296 			calc->flash.state = FLASH_ERAA;
297 		else if (v != 0xF0)
298 			WARN2("undefined command %02x->%06x after pre-erase", v, pa);
299 		return;
300 
301 	case FLASH_ERAA:
302 		if (((pa&0xFFF) == 0x555) && (v == 0x55))
303 			calc->flash.state = FLASH_ER55;
304 		else if (v != 0xF0)
305 			WARN2("undefined command %02x->%06x after pre-erase AA", v, pa);
306 		return;
307 
308 	case FLASH_ER55:
309 		if (((pa&0xFFF) == 0xAAA) && v==0x10) {
310 			tilem_message(calc, "Erasing entire Flash chip");
311 
312 			for (i = 0; i < calc->hw.nflashsectors; i++) {
313 				sec = &calc->hw.flashsectors[i];
314 				if (sector_writable(calc, sec))
315 					erase_sector(calc, sec->start,
316 						     sec->size);
317 			}
318 		}
319 		else if (v == 0x30) {
320 			tilem_flash_erase_address(calc, pa);
321 		}
322 		else if (v != 0xF0)
323 			WARN2("undefined command %02x->%06x after pre-erase AA,55", v, pa);
324 		return;
325 	}
326 }
327