From e121b34cf4bc2e110b9c5728a6c0565df5f1136d Mon Sep 17 00:00:00 2001 From: ikari Date: Thu, 2 Jul 2009 11:14:04 +0200 Subject: [PATCH] Initial Commit --- pcb/DIL-Labels.sch | Bin 0 -> 18424 bytes pcb/breakout1.brd | Bin 0 -> 70214 bytes pcb/breakout2.brd | Bin 0 -> 56848 bytes pcb/breakout2.sch | Bin 0 -> 180683 bytes pcb/breakouts1-old.brd | Bin 0 -> 65823 bytes pcb/snesbreakout.brd | Bin 0 -> 22021 bytes pcb/snesbreakout.sch | Bin 0 -> 24317 bytes pcb/test.brd | 247 +++ pcb/test.sch | 34 + src/Makefile | 618 +++++++ src/avrcompat.h | 162 ++ src/conf2h.awk | 29 + src/config | 41 + src/config.h | 113 ++ src/crc16.c | 51 + src/crc16.h | 54 + src/crc32.c | 74 + src/crc32.h | 85 + src/crc7.c | 107 ++ src/crc7.h | 33 + src/crcgen-new.c | 85 + src/diskio.c | 197 +++ src/diskio.h | 125 ++ src/eeprom.c | 170 ++ src/eeprom.h | 33 + src/ff.c | 2936 ++++++++++++++++++++++++++++++++++ src/ff.h | 547 +++++++ src/fileops.c | 31 + src/fileops.h | 18 + src/fpga.c | 186 +++ src/fpga.h | 46 + src/gcctest.awk | 15 + src/integer.h | 30 + src/led.c | 43 + src/led.h | 54 + src/main.c | 200 +++ src/memory.c | 121 ++ src/memory.h | 10 + src/sdcard.c | 749 +++++++++ src/sdcard.h | 40 + src/snes.c | 54 + src/snes.h | 11 + src/spi.c | 131 ++ src/spi.h | 68 + src/src2doxy.pl | 75 + src/time.h | 16 + src/timer.c | 130 ++ src/timer.h | 116 ++ src/uart.c | 164 ++ src/uart.h | 59 + src/ustring.h | 42 + src/utils.c | 77 + src/utils.h | 52 + verilog/sd2snes/address.v | 118 ++ verilog/sd2snes/data.v | 83 + verilog/sd2snes/dcm.v | 70 + verilog/sd2snes/main.ucf | 191 +++ verilog/sd2snes/main.v | 299 ++++ verilog/sd2snes/sd2snes.xise | 80 + verilog/sd2snes/tf_main.v | 136 ++ 60 files changed, 9256 insertions(+) create mode 100644 pcb/DIL-Labels.sch create mode 100644 pcb/breakout1.brd create mode 100644 pcb/breakout2.brd create mode 100644 pcb/breakout2.sch create mode 100644 pcb/breakouts1-old.brd create mode 100644 pcb/snesbreakout.brd create mode 100644 pcb/snesbreakout.sch create mode 100644 pcb/test.brd create mode 100644 pcb/test.sch create mode 100644 src/Makefile create mode 100644 src/avrcompat.h create mode 100644 src/conf2h.awk create mode 100644 src/config create mode 100644 src/config.h create mode 100644 src/crc16.c create mode 100644 src/crc16.h create mode 100644 src/crc32.c create mode 100644 src/crc32.h create mode 100644 src/crc7.c create mode 100644 src/crc7.h create mode 100644 src/crcgen-new.c create mode 100644 src/diskio.c create mode 100644 src/diskio.h create mode 100644 src/eeprom.c create mode 100644 src/eeprom.h create mode 100644 src/ff.c create mode 100644 src/ff.h create mode 100644 src/fileops.c create mode 100644 src/fileops.h create mode 100644 src/fpga.c create mode 100644 src/fpga.h create mode 100644 src/gcctest.awk create mode 100644 src/integer.h create mode 100644 src/led.c create mode 100644 src/led.h create mode 100644 src/main.c create mode 100644 src/memory.c create mode 100644 src/memory.h create mode 100644 src/sdcard.c create mode 100644 src/sdcard.h create mode 100644 src/snes.c create mode 100644 src/snes.h create mode 100644 src/spi.c create mode 100644 src/spi.h create mode 100755 src/src2doxy.pl create mode 100644 src/time.h create mode 100644 src/timer.c create mode 100644 src/timer.h create mode 100644 src/uart.c create mode 100644 src/uart.h create mode 100644 src/ustring.h create mode 100644 src/utils.c create mode 100644 src/utils.h create mode 100644 verilog/sd2snes/address.v create mode 100644 verilog/sd2snes/data.v create mode 100644 verilog/sd2snes/dcm.v create mode 100644 verilog/sd2snes/main.ucf create mode 100644 verilog/sd2snes/main.v create mode 100644 verilog/sd2snes/sd2snes.xise create mode 100644 verilog/sd2snes/tf_main.v diff --git a/pcb/DIL-Labels.sch b/pcb/DIL-Labels.sch new file mode 100644 index 0000000000000000000000000000000000000000..3c26fbad26cfafe9b82aa125b9d9dbacff3ecce0 GIT binary patch literal 18424 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 literal 0 HcmV?d00001 diff --git a/pcb/breakout1.brd b/pcb/breakout1.brd new file mode 100644 index 0000000000000000000000000000000000000000..ac8b2c6053aedbcbcd548abd5589476fcfa8d168 GIT binary patch literal 70214 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

