├── docs ├── .gitignore ├── requirements.txt ├── commands.rst ├── usb_h.rst ├── fx2delay_h.rst ├── fx2i2c_h.rst ├── fx2eeprom_h.rst ├── index.rst ├── fx2lib_h.rst ├── fx2usbdfu_h.rst ├── usbdfu_h.rst ├── fx2spi_h.rst ├── fx2spiflash_h.rst ├── fx2usbmassstor_h.rst ├── fx2debug_h.rst ├── usbmassstor_h.rst ├── usbcdc_h.rst ├── faq.rst ├── fx2uf2_h.rst ├── usbmicrosoft_h.rst ├── Makefile ├── device_library.rst ├── introduction.rst ├── fx2usb_h.rst ├── prerequisites.rst ├── host_library.rst ├── fx2regs_h.rst ├── fx2ints_h.rst ├── conf.py ├── getting_started.rst └── build_system.rst ├── software ├── .gitignore ├── setup.py ├── README.rst ├── normalize.py ├── pyproject.toml ├── deploy-bootloader.sh └── fx2 │ └── format.py ├── firmware ├── library │ ├── defautoisr.c │ ├── defisr.c │ ├── defusbsetup.c │ ├── defusbgetconfig.c │ ├── defusbhalt.c │ ├── defusbgetiface.c │ ├── defusbdesc.c │ ├── defusbsetiface.c │ ├── defusbsetconfig.c │ ├── syncdelay.asm │ ├── bswap.c │ ├── include │ │ ├── usbweb.h │ │ ├── fx2lib.h │ │ ├── bits │ │ │ └── asmargs.h │ │ ├── usbmicrosoft.h │ │ ├── fat.h │ │ ├── fx2eeprom.h │ │ ├── fx2i2c.h │ │ ├── usbmassstor.h │ │ ├── fx2uf2.h │ │ ├── fx2delay.h │ │ ├── fx2spi.h │ │ ├── fx2usbmassstor.h │ │ ├── fx2usbdfu.h │ │ ├── fx2ints.h │ │ ├── scsi.h │ │ ├── fx2debug.h │ │ ├── usbdfu.h │ │ ├── usb.h │ │ ├── fx2spiflash.h │ │ ├── fx2queue.h │ │ └── usbcdc.h │ ├── fx2conf.mk │ ├── xmemclr.c │ ├── fx2rules.mk │ ├── eeprom.c │ ├── xmemcpy.c │ ├── Makefile │ ├── i2c.c │ ├── autovec.asm │ ├── uf2scsi.c │ ├── usbmassstor.c │ ├── delay.c │ ├── usbdfu.c │ └── usb.c ├── boot-cypress │ ├── Makefile │ └── main.c ├── boot-uf2 │ ├── Makefile │ └── main.c ├── Makefile └── boot-dfu │ ├── Makefile │ └── main.c ├── examples ├── blinky │ ├── Makefile │ └── main.c ├── printf │ ├── Makefile │ └── main.c ├── cdc-acm │ └── Makefile ├── Makefile ├── boot-dfu-spiflash │ └── Makefile └── boot-uf2-dfu │ └── Makefile ├── .gitignore ├── README.md ├── LICENSE-0BSD.txt ├── .github └── workflows │ └── main.yaml └── tools ├── README.md └── regtxt2c.py /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | -------------------------------------------------------------------------------- /software/.gitignore: -------------------------------------------------------------------------------- 1 | !fx2/boot-cypress.ihex 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-argparse 2 | breathe 3 | mock 4 | -------------------------------------------------------------------------------- /software/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /firmware/library/defautoisr.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void ISRNAME(void) __interrupt {} 4 | -------------------------------------------------------------------------------- /firmware/library/defisr.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void ISRNAME(void) __interrupt(INTNAME) {} 4 | -------------------------------------------------------------------------------- /examples/blinky/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = blinky 2 | 3 | LIBFX2 = ../../firmware/library 4 | include $(LIBFX2)/fx2rules.mk 5 | -------------------------------------------------------------------------------- /firmware/boot-cypress/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = boot-cypress 2 | LIBRARIES = fx2 fx2usb fx2isrs 3 | 4 | LIBFX2 = ../library 5 | include $(LIBFX2)/fx2rules.mk 6 | -------------------------------------------------------------------------------- /examples/printf/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = printf 2 | LIBRARIES = fx2 3 | MODEL = small 4 | 5 | LIBFX2 = ../../firmware/library 6 | include $(LIBFX2)/fx2rules.mk 7 | -------------------------------------------------------------------------------- /firmware/library/defusbsetup.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void handle_usb_setup(__xdata struct usb_req_setup *request) { 4 | request; 5 | STALL_EP0(); 6 | } 7 | -------------------------------------------------------------------------------- /firmware/library/defusbgetconfig.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void handle_usb_get_configuration(void) { 4 | EP0BUF[0] = usb_config_value; 5 | SETUP_EP0_IN_BUF(1); 6 | } 7 | -------------------------------------------------------------------------------- /firmware/library/defusbhalt.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool handle_usb_clear_endpoint_halt(uint8_t endpoint) { 4 | endpoint; 5 | ACK_EP0(); 6 | return true; 7 | } 8 | -------------------------------------------------------------------------------- /examples/cdc-acm/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = cdc-acm 2 | LIBRARIES = fx2 fx2usb fx2isrs 3 | MODEL = small 4 | 5 | LIBFX2 = ../../firmware/library 6 | include $(LIBFX2)/fx2rules.mk 7 | -------------------------------------------------------------------------------- /firmware/library/defusbgetiface.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void handle_usb_get_interface(uint8_t interface) { 4 | interface; 5 | EP0BUF[0] = 0; 6 | SETUP_EP0_IN_BUF(1); 7 | } 8 | -------------------------------------------------------------------------------- /firmware/boot-uf2/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = boot-uf2 2 | LIBRARIES = fx2 fx2usb fx2usbmassstor fx2uf2 fx2isrs 3 | MODEL = medium 4 | 5 | LIBFX2 = ../library 6 | include $(LIBFX2)/fx2rules.mk 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C build products 2 | build/ 3 | .stamp 4 | *.bin 5 | *.lib 6 | *.ihex 7 | *.uf2 8 | *.UF2 9 | *.dfu 10 | 11 | # Python build products 12 | __pycache__/ 13 | *.pyc 14 | *.egg-info 15 | dist/ 16 | -------------------------------------------------------------------------------- /firmware/Makefile: -------------------------------------------------------------------------------- 1 | SUBDIRS = library boot-cypress boot-dfu boot-uf2 2 | 3 | all: 4 | @set -e; for dir in $(SUBDIRS); do $(MAKE) -C $${dir} all; done 5 | 6 | clean: 7 | @set -e; for dir in $(SUBDIRS); do $(MAKE) -C $${dir} clean; done 8 | -------------------------------------------------------------------------------- /firmware/boot-dfu/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = boot-dfu 2 | LIBRARIES = fx2 fx2usb fx2dfu fx2isrs 3 | MODEL = small 4 | 5 | CODE_SIZE ?= 0x3c00 6 | XRAM_SIZE ?= 0x0400 7 | 8 | LIBFX2 = ../library 9 | include $(LIBFX2)/fx2rules.mk 10 | -------------------------------------------------------------------------------- /docs/commands.rst: -------------------------------------------------------------------------------- 1 | Command-line tool reference 2 | =========================== 3 | 4 | .. _bootloader-tool: 5 | 6 | Bootloader tool 7 | --------------- 8 | 9 | .. argparse:: 10 | :ref: fx2.fx2tool.get_argparser 11 | :prog: fx2tool 12 | -------------------------------------------------------------------------------- /docs/usb_h.rst: -------------------------------------------------------------------------------- 1 | usb.h 2 | ===== 3 | 4 | The ``usb.h`` header contains standard USB request and descriptor definitions. See the USB 2.0 specification, chapter 9 for details. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: usb.h 10 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | SUBDIRS = blinky printf cdc-acm boot-uf2-dfu boot-dfu-spiflash 2 | 3 | all: 4 | @set -e; for dir in $(SUBDIRS); do $(MAKE) -C $${dir} all; done 5 | 6 | clean: 7 | @set -e; for dir in $(SUBDIRS); do $(MAKE) -C $${dir} clean; done 8 | -------------------------------------------------------------------------------- /firmware/library/defusbdesc.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern usb_descriptor_set_c usb_descriptor_set; 4 | 5 | void handle_usb_get_descriptor(enum usb_descriptor type, uint8_t index) { 6 | usb_serve_descriptor(&usb_descriptor_set, type, index); 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libfx2 2 | 3 | _libfx2_ is a chip support package for Cypress EZ-USB FX2 series microcontrollers. 4 | 5 | See the [complete documentation](https://libfx2.readthedocs.io) for details. 6 | 7 | ## License 8 | 9 | [0-clause BSD](LICENSE-0BSD.txt) 10 | -------------------------------------------------------------------------------- /examples/boot-dfu-spiflash/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = boot-dfu-spiflash 2 | LIBRARIES = fx2 fx2usb fx2dfu fx2isrs 3 | MODEL = medium 4 | 5 | CODE_SIZE ?= 0x3c00 6 | XRAM_SIZE ?= 0x0400 7 | 8 | LIBFX2 = ../../firmware/library 9 | include $(LIBFX2)/fx2rules.mk 10 | -------------------------------------------------------------------------------- /docs/fx2delay_h.rst: -------------------------------------------------------------------------------- 1 | fx2delay.h 2 | ========== 3 | 4 | The ``fx2delay.h`` header contains delay routines for the Cypress FX2 series. When using this header, the ``fx2`` library must be linked in. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: fx2delay.h 10 | -------------------------------------------------------------------------------- /docs/fx2i2c_h.rst: -------------------------------------------------------------------------------- 1 | fx2i2c.h 2 | ======== 3 | 4 | The ``fx2i2c.h`` header contains I2C and EEPROM routines for the Cypress FX2 series. When using this header, the ``fx2`` library must be linked in. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: fx2i2c.h 10 | -------------------------------------------------------------------------------- /examples/boot-uf2-dfu/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = boot-uf2-dfu 2 | LIBRARIES = fx2 fx2usb fx2dfu fx2usbmassstor fx2uf2 fx2isrs 3 | MODEL = medium 4 | 5 | CODE_SIZE ?= 0x3d00 6 | XRAM_SIZE ?= 0x0300 7 | 8 | LIBFX2 = ../../firmware/library 9 | include $(LIBFX2)/fx2rules.mk 10 | -------------------------------------------------------------------------------- /docs/fx2eeprom_h.rst: -------------------------------------------------------------------------------- 1 | fx2eeprom.h 2 | =========== 3 | 4 | The ``fx2eeprom.h`` header contains EEPROM routines for the Cypress FX2 series. When using this header, the ``fx2`` library must be linked in. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: fx2eeprom.h 10 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | libfx2 Reference 2 | ================ 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | introduction 10 | prerequisites 11 | getting_started 12 | commands 13 | host_library 14 | build_system 15 | device_library 16 | faq 17 | -------------------------------------------------------------------------------- /docs/fx2lib_h.rst: -------------------------------------------------------------------------------- 1 | fx2lib.h 2 | ======== 3 | 4 | The ``fx2lib.h`` header contains library routines that use the Cypress FX2 series architectural extensions. When using this header, the ``fx2`` library must be linked in. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: fx2lib.h 10 | -------------------------------------------------------------------------------- /docs/fx2usbdfu_h.rst: -------------------------------------------------------------------------------- 1 | fx2usbdfu.h 2 | =========== 3 | 4 | The ``fx2usbdfu.h`` header contains USB DFU interface support code for the Cypress FX2 series. When using this header, the ``fx2``, ``fx2usb``, and ``fx2usbdfu`` libraries must be linked in. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: fx2usbdfu.h 10 | -------------------------------------------------------------------------------- /docs/usbdfu_h.rst: -------------------------------------------------------------------------------- 1 | usbdfu.h 2 | ======== 3 | 4 | The ``usbdfu.h`` header contains USB Device Firmware Upgrade interface class request and descriptor definitions. See the USB Device Class Specification for Device Firmware Upgrade document for details. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: usbdfu.h 10 | -------------------------------------------------------------------------------- /software/README.rst: -------------------------------------------------------------------------------- 1 | fx2 2 | === 3 | 4 | The *fx2* Python package allows interacting with Cypress EZ-USB FX2 series microcontrollers. 5 | It provides: 6 | 7 | * *fx2*, a Python library for interacting with the bootloader, 8 | * *fx2tool*, a tool for programming and debugging the chips. 9 | 10 | See the documentation for details. 11 | -------------------------------------------------------------------------------- /docs/fx2spi_h.rst: -------------------------------------------------------------------------------- 1 | fx2spi.h 2 | ======== 3 | 4 | The ``fx2spi.h`` header contains templated SPI bitbang routines for the Cypress FX2 series implemented in assembly. This header is the complete implementation of the SPI interface and does not have a corresponding library. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: fx2spi.h 10 | -------------------------------------------------------------------------------- /docs/fx2spiflash_h.rst: -------------------------------------------------------------------------------- 1 | fx2spiflash.h 2 | ============= 3 | 4 | The ``fx2spiflash.h`` header contains templated 25C-compatible SPI flash routines for the Cypress FX2 series. This header is the complete implementation of the SPI flash interface and does not have a corresponding library. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: fx2spiflash.h 10 | -------------------------------------------------------------------------------- /docs/fx2usbmassstor_h.rst: -------------------------------------------------------------------------------- 1 | fx2usbmassstor.h 2 | ================ 3 | 4 | The ``fx2usbmassstor.h`` header contains USB Mass Storage Bulk-Only Transfer interface class support code for the Cypress FX2 series. When using this header, the ``fx2`` and ``fx2usb`` libraries must be linked in. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: fx2usbmassstor.h 10 | -------------------------------------------------------------------------------- /docs/fx2debug_h.rst: -------------------------------------------------------------------------------- 1 | fx2debug.h 2 | ========== 3 | 4 | The ``fx2debug.h`` header contains templated debug serial port bitbang routines for the Cypress FX2 series implemented in assembly. This header is the complete implementation of the debug serial port and does not have a corresponding library. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: fx2debug.h 10 | -------------------------------------------------------------------------------- /docs/usbmassstor_h.rst: -------------------------------------------------------------------------------- 1 | usbmassstor.h 2 | ============= 3 | 4 | The ``usbmassstor.h`` header contains USB Mass Storage interface class request and descriptor definitions. See the USB Mass Storage Class Specification Overview and USB Mass Storage Class Bulk-Only Transport documents for details. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: usbmassstor.h 10 | -------------------------------------------------------------------------------- /firmware/library/defusbsetiface.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern usb_descriptor_set_c usb_descriptor_set; 4 | 5 | bool handle_usb_set_interface(uint8_t interface, uint8_t alt_setting) { 6 | interface; 7 | if(alt_setting == 0) { 8 | usb_reset_data_toggles(&usb_descriptor_set, interface, alt_setting); 9 | return true; 10 | } 11 | 12 | return false; 13 | } 14 | -------------------------------------------------------------------------------- /docs/usbcdc_h.rst: -------------------------------------------------------------------------------- 1 | usbcdc.h 2 | ======== 3 | 4 | The ``usbcdc.h`` header contains USB Communications device and interface class request and descriptor definitions. See the USB Class Definitions for Communications Devices document as well as USB Communications Class Subclass Specification documents for details. 5 | 6 | Reference 7 | --------- 8 | 9 | .. autodoxygenfile:: usbcdc.h 10 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | FAQ 2 | === 3 | 4 | Why not just use fx2lib_? 5 | ------------------------- 6 | 7 | I wrote all of the code in *libfx2* from scratch using the USB specification and Cypress datasheets for two reasons: the fx2lib code quality is very low, and it is licensed under LGPL, which is absurd for chip support packages. 8 | 9 | .. _fx2lib: https://github.com/djmuhlestein/fx2lib 10 | -------------------------------------------------------------------------------- /firmware/library/defusbsetconfig.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern usb_descriptor_set_c usb_descriptor_set; 4 | 5 | bool handle_usb_set_configuration(uint8_t config_value) { 6 | if(config_value == 0 || config_value == 1) { 7 | usb_config_value = config_value; 8 | 9 | usb_reset_data_toggles(&usb_descriptor_set, /*inteface=*/0xff, /*alt_setting=*/0xff); 10 | return true; 11 | } 12 | 13 | return false; 14 | } 15 | -------------------------------------------------------------------------------- /docs/fx2uf2_h.rst: -------------------------------------------------------------------------------- 1 | fx2uf2.h 2 | ======== 3 | 4 | The ``fx2uf2.h`` header contains USB UF2 interface support code for the Cypress FX2 series. See the `Microsoft UF2 specification `_ for details. When using this header, the ``fx2``, ``fx2usb``, ``fx2usbmassstor`` and ``fx2uf2`` libraries must be linked in. 5 | 6 | .. _msuf2: https://github.com/Microsoft/uf2 7 | 8 | Reference 9 | --------- 10 | 11 | .. autodoxygenfile:: fx2uf2.h 12 | -------------------------------------------------------------------------------- /docs/usbmicrosoft_h.rst: -------------------------------------------------------------------------------- 1 | usbmicrosoft.h 2 | ============== 3 | 4 | The ``usbmicrosoft.h`` header contains Microsoft-specific USB request and descriptor definitions. See the `Microsoft OS Descriptors for USB Devices `_ page for details. 5 | 6 | .. _msdesc: https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors 7 | 8 | Reference 9 | --------- 10 | 11 | .. autodoxygenfile:: usbmicrosoft.h 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | SPHINXOPTS = 2 | SPHINXBUILD = sphinx-build 3 | 4 | # Put it first so that "make" without argument is like "make help". 5 | help: 6 | @$(SPHINXBUILD) -M help . _build $(SPHINXOPTS) $(O) 7 | 8 | .PHONY: help Makefile 9 | 10 | # Catch-all target: route all unknown targets to Sphinx using the new 11 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 12 | %: Makefile 13 | @$(SPHINXBUILD) -M $@ . _build $(SPHINXOPTS) $(O) 14 | -------------------------------------------------------------------------------- /docs/device_library.rst: -------------------------------------------------------------------------------- 1 | Device-side library reference 2 | ============================= 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | usb_h 8 | usbmicrosoft_h 9 | usbdfu_h 10 | usbcdc_h 11 | usbmassstor_h 12 | fx2regs_h 13 | fx2ints_h 14 | fx2lib_h 15 | fx2delay_h 16 | fx2debug_h 17 | fx2i2c_h 18 | fx2eeprom_h 19 | fx2spi_h 20 | fx2spiflash_h 21 | fx2usb_h 22 | fx2usbdfu_h 23 | fx2usbmassstor_h 24 | fx2uf2_h 25 | -------------------------------------------------------------------------------- /firmware/library/syncdelay.asm: -------------------------------------------------------------------------------- 1 | .module syncdelay 2 | ; Exports 3 | .globl _nop8 4 | .globl _nop9 5 | .globl _nop10 6 | .globl _nop11 7 | .globl _nop12 8 | .globl _nop13 9 | .globl _nop14 10 | .globl _nop15 11 | .globl _nop16 12 | ; Code 13 | .area CSEG (ABS,CODE) 14 | _nop16: 15 | nop 16 | _nop15: 17 | nop 18 | _nop14: 19 | nop 20 | _nop13: 21 | nop 22 | _nop12: 23 | nop 24 | _nop11: 25 | nop 26 | _nop10: 27 | nop 28 | _nop9: 29 | nop 30 | _nop8: 31 | ret 32 | -------------------------------------------------------------------------------- /docs/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | *libfx2* is a chip support package for Cypress EZ-USB FX2 series microcontrollers. 5 | 6 | On the firmware side, it provides: 7 | 8 | * register definitions, 9 | * makefile templates, 10 | * common code for handling standard USB requests, 11 | * a bootloader implementing Cypress vendor commands. 12 | 13 | On the software side, it provides: 14 | 15 | * :mod:`fx2`, a Python library for interacting with the bootloader, 16 | * :ref:`fx2tool `, a tool for programming and debugging the chips. 17 | -------------------------------------------------------------------------------- /software/normalize.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from fx2.format import input_data, output_data 3 | 4 | 5 | def normalize(input): 6 | output = [] 7 | for (addr, chunk) in sorted(input): 8 | if output and output[-1][0] + len(output[-1][1]) == addr: 9 | output[-1] = (output[-1][0], output[-1][1] + chunk) 10 | else: 11 | output.append((addr, chunk)) 12 | return output 13 | 14 | 15 | with open(sys.argv[1], "rb") as f: 16 | data = input_data(f) 17 | data = normalize(data) 18 | with open(sys.argv[2], "wb") as f: 19 | output_data(f, data) 20 | -------------------------------------------------------------------------------- /examples/blinky/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Register an interrupt handler for TIMER0 overflow 5 | void isr_TF0(void) __interrupt(_INT_TF0) { 6 | static int i; 7 | if(i++ % 64 == 0) 8 | PA0 = !PA0; 9 | } 10 | 11 | int main(void) { 12 | // Configure pins 13 | PA0 = 1; // set PA0 to high 14 | OEA = 0b1; // set PA0 as output 15 | 16 | // Configure TIMER0 17 | TCON = _M0_0; // use 16-bit counter mode 18 | ET0 = 1; // generate an interrupt 19 | TR0 = 1; // run 20 | 21 | // Enable interrupts 22 | EA = 1; 23 | while(1); 24 | } 25 | -------------------------------------------------------------------------------- /firmware/library/bswap.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | uint16_t bswap16(uint16_t value) __naked { 4 | value; 5 | __asm; 6 | xch a, dpl ; 2c 7 | xch a, dph ; 2c 8 | xch a, dpl ; 2c 9 | #if !defined(__SDCC_MODEL_HUGE) 10 | ret ; 4c 11 | #else 12 | ljmp __sdcc_banked_ret 13 | #endif 14 | __endasm; 15 | } 16 | 17 | uint32_t bswap32(uint32_t value) __naked { 18 | value; 19 | __asm; 20 | xch a, dpl ; 2c 21 | xch a, b ; 2c 22 | xch a, dph ; 2c 23 | xch a, b ; 2c 24 | #if !defined(__SDCC_MODEL_HUGE) 25 | ret ; 4c 26 | #else 27 | ljmp __sdcc_banked_ret 28 | #endif 29 | __endasm; 30 | } 31 | -------------------------------------------------------------------------------- /docs/fx2usb_h.rst: -------------------------------------------------------------------------------- 1 | fx2usb.h 2 | ======== 3 | 4 | The ``fx2usb.h`` header contains USB support code for the Cypress FX2 series. When using this header, the ``fx2`` and ``fx2usb`` libraries must be linked in. 5 | 6 | Callback resolution 7 | ------------------- 8 | 9 | This header defines a number of callback functions, which requests the linker to locate a implementation for every of them. If you provide a callback explicitly, this callback will be used; if you do not, the linker will use the default one from the ``fx2usb`` library. Not all callbacks have default implementations. 10 | 11 | Reference 12 | --------- 13 | 14 | .. autodoxygenfile:: fx2usb.h 15 | -------------------------------------------------------------------------------- /LICENSE-0BSD.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 whitequark@whitequark.org 2 | 3 | Permission to use, copy, modify, and/or distribute this software for 4 | any purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 7 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 8 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 9 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 10 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 11 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 12 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 13 | 14 | -------------------------------------------------------------------------------- /examples/printf/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | // An example of how to push characters to a software UART on PA0. 7 | // As a test, the CLK speed is changed to check the code works for 8 | // a variety of frequencies. 9 | // 38400 likely works in all scenarios, YMMV with higher values. 10 | 11 | 12 | DEFINE_DEBUG_PUTCHAR_FN(PA0, 38400) 13 | 14 | int main(void) { 15 | OEA = (1U<<0); 16 | PA0 = 1; 17 | 18 | CPUCS = 0; 19 | printf("Hello, world at 12MHz CPU clock!\r\n"); 20 | 21 | CPUCS = _CLKSPD0; 22 | printf("Hello, world at 24MHz CPU clock!\r\n"); 23 | 24 | CPUCS = _CLKSPD1; 25 | printf("Hello, world at 48MHz CPU clock!\r\n"); 26 | 27 | while(1); 28 | } 29 | -------------------------------------------------------------------------------- /firmware/library/include/usbweb.h: -------------------------------------------------------------------------------- 1 | #ifndef USBWEB_H 2 | #define USBWEB_H 3 | 4 | #include 5 | 6 | // {3408b638-09a9-47a0-8bfd-a0768815b665} 7 | #define USB_PLATFORM_CAPABILITY_UUID_WEBUSB \ 8 | {0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65} 9 | 10 | struct usb_desc_platform_capability_webusb { 11 | uint8_t bLength; 12 | uint8_t bDescriptorType; 13 | uint8_t bDevCapabilityType; 14 | uint8_t bReserved; 15 | uint8_t PlatformCapablityUUID[16]; 16 | uint16_t bcdVersion; 17 | uint8_t bVendorCode; 18 | uint8_t iLandingPage; 19 | }; 20 | 21 | typedef __code const struct usb_desc_platform_capability_webusb 22 | usb_desc_platform_capability_webusb_c; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: {} 3 | pull_request: 4 | types: [opened, reopened, synchronize] 5 | name: CI 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out source code 11 | uses: actions/checkout@v3 12 | with: 13 | fetch-depth: 0 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: '3.9' 18 | - name: Install dependencies 19 | run: | 20 | sudo apt-get install sdcc 21 | - name: Build firmware 22 | run: | 23 | make -C firmware CFLAGS=--Werror 24 | - name: Build examples 25 | run: | 26 | make -C examples CFLAGS=--Werror 27 | - name: Build Python package 28 | run: | 29 | pip install ./software 30 | -------------------------------------------------------------------------------- /docs/prerequisites.rst: -------------------------------------------------------------------------------- 1 | Prerequisites 2 | ============= 3 | 4 | *libfx2* has the following dependencies: 5 | 6 | * GNU make (BSD make is not sufficient) and sdcc_ for building the firmware code, 7 | * `Python 3 `_ and `Python libusb1 wrapper `_ for interacting with the device. 8 | 9 | On a Debian system, these can be installed with: 10 | 11 | .. code-block:: sh 12 | 13 | apt-get install make sdcc python3 python3-libusb1 14 | 15 | Then, compile all firmware components: 16 | 17 | .. code-block:: sh 18 | 19 | make -C firmware 20 | 21 | Then, install the Python components to ``~/.local``: 22 | 23 | .. code-block:: sh 24 | 25 | python3 software/setup.py develop --user 26 | 27 | The last step is not required if you just want to upload example firmware. 28 | 29 | .. _sdcc: http://sdcc.sourceforge.net 30 | .. _python: https://www.python.org/ 31 | .. _python-libusb1: https://pypi.python.org/pypi/libusb1 32 | -------------------------------------------------------------------------------- /firmware/library/fx2conf.mk: -------------------------------------------------------------------------------- 1 | # This file contains only configuration for fx2 builds, no rules. 2 | # Use it if you need tight control over the build system. 3 | 4 | # -- Configuration start -- 5 | 6 | VID ?= 04B4 7 | PID ?= 8613 8 | 9 | MODEL ?= small 10 | CODE_SIZE ?= 0x3e00 11 | XRAM_SIZE ?= 0x0200 12 | CFLAGS ?= 13 | 14 | # -- Configuration end -- 15 | 16 | # Ensure we can access fx2ng without installation. 17 | PYTHONPATH = $(LIBFX2)/../../software 18 | export PYTHONPATH 19 | 20 | SDCCFLAGS = \ 21 | --iram-size 0x100 \ 22 | --code-size $(CODE_SIZE) \ 23 | --xram-loc $(CODE_SIZE) \ 24 | --xram-size $(XRAM_SIZE) \ 25 | --std-sdcc99 \ 26 | --model-$(MODEL) \ 27 | $(CFLAGS) \ 28 | -I$(LIBFX2)/include \ 29 | -L$(LIBFX2)/lib/$(MODEL) 30 | ifeq ($(V),1) 31 | SDCCFLAGS += -V 32 | endif 33 | 34 | SDCC = sdcc -mmcs51 $(SDCCFLAGS) 35 | SDAS = sdas8051 -plo 36 | FX2LOAD = python3 -m fx2.fx2tool -d $(VID):$(PID) load 37 | -------------------------------------------------------------------------------- /docs/host_library.rst: -------------------------------------------------------------------------------- 1 | Host-side library reference 2 | =========================== 3 | 4 | .. automodule:: fx2 5 | 6 | .. autoclass:: FX2Config 7 | 8 | .. automethod:: append 9 | .. automethod:: encode 10 | .. automethod:: decode 11 | 12 | .. autoclass:: FX2Device 13 | 14 | .. automethod:: control_read 15 | .. automethod:: control_write 16 | .. automethod:: bulk_read 17 | .. automethod:: bulk_write 18 | 19 | .. automethod:: read_ram 20 | .. automethod:: write_ram 21 | .. automethod:: load_ram 22 | .. automethod:: cpu_reset 23 | .. automethod:: read_boot_eeprom 24 | .. automethod:: write_boot_eeprom 25 | .. automethod:: reenumerate 26 | 27 | .. autoexception:: FX2DeviceError 28 | 29 | .. automodule:: fx2.format 30 | 31 | .. autofunction:: autodetect 32 | .. autofunction:: input_data 33 | .. autofunction:: output_data 34 | .. autofunction:: flatten_data 35 | .. autofunction:: diff_data 36 | -------------------------------------------------------------------------------- /docs/fx2regs_h.rst: -------------------------------------------------------------------------------- 1 | fx2regs.h 2 | ========= 3 | 4 | The ``fx2regs.h`` header contains register definitions for the Cypress FX2 series. 5 | 6 | Renamed registers 7 | ----------------- 8 | 9 | All definitions are (semi-)automatically generated from the datasheet information, and names match the datasheet, except where that would result in a conflict. 10 | 11 | The following definitions are changed: 12 | 13 | * in the ``TMOD`` register, bits corresponding to the TIMER0 and TIMER1 peripherals are suffixed with ``_0`` and ``_1``, 14 | * in the ``PORTECFG`` register, bit ``INT6`` is renamed to ``INT6``, 15 | * in the ``EPnGPIFPFSTOP`` registers, bit ``FIFOnFLAG`` is renamed to ``FIFOFLAG``, 16 | * in the ``GPIFTRIG`` and ``GPIFIDLECS`` registers, bit ``GPIFDONE`` is renamed to ``GPIFIDLE``. 17 | 18 | The following definitions are absent: 19 | 20 | * all ``EPn`` and ``PKTSn`` bit definitions. 21 | 22 | All bit definitions that are a part of a two's-complement number (e.g. ``An`` and ``Dn`` address and data bits) are also absent. 23 | 24 | Reference 25 | --------- 26 | 27 | .. autodoxygenfile:: fx2regs.h 28 | -------------------------------------------------------------------------------- /firmware/library/xmemclr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | __xdata void *xmemclr(__xdata void *dest, uint16_t length) { 6 | dest; 7 | length; 8 | __asm 9 | // Retrieve arguments. 10 | // _ASM_GET_PARM may use dptr, so save that first. 11 | mov r2, dpl 12 | mov r3, dph 13 | _ASM_GET_PARM2(r4, r5, _xmemclr_PARM_2) 14 | 15 | // Handle edge conditions. 16 | // Skip the entire function if r7:r6=0. 17 | // If r6<>0, increment r7, since we always decrement it first in the outer loop. 18 | // If r6=0, the inner loop underflows, which has the same effect. 19 | mov a, r4 20 | jz 00000$ 21 | inc r5 22 | 00000$: 23 | mov a, r5 24 | jz 00002$ 25 | 26 | // Set up autopointers. 27 | mov _AUTOPTRSETUP, #0b11 ; APTR1INC|APTREN 28 | mov _AUTOPTRL1, r2 29 | mov _AUTOPTRH1, r3 30 | mov dptr, #_XAUTODAT1 31 | mov a, #0 32 | 33 | // Clear. 34 | 00001$: 35 | movx @dptr, a ; 2c+s 36 | djnz r4, 00001$ ; 4c 37 | djnz r5, 00001$ ; 4c 38 | 39 | 00002$: 40 | __endasm; 41 | } 42 | -------------------------------------------------------------------------------- /software/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools~=67.0", "setuptools_scm[toml]>=6.2"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | dynamic = ["version"] 7 | 8 | name = "fx2" 9 | authors = [{name = "whitequark", email = "whitequark@whitequark.org"}] 10 | description = "A Python package for interacting with Cypress EZ-USB FX2 series chips" 11 | readme = "README.rst" 12 | license = {text = "BSD-0-clause"} 13 | classifiers = [ 14 | "Development Status :: 4 - Beta", 15 | "License :: OSI Approved", # " :: 0-clause BSD License", (not in PyPI) 16 | "Topic :: Software Development :: Embedded Systems", 17 | "Topic :: System :: Hardware", 18 | ] 19 | 20 | dependencies = [ 21 | "libusb1>=1.0" 22 | ] 23 | 24 | [project.urls] 25 | "Documentation" = "https://libfx2.readthedocs.io/" 26 | "Source Code" = "https://github.com/whitequark/libfx2" 27 | "Bug Tracker" = "https://github.com/whitequark/libfx2/issues" 28 | 29 | [project.scripts] 30 | fx2tool = "fx2.fx2tool:main" 31 | 32 | [tool.setuptools.package-data] 33 | fx2 = ["boot-cypress.ihex"] 34 | 35 | [tool.setuptools_scm] 36 | root = ".." 37 | local_scheme = "node-and-timestamp" 38 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | The `regtxt2c.py` script allows semi-automatic generation of register definition from the datasheet by parsing the text that results from copying the register summary table. The copied text is garbled when the Description field is multi-line and in some other cases, and has many typos, so it was manually adjusted and put into `regs.txt`. If there is an error, the `fx2regs.h` header can be regenerated with: 2 | 3 | python3 tools/regtxt2c.py firmware/library/include/fx2regs.h 4 | 5 | Aside from typos and general reformatting, the following adjustments were done manually in `regs.txt`: 6 | 7 | * added "#" prior to section names 8 | * removed "([NOT] bit addressable)" in descriptions 9 | * upper-cased all register and bit names and removed extra dashes 10 | * removed all high speed `EPnFIFOPFb` registers 11 | * in `TMOD`, added `_0` and `_1` suffixes for high and low nibble bits 12 | * in `PORTECFG`, renamed `INT6` to `INT6EX` 13 | * in `EPnGPIFPFSTOP`, renamed `FIFOnFLAG` to `FIFOFLAG` 14 | * in `EPIE` and `EPIRQ`, added `EPI_` prefixes for all bits 15 | * in `IBNIE` and `IBNIRQ`, added `IBNI_` prefixes for all bits 16 | -------------------------------------------------------------------------------- /firmware/library/include/fx2lib.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2LIB_H 2 | #define FX2LIB_H 3 | 4 | #include 5 | 6 | /** 7 | * A macro that returns the statically known size of the array. 8 | */ 9 | #define ARRAYSIZE(x) (sizeof(x)/sizeof((x)[0])) 10 | 11 | /** 12 | * 0.5KB of general purpose scratch RAM. 13 | */ 14 | __xdata __at(0xe000) uint8_t scratch[512]; 15 | 16 | /** 17 | * A fast memory copy routine that uses the FX2-specific architecture extensions. 18 | * This routine clobbers the value of all autopointer registers. 19 | * Both pointers should be in the ``__xdata`` address space. 20 | * Returns destination, like ``memcpy``. 21 | */ 22 | __xdata void *xmemcpy(__xdata void *dest, __xdata void *src, uint16_t length); 23 | 24 | /** 25 | * A fast memory clear routine that uses the FX2-specific architecture extensions. 26 | * This routine clobbers the value of all autopointer registers. 27 | * The pointer should be in the ``__xdata``. 28 | * Returns destination, like ``memclr``. 29 | */ 30 | __xdata void *xmemclr(__xdata void *dest, uint16_t length); 31 | 32 | /** 33 | * An endianness swap routine for 16-bit integers. 34 | */ 35 | uint16_t bswap16(uint16_t value); 36 | 37 | /** 38 | * An endianness swap routine for 32-bit integers. 39 | */ 40 | uint32_t bswap32(uint32_t value); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /firmware/library/include/bits/asmargs.h: -------------------------------------------------------------------------------- 1 | #ifndef _ASMARGS_H 2 | #define _ASMARGS_H 3 | 4 | #define _ASM_HASH # 5 | 6 | #define _ASM_REG(x) _ ## x 7 | 8 | #if defined(__SDCC_MODEL_SMALL) 9 | #define _ASM_GET_PARM1(rA, parm) \ 10 | mov rA, parm+0 11 | #elif defined(__SDCC_MODEL_MEDIUM) 12 | #define _ASM_GET_PARM1(rA, parm) \ 13 | mov r0, _ASM_HASH parm \ 14 | movx a, @r0 \ 15 | mov rA, a 16 | #elif defined(__SDCC_MODEL_LARGE) || defined(__SDCC_MODEL_HUGE) 17 | #define _ASM_GET_PARM1(rA, parm) \ 18 | mov dptr, _ASM_HASH parm \ 19 | movx a, @dptr \ 20 | mov rA, a 21 | #endif 22 | 23 | #if defined(__SDCC_MODEL_SMALL) 24 | #define _ASM_GET_PARM2(rA, rB, parm) \ 25 | mov rA, parm+0 \ 26 | mov rB, parm+1 27 | #elif defined(__SDCC_MODEL_MEDIUM) 28 | #define _ASM_GET_PARM2(rA, rB, parm) \ 29 | mov r0, _ASM_HASH parm \ 30 | movx a, @r0 \ 31 | mov rA, a \ 32 | inc r0 \ 33 | movx a, @r0 \ 34 | mov rB, a 35 | #elif defined(__SDCC_MODEL_LARGE) || defined(__SDCC_MODEL_HUGE) 36 | #define _ASM_GET_PARM2(rA, rB, parm) \ 37 | mov dptr, _ASM_HASH parm \ 38 | movx a, @dptr \ 39 | mov rA, a \ 40 | inc dptr \ 41 | movx a, @dptr \ 42 | mov rB, a 43 | #endif 44 | 45 | #if !defined(__SDCC_MODEL_HUGE) 46 | #define _ASM_RET \ 47 | ret 48 | #else 49 | #define _ASM_RET \ 50 | ljmp __sdcc_banked_ret 51 | #endif 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /firmware/library/include/usbmicrosoft.h: -------------------------------------------------------------------------------- 1 | #ifndef USBMICROSOFT_H 2 | #define USBMICROSOFT_H 3 | 4 | #include 5 | 6 | #define USB_DESC_MICROSOFT_V10_SIGNATURE \ 7 | { 0x4D, 0x00, 0x53, 0x00, 0x46, 0x00, 0x54, 0x00, 0x31, 0x00, 0x30, 0x00, 0x30, 0x00 } 8 | 9 | struct usb_desc_microsoft_v10 { 10 | uint8_t bLength; 11 | uint8_t bDescriptorType; 12 | uint8_t qwSignature[14]; 13 | uint8_t bMS_VendorCode; 14 | uint8_t bPad; 15 | }; 16 | 17 | typedef __code const struct usb_desc_microsoft_v10 18 | usb_desc_microsoft_v10_c; 19 | 20 | enum usb_descriptor_microsoft { 21 | USB_DESC_MS_EXTENDED_COMPAT_ID = 0x04, 22 | USB_DESC_MS_EXTENDED_PROPERTIES = 0x05, 23 | }; 24 | 25 | struct usb_desc_ms_compat_function { 26 | uint8_t bFirstInterfaceNumber; 27 | uint8_t bReserved1; 28 | uint8_t compatibleID[8]; 29 | uint8_t subCompatibleID[8]; 30 | uint8_t bReserved[6]; 31 | }; 32 | 33 | struct usb_desc_ms_ext_compat_id { 34 | uint32_t dwLength; 35 | uint16_t bcdVersion; 36 | uint16_t wIndex; 37 | uint8_t bCount; 38 | uint8_t bReserved[7]; 39 | struct usb_desc_ms_compat_function functions[]; 40 | }; 41 | 42 | typedef __code const struct usb_desc_ms_ext_compat_id 43 | usb_desc_ms_ext_compat_id_c; 44 | 45 | struct usb_desc_ms_ext_property { 46 | uint32_t dwLength; 47 | uint16_t bcdVersion; 48 | uint16_t wIndex; 49 | uint16_t wCount; 50 | }; 51 | 52 | typedef __code const struct usb_desc_ms_ext_property 53 | usb_desc_ms_ext_property_c; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /firmware/library/include/fat.h: -------------------------------------------------------------------------------- 1 | #ifndef FAT_H 2 | #define FAT_H 3 | 4 | #include 5 | 6 | struct fat16_boot_sector { 7 | uint8_t jump_to_bootstrap[3]; 8 | uint8_t oem_name_version[8]; 9 | uint16_t bytes_per_sector; 10 | uint8_t sectors_per_cluster; 11 | uint16_t reserved_sectors; 12 | uint8_t fat_copies; 13 | uint16_t root_entries; 14 | uint16_t _reserved0; 15 | uint8_t media_descriptor; 16 | uint16_t sectors_per_fat; 17 | uint16_t sectors_per_track; 18 | uint16_t heads; 19 | uint32_t hidden_sectors; 20 | uint32_t total_sectors; 21 | uint8_t logical_drive_number; 22 | uint8_t dirty; 23 | uint8_t extended_signature; 24 | uint32_t serial_number; 25 | uint8_t volume_label[11]; 26 | uint8_t filesystem_type[8]; 27 | uint8_t bootstrap[448]; 28 | uint8_t signature[2]; 29 | }; 30 | 31 | #define FAT_TIME(h, m, s) (((h) << 11)|((m) << 5)|((s >> 1))) 32 | #define FAT_DATE(y, m, d) ((((uint16_t)(y) - 1980) << 9)|((m) << 5)|(d)) 33 | 34 | struct fat_timestamp { 35 | uint16_t time; 36 | uint16_t date; 37 | }; 38 | 39 | struct fat_directory_entry { 40 | uint8_t filename_ext[11]; 41 | uint8_t read_only:1; 42 | uint8_t hidden:1; 43 | uint8_t system:1; 44 | uint8_t volume_label:1; 45 | uint8_t subdirectory:1; 46 | uint8_t archive:1; 47 | uint8_t _reserved0:2; 48 | uint8_t _reserved1[2]; 49 | struct fat_timestamp create; 50 | uint8_t _reserved2[4]; 51 | struct fat_timestamp modify; 52 | uint16_t first_cluster; 53 | uint32_t size; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /firmware/library/fx2rules.mk: -------------------------------------------------------------------------------- 1 | # This file includes the fx2conf.mk configuration for fx2builds, 2 | # and contains rules that should suffice for most firmware. 3 | # It implements out-of-tree builds and dependency tracking for C, 4 | # so that rebuilds work correctly after modifying headers. 5 | 6 | # Configuration start 7 | 8 | # See the configuration in fx2conf.mk as well. 9 | include $(LIBFX2)/fx2conf.mk 10 | 11 | # Name of our build product. Build will create $(FIRMWARE).ihex. 12 | TARGET ?= firmware 13 | 14 | # List of C or assembly sources that comprise our firmware. 15 | # The extension is determined automatically. 16 | SOURCES ?= main 17 | 18 | # List of standard libraries to be included in the firmware. 19 | LIBRARIES ?= fx2isrs 20 | 21 | # Configuration end 22 | 23 | OBJECTS = \ 24 | $(patsubst %,build/%.rel,$(SOURCES)) \ 25 | $(patsubst %,$(LIBFX2)/lib/$(MODEL)/%.lib,$(LIBRARIES)) 26 | 27 | all: $(TARGET).ihex 28 | 29 | $(TARGET).ihex: $(OBJECTS) $(LIBFX2)/.stamp 30 | $(SDCC) -o build/$@ $(OBJECTS) 31 | @cp build/$@ $@ 32 | 33 | $(LIBFX2)/.stamp: $(wildcard $(LIBFX2)/*.c $(LIBFX2)/*.asm $(LIBFX2)/include/*.h) 34 | $(MAKE) -C $(LIBFX2) 35 | 36 | -include build/*.d 37 | build/%.rel: %.c 38 | @mkdir -p $(dir $@) 39 | $(SDCC) -MQ $@ -MMD -o build/$*.d $< 40 | $(SDCC) -c -o $@ $< 41 | 42 | build/%.rel: %.asm 43 | @mkdir -p $(dir $@) 44 | $(SDAS) $@ $< 45 | 46 | clean: 47 | @rm -rf build/ $(TARGET).ihex 48 | 49 | load: $(TARGET).ihex 50 | $(FX2LOAD) $< 51 | 52 | .PHONY: all clean load 53 | 54 | .SUFFIXES: 55 | MAKEFLAGS += -r 56 | -------------------------------------------------------------------------------- /firmware/library/include/fx2eeprom.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2EEPROM_H 2 | #define FX2EEPROM_H 3 | 4 | #include 5 | #include 6 | 7 | #if !defined(__SDCC_MODEL_HUGE) 8 | #pragma callee_saves eeprom_read 9 | #pragma callee_saves eeprom_write 10 | #endif 11 | 12 | /** 13 | * This function reads `len` bytes at memory address `addr` from EEPROM chip 14 | * with bus address `chip` to `buf`. It writes two address bytes if `double_byte` is true. 15 | * 16 | * Returns `true` if the read is successful, `false` otherwise. 17 | */ 18 | bool eeprom_read(uint8_t chip, uint16_t addr, uint8_t *buf, uint16_t len, bool double_byte); 19 | 20 | /** 21 | * This function writes `len` bytes at memory address `addr` to EEPROM chip 22 | * with bus address `chip` from `buf`. It writes two address bytes if `double_byte` is true. 23 | * 24 | * Data is written in chunks up to `2 ** page_size` bytes, with each chunk ending on an address 25 | * that is a multiple of `2 ** page_size`. Use the page size specified in the EEPROM datasheet 26 | * for significantly higher write performance. 27 | * 28 | * `timeout` specifies the number of polling attempts after a write; 0 means wait forever. 29 | * At 100 kHz, one polling attempt is ~120 us, at 400 kHz, ~30 us, so for 30 | * typical wait cycles of up to 5 ms, a timeout of 166 should be sufficient in all cases. 31 | * 32 | * Returns `true` if the write is successful, `false` otherwise. 33 | */ 34 | bool eeprom_write(uint8_t chip, uint16_t addr, uint8_t *buf, uint16_t len, bool double_byte, 35 | uint8_t page_size, uint8_t timeout); 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /firmware/library/include/fx2i2c.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2I2C_H 2 | #define FX2I2C_H 3 | 4 | #if !defined(__SDCC_MODEL_HUGE) 5 | #pragma callee_saves i2c_wait 6 | #pragma callee_saves i2c_start 7 | #pragma callee_saves i2c_stop 8 | #pragma callee_saves i2c_write 9 | #pragma callee_saves i2c_read 10 | #endif 11 | 12 | #include 13 | #include 14 | 15 | /** 16 | * A flag that terminates the current I2C transfer. 17 | */ 18 | extern volatile bool i2c_cancel; 19 | 20 | /** 21 | * This function waits until the current I2C transfer terminates, 22 | * or until `i2c_cancel` is set. In the latter case, `i2c_cancel` is cleared. 23 | * Returns `false` in case of bus contention, or if `need_ack` was set 24 | * and the transfer was not acknowledged, `true` otherwise. 25 | * The `BERR` bit in the `I2CS` register should be checked if `false` 26 | * is returned. 27 | * 28 | * Note that `i2c_wait` is not meant to be used directly; it is documented 29 | * to explain the behavior of `i2c_start`, `i2c_write` and `i2c_read`. 30 | */ 31 | bool i2c_wait(bool need_ack); 32 | 33 | /** 34 | * This function generates a start condition and writes the address `chip` 35 | * to the bus. See `i2c_wait` for description of return value. 36 | */ 37 | bool i2c_start(uint8_t chip); 38 | 39 | /** 40 | * This function generates a stop condition. 41 | */ 42 | bool i2c_stop(void); 43 | 44 | /** 45 | * This function writes `len` bytes from `buf` to the I2C bus. 46 | * See `i2c_wait` for description of return value. 47 | */ 48 | bool i2c_write(const uint8_t *buf, uint16_t len); 49 | 50 | /** 51 | * This function reads `len` bytes to `buf` from the I2C bus, responds with NAK 52 | * to the last byte read, and generates a stop condition. 53 | * See `i2c_wait` for description of return value. 54 | */ 55 | bool i2c_read(uint8_t *buf, uint16_t len); 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /docs/fx2ints_h.rst: -------------------------------------------------------------------------------- 1 | fx2ints.h 2 | ========= 3 | 4 | The ``fx2ints.h`` header contains interrupt handler declarations for the Cypress FX2 series. 5 | 6 | Interrupt handler resolution 7 | ---------------------------- 8 | 9 | By including the ``fx2ints.h`` header, which declares every interrupt handler, your firmware requests the linker to locate a handler for every interrupt. If you provide a handler explicitly, this handler will be used; if you do not, the linker will go through the list of libraries, looking for the first one that includes a handler. The library ``fx2isrs.lib`` provides a default empty handler for every interrupt (as well as autovectoring jump tables) and should be linked last into every firmware. 10 | 11 | Core interrupt handlers 12 | ----------------------- 13 | 14 | To define a core interrupt handler, override the corresponding ``isr_`` function and provide the interrupt number in the ``__interrupt`` attribute, e.g. for the ``TF0`` interrupt: 15 | 16 | .. code-block:: c 17 | 18 | void isr_TF0(void) __interrupt(_INT_TF0) { 19 | // TIMER0 has overflowed 20 | } 21 | 22 | Interrupts with flags in the ``EXIF`` register need to be reset manually: 23 | 24 | .. code-block:: c 25 | 26 | void isr_I2C(void) __interrupt(_INT_I2C) { 27 | // I2C is done or errored 28 | CLEAR_I2C_IRQ(); 29 | } 30 | 31 | Autovectored interrupt handlers 32 | ------------------------------- 33 | 34 | To define an autovectored interrupt handler, override the corresponding ``isr_`` function and provide the ``__interrupt`` attribute without a number, e.g. for the ``SOF`` interrupt: 35 | 36 | .. code-block:: c 37 | 38 | void isr_SOF(void) __interrupt { 39 | // Start of Frame packet has been received 40 | CLEAR_USB_IRQ(); 41 | USBIRQ = _SOF; 42 | } 43 | 44 | .. note:: 45 | The order of clearing of the IRQ flags is important. 46 | 47 | Reference 48 | --------- 49 | 50 | .. autodoxygenfile:: fx2ints.h 51 | -------------------------------------------------------------------------------- /firmware/library/eeprom.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool eeprom_read(uint8_t chip, uint16_t addr, uint8_t *buf, uint16_t len, bool double_byte) { 5 | uint8_t addr_bytes[2]; 6 | 7 | if(double_byte) { 8 | addr_bytes[0] = addr >> 8; 9 | addr_bytes[1] = addr & 0xff; 10 | } else { 11 | addr_bytes[0] = addr; 12 | } 13 | 14 | if(!i2c_start(chip << 1)) 15 | goto stop; 16 | if(!i2c_write(addr_bytes, 1 + double_byte)) 17 | goto stop; 18 | if(!i2c_start((chip << 1) | 1)) 19 | goto stop; 20 | if(!i2c_read(buf, len)) 21 | return false; 22 | return true; 23 | 24 | stop: 25 | i2c_stop(); 26 | return false; 27 | } 28 | 29 | bool eeprom_write(uint8_t chip, uint16_t addr, uint8_t *buf, uint16_t len, bool double_byte, 30 | uint8_t page_size, uint8_t timeout) { 31 | uint16_t written = 0; 32 | 33 | if(!i2c_start(chip << 1)) 34 | goto stop; 35 | 36 | while(written < len) { 37 | uint8_t addr_bytes[2]; 38 | uint16_t chunk_len; 39 | uint8_t attempt; 40 | 41 | if(double_byte) { 42 | addr_bytes[0] = addr >> 8; 43 | addr_bytes[1] = addr & 0xff; 44 | } else { 45 | addr_bytes[0] = addr; 46 | } 47 | if(!i2c_write(addr_bytes, 1 + double_byte)) 48 | goto stop; 49 | 50 | chunk_len = (1 << page_size) - addr % (1 << page_size); 51 | if(chunk_len > len - written) 52 | chunk_len = len - written; 53 | if(!i2c_write(&buf[written], chunk_len)) 54 | goto stop; 55 | 56 | // Stop condition (not repeated start!) is required to start the write cycle. 57 | if(!i2c_stop()) 58 | return false; 59 | 60 | for(attempt = 0; timeout == 0 || attempt < timeout; attempt++) { 61 | if(i2c_start(chip << 1)) 62 | break; 63 | } 64 | if(attempt == timeout) 65 | return false; 66 | 67 | addr += chunk_len; 68 | written += chunk_len; 69 | } 70 | 71 | stop: 72 | i2c_stop(); 73 | return written == len; 74 | } 75 | -------------------------------------------------------------------------------- /firmware/library/xmemcpy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | __xdata void *xmemcpy(__xdata void *dest, __xdata void *src, uint16_t length) { 6 | dest; 7 | src; 8 | length; 9 | __asm 10 | // Retrieve arguments. 11 | // _ASM_GET_PARM may use dptr, so save that first. 12 | mov r2, dpl 13 | mov r3, dph 14 | _ASM_GET_PARM2(r4, r5, _xmemcpy_PARM_2) 15 | _ASM_GET_PARM2(r6, r7, _xmemcpy_PARM_3) 16 | 17 | // Handle edge conditions. 18 | // Skip the entire function if r7:r6=0. 19 | // If r6<>0, increment r7, since we always decrement it first in the outer loop. 20 | // If r6=0, the inner loop underflows, which has the same effect. 21 | mov a, r6 22 | jz 00000$ 23 | inc r7 24 | 00000$: 25 | mov a, r7 26 | jz 00002$ 27 | 28 | // Set up autopointers. 29 | mov _AUTOPTRSETUP, #0b111 ; ATPTR2INC|APTR1INC|APTREN 30 | mov _AUTOPTRL1, r2 31 | mov _AUTOPTRH1, r3 32 | mov _AUTOPTRL2, r4 33 | mov _AUTOPTRH2, r5 34 | 35 | // Copy. 36 | 00001$: 37 | // We could save 2c per iteration by using `inc _DPS` instead of explicitly loading dptr, 38 | // but unfortunately, ISRs save dpl/dph which maps to _DPL0/_DPH0, but at the same time 39 | // use `mov dptr` which maps to the data pointer indexed by _DPS. We cannot fix this 40 | // without either (a) disabling interrupts within xmemcpy, increasing latency, or 41 | // (b) requiring all interrupts to carefully insert custom prologue/epilogue code, 42 | // both of which are undesirable. So, we just eat the increased cost. (It's quite a bit 43 | // faster than the naive memcpy, anyway.) 44 | mov dptr, #_XAUTODAT2 ; 3c 45 | movx a, @dptr ; 2c+s 46 | mov dptr, #_XAUTODAT1 ; 3c 47 | movx @dptr, a ; 2c+s 48 | djnz r6, 00001$ ; 4c 49 | djnz r7, 00001$ ; 4c 50 | 51 | 00002$: 52 | __endasm; 53 | } 54 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os 5 | import sphinx_rtd_theme 6 | from mock import Mock as MagicMock 7 | 8 | # Configure our load path 9 | sys.path.insert(0, os.path.abspath('../software')) 10 | 11 | # Mock out C dependencies for readthedocs 12 | class Mock(MagicMock): 13 | @classmethod 14 | def __getattr__(cls, name): 15 | return MagicMock() 16 | 17 | MOCK_MODULES = ['usb1'] 18 | sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) 19 | 20 | # Configure Breathe and doxygen 21 | breathe_projects_source = { 22 | 'libfx2': ( 23 | '../firmware/library/include', [ 24 | 'usb.h', 'usbmicrosoft.h', 'usbdfu.h', 'usbcdc.h', 'usbmassstor.h', 25 | 'fx2regs.h', 'fx2ints.h', 'fx2lib.h', 26 | 'fx2delay.h', 'fx2i2c.h', 'fx2eeprom.h', 'fx2spi.h', 'fx2spiflash.h', 'fx2debug.h', 27 | 'fx2usb.h', 'fx2usbdfu.h', 'fx2usbmassstor.h', 'fx2uf2.h', 28 | ] 29 | ) 30 | } 31 | breathe_default_project = 'libfx2' 32 | breathe_doxygen_config_options = { 33 | 'EXTRACT_ALL': 'YES', 34 | 'OPTIMIZE_OUTPUT_FOR_C': 'YES', 35 | 'SORT_MEMBER_DOCS': 'NO', 36 | 'ENABLE_PREPROCESSING': 'YES', 37 | 'MACRO_EXPANSION': 'YES', 38 | 'EXPAND_ONLY_PREDEF': 'YES', 39 | 'PREDEFINED': " ".join([ 40 | 'DOXYGEN', 41 | '_SFR(addr)="volatile sfr8_t"', 42 | '_SFR16(addr)="volatile sfr16_t"', 43 | '_SBIT(addr)="volatile sbit_t"', 44 | '_IOR(addr)="volatile ior8_t"', 45 | '_IOR16(addr)="volatile ior16_t"', 46 | '__sfr="volatile sfr8_t"', 47 | '__at(x)=', 48 | '__idata=', 49 | '__pdata=', 50 | '__xdata=', 51 | '__code=', 52 | '__reentrant=', 53 | ]) 54 | } 55 | 56 | # Configure Sphinx 57 | extensions = ['sphinx.ext.autodoc', 'sphinxarg.ext', 'sphinx.ext.viewcode', 'breathe'] 58 | autodoc_member_order = 'bysource' 59 | source_suffix = '.rst' 60 | master_doc = 'index' 61 | project = 'libfx2 Reference' 62 | author = 'whitequark' 63 | copyright = '2018-2019, whitequark' 64 | pygments_style = 'sphinx' 65 | html_theme = 'sphinx_rtd_theme' 66 | -------------------------------------------------------------------------------- /software/deploy-bootloader.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script is provided to ensure builds of firmware included in the repository are byte for byte 4 | # reproducible regardless of the host OS or the packages installed. 5 | # 6 | # Using this script is not a requirement to work on the firmware; installation instructions for 7 | # the prerequisites for a selection of operating systems are included in the documentation. 8 | 9 | BASE_IMAGE=debian:trixie-20250721-slim@sha256:cc92da07b99dd5c078cb5583fdb4ba639c7c9c14eb78508a2be285ca67cc738a 10 | 11 | if [ -z "${DOCKER}" ]; then 12 | exec docker run \ 13 | --volume $(dirname $(dirname $(readlink -f $0))):/glasgow \ 14 | --workdir /glasgow \ 15 | --env DOCKER=1 \ 16 | --env UID=$(id -u) \ 17 | --env GID=$(id -g) \ 18 | --rm ${BASE_IMAGE} \ 19 | software/deploy-bootloader.sh 20 | fi 21 | 22 | set -ex 23 | 24 | # Install dependencies. 25 | apt-get update -qq 26 | apt-get install -qq --no-install-recommends git make sdcc python3 python3-usb1 27 | 28 | # Any commands that create new files in the host mount must be invoked with the caller UID/GID, or 29 | # else the created files will be owned by root. We can't use `docker run --user` because then 30 | # apt-get would not be able to install packages. 31 | # 32 | # Create a user and a group with the UID/GID of the caller. 33 | groupadd --gid ${GID} caller || true 34 | useradd --uid ${UID} --gid ${GID} caller 35 | 36 | # Do the work. 37 | su caller - < 5 | 6 | enum { 7 | USB_IFACE_CLASS_MASS_STORAGE = 0x08, 8 | 9 | USB_IFACE_SUBCLASS_MASS_STORAGE_RBC = 0x01, 10 | USB_IFACE_SUBCLASS_MASS_STORAGE_ATAPI = 0x02, 11 | USB_IFACE_SUBCLASS_MASS_STORAGE_UFI = 0x04, 12 | USB_IFACE_SUBCLASS_MASS_STORAGE_SCSI = 0x06, 13 | USB_IFACE_SUBCLASS_MASS_STORAGE_LSD_FS = 0x07, 14 | USB_IFACE_SUBCLASS_MASS_STORAGE_IEEE_1667 = 0x08, 15 | 16 | USB_IFACE_PROTOCOL_MASS_STORAGE_CBI_COMPL = 0x00, 17 | USB_IFACE_PROTOCOL_MASS_STORAGE_CBI = 0x01, 18 | USB_IFACE_PROTOCOL_MASS_STORAGE_BBB = 0x50, 19 | USB_IFACE_PROTOCOL_MASS_STORAGE_UAS = 0x62, 20 | }; 21 | 22 | enum usb_mass_storage_request { 23 | USB_REQ_MASS_STORAGE_ADSC = 0x00, 24 | USB_REQ_MASS_STORAGE_GET_REQUESTS = 0xfc, 25 | USB_REQ_MASS_STORAGE_PUT_REQUESTS = 0xfd, 26 | USB_REQ_MASS_STORAGE_GET_MAX_LUN = 0xfe, 27 | USB_REQ_MASS_STORAGE_BOMSR = 0xff, 28 | }; 29 | 30 | enum { 31 | USB_MASS_STORAGE_CBW_SIGNATURE = 0x43425355, 32 | }; 33 | 34 | enum usb_mass_storage_cbw_flags { 35 | USB_MASS_STORAGE_CBW_FLAG_DATA_IN = 0b10000000, 36 | USB_MASS_STORAGE_CBW_RESERVED_FLAGS = 0b01111111, 37 | }; 38 | 39 | struct usb_mass_storage_cbw { 40 | uint32_t dCBWSignature; 41 | uint32_t dCBWTag; 42 | uint32_t dCBWDataTransferLength; 43 | uint8_t bmCBWFlags; 44 | uint8_t bCBWLUN; 45 | uint8_t bCBWCBLength; 46 | uint8_t CBWCB[16]; 47 | }; 48 | 49 | typedef __xdata struct usb_mass_storage_cbw 50 | usb_mass_storage_cbw_t; 51 | 52 | enum { 53 | USB_MASS_STORAGE_CSW_SIGNATURE = 0x53425355, 54 | }; 55 | 56 | enum usb_mass_storage_csw_status { 57 | USB_MASS_STORAGE_CSW_STATUS_PASSED = 0x00, 58 | USB_MASS_STORAGE_CSW_STATUS_FAILED = 0x01, 59 | USB_MASS_STORAGE_CSW_STATUS_PHASE_ERROR = 0x02, 60 | }; 61 | 62 | struct usb_mass_storage_csw { 63 | uint32_t dCSWSignature; 64 | uint32_t dCSWTag; 65 | uint32_t dCSWDataResidue; 66 | uint8_t bCSWStatus; 67 | }; 68 | 69 | typedef __xdata struct usb_mass_storage_csw 70 | usb_mass_storage_csw_t; 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /firmware/library/Makefile: -------------------------------------------------------------------------------- 1 | SDCC = sdcc -mmcs51 --std-sdcc99 -Iinclude $(CFLAGS) 2 | SDAS = sdas8051 -plo 3 | SDAR ?= sdar rc 4 | 5 | # core interrupts 6 | DEFISRS = \ 7 | IE0 TF0 IE1 TF1 RI_TI_0 TF2 RESUME RI_TI_1 I2C IE5 IE6 8 | # USB autovectored interrupts 9 | DEFAUTOISRS += \ 10 | SUDAV SOF SUTOK SUSPEND USBRESET HISPEED \ 11 | EP0ACK EP0IN EP0OUT EP1IN EP1OUT EP2 EP4 EP6 EP8 \ 12 | IBN EP0PING EP1PING EP2PING EP4PING EP6PING EP8PING \ 13 | ERRLIMIT EP2ISOERR EP4ISOERR EP6ISOERR EP8ISOERR 14 | # GPIF autovectored interrupts 15 | DEFAUTOISRS += \ 16 | EP2PF EP4PF EP6PF EP8PF \ 17 | EP2EF EP4EF EP6EF EP8EF \ 18 | EP2FF EP4FF EP6FF EP8FF GPIFDONE GPIFWF 19 | 20 | MODELS = small medium large huge 21 | 22 | OBJECTS_fx2 = xmemcpy.rel xmemclr.rel bswap.rel delay.rel syncdelay.rel i2c.rel eeprom.rel 23 | 24 | OBJECTS_fx2isrs = autovec.rel \ 25 | $(patsubst %,defisr_%.rel,$(DEFISRS)) \ 26 | $(patsubst %,defautoisr_%.rel,$(DEFAUTOISRS)) 27 | 28 | OBJECTS_fx2usb = usb.rel \ 29 | defusbdesc.rel \ 30 | defusbsetup.rel \ 31 | defusbsetconfig.rel defusbgetconfig.rel \ 32 | defusbsetiface.rel defusbgetiface.rel \ 33 | defusbhalt.rel \ 34 | 35 | OBJECTS_fx2usbmassstor = usbmassstor.rel 36 | 37 | OBJECTS_fx2dfu = usbdfu.rel 38 | 39 | OBJECTS_fx2uf2 = uf2scsi.rel uf2fat.rel 40 | 41 | LIBRARIES = fx2 fx2isrs fx2usb fx2usbmassstor fx2dfu fx2uf2 42 | 43 | all:: 44 | @touch .stamp 45 | 46 | define make-library 47 | lib/$1/$2.lib: $(addprefix build/$1/,$(OBJECTS_$2)) 48 | @mkdir -p $$(dir $$@) 49 | $(SDAR) $$@ $$^ 50 | endef 51 | 52 | define make-model 53 | all:: $(addprefix lib/$1/,$(addsuffix .lib,$(LIBRARIES))) 54 | 55 | $(foreach library,$(LIBRARIES),$(eval $(call make-library,$1,$(library)))) 56 | 57 | build/$1/%.rel: %.c 58 | @mkdir -p $$(dir $$@) 59 | $(SDCC) --model-$1 -c -o $$@ $$< 60 | 61 | build/$1/%.rel: %.asm 62 | @mkdir -p $$(dir $$@) 63 | $(SDAS) $$@ $$< 64 | 65 | build/$1/defisr_%.rel: defisr.c 66 | $(SDCC) --model-$1 -c -o $$@ $$< -DISRNAME=isr_$$* -DINTNAME=_INT_$$* 67 | 68 | build/$1/defautoisr_%.rel: defautoisr.c 69 | $(SDCC) --model-$1 -c -o $$@ $$< -DISRNAME=isr_$$* 70 | endef 71 | 72 | $(foreach model,$(MODELS),$(eval $(call make-model,$(model)))) 73 | 74 | clean: 75 | @rm -rf build/ lib/ .stamp *.lib 76 | 77 | .PHONY: all clean 78 | 79 | .SUFFIXES: 80 | MAKEFLAGS += -r 81 | -------------------------------------------------------------------------------- /firmware/library/include/fx2uf2.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2UF2_H 2 | #define FX2UF2_H 3 | 4 | #include 5 | #include 6 | 7 | /// Configuration of USB UF2 interface. 8 | struct uf2_configuration { 9 | /// Total number of sectors on the virtual USB Mass Storage device. The optimal number depends 10 | /// on the size of the firmware, but in general, 65536 sectors (32 MiB) is a reasonable size. 11 | uint32_t total_sectors; 12 | 13 | /// Contents of the INFO_UF2.TXT file on the virtual USB Mass Storage device. Should be at 14 | /// most one sector (512 bytes) long. 15 | __code const char *info_uf2_txt; 16 | 17 | /// Contents of the INDEX.HTM file on the virtual USB Mass Storage device. Should be at 18 | /// most one sector (512 bytes) long. 19 | __code const char *index_htm; 20 | 21 | /// Size of the firmware. Currently rounded up to the nearest UF2 payload size, 256, 22 | /// but this should not be relied on. 23 | uint32_t firmware_size; 24 | 25 | /// Firmware read function. This function should accept any ``address`` and ``length`` 26 | /// arguments that read bytes between ``0`` and ``firmware_size`` (after rounding). 27 | /// It should return ``true`` if the firmware could be read, and ``false`` otherwise. 28 | bool (*firmware_read )(uint32_t address, __xdata uint8_t *data, uint16_t length) __reentrant; 29 | 30 | /// Firmware write function. This function is passed the UF2 blocks directly without 31 | /// further verification of ``address`` and ``length`` against ``firmware_size``. 32 | /// It should return ``true`` if the firmware could be written, and ``false`` otherwise. 33 | bool (*firmware_write)(uint32_t address, __xdata uint8_t *data, uint16_t length) __reentrant; 34 | }; 35 | 36 | typedef __code const struct uf2_configuration 37 | uf2_configuration_c; 38 | 39 | #ifndef DOXYGEN 40 | bool uf2_scsi_command (uint8_t lun, __xdata uint8_t *command, uint8_t length) __reentrant; 41 | bool uf2_scsi_data_out(uint8_t lun, __xdata const uint8_t *data, uint16_t length) __reentrant; 42 | bool uf2_scsi_data_in (uint8_t lun, __xdata uint8_t *data, uint16_t length) __reentrant; 43 | 44 | bool uf2_fat_read (uint32_t lba, __xdata uint8_t *data); 45 | bool uf2_fat_write(uint32_t lba, __xdata const uint8_t *data); 46 | #endif 47 | 48 | /** 49 | * The global UF2 configuration, defined in the application code. 50 | * It only makes sense for a single device to expose a single UF2 interface at a time. 51 | */ 52 | extern uf2_configuration_c uf2_config; 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /firmware/library/i2c.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | volatile bool i2c_cancel; 6 | 7 | bool i2c_wait(bool need_ack) { 8 | while(!(I2CS & _DONE)) { 9 | if(i2c_cancel) { 10 | i2c_cancel = false; 11 | return false; 12 | } 13 | } 14 | 15 | if(I2CS & _BERR) 16 | return false; 17 | if(need_ack) 18 | return I2CS & _ACK; 19 | return true; 20 | } 21 | 22 | bool i2c_start(uint8_t chip) { 23 | I2CS = _START; 24 | I2DAT = chip; 25 | return i2c_wait(/*need_ack=*/true); 26 | } 27 | 28 | bool i2c_stop(void) { 29 | if(I2CS & _BERR) 30 | return false; 31 | 32 | I2CS = _STOP; 33 | while(I2CS & _STOP); 34 | 35 | return true; 36 | } 37 | 38 | bool i2c_write(const uint8_t *buf, uint16_t len) { 39 | uint16_t i = 0; 40 | 41 | if(len == 0) 42 | return true; 43 | 44 | for(i = 0; i != len; i++) { 45 | I2DAT = buf[i]; 46 | if(!i2c_wait(/*need_ack=*/true)) 47 | goto end; 48 | } 49 | 50 | end: 51 | return i == len; 52 | } 53 | 54 | bool i2c_read(uint8_t *buf, uint16_t len) { 55 | uint16_t i = 0; 56 | 57 | if(len == 0) 58 | return true; 59 | 60 | if(len == 1) 61 | I2CS = _LASTRD; 62 | I2DAT; // prime the transfer 63 | 64 | for(i = 0; i != len; i++) { 65 | if(!i2c_wait(/*need_ack=*/false)) 66 | goto end; 67 | if(i + 2 == len) 68 | I2CS = _LASTRD; 69 | if(i + 1 == len) { 70 | // TRM 13.5.4.14: Read the final byte from I2DAT immediately (the next 71 | // instruction) after setting the STOP bit. 72 | // This is quite tricky to actually achieve; we use the autopointers 73 | // and the fact that I2DAT immediately follows I2CS. 74 | // Alternatively, MPAGE could be used. 75 | uint8_t tmp; 76 | __asm 77 | mov _AUTOPTRSETUP, #0b11 ; APTR1INC|APTREN 78 | mov _AUTOPTRH1, #(_I2CS >> 8) 79 | mov _AUTOPTRL1, #(_I2CS & 0xff) 80 | mov dptr, #_XAUTODAT1 81 | mov a, #0b01000000 ; STOP 82 | movx @dptr, a 83 | movx a, @dptr 84 | __endasm; 85 | // This ensures that ACC won't get overwritten by the compiler before 86 | // it has a chance to get saved to buf[i]. 87 | tmp = ACC; 88 | buf[i] = tmp; 89 | } else { 90 | buf[i] = I2DAT; 91 | } 92 | } 93 | 94 | if(I2CS & _BERR) 95 | return false; 96 | 97 | while(I2CS & _STOP); 98 | 99 | end: 100 | return i == len; 101 | } 102 | -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | =============== 3 | 4 | Read configuration EEPROM: 5 | 6 | .. code-block:: sh 7 | 8 | fx2tool -S firmware/bootloader/bootloader.ihex read_eeprom 0 7 9 | 10 | Load the Blinky example (a LED should be attached to PA0): 11 | 12 | .. code-block:: sh 13 | 14 | make -C examples/blinky load 15 | 16 | Blinking a LED 17 | -------------- 18 | 19 | Read the code for the *blinky* example if you're looking for something minimal: 20 | 21 | .. literalinclude:: ../examples/blinky/main.c 22 | :caption: main.c 23 | :language: c 24 | 25 | .. literalinclude:: ../examples/blinky/Makefile 26 | :caption: Makefile 27 | :language: make 28 | 29 | Interacting over USB 30 | -------------------- 31 | 32 | Consider the code of the Cypress bootloader if you want to see how simple USB functionality can be implemented: 33 | 34 | .. literalinclude:: ../firmware/boot-cypress/main.c 35 | :caption: main.c 36 | :language: c 37 | 38 | .. literalinclude:: ../firmware/boot-cypress/Makefile 39 | :caption: Makefile 40 | :language: make 41 | 42 | Adding an DFU bootloader 43 | ------------------------ 44 | 45 | It is easy to integrate a standards-compliant and OS-agnostic Device Firmware Upgrade bootloader as *libfx2* provides all necessary infrastructure, and it only needs to be configured for a specific board and integrated into a target application: 46 | 47 | .. literalinclude:: ../firmware/boot-dfu/main.c 48 | :caption: main.c 49 | :language: c 50 | 51 | .. literalinclude:: ../firmware/boot-dfu/Makefile 52 | :caption: Makefile 53 | :language: make 54 | 55 | The DFU images suitable for flashing can be generated from Intel HEX firmware images using 56 | the ``dfu`` subcommand of the :ref:`command-line tool `. 57 | 58 | Adding an UF2 bootloader 59 | ------------------------ 60 | 61 | It is easy to integrate a very versatile and OS-agnostic `UF2 compliant `_ bootloader as *libfx2* provides all necessary infrastructure, and it only needs to be configured for a specific board and integrated into a target application: 62 | 63 | .. _uf2: https://github.com/Microsoft/uf2 64 | 65 | .. literalinclude:: ../firmware/boot-uf2/main.c 66 | :caption: main.c 67 | :language: c 68 | 69 | .. literalinclude:: ../firmware/boot-uf2/Makefile 70 | :caption: Makefile 71 | :language: make 72 | 73 | The UF2 images suitable for flashing can be generated from Intel HEX firmware images using 74 | the ``uf2`` subcommand of the :ref:`command-line tool `. 75 | -------------------------------------------------------------------------------- /docs/build_system.rst: -------------------------------------------------------------------------------- 1 | Build system reference 2 | ====================== 3 | 4 | *libfx2* provides a flexible build system based on GNU Make for convenient development. 5 | It provides: 6 | 7 | * out-of-tree builds, 8 | * rebuilds after changes to application headers, 9 | * rebuilds (of both *libfx2* and application) after changes to *libfx2*, 10 | * minimal and readable configuration for most common cases, 11 | * a ``load`` target for building and uploading the design using :ref:`fx2tool `. 12 | 13 | To start using it, create a source file... 14 | 15 | .. code-block:: c 16 | :caption: main.c 17 | 18 | #include 19 | 20 | int main(void) { 21 | IOA = OEA = 1; 22 | while(1); 23 | } 24 | 25 | \... and a Makefile, replacing ``.../libfx2`` with the path to the root of this repository... 26 | 27 | .. code-block:: make 28 | :caption: Makefile 29 | 30 | LIBFX2 = .../libfx2/firmware/library 31 | include $(LIBFX2)/fx2rules.mk 32 | 33 | \... and you're done! Running ``make`` will build a ``firmware.ihex``, and running ``make load`` will run it on any connected Cypress development kit. 34 | 35 | Of course, as your project grows, so has your build system. The configuration values that may be set are as follows: 36 | 37 | * ``TARGET`` sets the base name of the output Intel HEX file. It is ``firmware`` if not specified. 38 | * ``SOURCES`` lists the ``.c`` or ``.asm`` source files to be built, without extension. It is ``main`` if not specified. 39 | * ``LIBRARIES`` lists the standard libraries to be linked in, without extension. It is ``fx2isrs`` by default, and can be any of ``fx2``, ``fx2isrs`` and ``fx2usb``. 40 | * ``VID``, ``PID`` set the USB VID:PID pair used to search for the development board. They are ``04B4:8613`` if not specified, which is the VID:PID pair of the Cypress development kit. 41 | * ``MODEL`` sets the sdcc_ code model, one of ``small``, ``medium``, ``large`` or ``huge``. 42 | The *libfx2* standard library as well as sdcc_ standard library are built for all code models. It is ``small`` if not specified. 43 | * ``CODE_SIZE``, ``XRAM_SIZE`` set the sizes of the corresponding sdcc_ segments. The ``CODE`` and ``XRAM`` segments must add up to at most ``0x4000``. They are ``0x3e00`` and ``0x0200`` if not specified. 44 | * ``CFLAGS`` appends arbitrary flags to every sdcc_ invocation. 45 | 46 | An elaborate Makefile could look as follows: 47 | 48 | .. code-block:: make 49 | :caption: Makefile 50 | 51 | VID ?= 20b7 52 | PID ?= 9db1 53 | 54 | TARGET = glasgow 55 | SOURCES = main leds fpga dac_ldo adc 56 | LIBRARIES = fx2 fx2usb fx2isrs 57 | MODEL = medium 58 | CODE_SIZE = 0x3000 59 | XRAM_SIZE = 0x1000 60 | CFLAGS = -DSYNCDELAYLEN=16 61 | 62 | LIBFX2 = ../vendor/libfx2/firmware/library 63 | include $(LIBFX2)/fx2rules.mk 64 | 65 | .. _sdcc: http://sdcc.sourceforge.net 66 | -------------------------------------------------------------------------------- /firmware/library/autovec.asm: -------------------------------------------------------------------------------- 1 | .module autovec 2 | ; Exports 3 | .globl _isr_USB 4 | .globl _isr_GPIF_IE4 5 | ; Imports 6 | .globl _isr_SUDAV 7 | .globl _isr_SOF 8 | .globl _isr_SUTOK 9 | .globl _isr_SUSPEND 10 | .globl _isr_USBRESET 11 | .globl _isr_HISPEED 12 | .globl _isr_EP0ACK 13 | .globl _isr_EP0IN 14 | .globl _isr_EP0OUT 15 | .globl _isr_EP1IN 16 | .globl _isr_EP1OUT 17 | .globl _isr_EP2 18 | .globl _isr_EP4 19 | .globl _isr_EP6 20 | .globl _isr_EP8 21 | .globl _isr_IBN 22 | .globl _isr_EP0PING 23 | .globl _isr_EP1PING 24 | .globl _isr_EP2PING 25 | .globl _isr_EP4PING 26 | .globl _isr_EP6PING 27 | .globl _isr_EP8PING 28 | .globl _isr_ERRLIMIT 29 | .globl _isr_EP2ISOERR 30 | .globl _isr_EP4ISOERR 31 | .globl _isr_EP6ISOERR 32 | .globl _isr_EP8ISOERR 33 | .globl _isr_EP2PF 34 | .globl _isr_EP4PF 35 | .globl _isr_EP6PF 36 | .globl _isr_EP8PF 37 | .globl _isr_EP2EF 38 | .globl _isr_EP4EF 39 | .globl _isr_EP6EF 40 | .globl _isr_EP8EF 41 | .globl _isr_EP2FF 42 | .globl _isr_EP4FF 43 | .globl _isr_EP6FF 44 | .globl _isr_EP8FF 45 | .globl _isr_GPIFDONE 46 | .globl _isr_GPIFWF 47 | ; Code 48 | .area CSEG (ABS,CODE) 49 | .org 0x0100 50 | ; Actually a jump table, not an ISR, see the TRM 51 | _isr_USB: 52 | _isr_GPIF_IE4: 53 | ljmp _isr_SUDAV 54 | .ds 1 55 | ljmp _isr_SOF 56 | .ds 1 57 | ljmp _isr_SUTOK 58 | .ds 1 59 | ljmp _isr_SUSPEND 60 | .ds 1 61 | ljmp _isr_USBRESET 62 | .ds 1 63 | ljmp _isr_HISPEED 64 | .ds 1 65 | ljmp _isr_EP0ACK 66 | .ds 1 67 | ljmp _isr_reserved 68 | .ds 1 69 | ljmp _isr_EP0IN 70 | .ds 1 71 | ljmp _isr_EP0OUT 72 | .ds 1 73 | ljmp _isr_EP1IN 74 | .ds 1 75 | ljmp _isr_EP1OUT 76 | .ds 1 77 | ljmp _isr_EP2 78 | .ds 1 79 | ljmp _isr_EP4 80 | .ds 1 81 | ljmp _isr_EP6 82 | .ds 1 83 | ljmp _isr_EP8 84 | .ds 1 85 | ljmp _isr_IBN 86 | .ds 1 87 | ljmp _isr_reserved 88 | .ds 1 89 | ljmp _isr_EP0PING 90 | .ds 1 91 | ljmp _isr_EP1PING 92 | .ds 1 93 | ljmp _isr_EP2PING 94 | .ds 1 95 | ljmp _isr_EP4PING 96 | .ds 1 97 | ljmp _isr_EP6PING 98 | .ds 1 99 | ljmp _isr_EP8PING 100 | .ds 1 101 | ljmp _isr_ERRLIMIT 102 | .ds 1 103 | ljmp _isr_reserved 104 | .ds 1 105 | ljmp _isr_reserved 106 | .ds 1 107 | ljmp _isr_reserved 108 | .ds 1 109 | ljmp _isr_EP2ISOERR 110 | .ds 1 111 | ljmp _isr_EP4ISOERR 112 | .ds 1 113 | ljmp _isr_EP6ISOERR 114 | .ds 1 115 | ljmp _isr_EP8ISOERR 116 | .ds 1 117 | ljmp _isr_EP2PF 118 | .ds 1 119 | ljmp _isr_EP4PF 120 | .ds 1 121 | ljmp _isr_EP6PF 122 | .ds 1 123 | ljmp _isr_EP8PF 124 | .ds 1 125 | ljmp _isr_EP2EF 126 | .ds 1 127 | ljmp _isr_EP4EF 128 | .ds 1 129 | ljmp _isr_EP6EF 130 | .ds 1 131 | ljmp _isr_EP8EF 132 | .ds 1 133 | ljmp _isr_EP2FF 134 | .ds 1 135 | ljmp _isr_EP4FF 136 | .ds 1 137 | ljmp _isr_EP6FF 138 | .ds 1 139 | ljmp _isr_EP8FF 140 | .ds 1 141 | ljmp _isr_GPIFDONE 142 | .ds 1 143 | ljmp _isr_GPIFWF 144 | .ds 1 145 | ; Default handler for reserved interrupts 146 | _isr_reserved: 147 | reti 148 | -------------------------------------------------------------------------------- /firmware/library/include/fx2delay.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2DELAY_H 2 | #define FX2DELAY_H 3 | 4 | #include 5 | 6 | /** 7 | * Spin for the given number of milliseconds. 8 | */ 9 | void delay_ms(uint16_t count_ms) __reentrant; 10 | 11 | /** 12 | * Spin for the given number of microseconds, minus `overh_c` processor cycles. 13 | * `count_us` must be no greater than 21845, and `overh_c` must be no greater than 128. 14 | * 15 | * This function is cycle-accurate at any CPU clock frequency provided that the delay is not less 16 | * than the intrinsic overhead of up to 100 processor cycles (9..33 microseconds). 17 | */ 18 | void delay_us_overhead(uint16_t count_us, uint8_t overh_c) __reentrant; 19 | 20 | /** 21 | * Equivalent to `delay_us_overhead(count_us, 3)` where 3 is the number of cycles of overhead 22 | * when `delay_us` is called with a constant argument. 23 | */ 24 | void delay_us(uint16_t count_us) __reentrant; 25 | 26 | /** 27 | * Spin for `count * 4` processor cycles, or `count * 16` clock cycles. 28 | * Takes exactly 24 processor cycles (2..8 microseconds) if `count` is less than `6`. 29 | */ 30 | void delay_4c(uint16_t count_4c) __reentrant; 31 | 32 | /** 33 | * Synchronization delay length. 34 | * 35 | * This value defaults to 3, and should be overridden using a compiler flag 36 | * `-DSYNCDELAYLEN=n` if running with non-default IFCLK or CLKOUT clock frequency. 37 | * Delay length can be calculated using the following Python code: 38 | * 39 | * import math 40 | * math.ceil(1.5 * ((ifclk_period / clkout_period) + 1)) 41 | * 42 | * See TRM 15.15 for details. 43 | */ 44 | #ifndef SYNCDELAYLEN 45 | #define SYNCDELAYLEN 3 46 | #endif 47 | 48 | #if SYNCDELAYLEN < 2 || SYNCDELAYLEN > 16 49 | #error Invalid synchronization delay length 50 | #endif 51 | 52 | #ifndef DOXYGEN 53 | // Accumulator doesn't need to be preserved 54 | #define _NOP1 __asm__("nop") // 1b 1c 55 | #define _NOP2 __asm__("nop \n nop") // 2b 2c 56 | #define _NOP3 __asm__("ajmp .+2") // 2b 3c 57 | #define _NOP4 __asm__("nop \n movc a, @a+pc") // 2b 4c 58 | #define _NOP5 __asm__("nop \n nop \n movc a, @a+pc") // 3b 5c 59 | #define _NOP6 __asm__("ajmp .+2 \n movc a, @a+pc") // 3b 6c 60 | #define _NOP7 __asm__("acall .+2 \n ret") // 3b 7c 61 | #define _NOPn(n) __asm__("lcall _nop"#n) // 3b nc 8<=n<=16 62 | #endif 63 | 64 | /** 65 | * Synchronization delay for access to certain registers. 66 | * 67 | * See TRM 15.15 for details. 68 | * 69 | * This macro produces very compact code, using only 2 or 3 bytes per instance. 70 | */ 71 | #if SYNCDELAYLEN == 2 72 | #define SYNCDELAY _NOP2 73 | #elif SYNCDELAYLEN == 3 74 | #define SYNCDELAY _NOP3 75 | #elif SYNCDELAYLEN == 4 76 | #define SYNCDELAY _NOP4 77 | #elif SYNCDELAYLEN == 5 78 | #define SYNCDELAY _NOP5 79 | #elif SYNCDELAYLEN == 6 80 | #define SYNCDELAY _NOP6 81 | #elif SYNCDELAYLEN == 7 82 | #define SYNCDELAY _NOP7 83 | #elif SYNCDELAYLEN == 8 84 | #define SYNCDELAY _NOPn(8) 85 | #elif SYNCDELAYLEN == 9 86 | #define SYNCDELAY _NOPn(9) 87 | #elif SYNCDELAYLEN == 10 88 | #define SYNCDELAY _NOPn(10) 89 | #elif SYNCDELAYLEN == 11 90 | #define SYNCDELAY _NOPn(11) 91 | #elif SYNCDELAYLEN == 12 92 | #define SYNCDELAY _NOPn(12) 93 | #elif SYNCDELAYLEN == 13 94 | #define SYNCDELAY _NOPn(13) 95 | #elif SYNCDELAYLEN == 14 96 | #define SYNCDELAY _NOPn(14) 97 | #elif SYNCDELAYLEN == 15 98 | #define SYNCDELAY _NOPn(15) 99 | #elif SYNCDELAYLEN == 16 100 | #define SYNCDELAY _NOPn(16) 101 | #endif 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /firmware/library/include/fx2spi.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2SPI_H 2 | #define FX2SPI_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef DOXYGEN 9 | 10 | // See the implementation of xmemcpy for a detailed explanation of the loop structure below. 11 | #define _SPI_FN(name, cst, bit, sck, si, so, aldr, atlr) \ 12 | void name(cst __xdata uint8_t *data, uint16_t len) { \ 13 | data; \ 14 | len; \ 15 | __asm \ 16 | mov _AUTOPTRSETUP, _ASM_HASH 0b11 \ 17 | mov _AUTOPTRL1, dpl \ 18 | mov _AUTOPTRH1, dph \ 19 | \ 20 | _ASM_GET_PARM2(r2, r3, _##name##_PARM_2) \ 21 | \ 22 | mov a, r2 \ 23 | jz 00000$ \ 24 | inc r3 \ 25 | 00000$: \ 26 | mov a, r3 \ 27 | jz 00002$ \ 28 | \ 29 | mov dptr, _ASM_HASH _XAUTODAT1 \ 30 | 00001$: \ 31 | aldr ; 8c+s \ 32 | bit(sck, si, so, 7) ; 8c \ 33 | bit(sck, si, so, 6) ; 8c \ 34 | bit(sck, si, so, 5) ; 8c \ 35 | bit(sck, si, so, 4) ; 8c \ 36 | bit(sck, si, so, 3) ; 8c \ 37 | bit(sck, si, so, 2) ; 8c \ 38 | bit(sck, si, so, 1) ; 8c \ 39 | bit(sck, si, so, 0) ; 8c \ 40 | atlr ; 8c+s \ 41 | djnz r2, 00001$ ; 4c \ 42 | djnz r3, 00001$ ; 4c \ 43 | \ 44 | 00002$: \ 45 | __endasm; \ 46 | } 47 | 48 | #define _SPI_DUMMY 49 | 50 | #define _SPI_WR_LDR movx a, @dptr 51 | #define _SPI_WR_BIT(sck, si, so, num) \ 52 | mov c, acc+num ; 2c \ 53 | clr _ASM_REG(sck) ; 2c \ 54 | mov _ASM_REG(si), c ; 2c \ 55 | setb _ASM_REG(sck) ; 2c 56 | 57 | #define _SPI_RD_TLR movx @dptr, a 58 | #define _SPI_RD_BIT(sck, si, so, num) \ 59 | clr _ASM_REG(sck) ; 2c \ 60 | mov c, _ASM_REG(so) ; 2c \ 61 | setb _ASM_REG(sck) ; 2c \ 62 | mov acc+num, c ; 2c 63 | 64 | #endif 65 | 66 | /** 67 | * This macro defines a function `void name(const __xdata uint8_t *data, uint16_t len)` that 68 | * implements an optimized (76 clock cycles per iteration; ~5 MHz at 48 MHz CLKOUT) SPI Mode 3 69 | * write routine. The `sck` and `si` parameters may point to any pins, and are defined in 70 | * the format `Pxn`. 71 | * 72 | * For example, invoking the macro as `DEFINE_SPI_WR_FN(flash_write, PA1, PB6)` defines 73 | * a routine `void flash_write()` that assumes an SPI device's SCK pin is connected to A1 and 74 | * MOSI pin is connected to B6. 75 | */ 76 | #define DEFINE_SPI_WR_FN(name, sck, si) \ 77 | _SPI_FN(name, const, _SPI_WR_BIT, sck, si, 0, _SPI_WR_LDR, _SPI_DUMMY) 78 | 79 | /** 80 | * This macro defines a function `void name(__xdata uint8_t *data, uint16_t len)` that implements 81 | * an optimized (76 clock cycles per iteration; ~5 MHz at 48 MHz CLKOUT) SPI Mode 3 read routine. 82 | * The `sck` and `so` parameters may point to any pins, and are defined in the format `_Pxn` 83 | * (note the underscore). 84 | * 85 | * For example, invoking the macro as `DEFINE_SPI_RD_FN(flash_read, PA1, PB5)` defines 86 | * a routine `void flash_read()` that assumes an SPI device's SCK pin is connected to A1 and 87 | * MISO pin is connected to B5. 88 | */ 89 | #define DEFINE_SPI_RD_FN(name, sck, so) \ 90 | _SPI_FN(name, _SPI_DUMMY, _SPI_RD_BIT, sck, 0, so, _SPI_DUMMY, _SPI_RD_TLR) 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /firmware/library/include/fx2usbmassstor.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2USBMASSSTOR_H 2 | #define FX2USBMASSSTOR_H 3 | 4 | #include 5 | #include 6 | 7 | /// State of an USB Mass Storage Bulk-Only Transport interface. 8 | struct usb_mass_storage_bbb_state { 9 | /// The bInterfaceNumber field corresponding to this interface. 10 | uint8_t interface; 11 | 12 | /// The value of the wMaxPacketSize field of the BULK IN endpoint of this interface. 13 | uint16_t max_in_size; 14 | 15 | /// The maximum LUN number of this interface. Most interfaces will only use a single LUN, 16 | /// and so will have ``max_lun`` set to zero. 17 | uint8_t max_lun; 18 | 19 | /// The Command callback. This function is called for each CBW. It should return ``true`` 20 | /// if the command is recognized and well-formed, which either proceeds to the Data callbacks 21 | /// (if there is any data to be transferred) or returns a Passed CSW immediately; or ``false``, 22 | /// in which case the data callbacks are never called and a Failed CSW is returned. 23 | bool (*command)(uint8_t lun, __xdata uint8_t *command, uint8_t length) __reentrant; 24 | 25 | /// The Data-Out callback. This function is called with chunks of data that follow a CBW 26 | /// that specifies an OUT transfer. It can read exactly ``length`` bytes from 27 | /// the ``data`` buffer. If data is processed successfully, this function should return ``true``, 28 | /// which will eventually return a Passed CSW to the host; otherwise, the rest of the transfer 29 | /// is skipped (this callback is not invoked again), and a Failed CSW is returned. 30 | bool (*data_out)(uint8_t lun, __xdata const uint8_t *data, uint16_t length) __reentrant; 31 | 32 | /// The Data-In callback. This function is called each time a chunk of data is necessary 33 | /// following a CBW that specifies an IN transfer. It should fill exactly ``length`` 34 | /// bytes into the ``data`` buffer. If data is processed successfully, this function should 35 | /// return ``true``, which will eventually return a Passed CSW to the host; otherwise, 36 | /// the rest of the transfer is skipped (this callback is not invoked again), and a Failed CSW 37 | /// is returned. 38 | bool (*data_in)(uint8_t lun, __xdata uint8_t *data, uint16_t length) __reentrant; 39 | 40 | #ifndef DOXYGEN 41 | // Private fields, subject to change at any time. 42 | enum { 43 | USB_MASS_STORAGE_BBB_STATE_COMMAND, 44 | USB_MASS_STORAGE_BBB_STATE_DATA_OUT, 45 | USB_MASS_STORAGE_BBB_STATE_FAIL_OUT, 46 | USB_MASS_STORAGE_BBB_STATE_DATA_IN, 47 | USB_MASS_STORAGE_BBB_STATE_FAIL_IN, 48 | USB_MASS_STORAGE_BBB_STATE_STATUS, 49 | } _state; 50 | uint32_t _tag; 51 | uint8_t _lun; 52 | #if __SDCC_VERSION_MAJOR > 3 || __SDCC_VERSION_MINOR >= 7 53 | bool _data_in; 54 | bool _success; 55 | #else 56 | uint8_t _data_in; 57 | uint8_t _success; 58 | #endif 59 | uint32_t _data_length; 60 | uint32_t _residue; 61 | #endif 62 | }; 63 | 64 | typedef __xdata struct usb_mass_storage_bbb_state 65 | usb_mass_storage_bbb_state_t; 66 | 67 | /** 68 | * Handle USB Mass Storage Bulk-Only Transport interface SETUP packets. 69 | * This function makes the appropriate changes to the state and returns ``true`` if a SETUP packet 70 | * addressed this interface, or returns ``false`` otherwise. 71 | */ 72 | bool usb_mass_storage_bbb_setup(usb_mass_storage_bbb_state_t *state, 73 | __xdata struct usb_req_setup *request); 74 | 75 | /** 76 | * Process USB Mass Storage Bulk-Only Transport interface BULK OUT packets. 77 | * 78 | * This function should be called each time a BULK OUT packet is received. 79 | * 80 | * It returns a result flag. If the result is ``true``, a packet should be ended. If the result 81 | * is ``false``, both the BULK OUT and BULK IN endpoint should be stalled. 82 | */ 83 | bool usb_mass_storage_bbb_bulk_out(usb_mass_storage_bbb_state_t *state, 84 | __xdata const uint8_t *data, uint16_t length); 85 | 86 | /** 87 | * Emit USB Mass Storage Bulk-Only Transport interface BULK IN packets. 88 | * 89 | * This function should be called each time an IN BULK NAK interrupt occurs. 90 | * 91 | * It returns a result flag. If the result is ``true``, a packet should be committed if 92 | * ``length`` is nonzero. If the result is ``false``, the BULK IN endpoint should be stalled. 93 | */ 94 | bool usb_mass_storage_bbb_bulk_in(usb_mass_storage_bbb_state_t *state, 95 | __xdata uint8_t *data, __xdata uint16_t *length); 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /firmware/library/include/fx2usbdfu.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2USBDFU_H 2 | #define FX2USBDFU_H 3 | 4 | #include 5 | #include 6 | 7 | /// State of an USB Device Firmware Upgrade interface. 8 | struct usb_dfu_iface_state { 9 | /** 10 | * The bInterfaceNumber field corresponding to this interface in runtime mode. 11 | * (In upgrade mode, only one interface must be exported, so this field is ignored.) 12 | */ 13 | uint8_t interface; 14 | 15 | /** 16 | * Firmware upload function. This function reads the firmware block at ``offset`` 17 | * of requested ``length`` into ``data``. When end of firmware is reached, this function 18 | * should report this a block size shorter than provided ``length`` by changing it. 19 | * 20 | * The ``offset`` argument is maintained internally by this library and is increased after 21 | * each ``firmware_upload`` call by ``length``; it is not related to the ``wBlockNum`` field 22 | * from ``DFU_UPLOAD`` request. 23 | * 24 | * The firmware read out by this function must be in a format identical to that accepted 25 | * by the ``firmware_dnload`` function to be DFU compliant. 26 | * 27 | * This function should return ``USB_DFU_STATUS_OK`` if the firmware could be read, 28 | * and one of the other ``enum usb_dfu_status`` values otherwise. 29 | */ 30 | usb_dfu_status_t (*firmware_upload)(uint32_t offset, __xdata uint8_t *data, 31 | __xdata uint16_t *length) __reentrant; 32 | 33 | /** 34 | * Firmware download function. This function writes the firmware block at ``offset`` 35 | * from ``data`` that is ``length`` bytes long, which may be no greater than 36 | * ``wTransferSize`` field in the DFU functional descriptor. If this function receives 37 | * a block lenght of zero, it must validate whether it has received the complete firmware. 38 | * 39 | * The ``offset`` argument is maintained internally by this library and is increased after 40 | * each ``firmware_dnload`` call by ``length``; it is not related to the ``wBlockNum`` field 41 | * from ``DFU_DNLOAD`` request. 42 | * 43 | * This function should return ``USB_DFU_STATUS_OK`` if the firmware could be written 44 | * (or, in case of ``length == 0``, if the complete firmware was downloaded), and one 45 | * of the other ``enum usb_dfu_status`` values otherwise. 46 | */ 47 | usb_dfu_status_t (*firmware_dnload)(uint32_t offset, __xdata uint8_t *data, 48 | uint16_t length) __reentrant; 49 | 50 | /** 51 | * Firmware manifestation function. This function could perform any application-specific 52 | * actions required to finish updating firmware, such as changing the boot address. 53 | * 54 | * This function should return ``USB_DFU_STATUS_OK`` if the firmware could be manifested, 55 | * and one of the other ``enum usb_dfu_status`` values otherwise. 56 | * 57 | * If this callback is set to ``NULL``, the behavior is the same as if the callback was 58 | * implemented as an empty function returning ``USB_DFU_STATUS_OK``. 59 | */ 60 | usb_dfu_status_t (*firmware_manifest)(void) __reentrant; 61 | 62 | /// State of the DFU interface, as per DFU specification. 63 | volatile enum usb_dfu_state state; 64 | 65 | #ifndef DOXYGEN 66 | // Private fields, subject to change at any time. 67 | volatile enum usb_dfu_status status; 68 | #if __SDCC_VERSION_MAJOR > 3 || __SDCC_VERSION_MINOR >= 7 69 | volatile bool pending, sync; 70 | #else 71 | volatile uint8_t pending, sync; 72 | #endif 73 | uint16_t length; 74 | uint32_t offset; 75 | #endif 76 | }; 77 | 78 | typedef __xdata struct usb_dfu_iface_state 79 | usb_dfu_iface_state_t; 80 | 81 | /** 82 | * Handle USB Device Firmware Update interface SETUP packets. 83 | * This function makes the appropriate changes to the state and returns ``true`` if a SETUP packet 84 | * addressed this interface, or returns ``false`` otherwise. 85 | */ 86 | bool usb_dfu_setup(usb_dfu_iface_state_t *state, __xdata struct usb_req_setup *request); 87 | 88 | /** 89 | * Handle USB Device Firmware Update interface SETUP packets that perform lengthy operations 90 | * (i.e. actual firmware upload/download). 91 | */ 92 | void usb_dfu_setup_deferred(usb_dfu_iface_state_t *state); 93 | 94 | /** 95 | * The global DFU configuration, defined in the application code. 96 | * It only makes sense for a single device to expose a single DFU interface at a time. 97 | */ 98 | extern usb_dfu_iface_state_t usb_dfu_iface_state; 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /firmware/library/include/fx2ints.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2INTS_H 2 | #define FX2INTS_H 3 | 4 | enum fx2_core_interrupt { 5 | _INT_IE0 = 0, //< Pin PA0 / INT0# 6 | _INT_TF0 = 1, //< Internal, Timer 0 7 | _INT_IE1 = 2, //< Pin PA1 / INT1# 8 | _INT_TF1 = 3, //< Internal, Timer 1 9 | _INT_RI_TI_0 = 4, //< Internal, USART0 10 | _INT_TF2 = 5, //< Internal, Timer 2 11 | _INT_RESUME = 6, //< Pin WAKEUP or Pin PA3/WU2 12 | _INT_RI_TI_1 = 7, //< Internal, USART1 13 | _INT_USB = 8, //< Internal, USB 14 | _INT_I2C = 9, //< Internal, I2C Bus Controller 15 | _INT_GPIF_IE4 = 10, //< Internal, GPIF/FIFOs or Pin INT4 (100 and 128 pin only) 16 | _INT_IE5 = 11, //< Pin INT5# (100 and 128 pin only) 17 | _INT_IE6 = 12, //< Pin INT6 (100 and 128 pin only) 18 | }; 19 | 20 | /** 21 | * \name 8051 core interrupts 22 | * @{ 23 | */ 24 | 25 | /** 26 | * Clears the I2C interrupt request. 27 | */ 28 | #define CLEAR_I2C_IRQ() \ 29 | do { EXIF &= ~_I2CINT; } while(0) 30 | 31 | /** 32 | * Clears the INT4# interrupt request. 33 | */ 34 | #define CLEAR_INT4_IRQ() \ 35 | do { EXIF &= ~_IE4; } while(0) 36 | 37 | /** 38 | * Clears the INT5# interrupt request. 39 | */ 40 | #define CLEAR_INT5_IRQ() \ 41 | do { EXIF &= ~_IE5; } while(0) 42 | 43 | void isr_IE0(void) __interrupt(_INT_IE0); 44 | void isr_TF0(void) __interrupt(_INT_TF0); 45 | void isr_IE1(void) __interrupt(_INT_IE1); 46 | void isr_TF1(void) __interrupt(_INT_TF1); 47 | void isr_RI_TI_0(void) __interrupt(_INT_RI_TI_0); 48 | void isr_TF2(void) __interrupt(_INT_TF2); 49 | void isr_RESUME(void) __interrupt(_INT_RESUME); 50 | void isr_RI_TI_1(void) __interrupt(_INT_RI_TI_1); 51 | void isr_USB(void) __interrupt(_INT_USB); 52 | void isr_I2C(void) __interrupt(_INT_I2C); 53 | void isr_GPIF_IE4(void) __interrupt(_INT_GPIF_IE4); 54 | void isr_IE5(void) __interrupt(_INT_IE5); 55 | void isr_IE6(void) __interrupt(_INT_IE6); 56 | 57 | /**@}*/ 58 | 59 | /** 60 | * \name Autovectored USB interrupts 61 | * @{ 62 | */ 63 | 64 | /** 65 | * Enables the autovectored USB interrupt and the corresponding jump table. 66 | */ 67 | #define ENABLE_USB_AUTOVEC() \ 68 | do { EUSB = 1; INTSETUP |= _AV2EN; } while(0) 69 | 70 | /** 71 | * Clears the main USB interrupt request. 72 | * This must be done before clearing the individual USB interrupt request latch. 73 | */ 74 | #define CLEAR_USB_IRQ() \ 75 | do { EXIF &= ~_USBINT; } while(0) 76 | 77 | void isr_SUDAV(void) __interrupt; 78 | void isr_SOF(void) __interrupt; 79 | void isr_SUTOK(void) __interrupt; 80 | void isr_SUSPEND(void) __interrupt; 81 | void isr_USBRESET(void) __interrupt; 82 | void isr_HISPEED(void) __interrupt; 83 | void isr_EP0ACK(void) __interrupt; 84 | void isr_EP0IN(void) __interrupt; 85 | void isr_EP0OUT(void) __interrupt; 86 | void isr_EP1IN(void) __interrupt; 87 | void isr_EP1OUT(void) __interrupt; 88 | void isr_EP2(void) __interrupt; 89 | void isr_EP4(void) __interrupt; 90 | void isr_EP6(void) __interrupt; 91 | void isr_EP8(void) __interrupt; 92 | void isr_IBN(void) __interrupt; 93 | void isr_EP0PING(void) __interrupt; 94 | void isr_EP1PING(void) __interrupt; 95 | void isr_EP2PING(void) __interrupt; 96 | void isr_EP4PING(void) __interrupt; 97 | void isr_EP6PING(void) __interrupt; 98 | void isr_EP8PING(void) __interrupt; 99 | void isr_ERRLIMIT(void) __interrupt; 100 | void isr_EP2ISOERR(void) __interrupt; 101 | void isr_EP4ISOERR(void) __interrupt; 102 | void isr_EP6ISOERR(void) __interrupt; 103 | void isr_EP8ISOERR(void) __interrupt; 104 | 105 | /**@}*/ 106 | 107 | /** 108 | * \name Autovectored GPIF interrupts 109 | * @{ 110 | */ 111 | 112 | /** 113 | * Enables the autovectored GPIF interrupt and the corresponding jump table. 114 | * Note that this makes it impossible to provide an INT4 handler. 115 | */ 116 | #define ENABLE_GPIF_AUTOVEC() \ 117 | do { EX4 = 1; INTSETUP |= _AV4EN; } while(0) 118 | 119 | /** 120 | * Clears the main GPIF interrupt request. 121 | * This must be done before clearing the individual GPIF interrupt request latch. 122 | */ 123 | #define CLEAR_GPIF_IRQ() \ 124 | do { EXIF &= ~_IE4; } while(0) 125 | 126 | void isr_EP2PF(void) __interrupt; 127 | void isr_EP4PF(void) __interrupt; 128 | void isr_EP6PF(void) __interrupt; 129 | void isr_EP8PF(void) __interrupt; 130 | void isr_EP2EF(void) __interrupt; 131 | void isr_EP4EF(void) __interrupt; 132 | void isr_EP6EF(void) __interrupt; 133 | void isr_EP8EF(void) __interrupt; 134 | void isr_EP2FF(void) __interrupt; 135 | void isr_EP4FF(void) __interrupt; 136 | void isr_EP6FF(void) __interrupt; 137 | void isr_EP8FF(void) __interrupt; 138 | void isr_GPIFDONE(void) __interrupt; 139 | void isr_GPIFWF(void) __interrupt; 140 | 141 | /**@}*/ 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /firmware/library/include/scsi.h: -------------------------------------------------------------------------------- 1 | #ifndef SCSI_H 2 | #define SCSI_H 3 | 4 | #include 5 | 6 | // Command TEST UNIT READY 7 | 8 | struct scsi_test_unit_ready { 9 | uint8_t __reserved0[4]; 10 | uint8_t control; 11 | }; 12 | 13 | // Command REQUEST SENSE 14 | 15 | struct scsi_request_sense { 16 | uint8_t desc:1; 17 | uint8_t __reserved0:7; 18 | uint8_t __reserved1[2]; 19 | uint8_t allocation_length; 20 | uint8_t control; 21 | }; 22 | 23 | // Command INQUIRY 24 | 25 | struct scsi_inquiry { 26 | uint8_t evpd:1; 27 | uint8_t page_code; 28 | uint16_t allocation_length; 29 | uint8_t control; 30 | }; 31 | 32 | struct scsi_inquiry_data { 33 | uint8_t peripheral_device_type:5; 34 | uint8_t peripheral_qualifier:3; 35 | uint8_t __reserved0:7; 36 | uint8_t rmb:1; 37 | uint8_t version; 38 | uint8_t response_data_format:4; 39 | uint8_t hi_sup:1; 40 | uint8_t norm_aca:1; 41 | uint8_t __obsolete1:2; 42 | uint8_t additional_length; 43 | uint8_t protect:1; 44 | uint8_t __reserved2:2; 45 | uint8_t _3pc:1; 46 | uint8_t tpgs:2; 47 | uint8_t acc:1; 48 | uint8_t sccs:1; 49 | uint8_t addr16:1; 50 | uint8_t __obsolete3:2; 51 | uint8_t m_chngr:1; 52 | uint8_t multi_p:1; 53 | uint8_t vs:1; 54 | uint8_t enc_serv:1; 55 | uint8_t b_que:1; 56 | uint8_t vs2:1; 57 | uint8_t cmd_que:1; 58 | uint8_t __obsolete4:1; 59 | uint8_t linked:1; 60 | uint8_t sync:1; 61 | uint8_t wbus16:1; 62 | uint8_t __obsolete5:2; 63 | uint8_t t10_vendor_identification[8]; 64 | uint8_t product_identification[16]; 65 | uint8_t product_revision_level[4]; 66 | }; 67 | 68 | // Command PREVENT ALLOW MEDIUM REMOVAL 69 | 70 | struct scsi_prevent_allow_medium_removal { 71 | uint8_t __reserved0[3]; 72 | uint8_t prevent:1; 73 | uint8_t __reserved1:7; 74 | uint8_t control; 75 | }; 76 | 77 | // Command READ CAPACITY 78 | 79 | struct scsi_read_capacity { 80 | uint8_t __reserved0[8]; 81 | uint8_t control; 82 | }; 83 | 84 | struct scsi_read_capacity_data { 85 | uint32_t returned_logical_block_address; 86 | uint32_t block_length_in_bytes; 87 | }; 88 | 89 | // Command READ (10) 90 | 91 | struct scsi_read_10 { 92 | uint8_t __reserved0; 93 | uint32_t logical_block_address; 94 | uint8_t __reserved1; 95 | uint16_t transfer_length; 96 | uint8_t control; 97 | }; 98 | 99 | // Command WRITE (10) 100 | 101 | struct scsi_write_10 { 102 | uint8_t __reserved0:3; 103 | uint8_t fua:1; 104 | uint8_t __reserved1:4; 105 | uint32_t logical_block_address; 106 | uint8_t __reserved2; 107 | uint16_t transfer_length; 108 | uint8_t control; 109 | }; 110 | 111 | // Command descriptor 112 | 113 | enum scsi_operation_code { 114 | SCSI_OPERATION_TEST_UNIT_READY = 0x00, 115 | SCSI_OPERATION_REQUEST_SENSE = 0x03, 116 | SCSI_OPERATION_INQUIRY = 0x12, 117 | SCSI_OPERATION_MODE_SENSE_6 = 0x1A, 118 | SCSI_OPERATION_PREVENT_ALLOW_MEDIUM_REMOVAL 119 | = 0x1E, 120 | SCSI_OPERATION_READ_CAPACITY = 0x25, 121 | SCSI_OPERATION_READ_10 = 0x28, 122 | SCSI_OPERATION_WRITE_10 = 0x2A, 123 | }; 124 | 125 | struct scsi_command { 126 | uint8_t op_code; 127 | union { 128 | struct scsi_test_unit_ready test_unit_ready; 129 | struct scsi_request_sense request_sense; 130 | struct scsi_inquiry inquiry; 131 | struct scsi_read_capacity read_capacity; 132 | struct scsi_read_10 read_10; 133 | struct scsi_write_10 write_10; 134 | struct scsi_prevent_allow_medium_removal 135 | prevent_allow_medium_removal; 136 | }; 137 | }; 138 | 139 | // Sense data 140 | 141 | enum scsi_sense_response_code { 142 | SCSI_SENSE_CURRENT_ERROR_FIXED_FORMAT = 0x70, 143 | SCSI_SENSE_DEFERRED_ERROR_FIXED_FORMAT = 0x71, 144 | SCSI_SENSE_CURRENT_ERROR_DESCRIPTOR_FORMAT = 0x72, 145 | SCSI_SENSE_DEFERRED_ERROR_DESCRIPTOR_FORMAT = 0x73, 146 | }; 147 | 148 | enum scsi_sense_key { 149 | SCSI_SENSE_NO_SENSE = 0x0, 150 | SCSI_SENSE_RECOVERED_ERROR = 0x1, 151 | SCSI_SENSE_NOT_READY = 0x2, 152 | SCSI_SENSE_MEDIUM_ERROR = 0x3, 153 | SCSI_SENSE_HARDWARE_ERROR = 0x4, 154 | SCSI_SENSE_ILLEGAL_REQUEST = 0x5, 155 | SCSI_SENSE_UNIT_ATTENTION = 0x6, 156 | SCSI_SENSE_DATA_PROTECT = 0x7, 157 | SCSI_SENSE_BLANK_CHECK = 0x8, 158 | SCSI_SENSE_VENDOR_SPECIFIC = 0x9, 159 | SCSI_SENSE_COPY_ABORTED = 0xA, 160 | SCSI_SENSE_ABORTED_COMMAND = 0xB, 161 | }; 162 | 163 | struct scsi_sense_data_descriptor { 164 | uint8_t response_code:7; 165 | uint8_t __reserved0:1; 166 | uint8_t sense_key:4; 167 | uint8_t __reserved1:4; 168 | uint8_t additional_sense_code; 169 | uint8_t additional_sense_code_qualifier; 170 | uint8_t __reserved2[3]; 171 | uint8_t additional_sense_length; 172 | }; 173 | 174 | struct scsi_sense_data_fixed { 175 | uint8_t response_code:7; 176 | uint8_t valid:1; 177 | uint8_t __obsolete0; 178 | uint8_t sense_key:4; 179 | uint8_t __reserved1:1; 180 | uint8_t ili:1; 181 | uint8_t eom:1; 182 | uint8_t filemark:1; 183 | uint32_t information; 184 | uint8_t additional_sense_length; 185 | uint32_t command_specific_information; 186 | uint8_t additional_sense_code; 187 | uint8_t additional_sense_code_qualifier; 188 | uint8_t field_replaceable_unit_code; 189 | uint8_t sense_key_specific:7; 190 | uint8_t sksv:1; 191 | uint8_t sense_key_specific2[2]; 192 | }; 193 | 194 | #endif 195 | -------------------------------------------------------------------------------- /firmware/library/uf2scsi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static enum scsi_op_code current_op; 10 | static enum scsi_sense_key sense_key; 11 | static uint32_t block_index; 12 | static uint32_t blocks_left; 13 | 14 | bool uf2_scsi_command(uint8_t lun, __xdata uint8_t *buffer, uint8_t length) __reentrant { 15 | __xdata struct scsi_command *command = (__xdata struct scsi_command *)buffer; 16 | lun; 17 | 18 | // Here, we generally ignore the Allocation length field when validating the commands, 19 | // since this is already handled on the USB MSC BBB level. 20 | 21 | if(command->op_code == SCSI_OPERATION_TEST_UNIT_READY && 22 | length >= sizeof(command->op_code) + sizeof(command->test_unit_ready)) { 23 | goto success; 24 | } else if(command->op_code == SCSI_OPERATION_REQUEST_SENSE && 25 | length >= sizeof(command->op_code) + sizeof(command->request_sense)) { 26 | if(command->request_sense.desc == 0) { 27 | goto success; 28 | } 29 | } else if(command->op_code == SCSI_OPERATION_INQUIRY && 30 | length >= sizeof(command->op_code) + sizeof(command->inquiry)) { 31 | if(command->inquiry.evpd == 0 && command->inquiry.page_code == 0) { 32 | goto success; 33 | } 34 | } else if(command->op_code == SCSI_OPERATION_PREVENT_ALLOW_MEDIUM_REMOVAL && 35 | length >= sizeof(command->op_code) + sizeof(command->prevent_allow_medium_removal)) { 36 | goto success; 37 | } else if(command->op_code == SCSI_OPERATION_READ_CAPACITY && 38 | length >= sizeof(command->op_code) + sizeof(command->read_capacity)) { 39 | goto success; 40 | } else if(command->op_code == SCSI_OPERATION_READ_10 && 41 | length >= sizeof(command->op_code) + sizeof(command->read_10)) { 42 | block_index = bswap32(command->read_10.logical_block_address); 43 | blocks_left = bswap32(command->read_10.transfer_length); 44 | goto success; 45 | } else if(command->op_code == SCSI_OPERATION_WRITE_10 && 46 | length >= sizeof(command->op_code) + sizeof(command->write_10)) { 47 | block_index = bswap32(command->write_10.logical_block_address); 48 | blocks_left = bswap32(command->write_10.transfer_length); 49 | goto success; 50 | } 51 | 52 | current_op = 0; 53 | sense_key = SCSI_SENSE_ILLEGAL_REQUEST; 54 | return false; 55 | 56 | success: 57 | current_op = command->op_code; 58 | return true; 59 | } 60 | 61 | bool uf2_scsi_data_out(uint8_t lun, __xdata uint8_t *buffer, uint16_t length) __reentrant { 62 | lun; 63 | 64 | if(current_op == SCSI_OPERATION_WRITE_10) { 65 | if(blocks_left == 0) 66 | return false; 67 | if(length != 512) 68 | return false; 69 | if(!uf2_fat_write(block_index, buffer)) 70 | return false; 71 | 72 | block_index++; 73 | blocks_left--; 74 | if(blocks_left > 0) 75 | return true; 76 | 77 | goto done; 78 | } 79 | 80 | return false; 81 | 82 | done: 83 | current_op = 0; 84 | return true; 85 | } 86 | 87 | bool uf2_scsi_data_in(uint8_t lun, __xdata uint8_t *buffer, uint16_t length) __reentrant { 88 | lun; 89 | 90 | if(current_op == SCSI_OPERATION_REQUEST_SENSE) { 91 | __xdata struct scsi_sense_data_fixed *data = 92 | (__xdata struct scsi_sense_data_fixed *)buffer; 93 | xmemclr(buffer, length); 94 | 95 | data->response_code = SCSI_SENSE_CURRENT_ERROR_FIXED_FORMAT; 96 | data->sense_key = sense_key; 97 | 98 | sense_key = SCSI_SENSE_NO_SENSE; 99 | goto done; 100 | } else if(current_op == SCSI_OPERATION_INQUIRY) { 101 | __xdata struct scsi_inquiry_data *data = 102 | (__xdata struct scsi_inquiry_data *)buffer; 103 | xmemclr(buffer, length); 104 | 105 | data->additional_length = sizeof(struct scsi_inquiry_data) - 106 | (offsetof(struct scsi_inquiry_data, additional_length) + 107 | sizeof(data->additional_length)); 108 | 109 | // SBC-2 (Direct access block device). 110 | // It would be more fitting to use RBC here, but Windows does not understand 111 | // how to talk to that (even though RBC is basically boneless SBC...) 112 | data->peripheral_device_type = 0x00; 113 | 114 | // We're a removable device. Without this flag, Windows will refuse to mount 115 | // the device as a flat filesystem, and will instead demand to have it partitioned. 116 | data->rmb = true; 117 | 118 | // Our identification. 119 | xmemcpy(data->t10_vendor_identification, (__xdata void *)"Qi-Hardw", 8); 120 | xmemcpy(data->product_identification, (__xdata void *)"Cypress UF2 Boot", 16); 121 | xmemcpy(data->product_revision_level, (__xdata void *)"A0 ", 4); 122 | 123 | goto done; 124 | } else if(current_op == SCSI_OPERATION_READ_CAPACITY) { 125 | __xdata struct scsi_read_capacity_data *data = 126 | (__xdata struct scsi_read_capacity_data *)buffer; 127 | 128 | data->returned_logical_block_address = bswap32(uf2_config.total_sectors - 1); 129 | data->block_length_in_bytes = bswap32(512); 130 | 131 | goto done; 132 | } else if(current_op == SCSI_OPERATION_READ_10) { 133 | if(blocks_left == 0) 134 | return false; 135 | if(length != 512) 136 | return false; 137 | if(!uf2_fat_read(block_index, buffer)) 138 | return false; 139 | 140 | block_index++; 141 | blocks_left--; 142 | if(blocks_left > 0) 143 | return true; 144 | 145 | goto done; 146 | } 147 | 148 | return false; 149 | 150 | done: 151 | current_op = 0; 152 | return true; 153 | } 154 | -------------------------------------------------------------------------------- /firmware/library/usbmassstor.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #pragma save 4 | #pragma nooverlay 5 | bool usb_mass_storage_bbb_setup(usb_mass_storage_bbb_state_t *state, 6 | __xdata struct usb_req_setup *request) { 7 | if(request->bmRequestType == (USB_RECIP_IFACE|USB_TYPE_CLASS|USB_DIR_OUT) && 8 | request->bRequest == USB_REQ_MASS_STORAGE_BOMSR && 9 | request->wValue == 0 && request->wIndex == state->interface && request->wLength == 0) { 10 | state->_state = USB_MASS_STORAGE_BBB_STATE_COMMAND; 11 | ACK_EP0(); 12 | return true; 13 | } 14 | 15 | // USB MS BBB 3.2 says that "devices that do not support multiple LUNs may STALL this command", 16 | // but actually doing this appears to crash Linux's USB stack so hard that HC dies. Sigh. 17 | if(request->bmRequestType == (USB_RECIP_IFACE|USB_TYPE_CLASS|USB_DIR_IN) && 18 | request->bRequest == USB_REQ_MASS_STORAGE_GET_MAX_LUN && 19 | request->wValue == 0 && request->wIndex == state->interface && request->wLength == 1) { 20 | EP0BUF[0] = state->max_lun; 21 | SETUP_EP0_IN_BUF(1); 22 | return true; 23 | } 24 | 25 | return false; 26 | } 27 | #pragma restore 28 | 29 | bool usb_mass_storage_bbb_bulk_out(usb_mass_storage_bbb_state_t *state, 30 | __xdata const uint8_t *data, 31 | uint16_t length) { 32 | usb_mass_storage_cbw_t *cbw = (usb_mass_storage_cbw_t *)data; 33 | 34 | if(state->_state == USB_MASS_STORAGE_BBB_STATE_COMMAND) { 35 | // USB MS BBB 6.2.1: check for Valid CBW. 36 | if(length != sizeof(struct usb_mass_storage_cbw)) 37 | return false; 38 | if(cbw->dCBWSignature != USB_MASS_STORAGE_CBW_SIGNATURE) 39 | return false; 40 | 41 | // USB MS BBB 6.2.2: check for Meaningful CBW. 42 | // USB MS BBB 6.4 says that "the response of a device to a CBW that is not meaningful 43 | /// is not specified"; we opt to treat such CBWs the same as CBWs that are not valid. 44 | if(cbw->bmCBWFlags & USB_MASS_STORAGE_CBW_RESERVED_FLAGS) 45 | return false; 46 | if(cbw->bCBWCBLength > 16) 47 | return false; 48 | if(cbw->bCBWLUN > state->max_lun) 49 | return false; 50 | 51 | state->_success = state->command(cbw->bCBWLUN, cbw->CBWCB, cbw->bCBWCBLength); 52 | 53 | state->_tag = cbw->dCBWTag; 54 | state->_lun = cbw->bCBWLUN; 55 | state->_residue = 0; 56 | if(cbw->dCBWDataTransferLength == 0) { 57 | // USB MS BBB 5.1: "if [dCBWDataTransferLength] is zero, the device and the host 58 | // shall transfer no data between the CBW and the associated CSW, and the device 59 | // shall ignore the value of the Direction bit in bmCBWFlags. 60 | state->_state = USB_MASS_STORAGE_BBB_STATE_STATUS; 61 | } else { 62 | state->_data_in = !!(cbw->bmCBWFlags & USB_MASS_STORAGE_CBW_FLAG_DATA_IN); 63 | state->_data_length = cbw->dCBWDataTransferLength; 64 | if(state->_success) { 65 | state->_state = state->_data_in ? USB_MASS_STORAGE_BBB_STATE_DATA_IN 66 | : USB_MASS_STORAGE_BBB_STATE_DATA_OUT; 67 | } else { 68 | state->_residue = state->_data_length; 69 | state->_state = state->_data_in ? USB_MASS_STORAGE_BBB_STATE_FAIL_IN 70 | : USB_MASS_STORAGE_BBB_STATE_FAIL_OUT; 71 | } 72 | } 73 | return true; 74 | } 75 | 76 | if(state->_state == USB_MASS_STORAGE_BBB_STATE_DATA_OUT || 77 | state->_state == USB_MASS_STORAGE_BBB_STATE_FAIL_OUT) { 78 | if(state->_state == USB_MASS_STORAGE_BBB_STATE_DATA_OUT) { 79 | if(!state->data_out(state->_lun, data, length)) { 80 | state->_residue = state->_data_length; 81 | state->_success = false; 82 | state->_state = USB_MASS_STORAGE_BBB_STATE_FAIL_OUT; 83 | } 84 | } 85 | state->_data_length -= length; 86 | if(state->_data_length == 0) { 87 | state->_state = USB_MASS_STORAGE_BBB_STATE_STATUS; 88 | } 89 | return true; 90 | } 91 | 92 | return false; 93 | } 94 | 95 | bool usb_mass_storage_bbb_bulk_in(usb_mass_storage_bbb_state_t *state, 96 | __xdata uint8_t *data, 97 | __xdata uint16_t *length) { 98 | if(state->_state == USB_MASS_STORAGE_BBB_STATE_DATA_IN || 99 | state->_state == USB_MASS_STORAGE_BBB_STATE_FAIL_IN) { 100 | *length = (state->_data_length > state->max_in_size) 101 | ? state->max_in_size : state->_data_length; 102 | if(state->_state == USB_MASS_STORAGE_BBB_STATE_DATA_IN) { 103 | if(!state->data_in(state->_lun, data, *length)) { 104 | state->_residue = state->_data_length; 105 | state->_success = false; 106 | state->_state = USB_MASS_STORAGE_BBB_STATE_FAIL_IN; 107 | } 108 | } 109 | state->_data_length -= *length; 110 | if(state->_data_length == 0) { 111 | state->_state = USB_MASS_STORAGE_BBB_STATE_STATUS; 112 | } 113 | return true; 114 | } 115 | 116 | if(state->_state == USB_MASS_STORAGE_BBB_STATE_STATUS) { 117 | usb_mass_storage_csw_t *csw = (usb_mass_storage_csw_t *)data; 118 | *length = sizeof(struct usb_mass_storage_csw); 119 | csw->dCSWSignature = USB_MASS_STORAGE_CSW_SIGNATURE; 120 | csw->dCSWTag = state->_tag; 121 | csw->dCSWDataResidue = state->_residue; 122 | csw->bCSWStatus = state->_success ? USB_MASS_STORAGE_CSW_STATUS_PASSED 123 | : USB_MASS_STORAGE_CSW_STATUS_FAILED; 124 | state->_state = USB_MASS_STORAGE_BBB_STATE_COMMAND; 125 | return true; 126 | } 127 | 128 | return false; 129 | } 130 | -------------------------------------------------------------------------------- /firmware/library/include/fx2debug.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2DEBUG_H 2 | #define FX2DEBUG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef DOXYGEN 10 | 11 | #define _DEBUG_FN_DIV(a, b) (((a)+(b)/2+1)/(b)) 12 | 13 | // This implementation not only keeps overhead to absolute minimum, but it also takes great care 14 | // to execute in constant time per bit to minimize clock drift. 15 | #define _DEBUG_FN(retty, name, argty, tx, baud) \ 16 | retty name(argty c) __naked { \ 17 | c; \ 18 | __asm; \ 19 | inv_baud = _DEBUG_FN_DIV(12000000, baud) \ 20 | overhead = 25 \ 21 | \ 22 | push ar7 \ 23 | push ar6 \ 24 | push ar5 \ 25 | push ar4 \ 26 | push ar3 \ 27 | push ar2 \ 28 | \ 29 | mov r2, _ASM_HASH (00002$-00001$) \ 30 | mov r3, _ASM_HASH 0 \ 31 | mov r4, dpl \ 32 | mov r5, dpl \ 33 | \ 34 | mov dptr, _ASM_HASH _CPUCS \ 35 | movx a, @dptr \ 36 | mov dptr, _ASM_HASH _DEBUG_FN_DIV((inv_baud>>0)-overhead, 4) \ 37 | jb acc.4, 00000$ /* _CLKSPD1 */ \ 38 | mov dptr, _ASM_HASH _DEBUG_FN_DIV((inv_baud>>1)-overhead, 4) \ 39 | jb acc.3, 00000$ /* _CLKSPD0 */ \ 40 | mov dptr, _ASM_HASH _DEBUG_FN_DIV((inv_baud>>2)-overhead, 4) \ 41 | 00000$: \ 42 | mov r6, dpl \ 43 | mov r7, dph \ 44 | \ 45 | mov a, r3 \ 46 | 00001$: \ 47 | clr _ASM_REG(tx) ; 2c \ 48 | sjmp 00003$ ; 3c \ 49 | 00002$: \ 50 | mov _ASM_REG(tx), c \ 51 | sjmp 00003$ \ 52 | mov _ASM_REG(tx), c \ 53 | sjmp 00003$ \ 54 | mov _ASM_REG(tx), c \ 55 | sjmp 00003$ \ 56 | mov _ASM_REG(tx), c \ 57 | sjmp 00003$ \ 58 | mov _ASM_REG(tx), c \ 59 | sjmp 00003$ \ 60 | mov _ASM_REG(tx), c \ 61 | sjmp 00003$ \ 62 | mov _ASM_REG(tx), c \ 63 | sjmp 00003$ \ 64 | mov _ASM_REG(tx), c \ 65 | sjmp 00003$ \ 66 | setb _ASM_REG(tx) \ 67 | sjmp 00003$ \ 68 | sjmp 00004$ \ 69 | \ 70 | 00003$: \ 71 | add a, r2 ; 1c \ 72 | mov r3, a ; 1c \ 73 | \ 74 | mov dpl, r6 ; 2c \ 75 | mov dph, r7 ; 2c \ 76 | lcall _delay_4c \ 77 | \ 78 | mov a, r4 ; 1c \ 79 | rrc a ; 1c \ 80 | mov r4, a ; 1c \ 81 | \ 82 | mov dptr, _ASM_HASH (00001$) ; 3c \ 83 | mov a, r3 ; 1c \ 84 | jmp @a+dptr ; 3c \ 85 | \ 86 | 00004$: \ 87 | mov dpl, r5 \ 88 | \ 89 | pop ar2 \ 90 | pop ar3 \ 91 | pop ar4 \ 92 | pop ar5 \ 93 | pop ar6 \ 94 | pop ar7 \ 95 | _ASM_RET \ 96 | __endasm; \ 97 | } 98 | 99 | #endif 100 | 101 | /** 102 | * This macro defines a function `void name(uint8_t c)` that implements a robust blocking serial 103 | * transmitter for debug output. The `tx` parameter may point to any pin, and is defined in 104 | * the format `Pxn`. The serial format is fixed at 8 data bits, no parity, 1 stop bit, and 105 | * the baud rate is configurable, up to 230400 at 48 MHz, up to 115200 at 24 MHz, and up to 106 | * 57600 at 12 MHz. 107 | * 108 | * For example, invoking the macro as `DEFINE_DEBUG_FN(tx_byte, PA0, 57600)` defines a routine 109 | * `void tx_byte(uint8_t c)` that assumes an UART receiver's RX pin is connected to A0. 110 | */ 111 | #define DEFINE_DEBUG_FN(name, tx, baud) \ 112 | _DEBUG_FN(void, name, uint8_t, tx, baud) 113 | 114 | /** 115 | * Same as `DEFINE_DEBUG_FN()`, but defines an `int putchar(int c)` routine that can be used with 116 | * the `printf` family of functions. (The C standard requires `putchar` to have the specified 117 | * signature). 118 | */ 119 | #if (__SDCC_VERSION_MAJOR > 3) || ((__SDCC_VERSION_MAJOR == 3) && (__SDCC_VERSION_MINOR > 6)) 120 | #define DEFINE_DEBUG_PUTCHAR_FN(tx, baud) \ 121 | _DEBUG_FN(int, putchar, int, tx, baud) 122 | #else 123 | #define DEFINE_DEBUG_PUTCHAR_FN(tx, baud) \ 124 | _DEBUG_FN(void, putchar, char, tx, baud) 125 | #endif 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /firmware/library/include/usbdfu.h: -------------------------------------------------------------------------------- 1 | #ifndef USBDFU_H 2 | #define USBDFU_H 3 | 4 | #include 5 | 6 | enum /*usb_descriptor*/ { 7 | USB_DESC_DFU_FUNCTIONAL = 0x21, 8 | }; 9 | 10 | enum usb_dfu_attributes { 11 | USB_DFU_ATTR_CAN_DNLOAD = 0b00000001, 12 | USB_DFU_ATTR_CAN_UPLOAD = 0b00000010, 13 | USB_DFU_ATTR_MANIFESTATION_TOLERANT = 0b00000100, 14 | USB_DFU_ATTR_WILL_DETACH = 0b00001000, 15 | }; 16 | 17 | struct usb_dfu_desc_functional { 18 | uint8_t bLength; 19 | uint8_t bDescriptorType; 20 | uint8_t bmAttributes; 21 | uint16_t wDetachTimeOut; 22 | uint16_t wTransferSize; 23 | uint16_t bcdDFUVersion; 24 | }; 25 | 26 | typedef __code const struct usb_dfu_desc_functional 27 | usb_dfu_desc_functional_c; 28 | 29 | enum { 30 | USB_IFACE_SUBCLASS_DFU = 0x01, 31 | 32 | USB_IFACE_PROTOCOL_DFU_RUNTIME = 0x01, 33 | USB_IFACE_PROTOCOL_DFU_UPGRADE = 0x02, 34 | }; 35 | 36 | enum usb_dfu_request { 37 | USB_DFU_REQ_DETACH = 0, 38 | USB_DFU_REQ_DNLOAD = 1, 39 | USB_DFU_REQ_UPLOAD = 2, 40 | USB_DFU_REQ_GETSTATUS = 3, 41 | USB_DFU_REQ_CLRSTATUS = 4, 42 | USB_DFU_REQ_GETSTATE = 5, 43 | USB_DFU_REQ_ABORT = 6, 44 | }; 45 | 46 | enum usb_dfu_state { 47 | /// Device is running its normal application. 48 | USB_DFU_STATE_appIDLE = 0, 49 | /// Device is running its normal application, has received the DFU_DETACH request, and 50 | /// is waiting for a USB reset. 51 | USB_DFU_STATE_appDETACH = 1, 52 | /// Device is operating in the DFU mode and is waiting for requests. 53 | USB_DFU_STATE_dfuIDLE = 2, 54 | /// Device has received a block and is waiting for the host to solicit the status 55 | /// via DFU_GETSTATUS. 56 | USB_DFU_STATE_dfuDNLOAD_SYNC = 3, 57 | /// Device is programming a control-write block into its nonvolatile memories. 58 | USB_DFU_STATE_dfuDNBUSY = 4, 59 | /// Device is processing a download operation. Expecting DFU_DNLOAD requests. 60 | USB_DFU_STATE_dfuDNLOAD_IDLE = 5, 61 | /// Device has received the final block of firmware from the host and is waiting for receipt of 62 | /// DFU_GETSTATUS to begin the Manifestation phase; or device has completed the Manifestation 63 | /// phase and is waiting for receipt of DFU_GETSTATUS. (Devices that can enter this state after 64 | /// the Manifestation phase set bmAttributes bit bitManifestationTolerant to 1.) 65 | USB_DFU_STATE_dfuMANIFEST_SYNC = 6, 66 | /// Device is in the Manifestation phase. (Not all devices will be able to respond to 67 | /// DFU_GETSTATUS when in this state.) 68 | USB_DFU_STATE_dfuMANIFEST = 7, 69 | /// Device has programmed its memories and is waiting for a USB reset or a power on reset. 70 | /// (Devices that must enter this state clear bitManifestationTolerant to 0.) 71 | USB_DFU_STATE_dfuMANIFEST_WAIT_RESET = 8, 72 | /// The device is processing an upload operation. Expecting DFU_UPLOAD requests. 73 | USB_DFU_STATE_dfuUPLOAD_IDLE = 9, 74 | /// An error has occurred. Awaiting the DFU_CLRSTATUS request. 75 | USB_DFU_STATE_dfuERROR = 10, 76 | }; 77 | 78 | enum usb_dfu_status { 79 | /// No error condition is present. 80 | USB_DFU_STATUS_OK = 0x00, 81 | /// File is not targeted for use by this device. 82 | USB_DFU_STATUS_errTARGET = 0x01, 83 | /// File is for this device but fails some vendor-specific verification test. 84 | USB_DFU_STATUS_errFILE = 0x02, 85 | /// Device is unable to write memory. 86 | USB_DFU_STATUS_errWRITE = 0x03, 87 | /// Memory erase function failed. 88 | USB_DFU_STATUS_errERASE = 0x04, 89 | /// Memory erase check failed. 90 | USB_DFU_STATUS_errCHECK_ERASED = 0x05, 91 | /// Program memory function failed. 92 | USB_DFU_STATUS_errPROG = 0x06, 93 | /// Programmed memory failed verification. 94 | USB_DFU_STATUS_errVERIFY = 0x07, 95 | /// Cannot program memory due to received address that is out of range. 96 | USB_DFU_STATUS_errADDRESS = 0x08, 97 | /// Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet. 98 | USB_DFU_STATUS_errNOTDONE = 0x09, 99 | /// Device’s firmware is corrupt. It cannot return to run-time (non-DFU) operations. 100 | USB_DFU_STATUS_errFIRMWARE = 0x0A, 101 | /// iString indicates a vendor-specific error. 102 | USB_DFU_STATUS_errVENDOR = 0x0B, 103 | /// Device detected unexpected USB reset signaling. 104 | USB_DFU_STATUS_errUSBR = 0x0C, 105 | /// Device detected unexpected power on reset. 106 | USB_DFU_STATUS_errPOR = 0x0D, 107 | /// Something went wrong, but the device does not know what it was. 108 | USB_DFU_STATUS_errUNKNOWN = 0x0E, 109 | /// Device stalled an unexpected request. 110 | USB_DFU_STATUS_errSTALLEDPKT = 0x0F, 111 | }; 112 | 113 | #define USB_DFU_STATUS_NAMES \ 114 | "No error condition is present", \ 115 | "File is not targeted for use by this device", \ 116 | "File is for this device but fails some vendor-specific verification test", \ 117 | "Device is unable to write memory", \ 118 | "Memory erase function failed", \ 119 | "Memory erase check failed", \ 120 | "Program memory function failed", \ 121 | "Programmed memory failed verification", \ 122 | "Cannot program memory due to received address that is out of range", \ 123 | "Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet", \ 124 | "Device’s firmware is corrupt. It cannot return to run-time (non-DFU) operations", \ 125 | "iString indicates a vendor-specific error", \ 126 | "Device detected unexpected USB reset signaling", \ 127 | "Device detected unexpected power on reset", \ 128 | "Something went wrong, but the device does not know what it was", \ 129 | "Device stalled an unexpected request" 130 | 131 | typedef enum usb_dfu_status 132 | usb_dfu_status_t; 133 | 134 | struct usb_dfu_req_get_status { 135 | uint8_t bStatus; 136 | uint8_t bwPollTimeout; 137 | uint16_t bwPollTimeoutHigh; 138 | uint8_t bState; 139 | uint8_t iString; 140 | }; 141 | 142 | #endif 143 | -------------------------------------------------------------------------------- /firmware/library/delay.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Spin for exactly max(24, DP*4) cycles (i.e. max(96, DP*16) clocks), including the call of 6 | // this function. The implementation is a bit complicated, but the calculations in the caller 7 | // are simpler. 8 | // 9 | // This was originally optimized by @whitequark, and then improved by @8051Enthusiast. 10 | void delay_4c(uint16_t count) __naked { 11 | count; 12 | __asm 13 | ; (ljmp delay_4c) ; 0c > 4c 14 | mov a, dph ; 2c 15 | jnz 00001$ ; 3c > 9c [A] 16 | mov a, dpl ; 2c 17 | add a, #-(20/4+1) ; 2c 18 | jc 00000$ ; 3c > 16c [B] 19 | _ASM_RET ; 4c > 20c 20 | 00000$: 21 | cjne a, #0, 00004$ ; [B] > 4c > 20c [H] 22 | _ASM_RET ; 4c > 24c 23 | 00001$: 24 | mov a, dpl ; [A] > 2c 25 | jz 00003$ ; 3c > 14c [C] 26 | 00002$: 27 | djnz acc, 00002$ ; [C] > 4Lc > (14+4L)c [D] 28 | ; [E] > 1016c > (14+1024H+4L)c [G] 29 | 00003$: 30 | nop ; [D] > 1c 31 | mov acc, #-2 ; 3c 32 | djnz dph, 00002$ ; 4c > (14+8+4L) [E] 33 | mov a, #-((24+4)/4) ; 2c > (24+4L) [F] 34 | ; [G] > 1c 35 | ; 3c 36 | ; 4c > (14+8+1024H+4L)c [E] 37 | ; 2c > (24+1024H+4L)c [F] 38 | 00004$: 39 | djnz acc, 00004$ ; [H] > 4Lc 40 | _ASM_RET ; 4c > (24+4L)c 41 | ; [F] > 996c 42 | ; 4c > (1024H+4L)c 43 | __endasm; 44 | } 45 | 46 | void delay_us_overhead(uint16_t count, uint8_t caller_overh) __naked __reentrant { 47 | count; 48 | caller_overh; 49 | __asm; 50 | ; (ljmp delay_us_overhead) ; 4c 51 | 52 | // prolog 53 | ar7 = 0x07 54 | ar6 = 0x06 55 | ar1 = 0x01 56 | ar0 = 0x00 57 | // count dph:dpl 58 | // iters r7:r6 59 | // overh r1 60 | // cpucs r0 61 | push ar7 ; 2c 62 | push ar6 ; 2c 63 | push ar1 ; 2c 64 | push ar0 ; 2c 65 | mov r6, dpl ; 2c 66 | mov r7, dph ; 2c 67 | 68 | // iters = count * 3 69 | mov a, r6 ; 1c 70 | add a, r6 ; 1c 71 | mov r0, a ; 1c 72 | mov a, r7 ; 1c 73 | rlc a ; 1c 74 | mov r1, a ; 1c 75 | mov a, r6 ; 1c 76 | add a, r0 ; 1c 77 | mov r6, a ; 1c 78 | mov a, r7 ; 1c 79 | addc a, r1 ; 1c 80 | mov r7, a ; 1c 81 | 82 | // cpucs = CPUCS 83 | mov dptr, #_CPUCS ; 3c 84 | movx a, @dptr ; 2c 85 | mov r0, a ; 1c 86 | 87 | // overh = (48 MHz cycle tally) 88 | mov r1, #(40+36) ; 2c 89 | 90 | // if(cpucs & _CLKSPD1) skip 91 | jb acc.4, 00000$ ; 4c => 40c 92 | 93 | // iters >>= 1 94 | clr c ; 1c 95 | mov a, r7 ; 1c 96 | rrc a ; 1c 97 | xch a, r6 ; 1c 98 | rrc a ; 1c 99 | xch a, r6 ; 1c 100 | mov r7, a ; 1c 101 | 102 | // overh = (24 MHz cycle tally) 103 | mov r1, #(40+14+36) ; 2c 104 | 105 | // if(cpucs & _CLKSPD0) skip 106 | mov a, r0 ; 1c 107 | jb acc.3, 00000$ ; 4c => 14c 108 | 109 | // iters >>= 1 110 | clr c ; 1c 111 | mov a, r7 ; 1c 112 | rrc a ; 1c 113 | xch a, r6 ; 1c 114 | rrc a ; 1c 115 | xch a, r6 ; 1c 116 | mov r7, a ; 1c 117 | 118 | // overh = (12 MHz cycle tally) 119 | mov r1, #(40+14+9+36) ; 2c => 9c 120 | 121 | 00000$: 122 | // overh = (overh + caller_overh) >> 2 123 | mov a, sp ; 2c 124 | add a, #-6 ; 2c 125 | mov r0, a ; 1c 126 | mov a, r1 ; 1c 127 | add a, @r0 ; 1c 128 | clr c ; 1c 129 | rrc a ; 1c 130 | clr c ; 1c 131 | rrc a ; 1c 132 | mov r1, a ; 1c 133 | 134 | // iters -= overh 135 | clr c ; 1c 136 | mov a, r6 ; 1c 137 | subb a, r1 ; 1c 138 | mov dpl, a ; 1c 139 | mov a, r7 ; 1c 140 | subb a, #0 ; 2c 141 | mov dph, a ; 1c 142 | 143 | pop ar0 ; 2c 144 | pop ar1 ; 2c 145 | pop ar6 ; 2c 146 | pop ar7 ; 2c 147 | 148 | // if(underflow) return 149 | jnc 00001$ ; 4c => 36c 150 | _ASM_RET 151 | 152 | 00001$: 153 | // else delay_4c(iters) 154 | ljmp _delay_4c 155 | __endasm; 156 | } 157 | 158 | void delay_us(uint16_t count) __naked { 159 | count; 160 | __asm; 161 | ; (mov dptr, #?) ; 3c 162 | ; (lcall delay_us) ; 4c 163 | mov a, #17 ; 2c 164 | push acc ; 2c 165 | lcall _delay_us_overhead 166 | dec sp ; 2c 167 | _ASM_RET ; 4c => 17c 168 | __endasm; 169 | } 170 | 171 | void delay_ms(uint16_t count) __naked { 172 | count; 173 | __asm; 174 | // prolog 175 | ar7 = 0x07 176 | ar6 = 0x06 177 | push ar7 178 | push ar6 179 | mov r6, dpl 180 | mov r7, dph 181 | 182 | // overhead = (cycle tally) 183 | mov a, #17 184 | push acc 185 | 186 | 00000$: 187 | mov a, r6 ; 1c 188 | orl a, r7 ; 1c 189 | jz 00002$ ; 3c 190 | 191 | dec r6 ; 1c 192 | cjne r6, #0xff, 00001$ ; 4c 193 | dec r7 ; 1c 194 | 00001$: 195 | 196 | // count_us = 1000 197 | mov dptr, #1000 ; 3c 198 | lcall _delay_us_overhead 199 | 200 | sjmp 00000$ ; 3c => 17c 201 | 202 | 00002$: 203 | dec sp 204 | 205 | // epilog 206 | pop ar6 207 | pop ar7 208 | _ASM_RET 209 | __endasm; 210 | } 211 | -------------------------------------------------------------------------------- /firmware/boot-cypress/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | usb_desc_device_c usb_device = { 7 | .bLength = sizeof(struct usb_desc_device), 8 | .bDescriptorType = USB_DESC_DEVICE, 9 | .bcdUSB = 0x0200, 10 | .bDeviceClass = USB_DEV_CLASS_VENDOR, 11 | .bDeviceSubClass = USB_DEV_SUBCLASS_VENDOR, 12 | .bDeviceProtocol = USB_DEV_PROTOCOL_VENDOR, 13 | .bMaxPacketSize0 = 64, 14 | .idVendor = 0x04b4, 15 | .idProduct = 0x8613, 16 | .bcdDevice = 0x0000, 17 | .iManufacturer = 1, 18 | .iProduct = 2, 19 | .iSerialNumber = 0, 20 | .bNumConfigurations = 1, 21 | }; 22 | 23 | usb_desc_interface_c usb_interface = { 24 | .bLength = sizeof(struct usb_desc_interface), 25 | .bDescriptorType = USB_DESC_INTERFACE, 26 | .bInterfaceNumber = 0, 27 | .bAlternateSetting = 0, 28 | .bNumEndpoints = 0, 29 | .bInterfaceClass = USB_IFACE_CLASS_VENDOR, 30 | .bInterfaceSubClass = USB_IFACE_SUBCLASS_VENDOR, 31 | .bInterfaceProtocol = USB_IFACE_PROTOCOL_VENDOR, 32 | .iInterface = 0, 33 | }; 34 | 35 | usb_configuration_c usb_config = { 36 | { 37 | .bLength = sizeof(struct usb_desc_configuration), 38 | .bDescriptorType = USB_DESC_CONFIGURATION, 39 | .bNumInterfaces = 1, 40 | .bConfigurationValue = 1, 41 | .iConfiguration = 0, 42 | .bmAttributes = USB_ATTR_RESERVED_1, 43 | .bMaxPower = 50, 44 | }, 45 | { 46 | { .interface = &usb_interface }, 47 | { 0 } 48 | } 49 | }; 50 | 51 | // check for "earlier than 3.5", but version macros shipped in 3.6 52 | #if !defined(__SDCC_VERSION_MAJOR) 53 | __code const struct usb_configuration *__code const usb_configs[] = { 54 | #else 55 | usb_configuration_set_c usb_configs[] = { 56 | #endif 57 | &usb_config, 58 | }; 59 | 60 | usb_ascii_string_c usb_strings[] = { 61 | [0] = "whitequark@whitequark.org", 62 | [1] = "FX2 series Cypress-class bootloader", 63 | }; 64 | 65 | usb_descriptor_set_c usb_descriptor_set = { 66 | .device = &usb_device, 67 | .config_count = ARRAYSIZE(usb_configs), 68 | .configs = usb_configs, 69 | .string_count = ARRAYSIZE(usb_strings), 70 | .strings = usb_strings, 71 | }; 72 | 73 | enum { 74 | USB_REQ_CYPRESS_EEPROM_SB = 0xA2, 75 | USB_REQ_CYPRESS_EXT_RAM = 0xA3, 76 | USB_REQ_CYPRESS_RENUMERATE = 0xA8, 77 | USB_REQ_CYPRESS_EEPROM_DB = 0xA9, 78 | USB_REQ_LIBFX2_PAGE_SIZE = 0xB0, 79 | }; 80 | 81 | // We perform lengthy operations in the main loop to avoid hogging the interrupt. 82 | // This flag is used for synchronization between the main loop and the ISR; 83 | // to allow new SETUP requests to arrive while the previous one is still being 84 | // handled (with all data received), the flag should be reset as soon as 85 | // the entire SETUP request is parsed. 86 | volatile bool pending_setup; 87 | 88 | void handle_usb_setup(__xdata struct usb_req_setup *req) { 89 | req; 90 | if(pending_setup) { 91 | STALL_EP0(); 92 | } else { 93 | pending_setup = true; 94 | } 95 | } 96 | 97 | // The EEPROM write cycle time is the same for a single byte or a single page; 98 | // it is therefore far more efficient to write EEPROMs in entire pages. 99 | // Unfortunately, there is no way to discover page size if it is not known 100 | // beforehand. We play it safe and write individual bytes unless the page size 101 | // was set explicitly via a libfx2-specific request, such that Cypress vendor 102 | // requests A2/A9 work the same as in Cypress libraries by default. 103 | uint8_t page_size = 0; // log2(page size in bytes) 104 | 105 | void handle_pending_usb_setup(void) { 106 | __xdata struct usb_req_setup *req = (__xdata struct usb_req_setup *)SETUPDAT; 107 | 108 | if(req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_OUT) && 109 | req->bRequest == USB_REQ_CYPRESS_RENUMERATE) { 110 | pending_setup = false; 111 | 112 | USBCS |= _DISCON; 113 | delay_ms(10); 114 | USBCS &= ~_DISCON; 115 | 116 | return; 117 | } 118 | 119 | if(req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_OUT) && 120 | req->bRequest == USB_REQ_LIBFX2_PAGE_SIZE) { 121 | page_size = req->wValue; 122 | pending_setup = false; 123 | 124 | ACK_EP0(); 125 | return; 126 | } 127 | 128 | if((req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_IN) || 129 | req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_OUT)) && 130 | (req->bRequest == USB_REQ_CYPRESS_EEPROM_SB || 131 | req->bRequest == USB_REQ_CYPRESS_EEPROM_DB)) { 132 | bool arg_read = (req->bmRequestType & USB_DIR_IN); 133 | bool arg_dbyte = (req->bRequest == USB_REQ_CYPRESS_EEPROM_DB); 134 | uint8_t arg_chip = arg_dbyte ? 0x51 : 0x50; 135 | uint16_t arg_addr = req->wValue; 136 | uint16_t arg_len = req->wLength; 137 | pending_setup = false; 138 | 139 | while(arg_len > 0) { 140 | uint8_t len = arg_len < 64 ? arg_len : 64; 141 | 142 | if(arg_read) { 143 | while(EP0CS & _BUSY); 144 | if(!eeprom_read(arg_chip, arg_addr, EP0BUF, len, arg_dbyte)) { 145 | STALL_EP0(); 146 | break; 147 | } 148 | SETUP_EP0_IN_BUF(len); 149 | } else { 150 | SETUP_EP0_OUT_BUF(); 151 | while(EP0CS & _BUSY); 152 | if(!eeprom_write(arg_chip, arg_addr, EP0BUF, len, arg_dbyte, page_size, 153 | /*timeout=*/166)) { 154 | STALL_EP0(); 155 | break; 156 | } 157 | ACK_EP0(); 158 | } 159 | 160 | arg_len -= len; 161 | arg_addr += len; 162 | } 163 | 164 | return; 165 | } 166 | 167 | if((req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_IN) || 168 | req->bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_VENDOR|USB_DIR_OUT)) && 169 | req->bRequest == USB_REQ_CYPRESS_EXT_RAM) { 170 | bool arg_read = (req->bmRequestType & USB_DIR_IN); 171 | uint16_t arg_addr = req->wValue; 172 | uint16_t arg_len = req->wLength; 173 | pending_setup = false; 174 | 175 | while(arg_len > 0) { 176 | uint8_t len = arg_len < 64 ? arg_len : 64; 177 | 178 | if(arg_read) { 179 | while(EP0CS & _BUSY); 180 | xmemcpy(EP0BUF, (__xdata void *)arg_addr, len); 181 | SETUP_EP0_IN_BUF(len); 182 | } else { 183 | SETUP_EP0_OUT_BUF(); 184 | while(EP0CS & _BUSY); 185 | xmemcpy((__xdata void *)arg_addr, EP0BUF, arg_len); 186 | ACK_EP0(); 187 | } 188 | 189 | arg_len -= len; 190 | arg_addr += len; 191 | } 192 | 193 | return; 194 | } 195 | 196 | pending_setup = false; 197 | STALL_EP0(); 198 | } 199 | 200 | int main(void) { 201 | CPUCS = _CLKOE|_CLKSPD1; 202 | 203 | // Don't re-enumerate. `fx2tool -B` will load this firmware to access EEPROM, and it 204 | // expects to be able to keep accessing the device. If you are using this firmware 205 | // in your own code, set /*diconnect=*/true. 206 | usb_init(/*disconnect=*/false); 207 | 208 | while(1) { 209 | if(pending_setup) 210 | handle_pending_usb_setup(); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /firmware/library/include/usb.h: -------------------------------------------------------------------------------- 1 | #ifndef USB_H 2 | #define USB_H 3 | 4 | #include 5 | 6 | // Disable 'type of variable X is struct with flexible array field' as it fires 7 | // on usb_desc_langid and usb_desc_string. 8 | #pragma disable_warning 219 9 | 10 | enum usb_direction { 11 | USB_DIR_OUT = 0b00000000, 12 | USB_DIR_IN = 0b10000000, 13 | 14 | USB_DIR_MASK = 0b10000000, 15 | }; 16 | 17 | enum usb_type { 18 | USB_TYPE_STANDARD = 0b00000000, 19 | USB_TYPE_CLASS = 0b00100000, 20 | USB_TYPE_VENDOR = 0b01000000, 21 | 22 | USB_TYPE_MASK = 0b01100000, 23 | }; 24 | 25 | enum usb_recipient { 26 | USB_RECIP_DEVICE = 0b00000000, 27 | USB_RECIP_IFACE = 0b00000001, 28 | USB_RECIP_ENDPT = 0b00000010, 29 | USB_RECIP_OTHER = 0b00000011, 30 | 31 | USB_RECIP_MASK = 0b00001111, 32 | }; 33 | 34 | enum usb_request { 35 | USB_REQ_GET_STATUS = 0, 36 | USB_REQ_CLEAR_FEATURE = 1, 37 | USB_REQ_SET_FEATURE = 3, 38 | USB_REQ_SET_ADDRESS = 5, 39 | USB_REQ_GET_DESCRIPTOR = 6, 40 | USB_REQ_SET_DESCRIPTOR = 7, 41 | USB_REQ_GET_CONFIGURATION = 8, 42 | USB_REQ_SET_CONFIGURATION = 9, 43 | USB_REQ_GET_INTERFACE = 10, 44 | USB_REQ_SET_INTERFACE = 11, 45 | USB_REQ_SYNCH_FRAME = 12, 46 | }; 47 | 48 | enum usb_descriptor { 49 | USB_DESC_DEVICE = 1, 50 | USB_DESC_CONFIGURATION = 2, 51 | USB_DESC_STRING = 3, 52 | USB_DESC_URL = USB_DESC_STRING, 53 | USB_DESC_INTERFACE = 4, 54 | USB_DESC_ENDPOINT = 5, 55 | USB_DESC_DEVICE_QUALIFIER = 6, 56 | USB_DESC_OTHER_SPEED_CONFIGURATION = 7, 57 | USB_DESC_INTERFACE_POWER = 8, 58 | USB_DESC_BINARY_OBJECT_STORE = 15, 59 | USB_DESC_DEVICE_CAPABILITY = 16, 60 | }; 61 | 62 | enum usb_device_capability { 63 | USB_DEV_CAP_PLATFORM = 5, 64 | }; 65 | 66 | enum usb_feature { 67 | USB_FEAT_DEVICE_REMOTE_WAKEUP = 1, 68 | USB_FEAT_ENDPOINT_HALT = 0, 69 | USB_FEAT_TEST_MODE = 2, 70 | }; 71 | 72 | enum usb_attributes { 73 | USB_ATTR_RESERVED_1 = 0b10000000, 74 | USB_ATTR_SELF_POWERED = 0b01000000, 75 | USB_ATTR_REMOTE_WAKEUP = 0b00100000, 76 | }; 77 | 78 | enum usb_transfer_type { 79 | USB_XFER_CONTROL = 0b00000000, 80 | USB_XFER_ISOCHRONOUS = 0b00000001, 81 | USB_XFER_BULK = 0b00000010, 82 | USB_XFER_INTERRUPT = 0b00000011, 83 | 84 | USB_XFER_MASK = 0b00000011, 85 | }; 86 | 87 | enum usb_synchronization_type { 88 | USB_SYNC_NONE = 0b00000000, 89 | USB_SYNC_ASYNCHRONOUS = 0b00000100, 90 | USB_SYNC_ADAPTIVE = 0b00001000, 91 | USB_SYNC_SYNCHRONOUS = 0b00001100, 92 | 93 | USB_SYNC_MASK = 0b00001100, 94 | }; 95 | 96 | enum usb_usage_type { 97 | USB_USAGE_DATA = 0b00000000, 98 | USB_USAGE_FEEDBACK = 0b00010000, 99 | USB_USAGE_IMPLICIT_FEEDBACK_DATA = 0b00100000, 100 | 101 | USB_USAGE_MASK = 0b00110000, 102 | }; 103 | 104 | enum usb_tx_per_microframe { 105 | USB_TX_1_PER_MICROFRAME = 0b00 << 11, 106 | USB_TX_2_PER_MICROFRAME = 0b01 << 11, 107 | USB_TX_3_PER_MICROFRAME = 0b10 << 11, 108 | }; 109 | 110 | struct usb_req_setup { 111 | uint8_t bmRequestType; 112 | uint8_t bRequest; 113 | uint16_t wValue; 114 | uint16_t wIndex; 115 | uint16_t wLength; 116 | }; 117 | 118 | struct usb_desc_generic { 119 | uint8_t bLength; 120 | uint8_t bDescriptorType; 121 | uint8_t data[]; 122 | }; 123 | 124 | typedef __code const struct usb_desc_generic 125 | usb_desc_generic_c; 126 | 127 | enum { 128 | USB_DEV_CLASS_PER_INTERFACE = 0x00, 129 | USB_DEV_SUBCLASS_PER_INTERFACE = 0x00, 130 | USB_DEV_PROTOCOL_PER_INTERFACE = 0x00, 131 | 132 | USB_DEV_CLASS_VENDOR = 0xff, 133 | USB_DEV_SUBCLASS_VENDOR = 0xff, 134 | USB_DEV_PROTOCOL_VENDOR = 0xff, 135 | }; 136 | 137 | struct usb_desc_device { 138 | uint8_t bLength; 139 | uint8_t bDescriptorType; 140 | uint16_t bcdUSB; 141 | uint8_t bDeviceClass; 142 | uint8_t bDeviceSubClass; 143 | uint8_t bDeviceProtocol; 144 | uint8_t bMaxPacketSize0; 145 | uint16_t idVendor; 146 | uint16_t idProduct; 147 | uint16_t bcdDevice; 148 | uint8_t iManufacturer; 149 | uint8_t iProduct; 150 | uint8_t iSerialNumber; 151 | uint8_t bNumConfigurations; 152 | }; 153 | 154 | typedef __code const struct usb_desc_device 155 | usb_desc_device_c; 156 | 157 | typedef __xdata struct usb_desc_device 158 | usb_desc_device_t; 159 | 160 | struct usb_desc_device_qualifier { 161 | uint8_t bLength; 162 | uint8_t bDescriptorType; 163 | uint16_t bcdUSB; 164 | uint8_t bDeviceClass; 165 | uint8_t bDeviceSubClass; 166 | uint8_t bDeviceProtocol; 167 | uint8_t bMaxPacketSize0; 168 | uint8_t bNumConfigurations; 169 | uint8_t bReserved; 170 | }; 171 | 172 | typedef __code const struct usb_desc_device_qualifier 173 | usb_desc_device_qualifier_c; 174 | 175 | struct usb_desc_configuration { 176 | uint8_t bLength; 177 | uint8_t bDescriptorType; 178 | uint16_t wTotalLength; 179 | uint8_t bNumInterfaces; 180 | uint8_t bConfigurationValue; 181 | uint8_t iConfiguration; 182 | uint8_t bmAttributes; 183 | uint8_t bMaxPower; 184 | }; 185 | 186 | typedef __code const struct usb_desc_configuration 187 | usb_desc_configuration_c; 188 | 189 | enum { 190 | USB_IFACE_CLASS_APP_SPECIFIC = 0xfe, 191 | 192 | USB_IFACE_CLASS_VENDOR = 0xff, 193 | USB_IFACE_SUBCLASS_VENDOR = 0xff, 194 | USB_IFACE_PROTOCOL_VENDOR = 0xff, 195 | }; 196 | 197 | struct usb_desc_interface { 198 | uint8_t bLength; 199 | uint8_t bDescriptorType; 200 | uint8_t bInterfaceNumber; 201 | uint8_t bAlternateSetting; 202 | uint8_t bNumEndpoints; 203 | uint8_t bInterfaceClass; 204 | uint8_t bInterfaceSubClass; 205 | uint8_t bInterfaceProtocol; 206 | uint8_t iInterface; 207 | }; 208 | 209 | typedef __code const struct usb_desc_interface 210 | usb_desc_interface_c; 211 | 212 | struct usb_desc_endpoint { 213 | uint8_t bLength; 214 | uint8_t bDescriptorType; 215 | uint8_t bEndpointAddress; 216 | uint8_t bmAttributes; 217 | uint16_t wMaxPacketSize; 218 | uint8_t bInterval; 219 | }; 220 | 221 | typedef __code const struct usb_desc_endpoint 222 | usb_desc_endpoint_c; 223 | 224 | struct usb_desc_langid { 225 | uint8_t bLength; 226 | uint8_t bDescriptorType; 227 | uint16_t wLANGID[]; 228 | }; 229 | 230 | typedef __code const struct usb_desc_langid 231 | usb_desc_langid_c; 232 | 233 | struct usb_desc_string { 234 | uint8_t bLength; 235 | uint8_t bDescriptorType; 236 | uint8_t bString[]; 237 | }; 238 | 239 | typedef __code const struct usb_desc_string 240 | usb_desc_string_c; 241 | 242 | enum usb_url_scheme { 243 | USB_URL_SCHEME_HTTP = 0, 244 | USB_URL_SCHEME_HTTPS = 1, 245 | }; 246 | 247 | struct usb_desc_url { 248 | uint8_t bLength; 249 | uint8_t bDescriptorType; 250 | uint8_t bScheme; 251 | uint8_t bURL[]; 252 | }; 253 | 254 | typedef __code const struct usb_desc_url 255 | usb_desc_url_c; 256 | 257 | struct usb_desc_binary_object_store { 258 | uint8_t bLength; 259 | uint8_t bDescriptorType; 260 | uint16_t wTotalLength; 261 | uint8_t bNumDeviceCaps; 262 | }; 263 | 264 | typedef __code const struct usb_desc_binary_object_store 265 | usb_binary_object_store_c; 266 | 267 | #endif 268 | -------------------------------------------------------------------------------- /tools/regtxt2c.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | from collections import defaultdict 4 | 5 | 6 | hidebit = r""" 7 | .+:EP\d+$|.+:PKTS\d+$| # too ambiguous 8 | (?!IO|ACC|B).+:([AD]|BC|PFC)\d+$| # address/data/counter regs 9 | USBFRAME.|MICROFRAME|ERRCNTLIM|EP.AUTOINLEN.|EP.ISOINPKTS:INPPF.|GPIFTCB.| # counter reg 10 | INT.IVEC|FNADDR|SUDPTR.$|XAUTODAT.|GPIFADR.| # address reg 11 | REVID|ECC.B.|SETUPDAT|I2DAT|UDMACRC. # data reg 12 | """ 13 | 14 | 15 | banner = None 16 | banners = {} 17 | regnames = defaultdict(lambda: None) 18 | regaddrs = defaultdict(lambda: None) 19 | regdescs = defaultdict(lambda: None) 20 | regsizes = defaultdict(lambda: None) 21 | regbits = defaultdict(lambda: defaultdict(set)) 22 | 23 | try: 24 | lines = enumerate(sys.stdin.readlines()) 25 | while True: 26 | while True: 27 | line = next(lines)[1] 28 | if line[0] == '#': 29 | banner = line[1:-1] 30 | continue 31 | 32 | match = re.match(r"^[0-9a-f]{2,4}$", line, re.I) 33 | if match: 34 | regno = int(match[0], 16) 35 | if banner: 36 | banners[regno] = banner 37 | banner = None 38 | break 39 | 40 | regsz = int(next(lines)[1]) 41 | if regsz > 1: 42 | regsizes[regno] = regsz 43 | 44 | match = re.match(r"^ ([A-Z0-9_]+|reserved)", next(lines)[1]) 45 | if not match: 46 | raise SystemExit(f"{next(lines)[0]}: register name") 47 | regname = match[1] 48 | 49 | if regname == "reserved": 50 | continue 51 | 52 | regnames[regno] = regname 53 | regaddrs[regname] = regno 54 | 55 | match = re.match(r"^ (.+)", next(lines)[1]) 56 | if not match: 57 | raise SystemExit(f"{next(lines)[0]}: description") 58 | regdesc = match[1] 59 | 60 | regdescs[regno] = regdesc 61 | 62 | for bit in reversed(range(8)): 63 | match = re.match(r"^ (?:[01x]|reserved|([A-Z0-9_/]+))", next(lines)[1]) 64 | if not match: 65 | raise SystemExit(f"{next(lines)[0]}: bit name") 66 | if re.match(r"^[0-9]{2,}$", match[0]): 67 | raise SystemExit(f"{next(lines)[0]}: bit overrun") 68 | bitnames = match[1] 69 | if not bitnames: 70 | continue 71 | for bitname in bitnames.split("/"): 72 | if re.match(hidebit, f"{regname}:{bitname}", re.X): 73 | continue 74 | regbits[regno][bit].add(bitname) 75 | 76 | except StopIteration: 77 | pass 78 | 79 | 80 | reg16 = defaultdict(lambda: None) 81 | for reglow in regnames: 82 | match = re.match(r"^(.+)L(.*)$", regnames[reglow]) 83 | if not match: 84 | continue 85 | reghigh = regaddrs[f"{match[1]}H{match[2]}"] 86 | if not reghigh: 87 | continue 88 | regwordname = match[1] + match[2] 89 | if reglow < 0x100: # sfr 90 | reg16[reglow] = (regwordname, (reghigh << 8) | reglow) 91 | elif reghigh == reglow + 1: 92 | reg16[reglow] = (regwordname, reglow) 93 | 94 | 95 | bitpos = {} 96 | bitexcl = set() 97 | bitsets = defaultdict(set) 98 | for reg in regbits: 99 | regname = regnames[reg] 100 | bits = regbits[reg] 101 | for bit in range(8): 102 | if bit not in bits: 103 | continue 104 | bitnames = bits[bit] 105 | for bitname in bitnames: 106 | if re.match(r"^[AD][0-9]+$", bitname): 107 | continue 108 | elif bitname not in bitpos: 109 | bitpos[bitname] = bit 110 | elif bitpos[bitname] == bit: 111 | pass 112 | else: 113 | print(f"bit conflict at {bitname}: {regnames[reg]}.{bit}, " 114 | f"but .{bitpos[bitname]} seen previously", 115 | file=sys.stderr) 116 | bitexcl.add(bitname) 117 | continue 118 | bitsets[bitname].add(regname) 119 | 120 | bitgroups = defaultdict(set) 121 | for bitname in bitsets: 122 | bitgroups[frozenset(bitsets[bitname])].add(bitname) 123 | 124 | 125 | print("#ifndef FX2REGS_H") 126 | print("#define FX2REGS_H") 127 | print() 128 | print("// DO NOT EDIT. Automatically generated by regtxt2c.py.") 129 | print() 130 | print("#include ") 131 | print() 132 | print("#ifndef DOXYGEN") 133 | print("#define _SFR(addr) static __sfr __at (addr)") 134 | print("#define _SFR16(addr) static __sfr16 __at (addr)") 135 | print("#define _SBIT(addr) static __sbit __at (addr)") 136 | print("#define _IOR(addr) static __xdata __at (addr) volatile uint8_t") 137 | print("#define _IOR16(addr) static __xdata __at (addr) volatile uint16_t") 138 | print("#endif") 139 | print() 140 | print("// Register _XPAGE must be defined at a location that sets the upper") 141 | print("// address byte of movx using \\@rN for non-small memory models to work.") 142 | print("// This should *not* be declared as `static`.") 143 | print("/// Alias of `MPAGE` used internally by sdcc.") 144 | print("__sfr __at(0x92) _XPAGE;") 145 | 146 | 147 | for reg in sorted(regnames.keys()): 148 | regname = regnames[reg] 149 | regdescraw = regdescs[reg].replace("@", r"\@") 150 | 151 | regdesc = f"///< Register 0x{reg:02X}: {regdescraw}" 152 | 153 | if reg in banners: 154 | print() 155 | print(f"// {banners[reg]}") 156 | print() 157 | 158 | if reg16[reg]: 159 | regnameword, regword = reg16[reg] 160 | regdescword = re.sub(r" L$", "", regdesc) 161 | 162 | if regsizes[reg]: 163 | regsz = regsizes[reg] 164 | print(f"_IOR(0x{reg:04x}) {regname}[{regsz}]; {regdesc}") 165 | elif reg < 0x100: 166 | print(f"_SFR(0x{reg:02x}) {regname}; {regdesc}") 167 | if reg16[reg]: 168 | print(f"_SFR16(0x{regword:04x}) {regnameword}; {regdescword}") 169 | else: 170 | print(f"_IOR(0x{reg:04x}) {regname}; {regdesc}") 171 | if reg16[reg]: 172 | print(f"_IOR16(0x{reg:04x}) {regnameword}; {regdescword}") 173 | 174 | if reg < 0x100 and (reg & 0x7) == 0: 175 | bits = regbits[reg] 176 | for bit in range(8): 177 | if bit not in bits: 178 | continue 179 | bitnames = bits[bit] 180 | for bitname in bitnames: 181 | if re.match(r"D[0-7]$", bitname): 182 | if regname.startswith("IO"): 183 | bitname = f"P{regname[-1]}{bit}" 184 | else: 185 | bitname = f"{regname}{bit}" 186 | bitdesc = f"///< Register 0x{reg:02X} bit {bit}: {regdescraw} bit {bitname}" 187 | print(f" _SBIT(0x{reg:02x} + {bit}) {bitname}; {bitdesc}") 188 | print() 189 | 190 | for _, bitgroup in sorted([(regaddrs[next(iter(bitgroups[bitgroup]))], bitgroup) 191 | for bitgroup in bitgroups]): 192 | bitnames = bitgroups[bitgroup] 193 | bitnames = sorted(bitnames, key=lambda bitname: bitpos[bitname]) 194 | bitregs = sorted(bitgroup) 195 | 196 | header = "Bits from " 197 | if len(bitregs) == 1: 198 | header += "register " 199 | else: 200 | header += "registers " 201 | header += ", ".join(bitregs) 202 | print( "/**") 203 | print(fr" * \name {header}") 204 | print( " * @{") 205 | print( " */") 206 | for bitname in bitnames: 207 | print(f"#define _{bitname:10} (1u<<{bitpos[bitname]}) ///< Bit {bitpos[bitname]}") 208 | print( "/**@}*/") 209 | print() 210 | 211 | print("#endif") 212 | -------------------------------------------------------------------------------- /firmware/library/usbdfu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #pragma save 5 | #pragma nooverlay 6 | 7 | __xdata uint8_t scratch2[512]; 8 | 9 | bool usb_dfu_setup(usb_dfu_iface_state_t *dfu, __xdata struct usb_req_setup *req) { 10 | uint8_t interface = dfu->state > USB_DFU_STATE_appDETACH ? 0 : dfu->interface; 11 | 12 | if((req->bmRequestType & (USB_TYPE_MASK|USB_RECIP_MASK)) != (USB_TYPE_CLASS|USB_RECIP_IFACE) || 13 | req->wIndex != interface) 14 | return false; 15 | 16 | if((req->bmRequestType & USB_DIR_MASK) == USB_DIR_OUT && 17 | req->bRequest == USB_DFU_REQ_DETACH && 18 | req->wLength == 0 && 19 | dfu->state == USB_DFU_STATE_appIDLE) { 20 | dfu->state = USB_DFU_STATE_appDETACH; 21 | ACK_EP0(); 22 | return true; 23 | } 24 | 25 | if((req->bmRequestType & USB_DIR_MASK) == USB_DIR_IN && 26 | req->bRequest == USB_DFU_REQ_GETSTATE && req->wValue == 0 && 27 | req->wLength == sizeof(uint8_t)) { 28 | EP0BUF[0] = dfu->state; 29 | SETUP_EP0_IN_BUF(1); 30 | return true; 31 | } 32 | 33 | if((req->bmRequestType & USB_DIR_MASK) == USB_DIR_IN && 34 | req->bRequest == USB_DFU_REQ_GETSTATUS && req->wValue == 0 && 35 | req->wLength == sizeof(struct usb_dfu_req_get_status)) { 36 | __xdata struct usb_dfu_req_get_status *status = 37 | (__xdata struct usb_dfu_req_get_status *)EP0BUF; 38 | 39 | if((dfu->state == USB_DFU_STATE_dfuDNLOAD_SYNC || 40 | dfu->state == USB_DFU_STATE_dfuMANIFEST_SYNC) && 41 | !dfu->sync) { 42 | // If we're here, then EP0BUF is still in use, but the host already sent GETSTATUS. 43 | // If we do SETUP_EP0_* right now, we'll overwrite EP0BUF, and get corrupted data. 44 | // So, delay responding to this packet until after EP0BUF is copied a second scratch 45 | // space. We cannot use the normal scratch space, because it's used when getting 46 | // descriptors, and our downloaded data might get overwritten. 47 | // 48 | // (GETSTATUS in dfuMANIFEST-SYNC does not have this restriction, but these requests 49 | // use identical flows in the DFU spec, and it is simpler to handle them the same way.) 50 | dfu->sync = true; 51 | return true; 52 | } else if(dfu->state == USB_DFU_STATE_dfuDNLOAD_SYNC) { 53 | dfu->state = USB_DFU_STATE_dfuDNBUSY; 54 | } else if(dfu->state == USB_DFU_STATE_dfuMANIFEST_SYNC) { 55 | dfu->state = USB_DFU_STATE_dfuMANIFEST; 56 | } 57 | 58 | status->bStatus = dfu->status; 59 | status->bwPollTimeout = 10; 60 | status->bwPollTimeoutHigh = 0; 61 | status->bState = dfu->state; 62 | status->iString = 0; 63 | SETUP_EP0_IN_BUF(sizeof(struct usb_dfu_req_get_status)); 64 | return true; 65 | } 66 | 67 | if(dfu->state > USB_DFU_STATE_appDETACH) { 68 | if((req->bmRequestType & USB_DIR_MASK) == USB_DIR_OUT && 69 | req->bRequest == USB_DFU_REQ_CLRSTATUS && req->wValue == 0 && 70 | req->wLength == 0 && 71 | dfu->state == USB_DFU_STATE_dfuERROR) { 72 | dfu->status = USB_DFU_STATUS_OK; 73 | dfu->state = USB_DFU_STATE_dfuIDLE; 74 | ACK_EP0(); 75 | return true; 76 | } 77 | 78 | if((req->bmRequestType & USB_DIR_MASK) == USB_DIR_IN && 79 | req->bRequest == USB_DFU_REQ_UPLOAD) { 80 | if(dfu->state == USB_DFU_STATE_dfuIDLE) { 81 | dfu->state = USB_DFU_STATE_dfuUPLOAD_IDLE; 82 | dfu->offset = 0; 83 | dfu->length = req->wLength; 84 | dfu->pending = true; 85 | return true; 86 | } else if(dfu->state == USB_DFU_STATE_dfuUPLOAD_IDLE) { 87 | dfu->length = req->wLength; 88 | dfu->pending = true; 89 | return true; 90 | } 91 | } 92 | 93 | if((req->bmRequestType & USB_DIR_MASK) == USB_DIR_OUT && 94 | req->bRequest == USB_DFU_REQ_DNLOAD) { 95 | if(dfu->state == USB_DFU_STATE_dfuIDLE) { 96 | dfu->state = USB_DFU_STATE_dfuDNLOAD_SYNC; 97 | dfu->offset = 0; 98 | dfu->length = req->wLength; 99 | dfu->pending = true; 100 | dfu->sync = false; 101 | SETUP_EP0_OUT_BUF(); 102 | return true; 103 | } else if(dfu->state == USB_DFU_STATE_dfuDNLOAD_IDLE && req->wLength > 0) { 104 | dfu->state = USB_DFU_STATE_dfuDNLOAD_SYNC; 105 | dfu->length = req->wLength; 106 | dfu->pending = true; 107 | dfu->sync = false; 108 | SETUP_EP0_OUT_BUF(); 109 | return true; 110 | } else if(dfu->state == USB_DFU_STATE_dfuDNLOAD_IDLE) { 111 | dfu->state = USB_DFU_STATE_dfuMANIFEST_SYNC; 112 | dfu->pending = true; 113 | dfu->sync = false; 114 | ACK_EP0(); 115 | return true; 116 | } 117 | } 118 | 119 | if((req->bmRequestType & USB_DIR_MASK) == USB_DIR_OUT && 120 | req->bRequest == USB_DFU_REQ_ABORT && req->wValue == 0 && 121 | req->wLength == 0) { 122 | if(dfu->state == USB_DFU_STATE_dfuIDLE || 123 | dfu->state == USB_DFU_STATE_dfuDNLOAD_SYNC || 124 | dfu->state == USB_DFU_STATE_dfuDNLOAD_IDLE || 125 | dfu->state == USB_DFU_STATE_dfuMANIFEST_SYNC || 126 | dfu->state == USB_DFU_STATE_dfuUPLOAD_IDLE) { 127 | dfu->state = USB_DFU_STATE_dfuIDLE; 128 | ACK_EP0(); 129 | return true; 130 | } 131 | } 132 | } 133 | 134 | dfu->status = USB_DFU_STATUS_errSTALLEDPKT; 135 | dfu->state = USB_DFU_STATE_dfuERROR; 136 | STALL_EP0(); 137 | return true; 138 | } 139 | #pragma restore 140 | 141 | void usb_dfu_setup_deferred(usb_dfu_iface_state_t *dfu) { 142 | if(dfu->pending) { 143 | if(dfu->state == USB_DFU_STATE_dfuUPLOAD_IDLE) { 144 | uint16_t length = dfu->length; 145 | dfu->status = dfu->firmware_upload(dfu->offset, &EP0BUF[0], &dfu->length); 146 | if(dfu->status == USB_DFU_STATUS_OK) { 147 | SETUP_EP0_IN_BUF(dfu->length); 148 | if(dfu->length < length) { 149 | dfu->state = USB_DFU_STATE_dfuIDLE; 150 | } 151 | dfu->offset += dfu->length; 152 | dfu->pending = false; 153 | return; 154 | } 155 | } else if(dfu->state == USB_DFU_STATE_dfuDNLOAD_SYNC) { 156 | while(EP0CS & _BUSY); 157 | xmemcpy(scratch2, &EP0BUF[0], dfu->length); 158 | ACK_EP0(); 159 | 160 | // Wait until we get a GETSTATUS request (in case we still haven't got one), and then reply 161 | // to it from here, after we've safely stashed away EP0BUF contents. 162 | while(!dfu->sync); 163 | usb_dfu_setup(dfu, (__xdata struct usb_req_setup *)SETUPDAT); 164 | return; 165 | } else if(dfu->state == USB_DFU_STATE_dfuDNBUSY) { 166 | dfu->status = dfu->firmware_dnload(dfu->offset, scratch2, dfu->length); 167 | if(dfu->status == USB_DFU_STATUS_OK) { 168 | dfu->offset += dfu->length; 169 | dfu->state = USB_DFU_STATE_dfuDNLOAD_IDLE; 170 | dfu->pending = false; 171 | return; 172 | } 173 | } else if(dfu->state == USB_DFU_STATE_dfuMANIFEST_SYNC) { 174 | while(!dfu->sync); 175 | usb_dfu_setup(dfu, (__xdata struct usb_req_setup *)SETUPDAT); 176 | return; 177 | } else if(dfu->state == USB_DFU_STATE_dfuMANIFEST) { 178 | if(dfu->firmware_manifest) { 179 | dfu->status = dfu->firmware_manifest(); 180 | } else { 181 | dfu->status = USB_DFU_STATUS_OK; 182 | } 183 | if(dfu->status == USB_DFU_STATUS_OK) { 184 | dfu->state = USB_DFU_STATE_dfuIDLE; 185 | dfu->pending = false; 186 | return; 187 | } 188 | } 189 | 190 | dfu->state = USB_DFU_STATE_dfuERROR; 191 | dfu->pending = false; 192 | STALL_EP0(); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /firmware/boot-uf2/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | usb_desc_device_c usb_device = { 8 | .bLength = sizeof(struct usb_desc_device), 9 | .bDescriptorType = USB_DESC_DEVICE, 10 | .bcdUSB = 0x0200, 11 | .bDeviceClass = USB_DEV_CLASS_PER_INTERFACE, 12 | .bDeviceSubClass = USB_DEV_SUBCLASS_PER_INTERFACE, 13 | .bDeviceProtocol = USB_DEV_PROTOCOL_PER_INTERFACE, 14 | .bMaxPacketSize0 = 64, 15 | .idVendor = 0x04b4, 16 | .idProduct = 0x8613, 17 | .bcdDevice = 0x0000, 18 | .iManufacturer = 1, 19 | .iProduct = 2, 20 | .iSerialNumber = 3, 21 | .bNumConfigurations = 1, 22 | }; 23 | 24 | usb_desc_interface_c usb_interface_mass_storage = { 25 | .bLength = sizeof(struct usb_desc_interface), 26 | .bDescriptorType = USB_DESC_INTERFACE, 27 | .bInterfaceNumber = 0, 28 | .bAlternateSetting = 0, 29 | .bNumEndpoints = 2, 30 | .bInterfaceClass = USB_IFACE_CLASS_MASS_STORAGE, 31 | .bInterfaceSubClass = USB_IFACE_SUBCLASS_MASS_STORAGE_SCSI, 32 | .bInterfaceProtocol = USB_IFACE_PROTOCOL_MASS_STORAGE_BBB, 33 | .iInterface = 0, 34 | }; 35 | 36 | usb_desc_endpoint_c usb_endpoint_ep2_out = { 37 | .bLength = sizeof(struct usb_desc_endpoint), 38 | .bDescriptorType = USB_DESC_ENDPOINT, 39 | .bEndpointAddress = 2, 40 | .bmAttributes = USB_XFER_BULK, 41 | .wMaxPacketSize = 512, 42 | .bInterval = 0, 43 | }; 44 | 45 | usb_desc_endpoint_c usb_endpoint_ep6_in = { 46 | .bLength = sizeof(struct usb_desc_endpoint), 47 | .bDescriptorType = USB_DESC_ENDPOINT, 48 | .bEndpointAddress = 6|USB_DIR_IN, 49 | .bmAttributes = USB_XFER_BULK, 50 | .wMaxPacketSize = 512, 51 | .bInterval = 0, 52 | }; 53 | 54 | usb_configuration_c usb_config = { 55 | { 56 | .bLength = sizeof(struct usb_desc_configuration), 57 | .bDescriptorType = USB_DESC_CONFIGURATION, 58 | .bNumInterfaces = 1, 59 | .bConfigurationValue = 1, 60 | .iConfiguration = 0, 61 | .bmAttributes = USB_ATTR_RESERVED_1, 62 | .bMaxPower = 50, 63 | }, 64 | { 65 | { .interface = &usb_interface_mass_storage }, 66 | { .endpoint = &usb_endpoint_ep2_out }, 67 | { .endpoint = &usb_endpoint_ep6_in }, 68 | { 0 } 69 | } 70 | }; 71 | 72 | usb_configuration_set_c usb_configs[] = { 73 | &usb_config, 74 | }; 75 | 76 | usb_ascii_string_c usb_strings[] = { 77 | [0] = "whitequark@whitequark.org", 78 | [1] = "FX2 series UF2-class bootloader", 79 | // USB MS BBB 4.1.1 requires each device to have an unique serial number that is at least 80 | // 12 characters long. We cannot satisfy the uniqueness requirement, but we at least provide 81 | // a serial number in a valid format. 82 | [2] = "000000000000", 83 | }; 84 | 85 | usb_descriptor_set_c usb_descriptor_set = { 86 | .device = &usb_device, 87 | .config_count = ARRAYSIZE(usb_configs), 88 | .configs = usb_configs, 89 | .string_count = ARRAYSIZE(usb_strings), 90 | .strings = usb_strings, 91 | }; 92 | 93 | usb_mass_storage_bbb_state_t usb_mass_storage_state = { 94 | .interface = 0, 95 | .max_in_size = 512, 96 | 97 | .command = uf2_scsi_command, 98 | .data_out = uf2_scsi_data_out, 99 | .data_in = uf2_scsi_data_in, 100 | }; 101 | 102 | static bool firmware_read(uint32_t address, __xdata uint8_t *data, uint16_t length) __reentrant { 103 | // Only 2-byte EEPROMs are large enough to store any sort of firmware, and the address 104 | // of a 2-byte boot EEPROM is fixed, so it's safe to hardcode it here. 105 | return eeprom_read(0x51, address, data, length, /*double_byte=*/true); 106 | } 107 | 108 | static bool firmware_write(uint32_t address, __xdata uint8_t *data, uint16_t length) __reentrant { 109 | // Use 8-byte page writes, which are slow but universally compatible. (Strictly speaking, 110 | // no EEPROM can be assumed to provide any page writes, but virtually every EEPROM larger 111 | // than 16 KiB supports at least 8-byte pages). 112 | // 113 | // If the datasheet for the EEPROM lists larger pages as permissible, this would provide 114 | // a significant speed boost. Unfortunately it is not really possible to discover the page 115 | // size by interrogating the EEPROM. 116 | return eeprom_write(0x51, address, data, length, /*double_byte=*/true, 117 | /*page_size=*/3, /*timeout=*/166); 118 | } 119 | 120 | // Configure for 16Kx8 EEPROM, since this is upwards compatible with larger EEPROMs and 121 | // any application integrating the UF2 bootloader will be at least ~12 KB in size. 122 | // (The overhead of the bootloader is smaller than that, since much of the USB machinery 123 | // can be shared between the bootloader and the application.) 124 | uf2_configuration_c uf2_config = { 125 | // Provide a virtual mass storage device of 32 MiB in size. Using a device that is 126 | // too small will result in broken filesystem being generated (in particular, below 127 | // a certain cluster count, the filesystm gets interpreted as FAT12 instead of FAT16), 128 | // and a device that is too large will result in slower operations (mounting, etc). 129 | // 32 MiB is a good number. 130 | .total_sectors = 2 * 32768, 131 | // Replace the Model: and Board-ID: fields with ones specific for your board. 132 | // Note that Board-ID: field should be machine-readable. 133 | // The INFO_UF2.TXT file can be up to 512 bytes in size. 134 | .info_uf2_txt = 135 | "UF2 Bootloader for Cypress FX2\r\n" 136 | "Model: Generic Developer Board with 16Kx8 EEPROM\r\n" 137 | "Board-ID: FX2-Generic_16Kx8-v0\r\n", 138 | // Replace the URL with a hyperlink to a document describing your board. 139 | .index_htm = 140 | "", 141 | // Replace this with the actual EEPROM size on your board to use its full capacity. 142 | .firmware_size = 16384, 143 | 144 | .firmware_read = firmware_read, 145 | .firmware_write = firmware_write, 146 | }; 147 | 148 | void handle_usb_setup(__xdata struct usb_req_setup *req) { 149 | if(usb_mass_storage_bbb_setup(&usb_mass_storage_state, req)) 150 | return; 151 | 152 | STALL_EP0(); 153 | } 154 | 155 | volatile bool pending_ep6_in; 156 | 157 | void isr_IBN(void) __interrupt { 158 | pending_ep6_in = true; 159 | CLEAR_USB_IRQ(); 160 | NAKIRQ = _IBN; 161 | IBNIRQ = _IBNI_EP6; 162 | } 163 | 164 | int main(void) { 165 | // Run core at 48 MHz fCLK. 166 | CPUCS = _CLKSPD1; 167 | 168 | // Use newest chip features. 169 | REVCTL = _ENH_PKT|_DYN_OUT; 170 | 171 | // NAK all transfers. 172 | SYNCDELAY; 173 | FIFORESET = _NAKALL; 174 | 175 | // EP2 is configured as 512-byte double buffed BULK OUT. 176 | EP2CFG = _VALID|_TYPE1|_BUF1; 177 | EP2CS = 0; 178 | // EP6 is configured as 512-byte double buffed BULK IN. 179 | EP6CFG = _VALID|_DIR|_TYPE1|_BUF1; 180 | EP6CS = 0; 181 | // EP4/8 are not used. 182 | EP4CFG &= ~_VALID; 183 | EP8CFG &= ~_VALID; 184 | 185 | // Enable IN-BULK-NAK interrupt for EP6. 186 | IBNIE = _IBNI_EP6; 187 | NAKIE = _IBN; 188 | 189 | // Reset and prime EP2, and reset EP6. 190 | SYNCDELAY; 191 | FIFORESET = _NAKALL|2; 192 | SYNCDELAY; 193 | OUTPKTEND = _SKIP|2; 194 | SYNCDELAY; 195 | OUTPKTEND = _SKIP|2; 196 | SYNCDELAY; 197 | FIFORESET = _NAKALL|6; 198 | SYNCDELAY; 199 | FIFORESET = 0; 200 | 201 | // Re-enumerate, to make sure our descriptors are picked up correctly. 202 | usb_init(/*disconnect=*/true); 203 | 204 | while(1) { 205 | if(!(EP2CS & _EMPTY)) { 206 | uint16_t length = (EP2BCH << 8) | EP2BCL; 207 | if(usb_mass_storage_bbb_bulk_out(&usb_mass_storage_state, EP2FIFOBUF, length)) { 208 | EP2BCL = 0; 209 | } else { 210 | EP2CS = _STALL; 211 | EP6CS = _STALL; 212 | } 213 | } 214 | 215 | if(pending_ep6_in) { 216 | __xdata uint16_t length; 217 | if(usb_mass_storage_bbb_bulk_in(&usb_mass_storage_state, EP6FIFOBUF, &length)) { 218 | if(length > 0) { 219 | EP6BCH = length >> 8; 220 | SYNCDELAY; 221 | EP6BCL = length; 222 | } 223 | } else { 224 | EP6CS = _STALL; 225 | } 226 | 227 | pending_ep6_in = false; 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /firmware/library/include/fx2spiflash.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2SPIFLASH_H 2 | #define FX2SPIFLASH_H 3 | 4 | #include 5 | 6 | #ifndef DOXYGEN 7 | 8 | #define _DEFINE_SPIFLASH_STORAGE(name) \ 9 | __xdata uint8_t _##name##_spiflash_buf[4]; 10 | 11 | #define _DEFINE_SPIFLASH_INIT_FN(name, cs, sck, si, so) \ 12 | void name##_init(void) { \ 13 | __asm setb _ASM_REG(cs) __endasm; \ 14 | __asm setb _ASM_REG(sck) __endasm; \ 15 | __asm setb _ASM_REG(si) __endasm; \ 16 | } 17 | 18 | #define _DEFINE_SPIFLASH_RDP_FN(name, cs) \ 19 | void name##_rdp(void) { \ 20 | _##name##_spiflash_buf[0] = 0xAB; \ 21 | __asm clr _ASM_REG(cs) __endasm; \ 22 | _##name##_spi_wr(_##name##_spiflash_buf, 1); \ 23 | __asm setb _ASM_REG(cs) __endasm; \ 24 | } 25 | 26 | #define _DEFINE_SPIFLASH_DP_FN(name, cs) \ 27 | void name##_dp(void) { \ 28 | _##name##_spiflash_buf[0] = 0xB9; \ 29 | __asm clr _ASM_REG(cs) __endasm; \ 30 | _##name##_spi_wr(_##name##_spiflash_buf, 1); \ 31 | __asm setb _ASM_REG(cs) __endasm; \ 32 | } 33 | 34 | #define _DEFINE_SPIFLASH_READ_FN(name, cs) \ 35 | void name##_read(uint32_t addr, __xdata uint8_t *data, uint16_t length) { \ 36 | _##name##_spiflash_buf[0] = 0x03; \ 37 | _##name##_spiflash_buf[1] = (addr >> 16); \ 38 | _##name##_spiflash_buf[2] = (addr >> 8); \ 39 | _##name##_spiflash_buf[3] = (addr >> 0); \ 40 | __asm clr _ASM_REG(cs) __endasm; \ 41 | _##name##_spi_wr(_##name##_spiflash_buf, 4); \ 42 | _##name##_spi_rd(data, length); \ 43 | __asm setb _ASM_REG(cs) __endasm; \ 44 | } 45 | 46 | #define _DEFINE_SPIFLASH_WREN_FN(name, cs) \ 47 | void name##_wren(void) { \ 48 | _##name##_spiflash_buf[0] = 0x06; \ 49 | __asm clr _ASM_REG(cs) __endasm; \ 50 | _##name##_spi_wr(_##name##_spiflash_buf, 1); \ 51 | __asm setb _ASM_REG(cs) __endasm; \ 52 | } 53 | 54 | #define _DEFINE_SPIFLASH_RDSR_FN(name, cs) \ 55 | uint8_t name##_rdsr(void) { \ 56 | _##name##_spiflash_buf[0] = 0x05; \ 57 | __asm clr _ASM_REG(cs) __endasm; \ 58 | _##name##_spi_wr(_##name##_spiflash_buf, 1); \ 59 | _##name##_spi_rd(_##name##_spiflash_buf, 1); \ 60 | __asm setb _ASM_REG(cs) __endasm; \ 61 | return _##name##_spiflash_buf[0]; \ 62 | } 63 | 64 | #define _DEFINE_SPIFLASH_CE_FN(name, cs) \ 65 | void name##_ce(void) { \ 66 | _##name##_spiflash_buf[0] = 0x60; \ 67 | __asm clr _ASM_REG(cs) __endasm; \ 68 | _##name##_spi_wr(_##name##_spiflash_buf, 1); \ 69 | __asm setb _ASM_REG(cs) __endasm; \ 70 | } 71 | 72 | #define _DEFINE_SPIFLASH_SE_FN(name, cs) \ 73 | void name##_se(uint32_t addr) { \ 74 | _##name##_spiflash_buf[0] = 0x20; \ 75 | _##name##_spiflash_buf[1] = (addr >> 16); \ 76 | _##name##_spiflash_buf[2] = (addr >> 8); \ 77 | _##name##_spiflash_buf[3] = (addr >> 0); \ 78 | __asm clr _ASM_REG(cs) __endasm; \ 79 | _##name##_spi_wr(_##name##_spiflash_buf, 4); \ 80 | __asm setb _ASM_REG(cs) __endasm; \ 81 | } 82 | 83 | #define _DEFINE_SPIFLASH_PP_FN(name, cs) \ 84 | void name##_pp(uint32_t addr, const __xdata uint8_t *data, uint16_t length) { \ 85 | _##name##_spiflash_buf[0] = 0x02; \ 86 | _##name##_spiflash_buf[1] = (addr >> 16); \ 87 | _##name##_spiflash_buf[2] = (addr >> 8); \ 88 | _##name##_spiflash_buf[3] = (addr >> 0); \ 89 | __asm clr _ASM_REG(cs) __endasm; \ 90 | _##name##_spi_wr(_##name##_spiflash_buf, 4); \ 91 | _##name##_spi_wr(data, length); \ 92 | __asm setb _ASM_REG(cs) __endasm; \ 93 | } 94 | 95 | #endif 96 | 97 | /// Write-in-Progress status register bit. 98 | #define SPIFLASH_WIP 0b00000001 99 | 100 | /// Write Enable Latch status register bit. 101 | #define SPIFLASH_WEL 0b00000010 102 | 103 | /** 104 | * This macro defines a number of functions that implement common operations on 25C-compatible 105 | * SPI flashes. They are optimized and run at ~5 MHz SCK frequency at 48 MHz CLKOUT. 106 | * The `cs`, `sck`, `so`, and `si` parameters may point to any pins, and are defined in the 107 | * format `Pxn`. 108 | * 109 | * The defined routines are: 110 | * 111 | * * `void name_init()`, to set outputs to their idle state. (Note that output enables must be 112 | * configured by the user before calling `name_init`.) 113 | * * `void name_rdp()`, to leave deep power-down mode (command `AB`). 114 | * * `void name_dp()`, to enter deep power-down mode (command `B9`). 115 | * * `void name_read(uint32_t addr, __xdata uint8_t *data, uint16_t length)`, to read data at 116 | * the given address, with wraparound at array boundary (command `03`). 117 | * * `void name_wren()`, to latch the write enable bit (command `06`). 118 | * * `uint8_t name_rdsr()`, to read the status register (command `05`). 119 | * * `void name_ce()`, to erase the entire chip (command `60`). 120 | * * `void name_se(uint32_t addr)`, to erase a sector at the given address (command `20`). 121 | * * `void name_pp(uint32_t addr, const __xdata uint8_t *data, uint16_t length)`, to program 122 | * up to a whole page at the given address, with wraparound at page boundary (command `02`). 123 | * 124 | * For example, invoking the macro as `DEFINE_SPIFLASH_FNS(flash, PA0, PB0, PB1, PB2)` 125 | * defines the routines `void flash_init()`, `void flash_read()`, etc that assume an SPI flash's 126 | * CS# pin is connected to A0, SCK pin is connected to B0, MISO pin is connected to B1, and MOSI 127 | * pin is connected to B2. 128 | */ 129 | #define DEFINE_SPIFLASH_FNS(name, cs, sck, si, so) \ 130 | DEFINE_SPI_WR_FN(_##name##_spi_wr, sck, si) \ 131 | DEFINE_SPI_RD_FN(_##name##_spi_rd, sck, so) \ 132 | _DEFINE_SPIFLASH_STORAGE(name) \ 133 | _DEFINE_SPIFLASH_INIT_FN(name, cs, sck, si, so) \ 134 | _DEFINE_SPIFLASH_RDP_FN (name, cs) \ 135 | _DEFINE_SPIFLASH_DP_FN (name, cs) \ 136 | _DEFINE_SPIFLASH_READ_FN(name, cs) \ 137 | _DEFINE_SPIFLASH_WREN_FN(name, cs) \ 138 | _DEFINE_SPIFLASH_RDSR_FN(name, cs) \ 139 | _DEFINE_SPIFLASH_CE_FN (name, cs) \ 140 | _DEFINE_SPIFLASH_SE_FN (name, cs) \ 141 | _DEFINE_SPIFLASH_PP_FN (name, cs) 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /firmware/boot-dfu/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // Replace this with the actual EEPROM size on your board to use its full capacity. 7 | #define FIRMWARE_SIZE 16384 8 | 9 | // Application mode descriptors. 10 | 11 | usb_desc_device_c usb_device = { 12 | .bLength = sizeof(struct usb_desc_device), 13 | .bDescriptorType = USB_DESC_DEVICE, 14 | .bcdUSB = 0x0200, 15 | .bDeviceClass = USB_DEV_CLASS_PER_INTERFACE, 16 | .bDeviceSubClass = USB_DEV_SUBCLASS_PER_INTERFACE, 17 | .bDeviceProtocol = USB_DEV_PROTOCOL_PER_INTERFACE, 18 | .bMaxPacketSize0 = 64, 19 | .idVendor = 0x04b4, 20 | .idProduct = 0x8613, 21 | .bcdDevice = 0x0000, 22 | .iManufacturer = 1, 23 | .iProduct = 2, 24 | .iSerialNumber = 0, 25 | .bNumConfigurations = 1, 26 | }; 27 | 28 | extern usb_dfu_desc_functional_c usb_dfu_functional; 29 | 30 | usb_desc_interface_c usb_interface_dfu_runtime = { 31 | .bLength = sizeof(struct usb_desc_interface), 32 | .bDescriptorType = USB_DESC_INTERFACE, 33 | .bInterfaceNumber = 0, 34 | .bAlternateSetting = 0, 35 | .bNumEndpoints = 0, 36 | .bInterfaceClass = USB_IFACE_CLASS_APP_SPECIFIC, 37 | .bInterfaceSubClass = USB_IFACE_SUBCLASS_DFU, 38 | .bInterfaceProtocol = USB_IFACE_PROTOCOL_DFU_RUNTIME, 39 | .iInterface = 0, 40 | }; 41 | 42 | usb_configuration_c usb_config_app = { 43 | { 44 | .bLength = sizeof(struct usb_desc_configuration), 45 | .bDescriptorType = USB_DESC_CONFIGURATION, 46 | .bNumInterfaces = 1, 47 | .bConfigurationValue = 1, 48 | .iConfiguration = 0, 49 | .bmAttributes = USB_ATTR_RESERVED_1, 50 | .bMaxPower = 50, 51 | }, 52 | { 53 | { .interface = &usb_interface_dfu_runtime }, 54 | { .generic = (struct usb_desc_generic *) &usb_dfu_functional }, 55 | { 0 } 56 | } 57 | }; 58 | 59 | usb_configuration_set_c usb_configs_app[] = { 60 | &usb_config_app, 61 | }; 62 | 63 | usb_ascii_string_c usb_strings_app[] = { 64 | [0] = "whitequark@whitequark.org", 65 | [1] = "Example application with DFU support", 66 | }; 67 | 68 | // DFU mode descriptors 69 | 70 | usb_desc_interface_c usb_interface_dfu_upgrade = { 71 | .bLength = sizeof(struct usb_desc_interface), 72 | .bDescriptorType = USB_DESC_INTERFACE, 73 | .bInterfaceNumber = 0, 74 | .bAlternateSetting = 0, 75 | .bNumEndpoints = 0, 76 | .bInterfaceClass = USB_IFACE_CLASS_APP_SPECIFIC, 77 | .bInterfaceSubClass = USB_IFACE_SUBCLASS_DFU, 78 | .bInterfaceProtocol = USB_IFACE_PROTOCOL_DFU_UPGRADE, 79 | .iInterface = 3, 80 | }; 81 | 82 | usb_dfu_desc_functional_c usb_dfu_functional = { 83 | .bLength = sizeof(struct usb_dfu_desc_functional), 84 | .bDescriptorType = USB_DESC_DFU_FUNCTIONAL, 85 | .bmAttributes = USB_DFU_ATTR_CAN_DNLOAD | 86 | USB_DFU_ATTR_CAN_UPLOAD | 87 | USB_DFU_ATTR_MANIFESTATION_TOLERANT | 88 | USB_DFU_ATTR_WILL_DETACH, 89 | .wTransferSize = 64, 90 | .bcdDFUVersion = 0x0101, 91 | }; 92 | 93 | usb_configuration_c usb_config_dfu = { 94 | { 95 | .bLength = sizeof(struct usb_desc_configuration), 96 | .bDescriptorType = USB_DESC_CONFIGURATION, 97 | .bNumInterfaces = 1, 98 | .bConfigurationValue = 1, 99 | .iConfiguration = 0, 100 | .bmAttributes = USB_ATTR_RESERVED_1, 101 | .bMaxPower = 50, 102 | }, 103 | { 104 | { .interface = &usb_interface_dfu_upgrade }, 105 | { .generic = (struct usb_desc_generic *) &usb_dfu_functional }, 106 | { 0 } 107 | } 108 | }; 109 | 110 | usb_configuration_set_c usb_configs_dfu[] = { 111 | &usb_config_dfu, 112 | }; 113 | 114 | usb_ascii_string_c usb_strings_dfu[] = { 115 | [0] = "whitequark@whitequark.org", 116 | [1] = "FX2 series DFU-class bootloader", 117 | [2] = "Boot EEPROM" 118 | }; 119 | 120 | // Application and DFU code 121 | 122 | __xdata struct usb_descriptor_set usb_descriptor_set = { 123 | .device = &usb_device, 124 | .config_count = ARRAYSIZE(usb_configs_app), 125 | .configs = usb_configs_app, 126 | .string_count = ARRAYSIZE(usb_strings_app), 127 | .strings = usb_strings_app, 128 | }; 129 | 130 | usb_dfu_status_t firmware_upload(uint32_t address, __xdata uint8_t *data, 131 | __xdata uint16_t *length) __reentrant { 132 | if(address < FIRMWARE_SIZE) { 133 | // Only 2-byte EEPROMs are large enough to store any sort of firmware, and the address 134 | // of a 2-byte boot EEPROM is fixed, so it's safe to hardcode it here. 135 | if(eeprom_read(0x51, address, data, *length, /*double_byte=*/true)) { 136 | return USB_DFU_STATUS_OK; 137 | } else { 138 | return USB_DFU_STATUS_errUNKNOWN; 139 | } 140 | } else { 141 | *length = 0; 142 | return USB_DFU_STATUS_OK; 143 | } 144 | } 145 | 146 | usb_dfu_status_t firmware_dnload(uint32_t address, __xdata uint8_t *data, 147 | uint16_t length) __reentrant { 148 | if(length == 0) { 149 | if(address == FIRMWARE_SIZE) 150 | return USB_DFU_STATUS_OK; 151 | else 152 | return USB_DFU_STATUS_errNOTDONE; 153 | } else if(address < FIRMWARE_SIZE) { 154 | // Use 8-byte page writes, which are slow but universally compatible. (Strictly speaking, 155 | // no EEPROM can be assumed to provide any page writes, but virtually every EEPROM larger 156 | // than 16 KiB supports at least 8-byte pages). 157 | // 158 | // If the datasheet for the EEPROM lists larger pages as permissible, this would provide 159 | // a significant speed boost. Unfortunately it is not really possible to discover the page 160 | // size by interrogating the EEPROM. 161 | if(eeprom_write(0x51, address, data, length, /*double_byte=*/true, 162 | /*page_size=*/3, /*timeout=*/166)) { 163 | return USB_DFU_STATUS_OK; 164 | } else { 165 | return USB_DFU_STATUS_errWRITE; 166 | } 167 | } else { 168 | return USB_DFU_STATUS_errADDRESS; 169 | } 170 | } 171 | 172 | usb_dfu_status_t firmware_manifest(void) __reentrant { 173 | // Simulate committing the firmware. If this function is not necessary, it may simply be omitted, 174 | // together with its entry in `usb_dfu_iface_state`. 175 | delay_ms(1000); 176 | 177 | return USB_DFU_STATUS_OK; 178 | } 179 | 180 | usb_dfu_iface_state_t usb_dfu_iface_state = { 181 | // Set to bInterfaceNumber of the DFU descriptor in the application mode. 182 | .interface = 0, 183 | 184 | .firmware_upload = firmware_upload, 185 | .firmware_dnload = firmware_dnload, 186 | .firmware_manifest = firmware_manifest, 187 | }; 188 | 189 | void handle_usb_setup(__xdata struct usb_req_setup *req) { 190 | if(usb_dfu_setup(&usb_dfu_iface_state, req)) 191 | return; 192 | 193 | STALL_EP0(); 194 | } 195 | 196 | int main(void) { 197 | // Run core at 48 MHz fCLK. 198 | CPUCS = _CLKSPD1; 199 | 200 | // Re-enumerate, to make sure our descriptors are picked up correctly. 201 | usb_init(/*disconnect=*/true); 202 | 203 | while(1) { 204 | // Handle switching to DFU mode from application mode. 205 | if(usb_dfu_iface_state.state == USB_DFU_STATE_appDETACH) { 206 | // Wait until the host has received our ACK of the DETACH request before actually 207 | // disconnecting. This is because if we disconnect immediately, the host might just 208 | // return an error to the DFU utility. 209 | delay_ms(10); 210 | USBCS |= _DISCON; 211 | 212 | // Switch to DFU mode. 213 | usb_dfu_iface_state.state = USB_DFU_STATE_dfuIDLE; 214 | 215 | // Re-enumerate using the DFU mode descriptors. For Windows compatibility, it is necessary 216 | // to change USB Product ID in the Device descriptor as well, since Windows is unable 217 | // to rebind a DFU driver to the same VID:PID pair. (Windows is euphemistically called out 218 | // in the DFU spec as a "certain operating system"). 219 | ((usb_desc_device_t *)usb_device)->idProduct++; 220 | usb_descriptor_set.config_count = ARRAYSIZE(usb_configs_dfu); 221 | usb_descriptor_set.configs = usb_configs_dfu; 222 | usb_descriptor_set.string_count = ARRAYSIZE(usb_strings_dfu); 223 | usb_descriptor_set.strings = usb_strings_dfu; 224 | 225 | // Don't reconnect again in `usb_init`, as we have just disconnected explicitly. 226 | usb_init(/*disconnect=*/false); 227 | } 228 | 229 | // Handle any lengthy DFU requests, i.e. the ones that call back into firmware_* functions. 230 | usb_dfu_setup_deferred(&usb_dfu_iface_state); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /firmware/library/include/fx2queue.h: -------------------------------------------------------------------------------- 1 | #ifndef FX2QUEUE_H 2 | #define FX2QUEUE_H 3 | 4 | // This is an implementation of a bounded-length first-in first-out queue structured as a ring 5 | // buffer that may be used asynchronously with a single producer and a single consumer, with 6 | // an optimized internal state. The following is an informal proof of its correctness. 7 | // 8 | // To recap, the invariants for a bounded queue are: 9 | // 1a. for the dequeue operation, that the queue is not empty (length != 0). 10 | // 2a. for the enqueue operation, that the queue is not full (length != capacity); 11 | // 3a. always, 0 <= length <= capacity. 12 | // The invariant (3a) is ensured by (1a) and (2a). 13 | // 14 | // First, consider a more abstract implementation of a bounded length ring buffer that consists 15 | // of data storage, head and tail pointers. In this model, the head and tail pointers are natural 16 | // numbers; enqueueing an element advances the head pointer, and dequeueing an element advances 17 | // the tail pointer. The data storage is addressed modulo queue capacity, behaving as if it is 18 | // infinitely repeated along the natural number line. 19 | // 20 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 21 | // |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---> 22 | // T==========>H 23 | // +---+---+---+---+---+...................+.............. 24 | // | ? | W | X | Y | Z | ? | W | X | Y | Z | ? | W | X | 25 | // +---+---+---+---+---+...................+.............. 26 | // 27 | // This way, when the head pointer is incremented past the queue capacity, it rolls over to 28 | // the start of the storage, and same for the tail pointer. The invariants now are: 29 | // 1b. queue is not empty: !(tail == head); 30 | // 2b. queue is not full: !(tail + capacity == head); 31 | // 3b. 0 <= head - tail <= capacity. 32 | // 33 | // Using natural numbers is impractical. Instead of a natural number N, let's consider an _epoch_ 34 | // N / capacity, and an _index_ N % capacity. This is a 1:1 transformation. 35 | // 36 | // ( Epoch 0 ) ( Epoch 1 ) ( Epoch 2 ) ( ... 37 | // 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 38 | // |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---> 39 | // T==========>H 40 | // +---+---+---+---+---+...................+.............. 41 | // | ? | W | X | Y | Z | ? | W | X | Y | Z | ? | W | X | 42 | // +---+---+---+---+---+...................+.............. 43 | // 44 | // This way, storage accesses and invariants can be restated in terms of pointer epoch and pointer 45 | // index. The index-th storage element is read or written before advancing the pointer to the next 46 | // epoch and index numbers. The invariants now are: 47 | // 1c. queue is not empty: !(index(tail) == index(head) && epoch(tail) == epoch(head)); 48 | // 2c. queue is not full: !(index(tail) == index(head) && epoch(tail) + 1 == epoch(head)); 49 | // Since invariant (3b) follows from (1b) and (2b), we can weaken it to: 50 | // 3c. 0 <= epoch(head) - epoch(tail) <= 1. 51 | // 52 | // By considering the cases permitted by the inequality (3c), we can rewrite the invariants as: 53 | // 1d. queue is not empty: !(index(tail) == index(head) && 54 | // epoch(tail) % K == epoch(head) % K); 55 | // 2d. queue is not full: !(index(tail) == index(head) && 56 | // (epoch(tail) + 1) % K != epoch(head) % K). 57 | // for any K > 1. 58 | // 59 | // Let's represent each pointer as a fixed point number in base 2: 60 | // E--EE.I--II 61 | // where E are the epoch number bits and I are the index number bits. The index width should be 62 | // sufficient to represent capacity: 63 | // width(I--II) = 1 + floor(log2(capacity - 1)) 64 | // The epoch width matches the choice of K but is otherwise arbitrary, and is chosen to pad 65 | // the fixed point number to the most convenient machine representation: 66 | // width(E--EE) = log2(K) 67 | // 68 | // In this representation, we can rewrite the invariants as: 69 | // 1e. queue is not empty: !(fixp(tail) == fixp(head)); 70 | // 2e. queue is not full: !(fixp(tail) + (1 << width(I--II)) == fixp(head)). 71 | // 72 | // The operation of advancing the pointer becomes: 73 | // if(fixp(ptr) & ((1 << width(I--II)) - 1) == capacity - 1) 74 | // then fixp(ptr) = fixp(ptr) + 1 + ((1 << width(I--II)) - capacity) 75 | // else fixp(ptr) = fixp(ptr) + 1 76 | // I.e. the fixed point representation is exploited to reset the index to zero and increment 77 | // the epoch in a single addition. The unsigned overflow of index bits and epoch bits is invoked 78 | // deliberately, and does not violate any invariants. 79 | // 80 | // An astute reader will notice that with a power-of-2 capacity, this implementation degenerates 81 | // to the optimal power-of-two only capacity implementation in [Snellman]. 82 | // 83 | // References: 84 | // [Snellman]: https://www.snellman.net/blog/archive/2016-12-13-ring-buffers/ 85 | // The inspiration for this implementation. "Surely it must generalize to NPOT!" 86 | // [Cummings]: http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf 87 | // Unlike [Snellman], explicitly considers the most significant bit as a wraparound counter, 88 | // and is the inspiration for considering an "epoch" instead of using div/mod operations. 89 | // This work extends it by adapting to pointers that are at least as wide as needed, 90 | // as opposed to exactly as wide. 91 | 92 | #if (__SDCC_VERSION_MAJOR == 3) && (__SDCC_VERSION_MINOR <= 9) 93 | typedef unsigned char sig_atomic_t; 94 | #else 95 | #include 96 | #endif 97 | 98 | // The implementation below will only translate into efficient code if the compiler implements 99 | // aggressive constant propagation and can transform expressions of the form (a ? b : b) into (b). 100 | // Fortunately, that's most compilers. 101 | // 102 | // The data is separate from the pointers to reduce the amount of initialized data, as well as 103 | // allow placing data and pointers in different address spaces. 104 | 105 | // Round `v` up to next power of two, or itself if `v` is already a power of two. 106 | #define _QUEUE_NEXT_POT_S1(v) ((v)|((v)>>32)) 107 | #define _QUEUE_NEXT_POT_S2(v) ((v)|((v)>>16)) 108 | #define _QUEUE_NEXT_POT_S3(v) ((v)|((v)>>8)) 109 | #define _QUEUE_NEXT_POT_S4(v) ((v)|((v)>>4)) 110 | #define _QUEUE_NEXT_POT_S5(v) ((v)|((v)>>2)) 111 | #define _QUEUE_NEXT_POT_S6(v) ((v)|((v)>>1)) 112 | #define _QUEUE_NEXT_POT(v) \ 113 | (1+_QUEUE_NEXT_POT_S6(_QUEUE_NEXT_POT_S5(_QUEUE_NEXT_POT_S4( \ 114 | _QUEUE_NEXT_POT_S3(_QUEUE_NEXT_POT_S2(_QUEUE_NEXT_POT_S1((v)-1))))))) 115 | 116 | struct _queue { 117 | volatile sig_atomic_t head, tail; 118 | }; 119 | #define DECLARE_QUEUE(name, ty, cap) \ 120 | extern struct _queue name; \ 121 | extern ty name##_data[cap]; 122 | #define DEFINE_QUEUE(name, ty, cap) \ 123 | struct _queue name = {0, 0}; \ 124 | ty name##_data[cap]; \ 125 | _Static_assert((cap) <= (1 << (sizeof(sig_atomic_t) * 8 - 1)), \ 126 | "Capacity of queue " #name \ 127 | " must be less than one half of the range of sig_atomic_t"); 128 | 129 | #define _QUEUE_CAP(queue) \ 130 | (sizeof(queue##_data)/sizeof(queue##_data[0])) 131 | #define _QUEUE_EPOCH_LSB(queue) \ 132 | _QUEUE_NEXT_POT(_QUEUE_CAP(queue)) 133 | #define _QUEUE_INDEX_MASK(queue) \ 134 | (_QUEUE_EPOCH_LSB(queue)-1) 135 | #define _QUEUE_NEXT(queue, ptr) \ 136 | ((queue).ptr & _QUEUE_INDEX_MASK(queue)) == (_QUEUE_CAP(queue) - 1) \ 137 | ? (queue).ptr + 1 + (_QUEUE_EPOCH_LSB(queue) - _QUEUE_CAP(queue)) \ 138 | : (queue).ptr + 1 \ 139 | 140 | #define QUEUE_EMPTY(queue) \ 141 | ((queue).tail == (queue).head) 142 | #define QUEUE_FULL(queue) \ 143 | ((sig_atomic_t)((queue).tail + _QUEUE_EPOCH_LSB(queue)) == (queue).head) 144 | 145 | #define QUEUE_PUT(queue, elem) \ 146 | do { \ 147 | queue##_data[(queue).head & _QUEUE_INDEX_MASK(queue)] = (elem); \ 148 | (queue).head = _QUEUE_NEXT(queue, head); \ 149 | } while(0) 150 | #define QUEUE_GET(queue, elem) \ 151 | do { \ 152 | elem = queue##_data[(queue).tail & _QUEUE_INDEX_MASK(queue)]; \ 153 | (queue).tail = _QUEUE_NEXT(queue, tail); \ 154 | } while(0) 155 | 156 | #endif 157 | -------------------------------------------------------------------------------- /software/fx2/format.py: -------------------------------------------------------------------------------- 1 | import os 2 | import io 3 | import re 4 | 5 | 6 | __all__ = ['input_data', 'output_data'] 7 | 8 | 9 | def autodetect(file): 10 | """ 11 | Autodetect file format based on properties of a given file object. 12 | 13 | Returns `"ihex"` for `.hex`, `.ihex` and `.ihx` file extensions, 14 | `"bin"` for `.bin` file extension, `"hex"` if ``file`` is a TTY, 15 | and raises :class:`ValueError` otherwise. 16 | """ 17 | basename, extname = os.path.splitext(file.name) 18 | if extname in [".hex", ".ihex", ".ihx"]: 19 | return "ihex" 20 | elif extname in [".bin"]: 21 | return "bin" 22 | elif file.isatty(): 23 | return "hex" 24 | else: 25 | raise ValueError("Specify file format explicitly") 26 | 27 | 28 | def flatten_data(data, *, fill=0x00): 29 | """ 30 | Flatten a list of ``(addr, chunk)`` pairs, such as that returned by :func:`input_data`, 31 | to a flat byte array, such as that accepted by :func:`output_data`. 32 | """ 33 | data_flat = bytearray([fill]) * max([addr + len(chunk) for (addr, chunk) in data]) 34 | for (addr, chunk) in data: 35 | data_flat[addr:addr+len(chunk)] = chunk 36 | return data_flat 37 | 38 | 39 | def diff_data(old, new): 40 | """ 41 | Compute the difference between ``old`` and ``new`` byte arrays, and return a list 42 | of ``(addr, chunk)`` pairs containing only the bytes that are changed between 43 | ``new`` and ``old``. 44 | """ 45 | diff = [] 46 | 47 | cpos = None 48 | cchunk = bytearray() 49 | for (pos, (oldb, newb)) in enumerate(zip(old, new)): 50 | if oldb != newb: 51 | if cpos is None: 52 | cpos = pos 53 | elif cpos + len(cchunk) != pos: 54 | diff.append((cpos, bytes(cchunk))) 55 | cchunk.clear() 56 | cpos = pos 57 | cchunk.append(newb) 58 | if len(cchunk) > 0: 59 | diff.append((cpos, bytes(cchunk))) 60 | 61 | if len(new) > len(old): 62 | diff.append((len(old), new[len(old):])) 63 | 64 | return diff 65 | 66 | 67 | def output_data(file, data, fmt="auto", offset=0): 68 | """ 69 | Write Intel HEX, hexadecimal, or binary ``data`` to ``file``. 70 | 71 | :param data: 72 | A byte array (``bytes``, ``bytearray`` and ``list`` of ``(addr, chunk)`` pairs 73 | are all valid). 74 | :param fmt: 75 | ``"ihex"`` for Intel HEX, ``"hex"`` for hexadecimal, ``"bin"`` for binary, 76 | or ``"auto"`` for autodetection via :func:`autodetect`. 77 | :param offset: 78 | Offset the data by specified amount of bytes. 79 | """ 80 | 81 | if isinstance(file, io.TextIOWrapper): 82 | file = file.buffer 83 | 84 | if fmt == "auto": 85 | fmt = autodetect(file) 86 | 87 | if fmt == "bin": 88 | if isinstance(data, list): 89 | data = flatten_data(data) 90 | 91 | file.write(data) 92 | 93 | elif fmt == "hex": 94 | if isinstance(data, list): 95 | data = flatten_data(data) 96 | 97 | n = 0 98 | for n, byte in enumerate(data): 99 | file.write("{:02x}".format(byte).encode()) 100 | if n > 0 and n % 16 == 15: 101 | file.write(b"\n") 102 | elif n > 0 and n % 8 == 7: 103 | file.write(b" ") 104 | else: 105 | file.write(b" ") 106 | if n % 16 != 15: 107 | file.write(b"\n") 108 | 109 | elif fmt == "ihex": 110 | if not isinstance(data, list): 111 | data = [(offset, data)] 112 | 113 | def write_record(record): 114 | record.append((~sum(record) + 1) & 0xff) 115 | file.write(b":") 116 | file.write(bytes(record).hex().upper().encode()) 117 | file.write(b"\n") 118 | 119 | bankoff = 0 120 | for (addr, chunk) in data: 121 | pos = 0 122 | while pos < len(chunk): 123 | recoff = addr + pos 124 | if bankoff != recoff >> 16: 125 | bankoff = recoff >> 16 126 | write_record([ 127 | 2, 128 | 0x00, # dummy 129 | 0x00, # dummy 130 | 0x04, # Extended Linear Address 131 | (bankoff >> 8) & 0xff, 132 | (bankoff >> 0) & 0xff, 133 | ]) 134 | recdata = chunk[pos:pos + 0x10] 135 | write_record([ 136 | len(recdata), 137 | (recoff >> 8) & 0xff, 138 | (recoff >> 0) & 0xff, 139 | 0x00, # Data 140 | *list(recdata) 141 | ]) 142 | pos += len(recdata) 143 | 144 | write_record([ 145 | 0, 146 | 0x00, # dummy 147 | 0x00, # dummy 148 | 0x01, # End Of File 149 | ]) 150 | 151 | 152 | def input_data(file_or_data, fmt="auto", offset=0): 153 | """ 154 | Read Intel HEX, hexadecimal, or binary data from ``file_or_data``. 155 | If ``file_or_data`` is a string, it is treated as hexadecimal. Otherwise, 156 | the format is determined by the ``fmt`` argument. 157 | 158 | Raises :class:`ValueError` if the input data has invalid format. 159 | 160 | Returns a list of ``(address, data)`` chunks. 161 | 162 | :param fmt: 163 | ``"ihex"`` for Intel HEX, ``"hex"`` for hexadecimal, ``"bin"`` for binary, 164 | or ``"auto"`` for autodetection via :func:`autodetect`. 165 | :param offset: 166 | Offset the data by specified amount of bytes. 167 | """ 168 | 169 | if isinstance(file_or_data, io.TextIOWrapper): 170 | file_or_data = file_or_data.buffer 171 | 172 | if isinstance(file_or_data, str): 173 | fmt = "hex" 174 | data = file_or_data.encode() 175 | else: 176 | data = file_or_data.read() 177 | 178 | if fmt == "auto": 179 | fmt = autodetect(file_or_data) 180 | 181 | if fmt == "bin": 182 | return [(offset, data)] 183 | 184 | elif fmt == "hex": 185 | try: 186 | hexdata = re.sub(r"\s*", "", data.decode()) 187 | bindata = bytes.fromhex(hexdata) 188 | except ValueError as e: 189 | raise ValueError("Invalid hexadecimal data") 190 | return [(offset, bindata)] 191 | 192 | elif fmt == "ihex": 193 | RE_HDR = re.compile(rb":([0-9a-f]{8})", re.I) 194 | RE_WS = re.compile(rb"\s*") 195 | 196 | segoff = 0 197 | bankoff = 0 198 | resoff = 0 199 | resbuf = [] 200 | res = [] 201 | 202 | pos = 0 203 | while pos < len(data): 204 | match = RE_HDR.match(data, pos) 205 | if match is None: 206 | raise ValueError("Invalid record header at offset {}".format(pos)) 207 | *rechdr, = bytes.fromhex(match.group(1).decode()) 208 | reclen, recoffh, recoffl, rectype = rechdr 209 | 210 | recdatahex = data[match.end(0):match.end(0)+(reclen+1)*2] 211 | if len(recdatahex) < (reclen + 1) * 2: 212 | raise ValueError("Truncated record at offset {}".format(pos)) 213 | try: 214 | *recdata, recsum = bytes.fromhex(recdatahex.decode()) 215 | except ValueError: 216 | raise ValueError("Invalid record data at offset {}".format(pos)) 217 | if sum(rechdr + recdata + [recsum]) & 0xff != 0: 218 | raise ValueError("Invalid record checksum at offset {}".format(pos)) 219 | 220 | if rectype == 0x01: 221 | break 222 | 223 | elif rectype in (0x02, 0x04): 224 | res.append((offset + resoff + segoff + bankoff, resbuf)) 225 | 226 | # If we switch segments/banks, we know there is a discontinuity, so 227 | # make no assumption about previous position or buffer contents. 228 | resoff = 0 229 | resbuf = [] 230 | if rectype == 0x02: 231 | segoff = ((recdata[0] << 8) | recdata[1]) << 4 232 | elif rectype == 0x04: 233 | bankoff = ((recdata[0] << 8) | recdata[1]) << 16 234 | else: 235 | assert False 236 | 237 | elif rectype == 0x05: 238 | pass 239 | 240 | elif rectype == 0x00: 241 | recoff = (recoffh << 8) | recoffl 242 | if resoff + len(resbuf) == recoff: 243 | resbuf += recdata 244 | else: 245 | if len(resbuf) > 0: 246 | res.append((offset + resoff + segoff + bankoff, resbuf)) 247 | resoff = recoff 248 | resbuf = recdata 249 | 250 | else: 251 | raise ValueError("Unknown record type {:02x} at offset {}".format(rectype, pos)) 252 | 253 | match = RE_WS.match(data, match.end(0) + len(recdatahex)) 254 | pos = match.end(0) 255 | 256 | # Handle last record that was seen before Record Type 0x01. 257 | if len(resbuf) > 0: 258 | res.append((offset + resoff + segoff + bankoff, resbuf)) 259 | 260 | return res 261 | -------------------------------------------------------------------------------- /firmware/library/include/usbcdc.h: -------------------------------------------------------------------------------- 1 | #ifndef USBCDC_H 2 | #define USBCDC_H 3 | 4 | enum { 5 | /// Communications Device Class 6 | USB_DEV_CLASS_CDC = 0x02, 7 | 8 | /// Communications Interface Class 9 | USB_IFACE_CLASS_CIC = 0x02, 10 | /// Data Interface Class 11 | USB_IFACE_CLASS_DIC = 0x0A, 12 | 13 | /// Direct Line Control Model Interface Subclass 14 | USB_IFACE_SUBCLASS_CDC_CIC_DLCM = 0x01, 15 | /// Abstract Control Model Interface Subclass 16 | USB_IFACE_SUBCLASS_CDC_CIC_ACM = 0x02, 17 | /// Telephone Control Model Interface Subclass 18 | USB_IFACE_SUBCLASS_CDC_CIC_TCM = 0x03, 19 | /// Multi-Channel Control Model Interface Subclass 20 | USB_IFACE_SUBCLASS_CDC_CIC_MCCM = 0x04, 21 | /// CAPI Control Model Interface Subclass 22 | USB_IFACE_SUBCLASS_CDC_CIC_CAPICM = 0x05, 23 | /// Ethernet Networking Control Model Interface Subclass 24 | USB_IFACE_SUBCLASS_CDC_CIC_ENCM = 0x06, 25 | /// ATM Networking Control Model Interface Subclass 26 | USB_IFACE_SUBCLASS_CDC_CIC_ATMNCM = 0x07, 27 | /// Wireless Handset Control Model Interface Subclass 28 | USB_IFACE_SUBCLASS_CDC_CIC_WHCM = 0x08, 29 | /// Device Management Interface Subclass 30 | USB_IFACE_SUBCLASS_CDC_CIC_DEV_MGMT = 0x09, 31 | /// Mobile Direct Line Model Interface Subclass 32 | USB_IFACE_SUBCLASS_CDC_CIC_MDLM = 0x0A, 33 | /// OBEX Interface Subclass 34 | USB_IFACE_SUBCLASS_CDC_CIC_OBEX = 0x0B, 35 | /// Ethernet Emulation Model Interface Subclass 36 | USB_IFACE_SUBCLASS_CDC_CIC_EEM = 0x0C, 37 | /// Network Control Model Interface Subclass 38 | USB_IFACE_SUBCLASS_CDC_CIC_NCM = 0x0D, 39 | 40 | /// Communications Interface Protocol: No class specific protocol required 41 | USB_IFACE_PROTOCOL_CDC_CIC_NONE = 0x00, 42 | /// Communications Interface Protocol: AT Commands: V.250 etc 43 | USB_IFACE_PROTOCOL_CDC_CIC_AT_V250 = 0x01, 44 | /// Communications Interface Protocol: AT Commands defined by PCCA-101 45 | USB_IFACE_PROTOCOL_CDC_CIC_AT_PCCA_101 = 0x02, 46 | /// Communications Interface Protocol: AT Commands defined by PCCA-101 & Annex O 47 | USB_IFACE_PROTOCOL_CDC_CIC_AT_PCCA_101_ANNEX_O = 0x03, 48 | /// Communications Interface Protocol: AT Commands defined by GSM 07.07 49 | USB_IFACE_PROTOCOL_CDC_CIC_GSM_07_07 = 0x04, 50 | /// Communications Interface Protocol: AT Commands defined by 3GPP 27.007 51 | USB_IFACE_PROTOCOL_CDC_CIC_3GPP_27_007 = 0x05, 52 | /// Communications Interface Protocol: AT Commands defined by TIA for CDMA 53 | USB_IFACE_PROTOCOL_CDC_CIC_TIA_CDMA = 0x06, 54 | /// Communications Interface Protocol: Ethernet Emulation Model 55 | USB_IFACE_PROTOCOL_CDC_CIC_EEM = 0x07, 56 | /// Communications Interface Protocol: Commands defined by Command Set Functional Descriptor 57 | USB_IFACE_PROTOCOL_CDC_CIC_EXTERNAL = 0xFE, 58 | 59 | /// Data Interface Subclass 60 | USB_IFACE_SUBCLASS_CDC_DIC = 0x00, 61 | 62 | /// Data Interface Protocol: No class specific protocol required 63 | USB_IFACE_PROTOCOL_CDC_DIC_NONE = 0x00, 64 | /// Data Interface Protocol: Network Transfer Block 65 | USB_IFACE_PROTOCOL_CDC_DIC_NTB = 0x01, 66 | /// Data Interface Protocol: Physical interface protocol for ISDN BRI 67 | USB_IFACE_PROTOCOL_CDC_DIC_I_430 = 0x30, 68 | /// Data Interface Protocol: HDLC 69 | USB_IFACE_PROTOCOL_CDC_DIC_HDLC = 0x31, 70 | /// Data Interface Protocol: Transparent 71 | USB_IFACE_PROTOCOL_CDC_DIC_TRANSPARENT = 0x32, 72 | /// Data Interface Protocol: Management protocol for Q.921 data link protocol 73 | USB_IFACE_PROTOCOL_CDC_DIC_Q_921M = 0x50, 74 | /// Data Interface Protocol: Data link protocol for Q.931 75 | USB_IFACE_PROTOCOL_CDC_DIC_Q_921D = 0x51, 76 | /// Data Interface Protocol: TEI-multiplexor for Q.921 data link protocol 77 | USB_IFACE_PROTOCOL_CDC_DIC_Q_921TM = 0x52, 78 | /// Data Interface Protocol: Data compression procedures 79 | USB_IFACE_PROTOCOL_CDC_DIC_V_42BIS = 0x90, 80 | /// Data Interface Protocol: Euro-ISDN protocol control 81 | USB_IFACE_PROTOCOL_CDC_DIC_Q_931_EURO_ISDN = 0x91, 82 | /// Data Interface Protocol: V.24 rate adaptation to ISDN 83 | USB_IFACE_PROTOCOL_CDC_DIC_V_120 = 0x92, 84 | /// Data Interface Protocol: CAPI Commands 85 | USB_IFACE_PROTOCOL_CDC_DIC_CAPI_2_0 = 0x93, 86 | /// Data Interface Protocol: Host based driver. 87 | USB_IFACE_PROTOCOL_CDC_DIC_HOST_BASED = 0xFD, 88 | /// Data Interface Protocol: Protocol(s) defined by Protocol Unit Functional Descriptors 89 | /// on Communications Class Interface 90 | USB_IFACE_PROTOCOL_CDC_DIC_EXTERNAL = 0xFE, 91 | }; 92 | 93 | enum /*usb_descriptor*/ { 94 | USB_DESC_CS_INTERFACE = 0x24, 95 | USB_DESC_CS_ENDPOINT = 0x25, 96 | }; 97 | 98 | enum usb_cdc_desc_functional_subtype { 99 | /// Header Functional Descriptor 100 | USB_DESC_CDC_FUNCTIONAL_SUBTYPE_HEADER = 0x00, 101 | /// Call Management Functional Descriptor 102 | USB_DESC_CDC_FUNCTIONAL_SUBTYPE_CALL_MGMT = 0x01, 103 | /// Abstract Control Management Functional Descriptor 104 | USB_DESC_CDC_FUNCTIONAL_SUBTYPE_ACM = 0x02, 105 | /// Union Functional Descriptor 106 | USB_DESC_CDC_FUNCTIONAL_SUBTYPE_UNION = 0x06, 107 | }; 108 | 109 | struct usb_cdc_desc_functional_header { 110 | uint8_t bLength; 111 | uint8_t bDescriptorType; 112 | uint8_t bDescriptorSubType; 113 | uint16_t bcdCDC; 114 | }; 115 | 116 | typedef __code const struct usb_cdc_desc_functional_header 117 | usb_cdc_desc_functional_header_c; 118 | 119 | enum { 120 | /// Not set - Device does not handle call management itself. 121 | /// Set - Device handles call management itself. 122 | USB_CDC_CALL_MGMT_CAP_HANDLES_CALL_MGMT = 0b00000001, 123 | /// Not set - Device sends/receives call management information only over the Communications 124 | /// Class interface. 125 | /// Set - Device can send/receive call management information over a Data Class interface. 126 | USB_CDC_CALL_MGMT_CAP_CALL_MGMT_OVER_DATA = 0b00000010, 127 | }; 128 | 129 | struct usb_cdc_desc_functional_call_mgmt { 130 | uint8_t bLength; 131 | uint8_t bDescriptorType; 132 | uint8_t bDescriptorSubType; 133 | uint8_t bmCapabilities; 134 | uint8_t bDataInterface; 135 | }; 136 | 137 | typedef __code const struct usb_cdc_desc_functional_call_mgmt 138 | usb_cdc_desc_functional_call_mgmt_c; 139 | 140 | enum { 141 | /// Set - Device supports the request combination of Set_Comm_Feature, Clear_Comm_Feature, and 142 | /// Get_Comm_Feature. 143 | USB_CDC_ACM_CAP_REQ_COMM_FEATURE = 0b00000001, 144 | /// Set - Device supports the request combination of Set_Line_Coding, Set_Control_Line_State, 145 | /// Get_Line_Coding, and the notification Serial_State. 146 | USB_CDC_ACM_CAP_REQ_LINE_CODING_STATE = 0b00000010, 147 | /// Set - Device supports the request Send_Break 148 | USB_CDC_ACM_CAP_REQ_SEND_BREAK = 0b00000100, 149 | /// Set - Device supports the notification Network_Connection. 150 | USB_CDC_ACM_CAP_REQ_NETWORK_CONNECTION = 0b00001000, 151 | }; 152 | 153 | struct usb_cdc_desc_functional_acm { 154 | uint8_t bLength; 155 | uint8_t bDescriptorType; 156 | uint8_t bDescriptorSubType; 157 | uint8_t bmCapabilities; 158 | }; 159 | 160 | typedef __code const struct usb_cdc_desc_functional_acm 161 | usb_cdc_desc_functional_acm_c; 162 | 163 | struct usb_cdc_desc_functional_union { 164 | uint8_t bLength; 165 | uint8_t bDescriptorType; 166 | uint8_t bDescriptorSubType; 167 | uint8_t bControlInterface; 168 | uint8_t bSubordinateInterface[]; 169 | }; 170 | 171 | typedef __code const struct usb_cdc_desc_functional_union 172 | usb_cdc_desc_functional_union_c; 173 | 174 | /// Class-Specific Request Codes 175 | enum usb_cdc_request { 176 | USB_CDC_REQ_SEND_ENCAPSULATED_COMMAND = 0x00, 177 | USB_CDC_REQ_GET_ENCAPSULATED_RESPONSE = 0x01, 178 | }; 179 | 180 | /// Class-Specific Request Codes for PSTN subclasses 181 | enum usb_cdc_pstn_request { 182 | USB_CDC_PSTN_REQ_SET_COMM_FEATURE = 0x02, 183 | USB_CDC_PSTN_REQ_GET_COMM_FEATURE = 0x03, 184 | USB_CDC_PSTN_REQ_CLEAR_COMM_FEATURE = 0x04, 185 | USB_CDC_PSTN_REQ_SET_AUX_LINE_STATE = 0x10, 186 | USB_CDC_PSTN_REQ_SET_HOOK_STATE = 0x11, 187 | USB_CDC_PSTN_REQ_PULSE_SETUP = 0x12, 188 | USB_CDC_PSTN_REQ_SEND_PULSE = 0x13, 189 | USB_CDC_PSTN_REQ_SET_PULSE_TIME = 0x14, 190 | USB_CDC_PSTN_REQ_RING_AUX_JACK = 0x15, 191 | USB_CDC_PSTN_REQ_SET_LINE_CODING = 0x20, 192 | USB_CDC_PSTN_REQ_GET_LINE_CODING = 0x21, 193 | USB_CDC_PSTN_REQ_SET_CONTROL_LINE_STATE = 0x22, 194 | USB_CDC_PSTN_REQ_SEND_BREAK = 0x23, 195 | USB_CDC_PSTN_REQ_SET_RINGER_PARMS = 0x30, 196 | USB_CDC_PSTN_REQ_GET_RINGER_PARMS = 0x31, 197 | USB_CDC_PSTN_REQ_SET_OPERATION_PARMS = 0x32, 198 | USB_CDC_PSTN_REQ_GET_OPERATION_PARMS = 0x33, 199 | USB_CDC_PSTN_REQ_SET_LINE_PARMS = 0x34, 200 | USB_CDC_PSTN_REQ_GET_LINE_PARMS = 0x35, 201 | USB_CDC_PSTN_REQ_DIAL_DIGITS = 0x36, 202 | }; 203 | 204 | enum { 205 | USB_CDC_REQ_LINE_CODING_STOP_BITS_1 = 0, 206 | USB_CDC_REQ_LINE_CODING_STOP_BITS_1_5 = 1, 207 | USB_CDC_REQ_LINE_CODING_STOP_BITS_2 = 2, 208 | 209 | USB_CDC_REQ_LINE_CODING_PARITY_NONE = 0, 210 | USB_CDC_REQ_LINE_CODING_PARITY_ODD = 1, 211 | USB_CDC_REQ_LINE_CODING_PARITY_EVEN = 2, 212 | USB_CDC_REQ_LINE_CODING_PARITY_MARK = 3, 213 | USB_CDC_REQ_LINE_CODING_PARITY_SPACE = 4, 214 | }; 215 | 216 | struct usb_cdc_req_line_coding { 217 | uint32_t dwDTERate; 218 | uint8_t bCharFormat; 219 | uint8_t bParityType; 220 | uint8_t bDataBits; 221 | }; 222 | 223 | #endif 224 | -------------------------------------------------------------------------------- /firmware/library/usb.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | bool usb_self_powered; 8 | bool usb_remote_wakeup; 9 | uint8_t usb_config_value; 10 | 11 | void usb_init(bool disconnect) { 12 | usb_remote_wakeup = false; 13 | usb_config_value = 0; 14 | 15 | ENABLE_USB_AUTOVEC(); 16 | USBIE |= _SUDAV; 17 | USBIRQ = _SUDAV; 18 | EA = 1; 19 | 20 | // Take EP0 under firmware control. 21 | if(!(USBCS & _RENUM)) 22 | USBCS |= _RENUM; 23 | 24 | // If requested, disconnect and wait for the host to discover that. 25 | if(disconnect) { 26 | USBCS |= _DISCON; 27 | delay_ms(10); 28 | } 29 | 30 | // Make sure we're connected. 31 | USBCS &= ~_DISCON; 32 | } 33 | 34 | __xdata volatile uint8_t *EPnCS_for_n(uint8_t n) { 35 | switch(n) { 36 | case 0x00: 37 | case 0x80: 38 | return &EP0CS; 39 | case 0x01: 40 | return &EP1OUTCS; 41 | case 0x81: 42 | return &EP1INCS; 43 | case 0x02: 44 | case 0x82: 45 | return &EP2CS; 46 | case 0x04: 47 | case 0x84: 48 | return &EP4CS; 49 | case 0x06: 50 | case 0x86: 51 | return &EP6CS; 52 | case 0x08: 53 | case 0x88: 54 | return &EP8CS; 55 | default: 56 | return 0; 57 | } 58 | } 59 | 60 | void isr_SUDAV(void) __interrupt { 61 | __xdata struct usb_req_setup *req = (__xdata struct usb_req_setup *)SETUPDAT; 62 | bool handled = false; 63 | 64 | // The sdcc prologue/epilogue only save/restore DPH0/DPL0, but if DPS is 1, then we would 65 | // in fact modify DPH1/DPL1 when loading dptr with mov dptr. 66 | __asm 67 | push _DPS 68 | mov _DPS, #0 69 | __endasm; 70 | 71 | uint8_t bmRequestType = req->bmRequestType; 72 | uint8_t bRequest = req->bRequest; 73 | 74 | // Get Descriptor 75 | if(bmRequestType == (USB_RECIP_DEVICE|USB_DIR_IN) && 76 | bRequest == USB_REQ_GET_DESCRIPTOR) { 77 | enum usb_descriptor type = (enum usb_descriptor)(req->wValue >> 8); 78 | uint8_t index = req->wValue & 0xff; 79 | handle_usb_get_descriptor(type, index); 80 | // Set Configuration 81 | } else if(bmRequestType == (USB_RECIP_DEVICE|USB_DIR_OUT) && 82 | bRequest == USB_REQ_SET_CONFIGURATION) { 83 | if(handle_usb_set_configuration((uint8_t)req->wValue)) { 84 | ACK_EP0(); 85 | } else { 86 | STALL_EP0(); 87 | } 88 | // Get Configuration 89 | } else if(bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_STANDARD|USB_DIR_IN) && 90 | bRequest == USB_REQ_GET_CONFIGURATION) { 91 | handle_usb_get_configuration(); 92 | // Set Interface 93 | } else if(bmRequestType == (USB_RECIP_IFACE|USB_TYPE_STANDARD|USB_DIR_OUT) && 94 | bRequest == USB_REQ_SET_INTERFACE) { 95 | if(handle_usb_set_interface((uint8_t)req->wIndex, (uint8_t)req->wValue)) { 96 | ACK_EP0(); 97 | } else { 98 | STALL_EP0(); 99 | } 100 | // Get Interface 101 | } else if(bmRequestType == (USB_RECIP_IFACE|USB_TYPE_STANDARD|USB_DIR_IN) && 102 | bRequest == USB_REQ_GET_INTERFACE) { 103 | handle_usb_get_interface((uint8_t)req->wIndex); 104 | // Set Feature - Device 105 | } else if(bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_STANDARD|USB_DIR_OUT) && 106 | bRequest == USB_REQ_SET_FEATURE) { 107 | if(req->wValue == USB_FEAT_DEVICE_REMOTE_WAKEUP) { 108 | usb_remote_wakeup = true; 109 | ACK_EP0(); 110 | } else if(req->wValue == USB_FEAT_TEST_MODE) { 111 | ACK_EP0(); 112 | } 113 | // Get Status - Device 114 | } else if(bmRequestType == (USB_RECIP_DEVICE|USB_TYPE_STANDARD|USB_DIR_IN) && 115 | bRequest == USB_REQ_GET_STATUS) { 116 | EP0BUF[0] = (usb_self_powered << 0) | 117 | (usb_remote_wakeup << 1); 118 | EP0BUF[1] = 0; 119 | SETUP_EP0_IN_BUF(2); 120 | // Get Status - Interface 121 | } else if(bmRequestType == (USB_RECIP_IFACE|USB_TYPE_STANDARD|USB_DIR_IN) && 122 | bRequest == USB_REQ_GET_STATUS) { 123 | EP0BUF[0] = 0; 124 | EP0BUF[1] = 0; 125 | SETUP_EP0_IN_BUF(2); 126 | // Set Feature - Endpoint 127 | // Clear Feature - Endpoint 128 | } else if(bmRequestType == (USB_RECIP_ENDPT|USB_TYPE_STANDARD|USB_DIR_OUT) && 129 | (bRequest == USB_REQ_SET_FEATURE || 130 | bRequest == USB_REQ_CLEAR_FEATURE)) { 131 | if(req->wValue == USB_FEAT_ENDPOINT_HALT) { 132 | __xdata volatile uint8_t *EPnCS = EPnCS_for_n(req->wIndex); 133 | if(EPnCS != 0) { 134 | if(bRequest == USB_REQ_SET_FEATURE) { 135 | *EPnCS |= _STALL; 136 | ACK_EP0(); 137 | } else { 138 | if(handle_usb_clear_endpoint_halt((uint8_t)req->wIndex)) { 139 | *EPnCS &= ~_STALL; 140 | TOGCTL = (req->wIndex & 0x0f) | ((req->wIndex & 0x80) >> 3); 141 | TOGCTL |= _R; 142 | } 143 | } 144 | } 145 | } 146 | // Get Status - Endpoint 147 | } else if(bmRequestType == (USB_RECIP_ENDPT|USB_TYPE_STANDARD|USB_DIR_IN) && 148 | bRequest == USB_REQ_GET_STATUS) { 149 | __xdata volatile uint8_t *EPnCS = EPnCS_for_n(req->wIndex); 150 | if(EPnCS != 0) { 151 | EP0BUF[0] = ((*EPnCS & _STALL) != 0); 152 | EP0BUF[1] = 0; 153 | SETUP_EP0_IN_BUF(2); 154 | } 155 | } else { 156 | handle_usb_setup(req); 157 | } 158 | 159 | CLEAR_USB_IRQ(); 160 | USBIRQ = _SUDAV; 161 | 162 | __asm 163 | pop _DPS 164 | __endasm; 165 | } 166 | 167 | static usb_desc_langid_c usb_langid = { 168 | .bLength = sizeof(struct usb_desc_langid) + sizeof(uint16_t) * 1, 169 | .bDescriptorType = USB_DESC_STRING, 170 | .wLANGID = { /* English (United States) */ 0x0409 }, 171 | }; 172 | 173 | void usb_serve_descriptor(usb_descriptor_set_c *set, 174 | enum usb_descriptor type, uint8_t index) { 175 | #define APPEND(desc) \ 176 | do { \ 177 | xmemcpy(buf, (__xdata void *)(desc), (desc)->bLength); \ 178 | buf += (desc)->bLength; \ 179 | } while(0) 180 | 181 | __xdata uint8_t *buf = scratch; 182 | 183 | if(type == USB_DESC_DEVICE && index == 0) { 184 | APPEND(set->device); 185 | } else if(type == USB_DESC_DEVICE_QUALIFIER && index == 0) { 186 | if(!set->device_qualifier) { 187 | STALL_EP0(); 188 | return; 189 | } 190 | APPEND(set->device_qualifier); 191 | } else if(type == USB_DESC_CONFIGURATION && index < set->config_count) { 192 | usb_configuration_c *config = set->configs[index]; 193 | __xdata struct usb_desc_configuration *config_desc = 194 | (__xdata struct usb_desc_configuration *)buf; 195 | __code const union usb_config_item *config_item = &config->items[0]; 196 | 197 | APPEND(&config->desc); 198 | do { 199 | APPEND(config_item->generic); 200 | } while((++config_item)->generic); 201 | 202 | // Fix up wTotalLength so we don't need to calculate it explicitly. 203 | if(config_desc->wTotalLength == 0) 204 | config_desc->wTotalLength = (uint16_t)(buf - scratch); 205 | } else if(type == USB_DESC_STRING && index == 0) { 206 | APPEND(&usb_langid); 207 | } else if(type == USB_DESC_STRING && index - 1 < set->string_count) { 208 | __code const char *string = set->strings[index - 1]; 209 | *buf++ = 2; // bLength 210 | *buf++ = USB_DESC_STRING; // bDescriptorType 211 | while(*string) { 212 | *buf++ = *string++; 213 | *buf++ = 0; 214 | scratch[0] += 2; 215 | } 216 | } else if(type == USB_DESC_BINARY_OBJECT_STORE && index == 0) { 217 | __xdata struct usb_desc_binary_object_store *bos_desc = 218 | (__xdata struct usb_desc_binary_object_store *)buf; 219 | bos_desc->bLength = sizeof(struct usb_desc_binary_object_store); 220 | bos_desc->bDescriptorType = USB_DESC_BINARY_OBJECT_STORE; 221 | // bos_desc->wTotalLength filled in later 222 | bos_desc->bNumDeviceCaps = set->capability_count; 223 | buf += bos_desc->bLength; 224 | __code const struct usb_desc_generic *dev_cap_desc = set->capabilities; 225 | for(uint8_t i = 0; i < set->capability_count; i++) { 226 | APPEND(dev_cap_desc); 227 | dev_cap_desc++; 228 | } 229 | bos_desc->wTotalLength = (uint16_t)(buf - scratch); 230 | SETUP_EP0_IN_DATA(scratch, bos_desc->wTotalLength); 231 | return; 232 | } else { 233 | STALL_EP0(); 234 | return; 235 | } 236 | 237 | SETUP_EP0_IN_DESC(scratch); 238 | } 239 | #undef APPEND 240 | 241 | void usb_reset_data_toggles(usb_descriptor_set_c *set, uint8_t interface_num, 242 | uint8_t alt_setting) { 243 | uint8_t nconfig; 244 | for(nconfig = 0; nconfig < set->config_count; nconfig++) { 245 | usb_configuration_c *config = set->configs[nconfig]; 246 | __code const union usb_config_item *config_item = &config->items[0]; 247 | bool use_interface = false; 248 | 249 | if(config->desc.bConfigurationValue != usb_config_value) 250 | continue; 251 | 252 | do { 253 | if(config_item->generic->bDescriptorType == USB_DESC_INTERFACE) { 254 | use_interface = (config_item->interface->bInterfaceNumber == interface_num && 255 | config_item->interface->bAlternateSetting == alt_setting); 256 | } else if(config_item->generic->bDescriptorType == USB_DESC_ENDPOINT) { 257 | if(!use_interface) continue; 258 | 259 | TOGCTL = (config_item->endpoint->bEndpointAddress & 0x0f) | 260 | ((config_item->endpoint->bEndpointAddress & 0x80) >> 3); 261 | TOGCTL |= _R; 262 | } 263 | } while((++config_item)->generic); 264 | } 265 | } 266 | --------------------------------------------------------------------------------