From 5ceb148c857fb3f66daf3c4e0b95d707eddc985d Mon Sep 17 00:00:00 2001 From: Paul Molloy Date: Thu, 24 Aug 2017 13:41:08 -0500 Subject: [PATCH] Have SNES flashing and dumping working for all 3 kazzos on SNES v3.0p prototype which has STM8 CIC driving flash /OE with inversion of SYS /RST. STM8 CIC is running at 16Mhz, and doesn't actually function as CIC. Still need to come up with special way to signal to CIC that it's plugged into a programmer and not a console. Things aren't as fast as they could be, but they're good for now and proved working on all kazzo versions. Expecting decent speedup could be aquired by optimizing the flash routine, not changing address unless needed, or only changing low byte of address, etc. Could also let the host put the flash chip in unlock bypass mode and keep it there until done with flashing. Current speeds: INL6: 42.2 KBps flashing, 92KBps dumping stm adapter: 25.3 KBps flashing, 96KBps dumping AVR kazzo: 18.0KBps flashing, 14.3KBps dumping Was able to get the inl6 up to 59KBps flashing. Which was 35sec total flash time for 16mbit chip which has typical flash time of 22s plus overhead. This got slowed down when supporting stm adapter as checks for buffer status were required from what I recall. Also fixing flash polling routine AVR found slowed things down. Was able to get 140KBps dump time on inl6 with 16mbit SNES flash. This was slowed when supporting stm adapter which brought out issue with stm32 usb driver. Locks up the device if the buffer isn't fully dumped prior to calling. Need to get driver to support sending NAKs until data is dumped. Current fix for checking buffer status slows things down for all devices. AVR brought out issue with SNES v3 design where we can't rely on flash poll data to toggle between reads as /OE and /CE are stuck low. Have to toggle /RESET slowly to toggle /OE and ensure we don't move on to next byte until previous is fully flashed. STM32 found initial issue where /WR should be set low first to set direction of data level shifter, then set /ROMSEL low to enable level shifter output. Not doing this caused bus conflicts between the two causing flakey writes where not all bits were getting cleared. lua scripts currently force SNES, need to add smart check that identifies SNES flash board if vector data is 0xFFFF. Also funky order where it always erases after flashing as this was more convient for testing. While this commit is far from ideal, it's stable and I've done my best to not commit junk that will cause problems later. Just make sure to always verify dumping algo before assuming something is wrong with writes! --- .gitignore | 3 + firmware/build_avr/avr_kazzo.elf | Bin 19624 -> 20504 bytes firmware/build_avr/avr_kazzo.hex | 730 +++++++++++---------- firmware/build_stm/inlretro_stm.bin | Bin 7736 -> 8408 bytes firmware/build_stm/inlretro_stm.elf | Bin 110928 -> 115608 bytes firmware/build_stm/inlretro_stm.hex | 982 +++++++++++++++------------- firmware/build_stm/inlretro_stm.map | 322 ++++----- firmware/source/flash.c | 139 ++++ firmware/source/pinport_al.h | 1 + firmware/source/snes.c | 61 +- firmware/source/snes.h | 1 + host/scripts/app/buffers.lua | 8 +- host/scripts/app/cart.lua | 3 + host/scripts/app/dump.lua | 59 +- host/scripts/app/erase.lua | 50 ++ host/scripts/app/flash.lua | 124 ++++ host/scripts/app/snes.lua | 12 +- host/scripts/inlretro.lua | 46 +- 18 files changed, 1541 insertions(+), 1000 deletions(-) diff --git a/.gitignore b/.gitignore index ae82c28..74af85d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ ######################################## *.o *.swp +*.bin +*.bak +*.smc # ignore shared files that have been copied to host-firmware dirs firmware/source/shared_*.h diff --git a/firmware/build_avr/avr_kazzo.elf b/firmware/build_avr/avr_kazzo.elf index 87471ad809c322f1414f826e4b61503757711eb9..64a6b4e75bc6c7b1c2702010e7fb6c79eef89b74 100644 GIT binary patch delta 8348 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 delta 7594 zcmaKx33wD$w#TcwlCCP2q&pCk5QHoU17gyNQ8p0*VGCOjki}0DKq7GzA&A1ro3836 zkU&VfyAo&`75HQv#03NIDLsp}`i8+L>4JHn12alRh=k2_v?0K-y#MJXDe~U;s&nh! z-?``BbMCoIRd?LG*?l`$N1CCi(3z1j|G8v_p%lw7i~)&7x&dZbDI_!{|6)(GjB^TWauzE8&8A!P*n&(kIMugODdkF( zWt?;|plZ$xHsH);?Q-SlJ=bF7L|JOF+oRGXTeM`Cr4enx&9Xeq9FX$93Tn<}T)OQlWnqA6&O-_ zbZtYfi?0e+oOfz*zNMD3NKI29Tyq+E#iK5M-7iA@bC!$k;dW(_+QpID1y&SnIWE4e z{U7RvADl2$agOe`Cc1}bI<8JO+D40kRlc~o1qMkmvSieuWFk5lY`At$@VFC;4YTdQ zY=5pbCJDh}UsNZpJbx7l8{R6EHs zVk$FF1h&}EI#p+Xr%+FQC})fXNmUiL9Jb98lyVjXw$J|*Q+EbohN z&5{e{YWYnWofvEF7uAx|tIABFRJp^CGMo~Xk;2ENr^NqcZ?Izh+N?;Pa7|6$_D7Yr z&HLYcnrN4_{?2&#QnEa*wYQe}Q=TSiadj_nzoNm#RU}gJt zHM|n~yBa_F{u7(Ge`{}Vy|)bYZe#Dmy}f1fGH;P*z8hY6dLNGVC)9_}37faJb%Xbm z7ur^+kM(?eC*Mt{5?%I&vF>~iIUv-xy6(C3hSsw*tgyLjhok1kxx)s>dfIP?`=K7j z=?vdKpxEqhy!~Xgxt^tfM_*vO%k$ zQNqSo4ZiZ@T5oSQ&3fGJ#(#hL-QqKr=E+c z&ZY$(p4FmQpVqF%)g{LMOHJ2quxXgPCt@#l%=60FbLv?aO+fT~56|^_ws-d5vS|L6 zb$fV@o|?Vf=As`{jRBJztW9TSq)tQNVg^HFG0>70&a#0YsqCdjzN^mBW+ehTvk87#a^BlcG+H($4!SU`_tw zP_VuWsh#nMXLk&oy0EA{G`rbCuJD*JTO2No5}p+Dg#uxcP$*0niiFva>Ob=YT9SwT zv>5-xPfPKjpBCZ)KP|)e{j><*_0tl3$LgoW^tPXt-)=uGzBm1}^gMoAc-#HOp7F?) zke0)5)2fxQV>^79!v$f-UKch9TZNm#Her{rPiPSSAbcqt6HW==3l|{Oe*(hB26?4< zTeV`|p0KU2OVX^R{*4W`l@2Xo+Z##^#FF+bBpdFN%z;E&rPrBCl5)Ld97$f$OGc1n ztzKgBS9LKz$O_T@=KDcXd7t@2NbWb*gCzGE<4AI!v0IH@jkPuQb!~0=sj>Ir+SUpj z)E7~*hKuJ>#2RR%;}z#X;{@E#G};@&p^_1HN&8!4_(elT-7#p|kRNR{qupOXB>lW` zTbd~E$Ica%X-R!2RdG9Hr@5QY{S#(-db&9#pqkc3j&j zJMkr=rp3__N?ViTyhAZ72a-~s4+nRHc3DlgXg+qeXF`H z3j#YVDX^k&pW|)9V-bUYRweC^YUag9HCg*mb<5lD)X0t&*ueLN7!1Op`DC{_ zIM?K&S;0B(wRQB;y$>(i_`%Q4s{;CEQEh;;Fub)pyM5+W!?p;2&sUaWjN;dB(;77!b0e1`zLTr2eZr(aP*2{ z+U|W1IFlHa7?__>JgVb6;A1-O4yV4+aT56ZPPQ_c5Cl2}%uw)k9X|#(J_+#*Iqin^x_#A@GQ33eBz5Eq2|=7rVJLXBjz@uObX*8_=y(Cxt>fk3avhh059)Y5__tw(eK@cY0-sL70p6%% z5BQjl>E|<-jt_$0(eaLVpF&rvZpOIS{rVa-cT^1+eyFL}4&^uCBuo z;OnTzU#Npg;8@gSK!^a#6oI2x8V2wT_<2rZQbP3{^CDOu;n#?*)EXB>zy^Un0&jzd zpgssOd3+GO3~k5Mhb#(-_RJzJUo` zfHXN`AQfc?41`C_F$2I?P)8R{Np2T@5_~T*r4*5gU=8QUM(ERk77Cbur>?_aLxI5v z!y{msSHYcg$TCwfr=90Q4D)ZO*E?LI?xF5WW?t*W@yHVEi5z#lmAVx&D(4tP&IY5gx z-3o!Cb08P2AJLOtxDZ?ieOmQ&4$KD6fkSVB$-$-IAqeq|2(ReU{x@KXFkQ5*t(&?i zC}2F$LKkhr{oo@=(Gh=va%ctOPW7k3dWW0AC-E?fKJ9LS7vd-`f<8IOELl@px~x2F zDN|fLyQo;{K7M5Suw3T3H7~7%tn|gjtCu4$dl|Qv7MHC+wqn_8rDS|c=F&Cg`SYhO ze(t$)hKiHJ#Wky!K&SlGteoK^au01C{~4!DpLF1mdGbhBK+EE?6=km|g;N$N2c}Gp zCEFIIZ{e4w)i0O6^4j9^WyR>Hc>J`f%J+qtZ=0vG@g4H{A^D-yX`Jjmv!C|@Akm!e!k_NzWh!pwmNN2W3o z*^D8|5@aWaDw~mgGDSH!bKuya*;t}D^=XA|LZaC1@^#6CFC@^HXvxRK2&7%82 zzu>La(Pabg8{7r~taTRp1AS#++2H$BqHPeM)2i`qMgFz)Ga9Tz@kmwT$nR>l9j*va zy9&ZkLiJDy^-@LPEy2Pmb`xKrP;HigVlSXf+?xUwqY}TqbkcotX^wQ#MOEZ@O8ocR zrobco>+aWU$H<=qC@ss3F@W zFJOw8Qs#PQF(WXm84n{dGV>#58?&2mDZAdU2}8JyMfe({-sDK3%g0l6{J_nq27y7scgzVp0;Fl4?Ic-M%Nq(fCx& zbt5(UWFIJcT|w3Vr`PW!v%%ZadB!Z9n-2_ExHQ^Qhj(&rd9Yu6-Yo-Ftg*5=@68 zcve>m*TW*rU9cQhgBPi1bsNw+G{b}N2s{Z-!#=bLioJu$)og(Y_o(H3kfk&vhbQGA zmi1SZ16Vd*QC6_bT~YR9IW;2cIvL^45d<}gAh({^+pC0J)I3tUC0$RnnjklgAgIj* zS(-+O787L2d7?Qvk_9amu-{JC6Rjon*Nq^ktpq7aBScFH!k(w>Gtzt%oHyEw)1?Wc z#viN6_wRop^0lCRr=r#Uns$N|spxcoJ!Air$X2lA^aIdr-;#4``*1RB7`$ian~$a?d$E`?5xD zykeZ&&yh2FvnE6TKvIUQ=wc{^Ik1YZ1P;Cf)kv+<i{5L$}GB9(ZKeGy9V>7x?PH#I08dHz#l$B9j8& zFme3+lOs7oU|&^t`7)|JLO(%20s;mKFAqLJytv<`}`?Hc7-oib)w_@}5`w#to1=l-!f#u07P= zuP$4~z#oKn(`|pBZhNu+E#XuOC%cpAe^dCaIBf0IzK{DO?vpOT*9J6a;zjL*eAxf0 z2yB6`&9G0V?1ZZ`{fhlpLP5&MJSH5wVub(bKO!6vhdmeE|NdVH;VWi*rT?IC@QU__ z`d<);pQ`q?5tg&Pw&7cK}mD9N4r^wpPJ-L=-()8O7pb`{ew=)O(!|~QHryB z(wtTF-|7lwEcWG%*~V-eo6S4DK{jITU?R{1%Y8cT2iPO&!K-~!`ya)1TeR=V+V=$Q zJ6HQ2*MG=Qm5z%-X-1STT^dzNA$GiXR}s|uq%qYP3jDM9MT9@|vY_-`jw zd8F!?Yl_bzjp1mNThbp*uyD=wF$?;p1ewhL*<-Q#*}Z)fcIkMEg}?! zZySu zVgW8f!Z83v7vsq@^iA zMT$I>B*Ww9FwyU$$5E&J`2!x#Aa?K8sg4YNPJc z%N$?D$eTW+yiv9^@0Gj#e~}xS*^Qy*#*HEW-i>SS{b=L*-uoTU^=z-~fYY0LJ&qMo zuVY=b&T$5N76e#%pG=n*WH-tm$u+&I-RfSkk}5Yk24WUriu<&@Q?>~2$RwxOhviA` zcjSjzbcW~> zxsQk*5uGABG6CO%voI~hgxX~dV3G)pb5IrQyLXQ*$tI*cC^3#1HGeyUt8wc)<}DSXU1z4O*8U~4IA(e$RRwf9hv zeIma_?v@*xLQNK>8>QZW^6o}SH{5-T=WcQRk`~-K(z9RV;2In1e3qOg0ssQ*Ne4Oyd#qcRwYKToZpCG{U*Lh2$}QzR7K^g zo;cyDAmi0DR9hwTY~mC)k2djygX_ftzk*{8_5F0H5J!iS15w46!#mu#9=l0?Hcrl1 zX9+}O7Zm#v>7<+^;X0do{6WN?y0Fm!(I+m95!(X=QoWQdw+HH_yleqAm@aQ<+IoTL z0g0I`Bf5ZTH724LD=`tBA^I{4Qwt_5YS+`4rs)8dVD7@?K-z zK5;@-%gO*UFm6+g$yyH3it}WUc34N^H{-XfDv95W6UPgyz2n7gV8)x=;%V@Wb!bkQM;~GT0#uIrdl2wpDz1!eJ{|5PyZyX+jtYfUGXl=>GYZD+aYPxuW6qlgU&1r;b->hiZf!_Vvaip(VUn8 z#aW0zs}LzW$3T4B*%8r`{KcEH3NQ6J0f zD>z~ojK{}S+X4?cKkYQwb;{>)q7NjdNMeW_o>M2LC78&$uEIoOk>s48!1LRPZHD3i zudz$S4qfX8`A9Y&Y7A{y>~>aXbD>6st@q{WviSz@B4Lr+C@rcn@J5dj{Z?g$(G!)< zp|>8x6VGCc`50}PjjxF_(meNA{tUJ%L!UD$krsz-t9@=A#niojHFLPB9;NU@ z=?)#KpDGokk4XP13`7)=o?L$gfP! z5BjjG`mCz?uc#lymnG+C_x8;{b~S4FO{d;6=^5AfYv zySG#GNDk~TU2kkN}`H6K9_DsNSmk1E?{_{GY4UnD%Aqsl41BV6x& z(wXQ;+TvDkV^b$adkUodK(`!ft}|Pd=Cd8)EIJw|Jb0|H#&2`=WPQgM$~edz9Gf@B z!AE8W3!UE2nZM&3b%U>h^VHlXmE%kKcCW_u&Ia+jK6;ljb2k6Os=3Ws^la1+f!mD*UMPbHXP^U_xG9 zWytGoIuKW8%;e|N72*4ushta&KMTL;%1%ssU_gGgxy`&lq2lefg)X)4JC|44 z7GS>k@*dkf%)fn^YWpHQjo&OC4d<^b!#%z3a-@pn#>;vj`IyT;asDkv%{ds3#YfNa z`_t{VVjSb8!Bw_dm>(M4V=Kh`#ldfmxpy!*=G%kGF@HNaJZ2ZMv8aDE+Nx!k$UTe~ zHK)f!?%?4&nbvthQRr`!V)3O_MmY^Y*HOO;kvSa^LARbzo_8(6+e=tZH9;0Bi=7;70=q8!7)8 zqgf;-7xK=IJETSEZIyU#_uU|vxIHXJ-0-XzFowUkabNo8-Rs@wEs^F5+)NXPZL642 z!6xD((mQpb%@WoIbd0;=@`E-lg_oo4Z>Vx|m+t%tsJDr)){a%8I(^9JP)!}?hn_#$Yy_{z{fR^J#pX&;c^I8SUT*$=V@#0?t0 ztBIW@dr9_<>|f%@yzHxLUT*iC6%FLkk9*8jR@V|QT_t|dzwWU#=307}PW5(2a zarN_2SGF07S9(!bW^+_oE*}Ydy)DlF>`F#PNdyNy6K~@9u?ys^q{a6{HwH$1k4 zevSB9It}9h(eDyehv*ep!!UO7NTiCSEIJP;3Y79JJsv~y$)kW)(Z`BRdY=KBqJh%u zvh)s}8-574f)Braa5w(qP=o0%jGFu49*w#df2X?>f2sT5f2QG>H8_G5>Tra+@z;wx O;ajjw%WwN1hWQ8Wp|8aN literal 7736 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 F{{ZVAO)vle diff --git a/firmware/build_stm/inlretro_stm.elf b/firmware/build_stm/inlretro_stm.elf index 7f3af3e5f8286b8c352a2d5fc98cce341b8aa64a..edbdb0ac732742f249b29f0d141162301d286184 100644 GIT binary patch literal 115608 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 GA^Z&$O4~F5 literal 110928 zcmeIb3t&{$xj(x0?3plm81e#>5MU+~UO^@g!aLxQAd*aY1yDdVnLJ=dAsHqC1ErE8 zYE)VyT8n4}t!*tn>PgjFUrnvGXsu1qS{^=VMW7zF)V68`&Hwk?YwwwzfW7CQ(|hm# z-n$3Ze9!f*wZ6Tc-`?4?^`fe|ilPYPV-sTqsb2`;ECFnP1e8rUN`wnwtVk27nq0_l zCsdqMM+m{>(2OX$+(yc!C-D%-IC6BUhFV{i2P_X*9lZ>e zzd%yX7tViGG3pp{;{Q)4MG?;UiiFAp|6SSYTlN|Im-j3>{id_RSt(rA*|xXr(>oTO zc~d#{&S@d0j?n7vLjAS$kWIM0JkdS()Z;H!-tcI8hq7~6ml&@cQBM8yv-FPVE3WS9V9cXPA=id$v;PRZji%vu7%vou3$dcAhdZv37jhR(ForQT|lL(+iTq zf9_D)g{!c=cJxGFdibZ68R2WolwNVF?2A6n<5e$rdBTtRYr{FUww=K?Woq15<&bh} zcrT#op-;m%`7PUiz{`#N@bGRBTP*zP$Zlv8hiF+_|Mr^K5|tL-yellrAP z*gnkev5&Wx*k{_8+t=7P*tgpsw7+bB!+z9rcjYj3aVW8TNkDi!VR5SO%#eKLkT~_y znH1Q1@{6IuExclem?xHr8nISv6yFfv6uZTX_8%h2e_a~(bJ6Mcd0uiP1b3Gw)V>@{ zFaJ%g@VtJaGxvZaJ$P+JqA$I+-P0SqOeB`S2Fb5=$u&{Q5nb|>E@_KOE>mQlhjmFy zRB}X@>^-4)nkxF2eTS|cifUh`N;@~6uzTtUXm35y=~m9Ff9r|MDle-@4;r;UVwVS67NCTbvq~t}_2)$OFViheK4*=_pO9B5MY}>L&hQFdAf||B zu~}>vw}?B%{o)bvGjX2%1tj_JcN%tBbb5U5BaZCgP376Ohw*(F*Fine={e%a3J#m6 z__DyY`N;k3xa1D)$Nf;}ruF0g>3H{Cu}b)E87#zZjFnXwH}h(RuMTh*=+%IC0^W^u zKaxnX`TD->>+3s>^cSR`_w@<8m`4I54^WeU+xHh)i|&5q?K`fxpYiu^#a_K;KkcXa z?c;WL*8t6%p{bnZh4$n7{QKr*1S zVtG2Q+_2-Fx$PRwkw#A1@lH3#WqNrcsKl5YV~28El*7?P&OhH%JPnnG#gE^MTEzIF z{Lk;%J*%SfKS8dTYH;|ycF!f1N9;!a1JV2(Bh3F&jDxX3PB%CyCL2#iH8>im@ndkz zrTp+c`fKN(=&uI&D@WvtvG7=lm?mb4a#0}`ic3VN?Q$gfuU^Bh#3*p)9&zlh6Q1kd z!&TGqt-xJ6z6rQb$6J86>-f6&l-v=1<_w}X2OPHntAEdyo2ButfTp5rE`P5*cZh$d zLfvZMot`wCEYq)1I~7UIK_0uFXO<>8pi8EhtQEcYuf_=dkgECN=;vSMhr#f}V4M6` zuVGO?VCGj)mgWcGr;q7=0R9i)nje7w1-Rx1;D0`*bt8}F2jEX>63q{weqyrr@G+aGKFs!Bf0Wn# ziq8|%hk3?NoL*j8y|QlmRon4t*}i>?yu!mm>Aw1Q;n@ibY@I>h1lnbyw}ECW4f$r! zY^gy<(AxX_26a_bdO%3~b!hqSsQfa>*?L2N30iYIJYdj%$SFONctmCG%A@vnV&6c{ zba3bkBS&#mB=sz#8v70^yKNw(dJ49UVq+cWRaW_XF_*ojNgs!l`-gej>NvadVpAP|sOL=8a;Dc#tDI%3@vhUH_2z|2 z{dWk}*X?Q-g+8_1A1L%stymsPOHh1$t~g(E`4xc?!2-0cs64B7MM&`_l&=indIoma zWe3MrB=}@4*Pm|B_4p6iR)jqBs4)VwpW;ic?FqUnl=9x%qq!?WX(}bMugR9%b2i%a$!#CPZJ~=Y2okhgI{85Ne+g)E}bdO4GgvYu`zyA8Av((|Uy0-qY?C zy|*8|W3+NqoZ9)t@BAaeSI+&NKkdfs_VI1CxhuNFIB{guyXn64-YeVU@{@Y6>_|