Zx{$-$k0j zExcqmX%|Vkduig=nkHmz*JEj>z`7ihI@Y~bQ}>G0guLy#Y8u}Wzf3jZ9d>=H(xjxz zn#+#8Ud@__JJ%&otVX@XHOVb)x~09jU7JU0n!JA#Oyc`yTDNxn+AX;GCjWAnmDAPT z)m7--ud7sQacfIj-RKXuM(lOds=~^l8}?qcedpjcQI?rs^{B{gem3u)g zCs(<>rOiz&;x*LiFsqU)VK!a4>?OIJT+3QYrhn&e-Hu&53O*>XR|yN9*Y6nINp+FjKBdYj)AbAXUAUpYuXEi6OZQ#) z3OB=@(^N39Yt7nTN4D=mhxV*4^et~Ur)nqb{mG>|SzUOwkoaVSh%a>~b9&Z5YaPZ; zj7_U&+0T8;4BViTOWldT3b=fk7s<=C#v*~?LX~8j3a@VI==V3RLd=D~?)sLg1?(pExoQ$H&*b$I;u++T*Tr;-p%xD$!bAGwXI-HN3mfzjl?aCawAi zEvgbP?QjKk7UGi)I=R%Hc(MV`y&k>3?aKngBN?5bBreB#LB_IC|8upw)a{e=OwTYPegoJV?i{nf+awnc_4d;p3+qtiEe6oS7 zAWj-i99vG@jhyr+kmDO0hXc|5Mp$s-is!_s$1C10dBw*kn+n}V9BYO1l}6vGlMOn# zR3|Gr@ukEk0}k;VoID9PE5aLVeBzYljTMpFnrUR|`&@LXu8sx1-ih_;6xv;7^=LAf{VG;v!a~>5e#uj$Jo! zps#x9qMYyb<{EUvm4$WI%8s8W4#*1qyo|kTFd$!DSl22=^mFdpGCsM~A)c9Ac8%9f zgI$ih_YMzUYm2A5Y?sJ2H#Bb>S{n7Q8{n<`sQ=Y{rOjORH-5TM(p)J}Yy)R;`!IQ z;@|CcSMmiHR`zV@x>zE%P?MD8)yqoDi>0QOEp4TE=r63<&=Y_AC0MYJQo{073Ehmx zN;@n^w^Ms!LeufuKEzfXcI>)(*febW4X)iWGGg0%?FF{o+f3tYcU^5?x!JvI&cFuy z=*5!4&Y{6=g}v+yHX4h={X0Zg4UTL#@`8d67)O5kBNY?JeYyov8;}2p4-7x3#fp_a49fs(1HwMva~qui4Oho=jcOR3`Ne zh34kw!p^;0M$CP>$15%@Y@!R%*;uzE{R z?-mQnN{CTdTq+h@?RAspmhyRlvqPRZLXt9$uI8OvdNyq7>EGfbB(H+F$!P*>*QP%A z$k8iaI`3QUTjH1%&amh7pY`BYU$uBP&YX4=2NfkCb!GXA`tMUYGmfh_eIxGi?CpOa zz&Q4qVJE3cJd#YPKQl(=6Vo-HE4RGf?#hyM_gjoc zBdQf&JLH&l1Nzt`sh}0&hiNMzR_PHm+v+XMFf&ZuaDS9c%?g;XX85|u;pq7?+YR~V zgAmtcDNEYu8?v`e?D6DwP|{P?$_Jp+sn2G$~4vi*V+5+c54Sc~XOXI^_;Ld2&-YY{cy^3!<<5#I%^ vMf7(s_+(0CpGmAqRBXEK)P&Ql35Z^N((6;A<0c^b*hycQ^FBN5a`gWJ^N#K0 literal 0 HcmV?d00001 diff --git a/pcb/breakouts1-old.brd b/pcb/breakouts1-old.brd new file mode 100644 index 0000000000000000000000000000000000000000..a13dafae39fc370565925a4f0fd0eb294f2ca99e GIT binary patch literal 65823 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 Mo+-mmw90AqKLIn-?EnA( literal 0 HcmV?d00001 diff --git a/pcb/snesbreakout.sch b/pcb/snesbreakout.sch new file mode 100644 index 0000000000000000000000000000000000000000..3a9ad452342b9ecfe571f5fe762e73ffa7407a83 GIT binary patch literal 24317 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 +# to submit bug reports. +#AVRDUDE_VERBOSE = -v -v + +AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) +AVRDUDE_FLAGS += $(AVRDUDE_NO_VERIFY) +AVRDUDE_FLAGS += $(AVRDUDE_VERBOSE) +AVRDUDE_FLAGS += $(AVRDUDE_ERASE_COUNTER) + + + +#---------------- Debugging Options ---------------- + +# For simulavr only - target MCU frequency. +DEBUG_MFREQ = $(CONFIG_MCU_FREQ) + +# Set the DEBUG_UI to either gdb or insight. +# DEBUG_UI = gdb +DEBUG_UI = insight + +# Set the debugging back-end to either avarice, simulavr. +DEBUG_BACKEND = avarice +#DEBUG_BACKEND = simulavr + +# GDB Init Filename. +GDBINIT_FILE = __avr_gdbinit + +# When using avarice settings for the JTAG +JTAG_DEV = /dev/com1 + +# Debugging port used to communicate between GDB / avarice / simulavr. +DEBUG_PORT = 4242 + +# Debugging host used to communicate between GDB / avarice / simulavr, normally +# just set to localhost unless doing some sort of crazy debugging when +# avarice is running on a different computer. +DEBUG_HOST = localhost + + + +#============================================================================ + + +# De-dupe the list of C source files +CSRC := $(sort $(SRC)) + +# Define all object files. +OBJ := $(patsubst %,$(OBJDIR)/%,$(CSRC:.c=.o) $(ASRC:.S=.o)) + +# Define all listing files. +LST := $(patsubst %,$(OBJDIR)/%,$(CSRC:.c=.lst) $(ASRC:.S=.lst)) + + +# Compiler flags to generate dependency files. +GENDEPFLAGS = -MMD -MP -MF .dep/$(@F).d + + +# Combine all necessary flags and optional flags. +# Add target processor to flags. +ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) $(GENDEPFLAGS) +ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS) $(CDEFS) + + + + + +# Default target. +all: build + +build: elf bin hex + $(E) " SIZE $(TARGET).elf" + $(Q)$(ELFSIZE)|grep -v debug + +elf: $(TARGET).elf +bin: $(TARGET).bin +hex: $(TARGET).hex +eep: $(TARGET).eep +lss: $(TARGET).lss +sym: $(TARGET).sym + +# A little helper target for the maintainer =) +copy2card: + mount /mnt + cp $(TARGET).bin /mnt + umount /mnt + sync + + +# Doxygen output: +doxygen: + -rm -rf doxyinput + mkdir doxyinput + cp *.h *.c doxyinput + src2doxy.pl doxyinput/*.h doxyinput/*.c + doxygen doxygen.conf + +# Display size of file. +HEXSIZE = $(SIZE) --target=$(FORMAT) $(TARGET).hex +ELFSIZE = $(SIZE) -A $(TARGET).elf +AVRMEM = avr-mem.sh $(TARGET).elf $(MCU) + +# Program the device. +program: bin hex eep + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM) + +# Set fuses of the device +fuses: $(CONFIG) + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FUSES) + +# Generate avr-gdb config/init file which does the following: +# define the reset signal, load the target file, connect to target, and set +# a breakpoint at main(). +gdb-config: + @$(REMOVE) $(GDBINIT_FILE) + @echo define reset >> $(GDBINIT_FILE) + @echo SIGNAL SIGHUP >> $(GDBINIT_FILE) + @echo end >> $(GDBINIT_FILE) + @echo file $(TARGET).elf >> $(GDBINIT_FILE) + @echo target remote $(DEBUG_HOST):$(DEBUG_PORT) >> $(GDBINIT_FILE) +ifeq ($(DEBUG_BACKEND),simulavr) + @echo load >> $(GDBINIT_FILE) +endif + @echo break main >> $(GDBINIT_FILE) + +debug: gdb-config $(TARGET).elf +ifeq ($(DEBUG_BACKEND), avarice) + @echo Starting AVaRICE - Press enter when "waiting to connect" message displays. + @$(WINSHELL) /c start avarice --jtag $(JTAG_DEV) --erase --program --file \ + $(TARGET).elf $(DEBUG_HOST):$(DEBUG_PORT) + @$(WINSHELL) /c pause + +else + @$(WINSHELL) /c start simulavr --gdbserver --device $(MCU) --clock-freq \ + $(DEBUG_MFREQ) --port $(DEBUG_PORT) +endif + @$(WINSHELL) /c start avr-$(DEBUG_UI) --command=$(GDBINIT_FILE) + + + + +# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB. +COFFCONVERT=$(OBJCOPY) --debugging \ +--change-section-address .data-0x800000 \ +--change-section-address .bss-0x800000 \ +--change-section-address .noinit-0x800000 \ +--change-section-address .eeprom-0x810000 + + +coff: $(TARGET).elf + $(COFFCONVERT) -O coff-avr $< $(TARGET).cof + + +extcoff: $(TARGET).elf + $(COFFCONVERT) -O coff-ext-avr $< $(TARGET).cof + + +# Generate autoconf.h from config +.PRECIOUS : $(OBJDIR)/autoconf.h +$(OBJDIR)/autoconf.h: $(CONFIG) | $(OBJDIR) + $(E) " CONF2H $(CONFIG)" + $(Q)gawk -f conf2h.awk $(CONFIG) > $(OBJDIR)/autoconf.h + +# Create final output files (.hex, .eep) from ELF output file. +ifeq ($(CONFIG_BOOTLOADER),y) +$(OBJDIR)/%.bin: $(OBJDIR)/%.elf + $(E) " BIN $@" + $(Q)$(OBJCOPY) -O binary -R .eeprom $< $@ + $(E) " CRCGEN $@" + -$(Q)crcgen-new $@ $(BINARY_LENGTH) $(CONFIG_BOOT_DEVID) $(BOOT_VERSION) + $(E) " COPY $(CONFIG_HARDWARE_NAME)-firmware-$(PROGRAMVERSION).bin" + $(Q)$(COPY) $@ $(OBJDIR)/$(CONFIG_HARDWARE_NAME)-firmware-$(PROGRAMVERSION).bin +else +$(OBJDIR)/%.bin: $(OBJDIR)/%.elf + $(E) " BIN $@" + $(Q)$(OBJCOPY) -O binary -R .eeprom $< $@ +endif + + +$(OBJDIR)/%.hex: $(OBJDIR)/%.elf + $(E) " HEX $@" + $(Q)$(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ + +$(OBJDIR)/%.eep: $(OBJDIR)/%.elf + -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \ + --change-section-lma .eeprom=0 -O $(FORMAT) $< $@ + +# Create extended listing file from ELF output file. +$(OBJDIR)/%.lss: $(OBJDIR)/%.elf + $(E) " LSS $<" + $(Q)$(OBJDUMP) -h -S $< > $@ + +# Create a symbol table from ELF output file. +$(OBJDIR)/%.sym: $(OBJDIR)/%.elf + $(E) " SYM $<" + $(E)$(NM) -n $< > $@ + + + +# Link: create ELF output file from object files. +.SECONDARY : $(TARGET).elf +.PRECIOUS : $(OBJ) +$(OBJDIR)/%.elf: $(OBJ) + $(E) " LINK $@" + $(Q)$(CC) $(ALL_CFLAGS) $^ --output $@ $(LDFLAGS) + + +# Compile: create object files from C source files. +$(OBJDIR)/%.o : %.c | $(OBJDIR) $(OBJDIR)/autoconf.h + $(E) " CC $<" + $(Q)$(CC) -c $(ALL_CFLAGS) $< -o $@ + + +# Compile: create assembler files from C source files. +$(OBJDIR)/%.s : %.c | $(OBJDIR) $(OBJDIR)/autoconf.h + $(CC) -S $(ALL_CFLAGS) $< -o $@ + + +# Assemble: create object files from assembler source files. +$(OBJDIR)/%.o : %.S | $(OBJDIR) $(OBJDIR)/autoconf.h + $(E) " AS $<" + $(Q)$(CC) -c $(ALL_ASFLAGS) $< -o $@ + +# Create preprocessed source for use in sending a bug report. +$(OBJDIR)/%.i : %.c | $(OBJDIR) $(OBJDIR)/autoconf.h + $(CC) -E -mmcu=$(MCU) -I. $(CFLAGS) $< -o $@ + +# Create the output directory +$(OBJDIR): + $(E) " MKDIR $(OBJDIR)" + $(Q)mkdir $(OBJDIR) + +# Target: clean project. +clean: begin clean_list end + +clean_list : + $(E) " CLEAN" + $(Q)$(REMOVE) $(TARGET).hex + $(Q)$(REMOVE) $(TARGET).bin + $(Q)$(REMOVE) $(TARGET).eep + $(Q)$(REMOVE) $(TARGET).cof + $(Q)$(REMOVE) $(TARGET).elf + $(Q)$(REMOVE) $(TARGET).map + $(Q)$(REMOVE) $(TARGET).sym + $(Q)$(REMOVE) $(TARGET).lss + $(Q)$(REMOVE) $(OBJ) + $(Q)$(REMOVE) $(OBJDIR)/autoconf.h + $(Q)$(REMOVE) $(OBJDIR)/*.bin + $(Q)$(REMOVE) $(LST) + $(Q)$(REMOVE) $(CSRC:.c=.s) + $(Q)$(REMOVE) $(CSRC:.c=.d) + $(Q)$(REMOVE) .dep/* + $(Q)$(REMOVE) -rf codedoc + $(Q)$(REMOVE) -rf doxyinput + -$(Q)rmdir $(OBJDIR) + +# Include the dependency files. +-include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*) + +# Manual dependency for the assembler module +# $(OBJDIR)/fastloader-ll.o: config.h $(OBJDIR)/autoconf.h + +# Listing of phony targets. +.PHONY : all begin finish end sizebefore sizeafter gccversion \ +build elf hex eep lss sym coff extcoff \ +clean clean_list program debug gdb-config doxygen + diff --git a/src/avrcompat.h b/src/avrcompat.h new file mode 100644 index 0000000..814182d --- /dev/null +++ b/src/avrcompat.h @@ -0,0 +1,162 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + avrcompat.h: Compatibility defines for multiple target chips +*/ + +#ifndef AVRCOMPAT_H +#define AVRCOMPAT_H + +/* USART */ + +#if defined __AVR_ATmega644__ || defined __AVR_ATmega644P__ || defined __AVR_ATmega1281__ || defined __AVR_ATmega2561__ + +# ifdef USE_UART1 +# define RXC RXC1 +# define RXEN RXEN1 +# define TXC TXC1 +# define TXEN TXEN1 +# define UBRRH UBRR1H +# define UBRRL UBRR1L +# define UCSRA UCSR1A +# define UCSRB UCSR1B +# define UCSRC UCSR1C +# define UCSZ0 UCSZ10 +# define UCSZ1 UCSZ11 +# define UDR UDR1 +# define UDRIE UDRIE1 +# define RXCIE RXCIE1 +# define USART_UDRE_vect USART1_UDRE_vect +# define USART_RX_vect USART1_RX_vect +# else + /* Default is USART0 */ +# define RXC RXC0 +# define RXEN RXEN0 +# define TXC TXC0 +# define TXEN TXEN0 +# define UBRRH UBRR0H +# define UBRRL UBRR0L +# define UCSRA UCSR0A +# define UCSRB UCSR0B +# define UCSRC UCSR0C +# define UCSZ0 UCSZ00 +# define UCSZ1 UCSZ01 +# define UDR UDR0 +# define UDRIE UDRIE0 +# define RXCIE RXCIE0 +# define USART_UDRE_vect USART0_UDRE_vect +# define USART_RX_vect USART0_RX_vect +# endif + +#elif defined __AVR_ATmega48__ || defined __AVR_ATmega88__ || defined __AVR_ATmega168__ +/* These chips include the USART number in their register names, */ +/* but not in the ISR name. */ +# define RXC RXC0 +# define RXEN RXEN0 +# define TXC TXC0 +# define TXEN TXEN0 +# define UBRRH UBRR0H +# define UBRRL UBRR0L +# define UCSRA UCSR0A +# define UCSRB UCSR0B +# define UCSRC UCSR0C +# define UCSZ0 UCSZ00 +# define UCSZ1 UCSZ01 +# define UDR UDR0 +# define UDRIE UDRIE0 + +#elif defined __AVR_ATmega32__ +# define TIMER2_COMPA_vect TIMER2_COMP_vect +# define TCCR0B TCCR0 +# define TCCR2A TCCR2 +# define TCCR2B TCCR2 +# define TIFR0 TIFR +# define TIMSK2 TIMSK +# define OCIE2A OCIE2 +# define OCR2A OCR2 + +#elif defined __AVR_ATmega128__ +# define UBRRH UBRR0H +# define UBRRL UBRR0L +# define UCSRA UCSR0A +# define UCSRB UCSR0B +# define UCSRC UCSR0C +# define UDR UDR0 +# define USART_UDRE_vect USART0_UDRE_vect +# define TIMER2_COMPA_vect TIMER2_COMP_vect +# define TCCR0B TCCR0 +# define TCCR2A TCCR2 +# define TCCR2B TCCR2 +# define TIFR0 TIFR +# define TIMSK1 TIMSK +# define TIMSK2 TIMSK +# define OCIE2A OCIE2 +# define OCR2A OCR2 + +#else +# error Unknown chip! (USART) +#endif + +/* SPI and I2C */ +#if defined __AVR_ATmega32__ || defined __AVR_ATmega644__ || defined __AVR_ATmega644P__ + +# define SPI_PORT PORTB +# define SPI_DDR DDRB +# define SPI_SS _BV(PB4) +# define SPI_MOSI _BV(PB5) +# define SPI_MISO _BV(PB6) +# define SPI_SCK _BV(PB7) +# define HWI2C_PORT PORTC +# define HWI2C_SDA _BV(PC1) +# define HWI2C_SCL _BV(PC0) + +#elif defined __AVR_ATmega128__ || defined __AVR_ATmega1281__ || defined __AVR_ATmega2561__ + +# define SPI_PORT PORTB +# define SPI_DDR DDRB +# define SPI_SS _BV(PB0) +# define SPI_SCK _BV(PB1) +# define SPI_MOSI _BV(PB2) +# define SPI_MISO _BV(PB3) +# define HWI2C_PORT PORTD +# define HWI2C_SDA _BV(PD1) +# define HWI2C_SCL _BV(PD0) + +#elif defined __AVR_ATmega48__ || defined __AVR_ATmega88__ || defined __AVR_ATmega168__ + +# define SPI_PORT PORTB +# define SPI_DDR DDRB +# define SPI_SS _BV(PB2) +# define SPI_SCK _BV(PB5) +# define SPI_MOSI _BV(PB3) +# define SPI_MISO _BV(PB4) +# define HWI2C_PORT PORTC +# define HWI2C_SDA _BV(PC4) +# define HWI2C_SCL _BV(PC5) + +#else +# error Unknown chip! (SPI/TWI) +#endif + +#define SPI_MASK (SPI_SS|SPI_MOSI|SPI_MISO|SPI_SCK) + +#endif /* AVRCOMPAT_H */ diff --git a/src/conf2h.awk b/src/conf2h.awk new file mode 100644 index 0000000..3818f89 --- /dev/null +++ b/src/conf2h.awk @@ -0,0 +1,29 @@ +#! /usr/bin/gawk -f + +# Trivial little script to convert from a makefile-style configuration +# file to a C header. No copyright claimed. + +BEGIN { + print "// autoconf.h generated from " ARGV[1] " at " strftime() "\n" \ + "#ifndef AUTOCONF_H\n" \ + "#define AUTOCONF_H" +} + +/^#/ { sub(/^#/,"//") } + +/^CONFIG_.*=/ { + if (/=n$/) { + sub(/^/,"// "); + } else { + sub(/^/,"#define ") + if (/=y$/) { + sub(/=.*$/,"") + } else { + sub(/=/," ") + } + } +} + +{ print } + +END { print "#endif" } diff --git a/src/config b/src/config new file mode 100644 index 0000000..1227775 --- /dev/null +++ b/src/config @@ -0,0 +1,41 @@ +# This may not look like it, but it's a -*- makefile -*- +# +# sd2iec - SD/MMC to Commodore serial bus interface/controller +# Copyright (C) 2007-2009 Ingo Korb +# +# Inspiration and low-level SD/MMC access based on code from MMC2IEC +# by Lars Pontoppidan et al., see sdcard.c|h and config.h. +# +# FAT filesystem access based on code from ChaN, see tff.c|h. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License only. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# +# This file is included in the main sd2iec Makefile and also parsed +# into autoconf.h. + +CONFIG_MCU=atmega644p +CONFIG_LINKER_RELAX=n +CONFIG_MCU_FREQ=14318180 +CONFIG_BOOTLOADER=y +CONFIG_BOOT_DEVID=0x4e534453 +CONFIG_UART_DEBUG=y +CONFIG_UART_BAUDRATE=38400 +CONFIG_UART_BUF_SHIFT=7 +CONFIG_HARDWARE_NAME=sd2snes +CONFIG_SD_AUTO_RETRIES=10 +#CONFIG_SD_DATACRC=y +CONFIG_EEPROM_SIZE=512 +CONFIG_EEPROM_OFFSET=512 +CONFIG_MAX_PARTITIONS=2 diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..6036cfe --- /dev/null +++ b/src/config.h @@ -0,0 +1,113 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + config.h: User-configurable options to simplify hardware changes and/or + reduce the code/ram requirements of the code. + + + Based on MMC2IEC, original copyright header follows: + +// +// Title : MMC2IEC - Configuration +// Author : Lars Pontoppidan +// Date : Jan. 2007 +// Version : 0.7 +// Target MCU : AtMega32(L) at 8 MHz +// +// +// DISCLAIMER: +// The author is in no way responsible for any problems or damage caused by +// using this code. Use at your own risk. +// +// LICENSE: +// This code is distributed under the GNU Public License +// which can be found at http://www.gnu.org/licenses/gpl.txt +// + +*/ + +#ifndef CONFIG_H +#define CONFIG_H + +#include "autoconf.h" + +# define HW_NAME "SD2IEC" +# define HAVE_SD +# define SDCARD_DETECT (!(PIND & _BV(PD2))) +# define SDCARD_DETECT_SETUP() do { DDRD &= ~_BV(PD2); PORTD |= _BV(PD2); } while(0) +# if defined __AVR_ATmega32__ +# define SD_CHANGE_SETUP() do { MCUCR |= _BV(ISC00); GICR |= _BV(INT0); } while(0) +# elif defined __AVR_ATmega644__ || defined __AVR_ATmega644P__ +# define SD_CHANGE_SETUP() do { EICRA |= _BV(ISC00); EIMSK |= _BV(INT0); } while(0) +# else +# error Unknown chip! +# endif +# define SD_CHANGE_VECT INT0_vect +# define SDCARD_WP (PIND & _BV(PD6)) +# define SDCARD_WP_SETUP() do { DDRD &= ~ _BV(PD6); PORTD |= _BV(PD6); } while(0) +# define SD_CHANGE_ICR MCUCR +# define SD_SUPPLY_VOLTAGE (1L<<21) +# define DEVICE_SELECT (8+!(PINA & _BV(PA2))+2*!(PINA & _BV(PA3))) +# define DEVICE_SELECT_SETUP() do { \ + DDRA &= ~(_BV(PA2)|_BV(PA3)); \ + PORTA |= _BV(PA2)|_BV(PA3); \ + } while (0) +# define BUSY_LED_SETDDR() DDRA |= _BV(PA0) +# define BUSY_LED_ON() PORTA &= ~_BV(PA0) +# define BUSY_LED_OFF() PORTA |= _BV(PA0) +# define DIRTY_LED_SETDDR() DDRA |= _BV(PA1) +# define DIRTY_LED_ON() PORTA &= ~_BV(PA1) +# define DIRTY_LED_OFF() PORTA |= _BV(PA1) +# define DIRTY_LED_PORT PORTA +# define DIRTY_LED_BIT() _BV(PA1) +# define AUX_LED_SETDDR() do {} while (0) +# define AUX_LED_ON() do {} while (0) +# define AUX_LED_OFF() do {} while (0) +# define BUTTON_PIN PINA +# define BUTTON_PORT PORTA +# define BUTTON_DDR DDRA +# define BUTTON_MASK (_BV(PA4)|_BV(PA5)) +# define BUTTON_NEXT _BV(PA4) +# define BUTTON_PREV _BV(PA5) + +/* An interrupt for detecting card changes implies hotplugging capability */ +#if defined(SD_CHANGE_VECT) || defined (CF_CHANGE_VECT) +# define HAVE_HOTPLUG +#endif + +/* Generate dummy functions for the BUSY LED if required */ +#ifdef SINGLE_LED +# define BUSY_LED_SETDDR() do {} while(0) +# define BUSY_LED_ON() do {} while(0) +# define BUSY_LED_OFF() do {} while(0) +#endif + +/* Create some temporary symbols so we can calculate the number of */ +/* enabled storage devices. */ +#ifdef HAVE_SD +# define TMP_SD 1 +#endif + +/* Remove the temporary symbols */ +#undef TMP_SD + +#endif diff --git a/src/crc16.c b/src/crc16.c new file mode 100644 index 0000000..056d75b --- /dev/null +++ b/src/crc16.c @@ -0,0 +1,51 @@ +/** + * \file stdout + * Functions and types for CRC checks. + * + * Generated on Tue Jun 30 23:02:59 2009, + * by pycrc v0.7.1, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 16 + * Poly = 0x8005 + * XorIn = 0x0000 + * ReflectIn = True + * XorOut = 0x0000 + * ReflectOut = True + * Algorithm = bit-by-bit-fast + * Direct = True + *****************************************************************************/ +#include +#include "crc16.h" +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +crc_t crc16_update(crc_t crc, const unsigned char *data, size_t data_len) +{ + unsigned int i; + uint8_t bit; + unsigned char c; + + while (data_len--) { + c = *data++; + for (i = 0x01; i & 0xff; i <<= 1) { + bit = (crc & 0x8000 ? 1 : 0); + if (c & i) { + bit ^= 1; + } + crc <<= 1; + if (bit) { + crc ^= 0x8005; + } + } + crc &= 0xffff; + } + return crc & 0xffff; +} + + + diff --git a/src/crc16.h b/src/crc16.h new file mode 100644 index 0000000..5b5e491 --- /dev/null +++ b/src/crc16.h @@ -0,0 +1,54 @@ +/** + * \file stdout + * Functions and types for CRC checks. + * + * Generated on Tue Jun 30 23:02:56 2009, + * by pycrc v0.7.1, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 16 + * Poly = 0x8005 + * XorIn = 0x0000 + * ReflectIn = True + * XorOut = 0x0000 + * ReflectOut = True + * Algorithm = bit-by-bit-fast + * Direct = True + *****************************************************************************/ +#ifndef __STDOUT__ +#define __STDOUT__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The definition of the used algorithm. + *****************************************************************************/ +#define CRC_ALGO_BIT_BY_BIT_FAST 1 + +/** + * The type of the CRC values. + * + * This type must be big enough to contain at least 16 bits. + *****************************************************************************/ +typedef uint16_t crc_t; + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +crc_t crc16_update(crc_t crc, const unsigned char *data, size_t data_len); + +#ifdef __cplusplus +} /* closing brace for extern "C" */ +#endif + +#endif /* __STDOUT__ */ + diff --git a/src/crc32.c b/src/crc32.c new file mode 100644 index 0000000..a522512 --- /dev/null +++ b/src/crc32.c @@ -0,0 +1,74 @@ +/** + * \file stdout + * Functions and types for CRC checks. + * + * Generated on Tue Jun 30 21:35:13 2009, + * by pycrc v0.7.1, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 32 + * Poly = 0x04c11db7 + * XorIn = 0xffffffff + * ReflectIn = True + * XorOut = 0xffffffff + * ReflectOut = True + * Algorithm = bit-by-bit-fast + * Direct = True + *****************************************************************************/ +#include +#include "crc32.h" + +/** + * Reflect all bits of a \a data word of \a data_len bytes. + * + * \param data The data word to be reflected. + * \param data_len The width of \a data expressed in number of bits. + * \return The reflected data. + *****************************************************************************/ +long crc_reflect(long data, size_t data_len) +{ + unsigned int i; + long ret; + + ret = data & 0x01; + for (i = 1; i < data_len; i++) + { + data >>= 1; + ret = (ret << 1) | (data & 0x01); + } + return ret; +} + + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +crc_t crc_update(crc_t crc, const unsigned char *data, size_t data_len) +{ + unsigned int i; + uint8_t bit; + unsigned char c; + + while (data_len--) { + c = *data++; + for (i = 0x01; i & 0xff; i <<= 1) { + bit = ((crc & 0x80000000) ? 1 : 0); + if (c & i) { + bit ^= 1; + } + crc <<= 1; + if (bit) { + crc ^= 0x04c11db7; + } + } + crc &= 0xffffffff; + } + return crc & 0xffffffff; +} + + + diff --git a/src/crc32.h b/src/crc32.h new file mode 100644 index 0000000..6f360b9 --- /dev/null +++ b/src/crc32.h @@ -0,0 +1,85 @@ +/** + * \file stdout + * Functions and types for CRC checks. + * + * Generated on Tue Jun 30 21:35:19 2009, + * by pycrc v0.7.1, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 32 + * Poly = 0x04c11db7 + * XorIn = 0xffffffff + * ReflectIn = True + * XorOut = 0xffffffff + * ReflectOut = True + * Algorithm = bit-by-bit-fast + * Direct = True + *****************************************************************************/ +#ifndef __STDOUT__ +#define __STDOUT__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The definition of the used algorithm. + *****************************************************************************/ +#define CRC_ALGO_BIT_BY_BIT_FAST 1 + +/** + * The type of the CRC values. + * + * This type must be big enough to contain at least 32 bits. + *****************************************************************************/ +typedef uint32_t crc_t; + +/** + * Reflect all bits of a \a data word of \a data_len bytes. + * + * \param data The data word to be reflected. + * \param data_len The width of \a data expressed in number of bits. + * \return The reflected data. + *****************************************************************************/ +long crc_reflect(long data, size_t data_len); + +/** + * Calculate the initial crc value. + * + * \return The initial crc value. + *****************************************************************************/ +static inline crc_t crc_init(void) +{ + return 0xffffffff; +} + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +crc_t crc_update(crc_t crc, const unsigned char *data, size_t data_len); + +/** + * Calculate the final crc value. + * + * \param crc The current crc value. + * \return The final crc value. + *****************************************************************************/ +static inline crc_t crc_finalize(crc_t crc) +{ + return crc_reflect(crc, 32) ^ 0xffffffff; +} + + +#ifdef __cplusplus +} /* closing brace for extern "C" */ +#endif + +#endif /* __STDOUT__ */ + diff --git a/src/crc7.c b/src/crc7.c new file mode 100644 index 0000000..232e385 --- /dev/null +++ b/src/crc7.c @@ -0,0 +1,107 @@ +/* This file is a modified version of the output of pycrc. + * + * Licensing terms of pycrc: + * + * Copyright (c) 2006-2007, Thomas Pircher + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * + * crc7.c: Calculate CRC7 for SD card commands + * + */ + +/* + * Functions and types for CRC checks. + * + * Generated on Thu Nov 8 13:52:05 2007, + * by pycrc v0.6.3, http://www.tty1.net/pycrc/ + * using the configuration: + * Width = 7 + * Poly = 0x09 + * XorIn = 0x00 + * ReflectIn = False + * XorOut = 0x00 + * ReflectOut = False + * Algorithm = bit-by-bit-fast + */ +#include +#include +#include + +/** +; MMC/SD CRC Polynom X^7 + X^3 + 1 +crc7taba: + mov dptr,#CRC7_TABA ; (2) + clr c ; (1) + rrc a ; (1) + xrl a,crc7ta ; (1) + movc a,@a+dptr ; (2) + jnc crc7m ; (2) + xrl a,#00001001b ; (1) +crc7m: mov crc7ta,a ; (1) + ret ; (2) (LCALL=2) +*/ +uint8_t crc7tab[128] = { + 0x00,0x12,0x24,0x36,0x48,0x5a,0x6c,0x7e, + 0x19,0x0b,0x3d,0x2f,0x51,0x43,0x75,0x67, + 0x32,0x20,0x16,0x04,0x7a,0x68,0x5e,0x4c, + 0x2b,0x39,0x0f,0x1d,0x63,0x71,0x47,0x55, + 0x64,0x76,0x40,0x52,0x2c,0x3e,0x08,0x1a, + 0x7d,0x6f,0x59,0x4b,0x35,0x27,0x11,0x03, + 0x56,0x44,0x72,0x60,0x1e,0x0c,0x3a,0x28, + 0x4f,0x5d,0x6b,0x79,0x07,0x15,0x23,0x31, + 0x41,0x53,0x65,0x77,0x09,0x1b,0x2d,0x3f, + 0x58,0x4a,0x7c,0x6e,0x10,0x02,0x34,0x26, + 0x73,0x61,0x57,0x45,0x3b,0x29,0x1f,0x0d, + 0x6a,0x78,0x4e,0x5c,0x22,0x30,0x06,0x14, + 0x25,0x37,0x01,0x13,0x6d,0x7f,0x49,0x5b, + 0x3c,0x2e,0x18,0x0a,0x74,0x66,0x50,0x42, + 0x17,0x05,0x33,0x21,0x5f,0x4d,0x7b,0x69, + 0x0e,0x1c,0x2a,0x38,0x46,0x54,0x62,0x70 +}; + +uint8_t crc7update(uint8_t crc, const uint8_t data) { + uint8_t a = data; + uint8_t b = data & 1; + a = crc7tab[(a>>1) ^ crc]; + a ^= ((b<<3)|(b)); + crc = a; + return a; +} + +/*uint8_t crc7update(uint8_t crc, const uint8_t data) { + uint8_t i; + bool bit; + uint8_t c; + + c = data; + for (i = 0x80; i > 0; i >>= 1) { + bit = crc & 0x40; + if (c & i) { + bit = !bit; + } + crc <<= 1; + if (bit) { + crc ^= 0x09; + } + } + crc &= 0x7f; + return crc & 0x7f; +}*/ diff --git a/src/crc7.h b/src/crc7.h new file mode 100644 index 0000000..1ccba9d --- /dev/null +++ b/src/crc7.h @@ -0,0 +1,33 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + crc7.h: Definitions for CRC7 calculation routines + +*/ + +#ifndef CRC7_H +#define CRC7_H + +uint8_t crc7update(uint8_t crc, const uint8_t data); + +#endif + diff --git a/src/crcgen-new.c b/src/crcgen-new.c new file mode 100644 index 0000000..5e59c80 --- /dev/null +++ b/src/crcgen-new.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include + +#define lo8(x) (x & 0xFF) +#define hi8(x) ((x >> 8) & 0xFF) +#define xhi8(x) ((x >> 16) & 0xff) +#define xxhi8(x) ((x >> 24) & 0xff) + +unsigned short crc_ccitt_update (uint16_t crc, uint8_t data) +{ + data ^= lo8 (crc); + data ^= data << 4; + return ((((uint16_t)data << 8) | hi8 (crc)) ^ (uint8_t)(data >> 4) ^ ((uint16_t)data << 3)); +} + +int main(int argc, char *argv[]) { + if (argc != 5) { + printf("Usage: crcgen \r\n"); + return 1; + } + + unsigned long length = strtol(argv[2],NULL,0); + unsigned long devid = strtol(argv[3],NULL,0); + unsigned long version = strtol(argv[4],NULL,0); + + if (length > length+8) { + printf("Ha ha, very funny.\n"); + return 1; + } + + uint8_t *data = malloc(length+8); + + if (!data) { + perror("malloc"); + return 1; + } + + memset(data, 0xff, length); + + FILE *f; + + f = fopen(argv[1], "rb+"); + + if (f == 0) { + printf("Unable to open file %s\r\n", argv[1]); + return 1; + } + + fread(data, 1, length, f); + + data[length-8] = lo8(devid); + data[length-7] = hi8(devid); + data[length-6] = xhi8(devid); + data[length-5] = xxhi8(devid); + data[length-4] = lo8(version); + data[length-3] = hi8(version); + + unsigned long l; + unsigned short crc = 0xFFFF; + + for (l=0; l < length-2; l++) + crc = crc_ccitt_update(crc, data[l]); + + data[length-2] = lo8(crc); + data[length-1] = hi8(crc); + + if (fseek(f, 0, SEEK_SET)) { + perror("fseek"); + return 1; + } + + if (fwrite(data, length, 1, f) != 1) { + perror("fwrite"); + return 1; + } + + fclose(f); + + return 0; +} + diff --git a/src/diskio.c b/src/diskio.c new file mode 100644 index 0000000..b539516 --- /dev/null +++ b/src/diskio.c @@ -0,0 +1,197 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + diskio.c: Generic disk access routines and supporting stuff + +*/ + +#include "config.h" +#include "diskio.h" +#include "sdcard.h" + +volatile enum diskstates disk_state; + +#ifdef NEED_DISKMUX + +uint32_t drive_config; + +/* This function calculates the default drive configuration. */ +/* Its result is static after compilation, but doing this in */ +/* C in less messy than doing it with the preprocessor. */ +uint32_t get_default_driveconfig(void) { + uint32_t result = 0xffffffffL; + + /* Order matters: Whatever is checked first will be last in the config */ +#ifdef HAVE_DF + result = (result << 4) + (DISK_TYPE_DF << DRIVE_BITS) + 0; +#endif +#ifdef CONFIG_TWINSD + result = (result << 4) + (DISK_TYPE_SD << DRIVE_BITS) + 1; +#endif +#ifdef HAVE_SD + result = (result << 4) + (DISK_TYPE_SD << DRIVE_BITS) + 0; +#endif +#ifdef HAVE_ATA + result = (result << 4) + (DISK_TYPE_ATA << DRIVE_BITS) + 0; +#endif + return result; +} + +void disk_init(void) { +#ifdef HAVE_SD + sd_init(); +#endif +#ifdef HAVE_ATA + ata_init(); +#endif +#ifdef HAVE_DF + df_init(); +#endif +} + +DSTATUS disk_status(BYTE drv) { + switch(drv >> DRIVE_BITS) { +#ifdef HAVE_DF + case DISK_TYPE_DF: + return df_status(drv & DRIVE_MASK); +#endif + +#ifdef HAVE_ATA + case DISK_TYPE_ATA: + return ata_status(drv & DRIVE_MASK); + + case DISK_TYPE_ATA2: + return ata_status((drv & DRIVE_MASK) + 2); +#endif + +#ifdef HAVE_SD + case DISK_TYPE_SD: + return sd_status(drv & DRIVE_MASK); +#endif + + default: + return STA_NOINIT|STA_NODISK; + } +} + +DSTATUS disk_initialize(BYTE drv) { + switch(drv >> DRIVE_BITS) { +#ifdef HAVE_DF + case DISK_TYPE_DF: + return df_initialize(drv & DRIVE_MASK); +#endif + +#ifdef HAVE_ATA + case DISK_TYPE_ATA: + return ata_initialize(drv & DRIVE_MASK); + + case DISK_TYPE_ATA2: + return ata_initialize((drv & DRIVE_MASK) + 2); +#endif + +#ifdef HAVE_SD + case DISK_TYPE_SD: + return sd_initialize(drv & DRIVE_MASK); +#endif + + default: + return STA_NOINIT|STA_NODISK; + } +} + +DRESULT disk_read(BYTE drv, BYTE *buffer, DWORD sector, BYTE count) { + switch(drv >> DRIVE_BITS) { +#ifdef HAVE_DF + case DISK_TYPE_DF: + return df_read(drv & DRIVE_MASK,buffer,sector,count); +#endif + +#ifdef HAVE_ATA + case DISK_TYPE_ATA: + return ata_read(drv & DRIVE_MASK,buffer,sector,count); + + case DISK_TYPE_ATA2: + return ata_read((drv & DRIVE_MASK) + 2,buffer,sector,count); +#endif + +#ifdef HAVE_SD + case DISK_TYPE_SD: + return sd_read(drv & DRIVE_MASK,buffer,sector,count); +#endif + + default: + return RES_ERROR; + } +} + +DRESULT disk_write(BYTE drv, const BYTE *buffer, DWORD sector, BYTE count) { + switch(drv >> DRIVE_BITS) { +#ifdef HAVE_DF + case DISK_TYPE_DF: + return df_write(drv & DRIVE_MASK,buffer,sector,count); +#endif + +#ifdef HAVE_ATA + case DISK_TYPE_ATA: + return ata_write(drv & DRIVE_MASK,buffer,sector,count); + + case DISK_TYPE_ATA2: + return ata_write((drv & DRIVE_MASK) + 2,buffer,sector,count); +#endif + +#ifdef HAVE_SD + case DISK_TYPE_SD: + return sd_write(drv & DRIVE_MASK,buffer,sector,count); +#endif + + default: + return RES_ERROR; + } +} + +DRESULT disk_getinfo(BYTE drv, BYTE page, void *buffer) { + switch(drv >> DRIVE_BITS) { +#ifdef HAVE_DF + case DISK_TYPE_DF: + return df_getinfo(drv & DRIVE_MASK,page,buffer); +#endif + +#ifdef HAVE_ATA + case DISK_TYPE_ATA: + return ata_getinfo(drv & DRIVE_MASK,page,buffer); + + case DISK_TYPE_ATA2: + return ata_getinfo((drv & DRIVE_MASK) + 2,page,buffer); +#endif + +#ifdef HAVE_SD + case DISK_TYPE_SD: + return sd_getinfo(drv & DRIVE_MASK,page,buffer); +#endif + + default: + return RES_ERROR; + } +} + + +#endif diff --git a/src/diskio.h b/src/diskio.h new file mode 100644 index 0000000..f9fb70c --- /dev/null +++ b/src/diskio.h @@ -0,0 +1,125 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + diskio.h: Definitions for the disk access routines + + Based on diskio.h from FatFS by ChaN, see ff.c for + for full copyright details. + +*/ + +#ifndef DISKIO_H +#define DISKIO_H + +#include "integer.h" + +/* Status of Disk Functions */ +typedef BYTE DSTATUS; + +/* Disk Status Bits (DSTATUS) */ +#define STA_NOINIT 0x01 /* Drive not initialized */ +#define STA_NODISK 0x02 /* No medium in the drive */ +#define STA_PROTECT 0x04 /* Write protected */ + +/* Results of Disk Functions */ +typedef enum { + RES_OK = 0, /* 0: Successful */ + RES_ERROR, /* 1: R/W Error */ + RES_WRPRT, /* 2: Write Protected */ + RES_NOTRDY, /* 3: Not Ready */ + RES_PARERR /* 4: Invalid Parameter */ +} DRESULT; + +/** + * struct diskinfo0_t - disk info data structure for page 0 + * @validbytes : Number of valid bytes in this struct + * @maxpage : Highest diskinfo page supported + * @disktype : type of the disk (DISK_TYPE_* values) + * @sectorsize : sector size divided by 256 + * @sectorcount: number of sectors on the disk + * + * This is the struct returned in the data buffer when disk_getinfo + * is called with page=0. + */ +typedef struct { + uint8_t validbytes; + uint8_t maxpage; + uint8_t disktype; + uint8_t sectorsize; /* divided by 256 */ + uint32_t sectorcount; /* 2 TB should be enough... (512 byte sectors) */ +} diskinfo0_t; + +/*---------------------------------------*/ +/* Prototypes for disk control functions */ + +DSTATUS disk_initialize (BYTE); +DSTATUS disk_status (BYTE); +DRESULT disk_read (BYTE, BYTE*, DWORD, BYTE); +DRESULT disk_write (BYTE, const BYTE*, DWORD, BYTE); +#define disk_ioctl(a,b,c) RES_OK +DRESULT disk_getinfo(BYTE drv, BYTE page, void *buffer); + +void disk_init(void); + +/* Will be set to DISK_ERROR if any access on the card fails */ +enum diskstates { DISK_CHANGED = 0, DISK_REMOVED, DISK_OK, DISK_ERROR }; + +extern volatile enum diskstates disk_state; + +/* Disk type - part of the external API except for ATA2! */ +#define DISK_TYPE_ATA 0 +#define DISK_TYPE_ATA2 1 +#define DISK_TYPE_SD 2 +#define DISK_TYPE_DF 3 +#define DISK_TYPE_NONE 7 + +#ifdef NEED_DISKMUX + +/* Disk mux configuration */ + +extern uint32_t drive_config; + +uint32_t get_default_driveconfig(void); + +# define set_map_drive(drv,val) (drive_config = \ + (drive_config & (0xffffffff - (0x0f << (drv * 4)))) \ + | (val << (drv * 4))) +# define map_drive(drv) ((drive_config >> (4 * drv)) & 0x0f) +# define set_drive_config(config) drive_config = config + +/* Number of bits used for the drive, the disk type */ +/* uses the remainder (4 bits per entry). */ +# define DRIVE_BITS 1 + +/* Calculate mask from the shift value */ +# define DRIVE_MASK ((1 << DRIVE_BITS)-1) + +#else // NEED_DISKMUX + +# define set_map_drive(drv,val) do {} while(0) +# define map_drive(drv) (drv) +# define set_drive_config(conf) do {} while(0) +# define get_default_driveconfig() 0 + +#endif // NEED_DISKMUX + +#endif diff --git a/src/eeprom.c b/src/eeprom.c new file mode 100644 index 0000000..1ddfbef --- /dev/null +++ b/src/eeprom.c @@ -0,0 +1,170 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + eeprom.c: Persistent configuration storage + +*/ + +#include +#include +#include "config.h" +#include "diskio.h" +#include "fatops.h" +#include "flags.h" +#include "iec.h" +#include "timer.h" +#include "eeprom.h" + +#include "uart.h" + +/** + * struct storedconfig - in-eeprom data structure + * @dummy : EEPROM position 0 is unused + * @checksum : Checksum over the EEPROM contents + * @structsize : size of the eeprom structure + * @osccal : stored value of OSCCAL + * @globalflags: subset of the globalflags variable + * @address : device address set by software + * @hardaddress: device address set by jumpers + * @fileexts : file extension mapping mode + * @drvflags0 : 16 bits of drv mappings, organized as 4 nybbles. + * @drvflags1 : 16 bits of drv mappings, organized as 4 nybbles. + * + * This is the data structure for the contents of the EEPROM. + */ +static EEMEM struct { + uint8_t dummy; + uint8_t checksum; + uint16_t structsize; + uint8_t osccal; + uint8_t global_flags; + uint8_t address; + uint8_t hardaddress; + uint8_t fileexts; + uint16_t drvconfig0; + uint16_t drvconfig1; +} storedconfig; + +/** + * read_configuration - reads configuration from EEPROM + * + * This function reads the stored configuration values from the EEPROM. + * If the stored checksum doesn't match the calculated one nothing will + * be changed. + */ +void read_configuration(void) { + uint16_t i,size; + uint8_t checksum, tmp; + + /* Set default values */ + globalflags |= JIFFY_ENABLED; /* JiffyDos enabled */ + globalflags |= POSTMATCH; /* Post-* matching enabled */ + globalflags |= FAT32_FREEBLOCKS; /* Calculate the number of free blocks on FAT32 */ + file_extension_mode = 1; /* Store x00 extensions except for PRG */ + set_drive_config(get_default_driveconfig()); /* Set the default drive configuration */ + + /* Use the NEXT button to skip reading the EEPROM configuration */ + if (!(BUTTON_PIN & BUTTON_NEXT)) { + ignore_keys(); + return; + } + + size = eeprom_read_word(&storedconfig.structsize); + + /* Calculate checksum of EEPROM contents */ + checksum = 0; + for (i=2; i 9) { + uint32_t tmpconfig; + tmpconfig = eeprom_read_word(&storedconfig.drvconfig0); + tmpconfig |= (uint32_t)eeprom_read_word(&storedconfig.drvconfig1) << 16; + set_drive_config(tmpconfig); + } + + /* sanity check. If the user has truly turned off all drives, turn the + * defaults back on + */ + if(drive_config == 0xffffffff) + set_drive_config(get_default_driveconfig()); +#endif + + /* Paranoia: Set EEPROM address register to the dummy entry */ + EEAR = 0; +} + +/** + * write_configuration - stores configuration data to EEPROM + * + * This function stores the current configuration values to the EEPROM. + */ +void write_configuration(void) { + uint16_t i; + uint8_t checksum; + + /* Write configuration to EEPROM */ + eeprom_write_word(&storedconfig.structsize, sizeof(storedconfig)); + eeprom_write_byte(&storedconfig.osccal, OSCCAL); + eeprom_write_byte(&storedconfig.global_flags, + globalflags & (JIFFY_ENABLED | POSTMATCH | + EXTENSION_HIDING | FAT32_FREEBLOCKS)); + eeprom_write_byte(&storedconfig.address, device_address); + eeprom_write_byte(&storedconfig.hardaddress, DEVICE_SELECT); + eeprom_write_byte(&storedconfig.fileexts, file_extension_mode); +#ifdef NEED_DISKMUX + eeprom_write_word(&storedconfig.drvconfig0, drive_config); + eeprom_write_word(&storedconfig.drvconfig1, drive_config >> 16); +#endif + + /* Calculate checksum over EEPROM contents */ + checksum = 0; + for (i=2;i + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + eeprom.h: Persistent configuration storage + +*/ + +#ifndef EEPROM_H +#define EEPROM_H + +void read_configuration(void); +void write_configuration(void); + +#endif diff --git a/src/ff.c b/src/ff.c new file mode 100644 index 0000000..5b38038 --- /dev/null +++ b/src/ff.c @@ -0,0 +1,2936 @@ +/*----------------------------------------------------------------------------/ +/ FatFs - FAT file system module R0.07a (C)ChaN, 2009 +/-----------------------------------------------------------------------------/ +/ FatFs module is an open source software to implement FAT file system to +/ small embedded systems. This is a free software and is opened for education, +/ research and commercial developments under license policy of following trems. +/ +/ Copyright (C) 2009, ChaN, all right reserved. +/ +/ * The FatFs module is a free software and there is NO WARRANTY. +/ * No restriction on use. You can use, modify and redistribute it for +/ personal, non-profit or commercial use UNDER YOUR RESPONSIBILITY. +/ * Redistributions of source code must retain the above copyright notice. +//-----------------------------------------------------------------------------/ +/ Feb 26,'06 R0.00 Prototype. +/ +/ Apr 29,'06 R0.01 First stable version. +/ +/ Jun 01,'06 R0.02 Added FAT12 support. +/ Removed unbuffered mode. +/ Fixed a problem on small (<32M) patition. +/ Jun 10,'06 R0.02a Added a configuration option (_FS_MINIMUM). +/ +/ Sep 22,'06 R0.03 Added f_rename(). +/ Changed option _FS_MINIMUM to _FS_MINIMIZE. +/ Dec 11,'06 R0.03a Improved cluster scan algolithm to write files fast. +/ Fixed f_mkdir() creates incorrect directory on FAT32. +/ +/ Feb 04,'07 R0.04 Supported multiple drive system. +/ Changed some interfaces for multiple drive system. +/ Changed f_mountdrv() to f_mount(). +/ Added f_mkfs(). +/ Apr 01,'07 R0.04a Supported multiple partitions on a plysical drive. +/ Added a capability of extending file size to f_lseek(). +/ Added minimization level 3. +/ Fixed an endian sensitive code in f_mkfs(). +/ May 05,'07 R0.04b Added a configuration option _USE_NTFLAG. +/ Added FSInfo support. +/ Fixed DBCS name can result FR_INVALID_NAME. +/ Fixed short seek (<= csize) collapses the file object. +/ +/ Aug 25,'07 R0.05 Changed arguments of f_read(), f_write() and f_mkfs(). +/ Fixed f_mkfs() on FAT32 creates incorrect FSInfo. +/ Fixed f_mkdir() on FAT32 creates incorrect directory. +/ Feb 03,'08 R0.05a Added f_truncate() and f_utime(). +/ Fixed off by one error at FAT sub-type determination. +/ Fixed btr in f_read() can be mistruncated. +/ Fixed cached sector is not flushed when create and close +/ without write. +/ +/ Apr 01,'08 R0.06 Added fputc(), fputs(), fprintf() and fgets(). +/ Improved performance of f_lseek() on moving to the same +/ or following cluster. +/ +/ Apr 01,'09 R0.07 Merged Tiny-FatFs as a buffer configuration option. +/ Added long file name support. +/ Added multiple code page support. +/ Added re-entrancy for multitask operation. +/ Added auto cluster size selection to f_mkfs(). +/ Added rewind option to f_readdir(). +/ Changed result code of critical errors. +/ Renamed string functions to avoid name collision. +/ Apr 14,'09 R0.07a Separated out OS dependent code on reentrant cfg. +/ Added multiple sector size support. +/---------------------------------------------------------------------------*/ + +#include "ff.h" /* FatFs configurations and declarations */ +#include "diskio.h" /* Declarations of low level disk I/O functions */ + + +/*-------------------------------------------------------------------------- + + Module Private Definitions + +---------------------------------------------------------------------------*/ + +#if _FS_REENTRANT +#if _USE_LFN == 1 +#error Static LFN work area must not be used in re-entrant configuration. +#endif +#define ENTER_FF(fs) { if (!lock_fs(fs)) return FR_TIMEOUT; } +#define LEAVE_FF(fs, res) { unlock_fs(fs, res); return res; } + +#else +#define ENTER_FF(fs) +#define LEAVE_FF(fs, res) return res + +#endif + +#define ABORT(fs, res) { fp->flag |= FA__ERROR; LEAVE_FF(fs, res); } + +#ifndef NULL +#define NULL 0 +#endif + + +/*-------------------------------------------------------------------------- + + Private Work Area + +---------------------------------------------------------------------------*/ + +static +FATFS *FatFs[_DRIVES]; /* Pointer to the file system objects (logical drives) */ +static +WORD Fsid; /* File system mount ID */ + + +#if _USE_LFN == 1 /* LFN with static LFN working buffer */ +static +WORD LfnBuf[_MAX_LFN + 1]; +#define NAMEBUF(sp,lp) BYTE sp[12]; WCHAR *lp = LfnBuf +#define INITBUF(dj,sp,lp) dj.fn = sp; dj.lfn = lp + +#elif _USE_LFN > 1 /* LFN with dynamic LFN working buffer */ +#define NAMEBUF(sp,lp) BYTE sp[12]; WCHAR lbuf[_MAX_LFN + 1], *lp = lbuf +#define INITBUF(dj,sp,lp) dj.fn = sp; dj.lfn = lp + +#else /* No LFN */ +#define NAMEBUF(sp,lp) BYTE sp[12] +#define INITBUF(dj,sp,lp) dj.fn = sp + +#endif + + + + +/*-------------------------------------------------------------------------- + + Private Functions + +---------------------------------------------------------------------------*/ + + +/*-----------------------------------------------------------------------*/ +/* String functions */ +/*-----------------------------------------------------------------------*/ + +/* Copy memory to memory */ +static +void mem_cpy (void* dst, const void* src, int cnt) { + char *d = (char*)dst; + const char *s = (const char *)src; + while (cnt--) *d++ = *s++; +} + +/* Fill memory */ +static +void mem_set (void* dst, int val, int cnt) { + char *d = (char*)dst; + while (cnt--) *d++ = (char)val; +} + +/* Compare memory to memory */ +static +int mem_cmp (const void* dst, const void* src, int cnt) { + const char *d = (const char *)dst, *s = (const char *)src; + int r = 0; + while (cnt-- && (r = *d++ - *s++) == 0) ; + return r; +} + +/* Check if chr is contained in the string */ +static +int chk_chr (const char* str, int chr) { + while (*str && *str != chr) str++; + return *str; +} + + + +/*-----------------------------------------------------------------------*/ +/* Request/Release grant to access the volume */ +/*-----------------------------------------------------------------------*/ +#if _FS_REENTRANT + +static +BOOL lock_fs ( + FATFS *fs /* File system object */ +) +{ + return ff_req_grant(fs->sobj); +} + + +static +void unlock_fs ( + FATFS *fs, /* File system object */ + FRESULT res /* Result code to be returned */ +) +{ + if (res != FR_NOT_ENABLED && + res != FR_INVALID_DRIVE && + res != FR_INVALID_OBJECT && + res != FR_TIMEOUT) { + ff_rel_grant(fs->sobj); + } +} +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Change window offset */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT move_window ( + FATFS *fs, /* File system object */ + DWORD sector /* Sector number to make apperance in the fs->win[] */ +) /* Move to zero only writes back dirty window */ +{ + DWORD wsect; + + + wsect = fs->winsect; + if (wsect != sector) { /* Changed current window */ +#if !_FS_READONLY + if (fs->wflag) { /* Write back dirty window if needed */ + if (disk_write(fs->drive, fs->win, wsect, 1) != RES_OK) + return FR_DISK_ERR; + fs->wflag = 0; + if (wsect < (fs->fatbase + fs->sects_fat)) { /* In FAT area */ + BYTE nf; + for (nf = fs->n_fats; nf >= 2; nf--) { /* Refrect the change to FAT copy */ + wsect += fs->sects_fat; + disk_write(fs->drive, fs->win, wsect, 1); + } + } + } +#endif + if (sector) { + if (disk_read(fs->drive, fs->win, sector, 1) != RES_OK) + return FR_DISK_ERR; + fs->winsect = sector; + } + } + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Clean-up cached data */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT sync ( /* FR_OK: successful, FR_DISK_ERR: failed */ + FATFS *fs /* File system object */ +) +{ + FRESULT res; + + + res = move_window(fs, 0); + if (res == FR_OK) { + /* Update FSInfo sector if needed */ + if (fs->fs_type == FS_FAT32 && fs->fsi_flag) { + fs->winsect = 0; + mem_set(fs->win, 0, 512); + ST_WORD(fs->win+BS_55AA, 0xAA55); + ST_DWORD(fs->win+FSI_LeadSig, 0x41615252); + ST_DWORD(fs->win+FSI_StrucSig, 0x61417272); + ST_DWORD(fs->win+FSI_Free_Count, fs->free_clust); + ST_DWORD(fs->win+FSI_Nxt_Free, fs->last_clust); + disk_write(fs->drive, fs->win, fs->fsi_sector, 1); + fs->fsi_flag = 0; + } + /* Make sure that no pending write process in the physical drive */ + if (disk_ioctl(fs->drive, CTRL_SYNC, (void*)NULL) != RES_OK) + res = FR_DISK_ERR; + } + + return res; +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Get a cluster status */ +/*-----------------------------------------------------------------------*/ + +static +DWORD get_cluster ( /* 0xFFFFFFFF:Disk error, 1:Interal error, Else:Cluster status */ + FATFS *fs, /* File system object */ + DWORD clst /* Cluster# to get the link information */ +) +{ + WORD wc, bc; + DWORD fsect; + + + if (clst < 2 || clst >= fs->max_clust) /* Check cluster address range */ + return 1; + + fsect = fs->fatbase; + switch (fs->fs_type) { + case FS_FAT12 : + bc = (WORD)clst * 3 / 2; + if (move_window(fs, fsect + (bc / SS(fs)))) break; + wc = fs->win[bc & (SS(fs) - 1)]; bc++; + if (move_window(fs, fsect + (bc / SS(fs)))) break; + wc |= (WORD)fs->win[bc & (SS(fs) - 1)] << 8; + return (clst & 1) ? (wc >> 4) : (wc & 0xFFF); + + case FS_FAT16 : + if (move_window(fs, fsect + (clst / (SS(fs) / 2)))) break; + return LD_WORD(&fs->win[((WORD)clst * 2) & (SS(fs) - 1)]); + + case FS_FAT32 : + if (move_window(fs, fsect + (clst / (SS(fs) / 4)))) break; + return LD_DWORD(&fs->win[((WORD)clst * 4) & (SS(fs) - 1)]) & 0x0FFFFFFF; + } + + return 0xFFFFFFFF; /* An error occured at the disk I/O layer */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change a cluster status */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT put_cluster ( + FATFS *fs, /* File system object */ + DWORD clst, /* Cluster# to be changed (must be 2 to fs->max_clust-1) */ + DWORD val /* New value to mark the cluster */ +) +{ + WORD bc; + BYTE *p; + DWORD fsect; + FRESULT res; + + + if (clst < 2 || clst >= fs->max_clust) { /* Check cluster address range */ + res = FR_INT_ERR; + + } else { + fsect = fs->fatbase; + switch (fs->fs_type) { + case FS_FAT12 : + bc = (WORD)clst * 3 / 2; + res = move_window(fs, fsect + (bc / SS(fs))); + if (res != FR_OK) break; + p = &fs->win[bc & (SS(fs) - 1)]; + *p = (clst & 1) ? ((*p & 0x0F) | ((BYTE)val << 4)) : (BYTE)val; + bc++; + fs->wflag = 1; + res = move_window(fs, fsect + (bc / SS(fs))); + if (res != FR_OK) break; + p = &fs->win[bc & (SS(fs) - 1)]; + *p = (clst & 1) ? (BYTE)(val >> 4) : ((*p & 0xF0) | ((BYTE)(val >> 8) & 0x0F)); + break; + + case FS_FAT16 : + res = move_window(fs, fsect + (clst / (SS(fs) / 2))); + if (res != FR_OK) break; + ST_WORD(&fs->win[((WORD)clst * 2) & (SS(fs) - 1)], (WORD)val); + break; + + case FS_FAT32 : + res = move_window(fs, fsect + (clst / (SS(fs) / 4))); + if (res != FR_OK) break; + ST_DWORD(&fs->win[((WORD)clst * 4) & (SS(fs) - 1)], val); + break; + + default : + res = FR_INT_ERR; + } + fs->wflag = 1; + } + + return res; +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Remove a cluster chain */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT remove_chain ( + FATFS *fs, /* File system object */ + DWORD clst /* Cluster# to remove chain from */ +) +{ + FRESULT res; + DWORD nxt; + + + if (clst < 2 || clst >= fs->max_clust) { /* Check cluster address range */ + res = FR_INT_ERR; + + } else { + res = FR_OK; + while (clst < fs->max_clust) { /* Not a last link? */ + nxt = get_cluster(fs, clst); /* Get cluster status */ + if (nxt == 0) break; /* Empty cluster? */ + if (nxt == 1) { res = FR_INT_ERR; break; } /* Internal error? */ + if (nxt == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } /* Disk error? */ + res = put_cluster(fs, clst, 0); /* Mark the cluster "empty" */ + if (res != FR_OK) break; + if (fs->free_clust != 0xFFFFFFFF) { /* Update FSInfo */ + fs->free_clust++; + fs->fsi_flag = 1; + } + clst = nxt; /* Next cluster */ + } + } + + return res; +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Stretch or create a cluster chain */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +DWORD create_chain ( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */ + FATFS *fs, /* File system object */ + DWORD clst /* Cluster# to stretch. 0 means create a new chain. */ +) +{ + DWORD cs, ncl, scl, mcl; + + + mcl = fs->max_clust; + if (clst == 0) { /* Create new chain */ + scl = fs->last_clust; /* Get suggested start point */ + if (scl == 0 || scl >= mcl) scl = 1; + } + else { /* Stretch existing chain */ + cs = get_cluster(fs, clst); /* Check the cluster status */ + if (cs < 2) return 1; /* It is an invalid cluster */ + if (cs < mcl) return cs; /* It is already followed by next cluster */ + scl = clst; + } + + ncl = scl; /* Start cluster */ + for (;;) { + ncl++; /* Next cluster */ + if (ncl >= mcl) { /* Wrap around */ + ncl = 2; + if (ncl > scl) return 0; /* No free custer */ + } + cs = get_cluster(fs, ncl); /* Get the cluster status */ + if (cs == 0) break; /* Found a free cluster */ + if (cs == 0xFFFFFFFF || cs == 1)/* An error occured */ + return cs; + if (ncl == scl) return 0; /* No free custer */ + } + + if (put_cluster(fs, ncl, 0x0FFFFFFF)) /* Mark the new cluster "in use" */ + return 0xFFFFFFFF; + if (clst != 0) { /* Link it to previous one if needed */ + if (put_cluster(fs, clst, ncl)) + return 0xFFFFFFFF; + } + + fs->last_clust = ncl; /* Update FSINFO */ + if (fs->free_clust != 0xFFFFFFFF) { + fs->free_clust--; + fs->fsi_flag = 1; + } + + return ncl; /* Return new cluster number */ +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Get sector# from cluster# */ +/*-----------------------------------------------------------------------*/ + +static +DWORD clust2sect ( /* !=0: sector number, 0: failed - invalid cluster# */ + FATFS *fs, /* File system object */ + DWORD clst /* Cluster# to be converted */ +) +{ + clst -= 2; + if (clst >= (fs->max_clust - 2)) return 0; /* Invalid cluster# */ + return clst * fs->csize + fs->database; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Seek directory index */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_seek ( + DIR *dj, /* Pointer to directory object */ + WORD idx /* Directory index number */ +) +{ + DWORD clst; + WORD ic; + + + dj->index = idx; + clst = dj->sclust; + if (clst == 1 || clst >= dj->fs->max_clust) /* Check start cluster range */ + return FR_INT_ERR; + + if (clst == 0) { /* Static table */ + if (idx >= dj->fs->n_rootdir) /* Index is out of range */ + return FR_INT_ERR; + dj->sect = dj->fs->dirbase + idx / (SS(dj->fs) / 32); + } + else { /* Dynamic table */ + ic = SS(dj->fs) / 32 * dj->fs->csize; /* Indexes per cluster */ + while (idx >= ic) { /* Follow cluster chain */ + clst = get_cluster(dj->fs, clst); /* Get next cluster */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (clst < 2 || clst >= dj->fs->max_clust) /* Reached to end of table or int error */ + return FR_INT_ERR; + idx -= ic; + } + dj->clust = clst; + dj->sect = clust2sect(dj->fs, clst) + idx / (SS(dj->fs) / 32); + } + dj->dir = dj->fs->win + (idx % (SS(dj->fs) / 32)) * 32; + + return FR_OK; /* Seek succeeded */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Move directory index next */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_next ( /* FR_OK:Succeeded, FR_NO_FILE:End of table, FR_DENIED:EOT and could not streach */ + DIR *dj, /* Pointer to directory object */ + BOOL streach /* FALSE: Do not streach table, TRUE: Streach table if needed */ +) +{ + DWORD clst; + WORD i; + + + i = dj->index + 1; + if (!i || !dj->sect) /* Report EOT when index has reached 65535 */ + return FR_NO_FILE; + + if (!(i % (SS(dj->fs) / 32))) { /* Sector changed? */ + dj->sect++; /* Next sector */ + + if (dj->sclust == 0) { /* Static table */ + if (i >= dj->fs->n_rootdir) /* Report EOT when end of table */ + return FR_NO_FILE; + } + else { /* Dynamic table */ + if (((i / (SS(dj->fs) / 32)) & (dj->fs->csize - 1)) == 0) { /* Cluster changed? */ + clst = get_cluster(dj->fs, dj->clust); /* Get next cluster */ + if (clst <= 1) return FR_INT_ERR; + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; + if (clst >= dj->fs->max_clust) { /* When it reached end of dinamic table */ +#if !_FS_READONLY + BYTE c; + if (!streach) return FR_NO_FILE; /* When do not streach, report EOT */ + clst = create_chain(dj->fs, dj->clust); /* Streach cluster chain */ + if (clst == 0) return FR_DENIED; /* No free cluster */ + if (clst == 1) return FR_INT_ERR; + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; + /* Clean-up streached table */ + if (move_window(dj->fs, 0)) return FR_DISK_ERR; /* Flush active window */ + mem_set(dj->fs->win, 0, SS(dj->fs)); /* Clear window buffer */ + dj->fs->winsect = clust2sect(dj->fs, clst); /* Cluster start sector */ + for (c = 0; c < dj->fs->csize; c++) { /* Fill the new cluster with 0 */ + dj->fs->wflag = 1; + if (move_window(dj->fs, 0)) return FR_DISK_ERR; + dj->fs->winsect++; + } + dj->fs->winsect -= c; /* Rewind window address */ +#else + return FR_NO_FILE; /* Report EOT */ +#endif + } + dj->clust = clst; /* Initialize data for new cluster */ + dj->sect = clust2sect(dj->fs, clst); + } + } + } + + dj->index = i; + dj->dir = dj->fs->win + (i % (SS(dj->fs) / 32)) * 32; + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Test/Pick/Fit an LFN segment from/to directory entry */ +/*-----------------------------------------------------------------------*/ +#if _USE_LFN +static +const BYTE LfnOfs[] = {1,3,5,7,9,14,16,18,20,22,24,28,30}; /* Offset of LFN chars in the directory entry */ + + +static +BOOL test_lfn ( /* TRUE:Matched, FALSE:Not matched */ + WCHAR *lfnbuf, /* Pointer to the LFN to be compared */ + BYTE *dir /* Pointer to the directory entry containing a part of LFN */ +) +{ + int i, s; + WCHAR wc1, wc2; + + + i = ((dir[LDIR_Ord] & 0xBF) - 1) * 13; /* Offset in the LFN buffer */ + s = 0; + do { + if (i >= _MAX_LFN) return FALSE; /* Out of buffer range? */ + wc1 = LD_WORD(dir+LfnOfs[s]); /* Get both characters to compare */ + wc2 = lfnbuf[i++]; + if (IsLower(wc1)) wc1 -= 0x20; /* Compare it (ignore case) */ + if (IsLower(wc2)) wc2 -= 0x20; + if (wc1 != wc2) return FALSE; + } while (++s < 13 && wc1); /* Repeat until last char or a NUL char is processed */ + + return TRUE; /* The LFN entry matched */ +} + + + +static +BOOL pick_lfn ( /* TRUE:Succeeded, FALSE:Buffer overflow */ + WCHAR *lfnbuf, /* Pointer to the Unicode-LFN buffer */ + BYTE *dir /* Pointer to the directory entry */ +) +{ + int i, s; + WCHAR wchr; + + + i = ((dir[LDIR_Ord] & 0xBF) - 1) * 13; /* Offset in the LFN buffer */ + s = 0; + do { + wchr = LD_WORD(dir+LfnOfs[s]); /* Get an LFN char */ + if (!wchr) break; /* End of LFN? */ + if (i >= _MAX_LFN) return FALSE; /* Buffer overflow */ + lfnbuf[i++] = wchr; /* Store it */ + } while (++s < 13); /* Repeat until last char is copied */ + if (dir[LDIR_Ord] & 0x40) lfnbuf[i] = 0; /* Put terminator if last LFN entry */ + + return TRUE; +} + + +#if !_FS_READONLY +static +void fit_lfn ( + const WCHAR *lfnbuf, /* Pointer to the LFN buffer */ + BYTE *dir, /* Pointer to the directory entry */ + BYTE ord, /* LFN order (1-20) */ + BYTE sum /* SFN sum */ +) +{ + int i, s; + WCHAR wchr; + + + dir[LDIR_Chksum] = sum; /* Set check sum */ + dir[LDIR_Attr] = AM_LFN; /* Set attribute. LFN entry */ + dir[LDIR_Type] = 0; + ST_WORD(dir+LDIR_FstClusLO, 0); + + i = (ord - 1) * 13; /* Offset in the LFN buffer */ + s = wchr = 0; + do { + if (wchr != 0xFFFF) wchr = lfnbuf[i++]; /* Get an effective char */ + ST_WORD(dir+LfnOfs[s], wchr); /* Put it */ + if (!wchr) wchr = 0xFFFF; /* Padding chars following last char */ + } while (++s < 13); + if (wchr == 0xFFFF || !lfnbuf[i]) ord |= 0x40;/* Bottom LFN part is the start of LFN sequence */ + dir[LDIR_Ord] = ord; /* Set the LFN order */ +} + +#endif +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Create numbered name */ +/*-----------------------------------------------------------------------*/ +#if _USE_LFN +void gen_numname ( + BYTE *dst, /* Pointer to genartated SFN */ + const BYTE *src, /* Pointer to source SFN to be modified */ + const WCHAR *lfn, /* Pointer to LFN */ + WORD num /* Sequense number */ +) +{ + char ns[8]; + int i, j; + + + mem_cpy(dst, src, 11); + + if (num > 5) { /* On many collisions, generate a hash number instead of sequencial number */ + do num = (num >> 1) + (num << 15) + (WORD)*lfn++; while (*lfn); + } + + /* itoa */ + i = 7; + do { + ns[i--] = (num % 10) + '0'; + num /= 10; + } while (num); + ns[i] = '~'; + + /* Append the number */ + for (j = 0; j < i && dst[j] != ' '; j++) { + if (IsDBCS1(dst[j])) { + if (j == i - 1) break; + j++; + } + } + do { + dst[j++] = (i < 8) ? ns[i++] : ' '; + } while (j < 8); +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Calculate sum of an SFN */ +/*-----------------------------------------------------------------------*/ +#if _USE_LFN +static +BYTE sum_sfn ( + const BYTE *dir /* Ptr to directory entry */ +) +{ + BYTE sum = 0; + int n = 11; + + do sum = (sum >> 1) + (sum << 7) + *dir++; while (--n); + return sum; +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Find an object in the directory */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_find ( + DIR *dj /* Pointer to the directory object linked to the file name */ +) +{ + FRESULT res; + BYTE a, c, lfen, ord, sum, *dir; + + + res = dir_seek(dj, 0); /* Rewind directory object */ + if (res != FR_OK) return res; + + ord = sum = 0xFF; lfen = *(dj->fn+11) & 1; + do { + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + dir = dj->dir; /* Ptr to the directory entry of current index */ + c = dir[DIR_Name]; + if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */ + a = dir[DIR_Attr] & AM_MASK; +#if _USE_LFN /* LFN configuration */ + if (c == 0xE5 || c == '.' || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */ + ord = 0xFF; + } else { + if (a == AM_LFN) { /* An LFN entry is found */ + if (dj->lfn) { + if (c & 0x40) { /* Is it start of LFN sequence? */ + sum = dir[LDIR_Chksum]; + c &= 0xBF; ord = c; /* LFN start order */ + dj->lfn_idx = dj->index; + } + /* Check LFN validity. Compare LFN if it is out of 8.3 format */ + ord = (c == ord && sum == dir[LDIR_Chksum] && (!lfen || test_lfn(dj->lfn, dir))) ? ord - 1 : 0xFF; + } + } else { /* An SFN entry is found */ + if (ord || sum != sum_sfn(dir)) { /* Did not LFN match? */ + dj->lfn_idx = 0xFFFF; + ord = 0xFF; + } + if (lfen) { /* Match LFN if it is out of 8.3 format */ + if (ord == 0) break; + } else { /* Match SFN if LFN is in 8.3 format */ + if (!mem_cmp(dir, dj->fn, 11)) break; + } + } + } +#else /* Non LFN configuration */ + if (c != 0xE5 && c != '.' && !(a & AM_VOL) && !mem_cmp(dir, dj->fn, 11)) /* Is it a valid entry? */ + break; +#endif + res = dir_next(dj, FALSE); /* Next entry */ + } while (res == FR_OK); + + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read an object from the directory */ +/*-----------------------------------------------------------------------*/ +#if _FS_MINIMIZE <= 2 +static +FRESULT dir_read ( + DIR *dj /* Pointer to the directory object to store read object name */ +) +{ + FRESULT res; + BYTE a, c, ord, sum, *dir; + + + ord = sum = 0xFF; + res = FR_NO_FILE; + while (dj->sect) { + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + dir = dj->dir; /* Ptr to the directory entry of current index */ + c = dir[DIR_Name]; + if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */ + a = dir[DIR_Attr] & AM_MASK; +#if _USE_LFN /* LFN configuration */ + if (c == 0xE5 || c == '.' || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */ + ord = 0xFF; + } else { + if (a == AM_LFN) { /* An LFN entry is found */ + if (c & 0x40) { /* Is it start of LFN sequence? */ + sum = dir[LDIR_Chksum]; + c &= 0xBF; ord = c; + dj->lfn_idx = dj->index; + } + /* Check LFN validity and capture it */ + ord = (c == ord && sum == dir[LDIR_Chksum] && pick_lfn(dj->lfn, dir)) ? ord - 1 : 0xFF; + } else { /* An SFN entry is found */ + if (ord || sum != sum_sfn(dir)) /* Is there a valid LFN entry? */ + dj->lfn_idx = 0xFFFF; /* No LFN. */ + break; + } + } +#else /* Non LFN configuration */ + if (c != 0xE5 && c != '.' && !(a & AM_VOL)) /* Is it a valid entry? */ + break; +#endif + res = dir_next(dj, FALSE); /* Next entry */ + if (res != FR_OK) break; + } + + if (res != FR_OK) dj->sect = 0; + + return res; +} +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Register an object to the directory */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT dir_register ( /* FR_OK:Successful, FR_DENIED:No free entry or too many SFN collision, FR_DISK_ERR:Disk error */ + DIR *dj /* Target directory with object name to be created */ +) +{ + FRESULT res; + BYTE c, *dir; + +#if _USE_LFN /* LFN configuration */ + WORD n, ne, is; + BYTE sn[12], *fn, sum; + WCHAR *lfn; + + fn = dj->fn; lfn = dj->lfn; + mem_cpy(sn, fn, 12); + if (sn[11] & 1) { /* When LFN is out of 8.3 format, generate a numbered name */ + fn[11] = 0; dj->lfn = NULL; /* Find only SFN */ + for (n = 1; n < 100; n++) { + gen_numname(fn, sn, lfn, n); /* Generate a numbered name */ + res = dir_find(dj); /* Check if the name collides with existing SFN */ + if (res != FR_OK) break; + } + if (n == 100) return FR_DENIED; /* Abort if too many collisions */ + if (res != FR_NO_FILE) return res; /* Abort if the result is other than 'not collided' */ + fn[11] = sn[11]; dj->lfn = lfn; + } + if (sn[11] & 2) { /* When eliminate LFN, reserve only an SFN entry. */ + ne = 1; + } else { /* Otherwise reserve an SFN + LFN entries. */ + for (ne = 0; lfn[ne]; ne++) ; + ne = (ne + 25) / 13; + } + + /* Reserve contiguous entries */ + res = dir_seek(dj, 0); + if (res != FR_OK) return res; + n = is = 0; + do { + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + c = *dj->dir; /* Check the entry status */ + if (c == 0xE5 || c == 0) { /* Is it a blank entry? */ + if (n == 0) is = dj->index; /* First index of the contigulus entry */ + if (++n == ne) break; /* A contiguous entry that requiered count is found */ + } else { + n = 0; /* Not a blank entry. Restart to search */ + } + res = dir_next(dj, TRUE); /* Next entry with table streach */ + } while (res == FR_OK); + + if (res == FR_OK && ne > 1) { /* Initialize LFN entry if needed */ + res = dir_seek(dj, is); + if (res == FR_OK) { + sum = sum_sfn(dj->fn); /* Sum of the SFN tied to the LFN */ + ne--; + do { /* Store LFN entries in bottom first */ + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + fit_lfn(dj->lfn, dj->dir, (BYTE)ne, sum); + dj->fs->wflag = 1; + res = dir_next(dj, FALSE); /* Next entry */ + } while (res == FR_OK && --ne); + } + } + +#else /* Non LFN configuration */ + res = dir_seek(dj, 0); + if (res == FR_OK) { + do { /* Find a blank entry for the SFN */ + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + c = *dj->dir; + if (c == 0xE5 || c == 0) break; /* Is it a blank entry? */ + res = dir_next(dj, TRUE); /* Next entry with table streach */ + } while (res == FR_OK); + } +#endif + + if (res == FR_OK) { /* Initialize the SFN entry */ + res = move_window(dj->fs, dj->sect); + if (res == FR_OK) { + dir = dj->dir; + mem_set(dir, 0, 32); /* Clean the entry */ + mem_cpy(dir, dj->fn, 11); /* Put SFN */ + dir[DIR_NTres] = *(dj->fn+11) & 0x18; /* Put NT flag */ + dj->fs->wflag = 1; + } + } + + return res; +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Remove an object from the directory */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY && !_FS_MINIMIZE +static +FRESULT dir_remove ( /* FR_OK: Successful, FR_DISK_ERR: A disk error */ + DIR *dj /* Directory object pointing the entry to be removed */ +) +{ + FRESULT res; + +#if _USE_LFN /* LFN configuration */ + WORD i; + + i = dj->index; /* SFN index */ + res = dir_seek(dj, (WORD)((dj->lfn_idx == 0xFFFF) ? i : dj->lfn_idx)); /* Goto the SFN or top of the LFN entries */ + if (res == FR_OK) { + do { + res = move_window(dj->fs, dj->sect); + if (res != FR_OK) break; + *dj->dir = 0xE5; /* Mark the entry "deleted" */ + dj->fs->wflag = 1; + if (dj->index >= i) break; /* When SFN is deleted, all entries of the object is deleted. */ + res = dir_next(dj, FALSE); /* Next entry */ + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR; + } + +#else /* Non LFN configuration */ + res = dir_seek(dj, dj->index); + if (res == FR_OK) { + res = move_window(dj->fs, dj->sect); + if (res == FR_OK) { + *dj->dir = 0xE5; /* Mark the entry "deleted" */ + dj->fs->wflag = 1; + } + } +#endif + + return res; +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Pick a segment and create the object name in directory form */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT create_name ( + DIR *dj, /* Pointer to the directory object */ + const char **path /* Pointer to pointer to the segment in the path string */ +) +{ +#if _USE_LFN + BYTE c, b, cf, *sfn; + WCHAR w, *lfn; + int i, ni, si, di; + const char *p; + + /* Create LFN in Unicode */ + si = di = 0; + p = *path; + lfn = dj->lfn; + for (;;) { + w = (BYTE)p[si++]; /* Get a character */ + if (w < ' ' || w == '/' || w == '\\') break; /* Break on end of segment */ + if (IsDBCS1(w)) { /* If it is DBC 1st byte */ + c = p[si++]; /* Get 2nd byte */ + if (!IsDBCS2(c)) /* Reject invalid DBC */ + return FR_INVALID_NAME; + w = (w << 8) + c; + } else { + if (chk_chr("\"*:<>\?|\x7F", w)) /* Reject unallowable chars for LFN */ + return FR_INVALID_NAME; + } + w = ff_convert(w, 1); /* Convert OEM to Unicode, store it */ + if (!w || di >= _MAX_LFN) /* Reject invalid code or too long name */ + return FR_INVALID_NAME; + lfn[di++] = w; + } + *path = &p[si]; /* Rerurn pointer to the next segment */ + cf = (w < ' ') ? 4 : 0; /* Set last segment flag if end of path */ + + while (di) { /* Strip trailing spaces and dots */ + w = lfn[di - 1]; + if (w != ' ' && w != '.') break; + di--; + } + if (!di) return FR_INVALID_NAME; /* Reject null string */ + + lfn[di] = 0; /* LFN is created */ + + /* Create SFN in directory form */ + sfn = dj->fn; + mem_set(sfn, ' ', 11); + for (si = 0; lfn[si] == ' ' || lfn[si] == '.'; si++) ; /* Strip leading spaces and dots */ + if (si) cf |= 1; + while (di && lfn[di - 1] != '.') di--; /* Find extension (di<=si: no extension) */ + + b = i = 0; ni = 8; + for (;;) { + w = lfn[si++]; /* Get an LFN char */ + if (w == 0) break; /* Break when enf of the LFN */ + if (w == ' ' || (w == '.' && si != di)) { /* Remove spaces and dots */ + cf |= 1; continue; + } + if (i >= ni || si == di) { /* Here is extension or end of SFN */ + if (ni == 11) { /* Extension is longer than 3 bytes */ + cf |= 1; break; + } + if (si != di) cf |= 1; /* File name is longer than 8 bytes */ + if (si > di) break; /* No extension */ + si = di; i = 8; ni = 11; /* Enter extension section */ + b <<= 2; continue; + } + w = ff_convert(w, 0); /* Unicode -> OEM code */ + if (w >= 0x80) cf |= 0x20; /* If there is any extended char, force create an LFN */ + if (w >= 0x100) { /* Double byte char */ + if (i >= ni - 1) { + cf |= 1; i = ni; continue; + } + sfn[i++] = (BYTE)(w >> 8); + } else { /* Single byte char */ + if (chk_chr("+,;[=]", w)) { /* Replace unallowable chars for SFN */ + w = '_'; cf |= 1; + } else { + if (IsUpper(w)) { /* Large capital */ + b |= 2; + } else { + if (IsLower(w)) { /* Small capital */ + b |= 1; w -= 0x20; + } + } + } + } + sfn[i++] = (BYTE)w; + } + if (sfn[0] == 0xE5) sfn[0] = 0x05; /* When first char collides with 0xE5, replace it with 0x05 */ + + if (ni == 8) b <<= 2; + if ((cf & 0x21) == 0) { /* When LFN is in 8.3 format without extended char, NT flags are created */ + if ((b & 0x03) == 0x01) cf |= 0x10; /* NT flag (Extension has only small capital) */ + if ((b & 0x0C) == 0x04) cf |= 0x08; /* NT flag (Filename has only small capital) */ + if ((b & 0x0C) != 0x0C && (b & 0x03) != 0x03) cf |= 2; /* Eliminate LFN when non composite capitals */ + } + + sfn[11] = cf; /* SFN is created */ + +#else + BYTE c, d, b, *sfn; + int ni, si, i; + const char *p; + + /* Create file name in directory form */ + sfn = dj->fn; + mem_set(sfn, ' ', 11); + si = i = b = 0; ni = 8; + p = *path; + for (;;) { + c = p[si++]; + if (c < ' ' || c == '/' || c == '\\') break; /* Break on end of segment */ + if (c == '.' || i >= ni) { + if (ni != 8 || c != '.') return FR_INVALID_NAME; + i = 8; ni = 11; + b <<= 2; continue; + } + if (c >= 0x80) b |= 3; /* If there is any extended char, eliminate NT flag */ + if (IsDBCS1(c)) { /* If it is DBC 1st byte */ + d = p[si++]; /* Get 2nd byte */ + if (!IsDBCS2(d) || i >= ni - 1) /* Reject invalid DBC */ + return FR_INVALID_NAME; + sfn[i++] = c; + sfn[i++] = d; + } else { + if (chk_chr(" +,;[=]\"*:<>\?|\x7F", c)) /* Reject unallowable chrs for SFN */ + return FR_INVALID_NAME; + if (IsUpper(c)) { + b |= 2; + } else { + if (IsLower(c)) { + b |= 1; c -= 0x20; + } + } + sfn[i++] = c; + } + } + *path = &p[si]; /* Rerurn pointer to the next segment */ + c = (c < ' ') ? 4 : 0; /* Set last segment flag if end of path */ + + if (!i) return FR_INVALID_NAME; /* Reject null string */ + if (sfn[0] == 0xE5) sfn[0] = 0x05; /* When first char collides with 0xE5, replace it with 0x05 */ + + if (ni == 8) b <<= 2; + if ((b & 0x03) == 0x01) c |= 0x10; /* NT flag (Extension has only small capital) */ + if ((b & 0x0C) == 0x04) c |= 0x08; /* NT flag (Filename has only small capital) */ + + sfn[11] = c; /* Store NT flag, File name is created */ +#endif + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Get file information from directory entry */ +/*-----------------------------------------------------------------------*/ +#if _FS_MINIMIZE <= 1 +static +void get_fileinfo ( /* No return code */ + DIR *dj, /* Pointer to the directory object */ + FILINFO *fno /* Pointer to store the file information */ +) +{ + int i; + BYTE c, nt, *dir; + char *p; + + + p = fno->fname; + if (dj->sect) { + dir = dj->dir; + nt = dir[DIR_NTres]; /* NT flag */ + for (i = 0; i < 8; i++) { /* Copy file name body */ + c = dir[i]; + if (c == ' ') break; + if (c == 0x05) c = 0xE5; + if ((nt & 0x08) && IsUpper(c)) c += 0x20; + *p++ = c; + } + if (dir[8] != ' ') { /* Copy file name extension */ + *p++ = '.'; + for (i = 8; i < 11; i++) { + c = dir[i]; + if (c == ' ') break; + if ((nt & 0x10) && IsUpper(c)) c += 0x20; + *p++ = c; + } + } + fno->fattrib = dir[DIR_Attr]; /* Attribute */ + fno->fsize = LD_DWORD(dir+DIR_FileSize); /* Size */ + fno->fdate = LD_WORD(dir+DIR_WrtDate); /* Date */ + fno->ftime = LD_WORD(dir+DIR_WrtTime); /* Time */ + } + *p = 0; + +#if _USE_LFN + p = fno->lfname; + if (p) { + WCHAR wchr, *lfn; + + i = 0; + if (dj->sect && dj->lfn_idx != 0xFFFF) {/* Get LFN if available */ + lfn = dj->lfn; + while ((wchr = *lfn++) != 0) { /* Get an LFN char */ + wchr = ff_convert(wchr, 0); /* Unicode -> OEM code */ + if (!wchr) { i = 0; break; } /* Conversion error, no LFN */ + if (_DF1S && wchr >= 0x100) /* Put 1st byte if it is a DBC */ + p[i++] = (char)(wchr >> 8); + p[i++] = (char)wchr; + if (i >= fno->lfsize) { i = 0; break; } /* Buffer overrun, no LFN */ + } + } + p[i] = 0; /* Terminator */ + } +#endif +} +#endif /* _FS_MINIMIZE <= 1 */ + + + + +/*-----------------------------------------------------------------------*/ +/* Follow a file path */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ + DIR *dj, /* Directory object to return last directory and found object */ + const char *path /* Full-path string to find a file or directory */ +) +{ + FRESULT res; + BYTE *dir, last; + + + if (*path == '/' || *path == '\\' ) path++; /* Strip heading separator */ + + dj->sclust = /* Set start directory (root dir) */ + (dj->fs->fs_type == FS_FAT32) ? dj->fs->dirbase : 0; + + if ((BYTE)*path < ' ') { /* Null path means the root directory */ + res = dir_seek(dj, 0); + dj->dir = NULL; + + } else { /* Follow path */ + for (;;) { + res = create_name(dj, &path); /* Get a segment */ + if (res != FR_OK) break; + res = dir_find(dj); /* Find it */ + last = *(dj->fn+11) & 4; + if (res != FR_OK) { /* Could not find the object */ + if (res == FR_NO_FILE && !last) + res = FR_NO_PATH; + break; + } + if (last) break; /* Last segment match. Function completed. */ + dir = dj->dir; /* There is next segment. Follow the sub directory */ + if (!(dir[DIR_Attr] & AM_DIR)) { /* Cannot follow because it is a file */ + res = FR_NO_PATH; break; + } + dj->sclust = ((DWORD)LD_WORD(dir+DIR_FstClusHI) << 16) | LD_WORD(dir+DIR_FstClusLO); + } + } + + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Load boot record and check if it is an FAT boot record */ +/*-----------------------------------------------------------------------*/ + +static +BYTE check_fs ( /* 0:The FAT boot record, 1:Valid boot record but not an FAT, 2:Not a boot record, 3:Error */ + FATFS *fs, /* File system object */ + DWORD sect /* Sector# (lba) to check if it is an FAT boot record or not */ +) +{ + if (disk_read(fs->drive, fs->win, sect, 1) != RES_OK) /* Load boot record */ + return 3; + if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) /* Check record signature (always placed at offset 510 even if the sector size is >512) */ + return 2; + + if (!mem_cmp(&fs->win[BS_FilSysType], "FAT", 3)) /* Check FAT signature */ + return 0; + if (!mem_cmp(&fs->win[BS_FilSysType32], "FAT32", 5) && !(fs->win[BPB_ExtFlags] & 0x80)) + return 0; + + return 1; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Make sure that the file system is valid */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ + const char **path, /* Pointer to pointer to the path name (drive number) */ + FATFS **rfs, /* Pointer to pointer to the found file system object */ + BYTE chk_wp /* !=0: Check media write protection for write access */ +) +{ + FRESULT res; + BYTE vol, fmt, *tbl; + DSTATUS stat; + DWORD bsect, fsize, tsect, mclst; + const char *p = *path; + FATFS *fs; + + + /* Get logical drive number from the path name */ + vol = p[0] - '0'; /* Is there a drive number? */ + if (vol <= 9 && p[1] == ':') { + p += 2; /* Found a drive number, get and strip it */ + *path = p; /* Return pointer to the path name */ + } else { + vol = 0; /* No drive number is given, use drive number 0 as default */ + } + + /* Check if the logical drive number is valid or not */ + if (vol >= _DRIVES) return FR_INVALID_DRIVE; /* Is the drive number valid? */ + *rfs = fs = FatFs[vol]; /* Returen pointer to the corresponding file system object */ + if (!fs) return FR_NOT_ENABLED; /* Is the file system object registered? */ + + ENTER_FF(fs); /* Lock file system */ + + if (fs->fs_type) { /* If the logical drive has been mounted */ + stat = disk_status(fs->drive); + if (!(stat & STA_NOINIT)) { /* and physical drive is kept initialized (has not been changed), */ +#if !_FS_READONLY + if (chk_wp && (stat & STA_PROTECT)) /* Check write protection if needed */ + return FR_WRITE_PROTECTED; +#endif + return FR_OK; /* The file system object is valid */ + } + } + + /* The logical drive must be re-mounted. Following code attempts to mount the volume */ + + fs->fs_type = 0; /* Clear the file system object */ + fs->drive = LD2PD(vol); /* Bind the logical drive and a physical drive */ + stat = disk_initialize(fs->drive); /* Initialize low level disk I/O layer */ + if (stat & STA_NOINIT) /* Check if the drive is ready */ + return FR_NOT_READY; +#if _MAX_SS != 512 /* Get disk sector size if needed */ + if (disk_ioctl(fs->drive, GET_SECTOR_SIZE, &SS(fs)) != RES_OK || SS(fs) > _MAX_SS) + return FR_NO_FILESYSTEM; +#endif +#if !_FS_READONLY + if (chk_wp && (stat & STA_PROTECT)) /* Check write protection if needed */ + return FR_WRITE_PROTECTED; +#endif + /* Search FAT partition on the drive */ + fmt = check_fs(fs, bsect = 0); /* Check sector 0 as an SFD format */ + if (fmt == 1) { /* Not an FAT boot record, it may be patitioned */ + /* Check a partition listed in top of the partition table */ + tbl = &fs->win[MBR_Table + LD2PT(vol) * 16]; /* Partition table */ + if (tbl[4]) { /* Is the partition existing? */ + bsect = LD_DWORD(&tbl[8]); /* Partition offset in LBA */ + fmt = check_fs(fs, bsect); /* Check the partition */ + } + } + if (fmt == 3) return FR_DISK_ERR; + if (fmt || LD_WORD(fs->win+BPB_BytsPerSec) != SS(fs)) /* No valid FAT patition is found */ + return FR_NO_FILESYSTEM; + + /* Initialize the file system object */ + fsize = LD_WORD(fs->win+BPB_FATSz16); /* Number of sectors per FAT */ + if (!fsize) fsize = LD_DWORD(fs->win+BPB_FATSz32); + fs->sects_fat = fsize; + fs->n_fats = fs->win[BPB_NumFATs]; /* Number of FAT copies */ + fsize *= fs->n_fats; /* (Number of sectors in FAT area) */ + fs->fatbase = bsect + LD_WORD(fs->win+BPB_RsvdSecCnt); /* FAT start sector (lba) */ + fs->csize = fs->win[BPB_SecPerClus]; /* Number of sectors per cluster */ + fs->n_rootdir = LD_WORD(fs->win+BPB_RootEntCnt); /* Nmuber of root directory entries */ + tsect = LD_WORD(fs->win+BPB_TotSec16); /* Number of sectors on the file system */ + if (!tsect) tsect = LD_DWORD(fs->win+BPB_TotSec32); + fs->max_clust = mclst = (tsect /* Last cluster# + 1 */ + - LD_WORD(fs->win+BPB_RsvdSecCnt) - fsize - fs->n_rootdir / (SS(fs)/32) + ) / fs->csize + 2; + + fmt = FS_FAT12; /* Determine the FAT sub type */ + if (mclst >= 0xFF7) fmt = FS_FAT16; /* Number of clusters >= 0xFF5 */ + if (mclst >= 0xFFF7) fmt = FS_FAT32; /* Number of clusters >= 0xFFF5 */ + + if (fmt == FS_FAT32) + fs->dirbase = LD_DWORD(fs->win+BPB_RootClus); /* Root directory start cluster */ + else + fs->dirbase = fs->fatbase + fsize; /* Root directory start sector (lba) */ + fs->database = fs->fatbase + fsize + fs->n_rootdir / (SS(fs)/32); /* Data start sector (lba) */ + +#if !_FS_READONLY + /* Initialize allocation information */ + fs->free_clust = 0xFFFFFFFF; + fs->wflag = 0; + /* Get fsinfo if needed */ + if (fmt == FS_FAT32) { + fs->fsi_sector = bsect + LD_WORD(fs->win+BPB_FSInfo); + fs->fsi_flag = 0; + if (disk_read(fs->drive, fs->win, fs->fsi_sector, 1) == RES_OK && + LD_WORD(fs->win+BS_55AA) == 0xAA55 && + LD_DWORD(fs->win+FSI_LeadSig) == 0x41615252 && + LD_DWORD(fs->win+FSI_StrucSig) == 0x61417272) { + fs->last_clust = LD_DWORD(fs->win+FSI_Nxt_Free); + fs->free_clust = LD_DWORD(fs->win+FSI_Free_Count); + } + } +#endif + fs->winsect = 0; + fs->fs_type = fmt; /* FAT syb-type */ + fs->id = ++Fsid; /* File system mount ID */ + res = FR_OK; + + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Check if the file/dir object is valid or not */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT validate ( /* FR_OK(0): The object is valid, !=0: Invalid */ + FATFS *fs, /* Pointer to the file system object */ + WORD id /* Member id of the target object to be checked */ +) +{ + if (!fs || !fs->fs_type || fs->id != id) + return FR_INVALID_OBJECT; + + ENTER_FF(fs); /* Lock file system */ + + if (disk_status(fs->drive) & STA_NOINIT) + return FR_NOT_READY; + + return FR_OK; +} + + + + +/*-------------------------------------------------------------------------- + + Public Functions + +--------------------------------------------------------------------------*/ + + + +/*-----------------------------------------------------------------------*/ +/* Mount/Unmount a Locical Drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mount ( + BYTE vol, /* Logical drive number to be mounted/unmounted */ + FATFS *fs /* Pointer to new file system object (NULL for unmount)*/ +) +{ + FATFS *rfs; + + + if (vol >= _DRIVES) /* Check if the drive number is valid */ + return FR_INVALID_DRIVE; + rfs = FatFs[vol]; /* Get current state */ + + if (rfs) { +#if _FS_REENTRANT /* Discard sync object of the current volume */ + if (!ff_del_syncobj(fs->sobj)) return FR_INT_ERR; +#endif + rfs->fs_type = 0; /* Clear old fs object */ + } + + if (fs) { + fs->fs_type = 0; /* Clear new fs object */ +#if _FS_REENTRANT /* Create sync object for the new volume */ + if (!ff_cre_syncobj(vol, &fs->sobj)) return FR_INT_ERR; +#endif + } + FatFs[vol] = fs; /* Register new fs object */ + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Open or Create a File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_open ( + FIL *fp, /* Pointer to the blank file object */ + const char *path, /* Pointer to the file name */ + BYTE mode /* Access mode and file open mode flags */ +) +{ + FRESULT res; + DIR dj; + NAMEBUF(sfn, lfn); + BYTE *dir; + + + fp->fs = NULL; /* Clear file object */ +#if !_FS_READONLY + mode &= (FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW); + res = auto_mount(&path, &dj.fs, (BYTE)(mode & (FA_WRITE | FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW))); +#else + mode &= FA_READ; + res = auto_mount(&path, &dj.fs, 0); +#endif + if (res != FR_OK) LEAVE_FF(dj.fs, res); + INITBUF(dj, sfn, lfn); + res = follow_path(&dj, path); /* Follow the file path */ + +#if !_FS_READONLY + /* Create or Open a file */ + if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) { + DWORD ps, cl; + + if (res != FR_OK) { /* No file, create new */ + if (res == FR_NO_FILE) + res = dir_register(&dj); + if (res != FR_OK) LEAVE_FF(dj.fs, res); + mode |= FA_CREATE_ALWAYS; + dir = dj.dir; + } + else { /* Any object is already existing */ + if (mode & FA_CREATE_NEW) /* Cannot create new */ + LEAVE_FF(dj.fs, FR_EXIST); + dir = dj.dir; + if (!dir || (dir[DIR_Attr] & (AM_RDO | AM_DIR))) /* Cannot overwrite it (R/O or DIR) */ + LEAVE_FF(dj.fs, FR_DENIED); + if (mode & FA_CREATE_ALWAYS) { /* Resize it to zero if needed */ + cl = ((DWORD)LD_WORD(dir+DIR_FstClusHI) << 16) | LD_WORD(dir+DIR_FstClusLO); /* Get start cluster */ + ST_WORD(dir+DIR_FstClusHI, 0); /* cluster = 0 */ + ST_WORD(dir+DIR_FstClusLO, 0); + ST_DWORD(dir+DIR_FileSize, 0); /* size = 0 */ + dj.fs->wflag = 1; + ps = dj.fs->winsect; /* Remove the cluster chain */ + if (cl) { + res = remove_chain(dj.fs, cl); + if (res) LEAVE_FF(dj.fs, res); + dj.fs->last_clust = cl - 1; /* Reuse the cluster hole */ + } + res = move_window(dj.fs, ps); + if (res != FR_OK) LEAVE_FF(dj.fs, res); + } + } + if (mode & FA_CREATE_ALWAYS) { + dir[DIR_Attr] = 0; /* Reset attribute */ + ps = get_fattime(); + ST_DWORD(dir+DIR_CrtTime, ps); /* Created time */ + dj.fs->wflag = 1; + mode |= FA__WRITTEN; /* Set file changed flag */ + } + } + /* Open an existing file */ + else { +#endif /* !_FS_READONLY */ + if (res != FR_OK) LEAVE_FF(dj.fs, res); /* Follow failed */ + dir = dj.dir; + if (!dir || (dir[DIR_Attr] & AM_DIR)) /* It is a directory */ + LEAVE_FF(dj.fs, FR_NO_FILE); +#if !_FS_READONLY + if ((mode & FA_WRITE) && (dir[DIR_Attr] & AM_RDO)) /* R/O violation */ + LEAVE_FF(dj.fs, FR_DENIED); + } + fp->dir_sect = dj.fs->winsect; /* Pointer to the directory entry */ + fp->dir_ptr = dj.dir; +#endif + fp->flag = mode; /* File access mode */ + fp->org_clust = /* File start cluster */ + ((DWORD)LD_WORD(dir+DIR_FstClusHI) << 16) | LD_WORD(dir+DIR_FstClusLO); + fp->fsize = LD_DWORD(dir+DIR_FileSize); /* File size */ + fp->fptr = 0; fp->csect = 255; /* File pointer */ + fp->dsect = 0; + fp->fs = dj.fs; fp->id = dj.fs->id; /* Owner file system object of the file */ + + LEAVE_FF(dj.fs, FR_OK); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_read ( + FIL *fp, /* Pointer to the file object */ + void *buff, /* Pointer to data buffer */ + UINT btr, /* Number of bytes to read */ + UINT *br /* Pointer to number of bytes read */ +) +{ + FRESULT res; + DWORD clst, sect, remain; + UINT rcnt, cc; + BYTE *rbuff = buff; + + + *br = 0; + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->flag & FA__ERROR) /* Check abort flag */ + LEAVE_FF(fp->fs, FR_INT_ERR); + if (!(fp->flag & FA_READ)) /* Check access mode */ + LEAVE_FF(fp->fs, FR_DENIED); + remain = fp->fsize - fp->fptr; + if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */ + + for ( ; btr; /* Repeat until all data transferred */ + rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) { + if ((fp->fptr % SS(fp->fs)) == 0) { /* On the sector boundary? */ + if (fp->csect >= fp->fs->csize) { /* On the cluster boundary? */ + clst = (fp->fptr == 0) ? /* On the top of the file? */ + fp->org_clust : get_cluster(fp->fs, fp->curr_clust); + if (clst <= 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->curr_clust = clst; /* Update current cluster */ + fp->csect = 0; /* Reset sector offset in the cluster */ + } + sect = clust2sect(fp->fs, fp->curr_clust); /* Get current sector */ + if (!sect) ABORT(fp->fs, FR_INT_ERR); + sect += fp->csect; + cc = btr / SS(fp->fs); /* When remaining bytes >= sector size, */ + if (cc) { /* Read maximum contiguous sectors directly */ + if (fp->csect + cc > fp->fs->csize) /* Clip at cluster boundary */ + cc = fp->fs->csize - fp->csect; + if (disk_read(fp->fs->drive, rbuff, sect, (BYTE)cc) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + fp->csect += (BYTE)cc; /* Next sector address in the cluster */ + rcnt = SS(fp->fs) * cc; /* Number of bytes transferred */ + continue; + } +#if !_FS_TINY +#if !_FS_READONLY + if (fp->flag & FA__DIRTY) { /* Write sector I/O buffer if needed */ + if (disk_write(fp->fs->drive, fp->buf, fp->dsect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA__DIRTY; + } +#endif + if (fp->dsect != sect) { /* Fill sector buffer with file data */ + if (disk_read(fp->fs->drive, fp->buf, sect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + } +#endif + fp->dsect = sect; + fp->csect++; /* Next sector address in the cluster */ + } + rcnt = SS(fp->fs) - (fp->fptr % SS(fp->fs)); /* Get partial sector data from sector buffer */ + if (rcnt > btr) rcnt = btr; +#if _FS_TINY + if (move_window(fp->fs, fp->dsect)) /* Move sector window */ + ABORT(fp->fs, FR_DISK_ERR); + mem_cpy(rbuff, &fp->fs->win[fp->fptr % SS(fp->fs)], rcnt); /* Pick partial sector */ +#else + mem_cpy(rbuff, &fp->buf[fp->fptr % SS(fp->fs)], rcnt); /* Pick partial sector */ +#endif + } + + + LEAVE_FF(fp->fs, FR_OK); +} + + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Write File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_write ( + FIL *fp, /* Pointer to the file object */ + const void *buff, /* Pointer to the data to be written */ + UINT btw, /* Number of bytes to write */ + UINT *bw /* Pointer to number of bytes written */ +) +{ + FRESULT res; + DWORD clst, sect; + UINT wcnt, cc; + const BYTE *wbuff = buff; + + + *bw = 0; + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->flag & FA__ERROR) /* Check abort flag */ + LEAVE_FF(fp->fs, FR_INT_ERR); + if (!(fp->flag & FA_WRITE)) /* Check access mode */ + LEAVE_FF(fp->fs, FR_DENIED); + if (fp->fsize + btw < fp->fsize) btw = 0; /* File size cannot reach 4GB */ + + for ( ; btw; /* Repeat until all data transferred */ + wbuff += wcnt, fp->fptr += wcnt, *bw += wcnt, btw -= wcnt) { + if ((fp->fptr % SS(fp->fs)) == 0) { /* On the sector boundary? */ + if (fp->csect >= fp->fs->csize) { /* On the cluster boundary? */ + if (fp->fptr == 0) { /* On the top of the file? */ + clst = fp->org_clust; /* Follow from the origin */ + if (clst == 0) /* When there is no cluster chain, */ + fp->org_clust = clst = create_chain(fp->fs, 0); /* Create a new cluster chain */ + } else { /* Middle or end of the file */ + clst = create_chain(fp->fs, fp->curr_clust); /* Follow or streach cluster chain */ + } + if (clst == 0) break; /* Could not allocate a new cluster (disk full) */ + if (clst == 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->curr_clust = clst; /* Update current cluster */ + fp->csect = 0; /* Reset sector address in the cluster */ + } +#if _FS_TINY + if (fp->fs->winsect == fp->dsect && move_window(fp->fs, 0)) /* Write back data buffer prior to following direct transfer */ + ABORT(fp->fs, FR_DISK_ERR); +#else + if (fp->flag & FA__DIRTY) { /* Write back data buffer prior to following direct transfer */ + if (disk_write(fp->fs->drive, fp->buf, fp->dsect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA__DIRTY; + } +#endif + sect = clust2sect(fp->fs, fp->curr_clust); /* Get current sector */ + if (!sect) ABORT(fp->fs, FR_INT_ERR); + sect += fp->csect; + cc = btw / SS(fp->fs); /* When remaining bytes >= sector size, */ + if (cc) { /* Write maximum contiguous sectors directly */ + if (fp->csect + cc > fp->fs->csize) /* Clip at cluster boundary */ + cc = fp->fs->csize - fp->csect; + if (disk_write(fp->fs->drive, wbuff, sect, (BYTE)cc) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + fp->csect += (BYTE)cc; /* Next sector address in the cluster */ + wcnt = SS(fp->fs) * cc; /* Number of bytes transferred */ + continue; + } +#if _FS_TINY + if (fp->fptr >= fp->fsize) { /* Avoid silly buffer filling at growing edge */ + if (move_window(fp->fs, 0)) ABORT(fp->fs, FR_DISK_ERR); + fp->fs->winsect = sect; + } +#else + if (fp->dsect != sect) { /* Fill sector buffer with file data */ + if (fp->fptr < fp->fsize && + disk_read(fp->fs->drive, fp->buf, sect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + } +#endif + fp->dsect = sect; + fp->csect++; /* Next sector address in the cluster */ + } + wcnt = SS(fp->fs) - (fp->fptr % SS(fp->fs)); /* Put partial sector into file I/O buffer */ + if (wcnt > btw) wcnt = btw; +#if _FS_TINY + if (move_window(fp->fs, fp->dsect)) /* Move sector window */ + ABORT(fp->fs, FR_DISK_ERR); + mem_cpy(&fp->fs->win[fp->fptr % SS(fp->fs)], wbuff, wcnt); /* Fit partial sector */ + fp->fs->wflag = 1; +#else + mem_cpy(&fp->buf[fp->fptr % SS(fp->fs)], wbuff, wcnt); /* Fit partial sector */ + fp->flag |= FA__DIRTY; +#endif + } + + if (fp->fptr > fp->fsize) fp->fsize = fp->fptr; /* Update file size if needed */ + fp->flag |= FA__WRITTEN; /* Set file changed flag */ + + LEAVE_FF(fp->fs, FR_OK); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Synchronize the File Object */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_sync ( + FIL *fp /* Pointer to the file object */ +) +{ + FRESULT res; + DWORD tim; + BYTE *dir; + + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res == FR_OK) { + if (fp->flag & FA__WRITTEN) { /* Has the file been written? */ +#if !_FS_TINY /* Write-back dirty buffer */ + if (fp->flag & FA__DIRTY) { + if (disk_write(fp->fs->drive, fp->buf, fp->dsect, 1) != RES_OK) + LEAVE_FF(fp->fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA__DIRTY; + } +#endif + /* Update the directory entry */ + res = move_window(fp->fs, fp->dir_sect); + if (res == FR_OK) { + dir = fp->dir_ptr; + dir[DIR_Attr] |= AM_ARC; /* Set archive bit */ + ST_DWORD(dir+DIR_FileSize, fp->fsize); /* Update file size */ + ST_WORD(dir+DIR_FstClusLO, fp->org_clust); /* Update start cluster */ + ST_WORD(dir+DIR_FstClusHI, fp->org_clust >> 16); + tim = get_fattime(); /* Updated time */ + ST_DWORD(dir+DIR_WrtTime, tim); + fp->flag &= (BYTE)~FA__WRITTEN; + fp->fs->wflag = 1; + res = sync(fp->fs); + } + } + } + + LEAVE_FF(fp->fs, res); +} + +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Close File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_close ( + FIL *fp /* Pointer to the file object to be closed */ +) +{ + FRESULT res; + + +#if _FS_READONLY + res = validate(fp->fs, fp->id); + if (res == FR_OK) fp->fs = NULL; + LEAVE_FF(fp->fs, res); +#else + res = f_sync(fp); + if (res == FR_OK) fp->fs = NULL; + return res; +#endif +} + + + + +#if _FS_MINIMIZE <= 2 +/*-----------------------------------------------------------------------*/ +/* Seek File R/W Pointer */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_lseek ( + FIL *fp, /* Pointer to the file object */ + DWORD ofs /* File pointer from top of file */ +) +{ + FRESULT res; + DWORD clst, bcs, nsect, ifptr; + + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->flag & FA__ERROR) /* Check abort flag */ + LEAVE_FF(fp->fs, FR_INT_ERR); + if (ofs > fp->fsize /* In read-only mode, clip offset with the file size */ +#if !_FS_READONLY + && !(fp->flag & FA_WRITE) +#endif + ) ofs = fp->fsize; + + ifptr = fp->fptr; + fp->fptr = 0; fp->csect = 255; + nsect = 0; + if (ofs > 0) { + bcs = (DWORD)fp->fs->csize * SS(fp->fs); /* Cluster size (byte) */ + if (ifptr > 0 && + (ofs - 1) / bcs >= (ifptr - 1) / bcs) { /* When seek to same or following cluster, */ + fp->fptr = (ifptr - 1) & ~(bcs - 1); /* start from the current cluster */ + ofs -= fp->fptr; + clst = fp->curr_clust; + } else { /* When seek to back cluster, */ + clst = fp->org_clust; /* start from the first cluster */ +#if !_FS_READONLY + if (clst == 0) { /* If no cluster chain, create a new chain */ + clst = create_chain(fp->fs, 0); + if (clst == 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->org_clust = clst; + } +#endif + fp->curr_clust = clst; + } + if (clst != 0) { + while (ofs > bcs) { /* Cluster following loop */ +#if !_FS_READONLY + if (fp->flag & FA_WRITE) { /* Check if in write mode or not */ + clst = create_chain(fp->fs, clst); /* Force streached if in write mode */ + if (clst == 0) { /* When disk gets full, clip file size */ + ofs = bcs; break; + } + } else +#endif + clst = get_cluster(fp->fs, clst); /* Follow cluster chain if not in write mode */ + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + if (clst <= 1 || clst >= fp->fs->max_clust) ABORT(fp->fs, FR_INT_ERR); + fp->curr_clust = clst; + fp->fptr += bcs; + ofs -= bcs; + } + fp->fptr += ofs; + fp->csect = (BYTE)(ofs / SS(fp->fs)); /* Sector offset in the cluster */ + if (ofs % SS(fp->fs)) { + nsect = clust2sect(fp->fs, clst); /* Current sector */ + if (!nsect) ABORT(fp->fs, FR_INT_ERR); + nsect += fp->csect; + fp->csect++; + } + } + } + if (nsect && nsect != fp->dsect && fp->fptr % SS(fp->fs)) { +#if !_FS_TINY +#if !_FS_READONLY + if (fp->flag & FA__DIRTY) { /* Write-back dirty buffer if needed */ + if (disk_write(fp->fs->drive, fp->buf, fp->dsect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA__DIRTY; + } +#endif + if (disk_read(fp->fs->drive, fp->buf, nsect, 1) != RES_OK) + ABORT(fp->fs, FR_DISK_ERR); +#endif + fp->dsect = nsect; + } +#if !_FS_READONLY + if (fp->fptr > fp->fsize) { /* Set changed flag if the file size is extended */ + fp->fsize = fp->fptr; + fp->flag |= FA__WRITTEN; + } +#endif + + LEAVE_FF(fp->fs, res); +} + + + + +#if _FS_MINIMIZE <= 1 +/*-----------------------------------------------------------------------*/ +/* Create a Directroy Object */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_opendir ( + DIR *dj, /* Pointer to directory object to create */ + const char *path /* Pointer to the directory path */ +) +{ + FRESULT res; + NAMEBUF(sfn, lfn); + BYTE *dir; + + + res = auto_mount(&path, &dj->fs, 0); + if (res == FR_OK) { + INITBUF((*dj), sfn, lfn); + res = follow_path(dj, path); /* Follow the path to the directory */ + if (res == FR_OK) { /* Follow completed */ + dir = dj->dir; + if (dir) { /* It is not the root dir */ + if (dir[DIR_Attr] & AM_DIR) { /* The object is a directory */ + dj->sclust = ((DWORD)LD_WORD(dir+DIR_FstClusHI) << 16) | LD_WORD(dir+DIR_FstClusLO); + } else { /* The object is not a directory */ + res = FR_NO_PATH; + } + } else { /* It is the root dir */ + dj->sclust = (dj->fs->fs_type == FS_FAT32) ? dj->fs->dirbase : 0; + } + if (res == FR_OK) res = dir_seek(dj, 0); + dj->id = dj->fs->id; + } else { + if (res == FR_NO_FILE) res = FR_NO_PATH; + } + } + + LEAVE_FF(dj->fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read Directory Entry in Sequense */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_readdir ( + DIR *dj, /* Pointer to the open directory object */ + FILINFO *fno /* Pointer to file information to return */ +) +{ + FRESULT res; + NAMEBUF(sfn, lfn); + + + res = validate(dj->fs, dj->id); /* Check validity of the object */ + if (res == FR_OK) { + INITBUF((*dj), sfn, lfn); + if (!fno) { + res = dir_seek(dj, 0); + } else { + res = dir_read(dj); + if (res == FR_NO_FILE) { + dj->sect = 0; + res = FR_OK; + } + if (res == FR_OK) { /* A valid entry is found */ + get_fileinfo(dj, fno); /* Get the object information */ + res = dir_next(dj, FALSE); /* Increment index for next */ + if (res == FR_NO_FILE) { + dj->sect = 0; + res = FR_OK; + } + } + } + } + + LEAVE_FF(dj->fs, res); +} + + + +#if _FS_MINIMIZE == 0 +/*-----------------------------------------------------------------------*/ +/* Get File Status */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_stat ( + const char *path, /* Pointer to the file path */ + FILINFO *fno /* Pointer to file information to return */ +) +{ + FRESULT res; + DIR dj; + NAMEBUF(sfn, lfn); + + + res = auto_mount(&path, &dj.fs, 0); + if (res == FR_OK) { + INITBUF(dj, sfn, lfn); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) { /* Follwo completed */ + if (dj.dir) /* Found an object */ + get_fileinfo(&dj, fno); + else /* It is root dir */ + res = FR_INVALID_NAME; + } + } + + LEAVE_FF(dj.fs, res); +} + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Truncate File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_truncate ( + FIL *fp /* Pointer to the file object */ +) +{ + FRESULT res; + DWORD ncl; + + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->flag & FA__ERROR) /* Check abort flag */ + LEAVE_FF(fp->fs, FR_INT_ERR); + if (!(fp->flag & FA_WRITE)) /* Check access mode */ + LEAVE_FF(fp->fs, FR_DENIED); + + if (fp->fsize > fp->fptr) { + fp->fsize = fp->fptr; /* Set file size to current R/W point */ + fp->flag |= FA__WRITTEN; + if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */ + res = remove_chain(fp->fs, fp->org_clust); + fp->org_clust = 0; + } else { /* When truncate a part of the file, remove remaining clusters */ + ncl = get_cluster(fp->fs, fp->curr_clust); + res = FR_OK; + if (ncl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (ncl == 1) res = FR_INT_ERR; + if (res == FR_OK && ncl < fp->fs->max_clust) { + res = put_cluster(fp->fs, fp->curr_clust, 0x0FFFFFFF); + if (res == FR_OK) res = remove_chain(fp->fs, ncl); + } + } + } + if (res != FR_OK) fp->flag |= FA__ERROR; + + LEAVE_FF(fp->fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Get Number of Free Clusters */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getfree ( + const char *path, /* Pointer to the logical drive number (root dir) */ + DWORD *nclst, /* Pointer to the variable to return number of free clusters */ + FATFS **fatfs /* Pointer to pointer to corresponding file system object to return */ +) +{ + FRESULT res; + DWORD n, clst, sect; + BYTE fat, f, *p; + + + /* Get drive number */ + res = auto_mount(&path, fatfs, 0); + if (res != FR_OK) LEAVE_FF(*fatfs, res); + + /* If number of free cluster is valid, return it without cluster scan. */ + if ((*fatfs)->free_clust <= (*fatfs)->max_clust - 2) { + *nclst = (*fatfs)->free_clust; + LEAVE_FF(*fatfs, FR_OK); + } + + /* Get number of free clusters */ + fat = (*fatfs)->fs_type; + n = 0; + if (fat == FS_FAT12) { + clst = 2; + do { + if ((WORD)get_cluster(*fatfs, clst) == 0) n++; + } while (++clst < (*fatfs)->max_clust); + } else { + clst = (*fatfs)->max_clust; + sect = (*fatfs)->fatbase; + f = 0; p = 0; + do { + if (!f) { + res = move_window(*fatfs, sect++); + if (res != FR_OK) + LEAVE_FF(*fatfs, res); + p = (*fatfs)->win; + } + if (fat == FS_FAT16) { + if (LD_WORD(p) == 0) n++; + p += 2; f += 1; + } else { + if (LD_DWORD(p) == 0) n++; + p += 4; f += 2; + } + } while (--clst); + } + (*fatfs)->free_clust = n; + if (fat == FS_FAT32) (*fatfs)->fsi_flag = 1; + *nclst = n; + + LEAVE_FF(*fatfs, FR_OK); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Delete a File or Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_unlink ( + const char *path /* Pointer to the file or directory path */ +) +{ + FRESULT res; + DIR dj, sdj; + NAMEBUF(sfn, lfn); + BYTE *dir; + DWORD dclst; + + + res = auto_mount(&path, &dj.fs, 1); + if (res != FR_OK) LEAVE_FF(dj.fs, res); + + INITBUF(dj, sfn, lfn); + res = follow_path(&dj, path); /* Follow the file path */ + if (res != FR_OK) LEAVE_FF(dj.fs, res); /* Follow failed */ + + dir = dj.dir; + if (!dir) /* Is it the root directory? */ + LEAVE_FF(dj.fs, FR_INVALID_NAME); + if (dir[DIR_Attr] & AM_RDO) /* Is it a R/O object? */ + LEAVE_FF(dj.fs, FR_DENIED); + dclst = ((DWORD)LD_WORD(dir+DIR_FstClusHI) << 16) | LD_WORD(dir+DIR_FstClusLO); + + if (dir[DIR_Attr] & AM_DIR) { /* It is a sub-directory */ + if (dclst < 2) LEAVE_FF(dj.fs, FR_INT_ERR); + mem_cpy(&sdj, &dj, sizeof(DIR)); /* Check if the sub-dir is empty or not */ + sdj.sclust = dclst; + res = dir_seek(&sdj, 0); + if (res != FR_OK) LEAVE_FF(dj.fs, res); + res = dir_read(&sdj); + if (res == FR_OK) res = FR_DENIED; /* Not empty sub-dir */ + if (res != FR_NO_FILE) LEAVE_FF(dj.fs, res); + } + + res = dir_remove(&dj); /* Remove directory entry */ + if (res == FR_OK) { + if (dclst) + res = remove_chain(dj.fs, dclst); /* Remove the cluster chain */ + if (res == FR_OK) res = sync(dj.fs); + } + + LEAVE_FF(dj.fs, FR_OK); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create a Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mkdir ( + const char *path /* Pointer to the directory path */ +) +{ + FRESULT res; + DIR dj; + NAMEBUF(sfn, lfn); + BYTE *dir, n; + DWORD dsect, dclst, pclst, tim; + + + res = auto_mount(&path, &dj.fs, 1); + if (res != FR_OK) LEAVE_FF(dj.fs, res); + + INITBUF(dj, sfn, lfn); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) res = FR_EXIST; /* Any file or directory is already existing */ + if (res != FR_NO_FILE) /* Any error occured */ + LEAVE_FF(dj.fs, res); + + dclst = create_chain(dj.fs, 0); /* Allocate a new cluster for new directory table */ + res = FR_OK; + if (dclst == 0) res = FR_DENIED; + if (dclst == 1) res = FR_INT_ERR; + if (dclst == 0xFFFFFFFF) res = FR_DISK_ERR; + if (res == FR_OK) + res = move_window(dj.fs, 0); + if (res != FR_OK) LEAVE_FF(dj.fs, res); + dsect = clust2sect(dj.fs, dclst); + + dir = dj.fs->win; /* Initialize the new directory table */ + mem_set(dir, 0, SS(dj.fs)); + mem_set(dir+DIR_Name, ' ', 8+3); /* Create "." entry */ + dir[DIR_Name] = '.'; + dir[DIR_Attr] = AM_DIR; + tim = get_fattime(); + ST_DWORD(dir+DIR_WrtTime, tim); + ST_WORD(dir+DIR_FstClusLO, dclst); + ST_WORD(dir+DIR_FstClusHI, dclst >> 16); + mem_cpy(dir+32, dir, 32); /* Create ".." entry */ + dir[33] = '.'; + pclst = dj.sclust; + if (dj.fs->fs_type == FS_FAT32 && pclst == dj.fs->dirbase) + pclst = 0; + ST_WORD(dir+32+DIR_FstClusLO, pclst); + ST_WORD(dir+32+DIR_FstClusHI, pclst >> 16); + for (n = 0; n < dj.fs->csize; n++) { /* Write dot entries and clear left sectors */ + dj.fs->winsect = dsect++; + dj.fs->wflag = 1; + res = move_window(dj.fs, 0); + if (res) LEAVE_FF(dj.fs, res); + mem_set(dir, 0, SS(dj.fs)); + } + + res = dir_register(&dj); + if (res != FR_OK) { + remove_chain(dj.fs, dclst); + } else { + dir = dj.dir; + dir[DIR_Attr] = AM_DIR; /* Attribute */ + ST_DWORD(dir+DIR_WrtTime, tim); /* Crated time */ + ST_WORD(dir+DIR_FstClusLO, dclst); /* Table start cluster */ + ST_WORD(dir+DIR_FstClusHI, dclst >> 16); + dj.fs->wflag = 1; + res = sync(dj.fs); + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change File Attribute */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_chmod ( + const char *path, /* Pointer to the file path */ + BYTE value, /* Attribute bits */ + BYTE mask /* Attribute mask to change */ +) +{ + FRESULT res; + DIR dj; + NAMEBUF(sfn, lfn); + BYTE *dir; + + + res = auto_mount(&path, &dj.fs, 1); + if (res == FR_OK) { + INITBUF(dj, sfn, lfn); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) { + dir = dj.dir; + if (!dir) { /* Is it a root directory? */ + res = FR_INVALID_NAME; + } else { /* File or sub directory */ + mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */ + dir[DIR_Attr] = (value & mask) | (dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */ + dj.fs->wflag = 1; + res = sync(dj.fs); + } + } + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change Timestamp */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_utime ( + const char *path, /* Pointer to the file/directory name */ + const FILINFO *fno /* Pointer to the timestamp to be set */ +) +{ + FRESULT res; + DIR dj; + NAMEBUF(sfn, lfn); + BYTE *dir; + + + res = auto_mount(&path, &dj.fs, 1); + if (res == FR_OK) { + INITBUF(dj, sfn, lfn); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) { + dir = dj.dir; + if (!dir) { /* Root directory */ + res = FR_INVALID_NAME; + } else { /* File or sub-directory */ + ST_WORD(dir+DIR_WrtTime, fno->ftime); + ST_WORD(dir+DIR_WrtDate, fno->fdate); + dj.fs->wflag = 1; + res = sync(dj.fs); + } + } + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Rename File/Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_rename ( + const char *path_old, /* Pointer to the old name */ + const char *path_new /* Pointer to the new name */ +) +{ + FRESULT res; + DIR dj_old, dj_new; + NAMEBUF(sfn, lfn); + BYTE buf[21], *dir; + DWORD dw; + + + INITBUF(dj_old, sfn, lfn); + res = auto_mount(&path_old, &dj_old.fs, 1); + if (res == FR_OK) { + dj_new.fs = dj_old.fs; + res = follow_path(&dj_old, path_old); /* Check old object */ + } + if (res != FR_OK) LEAVE_FF(dj_old.fs, res); /* The old object is not found */ + + if (!dj_old.dir) LEAVE_FF(dj_old.fs, FR_NO_FILE); /* Is root dir? */ + mem_cpy(buf, dj_old.dir+DIR_Attr, 21); /* Save the object information */ + + mem_cpy(&dj_new, &dj_old, sizeof(DIR)); + res = follow_path(&dj_new, path_new); /* Check new object */ + if (res == FR_OK) res = FR_EXIST; /* The new object name is already existing */ + if (res == FR_NO_FILE) { /* Is it a valid path and no name collision? */ + res = dir_register(&dj_new); /* Register the new object */ + if (res == FR_OK) { + dir = dj_new.dir; /* Copy object information into new entry */ + mem_cpy(dir+13, buf+2, 19); + dir[DIR_Attr] = buf[0]; + dj_old.fs->wflag = 1; + if (dir[DIR_Attr] & AM_DIR) { /* Update .. entry in the directory if needed */ + dw = clust2sect(dj_new.fs, (DWORD)LD_WORD(dir+DIR_FstClusHI) | LD_WORD(dir+DIR_FstClusLO)); + if (!dw) { + res = FR_INT_ERR; + } else { + res = move_window(dj_new.fs, dw); + dir = dj_new.fs->win+32; + if (res == FR_OK && dir[1] == '.') { + dw = (dj_new.fs->fs_type == FS_FAT32 && dj_new.sclust == dj_new.fs->dirbase) ? 0 : dj_new.sclust; + ST_WORD(dir+DIR_FstClusLO, dw); + ST_WORD(dir+DIR_FstClusHI, dw >> 16); + dj_new.fs->wflag = 1; + } + } + } + if (res == FR_OK) { + res = dir_remove(&dj_old); /* Remove old entry */ + if (res == FR_OK) + res = sync(dj_old.fs); + } + } + } + + LEAVE_FF(dj_old.fs, res); +} + +#endif /* !_FS_READONLY */ +#endif /* _FS_MINIMIZE == 0 */ +#endif /* _FS_MINIMIZE <= 1 */ +#endif /* _FS_MINIMIZE <= 2 */ + + + +/*-----------------------------------------------------------------------*/ +/* Forward data to the stream directly (Available on only _FS_TINY cfg) */ +/*-----------------------------------------------------------------------*/ +#if _USE_FORWARD && _FS_TINY + +FRESULT f_forward ( + FIL *fp, /* Pointer to the file object */ + UINT (*func)(const BYTE*,UINT), /* Pointer to the streaming function */ + UINT btr, /* Number of bytes to forward */ + UINT *bf /* Pointer to number of bytes forwarded */ +) +{ + FRESULT res; + DWORD remain, clst, sect; + UINT rcnt; + + + *bf = 0; + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->flag & FA__ERROR) /* Check error flag */ + LEAVE_FF(fp->fs, FR_INT_ERR); + if (!(fp->flag & FA_READ)) /* Check access mode */ + LEAVE_FF(fp->fs, FR_DENIED); + + remain = fp->fsize - fp->fptr; + if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */ + + for ( ; btr && (*func)(NULL, 0); /* Repeat until all data transferred or stream becomes busy */ + fp->fptr += rcnt, *bf += rcnt, btr -= rcnt) { + if ((fp->fptr % SS(fp->fs)) == 0) { /* On the sector boundary? */ + if (fp->csect >= fp->fs->csize) { /* On the cluster boundary? */ + clst = (fp->fptr == 0) ? /* On the top of the file? */ + fp->org_clust : get_cluster(fp->fs, fp->curr_clust); + if (clst <= 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->curr_clust = clst; /* Update current cluster */ + fp->csect = 0; /* Reset sector address in the cluster */ + } + fp->csect++; /* Next sector address in the cluster */ + } + sect = clust2sect(fp->fs, fp->curr_clust); /* Get current data sector */ + if (!sect) ABORT(fp->fs, FR_INT_ERR); + sect += fp->csect - 1; + if (move_window(fp->fs, sect)) /* Move sector window */ + ABORT(fp->fs, FR_DISK_ERR); + fp->dsect = sect; + rcnt = SS(fp->fs) - (WORD)(fp->fptr % SS(fp->fs)); /* Forward data from sector window */ + if (rcnt > btr) rcnt = btr; + rcnt = (*func)(&fp->fs->win[(WORD)fp->fptr % SS(fp->fs)], rcnt); + if (!rcnt) ABORT(fp->fs, FR_INT_ERR); + } + + LEAVE_FF(fp->fs, FR_OK); +} +#endif /* _USE_FORWARD */ + + + +#if _USE_MKFS && !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Create File System on the Drive */ +/*-----------------------------------------------------------------------*/ +#define N_ROOTDIR 512 /* Multiple of 32 and <= 2048 */ +#define N_FATS 1 /* 1 or 2 */ +#define MAX_SECTOR 131072000UL /* Maximum partition size */ +#define MIN_SECTOR 2000UL /* Minimum partition size */ + + +FRESULT f_mkfs ( + BYTE drv, /* Logical drive number */ + BYTE partition, /* Partitioning rule 0:FDISK, 1:SFD */ + WORD allocsize /* Allocation unit size [bytes] */ +) +{ + static const DWORD sstbl[] = { 2048000, 1024000, 512000, 256000, 128000, 64000, 32000, 16000, 8000, 4000, 0 }; + static const WORD cstbl[] = { 32768, 16384, 8192, 4096, 2048, 16384, 8192, 4096, 2048, 1024, 512 }; + BYTE fmt, m, *tbl; + DWORD b_part, b_fat, b_dir, b_data; /* Area offset (LBA) */ + DWORD n_part, n_rsv, n_fat, n_dir; /* Area size */ + DWORD n_clst, n; + WORD as; + FATFS *fs; + DSTATUS stat; + + + /* Check validity of the parameters */ + if (drv >= _DRIVES) return FR_INVALID_DRIVE; + if (partition >= 2) return FR_MKFS_ABORTED; + + /* Check mounted drive and clear work area */ + fs = FatFs[drv]; + if (!fs) return FR_NOT_ENABLED; + fs->fs_type = 0; + drv = LD2PD(drv); + + /* Get disk statics */ + stat = disk_initialize(drv); + if (stat & STA_NOINIT) return FR_NOT_READY; + if (stat & STA_PROTECT) return FR_WRITE_PROTECTED; + if (disk_ioctl(drv, GET_SECTOR_COUNT, &n_part) != RES_OK || n_part < MIN_SECTOR) + return FR_MKFS_ABORTED; + if (n_part > MAX_SECTOR) n_part = MAX_SECTOR; + b_part = (!partition) ? 63 : 0; /* Boot sector */ + n_part -= b_part; +#if _MAX_SS == 512 + if (!allocsize) { /* Auto selection of cluster size */ + for (n = 0; n_part < sstbl[n]; n++) ; + allocsize = cstbl[n]; + } +#endif + for (as = 512; as <= 32768U && as != allocsize; as <<= 1); + if (as != allocsize) return FR_MKFS_ABORTED; +#if _MAX_SS != 512 /* Check disk sector size */ + if (disk_ioctl(drv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK + || SS(fs) > _MAX_SS + || SS(fs) > allocsize) + return FR_MKFS_ABORTED; +#endif + allocsize /= SS(fs); /* Number of sectors per cluster */ + + /* Pre-compute number of clusters and FAT type */ + n_clst = n_part / allocsize; + fmt = FS_FAT12; + if (n_clst >= 0xFF5) fmt = FS_FAT16; + if (n_clst >= 0xFFF5) fmt = FS_FAT32; + + /* Determine offset and size of FAT structure */ + switch (fmt) { + case FS_FAT12: + n_fat = ((n_clst * 3 + 1) / 2 + 3 + SS(fs) - 1) / SS(fs); + n_rsv = 1 + partition; + n_dir = N_ROOTDIR * 32 / SS(fs); + break; + case FS_FAT16: + n_fat = ((n_clst * 2) + 4 + SS(fs) - 1) / SS(fs); + n_rsv = 1 + partition; + n_dir = N_ROOTDIR * 32 / SS(fs); + break; + default: + n_fat = ((n_clst * 4) + 8 + SS(fs) - 1) / SS(fs); + n_rsv = 33 - partition; + n_dir = 0; + } + b_fat = b_part + n_rsv; /* FATs start sector */ + b_dir = b_fat + n_fat * N_FATS; /* Directory start sector */ + b_data = b_dir + n_dir; /* Data start sector */ + + /* Align data start sector to erase block boundary (for flash memory media) */ + if (disk_ioctl(drv, GET_BLOCK_SIZE, &n) != RES_OK) return FR_MKFS_ABORTED; + n = (b_data + n - 1) & ~(n - 1); + n_fat += (n - b_data) / N_FATS; + /* b_dir and b_data are no longer used below */ + + /* Determine number of cluster and final check of validity of the FAT type */ + n_clst = (n_part - n_rsv - n_fat * N_FATS - n_dir) / allocsize; + if ( (fmt == FS_FAT16 && n_clst < 0xFF5) + || (fmt == FS_FAT32 && n_clst < 0xFFF5)) + return FR_MKFS_ABORTED; + + /* Create partition table if needed */ + if (!partition) { + DWORD n_disk = b_part + n_part; + + tbl = fs->win+MBR_Table; + ST_DWORD(tbl, 0x00010180); /* Partition start in CHS */ + if (n_disk < 63UL * 255 * 1024) { /* Partition end in CHS */ + n_disk = n_disk / 63 / 255; + tbl[7] = (BYTE)n_disk; + tbl[6] = (BYTE)((n_disk >> 2) | 63); + } else { + ST_WORD(&tbl[6], 0xFFFF); + } + tbl[5] = 254; + if (fmt != FS_FAT32) /* System ID */ + tbl[4] = (n_part < 0x10000) ? 0x04 : 0x06; + else + tbl[4] = 0x0c; + ST_DWORD(tbl+8, 63); /* Partition start in LBA */ + ST_DWORD(tbl+12, n_part); /* Partition size in LBA */ + ST_WORD(tbl+64, 0xAA55); /* Signature */ + if (disk_write(drv, fs->win, 0, 1) != RES_OK) + return FR_DISK_ERR; + } + + /* Create boot record */ + tbl = fs->win; /* Clear buffer */ + mem_set(tbl, 0, SS(fs)); + ST_DWORD(tbl+BS_jmpBoot, 0x90FEEB); /* Boot code (jmp $, nop) */ + ST_WORD(tbl+BPB_BytsPerSec, SS(fs)); /* Sector size */ + tbl[BPB_SecPerClus] = (BYTE)allocsize; /* Sectors per cluster */ + ST_WORD(tbl+BPB_RsvdSecCnt, n_rsv); /* Reserved sectors */ + tbl[BPB_NumFATs] = N_FATS; /* Number of FATs */ + ST_WORD(tbl+BPB_RootEntCnt, SS(fs) / 32 * n_dir); /* Number of rootdir entries */ + if (n_part < 0x10000) { /* Number of total sectors */ + ST_WORD(tbl+BPB_TotSec16, n_part); + } else { + ST_DWORD(tbl+BPB_TotSec32, n_part); + } + tbl[BPB_Media] = 0xF8; /* Media descripter */ + ST_WORD(tbl+BPB_SecPerTrk, 63); /* Number of sectors per track */ + ST_WORD(tbl+BPB_NumHeads, 255); /* Number of heads */ + ST_DWORD(tbl+BPB_HiddSec, b_part); /* Hidden sectors */ + n = get_fattime(); /* Use current time as a VSN */ + if (fmt != FS_FAT32) { + ST_DWORD(tbl+BS_VolID, n); /* Volume serial number */ + ST_WORD(tbl+BPB_FATSz16, n_fat); /* Number of secters per FAT */ + tbl[BS_DrvNum] = 0x80; /* Drive number */ + tbl[BS_BootSig] = 0x29; /* Extended boot signature */ + mem_cpy(tbl+BS_VolLab, "NO NAME FAT ", 19); /* Volume lavel, FAT signature */ + } else { + ST_DWORD(tbl+BS_VolID32, n); /* Volume serial number */ + ST_DWORD(tbl+BPB_FATSz32, n_fat); /* Number of secters per FAT */ + ST_DWORD(tbl+BPB_RootClus, 2); /* Root directory cluster (2) */ + ST_WORD(tbl+BPB_FSInfo, 1); /* FSInfo record offset (bs+1) */ + ST_WORD(tbl+BPB_BkBootSec, 6); /* Backup boot record offset (bs+6) */ + tbl[BS_DrvNum32] = 0x80; /* Drive number */ + tbl[BS_BootSig32] = 0x29; /* Extended boot signature */ + mem_cpy(tbl+BS_VolLab32, "NO NAME FAT32 ", 19); /* Volume lavel, FAT signature */ + } + ST_WORD(tbl+BS_55AA, 0xAA55); /* Signature */ + if (disk_write(drv, tbl, b_part+0, 1) != RES_OK) + return FR_DISK_ERR; + if (fmt == FS_FAT32) + disk_write(drv, tbl, b_part+6, 1); + + /* Initialize FAT area */ + for (m = 0; m < N_FATS; m++) { + mem_set(tbl, 0, SS(fs)); /* 1st sector of the FAT */ + if (fmt != FS_FAT32) { + n = (fmt == FS_FAT12) ? 0x00FFFFF8 : 0xFFFFFFF8; + ST_DWORD(tbl, n); /* Reserve cluster #0-1 (FAT12/16) */ + } else { + ST_DWORD(tbl+0, 0xFFFFFFF8); /* Reserve cluster #0-1 (FAT32) */ + ST_DWORD(tbl+4, 0xFFFFFFFF); + ST_DWORD(tbl+8, 0x0FFFFFFF); /* Reserve cluster #2 for root dir */ + } + if (disk_write(drv, tbl, b_fat++, 1) != RES_OK) + return FR_DISK_ERR; + mem_set(tbl, 0, SS(fs)); /* Following FAT entries are filled by zero */ + for (n = 1; n < n_fat; n++) { + if (disk_write(drv, tbl, b_fat++, 1) != RES_OK) + return FR_DISK_ERR; + } + } + + /* Initialize Root directory */ + m = (BYTE)((fmt == FS_FAT32) ? allocsize : n_dir); + do { + if (disk_write(drv, tbl, b_fat++, 1) != RES_OK) + return FR_DISK_ERR; + } while (--m); + + /* Create FSInfo record if needed */ + if (fmt == FS_FAT32) { + ST_WORD(tbl+BS_55AA, 0xAA55); + ST_DWORD(tbl+FSI_LeadSig, 0x41615252); + ST_DWORD(tbl+FSI_StrucSig, 0x61417272); + ST_DWORD(tbl+FSI_Free_Count, n_clst - 1); + ST_DWORD(tbl+FSI_Nxt_Free, 0xFFFFFFFF); + disk_write(drv, tbl, b_part+1, 1); + disk_write(drv, tbl, b_part+7, 1); + } + + return (disk_ioctl(drv, CTRL_SYNC, (void*)NULL) == RES_OK) ? FR_OK : FR_DISK_ERR; +} + +#endif /* _USE_MKFS && !_FS_READONLY */ + + + + +#if _USE_STRFUNC +/*-----------------------------------------------------------------------*/ +/* Get a string from the file */ +/*-----------------------------------------------------------------------*/ +char* f_gets ( + char* buff, /* Pointer to the string buffer to read */ + int len, /* Size of string buffer */ + FIL* fil /* Pointer to the file object */ +) +{ + int i = 0; + char *p = buff; + UINT rc; + + + while (i < len - 1) { /* Read bytes until buffer gets filled */ + f_read(fil, p, 1, &rc); + if (rc != 1) break; /* Break when no data to read */ +#if _USE_STRFUNC >= 2 + if (*p == '\r') continue; /* Strip '\r' */ +#endif + i++; + if (*p++ == '\n') break; /* Break when reached end of line */ + } + *p = 0; + return i ? buff : NULL; /* When no data read (eof or error), return with error. */ +} + + + +#if !_FS_READONLY +#include +/*-----------------------------------------------------------------------*/ +/* Put a character to the file */ +/*-----------------------------------------------------------------------*/ +int f_putc ( + int chr, /* A character to be output */ + FIL* fil /* Ponter to the file object */ +) +{ + UINT bw; + char c; + + +#if _USE_STRFUNC >= 2 + if (chr == '\n') f_putc ('\r', fil); /* LF -> CRLF conversion */ +#endif + if (!fil) { /* Special value may be used to switch the destination to any other device */ + /* put_console(chr); */ + return chr; + } + c = (char)chr; + f_write(fil, &c, 1, &bw); /* Write a byte to the file */ + return bw ? chr : EOF; /* Return the result */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Put a string to the file */ +/*-----------------------------------------------------------------------*/ +int f_puts ( + const char* str, /* Pointer to the string to be output */ + FIL* fil /* Pointer to the file object */ +) +{ + int n; + + + for (n = 0; *str; str++, n++) { + if (f_putc(*str, fil) == EOF) return EOF; + } + return n; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Put a formatted string to the file */ +/*-----------------------------------------------------------------------*/ +int f_printf ( + FIL* fil, /* Pointer to the file object */ + const char* str, /* Pointer to the format string */ + ... /* Optional arguments... */ +) +{ + va_list arp; + UCHAR c, f, r; + ULONG val; + char s[16]; + int i, w, res, cc; + + + va_start(arp, str); + + for (cc = res = 0; cc != EOF; res += cc) { + c = *str++; + if (c == 0) break; /* End of string */ + if (c != '%') { /* Non escape cahracter */ + cc = f_putc(c, fil); + if (cc != EOF) cc = 1; + continue; + } + w = f = 0; + c = *str++; + if (c == '0') { /* Flag: '0' padding */ + f = 1; c = *str++; + } + while (c >= '0' && c <= '9') { /* Precision */ + w = w * 10 + (c - '0'); + c = *str++; + } + if (c == 'l') { /* Prefix: Size is long int */ + f |= 2; c = *str++; + } + if (c == 's') { /* Type is string */ + cc = f_puts(va_arg(arp, char*), fil); + continue; + } + if (c == 'c') { /* Type is character */ + cc = f_putc(va_arg(arp, int), fil); + if (cc != EOF) cc = 1; + continue; + } + r = 0; + if (c == 'd') r = 10; /* Type is signed decimal */ + if (c == 'u') r = 10; /* Type is unsigned decimal */ + if (c == 'X') r = 16; /* Type is unsigned hexdecimal */ + if (r == 0) break; /* Unknown type */ + if (f & 2) { /* Get the value */ + val = (ULONG)va_arg(arp, long); + } else { + val = (c == 'd') ? (ULONG)(long)va_arg(arp, int) : (ULONG)va_arg(arp, unsigned int); + } + /* Put numeral string */ + if (c == 'd') { + if (val & 0x80000000) { + val = 0 - val; + f |= 4; + } + } + i = sizeof(s) - 1; s[i] = 0; + do { + c = (UCHAR)(val % r + '0'); + if (c > '9') c += 7; + s[--i] = c; + val /= r; + } while (i && val); + if (i && (f & 4)) s[--i] = '-'; + w = sizeof(s) - 1 - w; + while (i && i > w) s[--i] = (f & 1) ? '0' : ' '; + cc = f_puts(&s[i], fil); + } + + va_end(arp); + return (cc == EOF) ? cc : res; +} + +#endif /* !_FS_READONLY */ +#endif /* _USE_STRFUNC */ diff --git a/src/ff.h b/src/ff.h new file mode 100644 index 0000000..567c853 --- /dev/null +++ b/src/ff.h @@ -0,0 +1,547 @@ +/*---------------------------------------------------------------------------/ +/ FatFs - FAT file system module include file R0.07a (C)ChaN, 2009 +/----------------------------------------------------------------------------/ +/ FatFs module is an open source software to implement FAT file system to +/ small embedded systems. This is a free software and is opened for education, +/ research and commercial developments under license policy of following trems. +/ +/ Copyright (C) 2009, ChaN, all right reserved. +/ +/ * The FatFs module is a free software and there is NO WARRANTY. +/ * No restriction on use. You can use, modify and redistribute it for +/ personal, non-profit or commercial use UNDER YOUR RESPONSIBILITY. +/ * Redistributions of source code must retain the above copyright notice. +/----------------------------------------------------------------------------*/ + +#include "integer.h" + +/*---------------------------------------------------------------------------/ +/ FatFs Configuration Options +/ +/ CAUTION! Do not forget to make clean the project after any changes to +/ the configuration options. +/ +/----------------------------------------------------------------------------*/ +#ifndef _FATFS +#define _FATFS + +#define _WORD_ACCESS 0 +/* The _WORD_ACCESS option defines which access method is used to the word +/ data in the FAT structure. +/ +/ 0: Byte-by-byte access. Always compatible with all platforms. +/ 1: Word access. Do not choose this unless following condition is met. +/ +/ When the byte order on the memory is big-endian or address miss-aligned +/ word access results incorrect behavior, the _WORD_ACCESS must be set to 0. +/ If it is not the case, the value can also be set to 1 to improve the +/ performance and code efficiency. */ + + +#define _FS_READONLY 0 +/* Setting _FS_READONLY to 1 defines read only configuration. This removes +/ writing functions, f_write, f_sync, f_unlink, f_mkdir, f_chmod, f_rename, +/ f_truncate and useless f_getfree. */ + + +#define _FS_MINIMIZE 0 +/* The _FS_MINIMIZE option defines minimization level to remove some functions. +/ +/ 0: Full function. +/ 1: f_stat, f_getfree, f_unlink, f_mkdir, f_chmod, f_truncate and f_rename +/ are removed. +/ 2: f_opendir and f_readdir are removed in addition to level 1. +/ 3: f_lseek is removed in addition to level 2. */ + + +#define _FS_TINY 0 +/* When _FS_TINY is set to 1, FatFs uses the sector buffer in the file system +/ object instead of the sector buffer in the individual file object for file +/ data transfer. This reduces memory consumption 512 bytes each file object. */ + + +#define _USE_STRFUNC 0 +/* To enable string functions, set _USE_STRFUNC to 1 or 2. */ + + +#define _USE_MKFS 0 +/* To enable f_mkfs function, set _USE_MKFS to 1 and set _FS_READONLY to 0 */ + + +#define _USE_FORWARD 0 +/* To enable f_forward function, set _USE_FORWARD to 1 and set _FS_TINY to 1. */ + + +#define _DRIVES 1 +/* Number of volumes (logical drives) to be used. */ + + +#define _MAX_SS 512 +/* Maximum sector size to be handled. (512/1024/2048/4096) */ +/* 512 for memroy card and hard disk, 1024 for floppy disk, 2048 for MO disk */ + + +#define _MULTI_PARTITION 0 +/* When _MULTI_PARTITION is set to 0, each volume is bound to the same physical +/ drive number and can mount only first primaly partition. When it is set to 1, +/ each volume is tied to the partitions listed in Drives[]. */ + + +#define _CODE_PAGE 850 +/* The _CODE_PAGE specifies the OEM code page to be used on the target system. +/ When it is non LFN configuration, there is no difference between SBCS code +/ pages. When LFN is enabled, the code page must always be set correctly. +/ 437 - U.S. +/ 720 - Arabic +/ 737 - Greek +/ 775 - Baltic +/ 850 - Multilingual Latin 1 +/ 852 - Latin 2 +/ 855 - Cyrillic +/ 857 - Turkish +/ 858 - Multilingual Latin 1 + Euro +/ 862 - Hebrew +/ 866 - Russian +/ 874 - Thai +/ 932 - Japanese Shift-JIS (DBCS) +/ 936 - Simplified Chinese GBK (DBCS) +/ 949 - Korean (DBCS) +/ 950 - Traditional Chinese Big5 (DBCS) +/ 1258 - Vietnam +*/ + + +#define _USE_LFN 0 +#define _MAX_LFN 255 /* Maximum LFN length to handle (max:255) */ +/* The _USE_LFN option switches the LFN support. +/ +/ 0: Disable LFN. +/ 1: Enable LFN with static working buffer on the bss. NOT REENTRANT. +/ 2: Enable LFN with dynamic working buffer on the caller's STACK. +/ +/ The working buffer occupies (_MAX_LFN + 1) * 2 bytes. When enable LFN, +/ a Unicode - OEM code conversion function ff_convert() must be added to +/ the project. */ + + +#define _FS_REENTRANT 0 +#define _TIMEOUT 1000 /* Timeout period in unit of time ticks */ +#define _SYNC_t HANDLE /* Type of sync object used on the OS. */ + /* e.g. HANDLE, OS_EVENT*, ID and etc.. */ +/* To make the FatFs module re-entrant, set _FS_REENTRANT to 1 and add user +/ provided synchronization handlers, ff_req_grant, ff_rel_grant, +/ ff_del_syncobj and ff_cre_syncobj function to the project. */ + + + +/* End of configuration options. Do not change followings without care. */ +/*--------------------------------------------------------------------------*/ + + + +/* Definitions corresponds to multiple sector size */ + +#if _MAX_SS == 512 +#define SS(fs) 512 +#else +#if _MAX_SS == 1024 || _MAX_SS == 2048 || _MAX_SS == 4096 +#define SS(fs) ((fs)->s_size) +#else +#error Sector size must be 512, 1024, 2048 or 4096. +#endif +#endif + + + +/* File system object structure */ + +typedef struct _FATFS { + BYTE fs_type; /* FAT sub type */ + BYTE drive; /* Physical drive number */ + BYTE csize; /* Number of sectors per cluster */ + BYTE n_fats; /* Number of FAT copies */ + BYTE wflag; /* win[] dirty flag (1:must be written back) */ + BYTE pad1; + WORD id; /* File system mount ID */ + WORD n_rootdir; /* Number of root directory entries (0 on FAT32) */ +#if _FS_REENTRANT + _SYNC_t sobj; /* Identifier of sync object */ +#endif +#if _MAX_SS != 512U + WORD s_size; /* Sector size */ +#endif +#if !_FS_READONLY + BYTE fsi_flag; /* fsinfo dirty flag (1:must be written back) */ + BYTE pad2; + DWORD last_clust; /* Last allocated cluster */ + DWORD free_clust; /* Number of free clusters */ + DWORD fsi_sector; /* fsinfo sector */ +#endif + DWORD sects_fat; /* Sectors per fat */ + DWORD max_clust; /* Maximum cluster# + 1. Number of clusters is max_clust - 2 */ + DWORD fatbase; /* FAT start sector */ + DWORD dirbase; /* Root directory start sector (Cluster# on FAT32) */ + DWORD database; /* Data start sector */ + DWORD winsect; /* Current sector appearing in the win[] */ + BYTE win[_MAX_SS];/* Disk access window for Directory/FAT */ +} FATFS; + + + +/* Directory object structure */ + +typedef struct _DIR { + WORD id; /* Owner file system mount ID */ + WORD index; /* Current index number */ + FATFS* fs; /* Pointer to the owner file system object */ + DWORD sclust; /* Table start cluster (0:Static table) */ + DWORD clust; /* Current cluster */ + DWORD sect; /* Current sector */ + BYTE* dir; /* Pointer to the current SFN entry in the win[] */ + BYTE* fn; /* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */ +#if _USE_LFN + WCHAR* lfn; /* Pointer to the LFN working buffer */ + WORD lfn_idx; /* Last matched LFN index (0xFFFF:No LFN) */ +#endif +} DIR; + + + +/* File object structure */ + +typedef struct _FIL { + FATFS* fs; /* Pointer to the owner file system object */ + WORD id; /* Owner file system mount ID */ + BYTE flag; /* File status flags */ + BYTE csect; /* Sector address in the cluster */ + DWORD fptr; /* File R/W pointer */ + DWORD fsize; /* File size */ + DWORD org_clust; /* File start cluster */ + DWORD curr_clust; /* Current cluster */ + DWORD dsect; /* Current data sector */ +#if !_FS_READONLY + DWORD dir_sect; /* Sector containing the directory entry */ + BYTE* dir_ptr; /* Ponter to the directory entry in the window */ +#endif +#if !_FS_TINY + BYTE buf[_MAX_SS];/* File R/W buffer */ +#endif +} FIL; + + + +/* File status structure */ + +typedef struct _FILINFO { + DWORD fsize; /* File size */ + WORD fdate; /* Last modified date */ + WORD ftime; /* Last modified time */ + BYTE fattrib; /* Attribute */ + char fname[13]; /* Short file name (8.3 format) */ +#if _USE_LFN + char *lfname; /* Pointer to the LFN buffer */ + int lfsize; /* Size of LFN buffer [bytes] */ +#endif +} FILINFO; + + + +/* DBCS code ranges */ + +#if _CODE_PAGE == 932 /* CP932 (Japanese Shift-JIS) */ +#define _DF1S 0x81 /* DBC 1st byte range 1 start */ +#define _DF1E 0x9F /* DBC 1st byte range 1 end */ +#define _DF2S 0xE0 /* DBC 1st byte range 2 start */ +#define _DF2E 0xFC /* DBC 1st byte range 2 end */ +#define _DS1S 0x40 /* DBC 2nd byte range 1 start */ +#define _DS1E 0x7E /* DBC 2nd byte range 1 end */ +#define _DS2S 0x80 /* DBC 2nd byte range 2 start */ +#define _DS2E 0xFC /* DBC 2nd byte range 2 end */ + +#elif _CODE_PAGE == 936 /* CP936 (Simplified Chinese GBK) */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x40 +#define _DS1E 0x7E +#define _DS2S 0x80 +#define _DS2E 0xFE + +#elif _CODE_PAGE == 949 /* CP949 (Korean) */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x41 +#define _DS1E 0x5A +#define _DS2S 0x61 +#define _DS2E 0x7A +#define _DS3S 0x81 +#define _DS3E 0xFE + +#elif _CODE_PAGE == 950 /* CP950 (Traditional Chinese Big5) */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x40 +#define _DS1E 0x7E +#define _DS2S 0xA1 +#define _DS2E 0xFE + +#else /* SBCS code pages */ +#define _DF1S 0 + +#endif + + + +/* Character code support macros */ + +#define IsUpper(c) (((c)>='A')&&((c)<='Z')) +#define IsLower(c) (((c)>='a')&&((c)<='z')) +#define IsDigit(c) (((c)>='0')&&((c)<='9')) + +#if _DF1S /* DBCS configuration */ + +#if _DF2S /* Two 1st byte areas */ +#define IsDBCS1(c) (((BYTE)(c) >= _DF1S && (BYTE)(c) <= _DF1E) || ((BYTE)(c) >= _DF2S && (BYTE)(c) <= _DF2E)) +#else /* One 1st byte area */ +#define IsDBCS1(c) ((BYTE)(c) >= _DF1S && (BYTE)(c) <= _DF1E) +#endif + +#if _DS3S /* Three 2nd byte areas */ +#define IsDBCS2(c) (((BYTE)(c) >= _DS1S && (BYTE)(c) <= _DS1E) || ((BYTE)(c) >= _DS2S && (BYTE)(c) <= _DS2E) || ((BYTE)(c) >= _DS3S && (BYTE)(c) <= _DS3E)) +#else /* Two 2nd byte areas */ +#define IsDBCS2(c) (((BYTE)(c) >= _DS1S && (BYTE)(c) <= _DS1E) || ((BYTE)(c) >= _DS2S && (BYTE)(c) <= _DS2E)) +#endif + +#else /* SBCS configuration */ + +#define IsDBCS1(c) 0 +#define IsDBCS2(c) 0 + +#endif /* _DF1S */ + + + +/* Definitions corresponds to multi partition */ + +#if _MULTI_PARTITION /* Multiple partition configuration */ + +typedef struct _PARTITION { + BYTE pd; /* Physical drive# */ + BYTE pt; /* Partition # (0-3) */ +} PARTITION; + +extern +const PARTITION Drives[]; /* Logical drive# to physical location conversion table */ +#define LD2PD(drv) (Drives[drv].pd) /* Get physical drive# */ +#define LD2PT(drv) (Drives[drv].pt) /* Get partition# */ + +#else /* Single partition configuration */ + +#define LD2PD(drv) (drv) /* Physical drive# is equal to the logical drive# */ +#define LD2PT(drv) 0 /* Always mounts the 1st partition */ + +#endif + + + +/* File function return code (FRESULT) */ + +typedef enum { + FR_OK = 0, /* 0 */ + FR_DISK_ERR, /* 1 */ + FR_INT_ERR, /* 2 */ + FR_NOT_READY, /* 3 */ + FR_NO_FILE, /* 4 */ + FR_NO_PATH, /* 5 */ + FR_INVALID_NAME, /* 6 */ + FR_DENIED, /* 7 */ + FR_EXIST, /* 8 */ + FR_INVALID_OBJECT, /* 9 */ + FR_WRITE_PROTECTED, /* 10 */ + FR_INVALID_DRIVE, /* 11 */ + FR_NOT_ENABLED, /* 12 */ + FR_NO_FILESYSTEM, /* 13 */ + FR_MKFS_ABORTED, /* 14 */ + FR_TIMEOUT /* 15 */ +} FRESULT; + + + +/*--------------------------------------------------------------*/ +/* FatFs module application interface */ + +FRESULT f_mount (BYTE, FATFS*); /* Mount/Unmount a logical drive */ +FRESULT f_open (FIL*, const char*, BYTE); /* Open or create a file */ +FRESULT f_read (FIL*, void*, UINT, UINT*); /* Read data from a file */ +FRESULT f_write (FIL*, const void*, UINT, UINT*); /* Write data to a file */ +FRESULT f_lseek (FIL*, DWORD); /* Move file pointer of a file object */ +FRESULT f_close (FIL*); /* Close an open file object */ +FRESULT f_opendir (DIR*, const char*); /* Open an existing directory */ +FRESULT f_readdir (DIR*, FILINFO*); /* Read a directory item */ +FRESULT f_stat (const char*, FILINFO*); /* Get file status */ +FRESULT f_getfree (const char*, DWORD*, FATFS**); /* Get number of free clusters on the drive */ +FRESULT f_truncate (FIL*); /* Truncate file */ +FRESULT f_sync (FIL*); /* Flush cached data of a writing file */ +FRESULT f_unlink (const char*); /* Delete an existing file or directory */ +FRESULT f_mkdir (const char*); /* Create a new directory */ +FRESULT f_chmod (const char*, BYTE, BYTE); /* Change attriburte of the file/dir */ +FRESULT f_utime (const char*, const FILINFO*); /* Change timestamp of the file/dir */ +FRESULT f_rename (const char*, const char*); /* Rename/Move a file or directory */ +FRESULT f_forward (FIL*, UINT(*)(const BYTE*,UINT), UINT, UINT*); /* Forward data to the stream */ +FRESULT f_mkfs (BYTE, BYTE, WORD); /* Create a file system on the drive */ + +#if _USE_STRFUNC +int f_putc (int, FIL*); /* Put a character to the file */ +int f_puts (const char*, FIL*); /* Put a string to the file */ +int f_printf (FIL*, const char*, ...); /* Put a formatted string to the file */ +char* f_gets (char*, int, FIL*); /* Get a string from the file */ +#define f_eof(fp) (((fp)->fptr == (fp)->fsize) ? 1 : 0) +#define f_error(fp) (((fp)->flag & FA__ERROR) ? 1 : 0) +#ifndef EOF +#define EOF -1 +#endif +#endif + + + +/*--------------------------------------------------------------*/ +/* User defined functions */ + +/* Real time clock */ +#if !_FS_READONLY +DWORD get_fattime (void); /* 31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20-16: Day(1-31) */ + /* 15-11: Hour(0-23), 10-5: Minute(0-59), 4-0: Second(0-29 *2) */ +#endif + +/* Unicode - OEM code conversion */ +#if _USE_LFN +WCHAR ff_convert (WCHAR, UINT); +#endif + +/* Sync functions */ +#if _FS_REENTRANT +BOOL ff_cre_syncobj(BYTE, _SYNC_t*); +BOOL ff_del_syncobj(_SYNC_t); +BOOL ff_req_grant(_SYNC_t); +void ff_rel_grant(_SYNC_t); +#endif + + + +/*--------------------------------------------------------------*/ +/* Flags and offset address */ + + +/* File access control and file status flags (FIL.flag) */ + +#define FA_READ 0x01 +#define FA_OPEN_EXISTING 0x00 +#if _FS_READONLY == 0 +#define FA_WRITE 0x02 +#define FA_CREATE_NEW 0x04 +#define FA_CREATE_ALWAYS 0x08 +#define FA_OPEN_ALWAYS 0x10 +#define FA__WRITTEN 0x20 +#define FA__DIRTY 0x40 +#endif +#define FA__ERROR 0x80 + + +/* FAT sub type (FATFS.fs_type) */ + +#define FS_FAT12 1 +#define FS_FAT16 2 +#define FS_FAT32 3 + + +/* File attribute bits for directory entry */ + +#define AM_RDO 0x01 /* Read only */ +#define AM_HID 0x02 /* Hidden */ +#define AM_SYS 0x04 /* System */ +#define AM_VOL 0x08 /* Volume label */ +#define AM_LFN 0x0F /* LFN entry */ +#define AM_DIR 0x10 /* Directory */ +#define AM_ARC 0x20 /* Archive */ +#define AM_MASK 0x3F /* Mask of defined bits */ + + +/* FatFs refers the members in the FAT structures with byte offset instead +/ of structure member because there are incompatibility of the packing option +/ between various compilers. */ + +#define BS_jmpBoot 0 +#define BS_OEMName 3 +#define BPB_BytsPerSec 11 +#define BPB_SecPerClus 13 +#define BPB_RsvdSecCnt 14 +#define BPB_NumFATs 16 +#define BPB_RootEntCnt 17 +#define BPB_TotSec16 19 +#define BPB_Media 21 +#define BPB_FATSz16 22 +#define BPB_SecPerTrk 24 +#define BPB_NumHeads 26 +#define BPB_HiddSec 28 +#define BPB_TotSec32 32 +#define BS_55AA 510 + +#define BS_DrvNum 36 +#define BS_BootSig 38 +#define BS_VolID 39 +#define BS_VolLab 43 +#define BS_FilSysType 54 + +#define BPB_FATSz32 36 +#define BPB_ExtFlags 40 +#define BPB_FSVer 42 +#define BPB_RootClus 44 +#define BPB_FSInfo 48 +#define BPB_BkBootSec 50 +#define BS_DrvNum32 64 +#define BS_BootSig32 66 +#define BS_VolID32 67 +#define BS_VolLab32 71 +#define BS_FilSysType32 82 + +#define FSI_LeadSig 0 +#define FSI_StrucSig 484 +#define FSI_Free_Count 488 +#define FSI_Nxt_Free 492 + +#define MBR_Table 446 + +#define DIR_Name 0 +#define DIR_Attr 11 +#define DIR_NTres 12 +#define DIR_CrtTime 14 +#define DIR_CrtDate 16 +#define DIR_FstClusHI 20 +#define DIR_WrtTime 22 +#define DIR_WrtDate 24 +#define DIR_FstClusLO 26 +#define DIR_FileSize 28 +#define LDIR_Ord 0 +#define LDIR_Attr 11 +#define LDIR_Type 12 +#define LDIR_Chksum 13 +#define LDIR_FstClusLO 26 + + + +/*--------------------------------*/ +/* Multi-byte word access macros */ + +#if _WORD_ACCESS == 1 /* Enable word access to the FAT structure */ +#define LD_WORD(ptr) (WORD)(*(WORD*)(BYTE*)(ptr)) +#define LD_DWORD(ptr) (DWORD)(*(DWORD*)(BYTE*)(ptr)) +#define ST_WORD(ptr,val) *(WORD*)(BYTE*)(ptr)=(WORD)(val) +#define ST_DWORD(ptr,val) *(DWORD*)(BYTE*)(ptr)=(DWORD)(val) +#else /* Use byte-by-byte access to the FAT structure */ +#define LD_WORD(ptr) (WORD)(((WORD)*(BYTE*)((ptr)+1)<<8)|(WORD)*(BYTE*)(ptr)) +#define LD_DWORD(ptr) (DWORD)(((DWORD)*(BYTE*)((ptr)+3)<<24)|((DWORD)*(BYTE*)((ptr)+2)<<16)|((WORD)*(BYTE*)((ptr)+1)<<8)|*(BYTE*)(ptr)) +#define ST_WORD(ptr,val) *(BYTE*)(ptr)=(BYTE)(val); *(BYTE*)((ptr)+1)=(BYTE)((WORD)(val)>>8) +#define ST_DWORD(ptr,val) *(BYTE*)(ptr)=(BYTE)(val); *(BYTE*)((ptr)+1)=(BYTE)((WORD)(val)>>8); *(BYTE*)((ptr)+2)=(BYTE)((DWORD)(val)>>16); *(BYTE*)((ptr)+3)=(BYTE)((DWORD)(val)>>24) +#endif + + +#endif /* _FATFS */ diff --git a/src/fileops.c b/src/fileops.c new file mode 100644 index 0000000..6f8f1cb --- /dev/null +++ b/src/fileops.c @@ -0,0 +1,31 @@ +// insert cool lenghty disclaimer here +// fileops.c: fatfs wrapping for convenience + +#include "config.h" +#include "uart.h" +#include "ff.h" +#include "fileops.h" + +void file_init() { + f_mount(0, &fatfs); +} + +void file_open(char* filename, BYTE flags) { + file_res = f_open(&file_handle, filename, flags); +} + +void file_close() { + file_res = f_close(&file_handle); +} + +UINT file_read() { + UINT bytes_read; + file_res = f_read(&file_handle, file_buf, sizeof(file_buf), &bytes_read); + return bytes_read; +} + +UINT file_write() { + UINT bytes_written; + file_res = f_write(&file_handle, file_buf, sizeof(file_buf), &bytes_written); + return bytes_written; +} diff --git a/src/fileops.h b/src/fileops.h new file mode 100644 index 0000000..604fb44 --- /dev/null +++ b/src/fileops.h @@ -0,0 +1,18 @@ +// insert cool lenghty disclaimer here +// fileops.h + +#ifndef FILEOPS_H +#define FILEOPS_H +#include "ff.h" + +BYTE file_buf[512]; +FATFS fatfs; +FIL file_handle; +FRESULT file_res; + +void file_init(void); +void file_open(char* filename, BYTE flags); +void file_close(void); +UINT file_read(void); +UINT file_write(void); +#endif diff --git a/src/fpga.c b/src/fpga.c new file mode 100644 index 0000000..32c3cd1 --- /dev/null +++ b/src/fpga.c @@ -0,0 +1,186 @@ +// insert cool lenghty disclaimer here + +// fpga.c: FPGA (re)programming + +/* + + FPGA pin mapping + ================ + + FPGA AVR dir + ------------------------ + PROG_B PD3 OUT + CCLK PD4 OUT + CS_B PD7 OUT + INIT_B PB2 IN + RDWR_B PB3 OUT + D7 PC0 OUT + D6 PC1 OUT + D5 PC2 OUT + D4 PC3 OUT + D3 PC4 OUT + D2 PC5 OUT + D1 PC6 OUT + D0 PC7 OUT + + */ + +#include +#include "fpga.h" +#include "config.h" +#include "uart.h" +#include "sdcard.h" +#include "diskio.h" +#include "ff.h" +#include "fileops.h" + +DWORD get_fattime(void) { + return 0L; +} +void set_prog_b(uint8_t val) { + if(val) { + PORTD |= _BV(PD3); + } else { + PORTD &= ~_BV(PD3); + } +} + +void set_cs_b(uint8_t val) { + if(val) { + PORTD |= _BV(PD7); + } else { + PORTD &= ~_BV(PD7); + } +} + +void set_rdwr_b(uint8_t val) { + if(val) { + PORTB |= _BV(PB3); + } else { + PORTB &= ~_BV(PB3); + } +} + +void set_cclk(uint8_t val) { + if(val) { + PORTD |= _BV(PD4); + } else { + PORTD &= ~_BV(PD4); + } +} + +void fpga_init() { + DDRB |= _BV(PB3); // PB3 is output + DDRB &= ~_BV(PB2); // PB2 is input + + DDRC = 0xff; // for FPGA config, all PORTC pins are outputs + + DDRD |= _BV(PD3) | _BV(PD4) | _BV(PD7); // PD3, PD4, PD7 are outputs + + set_cclk(0); // initial clk=0 +} + +int fpga_get_done(void) { + return 0; +} + +void fpga_postinit() { + DDRA |= _BV(PA0) | _BV(PA1) | _BV(PA2) | _BV(PA4) | _BV(PA5) | _BV(PA6); // MAPPER+NEXTADDR output + DDRB |= _BV(PB2) | _BV(PB1) | _BV(PB0); // turn PB2 into output, enable AVR_BANK +} + +void fpga_pgm(char* filename) { + set_prog_b(0); + uart_putc('P'); + set_prog_b(1); + loop_until_bit_is_set(PINB, PB2); + uart_putc('p'); + + FIL in; + FRESULT res; + UINT bytes_read; + + // open configware file + res=f_open(&in, filename, FA_READ); + if(res) { + uart_putc('?'); + return; + } + // file open successful + set_cs_b(0); + set_rdwr_b(0); + + for (;;) { + res = f_read(&in, file_buf, sizeof(file_buf), &bytes_read); + if (res || bytes_read == 0) break; // error or eof + for(int i=0; i 0) { + major = 0+ver[1]; + minor = 0+ver[2]; + if (major > 4 || (major == 4 && minor > 2)) { + print "YES"; + } + } +} diff --git a/src/integer.h b/src/integer.h new file mode 100644 index 0000000..c120dc7 --- /dev/null +++ b/src/integer.h @@ -0,0 +1,30 @@ +/* Integer definitions for ff, based on example code from ChaN */ + +#ifndef _INTEGER + +#include + +/* These types are assumed as 16-bit or larger integer */ +typedef int16_t INT; +typedef uint16_t UINT; + +/* These types are assumed as 8-bit integer */ +typedef int8_t CHAR; +typedef uint8_t UCHAR; +typedef uint8_t BYTE; + +/* These types are assumed as 16-bit integer */ +typedef int16_t SHORT; +typedef uint16_t USHORT; +typedef uint16_t WORD; + +/* These types are assumed as 32-bit integer */ +typedef int32_t LONG; +typedef uint32_t ULONG; +typedef uint32_t DWORD; + +/* Boolean type */ +typedef enum { FALSE = 0, TRUE } BOOL; + +#define _INTEGER +#endif diff --git a/src/led.c b/src/led.c new file mode 100644 index 0000000..d49563d --- /dev/null +++ b/src/led.c @@ -0,0 +1,43 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + led.c: Overdesigned LED handling + +*/ + +#include +#include "config.h" +#include "led.h" + +volatile uint8_t led_state; + +/** + * update_leds - set LEDs to correspond to the buffer status + * + * This function sets the busy/dirty LEDs to correspond to the current state + * of the buffers, i.e. busy on of at least one non-system buffer is + * allocated and dirty on if at least one buffer is allocated for writing. + * Call if you have manually changed the LEDs and you want to restore the + * "default" state. + */ +void update_leds(void) { +} diff --git a/src/led.h b/src/led.h new file mode 100644 index 0000000..7992aad --- /dev/null +++ b/src/led.h @@ -0,0 +1,54 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + led.h: Definitions for the LEDs + +*/ + +#ifndef LED_H +#define LED_H + +#include "config.h" +#include "uart.h" + +/* LED-to-bit mapping - BUSY/DIRTY are only used for SINGLE_LED */ +#define LED_ERROR 1 +#define LED_BUSY 2 +#define LED_DIRTY 4 + +extern volatile uint8_t led_state; + +/* Update the LEDs to match the buffer state */ +void update_leds(void); + +/* Wrapped in do..while to avoid "ambigious else" warnings */ +#ifdef SINGLE_LED +# define set_dirty_led(x) do{if (x) { led_state |= LED_DIRTY; } else { led_state &= (uint8_t)~LED_DIRTY; }}while(0) +# define set_busy_led(x) do{if (x) { led_state |= LED_BUSY ; } else { led_state &= (uint8_t)~LED_BUSY ; }}while(0) +# define set_error_led(x) do{if (x) { led_state |= LED_ERROR; } else { led_state &= (uint8_t)~LED_ERROR; }}while(0) +#else +# define set_dirty_led(x) do{if (x) { DIRTY_LED_ON(); } else { DIRTY_LED_OFF(); }}while(0) +# define set_busy_led(x) do{if (x) { BUSY_LED_ON(); } else { BUSY_LED_OFF(); }}while(0) +# define set_error_led(x) do{if (x) { led_state |= LED_ERROR; } else { led_state &= (uint8_t)~LED_ERROR; update_leds(); }}while(0) +#endif + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..a4310e8 --- /dev/null +++ b/src/main.c @@ -0,0 +1,200 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + main.c: Lots of init calls for the submodules + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "diskio.h" +#include "ff.h" +#include "led.h" +#include "timer.h" +#include "fpga.h" +#include "uart.h" +#include "ustring.h" +#include "utils.h" +#include "snes.h" +#include "fileops.h" +#include "memory.h" + +char stringbuf[100]; + +/* Make sure the watchdog is disabled as soon as possible */ +/* Copy this code to your bootloader if you use one and your */ +/* MCU doesn't disable the WDT after reset! */ +void get_mcusr(void) \ + __attribute__((naked)) \ + __attribute__((section(".init3"))); +void get_mcusr(void) +{ + MCUSR = 0; + wdt_disable(); +} + +#ifdef CONFIG_MEMPOISON +void poison_memory(void) \ + __attribute__((naked)) \ + __attribute__((section(".init1"))); +void poison_memory(void) { + register uint16_t i; + register uint8_t *ptr; + + asm("clr r1\n"); + /* There is no RAMSTARt variable =( */ + if (RAMEND > 2048 && RAMEND < 4096) { + /* 2K memory */ + ptr = (void *)RAMEND-2047; + for (i=0;i<2048;i++) + ptr[i] = 0x55; + } else if (RAMEND > 4096 && RAMEND < 8192) { + /* 4K memory */ + ptr = (void *)RAMEND-4095; + for (i=0;i<4096;i++) + ptr[i] = 0x55; + } else { + /* Assume 8K memory */ + ptr = (void *)RAMEND-8191; + for (i=0;i<8192;i++) + ptr[i] = 0x55; + } +} +#endif + +void avr_goto_addr(const uint32_t val) { + AVR_ADDR_RESET(); + for(uint32_t i=0; i 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1) +int main(void) __attribute__((OS_main)); +#endif +int main(void) { +#if defined __AVR_ATmega644__ || defined __AVR_ATmega644P__ || defined __AVR_ATmega2561__ + asm volatile("in r24, %0\n" + "ori r24, 0x80\n" + "out %0, r24\n" + "out %0, r24\n" + : + : "I" (_SFR_IO_ADDR(MCUCR)) + : "r24" + ); +#elif defined __AVR_ATmega32__ + asm volatile ("in r24, %0\n" + "ori r24, 0x80\n" + "out %0, r24\n" + "out %0, r24\n" + : + : "I" (_SFR_IO_ADDR(MCUCSR)) + : "r24" + ); +#elif defined __AVR_ATmega128__ || defined __AVR_ATmega1281__ + /* Just assume that JTAG doesn't hurt us on the m128 */ +#else +# error Unknown chip! +#endif + +#ifdef CLOCK_PRESCALE + clock_prescale_set(CLOCK_PRESCALE); +#endif + +/* BUSY_LED_SETDDR(); + DIRTY_LED_SETDDR(); + AUX_LED_SETDDR(); + + AUX_LED_OFF(); + set_busy_led(1); + set_dirty_led(0); +*/ + snes_reset(1); + uart_init(); + sei(); + _delay_ms(100); + disk_init(); + snes_init(); + timer_init(); + uart_puts_P(PSTR("\nsd2snes " VERSION)); + uart_putcrlf(); + + file_init(); + + FATFS fatfs; + f_mount(0,&fatfs); + set_busy_led(0); + uart_putc('W'); + fpga_init(); + fpga_pgm("/sd2snes/main.bit"); + uart_putc('!'); + _delay_ms(100); + set_avr_bank(0); + set_avr_ena(0); + set_avr_read(1); + set_avr_write(1); + AVR_ADDR_RESET(); + set_avr_addr_en(0); + snes_reset(1); + + uart_putc('('); + load_rom("/test.smc"); + uart_putc(')'); + + uart_putc('['); + load_sram("/test.srm"); + uart_putc(']'); + + AVR_ADDR_RESET(); + set_avr_mapper(0); + set_avr_ena(1); + set_avr_read(0); + set_avr_bank(0); + _delay_ms(100); + uart_puts_P(PSTR("SNES GO!")); + snes_reset(0); + DDRC = 0x00; + + while(1) { + snes_main_loop(); + } + while(1) { + uint8_t data=PINC; + _delay_ms(2); + if(data>=0x20 && data <= 0x7a) { + uart_putc(data); + } else { + uart_putc('.'); + } + SET_AVR_NEXTADDR(); + CLR_AVR_NEXTADDR(); +// set_avr_bank(3); + } + while(1); +} + diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..19caa8a --- /dev/null +++ b/src/memory.c @@ -0,0 +1,121 @@ +// insert cool lenghty disclaimer here +// memory.c: ROM+RAM operations + +#include +#include +#include +#include "config.h" +#include "uart.h" +#include "fpga.h" +#include "crc16.h" +#include "ff.h" +#include "fileops.h" + +char* hex = "0123456789ABCDEF"; + +uint32_t load_rom(char* filename) { +// TODO Mapper, Mirroring, Bankselect +// snes_rom_properties_t romprops; + set_avr_bank(0); + AVR_ADDR_RESET(); + SET_AVR_READ(); + UINT bytes_read; + DWORD filesize; + file_open(filename, FA_READ); + filesize = file_handle.fsize; + if(file_res) return 0; +// snes_rom_id(&romprops, &file_handle); + for(;;) { + bytes_read = file_read(); + if (file_res || !bytes_read) break; + for(int j=0; j>28)&0xf]); + uart_putc(hex[(crc>>24)&0xf]); + uart_putc(hex[(crc>>20)&0xf]); + uart_putc(hex[(crc>>16)&0xf]); */ + uart_putc(hex[(crc>>12)&0xf]); + uart_putc(hex[(crc>>8)&0xf]); + uart_putc(hex[(crc>>4)&0xf]); + uart_putc(hex[(crc)&0xf]); + uart_putcrlf(); + return crc; +} diff --git a/src/memory.h b/src/memory.h new file mode 100644 index 0000000..541c371 --- /dev/null +++ b/src/memory.h @@ -0,0 +1,10 @@ +// insert cool lengthy disclaimer here +// memory.h + +#ifndef MEMORY_H +#define MEMORY_H + uint32_t load_rom(char* filename); + uint32_t load_sram(char* filename); + void save_sram(char* filename, uint32_t sram_size); + uint32_t calc_sram_crc(uint32_t size); +#endif diff --git a/src/sdcard.c b/src/sdcard.c new file mode 100644 index 0000000..a27aff0 --- /dev/null +++ b/src/sdcard.c @@ -0,0 +1,749 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + sdcard.c: SD/MMC access routines + + Extended, optimized and cleaned version of code from MMC2IEC, + original copyright header follows: + +// +// Title : SD/MMC Card driver +// Author : Lars Pontoppidan, Aske Olsson, Pascal Dufour, +// Date : Jan. 2006 +// Version : 0.42 +// Target MCU : Atmel AVR Series +// +// CREDITS: +// This module is developed as part of a project at the technical univerisity of +// Denmark, DTU. +// +// DESCRIPTION: +// This SD card driver implements the fundamental communication with a SD card. +// The driver is confirmed working on 8 MHz and 14.7456 MHz AtMega32 and has +// been tested successfully with a large number of different SD and MMC cards. +// +// DISCLAIMER: +// The author is in no way responsible for any problems or damage caused by +// using this code. Use at your own risk. +// +// LICENSE: +// This code is distributed under the GNU Public License +// which can be found at http://www.gnu.org/licenses/gpl.txt +// + + The exported functions in this file are weak-aliased to their corresponding + versions defined in diskio.h so when this file is the only diskio provider + compiled in they will be automatically used by the linker. + +*/ + +#include +#include +#include +#include +#include "config.h" +#include "avrcompat.h" +#include "crc7.h" +#include "diskio.h" +#include "spi.h" +#include "uart.h" +#include "sdcard.h" + +#ifndef TRUE +#define TRUE -1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifdef CONFIG_TWINSD +# define MAX_CARDS 2 +#else +# define MAX_CARDS 1 +#endif + +// SD/MMC commands +#define GO_IDLE_STATE 0 +#define SEND_OP_COND 1 +#define SWITCH_FUNC 6 +#define SEND_IF_COND 8 +#define SEND_CSD 9 +#define SEND_CID 10 +#define STOP_TRANSMISSION 12 +#define SEND_STATUS 13 +#define SET_BLOCKLEN 16 +#define READ_SINGLE_BLOCK 17 +#define READ_MULTIPLE_BLOCK 18 +#define WRITE_BLOCK 24 +#define WRITE_MULTIPLE_BLOCK 25 +#define PROGRAM_CSD 27 +#define SET_WRITE_PROT 28 +#define CLR_WRITE_PROT 29 +#define SEND_WRITE_PROT 30 +#define ERASE_WR_BLK_STAR_ADDR 32 +#define ERASE_WR_BLK_END_ADDR 33 +#define ERASE 38 +#define LOCK_UNLOCK 42 +#define APP_CMD 55 +#define GEN_CMD 56 +#define READ_OCR 58 +#define CRC_ON_OFF 59 + +// SD ACMDs +#define SD_STATUS 13 +#define SD_SEND_NUM_WR_BLOCKS 22 +#define SD_SET_WR_BLK_ERASE_COUNT 23 +#define SD_SEND_OP_COND 41 +#define SD_SET_CLR_CARD_DETECT 42 +#define SD_SEND_SCR 51 + +// R1 status bits +#define STATUS_IN_IDLE 1 +#define STATUS_ERASE_RESET 2 +#define STATUS_ILLEGAL_COMMAND 4 +#define STATUS_CRC_ERROR 8 +#define STATUS_ERASE_SEQ_ERROR 16 +#define STATUS_ADDRESS_ERROR 32 +#define STATUS_PARAMETER_ERROR 64 + + +/* Card types - cardtype == 0 is MMC */ +#define CARD_SD (1<<0) +#define CARD_SDHC (1<<1) + +static uint8_t cardtype[MAX_CARDS]; + +/** + * getbits - read value from bit buffer + * @buffer: pointer to the data buffer + * @start : index of the first bit in the value + * @bits : number of bits in the value + * + * This function returns a value from the memory region passed as + * buffer, starting with bit "start" and "bits" bit long. The buffer + * is assumed to be MSB first, passing 0 for start will read starting + * from the highest-value bit of the first byte of the buffer. + */ +static uint32_t getbits(void *buffer, uint16_t start, int8_t bits) { + uint8_t *buf = buffer; + uint32_t result = 0; + + if ((start % 8) != 0) { + /* Unaligned start */ + result += buf[start / 8] & (0xff >> (start % 8)); + bits -= 8 - (start % 8); + start += 8 - (start % 8); + } + while (bits >= 8) { + result = (result << 8) + buf[start / 8]; + start += 8; + bits -= 8; + } + if (bits > 0) { + result = result << bits; + result = result + (buf[start / 8] >> (8-bits)); + } else if (bits < 0) { + /* Fraction of a single byte */ + result = result >> -bits; + } + return result; +} + +static uint8_t sdResponse(uint8_t expected) +{ + unsigned short count = 0x0FFF; + + while ((spiTransferByte(0xFF) != expected) && count ) + count--; + + // If count didn't run out, return success + return (count != 0); +} + +static uint8_t sdWaitWriteFinish(void) +{ + unsigned short count = 0xFFFF; // wait for quite some time + + while ((spiTransferByte(0xFF) == 0) && count ) + count--; + + // If count didn't run out, return success + return (count != 0); +} + +static void deselectCard(uint8_t card) { + // Send 8 clock cycles + SPI_SS_HIGH(card); + spiTransferByte(0xff); +} + +/** + * sendCommand - send a command to the SD card + * @card : card number to be accessed + * @command : command to be sent + * @parameter: parameter to be sent + * @deselect : Flags if the card should be deselected afterwards + * + * This function calculates the correct CRC7 for the command and + * parameter and transmits all of it to the SD card. If requested + * the card will be deselected afterwards. + */ +static int sendCommand(const uint8_t card, + const uint8_t command, + const uint32_t parameter, + const uint8_t deselect) { + union { + uint32_t l; + uint8_t c[4]; + } long2char; + + uint8_t i,crc=0,errorcount; + uint16_t counter; + + long2char.l = parameter; + crc = crc7update(0 , 0x40+command); + crc = crc7update(crc, long2char.c[3]); + crc = crc7update(crc, long2char.c[2]); + crc = crc7update(crc, long2char.c[1]); + crc = crc7update(crc, long2char.c[0]); + crc = (crc << 1) | 1; + + errorcount = 0; + while (errorcount < CONFIG_SD_AUTO_RETRIES) { + // Select card + SPI_SS_LOW(card); +#ifdef CONFIG_TWINSD + if (card == 0 && command == GO_IDLE_STATE) + /* Force both cards to SPI mode simultaneously */ + SPI_SS_LOW(1); +#endif + + // Transfer command + spiTransferByte(0x40+command); + spiTransferLong(parameter); + spiTransferByte(crc); + + // Wait for a valid response + counter = 0; + do { + i = spiTransferByte(0xff); + counter++; + } while (i & 0x80 && counter < 0x1000); + +#ifdef CONFIG_TWINSD + if (card == 0 && command == GO_IDLE_STATE) + SPI_SS_HIGH(1); +#endif + + // Check for CRC error + // can't reliably retry unless deselect is allowed + if (deselect && (i & STATUS_CRC_ERROR)) { + uart_putc('x'); + deselectCard(card); + errorcount++; + continue; + } + + if (deselect) deselectCard(card); + break; + } + + return i; +} + +// Extended init sequence for SDHC support +static uint8_t extendedInit(const uint8_t card) { + uint8_t i; + uint32_t answer; + + // Send CMD8: SEND_IF_COND + // 0b000110101010 == 2.7-3.6V supply, check pattern 0xAA + i = sendCommand(card, SEND_IF_COND, 0b000110101010, 0); + if (i > 1) { + // Card returned an error, ok (MMC oder SD1.x) but not SDHC + deselectCard(card); + return TRUE; + } + + // No error, continue SDHC initialization + answer = spiTransferLong(0); + deselectCard(card); + + if (((answer >> 8) & 0x0f) != 0b0001) { + // Card didn't accept our voltage specification + return FALSE; + } + + // Verify echo-back of check pattern + if ((answer & 0xff) != 0b10101010) { + // Check pattern mismatch, working but not SD2.0 compliant + // The specs say we should not use the card, but let's try anyway. + return TRUE; + } + + return TRUE; +} + +// SD common initialisation +static void sdInit(const uint8_t card) { + uint8_t i; + uint16_t counter; + + counter = 0xffff; + do { + // Prepare for ACMD, send CMD55: APP_CMD + i = sendCommand(card, APP_CMD, 0, 1); + if (i > 1) { + // Command not accepted, could be MMC + return; + } + + // Send ACMD41: SD_SEND_OP_COND + // 1L<<30 == Host has High Capacity Support + i = sendCommand(card, SD_SEND_OP_COND, 1L<<30, 1); + // Repeat while card card accepts command but isn't ready + } while (i == 1 && --counter > 0); + + // Ignore failures, there is at least one Sandisk MMC card + // that accepts CMD55, but not ACMD41. + if (i == 0) + /* We know that a card is SD if ACMD41 was accepted. */ + cardtype[card] |= CARD_SD; +} + +/* Detect changes of SD card 0 */ +#ifdef SD_CHANGE_VECT +ISR(SD_CHANGE_VECT) { + if (SDCARD_DETECT) + disk_state = DISK_CHANGED; + else + disk_state = DISK_REMOVED; +} +#endif + +#ifdef CONFIG_TWINSD +/* Detect changes of SD card 1 */ +ISR(SD2_CHANGE_VECT) { + if (SD2_DETECT) + disk_state = DISK_CHANGED; + else + disk_state = DISK_REMOVED; +} +#endif + +// +// Public functions +// +void sd_init(void) { + SDCARD_DETECT_SETUP(); + SDCARD_WP_SETUP(); + SD_CHANGE_SETUP(); +#ifdef CONFIG_TWINSD + /* Initialize the control lines for card 2 */ + SD2_SETUP(); + SD2_CHANGE_SETUP(); +#endif +} +void disk_init(void) __attribute__ ((weak, alias("sd_init"))); + + +DSTATUS sd_status(BYTE drv) { +#ifdef CONFIG_TWINSD + if (drv != 0) { + if (SD2_DETECT) { + if (SD2_PIN & SD2_WP) { + return STA_PROTECT; + } else { + return RES_OK; + } + } else { + return STA_NOINIT|STA_NODISK; + } + } else +#endif + if (SDCARD_DETECT) { +// uart_putc('0'); + if (SDCARD_WP) { +// uart_putc('1'); + return STA_PROTECT; + } else { +// uart_putc('2'); + return RES_OK; + } + } else { +// uart_putc('3'); + return STA_NOINIT|STA_NODISK; + } +} +DSTATUS disk_status(BYTE drv) __attribute__ ((weak, alias("sd_status"))); + + +/** + * sd_initialize - initialize SD card + * @drv : drive + * + * This function tries to initialize the selected SD card. + */ +DSTATUS sd_initialize(BYTE drv) { + uint8_t i; + uint16_t counter; + uint32_t answer; + + if (drv >= MAX_CARDS) + return STA_NOINIT|STA_NODISK; + + /* Don't bother initializing a card that isn't there */ +// uart_putc('#'); + if (sd_status(drv) & STA_NODISK) + return sd_status(drv); + + /* JLB: Should be in sd_init, but some uIEC versions have + * IEC lines tied to SPI, so I moved it here to resolve the + * conflict. + */ + spiInit(); + + disk_state = DISK_ERROR; + + cardtype[drv] = 0; + + SPI_SS_HIGH(drv); + + // Send 80 clks + for (i=0; i<10; i++) { + spiTransferByte(0xFF); + } + + // Reset card + i = sendCommand(drv, GO_IDLE_STATE, 0, 1); + if (i != 1) { + return STA_NOINIT | STA_NODISK; + } + + if (!extendedInit(drv)) + return STA_NOINIT | STA_NODISK; + + sdInit(drv); + + counter = 0xffff; + // According to the spec READ_OCR should work at this point + // without retries. One of my Sandisk-cards thinks otherwise. + do { + // Send CMD58: READ_OCR + i = sendCommand(drv, READ_OCR, 0, 0); + if (i > 1) + deselectCard(drv); + } while (i > 1 && counter-- > 0); + + if (counter > 0) { + answer = spiTransferLong(0); + + // See if the card likes our supply voltage + if (!(answer & SD_SUPPLY_VOLTAGE)) { + // The code isn't set up to completely ignore the card, + // but at least report it as nonworking + deselectCard(drv); + return STA_NOINIT | STA_NODISK; + } + + // See what card we've got + if (answer & 0x40000000) { + cardtype[drv] |= CARD_SDHC; + } + } + + // Keep sending CMD1 (SEND_OP_COND) command until zero response + counter = 0xffff; + do { + i = sendCommand(drv, SEND_OP_COND, 1L<<30, 1); + counter--; + } while (i != 0 && counter > 0); + + if (counter==0) { + return STA_NOINIT | STA_NODISK; + } + +#ifdef CONFIG_SD_DATACRC + // Enable CRC checking + // The SD spec says that the host "should" send CRC_ON_OFF before ACMD_SEND_OP_COND. + // The MMC manual I have says that CRC_ON_OFF isn't allowed before SEND_OP_COND. + // Let's just hope that all SD cards work with this order. =( + i = sendCommand(drv, CRC_ON_OFF, 1, 1); + if (i > 1) { + return STA_NOINIT | STA_NODISK; + } +#endif + + // Send MMC CMD16(SET_BLOCKLEN) to 512 bytes + i = sendCommand(drv, SET_BLOCKLEN, 512, 1); + if (i != 0) { + return STA_NOINIT | STA_NODISK; + } + + // Thats it! + disk_state = DISK_OK; + return sd_status(drv); +} +DSTATUS disk_initialize(BYTE drv) __attribute__ ((weak, alias("sd_initialize"))); + + +/** + * sd_read - reads sectors from the SD card to buffer + * @drv : drive + * @buffer: pointer to the buffer + * @sector: first sector to be read + * @count : number of sectors to be read + * + * This function reads count sectors from the SD card starting + * at sector to buffer. Returns RES_ERROR if an error occured or + * RES_OK if successful. Up to SD_AUTO_RETRIES will be made if + * the calculated data CRC does not match the one sent by the + * card. If there were errors during the command transmission + * disk_state will be set to DISK_ERROR and no retries are made. + */ +DRESULT sd_read(BYTE drv, BYTE *buffer, DWORD sector, BYTE count) { + uint8_t sec,res,tmp,errorcount; + uint16_t crc,recvcrc; + + if (drv >= MAX_CARDS) + return RES_PARERR; + + for (sec=0;sec= CONFIG_SD_AUTO_RETRIES) return RES_ERROR; + } + + return RES_OK; +} +DRESULT disk_read(BYTE drv, BYTE *buffer, DWORD sector, BYTE count) __attribute__ ((weak, alias("sd_read"))); + + + +/** + * sd_write - writes sectors from buffer to the SD card + * @drv : drive + * @buffer: pointer to the buffer + * @sector: first sector to be written + * @count : number of sectors to be written + * + * This function writes count sectors from buffer to the SD card + * starting at sector. Returns RES_ERROR if an error occured, + * RES_WPRT if the card is currently write-protected or RES_OK + * if successful. Up to SD_AUTO_RETRIES will be made if the card + * signals a CRC error. If there were errors during the command + * transmission disk_state will be set to DISK_ERROR and no retries + * are made. + */ +DRESULT sd_write(BYTE drv, const BYTE *buffer, DWORD sector, BYTE count) { + uint8_t res,sec,errorcount,status; + uint16_t crc; + + if (drv >= MAX_CARDS) + return RES_PARERR; + +#ifdef CONFIG_TWINSD + if (drv != 0) { + if (SD2_PIN & SD2_WP) + return RES_WRPRT; + } else +#endif + + if (SDCARD_WP) return RES_WRPRT; + + for (sec=0;sec> 8); + spiTransferByte(crc & 0xff); + + // Get and check status feedback + status = spiTransferByte(0xFF); + + // Retry if neccessary + if ((status & 0x0F) != 0x05) { + uart_putc('X'); + deselectCard(drv); + errorcount++; + buffer = oldbuffer; + continue; + } + + // Wait for write finish + if (!sdWaitWriteFinish()) { + SPI_SS_HIGH(drv); + disk_state = DISK_ERROR; + return RES_ERROR; + } + break; + } + deselectCard(drv); + + if (errorcount >= CONFIG_SD_AUTO_RETRIES) { + if (!(status & STATUS_CRC_ERROR)) + disk_state = DISK_ERROR; + return RES_ERROR; + } + } + + return RES_OK; +} +DRESULT disk_write(BYTE drv, const BYTE *buffer, DWORD sector, BYTE count) __attribute__ ((weak, alias("sd_write"))); + +DRESULT sd_getinfo(BYTE drv, BYTE page, void *buffer) { + uint8_t i; + uint8_t buf[18]; + uint32_t capacity; + + if (drv >= MAX_CARDS) + return RES_NOTRDY; + + if (sd_status(drv) & STA_NODISK) + return RES_NOTRDY; + + if (page != 0) + return RES_ERROR; + + /* Try to calculate the total number of sectors on the card */ + /* FIXME: Write a generic data read function and merge with sd_read */ + if (sendCommand(drv, SEND_CSD, 0, 0) != 0) { + deselectCard(drv); + return RES_ERROR; + } + + /* Wait for data token */ + if (!sdResponse(0xfe)) { + deselectCard(drv); + return RES_ERROR; + } + + for (i=0;i<18;i++) { + buf[i] = spiTransferByte(0xff); + } + deselectCard(drv); + + if (cardtype[drv] & CARD_SDHC) { + /* Special CSD for SDHC cards */ + capacity = (1 + getbits(buf,127-69,22)) * 1024; + } else { + /* Assume that MMC-CSD 1.0/1.1/1.2 and SD-CSD 1.1 are the same... */ + uint8_t exponent = 2 + getbits(buf, 127-49, 3); + capacity = 1 + getbits(buf, 127-73, 12); + exponent += getbits(buf, 127-83,4) - 9; + while (exponent--) capacity *= 2; + } + + diskinfo0_t *di = buffer; + di->validbytes = sizeof(diskinfo0_t); + di->disktype = DISK_TYPE_SD; + di->sectorsize = 2; + di->sectorcount = capacity; + + return RES_OK; +} +DRESULT disk_getinfo(BYTE drv, BYTE page, void *buffer) __attribute__ ((weak, alias("sd_getinfo"))); diff --git a/src/sdcard.h b/src/sdcard.h new file mode 100644 index 0000000..84a1df4 --- /dev/null +++ b/src/sdcard.h @@ -0,0 +1,40 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + sdcard.h: Definitions for the SD/MMC access routines + +*/ + +#ifndef SDCARD_H +#define SDCARD_H + +#include "diskio.h" + +/* These functions are weak-aliased to disk_... */ +void sd_init(void); +DSTATUS sd_status(BYTE drv); +DSTATUS sd_initialize(BYTE drv); +DRESULT sd_read(BYTE drv, BYTE *buffer, DWORD sector, BYTE count); +DRESULT sd_write(BYTE drv, const BYTE *buffer, DWORD sector, BYTE count); +DRESULT sd_getinfo(BYTE drv, BYTE page, void *buffer); + +#endif diff --git a/src/snes.c b/src/snes.c new file mode 100644 index 0000000..46f5907 --- /dev/null +++ b/src/snes.c @@ -0,0 +1,54 @@ +// insert cool lengthy disclaimer here + +// snes.c: SNES hardware control (resetting) + +#include +#include "avrcompat.h" +#include "config.h" +#include "uart.h" +#include "snes.h" +#include "memory.h" +#include "fileops.h" +#include "ff.h" + + +uint8_t initloop=1; +uint32_t sram_crc, sram_crc_old; +uint32_t sram_size = 8192; // sane default + +void snes_init() { + DDRD |= _BV(PD5); // PD5 = OUTPUT + snes_reset(1); +} + +/* + * sets the SNES reset state. + * + * state: put SNES in reset state when 1, release when 0 + */ +void snes_reset(int state) { + if(state) { + PORTD &= ~ _BV(PD5); + } else { + PORTD |= _BV(PD5); + } +} + +/* + * SD2SNES main loop. + * monitors SRAM changes, menu selections and other things + */ +void snes_main_loop() { + if(initloop) { + sram_crc_old = calc_sram_crc(sram_size); + initloop=0; + } + sram_crc = calc_sram_crc(sram_size); + if(sram_crc != sram_crc_old) { + uart_putc('U'); + uart_putcrlf(); + save_sram("/test.srm", sram_size); + } + sram_crc_old = sram_crc; + uart_putc('.'); +} diff --git a/src/snes.h b/src/snes.h new file mode 100644 index 0000000..354f2b7 --- /dev/null +++ b/src/snes.h @@ -0,0 +1,11 @@ +// insert cool lenghty disclaimer here + +// snes.h + +#ifndef SNES_H +#define SNES_H + +void snes_init(void); +void snes_reset(int state); +void snes_main_loop(void); +#endif diff --git a/src/spi.c b/src/spi.c new file mode 100644 index 0000000..c01dc14 --- /dev/null +++ b/src/spi.c @@ -0,0 +1,131 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + spi.c: Low level SPI access + + Extended, optimized and cleaned version of code from MMC2IEC, + original copyright header follows: + +// +// Title : SPI module +// Author : Lars Pontoppidan +// Date : January, 2007 +// Version : 1.03 +// Target MCU : Atmel AVR Series +// +// DESCRIPTION: +// This module implements initialization, sending and receiving bytes using +// hardware SPI on an AVR. +// +// DISCLAIMER: +// The author is in no way responsible for any problems or damage caused by +// using this code. Use at your own risk. +// +// LICENSE: +// This code is distributed under the GNU Public License +// which can be found at http://www.gnu.org/licenses/gpl.txt +// + +*/ + +// FIXME: Integrate into sdcard.c + +#include +#include "avrcompat.h" +#include "spi.h" + +// access routines +void spiInit(void) +{ + uint8_t dummy; + + // setup SPI I/O pins + SPI_PORT = (SPI_PORT & ~SPI_MASK) | SPI_SCK | SPI_SS | SPI_MISO; + SPI_DDR = (SPI_DDR & ~SPI_MASK) | SPI_SCK | SPI_SS | SPI_MOSI; + + // setup SPI interface: + // interrupts disabled, SPI enabled, MSB first, master mode, + // leading edge rising, sample on leading edge, clock = f/4 + SPCR = 0b01010000; + + // Enable SPI double speed mode -> clock = f/8 + SPSR = _BV(SPI2X); + + // clear status + dummy = SPSR; + + // clear receive buffer + dummy = SPDR; +} + + +uint8_t spiTransferByte(uint8_t data) +{ + // send the given data + SPDR = data; + + // wait for transfer to complete + loop_until_bit_is_set(SPSR, SPIF); + // *** reading of the SPSR and SPDR are crucial + // *** to the clearing of the SPIF flag + // *** in non-interrupt mode + + // return the received data + return SPDR; +} + + +uint32_t spiTransferLong(const uint32_t data) +{ + // It seems to be necessary to use the union in order to get efficient + // assembler code. + // Beware, endian unsafe union + union { + uint32_t l; + uint8_t c[4]; + } long2char; + + long2char.l = data; + + // send the given data + SPDR = long2char.c[3]; + // wait for transfer to complete + loop_until_bit_is_set(SPSR, SPIF); + long2char.c[3] = SPDR; + + SPDR = long2char.c[2]; + // wait for transfer to complete + loop_until_bit_is_set(SPSR, SPIF); + long2char.c[2] = SPDR; + + SPDR = long2char.c[1]; + // wait for transfer to complete + loop_until_bit_is_set(SPSR, SPIF); + long2char.c[1] = SPDR; + + SPDR = long2char.c[0]; + // wait for transfer to complete + loop_until_bit_is_set(SPSR, SPIF); + long2char.c[0] = SPDR; + + return long2char.l; +} diff --git a/src/spi.h b/src/spi.h new file mode 100644 index 0000000..44f0695 --- /dev/null +++ b/src/spi.h @@ -0,0 +1,68 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + spi.h: Definitions for the low-level SPI routines + + Based on original code by Lars Pontoppidan et al., see spi.c + for full copyright details. + +*/ + +#ifndef SPI_H +#define SPI_H + +// function prototypes + +// SPI interface initializer +void spiInit(void); + +// spiTransferByte(u08 data) waits until the SPI interface is ready +// and then sends a single byte over the SPI port. The function also +// returns the byte that was received during transmission. +uint8_t spiTransferByte(uint8_t data); + +// spiTransferLong(u08 data) waits until the SPI interface is ready +// and then sends the long with MSB first over the SPI port. +uint32_t spiTransferLong(uint32_t data); + +// Macros for setting slave select: +#ifdef CONFIG_TWINSD +# define SPI_SS_HIGH(card) do { \ + if (card == 0) { \ + SPI_PORT |= SPI_SS; \ + } else { \ + SD2_PORT |= SD2_CS; \ + } \ + } while (0) +#define SPI_SS_LOW(card) do { \ + if (card == 0) { \ + SPI_PORT &= (uint8_t)~SPI_SS; \ + } else { \ + SD2_PORT &= (uint8_t)~SD2_CS; \ + } \ + } while (0) +#else +# define SPI_SS_HIGH(card) SPI_PORT |= SPI_SS +# define SPI_SS_LOW(card) SPI_PORT &= (uint8_t)~SPI_SS +#endif + +#endif diff --git a/src/src2doxy.pl b/src/src2doxy.pl new file mode 100755 index 0000000..466a6ab --- /dev/null +++ b/src/src2doxy.pl @@ -0,0 +1,75 @@ +#!/usr/bin/perl -w -i~ + +# src2doxy.pl - create doxygen-compatible comments from readable C source +# Copyright (C) 2008 Ingo Korb +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License only. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# This script parses a subset of kernel-doc style comments and rewrites +# them as something doxygen will understand. +use strict; + +my $incomment = 0; +my $inspecialcomment = 0; +my @postcomment = (); +my @outputstack; + +while (<>) { + my $filename; + ($filename = $ARGV) =~ s!.*/!!; + + chomp; + s/\r$//; + s/\s+$//; + + # doxygen is too stupid to understand after-the-variable comments + # without external help. WARNING: Will substitute within strings! + s!([^ \t]\s+)/// !$1///< !; + + $incomment = 1 if m!^/\*!; + if (m!^/\*\*$!) { + $inspecialcomment = 1; + # Kill output + $_ = ""; + next; + } + + if ($incomment) { + if (m!\*/$!) { + # End of comment + $incomment = 0; + $inspecialcomment = 0; + @outputstack = @postcomment; + @postcomment = (); + } elsif (/$filename:\s+(.*).?\s*$/) { + # Add file description + push @postcomment, "\n/*! \\file $ARGV\n * \\brief $1. */\n"; + } + if ($inspecialcomment == 1) { + # First line of special comment: Brief description + $inspecialcomment = 2; + m/^\s*\*\s*((struct |union )?[^: \t]+):?\s+-?\s*(.*)\s*$/; + $_ = "/*! \\brief $3\n *"; + } elsif ($inspecialcomment == 2) { + # Modify parameters + s/\@([^: \t]+)\s*:\s+(.*)\s*$/\\param $1 $2/; + } + } +} continue { + print "$_\n"; + while (scalar(@outputstack)) { + print shift @outputstack,"\n"; + } +} + diff --git a/src/time.h b/src/time.h new file mode 100644 index 0000000..2ee8803 --- /dev/null +++ b/src/time.h @@ -0,0 +1,16 @@ +#ifndef TIME_H +#define TIME_H + +typedef uint32_t time_t; +struct tm { + uint8_t tm_sec; // 0..59 + uint8_t tm_min; // 0..59 + uint8_t tm_hour; // 0..23 + uint8_t tm_mday; // 1..[28..31] + uint8_t tm_mon; // 0..11 + uint8_t tm_year; // since 1900, i.e. 2000 is 100 + uint8_t tm_wday; // 0 to 6, sunday is 6 + // A Unix struct tm has a few more fields we don't need in this application +}; + +#endif /* TIME_H */ diff --git a/src/timer.c b/src/timer.c new file mode 100644 index 0000000..799d013 --- /dev/null +++ b/src/timer.c @@ -0,0 +1,130 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + timer.c: System timer (and button debouncer) + +*/ + +#include "config.h" +#include +#include +#include "avrcompat.h" +#include "led.h" +#include "timer.h" + +volatile tick_t ticks; +// Logical buttons +volatile uint8_t active_keys; + +// Physical buttons +uint8_t buttonstate; +tick_t lastbuttonchange; + +/* Called by the timer interrupt when the button state has changed */ +static void buttons_changed(void) { + /* Check if the previous state was stable for two ticks */ + if (time_after(ticks, lastbuttonchange+2)) { + if (active_keys & IGNORE_KEYS) { + active_keys &= ~IGNORE_KEYS; + } else if (!(buttonstate & (BUTTON_PREV|BUTTON_NEXT))) { + /* Both buttons held down */ + active_keys |= KEY_HOME; + } else if (!(buttonstate & BUTTON_NEXT) && + (BUTTON_PIN & BUTTON_NEXT)) { + /* "Next" button released */ + active_keys |= KEY_NEXT; + } else if (!(buttonstate & BUTTON_PREV) && + (BUTTON_PIN & BUTTON_NEXT)) { + active_keys |= KEY_PREV; + } + } + + lastbuttonchange = ticks; + buttonstate = BUTTON_PIN & BUTTON_MASK; +} + +/* The main timer interrupt */ +ISR(TIMER1_COMPA_vect) { + uint8_t tmp = BUTTON_PIN & BUTTON_MASK; + + if (tmp != buttonstate) { + buttons_changed(); + } + + ticks++; + +#ifdef SINGLE_LED + if (led_state & LED_ERROR) { + if ((ticks & 15) == 0) + DIRTY_LED_PORT ^= DIRTY_LED_BIT(); + } else { + if ((led_state & LED_BUSY) || (led_state & LED_DIRTY)) { + DIRTY_LED_ON(); + } else { + DIRTY_LED_OFF(); + } + } +#else + if (led_state & LED_ERROR) + if ((ticks & 15) == 0) + DIRTY_LED_PORT ^= DIRTY_LED_BIT(); +#endif + + /* Sleep button triggers when held down for 2sec */ + if (time_after(ticks, lastbuttonchange+2)) { + if (!(buttonstate & BUTTON_NEXT) && + (buttonstate & BUTTON_PREV) && + time_after(ticks, lastbuttonchange+2*HZ) && + !key_pressed(KEY_SLEEP)) { + /* Set ignore flag so the release doesn't trigger KEY_NEXT */ + active_keys |= KEY_SLEEP | IGNORE_KEYS; + /* Avoid triggering for the next two seconds */ + lastbuttonchange = ticks; + } + } +#if CONFIG_RTC_VARIANT == 1 + increment_rtc(); +#endif + +#ifdef CONFIG_REMOTE_DISPLAY + /* Check if the display wants to be queried */ + if (!(SOFTI2C_PIN & _BV(SOFTI2C_BIT_INTRQ))) { + active_keys |= KEY_DISPLAY; + } +#endif +} + +void timer_init(void) { + /* Count F_CPU/8 in timer 0 */ + TCCR0B = _BV(CS01); + + /* Set up a 100Hz interrupt using timer 1 */ + OCR1A = 1249; + TCNT1 = 0; + TCCR1A = 0; + TCCR1B = _BV(WGM12) | _BV(CS10) | _BV(CS11); + TIMSK1 |= _BV(OCIE1A); + + /* Buttons */ + BUTTON_DDR &= (uint8_t)~BUTTON_MASK; + BUTTON_PORT |= BUTTON_MASK; +} diff --git a/src/timer.h b/src/timer.h new file mode 100644 index 0000000..3a97dd5 --- /dev/null +++ b/src/timer.h @@ -0,0 +1,116 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + timer.h: System timer (and button-debouncer) + +*/ + +#ifndef TIMER_H +#define TIMER_H + +#include + +// Bit masks for the (simulated) keys +#define KEY_NEXT (1<<0) +#define KEY_PREV (1<<1) +#define KEY_HOME (1<<2) +#define KEY_SLEEP (1<<3) +/* Remote display service request */ +#define KEY_DISPLAY (1<<4) + +#define IGNORE_KEYS (1<<7) + +/// Logical keys that were pressed - must be reset by the reader. +extern volatile uint8_t active_keys; + +#define key_pressed(x) (active_keys & (x)) +#define reset_key(x) active_keys &= (uint8_t)~(x) +#define ignore_keys() active_keys = IGNORE_KEYS; + +typedef uint16_t tick_t; + +/// Global timing variable, 100 ticks per second +/// Use atomic access or getticks() ! +extern volatile tick_t ticks; +#define HZ 100 + +/** + * getticks - return the current system tick count + * + * This inline function returns the current system tick count. + */ +static inline tick_t getticks(void) { + tick_t tmp; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + tmp = ticks; + } + return tmp; +} + +#define MS_TO_TICKS(x) (x/10) + +/* Adapted from Linux 2.6 include/linux/jiffies.h: + * + * These inlines deal with timer wrapping correctly. You are + * strongly encouraged to use them + * 1. Because people otherwise forget + * 2. Because if the timer wrap changes in future you won't have to + * alter your driver code. + * + * time_after(a,b) returns true if the time a is after time b. + * + * Do this with "<0" and ">=0" to only test the sign of the result. A + * good compiler would generate better code (and a really good compiler + * wouldn't care). Gcc is currently neither. + * (">=0" refers to the time_after_eq macro which wasn't copied) + */ +#define time_after(a,b) \ + ((int16_t)(b) - (int16_t)(a) < 0) +#define time_before(a,b) time_after(b,a) + + +/// Calculate timer start value for given timeout in microseconds +#define TIMEOUT_US(x) (256-((float)F_CPU/10000000.0)*x) + +/** + * start_timeout - start a timeout using timer0 + * @startval: starting value for timer + * + * This function sets timer 0 to the specified value and clears its overflow + * flag. Use in conjunction with TIMEOUT_US to cause a timer overflow after + * a specified number of microseconds. DON'T use a variable as a parameter to + * the TIMEOUT_US macro because that would require run-time float calculations. + */ +#define start_timeout(startval) do { TCNT0 = startval; TIFR0 |= _BV(TOV0); } while (0) + +/** + * has_timed_out - returns true if timeout was reached + * + * This function returns true if the overflow flag of timer 0 is set which + * (together with start_timeout and TIMEOUT_US) will happen when the + * specified time has elapsed. + */ +#define has_timed_out() (TIFR0 & _BV(TOV0)) + +void timer_init(void); + +#endif diff --git a/src/uart.c b/src/uart.c new file mode 100644 index 0000000..f4adf01 --- /dev/null +++ b/src/uart.c @@ -0,0 +1,164 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + uart.c: UART access routines + +*/ + +#include +#include +#include +#include +#include "config.h" +#include "avrcompat.h" +#include "uart.h" + +static uint8_t txbuf[1 << CONFIG_UART_BUF_SHIFT]; +static volatile uint16_t read_idx; +static volatile uint16_t write_idx; + +ISR(USART_UDRE_vect) { + if (read_idx == write_idx) return; + UDR = txbuf[read_idx]; + read_idx = (read_idx+1) & (sizeof(txbuf)-1); + if (read_idx == write_idx) + UCSRB &= ~ _BV(UDRIE); +} + +void uart_putc(char c) { + uint16_t t=(write_idx+1) & (sizeof(txbuf)-1); +#ifndef CONFIG_DEADLOCK_ME_HARDER // :-) + UCSRB &= ~ _BV(UDRIE); // turn off RS232 irq +#else + while (t == read_idx); // wait for free space +#endif + txbuf[write_idx] = c; + write_idx = t; + //if (read_idx == write_idx) PORTD |= _BV(PD7); + UCSRB |= _BV(UDRIE); +} + +void uart_puthex(uint8_t num) { + uint8_t tmp; + tmp = (num & 0xf0) >> 4; + if (tmp < 10) + uart_putc('0'+tmp); + else + uart_putc('a'+tmp-10); + + tmp = num & 0x0f; + if (tmp < 10) + uart_putc('0'+tmp); + else + uart_putc('a'+tmp-10); +} + +void uart_trace(void *ptr, uint16_t start, uint16_t len) { + uint16_t i; + uint8_t j; + uint8_t ch; + uint8_t *data = ptr; + + data+=start; + for(i=0;i>8); + uart_puthex(start&0xff); + uart_putc('|'); + uart_putc(' '); + for(j=0;j<16;j++) { + if(i+j0x7e) + ch='.'; + uart_putc(ch); + } else { + uart_putc(' '); + } + } + uart_putc('|'); + uart_putcrlf(); + start+=16; + } +} + +static int ioputc(char c, FILE *stream) { + if (c == '\n') uart_putc('\r'); + uart_putc(c); + return 0; +} + +uint8_t uart_getc(void) { + loop_until_bit_is_set(UCSRA,RXC); + return UDR; +} + +void uart_flush(void) { + while (read_idx != write_idx) ; +} + +void uart_puts_P(prog_char *text) { + uint8_t ch; + + while ((ch = pgm_read_byte(text++))) { + uart_putc(ch); + } +} + +void uart_putcrlf(void) { + uart_putc(13); + uart_putc(10); +} + +static FILE mystdout = FDEV_SETUP_STREAM(ioputc, NULL, _FDEV_SETUP_WRITE); + +void uart_init(void) { + /* Seriellen Port konfigurieren */ + + UBRRH = (int)((double)F_CPU/(16.0*CONFIG_UART_BAUDRATE)-1) >> 8; + UBRRL = (int)((double)F_CPU/(16.0*CONFIG_UART_BAUDRATE)-1) & 0xff; + + UCSRB = _BV(RXEN) | _BV(TXEN); + // I really don't like random #ifdefs in the code =( +#if defined __AVR_ATmega32__ + UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0); +#else + UCSRC = _BV(UCSZ1) | _BV(UCSZ0); +#endif + + stdout = &mystdout; + + //UCSRB |= _BV(UDRIE); + read_idx = 0; + write_idx = 0; +} diff --git a/src/uart.h b/src/uart.h new file mode 100644 index 0000000..4f821b9 --- /dev/null +++ b/src/uart.h @@ -0,0 +1,59 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + uart.h: Definitions for the UART access routines + +*/ + +#ifndef UART_H +#define UART_H + +#ifdef CONFIG_UART_DEBUG + +#include + +void uart_init(void); +unsigned char uart_getc(void); +void uart_putc(char c); +void uart_puthex(uint8_t num); +void uart_trace(void *ptr, uint16_t start, uint16_t len); +void uart_flush(void); +void uart_puts_P(prog_char *text); +void uart_putcrlf(void); + +#include +#define dprintf(str,...) printf_P(PSTR(str), ##__VA_ARGS__) + +#else + +#define uart_init() do {} while(0) +#define uart_getc() 0 +#define uart_putc(x) do {} while(0) +#define uart_puthex(x) do {} while(0) +#define uart_flush() do {} while(0) +#define uart_puts_P(x) do {} while(0) +#define uart_putcrlf() do {} while(0) +#define uart_trace(a,b,c) do {} while(0) + +#endif + +#endif diff --git a/src/ustring.h b/src/ustring.h new file mode 100644 index 0000000..4ccddce --- /dev/null +++ b/src/ustring.h @@ -0,0 +1,42 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + ustring.h: uint8_t wrappers for string.h-functions + +*/ + +#ifndef USTRING_H +#define USTRING_H + +#include + +#define ustrcasecmp_P(s1,s2) (strcasecmp_P((char *)(s1), (s2))) +#define ustrchr(s,c) ((uint8_t *)strchr((char *)(s), (c))) +#define ustrcmp(s1,s2) (strcmp((char *)(s1), (char *)(s2))) +#define ustrcmp_P(s1,s2) (strcmp_P((char *)(s1), (s2))) +#define ustrcpy(s1,s2) (strcpy((char *)(s1), (char *)(s2))) +#define ustrcpy_P(s1,s2) (strcpy_P((char *)(s1), (s2))) +#define ustrncpy(s1,s2,n) (strncpy((char *)(s1), (char *)(s2),(n))) +#define ustrlen(s) (strlen((char *)(s))) +#define ustrrchr(s,c) ((uint8_t *)strrchr((char *)(s), (c))) + +#endif diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..6404e81 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,77 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + utils.c: Misc. utility functions that didn't fit elsewhere + +*/ + +#include +#include "ustring.h" + +uint8_t *appendnumber(uint8_t *msg, uint8_t value) { + if (value >= 100) { + *msg++ = '0' + value/100; + value %= 100; + } + + *msg++ = '0' + value/10; + *msg++ = '0' + value%10; + + return msg; +} + +/* Convert a one-byte BCD value to a normal integer */ +uint8_t bcd2int(uint8_t value) { + return (value & 0x0f) + 10*(value >> 4); +} + +/* Convert a uint8_t into a BCD value */ +uint8_t int2bcd(uint8_t value) { + return (value % 10) + 16*(value/10); +} + +/* Similiar to strtok_r, but only a single delimiting character */ +uint8_t *ustr1tok(uint8_t *str, const uint8_t delim, uint8_t **saveptr) { + uint8_t *tmp; + + if (str == NULL) + str = *saveptr; + + /* Skip leading delimiters */ + while (*str == delim) str++; + + /* If there is anything left... */ + if (*str) { + /* Search for the next delimiter */ + tmp = str; + while (*tmp && *tmp != delim) tmp++; + + /* Terminate the string there */ + if (*tmp != 0) + *tmp++ = 0; + + *saveptr = tmp; + + return str; + } else + return NULL; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..766b5b9 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,52 @@ +/* sd2iec - SD/MMC to Commodore serial bus interface/controller + Copyright (C) 2007-2009 Ingo Korb + + Inspiration and low-level SD/MMC access based on code from MMC2IEC + by Lars Pontoppidan et al., see sdcard.c|h and config.h. + + FAT filesystem access based on code from ChaN and Jim Brain, see ff.c|h. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + utils.c: Misc. utility functions that didn't fit elsewhere + +*/ + +#ifndef UTILS_H +#define UTILS_H + +/* Side-effect safe min/max */ +#define max(a,b) \ + ({ typeof (a) _a = (a); \ + typeof (b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define min(a,b) \ + ({ typeof (a) _a = (a); \ + typeof (b) _b = (b); \ + _a < _b ? _a : _b; }) + + +/* Write a number to a string as ASCII */ +uint8_t *appendnumber(uint8_t *msg, uint8_t value); + +/* Convert between integer and BCD */ +uint8_t bcd2int(uint8_t value); +uint8_t int2bcd(uint8_t value); + +/* Tokenize a string like strtok_r, but with a single delimiter character only */ +uint8_t *ustr1tok(uint8_t *str, const uint8_t delim, uint8_t **saveptr); + +#endif diff --git a/verilog/sd2snes/address.v b/verilog/sd2snes/address.v new file mode 100644 index 0000000..3f6fb9d --- /dev/null +++ b/verilog/sd2snes/address.v @@ -0,0 +1,118 @@ +`timescale 1 ns / 1 ns +////////////////////////////////////////////////////////////////////////////////// +// Company: Rehkopf +// Engineer: Rehkopf +// +// Create Date: 01:13:46 05/09/2009 +// Design Name: +// Module Name: address +// Project Name: +// Target Devices: +// Tool versions: +// Description: Address logic w/ SaveRAM masking +// +// Dependencies: +// +// Revision: +// Revision 0.02 - All new combinatorial glory. fucking slow. +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module address( + input CLK, + input [2:0] MAPPER, // AVR detected mapper + input [23:0] SNES_ADDR, // requested address from SNES + input SNES_CS, // "CART" pin from SNES (active low) + output [20:0] SRAM_ADDR, // Address to request from SRAM + output [3:0] ROM_SEL, // which SRAM unit to access (active low) + input AVR_ADDR_RESET, // reset AVR sequence (active low) + input AVR_NEXTADDR, // next byte request from AVR + input AVR_ENA, // enable AVR master mode (active low) + input AVR_ADDR_EN, // enable address counter (active low) + input [1:0] AVR_BANK, // which bank does the AVR want + input MODE, // AVR(1) or SNES(0) ("bus phase") + output IS_SAVERAM, // address/CS mapped as SRAM? + output IS_ROM, // address mapped as ROM? + input AVR_NEXTADDR_CURR, + input AVR_NEXTADDR_PREV + ); + +reg [22:0] SRAM_ADDR_BUF; +reg [3:0] ROM_SEL_BUF; +reg [3:0] AVR_ROM_SEL_BUF; +reg [20:0] AVR_ADDR; +reg [3:0] CS_ARRAY[3:0]; + +wire [3:0] CURRENT_ROM_SEL; +wire [22:0] SRAM_ADDR_FULL; + +initial begin + AVR_ADDR = 21'b0; + CS_ARRAY[0] = 4'b1110; + CS_ARRAY[1] = 4'b1101; + CS_ARRAY[2] = 4'b1011; + CS_ARRAY[3] = 4'b0111; +end + +/* currently supported mappers: + Index Mapper + 000 HiROM + 001 LoROM +*/ + +/* HiROM: SRAM @ Bank 0x20-0x3f, 0xa0-0xbf + Offset 6000-7fff */ +assign IS_SAVERAM = ((MAPPER == 3'b000) ? (!SNES_ADDR[22] + & SNES_ADDR[21] + & &SNES_ADDR[14:13] + & !SNES_ADDR[15] + ) +/* LoROM: SRAM @ Bank 0x70-0x7f, 0xf0-0xff + Offset 0000-7fff */ + :(MAPPER == 3'b001) ? (&SNES_ADDR[22:20] + & !SNES_ADDR[15] + & !SNES_CS) + : 1'b0); + +assign IS_ROM = ((MAPPER == 3'b000) ? ( (!SNES_ADDR[22] + & SNES_ADDR[15]) + |(SNES_ADDR[22])) + :(MAPPER == 3'b001) ? ( (SNES_ADDR[15]) ) + : 1'b0); + +assign SRAM_ADDR_FULL = (MODE) ? AVR_ADDR + : ((MAPPER == 3'b000) ? + (IS_SAVERAM ? SNES_ADDR[14:0] - 15'h6000 + : SNES_ADDR[22:0]) + :(MAPPER == 3'b001) ? + (IS_SAVERAM ? SNES_ADDR[14:0] + : {1'b0, SNES_ADDR[22:16], SNES_ADDR[14:0]}) + : 21'b0); + +assign SRAM_BANK = SRAM_ADDR_FULL[22:21]; +assign SRAM_ADDR = SRAM_ADDR_FULL[20:0]; + +assign ROM_SEL = (MODE) ? CS_ARRAY[AVR_BANK] : IS_SAVERAM ? 4'b0111 : 4'b1110; // CS_ARRAY[SRAM_BANK]; +//assign ROM_SEL = 4'b1110; + +always @(posedge CLK) begin + if(AVR_NEXTADDR_CURR) begin + if(!AVR_NEXTADDR_PREV) begin + if(!AVR_ADDR_RESET) + AVR_ADDR <= 21'b0; + else if (!AVR_ADDR_EN) + AVR_ADDR <= AVR_ADDR + 1; + end + end +end + + +/* +always @(posedge AVR_NEXTADDR) begin + if (!AVR_ADDR_RESET) + AVR_ADDR <= 21'b0; + else if (!AVR_ADDR_EN) + AVR_ADDR <= AVR_ADDR + 1; +end +*/ +endmodule diff --git a/verilog/sd2snes/data.v b/verilog/sd2snes/data.v new file mode 100644 index 0000000..01d7ee8 --- /dev/null +++ b/verilog/sd2snes/data.v @@ -0,0 +1,83 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: +// +// Create Date: 23:03:06 05/13/2009 +// Design Name: +// Module Name: data +// Project Name: +// Target Devices: +// Tool versions: +// Description: +// +// Dependencies: +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module data( + input CLK, + input SNES_READ, + input SNES_WRITE, + input AVR_READ, + input AVR_WRITE, + inout [7:0] SNES_DATA, + inout [7:0] SRAM_DATA, + inout [7:0] AVR_DATA, + input MODE, + input SNES_DATA_TO_MEM, + input AVR_DATA_TO_MEM, + input SRAM_DATA_TO_SNES_MEM, + input SRAM_DATA_TO_AVR_MEM, + input AVR_ENA, + input AVR_NEXTADDR_PREV, + input AVR_NEXTADDR_CURR + ); + +reg [7:0] SNES_IN_MEM; +reg [7:0] SNES_OUT_MEM; +reg [7:0] AVR_IN_MEM; +reg [7:0] AVR_OUT_MEM; + +assign SNES_DATA = SNES_READ ? 8'bZ : SNES_OUT_MEM; + +assign AVR_DATA = !AVR_ENA ? (!AVR_READ ? SRAM_DATA : 8'bZ) + : (AVR_READ ? 8'bZ : AVR_OUT_MEM); + +assign SRAM_DATA = !AVR_ENA ? (!AVR_WRITE ? AVR_DATA : 8'bZ)// /**/ : 8'bZ; + : MODE ? (!AVR_WRITE ? AVR_IN_MEM : 8'bZ) + : (!SNES_WRITE ? SNES_IN_MEM : 8'bZ); + +always @(posedge CLK) begin + if(SNES_DATA_TO_MEM) + SNES_IN_MEM <= SNES_DATA; + if(AVR_DATA_TO_MEM) + AVR_IN_MEM <= AVR_DATA; + if(SRAM_DATA_TO_SNES_MEM) + SNES_OUT_MEM <= SRAM_DATA; + if(SRAM_DATA_TO_AVR_MEM) + AVR_OUT_MEM <= SRAM_DATA; +end + + +/* +always @(posedge SNES_DATA_TO_MEM) begin + SNES_IN_MEM <= SNES_DATA; +end + +always @(posedge AVR_DATA_TO_MEM) begin + AVR_IN_MEM <= AVR_DATA; +end + +always @(posedge SRAM_DATA_TO_SNES_MEM) begin + SNES_OUT_MEM <= SRAM_DATA; +end + +always @(posedge SRAM_DATA_TO_AVR_MEM) begin + AVR_OUT_MEM <= SRAM_DATA; +end +*/ +endmodule diff --git a/verilog/sd2snes/dcm.v b/verilog/sd2snes/dcm.v new file mode 100644 index 0000000..9b8552a --- /dev/null +++ b/verilog/sd2snes/dcm.v @@ -0,0 +1,70 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: +// +// Create Date: 13:06:52 06/28/2009 +// Design Name: +// Module Name: dcm +// Project Name: +// Target Devices: +// Tool versions: +// Description: +// +// Dependencies: +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module my_dcm ( + input CLKIN, + input CLKFB, + output CLK2X + ); + + // DCM: Digital Clock Manager Circuit + // Spartan-3 + // Xilinx HDL Language Template, version 11.1 + + DCM #( + .SIM_MODE("SAFE"), // Simulation: "SAFE" vs. "FAST", see "Synthesis and Simulation Design Guide" for details + .CLKDV_DIVIDE(2.0), // Divide by: 1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5,6.0,6.5 + // 7.0,7.5,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0 or 16.0 + .CLKFX_DIVIDE(1), // Can be any integer from 1 to 32 + .CLKFX_MULTIPLY(16), // Can be any integer from 2 to 32 + .CLKIN_DIVIDE_BY_2("FALSE"), // TRUE/FALSE to enable CLKIN divide by two feature + .CLKIN_PERIOD(0.0), // Specify period of input clock + .CLKOUT_PHASE_SHIFT("NONE"), // Specify phase shift of NONE, FIXED or VARIABLE + .CLK_FEEDBACK("2X"), // Specify clock feedback of NONE, 1X or 2X + .DESKEW_ADJUST("SYSTEM_SYNCHRONOUS"), // SOURCE_SYNCHRONOUS, SYSTEM_SYNCHRONOUS or + // an integer from 0 to 15 + .DFS_FREQUENCY_MODE("LOW"), // HIGH or LOW frequency mode for frequency synthesis + .DLL_FREQUENCY_MODE("LOW"), // HIGH or LOW frequency mode for DLL + .DUTY_CYCLE_CORRECTION("TRUE"), // Duty cycle correction, TRUE or FALSE + .FACTORY_JF(16'hC080), // FACTORY JF values + .PHASE_SHIFT(0), // Amount of fixed phase shift from -255 to 255 + .STARTUP_WAIT("FALSE") // Delay configuration DONE until DCM LOCK, TRUE/FALSE + ) DCM_inst ( + .CLK0(CLK0), // 0 degree DCM CLK output + .CLK180(CLK180), // 180 degree DCM CLK output + .CLK270(CLK270), // 270 degree DCM CLK output + .CLK2X(CLK2X), // 2X DCM CLK output + .CLK2X180(CLK2X180), // 2X, 180 degree DCM CLK out + .CLK90(CLK90), // 90 degree DCM CLK output + .CLKDV(CLKDV), // Divided DCM CLK out (CLKDV_DIVIDE) + .CLKFX(CLKFX), // DCM CLK synthesis out (M/D) + .CLKFX180(CLKFX180), // 180 degree CLK synthesis out + .LOCKED(LOCKED), // DCM LOCK status output + .PSDONE(PSDONE), // Dynamic phase adjust done output + .STATUS(STATUS), // 8-bit DCM status bits output + .CLKFB(CLKFB), // DCM clock feedback + .CLKIN(CLKIN), // Clock input (from IBUFG, BUFG or DCM) + .PSCLK(PSCLK), // Dynamic phase adjust clock input + .PSEN(PSEN), // Dynamic phase adjust enable input + .PSINCDEC(PSINCDEC), // Dynamic phase adjust increment/decrement + .RST(RST) // DCM asynchronous reset input + ); + assign RST=0; +endmodule diff --git a/verilog/sd2snes/main.ucf b/verilog/sd2snes/main.ucf new file mode 100644 index 0000000..778c00b --- /dev/null +++ b/verilog/sd2snes/main.ucf @@ -0,0 +1,191 @@ +# INST "AVR_NEXTADDR_BUFGP" LOC = BUFGMUX7; +# INST "CLK_BUFGP" LOC = BUFGMUX0; +NET "AVR_BANK[0]" LOC = P56; +NET "AVR_BANK[1]" LOC = P57; +NET "AVR_ENA" LOC = P40; +NET "AVR_READ" LOC = P41; +NET "MAPPER[0]" LOC = P68; +NET "MAPPER[1]" LOC = P69; +NET "MAPPER[2]" LOC = P70; +NET "ROM_SEL[0]" LOC = P124; +NET "ROM_SEL[1]" LOC = P125; +NET "ROM_SEL[2]" LOC = P122; +NET "ROM_SEL[3]" LOC = P123; +NET "SNES_ADDR[0]" LOC = P1; +NET "SNES_ADDR[10]" LOC = P13; +NET "SNES_ADDR[11]" LOC = P14; +NET "SNES_ADDR[12]" LOC = P15; +NET "SNES_ADDR[13]" LOC = P17; +NET "SNES_ADDR[14]" LOC = P18; +NET "SNES_ADDR[15]" LOC = P20; +NET "SNES_ADDR[16]" LOC = P21; +NET "SNES_ADDR[17]" LOC = P23; +NET "SNES_ADDR[18]" LOC = P24; +NET "SNES_ADDR[19]" LOC = P25; +NET "SNES_ADDR[1]" LOC = P2; +NET "SNES_ADDR[20]" LOC = P26; +NET "SNES_ADDR[21]" LOC = P27; +NET "SNES_ADDR[22]" LOC = P28; +NET "SNES_ADDR[23]" LOC = P30; +NET "SNES_ADDR[2]" LOC = P4; +NET "SNES_ADDR[3]" LOC = P5; +NET "SNES_ADDR[4]" LOC = P6; +NET "SNES_ADDR[5]" LOC = P7; +NET "SNES_ADDR[6]" LOC = P8; +NET "SNES_ADDR[7]" LOC = P10; +NET "SNES_ADDR[8]" LOC = P11; +NET "SNES_ADDR[9]" LOC = P12; +NET "SRAM_ADDR[0]" LOC = P92; +NET "SRAM_ADDR[10]" LOC = P104; +NET "SRAM_ADDR[11]" LOC = P105; +NET "SRAM_ADDR[12]" LOC = P107; +NET "SRAM_ADDR[13]" LOC = P108; +NET "SRAM_ADDR[14]" LOC = P73; +NET "SRAM_ADDR[15]" LOC = P74; +NET "SRAM_ADDR[16]" LOC = P76; +NET "SRAM_ADDR[17]" LOC = P77; +NET "SRAM_ADDR[18]" LOC = P78; +NET "SRAM_ADDR[19]" LOC = P79; +NET "SRAM_ADDR[1]" LOC = P93; +NET "SRAM_ADDR[20]" LOC = P80; +NET "SRAM_ADDR[2]" LOC = P95; +NET "SRAM_ADDR[3]" LOC = P96; +NET "SRAM_ADDR[4]" LOC = P97; +NET "SRAM_ADDR[5]" LOC = P98; +NET "SRAM_ADDR[6]" LOC = P99; +NET "SRAM_ADDR[7]" LOC = P100; +NET "SRAM_ADDR[8]" LOC = P102; +NET "SRAM_ADDR[9]" LOC = P103; +NET "SRAM_DATA[0]" LOC = P82; +NET "SRAM_DATA[1]" LOC = P83; +NET "SRAM_DATA[2]" LOC = P84; +NET "SRAM_DATA[3]" LOC = P85; +NET "SRAM_DATA[4]" LOC = P86; +NET "SRAM_DATA[5]" LOC = P87; +NET "SRAM_DATA[6]" LOC = P89; +NET "SRAM_DATA[7]" LOC = P90; +NET "SRAM_OE" LOC = P118; +NET "SRAM_WE" LOC = P119; +NET "AVR_ADDR_RESET" LOC = P44; +NET "CLKIN" LOC = P55; +NET "AVR_NEXTADDR" LOC = P128; +NET "SNES_READ" LOC = P31; +NET "SNES_WRITE" LOC = P32; +NET "AVR_WRITE" LOC = P58; +NET "SNES_CS" LOC = P52; +NET "AVR_ADDR_EN" LOC = P53; +NET "CLK" TNM_NET = CLK; +NET "AVR_DATA[0]" LOC = P46; +NET "AVR_DATA[1]" LOC = P47; +NET "AVR_DATA[2]" LOC = P50; +NET "AVR_DATA[3]" LOC = P51; +NET "AVR_DATA[4]" LOC = P59; +NET "AVR_DATA[5]" LOC = P60; +NET "AVR_DATA[6]" LOC = P63; +NET "AVR_DATA[7]" LOC = P65; +NET "SNES_DATA[0]" LOC = P129; +NET "SNES_DATA[1]" LOC = P130; +NET "SNES_DATA[2]" LOC = P131; +NET "SNES_DATA[3]" LOC = P132; +NET "SNES_DATA[4]" LOC = P135; +NET "SNES_DATA[5]" LOC = P137; +NET "SNES_DATA[6]" LOC = P140; +NET "SNES_DATA[7]" LOC = P141; +NET "SNES_DATABUS_DIR" LOC = P35; +NET "SNES_DATABUS_OE" LOC = P33; +NET "MODE" LOC = P112; +NET "CLKIN" TNM_NET = CLKIN; +TIMESPEC TS_CLKIN = PERIOD "CLKIN" 30 MHz HIGH 50 %; +NET "AVR_ADDR_EN" IOSTANDARD = LVCMOS33; +NET "AVR_ADDR_RESET" IOSTANDARD = LVCMOS33; +NET "AVR_BANK[0]" IOSTANDARD = LVCMOS33; +NET "AVR_BANK[1]" IOSTANDARD = LVCMOS33; +NET "AVR_DATA[0]" IOSTANDARD = LVCMOS33; +NET "AVR_DATA[1]" IOSTANDARD = LVCMOS33; +NET "AVR_DATA[2]" IOSTANDARD = LVCMOS33; +NET "AVR_DATA[3]" IOSTANDARD = LVCMOS33; +NET "AVR_DATA[4]" IOSTANDARD = LVCMOS33; +NET "AVR_DATA[5]" IOSTANDARD = LVCMOS33; +NET "AVR_DATA[6]" IOSTANDARD = LVCMOS33; +NET "AVR_DATA[7]" IOSTANDARD = LVCMOS33; +NET "AVR_ENA" IOSTANDARD = LVCMOS33; +NET "AVR_NEXTADDR" IOSTANDARD = LVCMOS33; +NET "AVR_READ" IOSTANDARD = LVCMOS33; +NET "AVR_WRITE" IOSTANDARD = LVCMOS33; +NET "CLKIN" IOSTANDARD = LVCMOS33; +NET "MAPPER[0]" IOSTANDARD = LVCMOS33; +NET "MAPPER[1]" IOSTANDARD = LVCMOS33; +NET "MAPPER[2]" IOSTANDARD = LVCMOS33; +NET "MODE" IOSTANDARD = LVCMOS33; +NET "ROM_SEL[0]" IOSTANDARD = LVCMOS33; +NET "ROM_SEL[1]" IOSTANDARD = LVCMOS33; +NET "ROM_SEL[2]" IOSTANDARD = LVCMOS33; +NET "ROM_SEL[3]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[0]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[10]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[11]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[12]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[13]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[14]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[15]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[16]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[17]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[18]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[19]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[1]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[20]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[21]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[22]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[23]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[2]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[3]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[4]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[5]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[6]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[7]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[8]" IOSTANDARD = LVCMOS33; +NET "SNES_ADDR[9]" IOSTANDARD = LVCMOS33; +NET "SNES_CS" IOSTANDARD = LVCMOS33; +NET "SNES_DATABUS_DIR" IOSTANDARD = LVCMOS33; +NET "SNES_DATABUS_OE" IOSTANDARD = LVCMOS33; +NET "SNES_DATA[0]" IOSTANDARD = LVCMOS33; +NET "SNES_DATA[1]" IOSTANDARD = LVCMOS33; +NET "SNES_DATA[2]" IOSTANDARD = LVCMOS33; +NET "SNES_DATA[3]" IOSTANDARD = LVCMOS33; +NET "SNES_DATA[4]" IOSTANDARD = LVCMOS33; +NET "SNES_DATA[5]" IOSTANDARD = LVCMOS33; +NET "SNES_DATA[6]" IOSTANDARD = LVCMOS33; +NET "SNES_DATA[7]" IOSTANDARD = LVCMOS33; +NET "SNES_READ" IOSTANDARD = LVCMOS33; +NET "SNES_WRITE" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[0]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[10]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[11]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[12]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[13]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[14]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[15]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[16]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[17]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[18]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[19]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[1]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[20]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[2]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[3]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[4]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[5]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[6]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[7]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[8]" IOSTANDARD = LVCMOS33; +NET "SRAM_ADDR[9]" IOSTANDARD = LVCMOS33; +NET "SRAM_DATA[0]" IOSTANDARD = LVCMOS33; +NET "SRAM_DATA[1]" IOSTANDARD = LVCMOS33; +NET "SRAM_DATA[2]" IOSTANDARD = LVCMOS33; +NET "SRAM_DATA[3]" IOSTANDARD = LVCMOS33; +NET "SRAM_DATA[4]" IOSTANDARD = LVCMOS33; +NET "SRAM_DATA[5]" IOSTANDARD = LVCMOS33; +NET "SRAM_DATA[6]" IOSTANDARD = LVCMOS33; +NET "SRAM_DATA[7]" IOSTANDARD = LVCMOS33; +NET "SRAM_OE" IOSTANDARD = LVCMOS33; +NET "SRAM_WE" IOSTANDARD = LVCMOS33; diff --git a/verilog/sd2snes/main.v b/verilog/sd2snes/main.v new file mode 100644 index 0000000..1fc770b --- /dev/null +++ b/verilog/sd2snes/main.v @@ -0,0 +1,299 @@ +`timescale 1 ns / 1 ns +////////////////////////////////////////////////////////////////////////////////// +// Company: Rehkopf +// Engineer: Rehkopf +// +// Create Date: 01:13:46 05/09/2009 +// Design Name: +// Module Name: main +// Project Name: +// Target Devices: +// Tool versions: +// Description: Master Control FSM +// +// Dependencies: address +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module main( + input CLKIN, + input [2:0] MAPPER, + input [23:0] SNES_ADDR, + input SNES_READ, + input SNES_WRITE, + input SNES_CS, + inout [7:0] SNES_DATA, + inout [7:0] SRAM_DATA, + inout [7:0] AVR_DATA, + output [20:0] SRAM_ADDR, + output [3:0] ROM_SEL, + output SRAM_OE, + output SRAM_WE, + output SNES_DATABUS_OE, + output SNES_DATABUS_DIR, + input AVR_ADDR_RESET, + input AVR_ADDR_EN, + input AVR_READ, + input AVR_WRITE, + input AVR_NEXTADDR, + input AVR_ENA, + input [1:0] AVR_BANK, + output MODE + ); + +my_dcm snes_dcm(.CLKIN(CLKIN), + .CLK2X(CLK), + .CLKFB(CLKFB) + ); + +assign CLKFB = CLK; + +address snes_addr( + .CLK(CLK), + .MAPPER(MAPPER), + .SNES_ADDR(SNES_ADDR), // requested address from SNES + .SNES_CS(SNES_CS), // "CART" pin from SNES (active low) + .SRAM_ADDR(SRAM_ADDR), // Address to request from SRAM (active low) + .ROM_SEL(ROM_SEL), // which SRAM unit to access + .AVR_ADDR_RESET(AVR_ADDR_RESET), // reset AVR sequence (active low) + .AVR_NEXTADDR(AVR_NEXTADDR), // next byte request from AVR + .AVR_ENA(AVR_ENA), // enable AVR mode (active low) + .AVR_ADDR_EN(AVR_ADDR_EN), // enable AVR address counter (active low) + .AVR_BANK(AVR_BANK), // which bank does the AVR want + .MODE(MODE), // AVR(1) or SNES(0) ("bus phase") + .IS_SAVERAM(IS_SAVERAM), + .IS_ROM(IS_ROM), + .AVR_NEXTADDR_PREV(AVR_NEXTADDR_PREV), + .AVR_NEXTADDR_CURR(AVR_NEXTADDR_CURR) + ); + +data snes_data(.CLK(CLK), + .SNES_READ(SNES_READ), + .SNES_WRITE(SNES_WRITE), + .AVR_READ(AVR_READ), + .AVR_WRITE(AVR_WRITE), + .SNES_DATA(SNES_DATA), + .SRAM_DATA(SRAM_DATA), + .AVR_DATA(AVR_DATA), + .MODE(MODE), + .SNES_DATA_TO_MEM(SNES_DATA_TO_MEM), + .AVR_DATA_TO_MEM(AVR_DATA_TO_MEM), + .SRAM_DATA_TO_SNES_MEM(SRAM_DATA_TO_SNES_MEM), + .SRAM_DATA_TO_AVR_MEM(SRAM_DATA_TO_AVR_MEM), + .AVR_ENA(AVR_ENA), + .AVR_NEXTADDR_PREV(AVR_NEXTADDR_PREV), + .AVR_NEXTADDR_CURR(AVR_NEXTADDR_CURR) + ); + +parameter MODE_SNES = 1'b0; +parameter MODE_AVR = 1'b1; + +parameter STATE_0 = 8'b00000001; +parameter STATE_1 = 8'b00000010; +parameter STATE_2 = 8'b00000100; +parameter STATE_3 = 8'b00001000; +parameter STATE_4 = 8'b00010000; +parameter STATE_5 = 8'b00100000; +parameter STATE_6 = 8'b01000000; +parameter STATE_7 = 8'b10000000; + +reg [7:0] STATE; +reg [2:0] STATEIDX; + +reg STATE_RESET, CYCLE_RESET, CYCLE_RESET_ACK; +reg SRAM_WE_MASK; +reg SRAM_OE_MASK; + +reg [7:0] SRAM_WE_ARRAY [3:0]; +reg [7:0] SRAM_OE_ARRAY [3:0]; + +reg [7:0] SNES_DATA_TO_MEM_ARRAY[1:0]; +reg [7:0] AVR_DATA_TO_MEM_ARRAY[1:0]; +reg [7:0] SRAM_DATA_TO_SNES_MEM_ARRAY[1:0]; +reg [7:0] SRAM_DATA_TO_AVR_MEM_ARRAY[1:0]; + +reg [7:0] MODE_ARRAY; + +reg SNES_READ_CYCLE; +reg SNES_WRITE_CYCLE; +reg AVR_READ_CYCLE; +reg AVR_WRITE_CYCLE; + +reg SNES_DATABUS_OE_BUF; +reg SNES_DATABUS_DIR_BUF; + +reg AVR_NEXTADDR_PREV_BUF; +reg AVR_NEXTADDR_CURR_BUF; + +wire SNES_RW; + +assign MODE = !AVR_ENA ? MODE_AVR : MODE_ARRAY[STATEIDX]; + +assign SNES_RW = (SNES_READ & SNES_WRITE); + +initial begin + CYCLE_RESET = 0; + CYCLE_RESET_ACK = 0; + + STATE = STATE_7; + STATEIDX = 7; + SRAM_WE_MASK = 1'b1; + SRAM_OE_MASK = 1'b1; + SNES_READ_CYCLE = 1'b1; + SNES_WRITE_CYCLE = 1'b1; + AVR_READ_CYCLE = 1'b1; + AVR_WRITE_CYCLE = 1'b1; + + MODE_ARRAY = 8'b00011111; + + SRAM_WE_ARRAY[2'b00] = 8'b10010011; + SRAM_WE_ARRAY[2'b01] = 8'b10011111; + SRAM_WE_ARRAY[2'b10] = 8'b11110011; + SRAM_WE_ARRAY[2'b11] = 8'b11111111; + + SRAM_OE_ARRAY[2'b00] = 8'b11111111; + SRAM_OE_ARRAY[2'b01] = 8'b11100000; + SRAM_OE_ARRAY[2'b10] = 8'b00011111; + SRAM_OE_ARRAY[2'b11] = 8'b00000000; + + SNES_DATA_TO_MEM_ARRAY[1'b0] = 8'b10000000; + SNES_DATA_TO_MEM_ARRAY[1'b1] = 8'b00000000; + + AVR_DATA_TO_MEM_ARRAY[1'b0] = 8'b00010000; + AVR_DATA_TO_MEM_ARRAY[1'b1] = 8'b00000000; + + SRAM_DATA_TO_SNES_MEM_ARRAY[1'b0] = 8'b00000000; + SRAM_DATA_TO_SNES_MEM_ARRAY[1'b1] = 8'b00100000; + + SRAM_DATA_TO_AVR_MEM_ARRAY[1'b0] = 8'b00000000; + SRAM_DATA_TO_AVR_MEM_ARRAY[1'b1] = 8'b00000010; + + AVR_NEXTADDR_PREV_BUF = 0; + AVR_NEXTADDR_CURR_BUF = 0; +end + +// falling edge of SNES /RD or /WR marks the beginning of a new cycle +// SNES READ or WRITE always starts @posedge CLK !! +// CPU cycle can be 6, 8 or 12 CLK cycles so we must satisfy +// the minimum of 6 cycles to get everything done. + +always @(posedge CLK) begin + if (!SNES_RW) begin + if (!CYCLE_RESET_ACK) + CYCLE_RESET <= 1; + else + CYCLE_RESET <= 0; + end +end + +always @(posedge CLK) begin + if (CYCLE_RESET && !CYCLE_RESET_ACK) begin + CYCLE_RESET_ACK <= 1; + STATE <= STATE_0; + end else begin + case (STATE) + STATE_0: + STATE <= STATE_1; + STATE_1: + STATE <= STATE_2; + STATE_2: + STATE <= STATE_3; + STATE_3: + STATE <= STATE_4; + STATE_4: + STATE <= STATE_5; + STATE_5: + STATE <= STATE_6; + STATE_6: + STATE <= STATE_7; + STATE_7: begin + if (SNES_RW) // check for end of SNES cycle to avoid looping + CYCLE_RESET_ACK <= 0; // ready for new cycle + STATE <= STATE_7; + end + default: + STATE <= STATE_7; + endcase + end +end + +always @(posedge CLK) begin + case (STATE) + + STATE_7: begin + SNES_READ_CYCLE <= SNES_READ; + SNES_WRITE_CYCLE <= SNES_WRITE; + AVR_READ_CYCLE <= AVR_READ; + AVR_WRITE_CYCLE <= AVR_WRITE; + STATEIDX <= 7; + end + STATE_0: begin + STATEIDX <= 6; + end + + STATE_1: begin + STATEIDX <= 5; + end + + STATE_2: begin + STATEIDX <= 4; + end + + STATE_3: begin + STATEIDX <= 3; + end + + STATE_4: begin + STATEIDX <= 2; + end + + STATE_5: begin + STATEIDX <= 1; + end + + STATE_6: begin + STATEIDX <= 0; + end + endcase +end + +// When in AVR mode, enable SRAM_WE according to AVR programming +// else enable SRAM_WE according to state&cycle +assign SRAM_WE = !AVR_ENA ? AVR_WRITE + : ((!IS_SAVERAM & !MODE) | SRAM_WE_ARRAY[{SNES_WRITE_CYCLE, AVR_WRITE_CYCLE}][STATEIDX]); + +// When in AVR mode, enable SRAM_OE whenever not writing +// else enable SRAM_OE according to state&cycle +assign SRAM_OE = !AVR_ENA ? AVR_READ + : SRAM_OE_ARRAY[{SNES_WRITE_CYCLE, AVR_WRITE_CYCLE}][STATEIDX]; + +// dumb version +//assign SRAM_OE = !AVR_ENA ? AVR_READ : SNES_READ; +//assign SRAM_WE = !AVR_ENA ? AVR_WRITE : 1'b1; + +always @(posedge CLK) begin + SNES_DATABUS_OE_BUF <= SNES_CS | (SNES_READ & SNES_WRITE); +end + +always @(posedge CLK) begin + AVR_NEXTADDR_PREV_BUF <= AVR_NEXTADDR_CURR_BUF; + AVR_NEXTADDR_CURR_BUF <= AVR_NEXTADDR; +end + +assign AVR_NEXTADDR_PREV = AVR_NEXTADDR_PREV_BUF; +assign AVR_NEXTADDR_CURR = AVR_NEXTADDR_CURR_BUF; + +//assign SNES_DATABUS_OE = (!IS_SAVERAM & SNES_CS) | (SNES_READ & SNES_WRITE); +assign SNES_DATABUS_OE = (IS_ROM & SNES_CS) | (!IS_ROM & !IS_SAVERAM) | (SNES_READ & SNES_WRITE); +assign SNES_DATABUS_DIR = !SNES_WRITE ? 1'b0 : 1'b1; + +assign SNES_DATA_TO_MEM = SNES_DATA_TO_MEM_ARRAY[SNES_WRITE_CYCLE][STATEIDX]; +assign AVR_DATA_TO_MEM = AVR_DATA_TO_MEM_ARRAY[AVR_WRITE_CYCLE][STATEIDX]; + +assign SRAM_DATA_TO_SNES_MEM = SRAM_DATA_TO_SNES_MEM_ARRAY[SNES_WRITE_CYCLE][STATEIDX]; +assign SRAM_DATA_TO_AVR_MEM = SRAM_DATA_TO_AVR_MEM_ARRAY[AVR_WRITE_CYCLE][STATEIDX]; + +endmodule diff --git a/verilog/sd2snes/sd2snes.xise b/verilog/sd2snes/sd2snes.xise new file mode 100644 index 0000000..368506e --- /dev/null +++ b/verilog/sd2snes/sd2snes.xise @@ -0,0 +1,80 @@ + + + +

