usb: USB 2.0 function driver (WIP)

* Add the power management functions (mostly stable even under
  overclock; requires some testing, but no known issue)

* Add a dynamic configuration system where interfaces can declare
  descriptors with arbitrary endpoint numbers and additional
  parameters, and the driver allocates USB resources (endpoints, pipes
  and FIFO memory) between interfaces at startup. This allows
  implementations of different classes to be independent from each
  other.

* Add responses to common SETUP requests.

* Add pipe logic that allows programs to write data synchronously or
  asynchronously to pipes, in a single or several fragments, regardless
  of the buffer size (still WIP with a few details to polish and the
  API is not public yet).

* Add a WIP bulk IN interface that allows sending data to the host.
  This will eventually support the fxlink protocol.
This commit is contained in:
Lephe 2021-04-11 19:04:54 +02:00
parent 2891a85338
commit 1315c26099
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
17 changed files with 2246 additions and 1 deletions

View File

@ -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

View File

@ -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,
};
//---

View File

@ -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;

View File

@ -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 <https://bible.planet-casio.

440
include/gint/mpu/usb.h Normal file
View File

@ -0,0 +1,440 @@
//---
// gint:intc:usb - USB 2.0 Interface
//---
#ifndef GINT_MPU_USB
#define GINT_MPU_USB
#include <gint/defs/types.h>
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 */

28
include/gint/std/endian.h Normal file
View File

@ -0,0 +1,28 @@
//---
// gint:std:endian - Endianness conversion
//---
#ifndef GINT_STD_ENDIAN
#define GINT_STD_ENDIAN
#include <gint/defs/types.h>
#include <gint/defs/attributes.h>
/* 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 */

View File

@ -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 <gint/usb.h>
/* 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 */

235
include/gint/usb.h Normal file
View File

@ -0,0 +1,235 @@
//---
// gint:usb - USB communication
//---
#ifndef GINT_USB
#define GINT_USB
#include <gint/defs/types.h>
#include <gint/std/endian.h>
#include <gint/gint.h>
#include <stdarg.h>
//---
// 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 <gint/usb-*.h>. 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 */

View File

@ -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 */ },
};

44
src/usb/classes/ff-bulk.c Normal file
View File

@ -0,0 +1,44 @@
#include <gint/usb.h>
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);
}

211
src/usb/configure.c Normal file
View File

@ -0,0 +1,211 @@
#include <gint/usb.h>
#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;
}

29
src/usb/inth.s Normal file
View File

@ -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

292
src/usb/pipes.c Normal file
View File

@ -0,0 +1,292 @@
#include <gint/usb.h>
#include <gint/mpu/usb.h>
#include <gint/clock.h>
#include <gint/defs/util.h>
#include <gint/std/string.h>
#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);
}

205
src/usb/setup.c Normal file
View File

@ -0,0 +1,205 @@
#include <gint/usb.h>
#include <gint/mpu/usb.h>
#include <gint/std/endian.h>
#include "usb_private.h"
#include <stdarg.h>
#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;
}

59
src/usb/string.c Normal file
View File

@ -0,0 +1,59 @@
//---
// gint:usb:string - STRING descriptor management
//---
#include <gint/usb.h>
#include <gint/std/stdlib.h>
#include <gint/std/string.h>
/* 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];
}

380
src/usb/usb.c Normal file
View File

@ -0,0 +1,380 @@
#include <gint/usb.h>
#include <gint/mpu/usb.h>
#include <gint/mpu/power.h>
#include <gint/mpu/cpg.h>
#include <gint/drivers.h>
#include <gint/clock.h>
#include <gint/intc.h>
#include <gint/gint.h>
#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);

243
src/usb/usb_private.h Normal file
View File

@ -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 <gint/defs/attributes.h>
#include <gint/timer.h>
#include <gint/gint.h>
//---
// 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 */