#include #include #include #include #include #include #include #include #include #include #include "usb_private.h" #define USB SH7305_USB static void usb_interrupt_handler(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_call_t usb_open_callback = GINT_CALL_NULL; /* Whether the USB link is currently open */ static bool volatile usb_open_status = false; //--- // Debugging functions //--- static void (*usb_logger)(char const *format, va_list args) = NULL; static void (*usb_tracer)(char const *message) = 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); } void usb_set_trace(void (*tracer)(char const *message)) { usb_tracer = tracer; } void usb_trace(char const *message) { if(usb_tracer) { cpu_atomic_start(); usb_tracer(message); cpu_atomic_end(); } } //--- // Module powering and depowering //--- static bool hpowered(void) { return (SH7305_CPG.USBCLKCR.CLKSTP == 0) && (SH7305_POWER.MSTPCR2.USB0 == 0); } static void hpoweron(void) { if(hpowered()) return; SH7305_PFC.MSELCRA.UNKNOWN_USB = 0; SH7305_PFC.MSELCRB.XTAL_USB = 0; /* Leave some delay for the clock to settle. The OS leaves 100 ms, but it just never seems necessary. */ SH7305_CPG.USBCLKCR.CLKSTP = 0; sleep_us_spin(1000); SH7305_POWER.MSTPCR2.USB0 = 0; SH7305_USB_UPONCR.word = 0x0600; } /* Finish the poweron procedure by enabling writes in the registers */ static void hpoweron_write(void) { /* Turn on SCKE, which activates all other registers. The existing BUSWAIT delay might not be high enough, so wait a little bit before modifying registers; a couple CPU cycles is enough. */ USB.SYSCFG.SCKE = 1; for(int i = 0; i < 10; i++) __asm__ volatile("nop"); /* Set BUSWAIT to a safe value */ USB.BUSWAIT.word = 5; } static void hpoweroff(void) { 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(1000); /* The values used by the OS (a PFC driver could do better) */ SH7305_PFC.MSELCRB.XTAL_USB = 3; SH7305_PFC.MSELCRA.UNKNOWN_USB = 1; } int usb_open(usb_interface_t const **interfaces, gint_call_t callback) { /* TODO: Check whether the calculator can host devices (probably no) */ bool host = false; 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; if(!hpowered()) hpoweron(); hpoweron_write(); USB.REG_C2 = 0x0020; /* Disconnect (DPRPU=0) if we were previously connected as a function. Also drive down DRPR, since both are required for setting DCFM. */ USB.SYSCFG.DPRPU = 0; USB.SYSCFG.DRPD = 0; if(host) { /* Select the host controller */ USB.SYSCFG.DCFM = 1; /* Clear registers to prepare for host operation */ USB.SYSCFG.USBE = 0; /* Do not require high-speed operation; also accept full-speed */ USB.SYSCFG.HSE = 0; /* Pull DPRD and eliminate chattering */ USB.SYSCFG.DRPD = 1; GUNUSED volatile int LNST = USB.SYSSTS.LNST; /* Enable the module */ USB.SYSCFG.USBE = 1; } else { /* Select the function controller */ USB.SYSCFG.DCFM = 0; /* Clear registers to prepare for function operation */ USB.SYSCFG.USBE = 0; /* Use high-speed only */ USB.SYSCFG.HSE = 1; /* Enable the module */ USB.SYSCFG.USBE = 1; } /* 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(); usb_configure_clear_pipes(); /* 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 = 0x0000; USB.NRDYENB = 0x0000; USB.BEMPENB = 0x0000; intc_handler_function(0xa20, GINT_CALL(usb_interrupt_handler)); intc_priority(INTC_USB, 8); /* 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; return 0; } bool usb_is_open(void) { return usb_open_status; } void usb_open_wait(void) { while(!usb_open_status) sleep(); } void usb_close(void) { intc_priority(INTC_USB, 0); hpoweroff(); USB_LOG("---- usb_close ----\n"); usb_open_callback = GINT_CALL_NULL; usb_open_status = false; } //--- // Userspace interrupt handler //--- static void usb_interrupt_handler(void) { GUNUSED 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; 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_configure_clear_pipes(); usb_open_status = true; if(usb_open_callback.function) { gint_call(usb_open_callback); usb_open_callback = GINT_CALL_NULL; } } } else if(USB.INTSTS0.BEMP) { /* Invoke callbacks for each buffer-empty interrupt */ uint16_t status = USB.BEMPSTS; USB.BEMPSTS = 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); /* Restore PIPESEL which can have been used for transfers */ USB.PIPESEL.word = pipesel; } //--- // State and driver metadata //--- void hsave(usb_state_t *s) { s->SYSCFG = USB.SYSCFG.word; s->DVSTCTR = USB.DVSTCTR.word; s->TESTMODE = USB.TESTMODE.word; s->REG_C2 = USB.REG_C2; s->CFIFOSEL = USB.CFIFOSEL.word; s->D0FIFOSEL = USB.D0FIFOSEL.word; s->D1FIFOSEL = USB.D1FIFOSEL.word; s->INTENB0 = USB.INTENB0.word; s->BRDYENB = USB.BRDYENB; s->NRDYENB = USB.NRDYENB; s->BEMPENB = USB.BEMPENB; s->SOFCFG = USB.SOFCFG.word; s->DCPCFG = USB.DCPCFG.word; s->DCPMAXP = USB.DCPMAXP.word; s->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 hrestore(usb_state_t const *s) { hpoweron_write(); /* We will need to reconnect with the PC */ usb_open_status = false; USB.DVSTCTR.word = s->DVSTCTR; USB.TESTMODE.word = s->TESTMODE; USB.REG_C2 = s->REG_C2; USB.CFIFOSEL.word = s->CFIFOSEL; USB.D0FIFOSEL.word = s->D0FIFOSEL; USB.D1FIFOSEL.word = s->D1FIFOSEL; USB.INTENB0.word = s->INTENB0; USB.BRDYENB = s->BRDYENB; USB.NRDYENB = s->NRDYENB; USB.BEMPENB = s->BEMPENB; USB.SOFCFG.word = s->SOFCFG; USB.DCPCFG.word = s->DCPCFG; USB.DCPMAXP.word = s->DCPMAXP; USB.DCPCTR.word = s->DCPCTR; /* Clear remaining interrupts. Read-only bits will be ignored */ USB.INTSTS0.word = 0; USB.BRDYSTS = 0; USB.NRDYSTS = 0; USB.BEMPSTS = 0; /* Restore SYSCFG last as clearing SCKE disables writing */ USB.SYSCFG.word = s->SYSCFG; } gint_driver_t drv_usb = { .name = "USB", /* TODO: usb: Wait for remaining transfers in unbind() */ .hpowered = hpowered, .hpoweron = hpoweron, .hpoweroff = hpoweroff, .hsave = (void *)hsave, .hrestore = (void *)hrestore, .state_size = sizeof(usb_state_t), }; GINT_DECLARE_DRIVER(16, drv_usb);