+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/verilog/sd2snes/tf_main.v b/verilog/sd2snes/tf_main.v new file mode 100644 index 0000000..ad7076a --- /dev/null +++ b/verilog/sd2snes/tf_main.v @@ -0,0 +1,136 @@ +`timescale 1ns / 1ps + +//////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: +// +// Create Date: 23:11:58 05/13/2009 +// Design Name: main +// Module Name: /home/ikari/prj/sd2snes/verilog/sd2snes/tf_main.v +// Project Name: sd2snes +// Target Device: +// Tool versions: +// Description: +// +// Verilog Test Fixture created by ISE for module: main +// +// Dependencies: +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +//////////////////////////////////////////////////////////////////////////////// + +module tf_main; + + // Inputs + reg CLK; + reg [2:0] MAPPER; + reg [23:0] SNES_ADDR; + reg SNES_READ; + reg SNES_WRITE; + reg SNES_CS; + reg AVR_ADDR_RESET; + reg AVR_WRITE; + reg AVR_READ; + reg AVR_NEXTADDR; + reg AVR_ENA; + reg [1:0] AVR_BANK; + reg AVR_ADDR_EN; + + // Outputs + wire [20:0] SRAM_ADDR; + wire [3:0] ROM_SEL; + wire SRAM_OE; + wire SRAM_WE; + wire SNES_DATABUS_OE; + wire SNES_DATABUS_DIR; + wire MODE; + + // Bidirs + wire [7:0] SNES_DATA; + wire [7:0] SRAM_DATA; + wire [7:0] AVR_DATA; + + reg [7:0] SRAM_DATA_BUF; + reg [7:0] SNES_DATA_BUF; + +// Instantiate the Unit Under Test (UUT) + main uut ( + .CLKIN(CLK), + .MAPPER(MAPPER), + .SNES_ADDR(SNES_ADDR), + .SNES_READ(SNES_READ), + .SNES_WRITE(SNES_WRITE), + .SNES_CS(SNES_CS), + .SNES_DATA(SNES_DATA), + .SRAM_DATA(SRAM_DATA), + .AVR_DATA(AVR_DATA), + .SRAM_ADDR(SRAM_ADDR), + .ROM_SEL(ROM_SEL), + .SRAM_OE(SRAM_OE), + .SRAM_WE(SRAM_WE), + .AVR_ADDR_RESET(AVR_ADDR_RESET), + .AVR_WRITE(AVR_WRITE), + .AVR_NEXTADDR(AVR_NEXTADDR), + .AVR_ENA(AVR_ENA), + .AVR_BANK(AVR_BANK), + .AVR_READ(AVR_READ), + .AVR_ADDR_EN(AVR_ADDR_EN), + .SNES_DATABUS_OE(SNES_DATABUS_OE), + .SNES_DATABUS_DIR(SNES_DATABUS_DIR), + .MODE(MODE) + ); + + assign SRAM_DATA = SRAM_DATA_BUF; + initial begin + // Initialize Inputs + CLK = 1; + MAPPER = 0; + SNES_ADDR = 24'h223456; + SNES_READ = 1; + SNES_WRITE = 1; + SNES_CS = 0; + AVR_ADDR_RESET = 1; + AVR_WRITE = 1; + AVR_READ = 0; + AVR_NEXTADDR = 0; + AVR_ENA = 1; + AVR_BANK = 0; + AVR_ADDR_EN = 0; + SRAM_DATA_BUF = 8'hff; + // Wait for global reset to finish + #276; + #276 AVR_NEXTADDR <= ~AVR_NEXTADDR; + #276 AVR_NEXTADDR <= ~AVR_NEXTADDR; + #276 AVR_NEXTADDR <= ~AVR_NEXTADDR; + #276 AVR_NEXTADDR <= ~AVR_NEXTADDR; + #276 AVR_NEXTADDR <= ~AVR_NEXTADDR; + #276 AVR_NEXTADDR <= ~AVR_NEXTADDR; + #276 AVR_NEXTADDR <= ~AVR_NEXTADDR; + #276 AVR_NEXTADDR <= ~AVR_NEXTADDR; + SNES_ADDR <= 24'h123456; + SNES_READ <= 0; + #176; + SNES_READ <= 1; + #100; + SNES_WRITE <= 0; + #176; + SNES_WRITE <= 1; + #100; + AVR_WRITE <= 0; + SNES_READ <= 0; + #276; +// AVR_READ <= 1; + // Add stimulus here + end + + always + #23 CLK <= ~CLK; +// always begin +// #234 AVR_NEXTADDR <= ~AVR_NEXTADDR; +// end + +endmodule +