aJN6byc@gXZqHrH5}&gBuCDZUamx0E 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 + uint8_t n = buff->cur_byte; + uint8_t read; +// extern operation_info *oper_info; +// uint8_t cur_data = buff->data[n]; + +#ifdef AVR_CORE + wdt_reset(); +#endif + + + //set to program mode for first entry + EXP0_LO(); + + //enter unlock bypass mode + wr_func( 0x0AAA, 0xAA ); + wr_func( 0x0555, 0x55 ); + wr_func( 0x0AAA, 0x20 ); + + + while ( cur <= buff->last_idx ) { + //write unlock sequence + //need to make address and unlock data variable + //best for host to communcate these values + //actual value is part mapper dependent and part flash dependent + //mapper controlled address bits dictate where split is + //32KB banking A14-0 NES ctl, A15+ mapper ctl "bank" NROM, BNROM, ANROM + //addrH_dmask = 0b0111 1111 directly addressable addrH bits + //page2bankshft = A14->A8 = 7 shifts (equal to number of set bits in addrH_mask + //16KB banking A13-0 NES ctl, A14+ mapper ctl "bank" UxROM, MMC1 + //addrH_dmask = 0b0011 1111 + //page2bankshft = A13->A8 = 6 shifts + // 8KB banking A12-0 NES ctl, A13+ mapper ctl "bank" MMC3, FME7 + //addrH_dmask = 0b0001 1111 + //page2bankshft = A12->A8 = 5 shifts + // 4KB banking A11-0 NES ctl, A12+ mapper ctl "bank" ezNSF + //addrH_dmask = 0b0000 1111 + //page2bankshft = A11->A8 = 4 shifts + // + + //unlocked wr_func( 0x0AAA, 0xAA ); + //unlocked wr_func( 0x0555, 0x55 ); + + //wr_func( 0x0000, 0xA0 ); + snes_rom_wr_cur_addr( 0xA0 ); //gained ~3KBps (59.13KBps) inl6 with v3.0 proto + + wr_func( ((addrH<<8)| n), buff->data[n] ); + //wr_func( ((addrH<<8)| n), cur_data ); //didn't actually speed up + + //Targetting 2MByte 16mbit flash which doesn't have buffered writes + //currently have average flash speed of 21.05KBps going to start removing some of these NOPs + //and optimizing flash routine to get time down. + + //exit program mode + EXP0_HI(); + //pre-fetch next byte of data + //cur_data = buff->data[n+1]; +#ifdef AVR_CORE + wdt_reset(); +#endif + + //wait for byte to flash + // do { + // usbPoll(); + // read = rd_func((addrH<<8)|n); + // + // //} while( read != rd_func((addrH<<8)|n) ); + // } while( read != buff->data[n] ); + //this can cause things to hang on failed programs.. + //need a smarter flash polling algo, kind of a pain because we don't have + //a good way to toggle /OE or /CE quickly on v3 SNES boards + + + usbPoll(); + read = rd_func((addrH<<8)|n); + //prepare for upcoming write cycle, or allow for a polling read + EXP0_LO(); + //First check if already outputting final data + if (read != buff->data[n] ) { + //if not, lets see if toggle is occuring + EXP0_HI(); + while( read != rd_func((addrH<<8)|n) ){ + EXP0_LO(); + NOP(); NOP(); NOP(); NOP(); + NOP(); NOP(); NOP(); NOP(); + NOP(); NOP(); NOP(); NOP(); + EXP0_HI(); + read = rd_func((addrH<<8)|n); + } + //prepare for upcoming write cycle + EXP0_LO(); + } + +// //IDK why, but AVR will exit early sometimes +// //without this second check, ~20 errors per 32KByte on SNES v3.0 +// //All error bytes are 0xFF instead of true data +// //may need a smarter flash polling routine.. +// //Tried to add extra delay to read algo, and didn't change anything +// //Also have decent trust in read routine as it's comparable to page read +// //which works flawlessly for dumps. So think it has to do with flashing specifically... +// //Hmm maybe the avr is missing a read.. flash /CE, /OE, and /WE never toggle +// //so why would flash polling output different data between polls..? +// //Ahh this is the issue, adding the code below only adds delay which gives flash +// //enough time to complete write. + + + //retry if write failed + //this helped but still seeing similar fails to dumps + // if (read == buff->data[n]) { + n++; + cur++; + // } + + } + + buff->cur_byte = n; + + //exit unlock bypass mode + wr_func( 0x0000, 0x90 ); + wr_func( 0x0000, 0x00 ); + //reset the flash chip, supposed to exit too + wr_func( 0x0000, 0xF0 ); + + //exit program mode + EXP0_HI(); + + return SUCCESS; + +} + /* Desc:Flash buffer contents on to cartridge memory * Pre: buffer elements must be updated to designate how/where to flash * buffer's cur_byte must be cleared or set to where to start flashing @@ -141,6 +273,13 @@ uint8_t flash_buff( buffer *buff ) { // buff->last_idx, ~FALSE ); break; case SNESROM: + addrH |= 0x80; //$8000 LOROM space + //need to split page_num + //A14-8 page_num[7-0] + //A15 high (LOROM) + //A23-16 page_num[14-8] + HADDR_SET( (buff->page_num)>>7 ); + write_page_snes( 0, addrH, buff, snes_rom_wr, snes_rom_rd ); case SNESRAM: //warn addrX = ((buff->page_num)>>8); break; diff --git a/firmware/source/pinport_al.h b/firmware/source/pinport_al.h index 75d3d5b..79ebf87 100644 --- a/firmware/source/pinport_al.h +++ b/firmware/source/pinport_al.h @@ -8,6 +8,7 @@ #ifdef AVR_CORE #include "avr_gpio.h" + #include #elif STM_CORE #include #endif diff --git a/firmware/source/snes.c b/firmware/source/snes.c index bb8bc16..10a3448 100644 --- a/firmware/source/snes.c +++ b/firmware/source/snes.c @@ -89,7 +89,7 @@ uint8_t snes_rom_rd( uint16_t addr ) /* Desc:SNES ROM Write * /ROMSEL always set low * EXP0/RESET unaffected - * read value from currently selected bank + * write value to currently selected bank * Pre: snes_init() setup of io pins * Post:data latched by anything listening on the bus * address left on bus @@ -104,22 +104,75 @@ void snes_rom_wr( uint16_t addr, uint8_t data ) DATA_OP(); DATA_SET(data); - //PRG R/W LO - ROMSEL_LO(); + //set /WR low first as this sets direction of + //level shifter on v3.0 boards CSWR_LO(); + //Then set romsel as this enables output of level shifter + ROMSEL_LO(); + //Doing the other order creates bus conflict between ROMSEL low -> WR low //give some time NOP(); NOP(); + NOP(); //3x total NOPs fails ~2Bytes per 2MByte on v3.0 proto and inl6 + //swaping /WR /ROMSEL order above helped greatly + //but still had 2 byte fails adding NOPS + NOP(); //4x total NOPs passed all bytes v3.0 SNES and inl6 + //NOP(); + //NOP(); //6x total NOPs passed all bytes + //latch data to cart memory/mapper - ROMSEL_HI(); CSWR_HI(); + ROMSEL_HI(); //Free data bus DATA_IP(); } +/* Desc:SNES ROM Write to current address + * /ROMSEL always set low + * EXP0/RESET unaffected + * write value to currently selected bank, and current address + * Mostly used when address is don't care + * Pre: snes_init() setup of io pins + * Post:data latched by anything listening on the bus + * address left on bus + * Rtn: None + */ +void snes_rom_wr_cur_addr( uint8_t data ) +{ + +// ADDR_SET(addr); + + //put data on bus + DATA_OP(); + DATA_SET(data); + + //set /WR low first as this sets direction of + //level shifter on v3.0 boards + CSWR_LO(); + //Then set romsel as this enables output of level shifter + ROMSEL_LO(); + //Doing the other order creates bus conflict between ROMSEL low -> WR low + + //give some time + NOP(); + NOP(); + NOP(); //3x total NOPs fails ~2Bytes per 2MByte on v3.0 proto and inl6 + //swaping /WR /ROMSEL order above helped greatly + //but still had 2 byte fails adding NOPS + NOP(); //4x total NOPs passed all bytes v3.0 SNES and inl6 + //NOP(); + //NOP(); //6x total NOPs passed all bytes + + //latch data to cart memory/mapper + CSWR_HI(); + ROMSEL_HI(); + + //Free data bus + DATA_IP(); +} /* Desc:SNES ROM Page Read with optional USB polling * /ROMSEL always low, EXP0/RESET unaffected * if poll is true calls usbdrv.h usbPoll fuction diff --git a/firmware/source/snes.h b/firmware/source/snes.h index ebe2188..91f6476 100644 --- a/firmware/source/snes.h +++ b/firmware/source/snes.h @@ -9,6 +9,7 @@ uint8_t snes_call( uint8_t opcode, uint8_t miscdata, uint16_t operand, uint8_t *rdata ); uint8_t snes_rom_rd( uint16_t addr ); void snes_rom_wr( uint16_t addr, uint8_t data ); +void snes_rom_wr_cur_addr( uint8_t data ); uint8_t snes_rom_page_rd_poll( uint8_t *data, uint8_t addrH, uint8_t first, uint8_t len, uint8_t poll ); diff --git a/host/scripts/app/buffers.lua b/host/scripts/app/buffers.lua index 2f6737e..6ab40bc 100644 --- a/host/scripts/app/buffers.lua +++ b/host/scripts/app/buffers.lua @@ -93,24 +93,24 @@ end -- pass in table buffer numbers would like to wait on -- pass in table of status waiting on for all buffers -local function status_wait( buff_nums, end_status ) +local function status_wait( buff_nums, end_status, debug ) local rv = nil for key_buff, buff in pairs(buff_nums) do rv = nil - print("buffer wait:", key_buff, buff) + if debug then print("buffer wait:", key_buff, buff) end while rv ~= "EXIT" do for key_stat, stat in pairs(end_status) do rv = (dict.buffer("GET_PRI_ELEMENTS", nil, buff, nil, true )) --status is the second byte of return data rv = string.unpack("B", rv, 2) if rv == op_buffer[stat] then - print("buffer", buff, rv, "matched", stat) + if debug then print("buffer", buff, rv, "matched", stat) end rv = "EXIT" break else - print("buffer", buff, "is", rv, "not", stat) + if debug then print("buffer", buff, "is", rv, "not", stat) end end end end diff --git a/host/scripts/app/cart.lua b/host/scripts/app/cart.lua index be0d8c2..b31b31f 100644 --- a/host/scripts/app/cart.lua +++ b/host/scripts/app/cart.lua @@ -60,6 +60,9 @@ local function detect_console( debug ) --bank 0 and bank 1 would have same reset vector on a NES cart --these probably differ on a SNES if there's more than 32/64KB of ROM. + --fake cart detection of erased board for now + cart_console = "SNES" + if bank0vect ~= bank1vect then if (bank0vect >= 0x8000) and (bank0vect < 0xFFFA) then if debug then print("valid SNES reset vector found that differs between bank0 & bank1") end diff --git a/host/scripts/app/dump.lua b/host/scripts/app/dump.lua index f768f06..56d7e60 100644 --- a/host/scripts/app/dump.lua +++ b/host/scripts/app/dump.lua @@ -190,7 +190,7 @@ local function dump_nes( file, debug ) -- check(! set_operation( transfer, STARTDUMP ), "Unable to set buffer operation"); dict.operation("SET_OPERATION", op_buffer["STARTDUMP"] ) -- --- tstop = clock(); +-- tstop = os.clock(); -- float timediff = ( (float)(tstop-tstart) / CLOCKS_PER_SEC); -- printf("total time: %fsec, speed: %fKBps", timediff, (512/timediff)); -- //TODO flush file from time to time..? @@ -280,34 +280,59 @@ local function dump_snes( file, mapping, debug ) -- //tell buffers what function to use for dumping -- //TODO when start implementing other mappers - --debugging print out buffer elements - --print("\nget operation:") - --dict.operation("GET_OPERATION" ) - --print("\n\ngetting cur_buff status") - --dict.buffer("GET_CUR_BUFF_STATUS" ) - --print("\n\ngetting elements") - --dict.buffer("GET_PRI_ELEMENTS", nil, buff0 ) - --dict.buffer("GET_PRI_ELEMENTS", nil, buff1 ) - --dict.buffer("GET_SEC_ELEMENTS", nil, buff0 ) - --dict.buffer("GET_SEC_ELEMENTS", nil, buff1 ) - --dict.buffer("GET_PAGE_NUM", nil, buff0 ) - --dict.buffer("GET_PAGE_NUM", nil, buff1 ) - print("\n\nsetting operation STARTDUMP"); --inform buffer manager to start dumping operation now that buffers are initialized dict.operation("SET_OPERATION", op_buffer["STARTDUMP"] ) --need these calls to delay things a bit to let first buffer dump complete.. --wait for first buffer to finish dumping before calling payload - buffers.status_wait({buff0}, {"DUMPED"}) + --buffers.status_wait({buff0}, {"DUMPED"}) + + local tstart = os.clock(); + local tlast = tstart print("starting first payload"); --now just need to call series of payload IN transfers to retrieve data - for i=1, (2048*1024/buff_size) do + for i=1, (2048*1024/buff_size) do --dump next buff + --for i=1, (1024*1024/buff_size) do --dump buff0 then buff1 + --for i=1, (32*1024/buff_size) do --dump buff0 then buff1 + --stm adapter had trouble dumping + --same buffer was getting sent twice, so I think + --buffer mangager wasn't getting enough time to update + --to point to next buffer and last buffer was redumped + --that doesn't quite make sense, but something like that is going on + --need to setup buffer manager to nak anytime something like this happens + --the following setup slows down everything due to status waits.. +-- buffers.status_wait({buff0}, {"DUMPED"}) +-- file:write( dict.buffer_payload_in( buff_size )) +-- buffers.status_wait({buff1}, {"DUMPED"}) + + --stm adapter having issues with race situation.. + --can't dump as fast as host is requesting + --and driver doesn't nak until ready to send data + cur_buff_status = dict.buffer("GET_CUR_BUFF_STATUS") + while (cur_buff_status ~= op_buffer["DUMPED"]) do + -- nak = nak +1 + --print(nak, "cur_buff->status: ", cur_buff_status) + cur_buff_status = dict.buffer("GET_CUR_BUFF_STATUS") + end file:write( dict.buffer_payload_in( buff_size )) + -- print("dumped page:", i) + + if ( (i % (1024*1024/buff_size/16)) == 0) then + local tdelta = os.clock() - tlast + print("time delta:", tdelta, "seconds, speed:", (1024/16/tdelta), "KBps"); + print("dumped part:", i/1024, "of 16 \n") + tlast = os.clock(); + end end - print("payload done"); + print("DUMPING DONE") + + tstop = os.clock() + timediff = ( tstop-tstart) + print("total time:", timediff, "seconds, average speed:", (2048/timediff), "KBps") + --buffer manager updates from USB_UNLOADING -> DUMPING -> DUMPED --while one buffer is unloading, it sends next buffer off to dump --payout opcode updates from DUMPED -> USB_LOADING diff --git a/host/scripts/app/erase.lua b/host/scripts/app/erase.lua index 80b3d4a..362e8b6 100644 --- a/host/scripts/app/erase.lua +++ b/host/scripts/app/erase.lua @@ -54,6 +54,55 @@ local function erase_nes() end +-- local functions +local function erase_snes(debug) + + local rv = nil + + print("erasing SNES takes about 30sec"); + + dict.io("IO_RESET") + dict.io("SNES_INIT") + + --WR $AAA:AA $555:55 $AAA:AA + dict.snes("SNES_SET_BANK", 0x00) + + --put cart in program mode + dict.pinport("CTL_SET_LO", "SNES_RST") + + dict.snes("SNES_ROM_WR", 0x0AAA, 0xAA) + dict.snes("SNES_ROM_WR", 0x0555, 0x55) + dict.snes("SNES_ROM_WR", 0x0AAA, 0x80) + dict.snes("SNES_ROM_WR", 0x0AAA, 0xAA) + dict.snes("SNES_ROM_WR", 0x0555, 0x55) + dict.snes("SNES_ROM_WR", 0x0AAA, 0x10) + + --exit program mode + dict.pinport("CTL_SET_HI", "SNES_RST") + + rv = dict.snes("SNES_ROM_RD", 0x0000) + + local i = 0 + + while ( rv ~= 0xFF ) do + rv = dict.snes("SNES_ROM_RD", 0x0000) + i = i + 1 + if debug then print(" ", i,":", string.format("%x",rv)) end + end + print(i, " done erasing snes.\n"); + + --put cart in program mode + dict.pinport("CTL_SET_LO", "SNES_RST") + + --reset flash + dict.snes("SNES_ROM_WR", 0x0000, 0xF0) + + --return to PLAY mode + dict.pinport("CTL_SET_HI", "SNES_RST") + +end + + -- global variables so other modules can use them @@ -62,6 +111,7 @@ end -- functions other modules are able to call erase.erase_nes = erase_nes +erase.erase_snes = erase_snes -- return the module's table return erase diff --git a/host/scripts/app/flash.lua b/host/scripts/app/flash.lua index 23463b0..4308415 100644 --- a/host/scripts/app/flash.lua +++ b/host/scripts/app/flash.lua @@ -210,6 +210,7 @@ local function flash_nes( file, debug ) print("\n\nsetting operation STARTFLASH"); -- //inform buffer manager to start dumping operation now that buffers are initialized dict.operation("SET_OPERATION", op_buffer["STARTFLASH"] ) + print("set operation STARTFLASH"); -- clock_t tstart, tstop; -- tstart = clock(); @@ -364,6 +365,128 @@ local function flash_nes( file, debug ) end +local function flash_snes( file, debug ) +-- //make some checks to ensure rom is compatible with cart +-- +-- //first do some checks like ensuring proper areas or sectors are blank +-- +-- //erase sectors or chip as needed +-- +-- //reset, allocate, and initialize device buffers +-- +-- //initialize mapper registers as needed for memory being programmed +-- +-- //set device operation to STARTFLASH +-- +-- //send payload data +-- +-- //run checksums to verify successful flash operation +-- + local buff0 = 0 + local buff1 = 1 + local cur_buff_status = 0 + local data = nil --lua stores data in strings + + if debug then print("flashing cart") end + +-- //start with reset and init + dict.io("IO_RESET") + dict.io("SNES_INIT") + +-- //start operation at reset + dict.operation("SET_OPERATION", op_buffer["RESET"] ) + +-- //setup buffers and manager +-- //reset buffers first + dict.buffer("RAW_BUFFER_RESET") +-- //need to allocate some buffers for flashing +-- //2x 256Byte buffers + local num_buffers = 2 + local buff_size = 256 + print("allocating buffers") + assert(buffers.allocate( num_buffers, buff_size ), "fail to allocate buffers") + +-- //set mem_type and part_num to designate how to get/write data + print("setting map n part") + dict.buffer("SET_MEM_N_PART", (op_buffer["SNESROM"]<<8 | op_buffer["MASKROM"]), buff0 ) + dict.buffer("SET_MEM_N_PART", (op_buffer["SNESROM"]<<8 | op_buffer["MASKROM"]), buff1 ) +-- //set multiple and add_mult only when flashing +-- //TODO +-- //set mapper, map_var, and function to designate read/write algo +-- +-- //just dump visible NROM memory to start + print("setting map n mapvar") + dict.buffer("SET_MAP_N_MAPVAR", (op_buffer["LOROM"]<<8 | op_buffer["NOVAR"]), buff0 ) + dict.buffer("SET_MAP_N_MAPVAR", (op_buffer["LOROM"]<<8 | op_buffer["NOVAR"]), buff1 ) + + --set cart in program mode + dict.pinport("CTL_SET_LO", "SNES_RST") + + print("\n\nsetting operation STARTFLASH"); +-- //inform buffer manager to start dumping operation now that buffers are initialized + dict.operation("SET_OPERATION", op_buffer["STARTFLASH"] ) + print("set operation STARTFLASH"); --this prints fine not getting stuck above here + + cur_buff_status = dict.buffer("GET_CUR_BUFF_STATUS") + print("got status") + dict.operation("GET_OPERATION") + print("got operation") + + --think the not responding bug is related to payload out before device is ready..? + + local tstart = os.clock(); + local tlast = tstart + + local i = 1 + local nak = 0 + for bytes in file:lines(buff_size) do + dict.buffer_payload_out( buff_size, bytes ) + + cur_buff_status = dict.buffer("GET_CUR_BUFF_STATUS") + while (cur_buff_status ~= op_buffer["EMPTY"]) do + nak = nak +1 + --print(nak, "cur_buff->status: ", cur_buff_status) + cur_buff_status = dict.buffer("GET_CUR_BUFF_STATUS") + end + if ( i == 2048*1024/buff_size) then break end +-- if ( i == 32*1024/buff_size) then break end + i = i + 1 + if ( (i % (2048*1024/buff_size/16)) == 0) then + local tdelta = os.clock() - tlast + print("time delta:", tdelta, "seconds, speed:", (2048/16/tdelta), "KBps"); + print("flashed part:", i/512, "of 16 \n") + tlast = os.clock(); + end + end + print("FLASHING DONE") + print("number of naks", nak) + tstop = os.clock() + timediff = ( tstop-tstart) + print("total time:", timediff, "seconds, average speed:", (2048/timediff), "KBps") +-- //TODO flush file from time to time..? +-- +-- //The device doesn't have a good way to respond if the last buffer is flashing +-- //and the current one is full. We can only send a payload if the current buffer +-- //is empty. + + -- wait till all buffers are done + --while flashing buffer manager updates from USB_FULL -> FLASHING -> FLASHED + --then next time a USB_FULL buffer comes it it updates the last buffer (above) to EMPTY + --the next payload opcode updates from EMPTY -> USB_LOADING + --so when complete, buff0 should be EMPTY, and buff1 should be FLASHED + --just pass the possible status to exit wait, and buffer numbers we're waiting on + buffers.status_wait({buff0, buff1}, {"EMPTY","FLASHED"}) + + dict.operation("SET_OPERATION", op_buffer["RESET"] ) + + --set cart in play mode + dict.pinport("CTL_SET_HI", "SNES_RST") + + dict.buffer("RAW_BUFFER_RESET") + dict.io("IO_RESET") + +end + -- global variables so other modules can use them @@ -372,6 +495,7 @@ end -- functions other modules are able to call flash.flash_nes = flash_nes +flash.flash_snes = flash_snes -- return the module's table return flash diff --git a/host/scripts/app/snes.lua b/host/scripts/app/snes.lua index 81c8a34..5f77daf 100644 --- a/host/scripts/app/snes.lua +++ b/host/scripts/app/snes.lua @@ -54,6 +54,10 @@ local function read_flashID( debug ) dict.snes("SNES_ROM_WR", 0x0AAA, 0xAA) dict.snes("SNES_ROM_WR", 0x0555, 0x55) dict.snes("SNES_ROM_WR", 0x0AAA, 0x90) + + --exit program mode + dict.pinport("CTL_SET_HI", "SNES_RST") + --read manf ID rv = dict.snes("SNES_ROM_RD", 0x0000) if debug then print("attempted read SNES ROM manf ID:", string.format("%X", rv)) end @@ -79,13 +83,15 @@ local function read_flashID( debug ) -- flash->wr_opcode = NES_PPU_WR; -- } -- + --put cart in program mode + dict.pinport("CTL_SET_LO", "SNES_RST") + --exit software --- dict.nes("NES_PPU_WR", 0x0000, 0xF0) dict.snes("SNES_ROM_WR", 0x0000, 0xF0) --- - + --exit program mode dict.pinport("CTL_SET_HI", "SNES_RST") + --return true diff --git a/host/scripts/inlretro.lua b/host/scripts/inlretro.lua index 8c4b9c7..cc3feca 100644 --- a/host/scripts/inlretro.lua +++ b/host/scripts/inlretro.lua @@ -8,6 +8,7 @@ function main () local dict = require "scripts.app.dict" local cart = require "scripts.app.cart" local nes = require "scripts.app.nes" + local snes = require "scripts.app.snes" local dump = require "scripts.app.dump" local erase = require "scripts.app.erase" local flash = require "scripts.app.flash" @@ -29,12 +30,12 @@ function main () print(string.format("%X", rv)) end - print(dict.io("EXP0_PULLUP_TEST")) +-- print(dict.io("EXP0_PULLUP_TEST")) -- debug = true -- rv = cart.detect(debug) - if cart.detect_console() then + if cart.detect_console(true) then if cart_console == "NES" or cart_console == "Famicom" then dict.io("IO_RESET") dict.io("NES_INIT") @@ -91,6 +92,47 @@ function main () dict.io("IO_RESET") elseif cart_console == "SNES" then + dict.io("IO_RESET") + dict.io("SNES_INIT") + + --SNES detect HiROM or LoROM + --nes.detect_mapper_mirroring(true) + local snes_mapping = "LOROM" + --SNES detect if there's save ram and size + + --SNES detect if able to read flash ID's + snes.read_flashID(true) + + + + --FLASHING: + --erase cart + -- erase.erase_snes( false ) + --open file + local file + file = assert(io.open("flash.bin", "rb")) + --determine if auto-doubling, deinterleaving, etc, + --needs done to make board compatible with rom + --flash cart + flash.flash_snes( file, true ) + --close file + assert(file:close()) + + --DUMPING: + --create new file + local file + file = assert(io.open("snesdump.bin", "wb")) + --dump cart into file + dump.dump_snes( file, snes_mapping, true ) + + --close file + assert(file:close()) + + + --trick to do this at end while debugging so don't have to wait for it before starting + erase.erase_snes( false ) + + dict.io("IO_RESET") elseif cart_console == "SegaGen" then