diff --git a/CMakeLists.txt b/CMakeLists.txt index 625af77..51a4aab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,13 @@ set(SOURCES_COMMON src/tmu/inth-tmu.s src/tmu/sleep.c src/tmu/tmu.c + src/usb/classes/ff-bulk.c + src/usb/configure.c + src/usb/inth.s + src/usb/pipes.c + src/usb/setup.c + src/usb/string.c + src/usb/usb.c src/3rdparty/tinymt32/rand.c src/3rdparty/tinymt32/tinymt32.c src/3rdparty/grisu2b_59_56/grisu2b_59_56.c diff --git a/include/gint/intc.h b/include/gint/intc.h index ff74c07..959e13d 100644 --- a/include/gint/intc.h +++ b/include/gint/intc.h @@ -41,6 +41,8 @@ enum { /* SPU; interrupts from the DSPs and the SPU-bound DMA */ INTC_SPU_DSP0, INTC_SPU_DSP1, + /* USB communication */ + INTC_USB, }; //--- diff --git a/include/gint/mpu/cpg.h b/include/gint/mpu/cpg.h index bbce5a6..d43d099 100644 --- a/include/gint/mpu/cpg.h +++ b/include/gint/mpu/cpg.h @@ -64,7 +64,14 @@ typedef volatile struct uint32_t SRC :2; /* Clock source select */ uint32_t DIVA :6; /* Division ratio for port A */ ); - pad(0x18); + pad(0x08); + + lword_union(USBCLKCR, + uint32_t :23; + uint32_t CLKSTP :1; /* Clock Stop */ + uint32_t :8; + ); + pad(0x0c); lword_union(PLLCR, uint32_t :17; diff --git a/include/gint/mpu/power.h b/include/gint/mpu/power.h index 274c297..59c4e5d 100644 --- a/include/gint/mpu/power.h +++ b/include/gint/mpu/power.h @@ -63,6 +63,7 @@ typedef volatile struct uint32_t MSIOF1 :1; uint32_t :1; ); + pad(4); /* Module Stop Control Register 2 The list was established by Yatis. See + +typedef volatile struct +{ + /* System Configuration Control Register */ + word_union(SYSCFG, + uint16_t :5; + uint16_t SCKE :1; /* USB Module Clock Enabled */ + uint16_t :2; + uint16_t HSE :1; /* High Speed Enable */ + uint16_t DCFM :1; /* Controller Function Select */ + uint16_t DRPD :1; /* D+/D- Line Resistor Control */ + uint16_t DPRPU :1; /* D+ Line Resistor Control */ + uint16_t :3; + uint16_t USBE :1; /* USB Module Enable */ + ); + + /* CPU Bus Wait Setting Register */ + word_union(BUSWAIT, + uint16_t :12; + uint16_t BWAIT :4; /* Bus Wait */ + ); + + /* System Configuration Status Register */ + word_union(SYSSTS, + uint16_t :14; + uint16_t LNST :2; /* Line Status */ + ); + pad(2); + + /* Device State Control Register */ + word_union(DVSTCTR, + uint16_t :7; + uint16_t WKUP :1; /* Wakeup Output */ + uint16_t RWUPE :1; /* Wakeup Detection Enable */ + uint16_t USBRT :1; /* USB Reset Output */ + uint16_t RESUME :1; /* Resume Output */ + uint16_t UACT :1; /* USB Bus Enable */ + uint16_t :1; + uint16_t RHST :3; /* Reset Handshake */ + ); + pad(2); + + /* Test Mode Register */ + word_union(TESTMODE, + uint16_t :12; + uint16_t UTST :4; /* Test Mode */ + ); + pad(6); + + /* FIFO Port Registers */ + uint32_t CFIFO; /* DCP FIFO port */ + uint32_t D0FIFO; /* Data 0 FIFO port */ + uint32_t D1FIFO; /* Data 1 FIFO port */ + + /* FIFO Port Select and Control Registers */ + word_union(CFIFOSEL, + uint16_t RCNT :1; /* Read Count Mode */ + uint16_t REW :1; /* Pointer Buffer Rewind */ + uint16_t :2; + uint16_t MBW :2; /* Access Bits Width */ + uint16_t :1; + uint16_t BIGEND :1; /* Endiant Control */ + uint16_t :2; + uint16_t ISEL :1; /* Access Direction When DCP is used */ + uint16_t :1; + uint16_t CURPIPE:4; /* Port Access Pipe Specification */ + ); + word_union(CFIFOCTR, + uint16_t BVAL :1; /* Buffer Memory Valid Flags */ + uint16_t BCLR :1; /* CPU Buffer Clear */ + uint16_t FRDY :1; /* FIFO Port Ready */ + uint16_t :1; + uint16_t DTLN :12; /* Receive Data Length */ + ); + pad(4); + word_union(D0FIFOSEL, + uint16_t RCNT :1; /* Read Count Mode */ + uint16_t REW :1; /* Pointer Buffer Rewind */ + uint16_t DCLRM :1; /* Auto Buffer Clear Mode */ + uint16_t DREQE :1; /* DMA Transfert Request Enable */ + uint16_t MBW :2; /* Access Bits Width */ + uint16_t :1; + uint16_t BIGEND :1; /* Endiant Control */ + uint16_t :4; + uint16_t CURPIPE:4; /* Port Access Pipe Specification */ + ); + word_union(D0FIFOCTR, + uint16_t BVAL :1; /* Buffer Memory Valid Flags */ + uint16_t BCLR :1; /* CPU Buffer Clear */ + uint16_t FRDY :1; /* FIFO Port Ready */ + uint16_t :1; + uint16_t DTLN :12; /* Receive Data Length */ + ); + word_union(D1FIFOSEL, + uint16_t RCNT :1; /* Read Count Mode */ + uint16_t REW :1; /* Pointer Buffer Rewind */ + uint16_t DCLRM :1; /* Auto Buffer Clear Mode */ + uint16_t DREQE :1; /* DMA Transfert Request Enable */ + uint16_t MBW :2; /* Access Bits Width */ + uint16_t :1; + uint16_t BIGEND :1; /* Endian Control */ + uint16_t :4; + uint16_t CURPIPE:4; /* Port Access Pipe Specification */ + ); + word_union(D1FIFOCTR, + uint16_t BVAL :1; /* Buffer Memory Valid Flags */ + uint16_t BCLR :1; /* CPU Buffer Clear */ + uint16_t FRDY :1; /* FIFO Port Ready */ + uint16_t :1; + uint16_t DTLN :12; /* Receive Data Length */ + ); + + /* Interrupts Enable Registers */ + word_union(INTENB0, + uint16_t VBSE :1; /* VBUS */ + uint16_t RSME :1; /* Resume */ + uint16_t SOFE :1; /* Frame Number Update */ + uint16_t DVSE :1; /* Device State Transition */ + uint16_t CTRE :1; /* Control Transfer Stage Transition */ + uint16_t BEMPE :1; /* Buffer Empty */ + uint16_t NRDYE :1; /* Buffer Not Ready */ + uint16_t BRDYE :1; /* Buffer Ready */ + uint16_t :8; + ); + pad(4); + + /* BRDY Interrupt Enable Register */ + word_union(BRDYENB, + uint16_t :6; + uint16_t PIPE9BRDYE :1; + uint16_t PIPE8BRDYE :1; + uint16_t PIPE6BRDYE :1; + uint16_t PIPE7BRDYE :1; + uint16_t PIPE5BRDYE :1; + uint16_t PIPE3BRDYE :1; + uint16_t PIPE4BRDYE :1; + uint16_t PIPE2BRDYE :1; + uint16_t PIPE1BRDYE :1; + uint16_t PIPE0BRDYE :1; + ); + /* NRDY Interrupt Enable Register */ + word_union(NRDYENB, + uint16_t :6; + uint16_t PIPE9NRDYE :1; + uint16_t PIPE8NRDYE :1; + uint16_t PIPE6NRDYE :1; + uint16_t PIPE7NRDYE :1; + uint16_t PIPE5NRDYE :1; + uint16_t PIPE3NRDYE :1; + uint16_t PIPE4NRDYE :1; + uint16_t PIPE2NRDYE :1; + uint16_t PIPE1NRDYE :1; + uint16_t PIPE0NRDYE :1; + ); + /* BEMP Interrupt Enable Register */ + word_union(BEMPENB, + uint16_t :6; + uint16_t PIPE9BEMPE :1; + uint16_t PIPE8BEMPE :1; + uint16_t PIPE6BEMPE :1; + uint16_t PIPE7BEMPE :1; + uint16_t PIPE5BEMPE :1; + uint16_t PIPE3BEMPE :1; + uint16_t PIPE4BEMPE :1; + uint16_t PIPE2BEMPE :1; + uint16_t PIPE1BEMPE :1; + uint16_t PIPE0BEMPE :1; + ); + + /* SOF Control Register */ + word_union(SOFCFG, + uint16_t :7; + uint16_t TRNENSEL :1; /* Transaction-Enabled Time Select */ + uint16_t :1; + uint16_t BRDYM :1; /* BRDY Status Clear Timing */ + uint16_t enable :1; /* SHOULD BE SET TO 1 MANUALLY */ + uint16_t :5; + ); + pad(2); + + /* Interrupt Status Registers */ + word_union(INTSTS0, + uint16_t VBINT :1; /* VBUS */ + uint16_t RESM :1; /* Resume */ + uint16_t SOFR :1; /* Frame Number Refresh */ + uint16_t DVST :1; /* Device State Transition */ + uint16_t CTRT :1; /* Control Transfer Stage Transition */ + uint16_t BEMP :1; /* Buffer Empty */ + uint16_t NRDY :1; /* Buffer Not Ready */ + uint16_t BRDY :1; /* Buffer Ready */ + uint16_t VBSTS :1; /* VBUS Input Status */ + uint16_t DVSQ :3; /* Device state */ + uint16_t VALID :1; /* USB Request Reception */ + uint16_t CTSQ :3; /* Control Transfer Stage */ + ); + pad(4); + + /* BRDY Interrupt Status Register */ + word_union(BRDYSTS, + uint16_t :6; + uint16_t PIPE9BRDY :1; + uint16_t PIPE8BRDY :1; + uint16_t PIPE6BRDY :1; + uint16_t PIPE7BRDY :1; + uint16_t PIPE5BRDY :1; + uint16_t PIPE3BRDY :1; + uint16_t PIPE4BRDY :1; + uint16_t PIPE2BRDY :1; + uint16_t PIPE1BRDY :1; + uint16_t PIPE0BRDY :1; + ); + /* NRDY Interrupt Status Register */ + word_union(NRDYSTS, + uint16_t :6; + uint16_t PIPE9NRDY :1; + uint16_t PIPE8NRDY :1; + uint16_t PIPE6NRDY :1; + uint16_t PIPE7NRDY :1; + uint16_t PIPE5NRDY :1; + uint16_t PIPE3NRDY :1; + uint16_t PIPE4NRDY :1; + uint16_t PIPE2NRDY :1; + uint16_t PIPE1NRDY :1; + uint16_t PIPE0NRDY :1; + ); + /* BEMP Interrupt Status Register */ + word_union(BEMPSTS, + uint16_t :6; + uint16_t PIPE9BEMP :1; + uint16_t PIPE8BEMP :1; + uint16_t PIPE6BEMP :1; + uint16_t PIPE7BEMP :1; + uint16_t PIPE5BEMP :1; + uint16_t PIPE3BEMP :1; + uint16_t PIPE4BEMP :1; + uint16_t PIPE2BEMP :1; + uint16_t PIPE1BEMP :1; + uint16_t PIPE0BEMP :1; + ); + + /* Frame Number Registers */ + word_union(FRMNUM, + uint16_t OVRN :1; /* Overrun/Underrun Detection Status */ + uint16_t CRCE :1; /* Receive Data Error */ + uint16_t cons :3; + uint16_t FRNM :11; /* Frame Number */ + ); + word_union(UFRMNUM, + uint16_t :13; + uint16_t UFRNM :3; /* uFrame */ + ); + + /* USB Address Register */ + word_union(USBADDR, + uint16_t :9; + uint16_t const USBADDR :7; /* USB Address */ + ); + pad(2); + + /* USB Request Type Register */ + word_union(USBREQ, + uint16_t BREQUEST :8; /* USB request data value */ + uint16_t BMREQUEST :8; /* USB request type value */ + ); + /* USB Request Value Register */ + word_union(USBVAL, + uint16_t WVALUE :16; /* USB request wValue value */ + ); + /* USB Request Index Register */ + word_union(USBINDX, + uint16_t WINDEX :16; /* USB USB request wIndex value */ + ); + /* USB Request Length Register */ + word_union(USBLENG, + uint16_t WLENGTH :16; /* USB USB request wLength value */ + ); + + /* DCP Configuration Register */ + word_union(DCPCFG, + uint16_t :11; + uint16_t DIR :1; /* Transfer Direction */ + uint16_t :4; + ); + /* DCP Maximum Packet Size Register */ + word_union(DCPMAXP, + uint16_t DEVSEL :4; /* Device Select */ + uint16_t :5; + uint16_t MXPS :7; /* Maximum Packet Size */ + ); + /* DCP Control Register */ + word_union(DCPCTR, + uint16_t BSTS :1; /* Buffer Status */ + uint16_t SUREQ :1; /* SETUP Token Transmission */ + uint16_t CSCLR :1; /* C-SPLIT Status Clear */ + uint16_t CSSTS :1; /* C-SPLIT Status */ + uint16_t SUREQCLR :1; /* SUREQ Bit Clear */ + uint16_t :2; + uint16_t SQCLR :1; /* Toggle Bit Clear */ + uint16_t SQSET :1; /* Toggle Bit Set */ + uint16_t SQMON :1; /* Sequence Toggle Bit Monitor */ + uint16_t PBUSY :1; /* Pipe Busy */ + uint16_t PINGE :1; /* PING Token Issue Enable */ + uint16_t :1; + uint16_t CCPL :1; /* Control Transfer End Enable */ + uint16_t PID :2; /* Response PID */ + ); + pad(2); + + /* Pipe Window Select Register */ + word_union(PIPESEL, + uint16_t :12; + uint16_t PIPESEL :4; /* Pipe Window Select */ + ); + pad(2); + + /* Pipe Configuration Register */ + word_union(PIPECFG, + uint16_t TYPE :2; /* Transfer Type */ + uint16_t :3; + uint16_t BFRE :1; /* BRDY Interrupt Operation Specification */ + uint16_t DBLB :1; /* Double Buffer Mode */ + uint16_t CNTMD :1; /* Continuous Transfer Mode */ + uint16_t SHTNAK :1; /* Pipe Disabled at End of Transfer */ + uint16_t :2; + uint16_t DIR :1; /* Transfer Direction */ + uint16_t EPNUM :4; /* Endpoint Number */ + ); + + /* Pipe Buffer Setting Register */ + word_union(PIPEBUF, + uint16_t :1; + uint16_t BUFSIZE : 5; /* Buffer Size */ + uint16_t :2; + uint16_t BUFNMB :8; /* Buffer Number */ + ); + + /* Pipe Maximum Packet Size Register */ + word_union(PIPEMAXP, + uint16_t DEVSEL :4; /* Device Select */ + uint16_t :1; + uint16_t MXPS :11; /* Maximum Packet Size */ + ); + + /* Pipe Timing Control Register */ + word_union(PIPEPERI, + uint16_t :3; + uint16_t IFIS :1; /* Isochronous IN Buffer Flush */ + uint16_t :9; + uint16_t IITV :3; /* Interval Error Detection Interval */ + ); + + /* PIPEn Control Registers */ + word_union(PIPECTR[9], + uint16_t BSTS :1; /* Buffer Status */ + uint16_t INBUFM :1; /* IN Buffer Monitor (PIPE1..5) */ + uint16_t CSCLR :1; /* C-SPLIT Status Clear Bit */ + uint16_t CSSTS :1; /* CSSTS Status Bit */ + uint16_t :1; + uint16_t ATREPM :1; /* Auto Response Mode (PIPE1..5) */ + uint16_t ACLRM :1; /* Auto Buffer Clear Mode */ + uint16_t SQCLR :1; /* Toggle Bit Clear */ + uint16_t SQSET :1; /* Toggle Bit Set */ + uint16_t SQMON :1; /* Toggle Bit Confirmation */ + uint16_t PBUSY :1; /* Pipe Busy */ + uint16_t :3; + uint16_t PID :2; /* Response PID */ + ); + pad(14); + + /* PIPEn Transaction Counter Enable Registers and */ + /* PIPEn Transaction Counter Registers */ + word_union(PIPE1TRE, + uint16_t :6; + uint16_t TRENB :1; /* Transaction Counter Enable */ + uint16_t TRCLR :1; /* Transaction Counter Clear */ + uint16_t :8; + ); + word_union(PIPE1TRN, + uint16_t TRCNT :16; /* Transaction Counter */ + ); + word_union(PIPE2TRE, + uint16_t :6; + uint16_t TRENB :1; /* Transaction Counter Enable */ + uint16_t TRCLR :1; /* Transaction Counter Clear */ + uint16_t :8; + ); + word_union(PIPE2TRN, + uint16_t TRCNT :16; /* Transaction Counter */ + ); + word_union(PIPE3TRE, + uint16_t :6; + uint16_t TRENB :1; /* Transaction Counter Enable */ + uint16_t TRCLR :1; /* Transaction Counter Clear */ + uint16_t :8; + ); + word_union(PIPE3TRN, + uint16_t TRCNT :16; /* Transaction Counter */ + ); + word_union(PIPE4TRE, + uint16_t :6; + uint16_t TRENB :1; /* Transaction Counter Enable */ + uint16_t TRCLR :1; /* Transaction Counter Clear */ + uint16_t :8; + ); + word_union(PIPE4TRN, + uint16_t TRCNT :16; /* Transaction Counter */ + ); + word_union(PIPE5TRE, + uint16_t :6; + uint16_t TRENB :1; /* Transaction Counter Enable */ + uint16_t TRCLR :1; /* Transaction Counter Clear */ + uint16_t :8; + ); + word_union(PIPE5TRN, + uint16_t TRCNT :16; /* Transaction Counter */ + ); + pad(0x1e); + + uint16_t REG_C2; + +} GPACKED(4) sh7305_usb_t; + +typedef volatile word_union(sh7305_usb_uponcr_t, + uint16_t :5; + uint16_t UPON :2; /* USB Power ON */ + uint16_t :9; +); + +#define SH7305_USB (*(sh7305_usb_t *)0xa4d80000) +#define SH7305_USB_UPONCR (*(sh7305_usb_uponcr_t *)0xa40501d4) + +#endif /* GINT_MPU_USB */ diff --git a/include/gint/std/endian.h b/include/gint/std/endian.h new file mode 100644 index 0000000..7b98628 --- /dev/null +++ b/include/gint/std/endian.h @@ -0,0 +1,28 @@ +//--- +// gint:std:endian - Endianness conversion +//--- + +#ifndef GINT_STD_ENDIAN +#define GINT_STD_ENDIAN + +#include +#include + +/* CASIO calculators are configured as big-endian. */ + +#define htobe16(x) (x) +#define htole16(x) (__builtin_bswap16(x)) +#define be16toh(x) (x) +#define le16toh(x) (__builtin_bswap16(x)) + +#define htobe32(x) (x) +#define htole32(x) (__builtin_bswap32(x)) +#define be32toh(x) (x) +#define le32toh(x) (__builtin_bswap32(x)) + +#define htobe64(x) (x) +#define htole64(x) (__builtin_bswap64(x)) +#define be64toh(x) (x) +#define le64toh(x) (__builtin_bswap64(x)) + +#endif /* GINT_STD_ENDIAN */ diff --git a/include/gint/usb-ff-bulk.h b/include/gint/usb-ff-bulk.h new file mode 100644 index 0000000..4a14c87 --- /dev/null +++ b/include/gint/usb-ff-bulk.h @@ -0,0 +1,60 @@ +//--- +// gint:usb-ff-bulk - A trivial bulk-based transfer class +//--- + +#ifndef GINT_USB_FF_BULK +#define GINT_USB_FF_BULK + +#include + +/* The bulk transfer interface with class code 0xff provides a very simple + communication channel between the calculator and a host. There is + (currently) a single IN pipe that sends data from the calculator to the + host, at high-speed (USB 2.0). + + The class code of this interface is 0xff, which means that the protocol is + custom and requires a custom program on the host to receive the data (unlike + for instance LINK on a Graph 90+E which behaves as a USB stick and can be + used with the file browser). fxlink can be used to the effect. */ + +extern usb_interface_t const usb_ff_bulk; + +//--- +// fxlink protocol +//--- + +/* usb_fxlink_header_t: Packet header for fxlink + + fxlink supports a minimalistic protocol to receive data sent from the + calculator and automatically process it (such as save it to file, convert + to an image, etc). It is designed as a convenience feature, and it can be + extended with custom types rather easily. + + A packet is categorized with an (application, type) pair; both are UTF-8 + strings of up to 16 characters. The application name "fxlink" is reserved + for built-in types supported by fxlink; any other custom application name + can be set (in which case fxlink will call a user-provided program to handle + the packet). + + The size of the data to be transferred must be specified in order of the + transfer to proceed correctly, as it cannot reliably be guessed on the other + side (and guessing wrong means all further packets will be in trouble). + + Most of the time you don't need to create custom packets yourself since the + convenience functions below will create them for you. */ +typedef struct +{ + /* Protocol version = 0x00000100 */ + uint32_t version; + /* Size of the data to transfer, including this header */ + uint32_t size; + /* Size of individual transfers (related to the size of the FIFO) */ + uint32_t transfer_size; + /* Application name, UTF-8 (might not be zero-terminated) */ + char application[16]; + /* Packet type */ + char type[16]; + +} usb_fxlink_header_t; + +#endif /* GINT_USB_FF_BULK */ diff --git a/include/gint/usb.h b/include/gint/usb.h new file mode 100644 index 0000000..53d4545 --- /dev/null +++ b/include/gint/usb.h @@ -0,0 +1,235 @@ +//--- +// gint:usb - USB communication +//--- + +#ifndef GINT_USB +#define GINT_USB + +#include +#include +#include +#include + +//--- +// Interfaces to communicate with USB transfers +// +// These interfaces define how the calculator behaves on the USB connection, +// and can include stuff like: +// +// -> Communicate with a custom protocol and a custom program on the PC +// (like Protocol 7 does with FA-124, or fxlink) +// -> Exchange text as a Communications and CDC Control (class 0x03) device +// (like an Internet router) +// -> Share a video stream as a video input (class 0x0e) device (like a webcam) +// +// Normal add-ins that just want to use the USB connection don't need to worry +// about programming the interfaces; they can simply use interfaces that are +// already implemented. Start with usb_open(). +//--- + +/* usb_interface_t: A USB interface that can be enabled in usb_open() + + This driver provides a device that only has one configuration (due to how + rare it is for devices to have several configurations). However, a number of + interfaces can be activated independently. This structure describes an + interface with regards to this driver. + + The driver chooses endpoint numbers and slices of the FIFO buffer for the + interface to use, therefore the supplied descriptors cannot specify them. + Instead, the supplied descriptors should use arbitrary endpoint numbers; the + driver will use them to communicate with the interface, and transparently + use concrete endpoint numbers internally. */ +typedef struct { + /* NULL-terminated array of descriptors for the interface */ + void const **dc; + /* Array of endpoint parameters, see below */ + struct usb_interface_endpoint *params; + + /* Answer class-specific SETUP requests */ + /* TODO */ + + /* Receive data from an endpoint */ + /* TODO */ + +} usb_interface_t; + +/* usb_interface_endpoint_t: Parameters for an interface endpoint + + This structure mainly specifies the settings for the pipe associated to the + endpoint. There 10 pipes, but not all can be used with any transfer type, + and not all have access to the same amount of memory. */ +typedef struct usb_interface_endpoint { + /* Endpoint number as specified in the interface's descriptors + (including the IN/OUT bit) */ + uint8_t endpoint; + /* Requested buffer size, should be a multiple of 64 and not more than + 2048. Valid only for bulk and isochronous endpoints. */ + uint16_t buffer_size; + +} usb_interface_endpoint_t; + +/* usb_open(): Open the USB link + + This function opens the USB link and notifies the host that the device is + ready to connect. Usually the host immediately queries the device, and after + some exchanges the device can be used. The USB link might not be ready when + this function returns, use the callback or usb_open_wait() for that. + + The first parameters is a NULL-terminated array of interfaces to open. To + see available interfaces, please see header files in . Each + interface can be used independently, however if there are not enough USB + resources (buffer memory, pipes or endpoints) for all of them, usb_open() + will return an error. + + The second parameter is a callback to be (asynchronously) invoked when the + USB link becomes ready. Use GINT_CB() to create one, or pass GINT_CB_NULL + for no callback. You can also use usb_open_wait() to synchronously wait for + the link to be ready. + + @interfaces NULL-terminate list of interfaces to open + @callback Optional function to be called when the USB link opens */ +int usb_open(usb_interface_t const **interfaces, gint_callback_t callback); + +/* usb_open_wait(): Wait until the USB link is ready + When called after usb_open(), this function waits until the communication is + established. You should only call this if usb_open() returns 0. */ +void usb_open_wait(void); + +/* usb_close(): Close the USB link + + This function closes the link opened by usb_open(), and notifies the host of + the disconnection (if any was established). The USB link can be reopened + later to perform more tasks. + + There are two reasons to close the USB link: to save battery power and to + return to the calculator's main menu. You should thus close it if (1) the + USB link might not be used for a while, or (2) you want to return to the + main menu before using it again. */ +void usb_close(void); + +//--- +// USB debugging log +//--- + +/* usb_set_log(): Set the logging function for the USB driver + + The USB driver can produce logs, which are mostly useful to troubleshoot + problems and add new protocols. The logging is disabled by default but can + be enabled by specifying this function. + + It is up to you whether to store that in a buffer, rotate logs, send them to + storage memory or a console on the PC. */ +void usb_set_log(void (*logger)(char const *format, va_list args)); + +/* usb_log(): Send a message to the USB log */ +void usb_log(char const *format, ...); + +//--- +// Standard descriptors +//--- + +/* Descriptor types */ +enum { + USB_DC_DEVICE = 1, + USB_DC_CONFIGURATION = 2, + USB_DC_STRING = 3, + USB_DC_INTERFACE = 4, + USB_DC_ENDPOINT = 5, + USB_DC_DEVICE_QUALIFIER = 6, + USB_DC_OTHER_SPEED_CONFIGURATION = 7, + USB_DC_INTERFACE_POWER = 8, +}; + +/* Standard DEVICE descriptor */ +typedef struct { + uint8_t bLength; /* = 18 */ + uint8_t bDescriptorType; /* = USB_DC_DEVICE */ + uint16_t bcdUSB; + + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + + uint16_t idVendor; + uint16_t idProduct; + + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + + uint8_t iSerialNumber; + uint8_t bNumConfigurations; + +} GPACKED(2) usb_dc_device_t; + +/* Standard CONFIGURATION descriptor */ +typedef struct { + uint8_t bLength; /* = 9 */ + uint8_t bDescriptorType; /* = USB_DC_CONFIG */ + uint16_t wTotalLength; + + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + + uint8_t bmAttributes; + uint8_t bMaxPower; + +} GPACKED(1) usb_dc_configuration_t; + +/* Standard INTERFACE descriptor */ +typedef struct { + uint8_t bLength; /* = 9 */ + uint8_t bDescriptorType; /* = USB_DC_INTERFACE */ + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + + uint8_t iInterface; + +} GPACKED(1) usb_dc_interface_t; + +/* Standard ENDPOINT descriptor */ +typedef struct +{ + uint8_t bLength; /* = 7 */ + uint8_t bDescriptorType; /* = USB_DC_ENDPOINT */ + uint8_t bEndpointAddress; + uint8_t bmAttributes; + + uint16_t wMaxPacketSize; + uint8_t bInterval; + +} GPACKED(1) usb_dc_endpoint_t; + +/* Standard STRING descriptor */ +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; /* = USB_DC_STRING */ + uint16_t data[]; + +} GPACKED(2) usb_dc_string_t; + +/* usb_dc_string(): Create a STRING descriptor and return its ID + + This function registers the provided string in an array of STRING + descriptors used to answer GET_DESCRIPTOR requests, and returns the string's + ID. USB 2.0 only has provision for 256 strings in the device, so this + function will return 0 when out of space. + + The string should be encoded as UTF-16 (big-endian), which can be achieved + with a "u" prefix on the string literal, for instance: u"Hello,World!". If + (len) is specified, it should be the number of UTF-16 code points to count + in the string. If it is 0, it defaults to the length of the string. */ +uint16_t usb_dc_string(uint16_t const *literal, size_t len); + +/* usb_dc_string_get(): Get the descriptor for a STRING id + This is mostly used by the driver to answer GET_DESCRIPTOR requests. */ +usb_dc_string_t *usb_dc_string_get(uint16_t id); + +#endif /* GINT_USB */ diff --git a/src/intc/intc.c b/src/intc/intc.c index 9ebee1e..9755cfc 100644 --- a/src/intc/intc.c +++ b/src/intc/intc.c @@ -59,6 +59,8 @@ static struct info { /* SPU */ { IPRC, 0x000f, IMR3, 0x04, _ /* Not supported on SH3! */ }, { IPRC, 0x000f, IMR4, 0x08, _ }, + /* USB */ + { IPRF, 0x00f0, IMR9, 0x02, _ /* Driver not SH3-compatible yet */ }, }; diff --git a/src/usb/classes/ff-bulk.c b/src/usb/classes/ff-bulk.c new file mode 100644 index 0000000..a760b70 --- /dev/null +++ b/src/usb/classes/ff-bulk.c @@ -0,0 +1,44 @@ +#include + +static usb_dc_interface_t dc_interface = { + .bLength = sizeof(usb_dc_interface_t), + .bDescriptorType = USB_DC_INTERFACE, + .bInterfaceNumber = -1 /* Set by driver */, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = 0xff, /* Vendor-Specific */ + .bInterfaceSubClass = 0x77, /* (not recognized by Casio tools?) */ + .bInterfaceProtocol = 0x00, + .iInterface = 0, +}; + +/* Endpoint for calculator -> PC communication */ +static usb_dc_endpoint_t dc_endpoint1i = { + .bLength = sizeof(usb_dc_endpoint_t), + .bDescriptorType = USB_DC_ENDPOINT, + .bEndpointAddress = 0x81, /* 1 IN */ + .bmAttributes = 0x02, /* Bulk transfer */ + /* TODO: Additional transactions per µframe ?! */ + .wMaxPacketSize = htole16(512), + .bInterval = 1, +}; + +usb_interface_t usb_ff_bulk = { + /* List of descriptors */ + .dc = (void const *[]){ + &dc_interface, + &dc_endpoint1i, + NULL, + }, + /* Parameters for each endpoint */ + .params = (usb_interface_endpoint_t []){ + { .endpoint = 0x81, /* 1 IN */ + .buffer_size = 2048, }, + { 0 }, + }, +}; + +GCONSTRUCTOR static void set_strings(void) +{ + dc_interface.iInterface = usb_dc_string(u"Bulk Input", 0); +} diff --git a/src/usb/configure.c b/src/usb/configure.c new file mode 100644 index 0000000..5f5b854 --- /dev/null +++ b/src/usb/configure.c @@ -0,0 +1,211 @@ +#include +#include "usb_private.h" + +//--- +// Endpoint assignment +//--- + +/* Current configuration: list of interfaces and endpoint assignments */ +static usb_interface_t const *conf_if[16]; +static endpoint_t conf_ep[32]; + +/* usb_configure_endpoint(): Get endpoint data for a concrete address */ +endpoint_t *usb_configure_endpoint(int endpoint) +{ + /* Refuse access to endpoint 0, which is for the DCP */ + if(endpoint < 0 || ((endpoint & 0x0f) == 0)) return NULL; + + int dir = (endpoint & 0x80) ? 0x10 : 0; + endpoint &= 0x7f; + + if(endpoint <= 0 || endpoint > 15) return NULL; + return &conf_ep[dir + endpoint]; +} + +/* usb_configure_address(): Get the concrete endpoint address */ +int usb_configure_address(usb_interface_t const *intf, int address) +{ + int dir = address & 0x80; + int base = address & 0x0f; + + for(int i = 0; i < 32; i++) + { + if(conf_ep[i].intf != intf) continue; + if((conf_ep[i].dc->bEndpointAddress & 0x0f) != base) continue; + return (i & 0x0f) | dir; + } + return -1; +} + +//--- +// Resource allocation +//--- + +/* is_pipe_used(): Determine whether a pipe is used by an endpoint */ +static bool is_pipe_used(int pipe) +{ + for(int i = 0; i < 32; i++) + { + if(conf_ep[i].pipe == pipe) return true; + } + return false; +} + +/* find_pipe(): Find an unused pipe for the specified type of transfer */ +static int find_pipe(int type) +{ + int min=0, max=-1; + + /* Isochronous transfers: use pipes 1,2 */ + if(type == 1) min=1, max=2; + /* Bulk transfers: try 3..5 first, leaving 1,2 for isochronous */ + if(type == 2) min=1, max=5; + /* Interrupt transfers: use pipes 6..9 */ + if(type == 3) min=6, max=9; + + /* Start from the end to avoid using pipes 1,2 on bulk transfers */ + for(int pipe = max; pipe >= min; pipe--) + { + if(!is_pipe_used(pipe)) return pipe; + } + return -1; +} + +/* usb_configure_solve(): Allocate resources for all activated interfaces */ +int usb_configure_solve(usb_interface_t const **interfaces) +{ + /* Reset the previous configuration */ + for(int i = 0; i < 16; i++) + { + conf_if[i] = NULL; + } + for(int i = 0; i < 32; i++) + { + conf_ep[i].intf = NULL; + conf_ep[i].dc = NULL; + conf_ep[i].pipe = 0; + conf_ep[i].bufnmb = 0; + conf_ep[i].bufsize = 0; + } + + /* Next interface number to assign */ + int next_interface = 0; + /* Next endpoint to assign */ + int next_endpoint = 1; + /* Next buffer position to assign for pipes 1..5 */ + int next_bufnmb = 8; + + for(int i = 0; interfaces[i]; i++) + { + if(i == 16) return USB_OPEN_TOO_MANY_INTERFACES; + usb_interface_t const *intf = interfaces[i]; + + conf_if[next_interface++] = intf; + + for(int k = 0; intf->dc[k]; k++) + { + uint8_t const *dc = intf->dc[k]; + if(dc[1] != USB_DC_ENDPOINT) continue; + + /* If the same endpoint with a different direction has + already been assigned, use that */ + int address = usb_configure_address(intf, dc[2]); + if(address == -1) + { + if(next_endpoint >= 16) + return USB_OPEN_TOO_MANY_ENDPOINTS; + address = (next_endpoint++) | (dc[2] & 0x80); + } + + int pipe = find_pipe(dc[3] & 0x03); + if(pipe < 0) return USB_OPEN_TOO_MANY_ENDPOINTS; + + endpoint_t *ep = usb_configure_endpoint(address); + ep->intf = intf; + ep->dc = (void *)dc; + ep->pipe = pipe; + ep->bufnmb = 0; + ep->bufsize = 0; + + /* Fixed areas */ + if(pipe >= 6) + { + ep->bufnmb = (pipe - 2); + ep->bufsize = 1; + } + } + + for(int k = 0; intf->params[k].endpoint; k++) + { + usb_interface_endpoint_t *params = &intf->params[k]; + + int a = usb_configure_address(intf, params->endpoint); + endpoint_t *ep = usb_configure_endpoint(a); + if(!ep) return USB_OPEN_INVALID_PARAMS; + + uint bufsize = params->buffer_size >> 6; + if(params->buffer_size & 63 + || bufsize <= 0 + || bufsize > 0x20 + || (ep->pipe >= 6 && bufsize != 1)) + return USB_OPEN_INVALID_PARAMS; + if(ep->pipe >= 6) continue; + + if(next_bufnmb + bufsize > 0x100) + return USB_OPEN_NOT_ENOUGH_MEMORY; + + ep->bufnmb = next_bufnmb; + ep->bufsize = bufsize; + next_bufnmb += bufsize; + } + } + + /* Check that all endpoints have memory assigned */ + for(int i = 0; i < 32; i++) + { + if(!conf_ep[i].intf) continue; + if(conf_ep[i].bufsize == 0) + return USB_OPEN_MISSING_DATA; + } + + return 0; +} + +/* usb_configure_log(): Print configuration results in the usb_log() */ +void usb_configure_log(void) +{ + /* Log the final configuration */ + for(int i = 0; i < 16 && conf_if[i]; i++) + usb_log("Interface #%d: %p\n", i, conf_if[i]); + + for(int i = 0; i < 32; i++) + { + if(!conf_ep[i].intf) continue; + + endpoint_t *ep = &conf_ep[i]; + usb_log("Endpoint %02x\n", + (i & 15) + (i >= 16 ? 0x80 : 0)); + usb_log(" Interface %p address %02x\n", + ep->intf, ep->dc->bEndpointAddress); + usb_log(" Pipe %d (FIFO: %02x..%02x)\n", + ep->pipe, ep->bufnmb, ep->bufnmb + ep->bufsize); + } +} + +/* usb_configure(): Load the generated configuration to the USB module */ +void usb_configure(void) +{ + for(int i = 0; i < 32; i++) + { + if(!conf_ep[i].intf) continue; + int address = (i & 0xf) + (i >= 16 ? 0x80 : 0); + usb_pipe_configure(address, &conf_ep[i]); + usb_pipe_clear(conf_ep[i].pipe); + } +} + +/* usb_configure_interfaces(): List configures interfaces */ +usb_interface_t const * const *usb_configure_interfaces(void) +{ + return conf_if; +} diff --git a/src/usb/inth.s b/src/usb/inth.s new file mode 100644 index 0000000..aaa2713 --- /dev/null +++ b/src/usb/inth.s @@ -0,0 +1,29 @@ +/* +** gint:usb:inth - Interrupt handler for the USB function module +*/ + +.global _inth_usb + +.section .gint.blocks, "ax" +.align 4 + +/* USB INTERRUPT HANDLER */ + +_inth_usb: + /* Call back into driver code, there is way too much work that needs to + be done to fit in here */ + sts.l pr, @-r15 + mov.l 1f, r0 + mov.l @r0, r0 + mov.l 2f, r4 + jsr @r0 + nop + + lds.l @r15+, pr + rts + nop + +.zero 6 + +1: .long _gint_inth_callback +2: .long _usb_interrupt_handler diff --git a/src/usb/pipes.c b/src/usb/pipes.c new file mode 100644 index 0000000..44003b0 --- /dev/null +++ b/src/usb/pipes.c @@ -0,0 +1,292 @@ +#include +#include +#include +#include +#include +#include "usb_private.h" + +#define USB SH7305_USB + +//--- +// Operations on pipe controllers +//--- + +/* pipect_t: Pipe controllers used to access pipe configuration and contents */ +typedef enum { + CF, /* Used for the Default Control Pipe */ + D0F, /* Used for main-thread access to other pipes */ + D1F, /* Used for interrupt-thread access to other pipes */ +} pipect_t; + +/* pipect(): Determine which controller to use to access a pipe */ +pipect_t pipect(int pipe) +{ + if(pipe == 0) return CF; + + /* In normal program flow, use D0FIF0; in interrupt mode, use D1FIF0. + This avoids access races */ + uint32_t sr, IMASK; + __asm__("stc sr, %0": "=r"(sr)); + IMASK = (sr >> 4) & 0xf; + + return (IMASK ? D1F : D0F); +} + +/* usb_pipe_configure(): Configure a pipe when opening the connection */ +void usb_pipe_configure(int address, endpoint_t const *ep) +{ + /* Maps USB 2.0 transfer types to SH7305 USB module transfer types */ + static uint8_t type_map[4] = { 0, 3, 1, 2 }; + USB.PIPESEL.PIPESEL = ep->pipe; + + USB.PIPECFG.TYPE = type_map[ep->dc->bmAttributes & 0x03]; + USB.PIPECFG.BFRE = 0; + USB.PIPECFG.DBLB = 0; + USB.PIPECFG.CNTMD = 1; + USB.PIPECFG.SHTNAK = 0; + USB.PIPECFG.DIR = (address & 0x80) != 0; + USB.PIPECFG.EPNUM = (address & 0x0f); + + USB.PIPEBUF.BUFSIZE = ep->bufsize - 1; + USB.PIPEBUF.BUFNMB = ep->bufnmb; + + USB.PIPEMAXP.MXPS = le16toh(ep->dc->wMaxPacketSize); +} + +/* usb_pipe_clear(): Clear all data in the pipe */ +void usb_pipe_clear(int pipe) +{ + if(pipe <= 0 || pipe > 9) return; + + /* Set PID=NAK then use ACLRM to clear the pipe */ + USB.PIPECTR[pipe-1].PID = 0; + while(USB.PIPECTR[pipe-1].PBUSY) {} + + USB.PIPECTR[pipe-1].ACLRM = 1; + USB.PIPECTR[pipe-1].ACLRM = 0; + while(!USB.PIPECTR[pipe-1].BSTS) {} + + USB.PIPECTR[pipe-1].PID = 0; + USB.PIPECTR[pipe-1].SQCLR = 1; +} + +/* pipe_mode(): Set the pipe in reading or writing mode */ +static void pipe_mode(pipect_t ct, int pipe, int mode, int size) +{ + size = (size - (size == 4) - 1) & 3; + + if(ct == CF) + { + if(mode == 1) USB.DCPCTR.PID = 1; + /* RCNT=0 REW=0 MBW=size BIGEND=1 ISEL=mode CURPIPE=0 */ + USB.CFIFOSEL.word = 0x0100 | (mode << 5) | (size << 10); + usb_while(!USB.CFIFOCTR.FRDY || USB.CFIFOSEL.ISEL != mode); + return; + } + + /* Set PID to NAK to clear the toggle bit, then BUF */ + USB.PIPECTR[pipe-1].PID = 0; + USB.PIPECTR[pipe-1].SQCLR = 1; + USB.PIPECTR[pipe-1].PID = 1; + + /* RCNT=0 REW=0 DCLRM=0 DREQE=0 MBW=size BIGEND=1 */ + if(ct == D0F) USB.D0FIFOSEL.word = 0x0100 | (size << 10) | pipe; + if(ct == D1F) USB.D1FIFOSEL.word = 0x0100 | (size << 10) | pipe; + + if(ct == D0F) usb_while(!USB.D0FIFOCTR.FRDY || USB.PIPECFG.DIR!=mode); + if(ct == D1F) usb_while(!USB.D1FIFOCTR.FRDY || USB.PIPECFG.DIR!=mode); +} +void usb_pipe_mode_read(int pipe, int read_size) +{ + return pipe_mode(pipect(pipe), pipe, 0, read_size); +} +void usb_pipe_mode_write(int pipe, int write_size) +{ + return pipe_mode(pipect(pipe), pipe, 1, write_size); +} + +//--- +// Writing operations +//--- + +/* Current operation waiting to be performed on each pipe */ +struct transfer { + /* Address of data to transfer next; NULL if no transfer */ + void const *data; + /* Size of data left to transfer */ + int size; + /* Size of data currently in the FIFO (less than the FIFO capacity) */ + int used; + /* Write size */ + uint8_t unit_size; + /* Whether the data has been committed to a transfer */ + bool committed; + /* Whether to use the DMA */ + bool dma; + /* Callback at the end of the transfer */ + gint_callback_t callback; +}; +/* Operations to be continued whenever buffers get empty */ +GBSS static struct transfer volatile pipe_transfers[10]; + +void usb_pipe_init_transfers(void) +{ + memset((void *)pipe_transfers, 0, sizeof pipe_transfers); +} + +static void write_8(uint8_t const *data, int size, uint8_t volatile *FIFO) +{ + for(int i = 0; i < size; i++) *FIFO = data[i]; +} +static void write_16(uint16_t const *data, int size, uint16_t volatile *FIFO) +{ + for(int i = 0; i < size; i++) *FIFO = data[i]; +} +static void write_32(uint32_t const *data, int size, uint32_t volatile *FIFO) +{ + for(int i = 0; i < size; i++) *FIFO = data[i]; +} + +/* Commit the pipe if there is no data left and the commit flag is set */ +static void maybe_commit(struct transfer volatile *t, int pipe) +{ + /* The DCP is always committed immediately and with CCPL */ + if(pipe == 0) return; + pipect_t ct = pipect(pipe); + + /* The buffer is committed automatically if full because continuous + mode is enabled. Manually commit if no data is left. */ + if(t->committed && !t->data) + { + if(ct == D0F) USB.D0FIFOCTR.BVAL = 1; + if(ct == D1F) USB.D1FIFOCTR.BVAL = 1; + + t->committed = false; + usb_log("[PIPE%d] Committed transfer\n", pipe); + } +} + +/* write_round(): Write up to a FIFO's worth of data to a pipe + + Returns true if this last write will empty the queue, false if further + writes are required. When writing with the DMA, returning true does not + imply that the pipe can be accessed. */ +static bool write_round(struct transfer volatile *t, int pipe) +{ + pipect_t ct = pipect(pipe); + void volatile *FIFO = NULL; + + if(ct == CF) FIFO = &USB.CFIFO; + if(ct == D0F) FIFO = &USB.D0FIFO; + if(ct == D1F) FIFO = &USB.D1FIFO; + + if(pipe) pipe_mode(ct, pipe, 1, t->unit_size); + + /* Amount of data that can be transferred in a single run */ + int bufsize=64, available=64; + if(pipe != 0) + { + USB.PIPESEL.PIPESEL = pipe; + bufsize = (USB.PIPEBUF.BUFSIZE + 1) * 64; + available = bufsize - t->used; + } + int size = min(t->size, available); + + if(t->dma) + { + /* TODO: DMA support in usb_pipe_write(), write_round() */ + /* After the DMA starts the code below will update pointers for + the next iteration */ + // dma_start(X, Y, Z, + // GINT_CB(maybe_commit, (void *)t, pipe)); + } + else + { + if(t->unit_size == 1) write_8(t->data, size, FIFO); + if(t->unit_size == 2) write_16(t->data, size >> 1, FIFO); + if(t->unit_size == 4) write_32(t->data, size >> 2, FIFO); + } + + t->used += size; + t->data += size; + t->size -= size; + if(t->used == bufsize || t->committed) t->used = 0; + if(t->size == 0) t->data = NULL; + + /* After a CPU write, commit if needed */ + if(!t->dma) maybe_commit(t, pipe); + return (t->data == NULL); +} + +/* usb_write_async(): Asynchronously write to a USB pipe */ +int usb_write_async(int pipe, void const *data, int size, int unit_size, + bool use_dma, gint_callback_t callback) +{ + struct transfer volatile *t = &pipe_transfers[pipe]; + + /* Do not initiate a write if a previous write is unfinished or an + ongoing transfer is awaiting completion */ + if(t->data || (pipe && !USB.PIPECTR[pipe-1].BSTS)) + return USB_WRITE_BUSY; + + if(!data || !size) return 0; + + t->data = data; + t->size = size; + t->unit_size = unit_size; + t->dma = use_dma; + t->committed = false; + t->callback = callback; + + // TODO: Support callback in usb_write_async() + + write_round(t, pipe); + + /* Set up the Buffer Empty interrupt to refill the buffer when it gets + empty, and be notified when the transfer completes. */ + if(pipe) USB.BEMPENB.word |= (1 << pipe); + return 0; +} + +/* usb_write_sync(): Synchronously write to a USB pipe */ +int usb_write_sync(int pipe, void const *data, int size, int unit_size, + bool use_dma) +{ + struct transfer volatile *t = &pipe_transfers[pipe]; + + /* Wait for a previous write and/or transfer to finish */ + while(t->data || (pipe && !USB.PIPECTR[pipe-1].BSTS)) sleep(); + + usb_write_async(pipe, data, size, unit_size, use_dma, GINT_CB_NULL); + + /* Wait for the write to finish (but not the transfer) */ + while(t->data) sleep(); + return 0; +} + +void usb_commit_async(int pipe, gint_callback_t callback) +{ + struct transfer volatile *t = &pipe_transfers[pipe]; + t->committed = true; + t->callback = callback; + + /* Commit the pipe if writes have been completed already */ + maybe_commit(t, pipe); +} + +/* usb_pipe_write_bemp(): Callback for the BEMP interrupt on a pipe */ +void usb_pipe_write_bemp(int pipe) +{ + /* Eliminate interrupts that occur when the pipe is set up but no + transfer is occurring */ + struct transfer volatile *t = &pipe_transfers[pipe]; + if(!t->data) return; + + bool complete = write_round(t, pipe); + if(!complete) return; + + USB.BEMPENB.word &= ~(1 << pipe); + + if(t->callback.function) + gint_callback_invoke(t->callback); +} diff --git a/src/usb/setup.c b/src/usb/setup.c new file mode 100644 index 0000000..6aadd55 --- /dev/null +++ b/src/usb/setup.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include "usb_private.h" +#include + +#define USB SH7305_USB + +/* Write a response to the DCP, and automatically set write mode */ +static void dcp_write(void const *data, size_t size) +{ + /* Automatically enable write mode (heuristic condition) */ + /* TODO: dcp_write(): Try to set the mode in usb_pipe_write() */ + if(USB.CFIFOSEL.ISEL != 1 || USB.DCPCTR.PID != 1) + usb_pipe_mode_write(0, 1); + + usb_write_sync(0, data, size, 1, false); +} + +//--- +// SETUP requests +//--- + +#ifdef FX9860G +#define ID_PRODUCT 0x6101 /* fx-9860G II, Protocol 7.00, etc. */ +#else +#define ID_PRODUCT 0x6102 /* fx-CP 400, fx-CG 50, Mass Storage, etc. */ +#endif + +static usb_dc_device_t dc_device = { + .bLength = sizeof(usb_dc_device_t), + .bDescriptorType = USB_DC_DEVICE, + .bcdUSB = htole16(0x0200), /* USB 2.00 */ + .bDeviceClass = 0, /* Configuration-specific */ + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = htole16(0x07cf), /* Casio Computer Co., Ltd. */ + .idProduct = htole16(ID_PRODUCT), + .bcdDevice = htole16(0x0100), + .iManufacturer = 0, + .iProduct = 0, + .iSerialNumber = 0, + .bNumConfigurations = 1, +}; +static usb_dc_configuration_t dc_configuration = { + .bLength = sizeof(usb_dc_configuration_t), + .bDescriptorType = USB_DC_CONFIGURATION, + .wTotalLength = 0, + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0xc0, + .bMaxPower = 50, +}; +static usb_dc_string_t dc_string0 = { + .bLength = 4, + .bDescriptorType = USB_DC_STRING, + .data = { htole16(0x0409) }, /* English (US) */ +}; + +GCONSTRUCTOR static void set_strings(void) +{ + #ifdef FX9860G + char const *serial_base = (void *)0x8000ffd0; + uint16_t const *product = u"CASIO fx-9860G family on gint"; + #endif + + #ifdef FXCG50 + char const *serial_base = (void *)0x8001ffd0; + uint16_t const *product = u"CASIO fx-CG 50 family on gint"; + #endif + + /* Convert the serial number to UTF-16 */ + uint16_t serial[8]; + for(int i = 0; i < 8; i++) serial[i] = serial_base[i]; + + dc_device.iManufacturer = usb_dc_string(u"CASIO Computer Co., Ltd", 0); + dc_device.iProduct = usb_dc_string(product, 0); + dc_device.iSerialNumber = usb_dc_string(serial, 8); +} + +//--- +// Configuration descriptor generation +//--- + +static void write_configuration_descriptor(int wLength) +{ + usb_interface_t const * const *interfaces = usb_configure_interfaces(); + size_t total_length = sizeof(usb_dc_configuration_t); + + for(int i = 0; interfaces[i]; i++) + for(int k = 0; interfaces[i]->dc[k]; k++) + { + uint8_t const *dc = interfaces[i]->dc[k]; + total_length += dc[0]; + } + usb_log("Configuration descriptor size: %d\n", (int)total_length); + + /* Write the configuration descriptor */ + dc_configuration.wTotalLength = htole16(total_length); + dcp_write(&dc_configuration, dc_configuration.bLength); + /* For the first call, the host usually wants only this */ + if(wLength <= dc_configuration.bLength) return; + + /* Write all the other descriptors */ + for(int i = 0; interfaces[i]; i++) + for(int k = 0; interfaces[i]->dc[k]; k++) + { + uint8_t const *dc = interfaces[i]->dc[k]; + + /* Edit interface numbers on-the-fly */ + if(dc[1] == USB_DC_INTERFACE) + { + usb_dc_interface_t idc = *(usb_dc_interface_t *)dc; + idc.bInterfaceNumber = k; + dcp_write(&idc, idc.bLength); + } + /* Edit endpoint numbers on-the-fly */ + else if(dc[1] == USB_DC_ENDPOINT) + { + usb_dc_endpoint_t edc = *(usb_dc_endpoint_t *)dc; + edc.bEndpointAddress = + usb_configure_address(interfaces[i], dc[2]); + dcp_write(&edc, edc.bLength); + } + /* Forward other descriptors */ + else dcp_write(dc, dc[0]); + } +} + +static void req_get_descriptor(int wValue, int wLength) +{ + int type = (wValue >> 8) & 0xff; + int num = (wValue & 0xff); + + static char const *strs[] = { + "DEV","CONFIG","STR","INTF","ENDP","DEVQ","OSC","POWER" }; + usb_log("GET_DESCRIPTOR: %s #%d len:%d\n", strs[type-1], num, wLength); + + if(type == USB_DC_DEVICE && num == 0) + dcp_write(&dc_device, dc_device.bLength); + + else if(type == USB_DC_CONFIGURATION && num == 0) + write_configuration_descriptor(wLength); + + else if(type == USB_DC_STRING && num == 0) + dcp_write(&dc_string0, dc_string0.bLength); + + else if(type == USB_DC_STRING) + { + usb_dc_string_t *dc = usb_dc_string_get(num); + if(dc) dcp_write(dc, dc->bLength); + else USB.DCPCTR.PID = 2; + } +} + +static void req_get_configuration(void) +{ + usb_log("GET_CONFIGURATION -> %d\n", 1); + dcp_write("\x01", 1); +} + +static void req_set_configuration(int wValue) +{ + usb_log("SET_CONFIGURATION: %d\n", wValue); + /* Ok for (wValue == 1) only */ + USB.DCPCTR.PID = (wValue == 1) ? 1 : 2; +} + +void usb_req_setup(void) +{ + /* Respond to setup requests */ + int bRequest = USB.USBREQ.BREQUEST; + int bmRequestType = USB.USBREQ.BMREQUEST; + int wValue = USB.USBVAL.word; + int wIndex = USB.USBINDX.word; + int wLength = USB.USBLENG.word; + + do USB.INTSTS0.VALID = 0; + while(USB.INTSTS0.VALID); + + /* Standard requests */ + + if(bmRequestType == 0x80 && bRequest == GET_DESCRIPTOR) + req_get_descriptor(wValue, wLength); + + else if(bmRequestType == 0x80 && bRequest == GET_CONFIGURATION) + req_get_configuration(); + + else if(bmRequestType == 0x00 && bRequest == SET_CONFIGURATION) + req_set_configuration(wValue); + + /* TODO: Other standard SETUP requests */ + else usb_log("SETUP: bRequest=%02x bmRequestType=%02x wValue=%04x\n" + " wIndex=%04x wLength=%d -> ???\n", + bRequest, bmRequestType, wValue, wIndex, wLength); + + /* Push the buffer when responding to an IN request with a BUF */ + if((bmRequestType & 0x80) && USB.DCPCTR.PID == 1) + USB.CFIFOCTR.BVAL = 1; + + /* Finalize request */ + USB.DCPCTR.CCPL = 1; +} diff --git a/src/usb/string.c b/src/usb/string.c new file mode 100644 index 0000000..83d0940 --- /dev/null +++ b/src/usb/string.c @@ -0,0 +1,59 @@ +//--- +// gint:usb:string - STRING descriptor management +//--- + +#include +#include +#include + +/* String descriptor array */ +static usb_dc_string_t **array = NULL; +static int array_size = 0; + +uint16_t usb_dc_string(uint16_t const *literal, size_t len) +{ + if(array_size == 255) return 0; + + /* Determine the length of the string */ + if(len == 0) + { + while(literal[len]) len++; + } + if(2*len + 2 >= 256) return 0; + + /* Allocate a new descriptor */ + usb_dc_string_t *dc = malloc(sizeof *dc + 2*len); + if(!dc) return 0; + + dc->bLength = 2*len + 2; + dc->bDescriptorType = USB_DC_STRING; + for(size_t i = 0; i < len; i++) dc->data[i] = htole16(literal[i]); + + /* Try to make room in the array; if realloc() fails, try to allocate + again in another arena */ + size_t new_size = (array_size + 1) * sizeof(*array); + usb_dc_string_t **new_array = realloc(array, new_size); + + if(!new_array) + { + new_array = malloc(new_size); + if(!new_array) + { + free(dc); + return 0; + } + memcpy(new_array, array, array_size * sizeof(*array)); + free(array); + } + + array = new_array; + array[array_size++] = dc; + /* IDs are numbered 1 to 255 */ + return (array_size - 1) + 1; +} + +usb_dc_string_t *usb_dc_string_get(uint16_t id) +{ + if((int)id < 1 || id - 1 >= array_size) return NULL; + return array[id - 1]; +} diff --git a/src/usb/usb.c b/src/usb/usb.c new file mode 100644 index 0000000..46a5157 --- /dev/null +++ b/src/usb/usb.c @@ -0,0 +1,380 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "usb_private.h" + +#define USB SH7305_USB + +/* Interrupt handler */ +extern void inth_usb(void); + +/* Shorthand to clear a bit in INTSTS0 */ +#define INTSTS0_clear(field_name) { \ + __typeof__(USB.INTSTS0) __intsts0 = { .word = 0xffff }; \ + __intsts0.field_name = 0; \ + do USB.INTSTS0 = __intsts0; \ + while(USB.INTSTS0.field_name != 0); \ +} + +/* Callback function to invoke when the USB module is configured */ +/* TODO: usb_open() callback: Let interfaces specify when they're ready! */ +static gint_callback_t usb_open_callback = GINT_CB_NULL; +/* Whether the USB link is currently open */ +static bool volatile usb_open_status = false; + +//--- +// Logging system +//--- + +static void (*usb_logger)(char const *format, va_list args) = NULL; + +void usb_set_log(void (*logger)(char const *format, va_list args)) +{ + usb_logger = logger; +} + +void usb_log(char const *format, ...) +{ + if(!usb_logger) return; + + va_list args; + va_start(args, format); + usb_logger(format, args); + va_end(args); +} + +//--- +// Module powering and depowering +//--- + +static bool usb_module_active(void) +{ + return (SH7305_CPG.USBCLKCR.CLKSTP == 0) && + (SH7305_POWER.MSTPCR2.USB0 == 0); +} + +/* Start the clock of the USB module, without enabling operations or notifying + the host. This procedure does just enough to allow reading registers, and + writing to SYSCFG and BUSWAIT. If (enable_write == true), it also turns on + SCKE so that registers can be written to. */ +static void usb_module_start(bool enable_write) +{ + if(!usb_module_active()) + { + /* TODO: USB: Proper handling of MSELCRA and MSELCRB */ + uint16_t volatile *MSELCRA = (void *)0xa4050180; + uint16_t volatile *MSELCRB = (void *)0xa4050182; + *MSELCRA &= 0xff3f; + *MSELCRB &= 0x3fff; + + /* Leave some delay for the clock to settle (like OS). I have + observed that after a reset, 20 ms are *NOT ENOUGH* for the + clock to start properly. 35 ms seemed fine. (?) */ + SH7305_CPG.USBCLKCR.CLKSTP = 0; + sleep_us_spin(50000); + + SH7305_POWER.MSTPCR2.USB0 = 0; + SH7305_USB_UPONCR.word = 0x0600; + } + + if(!enable_write) return; + + /* Turn on SCKE, which activates all other registers. Because the clock + for the USB module is 48 MHz and the processor runs at a higher + frequency, wait for a little bit before modifying registers. Writing + within 3-4 CPU cycles is 100% unsafe and has been observed to cause + freezes during testing. */ + USB.SYSCFG.SCKE = 1; + for(int i = 0; i < 10; i++) __asm__ volatile("nop"); + + /* Set BUSWAIT to a safe value, hopefully avoiding overlock problems */ + USB.BUSWAIT.word = 15; +} + +/* Stop the clock of the USB module. */ +static void usb_module_stop(bool restore_mselcr) +{ + uint16_t volatile *MSELCRA = (void *)0xa4050180; + uint16_t volatile *MSELCRB = (void *)0xa4050182; + + SH7305_USB_UPONCR.word = 0x0000; + + /* This delay is crucial and omitting it has caused constant freezes in + the past. Blame undocumented clock magic? */ + sleep_us_spin(1000); + SH7305_POWER.MSTPCR2.USB0 = 1; + + SH7305_CPG.USBCLKCR.CLKSTP = 1; + sleep_us_spin(50000); + + if(!restore_mselcr) return; + + /* The values used by the OS (a PFC driver could do better) */ + *MSELCRB = (*MSELCRB & 0x3fff) | 0xc000; + *MSELCRA = (*MSELCRA & 0xff3f) | 0x0040; +} + +int usb_open(usb_interface_t const **interfaces, gint_callback_t callback) +{ + usb_log("---- usb_open ----\n"); + + int rc = usb_configure_solve(interfaces); + usb_configure_log(); + + if(rc != 0) + { + usb_log("configure failure: %d\n", rc); + return rc; + } + + usb_open_callback = callback; + usb_module_start(true); + + *(uint16_t volatile *)0xa4d800c2 = 0x0020; + + /* Turn on the module now that SCKE=1 */ + USB.SYSCFG.USBE = 1; + USB.SYSCFG.HSE = 1; + + /* Disable pin pull-down and pull-up in order to change DCFM */ + USB.SYSCFG.DRPD = 0; + USB.SYSCFG.DPRPU = 0; + /* Select function controller */ + USB.SYSCFG.DCFM = 0; + + /* Pull D+ up to 3.3V, notifying connection when possible. Read + SYSSTS.LNST as required after modifying DPRPU */ + USB.SYSCFG.DPRPU = 1; + GUNUSED volatile int LNST = USB.SYSSTS.LNST; + + /* Prepare the default control pipe. */ + USB.DCPCFG.DIR = 0; + USB.DCPMAXP.DEVSEL = 0; + USB.DCPMAXP.MXPS = 64; + + USB.SOFCFG.enable = 1; + + /* Configure CFIFOSEL to use the DCP */ + USB.CFIFOSEL.RCNT = 0; + USB.CFIFOSEL.REW = 0; + USB.CFIFOSEL.BIGEND = 1; + /* Clear D0FIFOSEL and D1FIFOSEL so that pipes can be configured */ + USB.D0FIFOSEL.word = 0x0000; + USB.D1FIFOSEL.word = 0x0000; + + /* Configure other pipes to use activated interfaces */ + usb_configure(); + /* Initialize transfer tracker for multi-part transfers */ + usb_pipe_init_transfers(); + + /* VBSE=1 RSME=0 SOFE=0 DVSE=1 CTRE=1 BEMPE=1 NRDYE=0 BRDYE=0 */ + USB.INTENB0.word = 0x9c00; + USB.BRDYENB.word = 0x0000; + USB.NRDYENB.word = 0x0000; + USB.BEMPENB.word = 0x0000; + + gint_inthandler(0xa20, inth_usb, 32); + intc_priority(INTC_USB, 15); + + usb_open_status = true; + return 0; +} + +void usb_open_wait(void) +{ + while(!usb_open_status) sleep(); +} + +void usb_close(void) +{ + intc_priority(INTC_USB, 0); + usb_module_stop(false); + usb_log("---- usb_close ----\n"); + + usb_open_callback = GINT_CB_NULL; + usb_open_status = false; +} + +//--- +// Userspace interrupt handler +//--- + +void usb_interrupt_handler(void) +{ + static char const * const device_st[] = { + "powered", "default", "address", "configured", + "suspended-powered", "suspended-default", "suspended-address", + "suspended-configured", + }; + /* Save PIPESEL to avoid concurrent access issues */ + uint16_t pipesel = USB.PIPESEL.word; + /* Change D0FIFOSEL to make sure that the same pipe can't be specified + in D0FIFOSEL and D1FIFOSEL simultaneously */ + uint16_t d0fifosel = USB.D0FIFOSEL.word; + USB.D0FIFOSEL.word = 0x0000; + + if(USB.INTSTS0.VBINT) + { + INTSTS0_clear(VBINT); + usb_log("VBUS %s\n", USB.INTSTS0.VBSTS ? "up" : "down"); + } + else if(USB.INTSTS0.CTRT) + { + INTSTS0_clear(CTRT); + if(USB.INTSTS0.VALID) usb_req_setup(); + } + else if(USB.INTSTS0.DVST) + { + INTSTS0_clear(DVST); + usb_log("DVST %s", device_st[USB.INTSTS0.DVSQ]); + if(USB.INTSTS0.DVSQ == 2) usb_log(": %04x\n",USB.USBADDR.word); + else usb_log("\n"); + + /* When configured, run the callback for usb_open() */ + if(USB.INTSTS0.DVSQ == 3) + { + usb_open_status = true; + if(usb_open_callback.function) + { + gint_callback_invoke(usb_open_callback); + usb_open_callback = GINT_CB_NULL; + } + } + } + else if(USB.INTSTS0.BEMP) + { + /* Invoke callbacks for each buffer-empty interrupt */ + uint16_t status = USB.BEMPSTS.word; + USB.BEMPSTS.word = 0; + + for(int i = 1; i <= 9; i++) + { + if(status & (1 << i)) usb_pipe_write_bemp(i); + } + } + else usb_log("<%04X> -> ???\n", USB.INTSTS0.word); + + /* Disable D1FIFO so that concurrent access does not occur */ + USB.D1FIFOSEL.word = 0x0000; + USB.D0FIFOSEL.word = d0fifosel; + /* Restore PIPESEL which can have been used for transfers */ + USB.PIPESEL.word = pipesel; +} + +//--- +// Hardware context +//--- + +typedef struct +{ + /* Control and power-up. We don't try to save power-related registers + from other modules nor UPONCR, because (1) they have to be changed + to access the module, so the OS cannot realy on their value being + preserved, and (2) numerous timing and order issues mean in practice + that changing them less makes program more stable. */ + uint16_t SYSCFG, DVSTCTR, TESTMODE, REG_C2; + /* FIFO configuration */ + uint16_t CFIFOSEL, D0FIFOSEL, D1FIFOSEL; + /* Interrupt configuration */ + uint16_t INTENB0, BRDYENB, NRDYENB, BEMPENB, SOFCFG; + /* Default Control Pipe (maybe not needed) */ + uint16_t DCPCFG, DCPMAXP, DCPCTR; + /* Whether the module is running at this time */ + bool active; +} ctx_t; + +GBSS static ctx_t sys_ctx, gint_ctx; + +static void ctx_save(void *buf) +{ + ctx_t *ctx = buf; + + /* We only save the OS context once to avoid unnecessary quick power + cycles (which are the most unstable things in this driver) */ + static bool has_saved_sys = false; + if(buf == &sys_ctx && has_saved_sys) return; + if(buf == &sys_ctx) has_saved_sys = true; + + ctx->active = usb_module_active() || (ctx == &gint_ctx); + + /* Power ON the USB module clock so that the registers can be saved. + We don't enable writing since we only want to observe registers */ + usb_module_start(false); + + ctx->SYSCFG = USB.SYSCFG.word; + ctx->DVSTCTR = USB.DVSTCTR.word; + ctx->TESTMODE = USB.TESTMODE.word; + ctx->REG_C2 = USB.REG_C2; + + ctx->CFIFOSEL = USB.CFIFOSEL.word; + ctx->D0FIFOSEL = USB.D0FIFOSEL.word; + ctx->D1FIFOSEL = USB.D1FIFOSEL.word; + + ctx->INTENB0 = USB.INTENB0.word; + ctx->BRDYENB = USB.BRDYENB.word; + ctx->NRDYENB = USB.NRDYENB.word; + ctx->BEMPENB = USB.BEMPENB.word; + ctx->SOFCFG = USB.SOFCFG.word; + + ctx->DCPCFG = USB.DCPCFG.word; + ctx->DCPMAXP = USB.DCPMAXP.word; + ctx->DCPCTR = USB.DCPCTR.word; + + /* Leave the module open for gint to use it, or for the next restore + to proceed more quickly (if during a world switch) */ +} + +static void ctx_restore(void *buf) +{ + ctx_t *ctx = buf; + + usb_module_start(true); + + USB.DVSTCTR.word = ctx->DVSTCTR; + USB.TESTMODE.word = ctx->TESTMODE; + USB.REG_C2 = ctx->REG_C2; + + USB.CFIFOSEL.word = ctx->CFIFOSEL; + USB.D0FIFOSEL.word = ctx->D0FIFOSEL; + USB.D1FIFOSEL.word = ctx->D1FIFOSEL; + + USB.INTENB0.word = ctx->INTENB0; + USB.BRDYENB.word = ctx->BRDYENB; + USB.NRDYENB.word = ctx->NRDYENB; + USB.BEMPENB.word = ctx->BEMPENB; + USB.SOFCFG.word = ctx->SOFCFG; + + USB.DCPCFG.word = ctx->DCPCFG; + USB.DCPMAXP.word = ctx->DCPMAXP; + USB.DCPCTR.word = ctx->DCPCTR; + + /* Clear remaining interrupts. Read-only bits will be ignored */ + USB.INTSTS0.word = 0; + USB.BRDYSTS.word = 0; + USB.NRDYSTS.word = 0; + USB.BEMPSTS.word = 0; + + /* Restore SYSCFG last as clearing SCKE disables writing */ + USB.SYSCFG.word = ctx->SYSCFG; + + if(!ctx->active) usb_module_stop(true); +} + +//--- +// Driver structure definition +//--- + +gint_driver_t drv_usb = { + .name = "USB", + .sys_ctx = &sys_ctx, + .gint_ctx = &gint_ctx, + .ctx_save = ctx_save, + .ctx_restore = ctx_restore, +}; + +GINT_DECLARE_DRIVER(3, drv_usb); diff --git a/src/usb/usb_private.h b/src/usb/usb_private.h new file mode 100644 index 0000000..4351e26 --- /dev/null +++ b/src/usb/usb_private.h @@ -0,0 +1,243 @@ +//--- +// gint:usb:usb-private - Private definitions for the USB driver +//--- + +#ifndef GINT_USB_USB_PRIVATE +#define GINT_USB_USB_PRIVATE + +#include +#include +#include + +//--- +// Configuration of the communication surface between module and host +//--- + +/* usb_configure_solve(): Find a configuration that can open these interfaces + + This function determines a way to share USB resources (endpoint numbers, + pipes, and FIFO memory) between the provided interfaces, in order to open + the connection with all of these interfaces enabled. + + This function must only be called when the USB connection is closed. It + returns 0 on success and one of the USB_* error codes otherwise. + + @interfaces NULL-terminated list of interfaces to open + Returns an USB_* error code. */ +int usb_configure_solve(usb_interface_t const **interfaces); + +/* usb_configure_log(): Print configuration results in the usb_log() + This function can be called even if usb_configure_solve() fails. */ +void usb_configure_log(void); + +/* usb_configure(): Load the generated configuration to the USB module + + This function configures the USB module's pipes and FIFO memory to prepare + handling requests to the interfaces activated in usb_configure_solve(). This + configuration step is un-done by either another configuration through a + successful usb_open(), or a context restore in the USB driver. */ +void usb_configure(void); + +/* Error codes for USB functions */ +enum { + /* There are no interfaces */ + USB_OPEN_NO_INTERFACE = 1, + /* There are more interfaces than supported (16) */ + USB_OPEN_TOO_MANY_INTERFACES, + /* There are not enough endpoint numbers for every interface, or there + are not enough pipes to set them up */ + USB_OPEN_TOO_MANY_ENDPOINTS, + /* There is not enough FIFO memory to use the requested buffer sizes */ + USB_OPEN_NOT_ENOUGH_MEMORY, + /* Information is missing, such as buffer size for some endpoints */ + USB_OPEN_MISSING_DATA, + /* Invalid parameters: bad endpoint numbers, bad buffer sizes... */ + USB_OPEN_INVALID_PARAMS, + + /* A write is already pending on this pipe */ + USB_WRITE_BUSY, +}; + +/* endpoint_t: Driver information for each open endpoint in the device + + There is one such structure for all 16 configurable endpoints, for each + direction (totaling 32). endpoint_get() is used to query the structure by + endpoint number (including the IN/OUT bit). */ +typedef struct { + /* Which interface this endpoint belongs to */ + usb_interface_t const *intf; + /* Associated endpoint descriptor */ + usb_dc_endpoint_t const *dc; + /* Associated pipe, must be a number from 1..9 */ + uint8_t pipe; + /* Allocated pipe buffer area; this is valid for pipes 1..5. The + bufsize here is in range 1..32, as opposed to the field in PIPEBUF + which is in range 0..31. */ + uint8_t bufnmb; + uint8_t bufsize; + +} endpoint_t; + +/* usb_configure_interfaces(): List configured interfaces */ +usb_interface_t const * const *usb_configure_interfaces(void); + +/* usb_configure_address(): Get the concrete endpoint address + + This function returns the endpoint address associated with the provided + interface-numbered endpoint. The value is defined if an interface-provided + endpoint descriptor with bEndpointAddress equal to either (address) or + (address ^ 0x80) has been processed. + + This function is used both to access endpoint data for an interface-provided + endpoint number, and to ensure that two interface-provided enpoint numbers + with same base and opposing directions are assigned the same concrete + endpoint number with its two opposing directions. + + @intf Interface that provided the address + @address Endpoint address (as numbered by the interaface) + -> Returns the assigned endpoint address, or -1 if unassigned. */ +int usb_configure_address(usb_interface_t const *intf, int address); + +/* usb_configure_endpoint(): Get endpoint data for a concrete address */ +endpoint_t *usb_configure_endpoint(int endpoint); + +//--- +// Pipe operations +//--- + +/* usb_pipe_configure(): Configure a pipe when opening the connection */ +void usb_pipe_configure(int address, endpoint_t const *ep); + +/* usb_pipe_clear(): Clear all data in the pipe */ +void usb_pipe_clear(int pipe); + +/* usb_pipe_mode_read(): Set a pipe in read mode */ +void usb_pipe_mode_read(int pipe, int read_size); + +/* usb_pipe_mode_write(): Set a pipe in write mode */ +void usb_pipe_mode_write(int pipe, int write_size); + +/* usb_write_sync(): Synchronously write to a USB pipe + + This functions writes (size) bytes of (data) into the specified pipe, by + units of (unit_size) bytes. The unit size must be 1, 2 or 4, and both (data) + and (size) must be multiples of the unit size. In general, you should try to + use the largest possible unit size, as it will be much faster. In a sequence + of writes that concludes with a commit, all the writes must use the same + unit size. + + If the data fits into the pipe, this function returns right away, and the + data is *not* transmitted. Otherwise, data is written until the pipe is + full, at which point it is automatically transmitted. After the transfer, + this function resumes writing, returning only once everything is written. + Even then the last bytes will still not have been transmitted, to allow for + other writes to follow. After the last write in a sequence, use + usb_commit_sync() or usb_commit_async() to transmit the last bytes. + + If (use_dma=true), the write is performed wita the DMA instead of the CPU, + which is generally faster. + + If the pipe is busy due to a previous asynchronous write, this function + waits for the previous write to finish before proceeding normally. + + @pipe Pipe to write into + @data Source data (unit_size-aligned) + @size Size of source (multiple of unit_size) + @unit_size FIFO access size (must be 1, 2, or 4) + @dma Whether to use the DMA to perform the write + -> Returns an error code (0 on success). */ +int usb_write_sync(int pipe, void const *data, int size, int unit_size, + bool use_dma); + +/* usb_write_async(): Asynchronously write to a USB pipe + + This function is similar to usb_write_sync(), but it only starts the writing + and returns immediately without ever waiting. The writing then occurs in the + background of the calling code, and the caller is notified through a + callback when it completes. Use GINT_CB() to create a callback or pass + GINT_CB_NULL. + + If the pipe is busy due to a previous asynchronous write, this function + returns USB_PIPE_BUSY. When called with (use_dma=true), it returns as soon + as the DMA starts, without even a guarantee that the first few bytes have + been written. + + There is no guarantee that the write is complete until the callback is + called, however calling again with data=NULL and size=0 can be used to + determine whether the write has finished, since it will return 0 if the pipe + is idle and USB_PIPE_BUSY otherwise. + + @pipe Pipe to write into + @data Source data (unit_size-aligned) + @size Size of source (multiple of unit_size) + @unit_size FIFO access size (must be 1, 2, or 4) + @dma Whether to use the DMA to perform the write + @callback Optional callback to invoke when the write completes + -> Returns an error code (0 on success). */ +int usb_write_async(int pipe, void const *data, int size, int unit_size, + bool use_dma, gint_callback_t callback); + +/* usb_commit_sync(): Synchronously commit a write + + This function waits for any pending write on the pipe to finish, then + transfers whatever data is left, and returns when the transfer completes. + + @pipe Pipe that has been used in previous usb_write_*() calls */ +void usb_commit_sync(int pipe); + +/* usb_commit_async(): Asynchronously commit a write + + This function commits the specified pipe, causing the pipe to transfer + written data as soon as all the writes complete. It returns immediately and + instead the specified callback is invoked when the transfer completes. */ +void usb_commit_async(int pipe, gint_callback_t callback); + +/* usb_pipe_write_bemp(): Callback for the BEMP interrupt on a pipe */ +void usb_pipe_write_bemp(int pipe); + +/* usb_pipe_init_transfers(): Initialize transfer information */ +void usb_pipe_init_transfers(void); + +//--- +// Timout waits +//--- + +/* usb_while(): A while loop with a timeout */ +#define usb_while(condition) ({ \ + volatile int __f = 0; \ + int __t = timer_setup(TIMER_ANY, 100000 /*µs*/, timer_timeout, &__f); \ + if(__t >= 0) timer_start(__t); \ + while((condition) && __f == 0) {} \ + if(__f) usb_log("%s: %d: (" #condition ") holds\n", \ + __FUNCTION__, __LINE__); \ + if(__t >= 0) timer_stop(__t); \ + __f != 0; \ +}) + +//--- +// SETUP requests +//--- + +/* Standard SETUP requests */ +enum { + GET_STATUS = 0, + CLEAR_FEATURE = 1, + SET_FEATURE = 3, + SET_ADDRESS = 5, + GET_DESCRIPTOR = 6, + SET_DESCRIPTOR = 7, + GET_CONFIGURATION = 8, + SET_CONFIGURATION = 9, + GET_INTERFACE = 10, + SET_INTERFACE = 11, + SYNCH_FRAME = 12, +}; + +/* usb_req_setup(): Answer a SETUP request from the userspace handler + + THis function handles a SETUP request from the host, detected with the VALID + bit in the INTSTS0 register. The inputs are the USBREQ, USBVAL, USBINDX and + USBLENG registers, along with the DCP FIFO. */ +void usb_req_setup(void); + +#endif /* GINT_USB_USB_PRIVATE */