diff --git a/src/device/nxp/mimxrt1062_clock.go b/src/device/nxp/mimxrt1062_clock.go index 2bd0e33ea..916f1e93a 100644 --- a/src/device/nxp/mimxrt1062_clock.go +++ b/src/device/nxp/mimxrt1062_clock.go @@ -8,6 +8,7 @@ package nxp import ( + "device/arm" "runtime/volatile" "unsafe" ) @@ -378,30 +379,6 @@ func (clk Clock) setCcm(value uint32) { } } -func setSysPfd(value ...uint32) { - for i, val := range value { - pfd528 := CCM_ANALOG.PFD_528.Get() & - ^((CCM_ANALOG_PFD_528_PFD0_CLKGATE_Msk | CCM_ANALOG_PFD_528_PFD0_FRAC_Msk) << (8 * uint32(i))) - frac := (val << CCM_ANALOG_PFD_528_PFD0_FRAC_Pos) & CCM_ANALOG_PFD_528_PFD0_FRAC_Msk - // disable the clock output first - CCM_ANALOG.PFD_528.Set(pfd528 | (CCM_ANALOG_PFD_528_PFD0_CLKGATE_Msk << (8 * uint32(i)))) - // set the new value and enable output - CCM_ANALOG.PFD_528.Set(pfd528 | (frac << (8 * uint32(i)))) - } -} - -func setUsb1Pfd(value ...uint32) { - for i, val := range value { - pfd480 := CCM_ANALOG.PFD_480.Get() & - ^((CCM_ANALOG_PFD_480_PFD0_CLKGATE_Msk | CCM_ANALOG_PFD_480_PFD0_FRAC_Msk) << (8 * uint32(i))) - frac := (val << CCM_ANALOG_PFD_480_PFD0_FRAC_Pos) & CCM_ANALOG_PFD_480_PFD0_FRAC_Msk - // disable the clock output first - CCM_ANALOG.PFD_480.Set(pfd480 | (CCM_ANALOG_PFD_480_PFD0_CLKGATE_Msk << (8 * uint32(i)))) - // set the new value and enable output - CCM_ANALOG.PFD_480.Set(pfd480 | (frac << (8 * uint32(i)))) - } -} - // PLL configuration for ARM type ClockConfigArmPll struct { LoopDivider uint32 // PLL loop divider. Valid range for divider value: 54-108. Fout=Fin*LoopDivider/2. @@ -472,59 +449,178 @@ func (cfg ClockConfigSysPll) Configure(pfd ...uint32) { setSysPfd(pfd...) } -// PLL configuration for USB -type ClockConfigUsbPll struct { - Instance uint8 // USB PLL number (1 or 2) - LoopDivider uint8 // PLL loop divider: 0 - Fout=Fref*20, 1 - Fout=Fref*22 - Src uint8 // Pll clock source, reference _clock_pll_clk_src +func setSysPfd(value ...uint32) { + for i, val := range value { + pfd528 := CCM_ANALOG.PFD_528.Get() & + ^((CCM_ANALOG_PFD_528_PFD0_CLKGATE_Msk | CCM_ANALOG_PFD_528_PFD0_FRAC_Msk) << (8 * uint32(i))) + frac := (val << CCM_ANALOG_PFD_528_PFD0_FRAC_Pos) & CCM_ANALOG_PFD_528_PFD0_FRAC_Msk + // disable the clock output first + CCM_ANALOG.PFD_528.Set(pfd528 | (CCM_ANALOG_PFD_528_PFD0_CLKGATE_Msk << (8 * uint32(i)))) + // set the new value and enable output + CCM_ANALOG.PFD_528.Set(pfd528 | (frac << (8 * uint32(i)))) + } } -func (cfg ClockConfigUsbPll) Configure(pfd ...uint32) { +// PHY configuration for USB HS +type ClockConfigUsbPhy struct { + Instance uint8 // USB PHY number (1 or 2) + XtalFreq uint32 // External reference clock frequency (Hz) + DCal uint32 // Decode to trim nominal 17.78mA current source + TxCal45DP uint32 // Decode to trim nominal 45-Ohm series Rp on USB D+ + TxCal45DM uint32 // Decode to trim nominal 45-Ohm series Rp on USB D- + PllConfig ClockConfigUsbPll +} +// Configure initializes the USB HS (480 Mbit/s) PHY and PLL clocks, including +// the USB +3V regulator (PMU), for use as either USB host or device. +func (cfg ClockConfigUsbPhy) Configure() { + + var ( + usb *USB_Type + phy *USBPHY_Type + chrgDetectReg *volatile.Register32 + chrgDetectMsk uint32 + ) + + // Select appropriate peripherals based on receiver Instance switch cfg.Instance { case 1: - - // bypass PLL first - src := (uint32(cfg.Src) << CCM_ANALOG_PLL_USB1_BYPASS_CLK_SRC_Pos) & CCM_ANALOG_PLL_USB1_BYPASS_CLK_SRC_Msk - CCM_ANALOG.PLL_USB1.Set( - (CCM_ANALOG.PLL_USB1.Get() & ^uint32(CCM_ANALOG_PLL_USB1_BYPASS_CLK_SRC_Msk)) | - CCM_ANALOG_PLL_USB1_BYPASS_Msk | src) - - sel := uint32((cfg.LoopDivider << CCM_ANALOG_PLL_USB1_DIV_SELECT_Pos) & CCM_ANALOG_PLL_USB1_DIV_SELECT_Msk) - CCM_ANALOG.PLL_USB1_SET.Set( - (CCM_ANALOG.PLL_USB1.Get() & ^uint32(CCM_ANALOG_PLL_USB1_DIV_SELECT_Msk)) | - CCM_ANALOG_PLL_USB1_ENABLE_Msk | CCM_ANALOG_PLL_USB1_POWER_Msk | - CCM_ANALOG_PLL_USB1_EN_USB_CLKS_Msk | sel) - - for !CCM_ANALOG.PLL_USB1.HasBits(CCM_ANALOG_PLL_USB1_LOCK_Msk) { - } - - // disable bypass - CCM_ANALOG.PLL_USB1_CLR.Set(CCM_ANALOG_PLL_USB1_BYPASS_Msk) - - // update PFDs after update - setUsb1Pfd(pfd...) - + usb = USB1 // Select USB1 HS PHY/PLL + phy = USBPHY1 // + chrgDetectReg = &USB_ANALOG.USB1_CHRG_DETECT_SET + chrgDetectMsk = USB_ANALOG_USB1_CHRG_DETECT_SET_CHK_CHRG_B | + USB_ANALOG_USB1_CHRG_DETECT_SET_EN_B case 2: - // bypass PLL first - src := (uint32(cfg.Src) << CCM_ANALOG_PLL_USB2_BYPASS_CLK_SRC_Pos) & CCM_ANALOG_PLL_USB2_BYPASS_CLK_SRC_Msk - CCM_ANALOG.PLL_USB2.Set( - (CCM_ANALOG.PLL_USB2.Get() & ^uint32(CCM_ANALOG_PLL_USB2_BYPASS_CLK_SRC_Msk)) | - CCM_ANALOG_PLL_USB2_BYPASS_Msk | src) + usb = USB2 // Select USB2 HS PHY/PLL + phy = USBPHY2 // + chrgDetectReg = &USB_ANALOG.USB2_CHRG_DETECT_SET + chrgDetectMsk = USB_ANALOG_USB2_CHRG_DETECT_SET_CHK_CHRG_B | + USB_ANALOG_USB2_CHRG_DETECT_SET_EN_B + default: + panic("nxp: invalid USB PHY") + } - sel := uint32((cfg.LoopDivider << CCM_ANALOG_PLL_USB2_DIV_SELECT_Pos) & CCM_ANALOG_PLL_USB2_DIV_SELECT_Msk) - CCM_ANALOG.PLL_USB2.Set( - (CCM_ANALOG.PLL_USB2.Get() & ^uint32(CCM_ANALOG_PLL_USB2_DIV_SELECT_Msk)) | - CCM_ANALOG_PLL_USB2_ENABLE_Msk | CCM_ANALOG_PLL_USB2_POWER_Msk | - CCM_ANALOG_PLL_USB2_EN_USB_CLKS_Msk | sel) + // Configure and enable USB PLL clocks + cfg.PllConfig.Configure() - for !CCM_ANALOG.PLL_USB2.HasBits(CCM_ANALOG_PLL_USB2_LOCK_Msk) { + // Release PHY from reset + phy.CTRL.ClearBits(USBPHY_CTRL_SFTRST) + phy.CTRL.ClearBits(USBPHY_CTRL_CLKGATE) + + // Enable power to USB PHY + phy.PWD.Set(0) + phy.CTRL.SetBits(USBPHY_CTRL_ENAUTOCLR_PHY_PWD | USBPHY_CTRL_ENAUTOCLR_CLKGATE | + // enable support for low-speed device connection, direct and indirect (hub) + USBPHY_CTRL_ENUTMILEVEL2 | USBPHY_CTRL_ENUTMILEVEL3) + + // Enable USB HS clocks gate + ClockIpUsbOh3.Enable(true) + // Reset USB peripheral + usb.USBCMD.SetBits(USB_USBCMD_RST) + + // Add a delay after RST to ensure there is a USB D+ pullup sequence + nopDelay(400000) + + // Enable USB LDO + PMU.REG_3P0.Set((PMU.REG_3P0.Get() & ^uint32(PMU_REG_3P0_OUTPUT_TRG_Msk)) | + (0x17 << PMU_REG_3P0_OUTPUT_TRG_Pos) | PMU_REG_3P0_ENABLE_LINREG) + + // check whether we are connected to USB charger + chrgDetectReg.Set(chrgDetectMsk) + + // Decode to trim nominal 17.78mA source for HS TX on USB D+/D- + phy.TX.Set((phy.TX.Get() & + ^uint32(USBPHY_TX_D_CAL_Msk|USBPHY_TX_TXCAL45DN_Msk|USBPHY_TX_TXCAL45DP_Msk)) | + ((cfg.DCal << USBPHY_TX_D_CAL_Pos) & USBPHY_TX_D_CAL_Msk) | + ((cfg.TxCal45DM << USBPHY_TX_TXCAL45DN_Pos) & USBPHY_TX_TXCAL45DN_Msk) | + ((cfg.TxCal45DP << USBPHY_TX_TXCAL45DP_Pos) & USBPHY_TX_TXCAL45DP_Msk)) +} + +// PLL configuration for USB +type ClockConfigUsbPll struct { + Instance uint8 // USB PLL number (1 or 2) + LoopDivider uint8 // PLL loop divider (0 [Fout=Fref*20] or 1 [Fout=Fref*22]) + Src uint8 // PLL bypass clock source (0 [OSC24M] or 1 [CLK1_P & CLK1_N]) + Pfd []uint32 // Phase fractional divisors (len=4, or nil for boot default) +} + +func (cfg ClockConfigUsbPll) Configure() { + // select USB peripheral registers based on receiver's Instance + switch cfg.Instance { + case 1: // USB1 PLL + if CCM_ANALOG.PLL_USB1.HasBits(CCM_ANALOG_PLL_USB1_ENABLE) { + // PLL already configured, enable USB clocks + CCM_ANALOG.PLL_USB1.SetBits(CCM_ANALOG_PLL_USB1_EN_USB_CLKS) + } else { + // bypass PLL first + src := (uint32(cfg.Src) << CCM_ANALOG_PLL_USB1_BYPASS_CLK_SRC_Pos) & + CCM_ANALOG_PLL_USB1_BYPASS_CLK_SRC_Msk + CCM_ANALOG.PLL_USB1.Set( + (CCM_ANALOG.PLL_USB1.Get() & ^uint32(CCM_ANALOG_PLL_USB1_BYPASS_CLK_SRC_Msk)) | + CCM_ANALOG_PLL_USB1_BYPASS | src) + // reconfigure PLL + sel := (uint32(cfg.LoopDivider) << CCM_ANALOG_PLL_USB1_DIV_SELECT_Pos) & + CCM_ANALOG_PLL_USB1_DIV_SELECT_Msk + CCM_ANALOG.PLL_USB1.Set( + (CCM_ANALOG.PLL_USB1.Get() & ^uint32(CCM_ANALOG_PLL_USB1_DIV_SELECT_Msk)) | + CCM_ANALOG_PLL_USB1_ENABLE | CCM_ANALOG_PLL_USB1_POWER | + CCM_ANALOG_PLL_USB1_EN_USB_CLKS | sel) + for !CCM_ANALOG.PLL_USB1.HasBits(CCM_ANALOG_PLL_USB1_LOCK) { + } + // disable bypass + CCM_ANALOG.PLL_USB1.ClearBits(CCM_ANALOG_PLL_USB1_BYPASS) + + // update PFDs (if provided) + if nil != cfg.Pfd { + setUsb1Pfd(cfg.Pfd...) + } + } + case 2: // USB2 PLL + if CCM_ANALOG.PLL_USB2.HasBits(CCM_ANALOG_PLL_USB2_ENABLE) { + // PLL already configured, enable USB clocks + CCM_ANALOG.PLL_USB2.SetBits(CCM_ANALOG_PLL_USB2_EN_USB_CLKS) + } else { + // bypass PLL first + src := (uint32(cfg.Src) << CCM_ANALOG_PLL_USB2_BYPASS_CLK_SRC_Pos) & + CCM_ANALOG_PLL_USB2_BYPASS_CLK_SRC_Msk + CCM_ANALOG.PLL_USB2.Set( + (CCM_ANALOG.PLL_USB2.Get() & ^uint32(CCM_ANALOG_PLL_USB2_BYPASS_CLK_SRC_Msk)) | + CCM_ANALOG_PLL_USB2_BYPASS | src) + // reconfigure PLL + sel := (uint32(cfg.LoopDivider) << CCM_ANALOG_PLL_USB2_DIV_SELECT_Pos) & + CCM_ANALOG_PLL_USB2_DIV_SELECT_Msk + CCM_ANALOG.PLL_USB2.Set( + (CCM_ANALOG.PLL_USB2.Get() & ^uint32(CCM_ANALOG_PLL_USB2_DIV_SELECT_Msk)) | + CCM_ANALOG_PLL_USB2_ENABLE | CCM_ANALOG_PLL_USB2_POWER | + CCM_ANALOG_PLL_USB2_EN_USB_CLKS | sel) + for !CCM_ANALOG.PLL_USB2.HasBits(CCM_ANALOG_PLL_USB2_LOCK) { + } + // disable bypass + CCM_ANALOG.PLL_USB2.ClearBits(CCM_ANALOG_PLL_USB2_BYPASS) } - - // disable bypass - CCM_ANALOG.PLL_USB2.ClearBits(CCM_ANALOG_PLL_USB2_BYPASS_Msk) default: panic("nxp: invalid USB PLL") } } + +func setUsb1Pfd(value ...uint32) { + for i, val := range value { + pfd480 := CCM_ANALOG.PFD_480.Get() & + ^((CCM_ANALOG_PFD_480_PFD0_CLKGATE_Msk | CCM_ANALOG_PFD_480_PFD0_FRAC_Msk) << (8 * uint32(i))) + frac := (val << CCM_ANALOG_PFD_480_PFD0_FRAC_Pos) & CCM_ANALOG_PFD_480_PFD0_FRAC_Msk + // disable the clock output first + CCM_ANALOG.PFD_480.Set(pfd480 | (CCM_ANALOG_PFD_480_PFD0_CLKGATE_Msk << (8 * uint32(i)))) + // set the new value and enable output + CCM_ANALOG.PFD_480.Set(pfd480 | (frac << (8 * uint32(i)))) + } +} + +// We cannot use the sleep timer from this context (import cycle), but we need +// an approximate method to spin CPU cycles for short periods of time. +// go:inline +func nopDelay(cycles uint32) { + for i := uint32(0); i < cycles; i++ { + arm.Asm(`nop`) + } +} diff --git a/src/machine/board_teensy40.go b/src/machine/board_teensy40.go index 070add232..9ecf446f7 100644 --- a/src/machine/board_teensy40.go +++ b/src/machine/board_teensy40.go @@ -56,21 +56,21 @@ const ( // Analog pins const ( - // = Pin // Dig | [Pad] {ADC1/ADC2} - A0 = PA18 // D14 | [AD_B1_02] { 7 / 7 } - A1 = PA19 // D15 | [AD_B1_03] { 8 / 8 } - A2 = PA23 // D16 | [AD_B1_07] { 12 / 12 } - A3 = PA22 // D17 | [AD_B1_06] { 11 / 11 } - A4 = PA17 // D18 | [AD_B1_01] { 6 / 6 } - A5 = PA16 // D19 | [AD_B1_00] { 5 / 5 } - A6 = PA26 // D20 | [AD_B1_10] { 15 / 15 } - A7 = PA27 // D21 | [AD_B1_11] { 0 / 0 } - A8 = PA24 // D22 | [AD_B1_08] { 13 / 13 } - A9 = PA25 // D23 | [AD_B1_09] { 14 / 14 } - A10 = PA12 // D24 | [AD_B0_12] { 1 / - } - A11 = PA13 // D25 | [AD_B0_13] { 2 / - } - A12 = PA30 // D26 | [AD_B1_14] { - / 3 } - A13 = PA31 // D27 | [AD_B1_15] { - / 4 } + // = Pin // Dig [Pad] {ADC1/ADC2} + A0 = PA18 // D14 [AD_B1_02] { 7 / 7 } + A1 = PA19 // D15 [AD_B1_03] { 8 / 8 } + A2 = PA23 // D16 [AD_B1_07] { 12 / 12 } + A3 = PA22 // D17 [AD_B1_06] { 11 / 11 } + A4 = PA17 // D18 [AD_B1_01] { 6 / 6 } + A5 = PA16 // D19 [AD_B1_00] { 5 / 5 } + A6 = PA26 // D20 [AD_B1_10] { 15 / 15 } + A7 = PA27 // D21 [AD_B1_11] { 0 / 0 } + A8 = PA24 // D22 [AD_B1_08] { 13 / 13 } + A9 = PA25 // D23 [AD_B1_09] { 14 / 14 } + A10 = PA12 // D24 [AD_B0_12] { 1 / - } + A11 = PA13 // D25 [AD_B0_13] { 2 / - } + A12 = PA30 // D26 [AD_B1_14] { - / 3 } + A13 = PA31 // D27 [AD_B1_15] { - / 4 } ) // Default peripheral pins @@ -105,6 +105,18 @@ func init() { _UART7.Interrupt = interrupt.New(nxp.IRQ_LPUART7, _UART7.handleInterrupt) } +// #=====================================================# +// | USB | +// #=====================================================# +var ( + // USBCDC is a legacy class being retained here as temporary wrapper. + // See godoc comments on type USBCDC struct definition for details. + USBCDC0 = USBCDC{ + port: 0, // USB_OTG1 (Micro-B port on Teensy 4.0) + buff: NewRingBuffer(), + } +) + // #=====================================================# // | UART | // #===========#===========#=============#===============# diff --git a/src/machine/machine_mimxrt1062_usb.go b/src/machine/machine_mimxrt1062_usb.go new file mode 100644 index 000000000..627cb2b0c --- /dev/null +++ b/src/machine/machine_mimxrt1062_usb.go @@ -0,0 +1,29 @@ +// +build mimxrt1062 + +// Compatibility wrapper for legacy type USBCDC, which provides USB CDC-ACM +// device class emulation for serial UART communication. +// This functionality is being replaced by a platform-agnostic type usb.UART in +// package "machine/usb". + +package machine + +import ( + "machine/usb2" +) + +// USBCDC is the legacy TinyGo type used to implement USB CDC-ACM device class +// emulation for serial UART communication. It is retained here as a temporary +// wrapper for type usb.UART from new package "machine/usb", which is still in +// active development. Once that package has stabilized a bit, this type should +// be removed and usb.UART should be used directly instead. +type USBCDC struct { + port uint8 + buff *RingBuffer + uart usb2.UART +} + +// Configure the embedded usb.UART with our receiver's settings and the given +// UART configuration. This provides compatibility with machine.UART. +func (cdc *USBCDC) Configure(config UARTConfig) { + cdc.uart.Configure(usb2.UARTConfig{BaudRate: config.BaudRate}) +} diff --git a/src/machine/usb/config.go b/src/machine/usb/config.go new file mode 100644 index 000000000..7663029a3 --- /dev/null +++ b/src/machine/usb/config.go @@ -0,0 +1,25 @@ +package usb + +// The following constants must be defined for USB 2.0 host/device support. +// +// However, some or all of these constants may be unused in the core driver +// code, depending on which components are allocated by the USB driver. +// +// These values are __PLATFORM-AGNOSTIC__, and they serve two primary roles: +// 1. Aggregate and export values derived from platform-specific constants. +// 2. Configure core attributes defined by the USB 2.0 specification. +// +const ( + // ConfigPortCount defines the number of USB cores supported on this platform. + ConfigPortCount = configDeviceCount + configHostCount +) + +// ConfigDeviceDescriptor defines the fields used to populate host-enumerated +// device descriptors. +type ConfigDeviceDescriptor struct { + Manufacturer string + Product string + VID uint16 + PID uint16 + BCD uint16 +} diff --git a/src/machine/usb/config_mimxrt1062.go b/src/machine/usb/config_mimxrt1062.go new file mode 100644 index 000000000..b14464e1b --- /dev/null +++ b/src/machine/usb/config_mimxrt1062.go @@ -0,0 +1,233 @@ +// +build mimxrt1062 + +package usb + +const configCPUFrequencyHz = 600000000 + +// The following constants must be defined for USB 2.0 host/device support. +// +// However, some or all of these constants may be unused in the core driver +// code, depending on which components are allocated by the USB driver. +// +// These values are __PLATFORM-SPECIFIC__, and they serve two primary roles: +// 1. Determine which components to allocate from the USB driver. +// 2. Configure driver attributes NOT defined by the USB 2.0 specification. +// +const ( + // configDeviceCount defines the number of USB device-mode ports available. + configDeviceCount = configDeviceCDCACMCount + + // configHostCount defines the number of USB host-mode ports available. + configHostCount = 0 + + // configInterruptQueueSize defines the number of interrupts to retain in the + // queue of runtime processes. + configInterruptQueueSize = 8 + + // configDeviceBufferSize defines the size of the send and receive data + // endpoint buffers. + configDeviceBufferSize = 512 + + // configDeviceMaxEndpoints defines the maximum number of endpoints supported. + configDeviceMaxEndpoints = 4 + + // configDeviceControllerMaxPacketSize defines the maximum packet size for + // communication with an endpoint. The maximum per USB 2.0 spec is 64 bytes, + // although a platform may restrict this to something lower if needed. + configDeviceControllerMaxPacketSize = 64 + + // configDeviceMaxPower defines the maximum power consumption of the USB + // device when fully-operational, expressed in 2 mA units. + configDeviceMaxPower = 50 // 100 mA + + // configDeviceSelfPowered defines whether the device is self-powered (1) or + // not (0). + configDeviceSelfPowered = 1 + + // configDeviceRemoteWakeup defines whether the device supports remote-wakeup + // (1) or not (0). + configDeviceRemoteWakeup = 0 // (NOT YET SUPPORTED) + + // configDeviceControllerMaxDTD defines the maximum number of DTD supported. + configDeviceControllerMaxDTD = 16 + + // configDeviceControllerQHAlign defines the memory alignment of QH buffer. + // Ensure this agrees with the go:align pragma on deviceControllerQHBuffer. + configDeviceControllerQHAlign = 2048 + + // configDeviceControllerDTDAlign defines the memory alignment of DTD buffer. + // Ensure this agrees with the go:align pragma on deviceControllerDTDBuffer. + configDeviceControllerDTDAlign = 32 +) + +// If USB CDC-ACM device support is required, the following constants must be +// defined. +const ( + // configDeviceCDCACMCount defines the number of USB CDC-ACM interfaces + // to initialize; must be less-than or equal to configDeviceCount. + configDeviceCDCACMCount = 1 + + // configDeviceCDCACMConfigurationCount defines the number of configurations + // required for each CDC-ACM device port. + configDeviceCDCACMConfigurationCount = 1 + + // configDeviceCDCACMConfigurationIndex defines the default configuration + // index used to initialize CDC-ACM device class configurations. This is a + // 1-based index into the list of CDC-ACM configurations (which are 0-based + // slices, and thus this index is offset by -1). A configuration index of 0 + // represents the invalid configuration index. + configDeviceCDCACMConfigurationIndex = 1 + + // configDeviceCDCACMInterfaceCount defines the number of interfaces required + // for each CDC-ACM configuration. + configDeviceCDCACMInterfaceCount = 2 + + // configDeviceCDCACMInterruptInPacketSize defines the packet size of CDC-ACM + // communication interface's interrupt input endpoint. + configDeviceCDCACMInterruptInPacketSize = configDeviceCDCACMFSInterruptInPacketSize // (full-speed) + + // configDeviceCDCACMBulkInPacketSize defines the packet size of CDC-ACM data + // interface's bulk input endpoint. + configDeviceCDCACMBulkInPacketSize = configDeviceCDCACMFSBulkInPacketSize // (full-speed) + + // configDeviceCDCACMBulkOutPacketSize defines the packet size of CDC-ACM data + // interface's bulk output endpoint. + configDeviceCDCACMBulkOutPacketSize = configDeviceCDCACMFSBulkOutPacketSize // (full-speed) + + // configDeviceCDCACMInterruptInInterval defines the interval of CDC-ACM + // communication interface's interrupt input endpoint. + configDeviceCDCACMInterruptInInterval = configDeviceCDCACMFSInterruptInInterval // (full-speed) +) + +// If USB CDC-ACM device support is required, the following arrays must be +// initialized with each device's CDC-ACM configuration. +var ( + // configDeviceCDCACM defines the configuration specific to CDC-ACM devices + // including the properties of endpoints required by the device class driver, + // as well as the serial UART line coding properties. + configDeviceCDCACM = [configDeviceCDCACMCount][configDeviceCDCACMConfigurationCount]deviceCDCACMConfig{ + {{ // USB CDC-ACM [0] + interfaceSpeed: specSpeedFull, // USB full-speed (12 Mbit/s) + // Serial line configuration + lineCodingSize: 7, // Size of line-coding message + lineCodingBaudRate: 115200, // Data terminal rate + lineCodingCharFormat: 0, // Character format + lineCodingParityType: 0, // Parity type + lineCodingDataBits: 8, // Data word size + // Communication/control interface + commInterfaceIndex: descriptorInterfaceCDCACMComm, // communication/control interface index + commInterruptInEndpoint: descriptorEndpointCDCACMCommInterruptIn, // interrupt input endpoint index (address) + commInterruptInPacketSize: configDeviceCDCACMInterruptInPacketSize, + commInterruptInInterval: configDeviceCDCACMInterruptInInterval, + // Data interface + dataInterfaceIndex: descriptorInterfaceCDCACMData, // data interface index + dataBulkInEndpoint: descriptorEndpointCDCACMDataBulkIn, // bulk input endpoint index (address) + dataBulkInPacketSize: configDeviceCDCACMBulkInPacketSize, + dataBulkOutEndpoint: descriptorEndpointCDCACMDataBulkOut, // bulk output endpoint index (address) + dataBulkOutPacketSize: configDeviceCDCACMBulkOutPacketSize, + }}, + } + + // configDeviceCDCACMDescriptor defines the generic USB 2.0 descriptors used + // to describe each CDC-ACM device enabled. + // + // Note that the actual descriptor byte slices need not (should not) be + // defined here. Instead, a reusable global slice should be declared as a + // standalone type and not a member of a composite type; this allows both + // reusability and better control over the memory layout/alignment for USB + // PHYs that may require constraints of this sort. The fields of struct + // deviceDescriptor are therefore all pointers to byte slices so that they + // can be initialized with references to the aforementioned global arrays. + configDeviceCDCACMDescriptor = [configDeviceCDCACMCount]deviceDescriptor{ + { // USB CDC-ACM [0] + pDevice: &descriptorDeviceCDCACM, + pConfig: &descriptorConfigurationCDCACM, + language: []deviceDescriptorLanguage{ + { + pString: []deviceDescriptorString{ + &descriptorStringCDCACMLanguage, + &configDescriptorStringCDCACMManufacturer, + &configDescriptorStringCDCACMProduct, + }, + ident: 0x0409, + }, + }, + }, + } + + // default device descriptor info (little-endian byte order). + configDescriptorDeviceCDCACMVID = []uint8{0xC9, 0x1F} // USB Vendor ID (0x1FC9) + configDescriptorDeviceCDCACMPID = []uint8{0x94, 0x00} // USB Product ID (0x0094) + configDescriptorDeviceCDCACMBCD = []uint8{0x01, 0x01} // USB Device Version (0x0101) + + configDescriptorStringCDCACMManufacturer = []uint8{ + 2 + 2*18, specDescriptorTypeString, + 'N', 0, + 'X', 0, + 'P', 0, + ' ', 0, + 'S', 0, + 'e', 0, + 'm', 0, + 'i', 0, + 'c', 0, + 'o', 0, + 'n', 0, + 'd', 0, + 'u', 0, + 'c', 0, + 't', 0, + 'o', 0, + 'r', 0, + 's', 0, + } + + configDescriptorStringCDCACMProduct = []uint8{ + 2 + 2*20, specDescriptorTypeString, + 'T', 0, + 'i', 0, + 'n', 0, + 'y', 0, + 'G', 0, + 'o', 0, + ' ', 0, + 'U', 0, + 'S', 0, + 'B', 0, + ' ', 0, + '(', 0, + 'C', 0, + 'D', 0, + 'C', 0, + '-', 0, + 'A', 0, + 'C', 0, + 'M', 0, + ')', 0, + } +) + +// The following additional constants are not required by the USB driver but are +// used by the usb package on this platform. +const ( + + // configInterruptPriority defines the priority number for USB interrupts. + configInterruptPriority = 3 + + // configDeviceControllerMaxPrimeAttempts defines the maximum number of + // attempts to prime an endpoint for transfer. If attempts exceeds this + // value, then the endpoint status has been reset. + configDeviceControllerMaxPrimeAttempts = 10000000 + + // USB CDC-ACM high-speed (480 Mbit/s) packet size + configDeviceCDCACMHSInterruptInPacketSize = 16 + configDeviceCDCACMHSInterruptInInterval = 7 // 2^(7-1)/8 = 8ms + configDeviceCDCACMHSBulkInPacketSize = 512 + configDeviceCDCACMHSBulkOutPacketSize = 512 + + // USB CDC-ACM full-speed (12 Mbit/s) packet size + configDeviceCDCACMFSInterruptInPacketSize = 16 + configDeviceCDCACMFSInterruptInInterval = 8 // 2^(8-1)/8 = 16ms + configDeviceCDCACMFSBulkInPacketSize = 64 + configDeviceCDCACMFSBulkOutPacketSize = 64 +) diff --git a/src/machine/usb/descriptor.go b/src/machine/usb/descriptor.go new file mode 100644 index 000000000..51f0ad82f --- /dev/null +++ b/src/machine/usb/descriptor.go @@ -0,0 +1,162 @@ +package usb + +// USB specification version number (BCD) +var descriptorUSBSpecification = []uint8{0x00, 0x02} // USB 2.0 + +// USB CDC-ACM descriptor constants +const ( + // interface indices + descriptorInterfaceCDCACMComm = 0 // communication/control interface + descriptorInterfaceCDCACMData = 1 // data interface + + descriptorEndpointCDCACMCommInterruptIn = 1 // interrupt endpoint (input) + descriptorEndpointCDCACMDataBulkIn = 2 // data endpoint (input) + descriptorEndpointCDCACMDataBulkOut = 3 // data endpoint (output) + + descriptorConfigurationCDCACMHeaderFuncSize = 5 + descriptorConfigurationCDCACMCallManageSize = 5 + descriptorConfigurationCDCACMAbstractSize = 4 + descriptorConfigurationCDCACMUnionFuncSize = 5 + + descriptorConfigurationCDCACMSize = uint16( + specDescriptorLengthConfigure + // configuration + specDescriptorLengthInterface + // communication/control interface + descriptorConfigurationCDCACMHeaderFuncSize + // CDC header + descriptorConfigurationCDCACMCallManageSize + // CDC call management + descriptorConfigurationCDCACMAbstractSize + // CDC abstract control + descriptorConfigurationCDCACMUnionFuncSize + // CDC union + specDescriptorLengthEndpoint + // communication/control input endpoint + specDescriptorLengthInterface + // data interface + specDescriptorLengthEndpoint + // data input endpoint + specDescriptorLengthEndpoint) // data output endpoint + + descriptorConfigurationCDCACMAttributes = (specDescriptorConfigureAttributeD7Msk) | // Bit 7: reserved (1) + (configDeviceSelfPowered << specDescriptorConfigureAttributeSelfPoweredPos) | // Bit 6: self-powered + (configDeviceRemoteWakeup << specDescriptorConfigureAttributeRemoteWakeupPos) | // Bit 5: remote wakeup + 0 // Bits 0-4: reserved (0) +) + +// USB 2.0 CDC-ACM descriptors +var ( + // descriptorDeviceCDCACM is the default device descriptor for CDC-ACM + // devices. + descriptorDeviceCDCACM = []uint8{ + specDescriptorLengthDevice, // Size of this descriptor in bytes + specDescriptorTypeDevice, // DEVICE Descriptor Type + descriptorUSBSpecification[0], // USB Specification Release Number in BCD (low) + descriptorUSBSpecification[1], // USB Specification Release Number in BCD (high) + uint8(deviceClassCDC), // Class code (assigned by the USB-IF). + 0, // Subclass code (assigned by the USB-IF). + deviceCDCNoClassSpecificProtocol, // Protocol code (assigned by the USB-IF). + configDeviceControllerMaxPacketSize, // Maximum packet size for endpoint zero (8, 16, 32, or 64) + configDescriptorDeviceCDCACMVID[0], // Vendor ID (low) (assigned by the USB-IF) + configDescriptorDeviceCDCACMVID[1], // Vendor ID (high) (assigned by the USB-IF) + configDescriptorDeviceCDCACMPID[0], // Product ID (low) (assigned by the manufacturer) + configDescriptorDeviceCDCACMPID[1], // Product ID (high) (assigned by the manufacturer) + configDescriptorDeviceCDCACMBCD[0], // Device release number in BCD (low) + configDescriptorDeviceCDCACMBCD[1], // Device release number in BCD (high) + 0x01, // Index of string descriptor describing manufacturer + 0x02, // Index of string descriptor describing product + 0x00, // Index of string descriptor describing the device's serial number + configDeviceCDCACMConfigurationCount, // Number of possible configurations + } + + // descriptorConfigurationCDCACM is the default configuration descriptor for + // CDC-ACM devices. + descriptorConfigurationCDCACM = []uint8{ + specDescriptorLengthConfigure, // Size of this descriptor in bytes + specDescriptorTypeConfigure, // Descriptor Type + uint8(descriptorConfigurationCDCACMSize), // Total length of data returned for this configuration (low) + uint8(descriptorConfigurationCDCACMSize >> 8), // Total length of data returned for this configuration (high) + configDeviceCDCACMInterfaceCount, // Number of interfaces supported by this configuration + configDeviceCDCACMConfigurationIndex, // Value to use to select this configuration + 0, // Index of string descriptor describing this configuration + descriptorConfigurationCDCACMAttributes, // Configuration attributes + configDeviceMaxPower, // Max power consumption when fully-operational (2 mA units) + + // Communication/Control Interface Descriptor + specDescriptorLengthInterface, // Descriptor length + specDescriptorTypeInterface, // Descriptor type + descriptorInterfaceCDCACMComm, // Interface index + 0, // Alternate setting + 1, // Number of endpoints + deviceCDCCommClass, // Class code + deviceCDCAbstractControlModel, // Subclass code + deviceCDCNoClassSpecificProtocol, // Protocol code (NOTE: Teensyduino defines this as 1 [AT V.250]) + 0, // Interface Description String Index + + // CDC Header Functional Descriptor + descriptorConfigurationCDCACMHeaderFuncSize, // Size of this descriptor in bytes + specDescriptorTypeCDCInterface, // Descriptor Type + deviceCDCHeaderFuncDesc, // Descriptor Subtype + 0x10, // USB CDC specification version 1.10 (low) + 0x01, // USB CDC specification version 1.10 (high) + + // CDC Call Management Functional Descriptor + descriptorConfigurationCDCACMCallManageSize, // Size of this descriptor in bytes + specDescriptorTypeCDCInterface, // Descriptor Type + deviceCDCCallManagementFuncDesc, // Descriptor Subtype + 0x01, // Capabilities + 1, // Data Interface + + // CDC Abstract Control Management Functional Descriptor + descriptorConfigurationCDCACMAbstractSize, // Size of this descriptor in bytes + specDescriptorTypeCDCInterface, // Descriptor Type + deviceCDCAbstractControlFuncDesc, // Descriptor Subtype + 0x06, // Capabilities + + // CDC Union Functional Descriptor + descriptorConfigurationCDCACMUnionFuncSize, // Size of this descriptor in bytes + specDescriptorTypeCDCInterface, // Descriptor Type + deviceCDCUnionFuncDesc, // Descriptor Subtype + 0, // Controlling interface index + 1, // Controlled interface index + + // Communication/Control Notification Endpoint descriptor + specDescriptorLengthEndpoint, // Size of this descriptor in bytes + specDescriptorTypeEndpoint, // Descriptor Type + descriptorEndpointCDCACMCommInterruptIn | // Endpoint address + specDescriptorEndpointAddressDirectionIn, + uint8(specEndpointInterrupt), // Attributes + uint8(configDeviceCDCACMInterruptInPacketSize), // Max packet size (low) + uint8(configDeviceCDCACMInterruptInPacketSize >> 8), // Max packet size (high) + configDeviceCDCACMInterruptInInterval, // Polling Interval + + // Data Interface Descriptor + specDescriptorLengthInterface, // Interface length + specDescriptorTypeInterface, // Interface type + descriptorInterfaceCDCACMData, // Interface index + 0, // Alternate setting + 2, // Number of endpoints + deviceCDCDataClass, // Class code + 0, // Subclass code + deviceCDCNoClassSpecificProtocol, // Protocol code + 0, // Interface Description String Index + + // Data Bulk Input Endpoint descriptor + specDescriptorLengthEndpoint, // Size of this descriptor in bytes + specDescriptorTypeEndpoint, // Descriptor Type + descriptorEndpointCDCACMDataBulkIn | // Endpoint address + specDescriptorEndpointAddressDirectionIn, + uint8(specEndpointBulk), // Attributes + uint8(configDeviceCDCACMBulkInPacketSize), // Max packet size (low) + uint8(configDeviceCDCACMBulkInPacketSize >> 8), // Max packet size (high) + 0, // Polling Interval + + // Data Bulk Output Endpoint descriptor + specDescriptorLengthEndpoint, // Size of this descriptor in bytes + specDescriptorTypeEndpoint, // Descriptor Type + descriptorEndpointCDCACMDataBulkOut | // Endpoint address + specDescriptorEndpointAddressDirectionOut, + uint8(specEndpointBulk), // Attributes + uint8(configDeviceCDCACMBulkOutPacketSize), // Max packet size (low) + uint8(configDeviceCDCACMBulkOutPacketSize >> 8), // Max packet size (high) + 0, // Polling Interval + } + + descriptorStringCDCACMLanguage = []uint8{ + 2 + 2*1, // Size of this descriptor in bytes + specDescriptorTypeString, + 0x09, 0x04, + } +) diff --git a/src/machine/usb/device.go b/src/machine/usb/device.go new file mode 100644 index 000000000..ce853190f --- /dev/null +++ b/src/machine/usb/device.go @@ -0,0 +1,1066 @@ +package usb + +// Unexported USB device type definitions. +type ( + // deviceClassDriver defines the class driver interface. + // + // This interface is used internally to abstract communication between a USB + // device and the device class (e.g., CDC, HID, MSC, etc.) in which it is + // configured. + deviceClassDriver interface { + init(device *device, config *deviceClassConfig, id uint8) status // Class driver initialization- entry of the class driver + deinit() status // Class driver de-initialization + event(event deviceClassEventID, param interface{}) status // Class driver event callback + send(ep uint8, buffer []uint8, length uint32) status // Class driver send to endpoint + receive(ep uint8, buffer []uint8, length uint32) status // Class driver receive from endpoint + } + + // deviceClassEventHandler defines the methods required to receive all device- + // and class-level event notifications on a given USB port. + // + // This interface is intended as the primary communication mechanism between + // a USB device (of any class) and the application layer using that device. + // Thus, it is meant to be implemented internally by one of the application's + // USB handlers (e.g., a serial UART driver using the CDC-ACM device class). + deviceClassEventHandler interface { + deviceEvent(ev deviceEventID, param interface{}) status + classEvent(ev uint32, param interface{}) status + } + + // deviceEndpointController defines the method(s) required + deviceEndpointController interface { + controlEndpoint(message deviceEndpointControlMessage, param interface{}) status + } + + deviceEventFunc func(ev deviceEventID, param interface{}) status + deviceRequestCallbackFunc func(setup *deviceSetup, buffer *[]uint8, length *uint32) status + + deviceNotificationID uint8 + deviceControlID uint8 + deviceStatusID uint8 + deviceStateID uint8 + deviceEndpointStatusID uint8 + deviceClassEventID uint8 + deviceEventID uint8 + deviceClassID uint8 + deviceControlRWSequence uint8 + + deviceDescriptorString *[]uint8 + + deviceDescriptorLanguage struct { + pString []deviceDescriptorString + ident uint16 + } + + deviceDescriptor struct { + pDevice *[]uint8 + pConfig *[]uint8 + language []deviceDescriptorLanguage + } + + deviceEndpointConfig struct { + maxPacketSize uint16 // Endpoint maximum packet size + address uint8 // Endpoint address + transferType uint8 // Endpoint transfer type + zlt uint8 // ZLT flag + interval uint8 // Endpoint interval + } + + deviceEndpointStatus struct { + address uint8 // Endpoint address + status uint16 // Endpoint status (idle or stalled) + } + + deviceNotification struct { + buffer []uint8 // Transferred buffer + length uint32 // Transferred data length + code deviceNotificationID // Notification code + isSetup bool // Is in a setup phase + } + + // deviceSetup contains the setup information for a USB device. + deviceSetupBitmap uint64 + deviceSetupBuffer [deviceSetupSize]uint8 + deviceSetup struct { + bmRequestType uint8 // 8, 1 (bits, bytes) + bRequest uint8 // 8, 1 + wValue uint16 // 16, 2 + wIndex uint16 // 16, 2 + wLength uint16 // 16, 2 (= 64 bits, 8 bytes) + } + + deviceEndpointControlMessage struct { + buffer []uint8 // Transferred buffer + length uint32 // Transferred data length + isSetup bool // Is in a setup phase + } + + deviceEndpointControlList [2 * configDeviceMaxEndpoints]deviceEndpointControl + deviceEndpointControl struct { + handler deviceEndpointController + param interface{} // Parameter for callback function + isBusy bool + } + + // deviceEndpoint contains the information for a USB device endpoint. + deviceEndpoint struct { + address uint8 // Endpoint address + transferType uint8 // Endpoint transfer type + maxPacketSize uint16 // Endpoint maximum packet size + interval uint8 // Endpoint interval + } + + // deviceInterface contains the endpoints and class-specific information for + // a USB device interface. + deviceInterface struct { + alternateSetting uint8 // Alternate setting number + endpoint []deviceEndpoint // Endpoints of the interface + classSpecific interface{} // Class specific structure handle + } + + // deviceClassInterface contains the USB device class details, including + // all of its device interfaces. + deviceClassInterface struct { + classCode uint8 // Class code of the interface + subclassCode uint8 // Subclass code of the interface + protocolCode uint8 // Protocol code of the interface + interfaceNumber uint8 // Interface number + deviceInterface []deviceInterface // Interface structure list + } + + // deviceClassInfo contains the USB device class ID and all of its class + // interfaces. + deviceClassInfo struct { + classID deviceClassID // Class type + interfaceList []deviceClassInterface // Interfaces of the class + } + + // deviceClassConfig contains the configuration for a USB device class. + deviceClassConfig struct { + driver deviceClassDriver // USB device class driver interface + info deviceClassInfo // Detailed information of the class + } + + // deviceClass contains common device class state information. + deviceClass struct { + device *device // USB device handle + config []deviceClassConfig // USB device class configuration list + handler deviceClassEventHandler // application callback + setupBuffer []uint8 // Setup packet data buffer + transcationBuffer uint16 // Get status/configuration, get/set interface, get sync frame + } + + device struct { + port uint8 // USB port (core index) + controller deviceController // Controller interface + class *deviceClass // USB device class + endpointControl deviceEndpointControlList // Endpoint callback function structure + deviceAddress uint8 // Current device address + state deviceStateID // Current device state + isResetting bool // Is doing device reset or not + hwTick int64 // (volatile) Current hw tick (ms) + } + + // // This structure is used to pass the control request information. + // // The structure is used in following two cases. + // // 1. Case one, the host wants to send data to the device in the control data stage: @n + // // a. If a setup packet is received, the structure is used to pass the setup packet data and wants to get the + // // buffer to receive data sent from the host. + // // The field isSetup is 1. + // // The length is the requested buffer length. + // // The buffer is filled by the class or application by using the valid buffer address. + // // The setup is the setup packet address. + // // b. If the data received is sent by the host, the structure is used to pass the data buffer address and the + // // data + // // length sent by the host. + // // In this way, the field isSetup is 0. + // // The buffer is the address of the data sent from the host. + // // The length is the received data length. + // // The setup is the setup packet address. @n + // // 2. Case two, the host wants to get data from the device in control data stage: @n + // // If the setup packet is received, the structure is used to pass the setup packet data and wants to get the + // // data buffer address to send data to the host. + // // The field isSetup is 1. + // // The length is the requested data length. + // // The buffer is filled by the class or application by using the valid buffer address. + // // The setup is the setup packet address. + deviceControlRequest struct { + setup deviceSetup // Setup data + buffer []uint8 // Buffer + length uint32 // Buffer length or requested length + isSetup bool // Indicates whether a setup packet is received + } + + // // deviceGetDescriptorCommon contains the result of a control request for: + // // get descriptor common + // deviceGetDescriptorCommon struct { + // buffer []uint8 // Buffer + // length uint32 // Buffer length + // } + + // deviceGetDeviceDescriptor contains the result of a control request for: + // get device descriptor + deviceGetDeviceDescriptor struct { + buffer []uint8 // Buffer + length uint32 // Buffer length + } + + // // deviceGetDeviceQualifierDescriptor contains the result of a control + // // request for: get device qualifier descriptor + // deviceGetDeviceQualifierDescriptor struct { + // buffer []uint8 // Buffer + // length uint32 // Buffer length + // } + + // deviceGetConfigurationDescriptor contains the result of a control request + // for: get configuration descriptor + deviceGetConfigurationDescriptor struct { + buffer []uint8 // Buffer + length uint32 // Buffer length + configuration uint8 // The configuration number + } + + // // deviceGetBOSDescriptor contains the result of a control request for: get + // // bos descriptor + // deviceGetBOSDescriptor struct { + // buffer []uint8 // Buffer + // length uint32 // Buffer length + // } + + // deviceGetStringDescriptor contains the result of a control request for: + // get string descriptor + deviceGetStringDescriptor struct { + buffer []uint8 // Buffer + length uint32 // Buffer length + languageID uint16 // Language ID + stringIndex uint8 // String index + } + + // // deviceGetHIDDescriptor contains the result of a control request for: get + // // HID descriptor + // deviceGetHIDDescriptor struct { + // buffer []uint8 // Buffer + // length uint32 // Buffer length + // interfaceNumber uint8 // The interface number + // } + + // // deviceGetHIDReportDescriptor contains the result of a control request for: + // // get HID report descriptor + // deviceGetHIDReportDescriptor struct { + // buffer []uint8 // Buffer + // length uint32 // Buffer length + // interfaceNumber uint8 // The interface number + // } + + // // deviceGetHIDPhysicalDescriptor contains the result of a control request + // // for: get HID physical descriptor + // deviceGetHIDPhysicalDescriptor struct { + // buffer []uint8 // Buffer + // length uint32 // Buffer length + // index uint8 // Physical index + // interfaceNumber uint8 // The interface number + // } + +) + +// Unexported enumerated constant values for USB device. +const ( + deviceNotifyBusReset deviceNotificationID = iota + 0x10 // Reset signal detected + deviceNotifySuspend // Suspend signal detected + deviceNotifyResume // Resume signal detected + deviceNotifyLPMSleep // LPM signal detected + deviceNotifyLPMResume // Resume signal detected + deviceNotifyError // Errors happened in bus + deviceNotifyDetach // Device disconnected from a host + deviceNotifyAttach // Device connected to a host + deviceNotifyDCDDetectFinished // Device charger detection finished +) + +const ( + deviceControlRun deviceControlID = iota // Enable the device functionality + deviceControlStop // Disable the device functionality + deviceControlEndpointInit // Initialize a specified endpoint + deviceControlEndpointDeinit // De-initialize a specified endpoint + deviceControlEndpointStall // Stall a specified endpoint + deviceControlEndpointUnstall // Un-stall a specified endpoint + deviceControlGetDeviceStatus // Get device status + deviceControlGetEndpointStatus // Get endpoint status + deviceControlSetDeviceAddress // Set device address + deviceControlGetSynchFrame // Get current frame + deviceControlResume // Drive controller to generate a resume signal in USB bus + deviceControlSleepResume // Drive controller to generate a LPM resume signal in USB bus + deviceControlSuspend // Drive controller to enter into suspend mode + deviceControlSleep // Drive controller to enter into sleep mode + deviceControlSetDefaultStatus // Set controller to default status + deviceControlGetSpeed // Get current speed + deviceControlGetOTGStatus // Get OTG status + deviceControlSetOTGStatus // Set OTG status + deviceControlSetTestMode // Drive xCHI into test mode + deviceControlGetRemoteWakeUp // Get flag of LPM Remote Wake-up Enabled by USB host. + deviceControlDCDDisable // disable dcd module function. + deviceControlDCDEnable // enable dcd module function. + deviceControlPreSetDeviceAddress // Pre set device address + deviceControlUpdateHwTick // update hardware tick +) + +const ( + deviceStatusTestMode deviceStatusID = iota + 1 // Test mode + deviceStatusSpeed // Current speed + deviceStatusOTG // OTG status + deviceStatusDevice // Device status + deviceStatusEndpoint // Endpoint state usb_device_endpoint_status_t + deviceStatusDeviceState // Device state + deviceStatusAddress // Device address + deviceStatusSynchFrame // Current frame + deviceStatusBus // Bus status + deviceStatusBusSuspend // Bus suspend + deviceStatusBusSleep // Bus suspend + deviceStatusBusResume // Bus resume + deviceStatusRemoteWakeup // Remote wakeup state + deviceStatusBusSleepResume // Bus resume +) + +const ( + deviceStateConfigured deviceStateID = iota // Device state, Configured + deviceStateAddress // Device state, Address + deviceStateDefault // Device state, Default + deviceStateAddressing // Device state, Address setting + deviceStateTestMode // Device state, Test mode + deviceStateInit // Device state, initializing +) + +const ( + deviceEndpointStateIdle deviceEndpointStatusID = iota // Endpoint state, idle + deviceEndpointStateStalled // Endpoint state, stalled +) + +const ( + deviceEventBusReset deviceEventID = iota + 1 // USB bus reset signal detected + deviceEventSuspend // USB bus suspend signal detected + deviceEventResume // USB bus resume signal detected. The resume signal is driven by itself or a host + deviceEventSleeped // USB bus LPM suspend signal detected + deviceEventLPMResume // USB bus LPM resume signal detected. The resume signal is driven by itself or a host + deviceEventError // An error is happened in the bus. + deviceEventDetach // USB device is disconnected from a host. + deviceEventAttach // USB device is connected to a host. + deviceEventSetConfiguration // Set configuration. + deviceEventSetInterface // Set interface. + deviceEventGetDeviceDescriptor // Get device descriptor. + deviceEventGetConfigurationDescriptor // Get configuration descriptor. + deviceEventGetStringDescriptor // Get string descriptor. + deviceEventGetHIDDescriptor // Get HID descriptor. + deviceEventGetHIDReportDescriptor // Get HID report descriptor. + deviceEventGetHIDPhysicalDescriptor // Get HID physical descriptor. + deviceEventGetBOSDescriptor // Get configuration descriptor. + deviceEventGetDeviceQualifierDescriptor // Get device qualifier descriptor. + deviceEventVendorRequest // Vendor request. + deviceEventSetRemoteWakeup // Enable or disable remote wakeup function. + deviceEventGetConfiguration // Get current configuration index + deviceEventGetInterface // Get current interface alternate setting value + deviceEventSetBHNPEnable // Enable or disable BHNP. + deviceEventDCDDetectionfinished // The DCD detection finished +) + +const ( + deviceClassInvalid deviceClassID = iota + deviceClassHID + deviceClassCDC + deviceClassMSC + deviceClassAudio + deviceClassPHDC + deviceClassVideo + deviceClassPrinter + deviceClassDFU + deviceClassCCID +) + +const ( + deviceClassEventInvalid deviceClassEventID = iota + deviceClassEventClassRequest + deviceClassEventDeviceReset + deviceClassEventSetConfiguration + deviceClassEventSetInterface + deviceClassEventSetEndpointHalt + deviceClassEventClearEndpointHalt +) + +const ( + deviceControlPipeSetupStage deviceControlRWSequence = iota // Setup stage + deviceControlPipeDataStage // Data stage + deviceControlPipeStatusStage // status stage +) + +// Sizes of various device structures and buffers. +const ( + deviceSetupSize = 8 // deviceSetup struct (bytes) +) + +var ( + deviceClassInstance [configDeviceCount]deviceClass + deviceSetupBufferInstance [configDeviceCount]deviceSetupBuffer + + deviceEndpointControlInConfig = deviceEndpointConfig{ + maxPacketSize: configDeviceControllerMaxPacketSize, + address: uint8(specEndpointControl | specDescriptorEndpointAddressDirectionIn), + transferType: specEndpointControl, + zlt: 1, + interval: 0, + } + deviceEndpointControlOutConfig = deviceEndpointConfig{ + maxPacketSize: configDeviceControllerMaxPacketSize, + address: uint8(specEndpointControl | specDescriptorEndpointAddressDirectionOut), + transferType: specEndpointControl, + zlt: 1, + interval: 0, + } +) + +func (d *device) init(port uint8) (s status) { + + // initialize device + d.port = port + d.controller = d.initController() // obtain a device controller handle + d.deviceAddress = 0 + d.state = deviceStateDefault + d.isResetting = false + d.hwTick = 0 + for i := range d.endpointControl { + d.endpointControl[i].handler = nil + d.endpointControl[i].param = nil + d.endpointControl[i].isBusy = false + } + + // initialize platform via device controller interface + return d.controller.init() +} + +func (d *device) deinit() status { + + // de=initialize device + s := d.controller.deinit() + d.controller = nil + return s +} + +func (d *device) initClass(id uint8, config []deviceClassConfig, + handler deviceClassEventHandler) *deviceClass { + + // verify a device configuration was provided + if nil == d || nil == config || len(config) == 0 { + return nil + } + + if 0 == id || int(id) > len(config) { + return nil + } + + c := &deviceClassInstance[d.port] + b := &deviceSetupBufferInstance[d.port] + + // initialize device class + c.device = d + c.config = config + c.handler = handler + c.setupBuffer = b[:] + c.transcationBuffer = 0 + + // add a class reference to the receiver + d.class = c + + // initialze each of the device class drivers + for i := range c.config { + if nil != c.config[i].driver { + if !c.config[i].driver.init(d, &c.config[i], id).OK() { + // remove the driver from configuration if it fails initialization + c.config[i].driver = nil + } + } + } + + return c +} + +func (d *device) transfer(address uint8, buffer []uint8, length uint32) status { + + if nil == d.controller { + return statusInvalidController + } + + endpoint, direction := unpackEndpoint(address) + index := (endpoint << 1) | direction + + if d.endpointControl[index].isBusy { + return statusBusy + } + d.endpointControl[index].isBusy = true + + var s status + if specDescriptorEndpointAddressDirectionIn == + address&specDescriptorEndpointAddressDirectionMsk { + s = d.controller.send(address, buffer, length) + } else { + s = d.controller.receive(address, buffer, length) + } + if !s.OK() { + d.endpointControl[index].isBusy = false + } + return s +} + +func (d *device) send(address uint8, buffer []uint8, length uint32) status { + return d.transfer((address&specDescriptorEndpointAddressNumberMsk)| + (specDescriptorEndpointAddressDirectionIn), buffer, length) +} + +func (d *device) receive(address uint8, buffer []uint8, length uint32) status { + return d.transfer((address&specDescriptorEndpointAddressNumberMsk)| + (specDescriptorEndpointAddressDirectionOut), buffer, length) +} + +func (d *device) cancel(address uint8) status { + if nil == d.controller { + return statusInvalidController + } + return d.controller.cancel(address) +} + +func (d *device) control(command deviceControlID, param interface{}) status { + if nil == d.controller { + return statusInvalidController + } + return d.controller.control(command, param) +} + +func (d *device) initControlPipes() status { + + if s := d.initEndpoint(&deviceEndpointControlInConfig, d, d.class); !s.OK() { + return s + } + + if s := d.initEndpoint(&deviceEndpointControlOutConfig, d, d.class); !s.OK() { + _ = d.deinitEndpoint(deviceEndpointControlInConfig.address) + return s + } + + return statusSuccess +} + +func (d *device) initEndpoint(config *deviceEndpointConfig, + handler deviceEndpointController, param interface{}) status { + + endpoint, direction := unpackEndpoint(config.address) + + if endpoint >= configDeviceMaxEndpoints { + return statusInvalidParameter + } + + d.endpointControl[(endpoint<<1)|direction].handler = handler + d.endpointControl[(endpoint<<1)|direction].param = param + d.endpointControl[(endpoint<<1)|direction].isBusy = false + + return d.control(deviceControlEndpointInit, config) +} + +func (d *device) deinitEndpoint(address uint8) status { + + s := d.control(deviceControlEndpointDeinit, address) + + endpoint, direction := unpackEndpoint(address) + + if endpoint >= configDeviceMaxEndpoints { + return statusInvalidParameter + } + + d.endpointControl[(endpoint<<1)|direction].handler = nil + d.endpointControl[(endpoint<<1)|direction].param = nil + d.endpointControl[(endpoint<<1)|direction].isBusy = false + + return s +} + +func (d *device) stallEndpoint(address uint8) status { + + endpoint, _ := unpackEndpoint(address) + + if endpoint >= configDeviceMaxEndpoints { + return statusInvalidParameter + } + return d.control(deviceControlEndpointStall, address) +} + +func (d *device) unstallEndpoint(address uint8) status { + + endpoint, _ := unpackEndpoint(address) + + if endpoint >= configDeviceMaxEndpoints { + return statusInvalidParameter + } + return d.control(deviceControlEndpointUnstall, address) +} + +func (d *device) feedback(setup *deviceSetup, status status, stage deviceControlRWSequence, + buffer *[]uint8, length *uint32) status { + direction := uint8(specIn) + if !status.OK() { + if (setup.bmRequestType&specRequestTypeTypeMsk) == specRequestTypeTypeStandard && + (setup.bmRequestType&specRequestTypeDirMsk) == specRequestTypeDirOut && + 0 != setup.wLength && deviceControlPipeSetupStage == stage { + direction = specOut + } + return d.stallEndpoint(specEndpointControl | + (direction << specDescriptorEndpointAddressDirectionPos)) + } else { + if *length > uint32(setup.wLength) { + *length = uint32(setup.wLength) + } + s := d.send(specEndpointControl, *buffer, *length) + if s.OK() && (setup.bmRequestType&specRequestTypeDirMsk) == specRequestTypeDirIn { + s = d.receive(specEndpointControl, nil, 0) + } + return s + } +} + +func (d *device) controlEndpoint(message deviceEndpointControlMessage, param interface{}) status { + + // verify request and parameters + if message.length == 0xFFFFFFFF || nil == param { + return statusInvalidRequest + } + class, ok := param.(*deviceClass) + if !ok { + return statusInvalidParameter + } + // read current setup buffer from given deviceClass in param + var setup deviceSetup + setupStatus := setup.parse(class.setupBuffer) + if !setupStatus.OK() { + return setupStatus + } + + // read current device state of receiver + var state deviceStateID + s := d.status(deviceStatusDeviceState, &state) + if !s.OK() { + return statusInvalidHandle + } + + // allocate buffer for request responses + buffer := []uint8{} + length := uint32(0) + + if message.isSetup { + // verify message received contains expected setup data + if nil == message.buffer || deviceSetupSize != message.length { + return statusInvalidRequest + } + // read setup data from given message buffer + s = setup.parse(message.buffer) + if !s.OK() { + return statusInvalidRequest + } + // process message as a received setup request + if (setup.bmRequestType & specRequestTypeTypeMsk) == specRequestTypeTypeStandard { + // handle standard request + if int(setup.bRequest) <= specRequestStandardSynchFrame { + _ = d.requestStandard(&setup, &buffer, &length) + } + } else { + if 0 != setup.wLength && + (setup.bmRequestType&specRequestTypeDirMsk) == specRequestTypeDirOut { + if (setup.bmRequestType & specRequestTypeTypeClass) == specRequestTypeTypeClass { + req := deviceControlRequest{ + setup: setup, + buffer: nil, + length: uint32(setup.wLength), + isSetup: true, + } + d.classEvent(deviceClassEventClassRequest, &req) + buffer = req.buffer + length = req.length + } else if (setup.bmRequestType & specRequestTypeTypeVendor) == specRequestTypeTypeVendor { + req := deviceControlRequest{ + setup: setup, + buffer: nil, + length: uint32(setup.wLength), + isSetup: true, + } + d.event(deviceEventVendorRequest, &req) + buffer = req.buffer + length = req.length + } + if s.OK() { + return d.receive(specEndpointControl, buffer, uint32(setup.wLength)) + } + } else { + if (setup.bmRequestType & specRequestTypeTypeClass) == specRequestTypeTypeClass { + req := deviceControlRequest{ + setup: setup, + buffer: nil, + length: uint32(setup.wLength), + isSetup: true, + } + d.classEvent(deviceClassEventClassRequest, &req) + buffer = req.buffer + length = req.length + } else if (setup.bmRequestType & specRequestTypeTypeVendor) == specRequestTypeTypeVendor { + req := deviceControlRequest{ + setup: setup, + buffer: nil, + length: uint32(setup.wLength), + isSetup: true, + } + d.event(deviceEventVendorRequest, &req) + buffer = req.buffer + length = req.length + } + } + } + s = d.feedback(&setup, s, deviceControlPipeSetupStage, &buffer, &length) + } else if deviceStateAddressing == state { + // handle standard request + if int(setup.bRequest) <= specRequestStandardSynchFrame { + _ = d.requestStandard(&setup, &buffer, &length) + } + } else if 0 != message.length && 0 != setup.wLength && + (setup.bmRequestType&specRequestTypeDirMsk) == specRequestTypeDirOut { + if setup.bmRequestType&specRequestTypeTypeClass == specRequestTypeTypeClass { + req := deviceControlRequest{ + setup: setup, + buffer: message.buffer, + length: message.length, + isSetup: false, + } + d.classEvent(deviceClassEventClassRequest, &req) + } else if setup.bmRequestType&specRequestTypeTypeVendor == specRequestTypeTypeVendor { + req := deviceControlRequest{ + setup: setup, + buffer: message.buffer, + length: message.length, + isSetup: false, + } + d.event(deviceEventVendorRequest, &req) + } + s = d.feedback(&setup, s, deviceControlPipeDataStage, &buffer, &length) + } + + return s +} + +func (d *device) requestStandard( + setup *deviceSetup, buffer *[]uint8, length *uint32) status { + switch setup.bRequest { + case specRequestStandardGetStatus: + return d.requestGetStatus(setup, buffer, length) + case specRequestStandardClearFeature, specRequestStandardSetFeature: + return d.requestSetClearFeature(setup, buffer, length) + case specRequestStandardSetAddress: + return d.requestSetAddress(setup, buffer, length) + case specRequestStandardGetDescriptor: + return d.requestGetDescriptor(setup, buffer, length) + case specRequestStandardGetConfiguration: + return d.requestGetConfiguration(setup, buffer, length) + case specRequestStandardSetConfiguration: + return d.requestSetConfiguration(setup, buffer, length) + case specRequestStandardGetInterface: + return d.requestGetInterface(setup, buffer, length) + case specRequestStandardSetInterface: + return d.requestSetInterface(setup, buffer, length) + case specRequestStandardSynchFrame: + return d.requestSynchFrame(setup, buffer, length) + default: + return statusInvalidRequest + } +} + +func (d *device) requestGetStatus( + setup *deviceSetup, buffer *[]uint8, length *uint32) status { + return statusSuccess +} + +func (d *device) requestSetClearFeature( + setup *deviceSetup, buffer *[]uint8, length *uint32) status { + return statusSuccess +} + +func (d *device) requestSetAddress( + setup *deviceSetup, buffer *[]uint8, length *uint32) status { + return statusSuccess +} + +func (d *device) requestGetDescriptor( + setup *deviceSetup, buffer *[]uint8, length *uint32) status { + return statusSuccess +} + +func (d *device) requestGetConfiguration( + setup *deviceSetup, buffer *[]uint8, length *uint32) status { + return statusSuccess +} + +func (d *device) requestSetConfiguration( + setup *deviceSetup, buffer *[]uint8, length *uint32) status { + return statusSuccess +} + +func (d *device) requestGetInterface( + setup *deviceSetup, buffer *[]uint8, length *uint32) status { + return statusSuccess +} + +func (d *device) requestSetInterface( + setup *deviceSetup, buffer *[]uint8, length *uint32) status { + return statusSuccess +} + +func (d *device) requestSynchFrame( + setup *deviceSetup, buffer *[]uint8, length *uint32) status { + return statusSuccess +} + +func (d *device) classEvent(ev deviceClassEventID, param interface{}) { + if nil != d.class && nil != d.class.config { + // route the event to all class configurations + for i := range d.class.config { + _ = d.class.config[i].driver.event(ev, param) + } + } +} + +func (d *device) event(ev deviceEventID, param interface{}) { + + switch ev { + case deviceEventBusReset: + // initialize control pipes + d.initControlPipes() + + // notify all classes of a bus reset signal + for i := range d.class.config { + _ = d.class.config[i].driver.event(deviceClassEventDeviceReset, d.class) + } + } + // notify the application driver of all events + if nil != d.class.handler { + _ = d.class.handler.deviceEvent(ev, param) + } +} + +func (d *device) notify(message deviceNotification) { + + switch message.code { + case deviceNotifyBusReset: + d.notifyReset(message) + + default: + endpoint, direction := unpackEndpoint(uint8(message.code)) + + if endpoint < configDeviceMaxEndpoints { + if nil != d.endpointControl[(endpoint<<1)|direction].handler { + if message.isSetup { + d.endpointControl[0].isBusy = false + d.endpointControl[1].isBusy = false + } else { + d.endpointControl[(endpoint<<1)|direction].isBusy = false + } + // call endpoint callback + _ = d.endpointControl[(endpoint<<1)|direction].handler.controlEndpoint( + deviceEndpointControlMessage{ + buffer: message.buffer, + length: message.length, + isSetup: message.isSetup, + }, + d.endpointControl[(endpoint<<1)|direction].param, + ) + } + } + } +} + +func (d *device) notifyReset(message deviceNotification) { + + d.isResetting = true + _ = d.control(deviceControlSetDefaultStatus, nil) + + d.state = deviceStateDefault + d.deviceAddress = 0 + + for count := 0; count < 2*configDeviceMaxEndpoints; count++ { + d.endpointControl[count].handler = nil + d.endpointControl[count].param = nil + d.endpointControl[count].isBusy = false + } + + d.event(deviceEventBusReset, nil) + d.isResetting = false +} + +func (d *device) status(deviceStatus deviceStatusID, param interface{}) status { + + if nil == param { + return statusInvalidParameter + } + + switch deviceStatus { + case deviceStatusSpeed: + return d.control(deviceControlGetSpeed, param) + + case deviceStatusOTG: + return d.control(deviceControlGetOTGStatus, param) + + case deviceStatusDeviceState: + if state, ok := param.(*deviceStateID); ok { + *state = d.state + return statusSuccess + } + return statusInvalidParameter + + case deviceStatusAddress: + if address, ok := param.(*uint8); ok { + *address = d.deviceAddress + return statusSuccess + } + return statusInvalidParameter + + case deviceStatusDevice: + return d.control(deviceControlGetDeviceStatus, param) + + case deviceStatusEndpoint: + return d.control(deviceControlGetEndpointStatus, param) + + case deviceStatusSynchFrame: + return d.control(deviceControlGetSynchFrame, param) + + default: + return statusInvalidParameter + } +} + +func (d *device) setStatus(deviceStatus deviceStatusID, param interface{}) status { + + switch deviceStatus { + case deviceStatusOTG: + return d.control(deviceControlSetOTGStatus, param) + + case deviceStatusDeviceState: + if state, ok := param.(deviceStateID); ok { + d.state = state + return statusSuccess + } + return statusInvalidParameter + + case deviceStatusAddress: + if d.state != deviceStateAddressing { + if address, ok := param.(uint8); ok { + d.deviceAddress = address + d.state = deviceStateAddressing + return d.control(deviceControlPreSetDeviceAddress, d.deviceAddress) + } + return statusInvalidParameter + } + return d.control(deviceControlSetDeviceAddress, d.deviceAddress) + + case deviceStatusBusResume: + return d.control(deviceControlResume, param) + + case deviceStatusBusSleepResume: + return d.control(deviceControlSleepResume, param) + + case deviceStatusBusSuspend: + return d.control(deviceControlSuspend, param) + + case deviceStatusBusSleep: + return d.control(deviceControlSleep, param) + + default: + return statusInvalidParameter + } +} + +func (d *device) busSpeed(speed *uint8) status { + return d.status(deviceStatusSpeed, speed) +} + +func (d *device) setBusSpeed(speed uint8) status { + // TODO + return statusSuccess +} + +func (d *device) deviceDescriptor(desc *deviceGetDeviceDescriptor) status { + if int(d.port) < len(configDeviceCDCACMDescriptor) { + if nil != configDeviceCDCACMDescriptor[d.port].pDevice { + desc.buffer = *configDeviceCDCACMDescriptor[d.port].pDevice + desc.length = specDescriptorLengthDevice + return statusSuccess + } + } + return statusInvalidHandle +} + +func (d *device) configurationDescriptor(desc *deviceGetConfigurationDescriptor) status { + if int(d.port) < len(configDeviceCDCACMDescriptor) { + if nil != configDeviceCDCACMDescriptor[d.port].pConfig { + desc.buffer = *configDeviceCDCACMDescriptor[d.port].pConfig + desc.length = uint32(descriptorConfigurationCDCACMSize) + return statusSuccess + } + } + return statusInvalidHandle +} + +func (d *device) stringDescriptor(desc *deviceGetStringDescriptor) status { + if 0 == desc.stringIndex { + desc.buffer = descriptorStringCDCACMLanguage + desc.length = uint32(len(descriptorStringCDCACMLanguage)) + return statusSuccess + } + if int(d.port) < len(configDeviceCDCACMDescriptor) { + for _, lang := range configDeviceCDCACMDescriptor[d.port].language { + if desc.languageID == lang.ident { + if int(desc.stringIndex) < len(lang.pString) { + desc.buffer = *lang.pString[desc.stringIndex] + desc.length = uint32(len(desc.buffer)) + } + } + } + } + return statusInvalidRequest +} + +func (s deviceSetup) pack() deviceSetupBitmap { + return deviceSetupBitmap( + ((uint64(s.bmRequestType) & 0xFF) << 0) | // uint8 // 8 (bits) + ((uint64(s.bRequest) & 0xFF) << 8) | // uint8 // 8 + ((uint64(s.wValue) & 0xFFFF) << 16) | // uint16 // 16 + ((uint64(s.wIndex) & 0xFFFF) << 32) | // uint16 // 16 + ((uint64(s.wLength) & 0xFFFF) << 48)) // uint16 // 16 (= 64 bits) +} + +func (s deviceSetup) bytes() deviceSetupBuffer { + return [deviceSetupSize]uint8{ + s.bmRequestType, + s.bRequest, + // for 16-bit words: low-byte is at index N, high-byte is at N+1 + uint8(s.wValue), uint8(s.wValue >> 8), + uint8(s.wIndex), uint8(s.wIndex >> 8), + uint8(s.wLength), uint8(s.wLength >> 8), + } +} + +func (s deviceSetupBitmap) bytes() deviceSetupBuffer { + return [deviceSetupSize]uint8{ + uint8(s >> 0), uint8(s >> 8), uint8(s >> 16), uint8(s >> 24), + uint8(s >> 32), uint8(s >> 40), uint8(s >> 48), uint8(s >> 56), + } +} + +func (s *deviceSetup) parse(buffer []uint8) status { + if nil == buffer || len(buffer) < deviceSetupSize { + return statusInvalidParameter + } + s.bmRequestType = buffer[0] + s.bRequest = buffer[1] + s.wValue = (uint16(buffer[3]) << 8) | uint16(buffer[2]) + s.wIndex = (uint16(buffer[5]) << 8) | uint16(buffer[4]) + s.wLength = (uint16(buffer[7]) << 8) | uint16(buffer[6]) + return statusSuccess +} diff --git a/src/machine/usb/device_cdc.go b/src/machine/usb/device_cdc.go new file mode 100644 index 000000000..fadec6193 --- /dev/null +++ b/src/machine/usb/device_cdc.go @@ -0,0 +1,135 @@ +package usb + +const ( + // Communication Class + deviceCDCCommClass = 0x02 + // Data Class + deviceCDCDataClass = 0x0A + + // Communication Class SubClass Codes + deviceCDCDirectLineControlModel = 0x01 + deviceCDCAbstractControlModel = 0x02 + deviceCDCTelephoneControlModel = 0x03 + deviceCDCMultiChannelControlModel = 0x04 + deviceCDCCAPIControlMopdel = 0x05 + deviceCDCEthernetNetworkingControlModel = 0x06 + deviceCDCATMNetworkingControlModel = 0x07 + deviceCDCWirelessHandsetControlModel = 0x08 + deviceCDCDeviceManagement = 0x09 + deviceCDCMobileDirectLineModel = 0x0A + deviceCDCOBEX = 0x0B + deviceCDCEthernetEmulationModel = 0x0C + + // Communication Class Protocol Codes + deviceCDCNoClassSpecificProtocol = 0x00 // also for Data Class Protocol Code + deviceCDCAT250Protocol = 0x01 + deviceCDCATPCCA101Protocol = 0x02 + deviceCDCATPCCA101AnnexO = 0x03 + deviceCDCATGSM707 = 0x04 + deviceCDCAT3GPP27007 = 0x05 + deviceCDCATTIACDMA = 0x06 + deviceCDCEthernetEmulationProtocol = 0x07 + deviceCDCExternalProtocol = 0xFE + deviceCDCVendorSpecific = 0xFF // also for Data Class Protocol Code + + // Data Class Protocol Codes + deviceCDCPyhsicalInterfaceProtocol = 0x30 + deviceCDCHDLCProtocol = 0x31 + deviceCDCTransparentProtocol = 0x32 + deviceCDCManagementProtocol = 0x50 + deviceCDCDataLinkQ931Protocol = 0x51 + deviceCDCDataLinkQ921Protocol = 0x52 + deviceCDCDataCompressionV42BIS = 0x90 + deviceCDCEuroISDNProtocol = 0x91 + deviceCDCRateAdaptionISDNV24 = 0x92 + deviceCDCCAPICommands = 0x93 + deviceCDCHostBasedDriver = 0xFD + deviceCDCUnitFunctional = 0xFE + + // Descriptor SubType in Communications Class Functional Descriptors + deviceCDCHeaderFuncDesc = 0x00 + deviceCDCCallManagementFuncDesc = 0x01 + deviceCDCAbstractControlFuncDesc = 0x02 + deviceCDCDirectLineFuncDesc = 0x03 + deviceCDCTelephoneRingerFuncDesc = 0x04 + deviceCDCTelephoneReportFuncDesc = 0x05 + deviceCDCUnionFuncDesc = 0x06 + deviceCDCCountrySelectFuncDesc = 0x07 + deviceCDCTelephoneModesFuncDesc = 0x08 + deviceCDCTerminalFuncDesc = 0x09 + deviceCDCNetworkChannelFuncDesc = 0x0A + deviceCDCProtocolUnitFuncDesc = 0x0B + deviceCDCExtensionUnitFuncDesc = 0x0C + deviceCDCMultiChannelFuncDesc = 0x0D + deviceCDCCAPIControlFuncDesc = 0x0E + deviceCDCEthernetNetworkingFuncDesc = 0x0F + deviceCDCATMNetworkingFuncDesc = 0x10 + deviceCDCWirelessControlFuncDesc = 0x11 + deviceCDCMobileDirectLineFuncDesc = 0x12 + deviceCDCMDLMDetailFuncDesc = 0x13 + deviceCDCDeviceManagementFuncDesc = 0x14 + deviceCDCOBEXFuncDesc = 0x15 + deviceCDCCommandSetFuncDesc = 0x16 + deviceCDCCommandSetDetailFuncDesc = 0x17 + deviceCDCTelephoneControlFuncDesc = 0x18 + deviceCDCOBEXServiceIDFuncDesc = 0x19 + + deviceCDCRequestSendEncapsulatedCommand = 0x00 // CDC request SEND_ENCAPSULATED_COMMAND + deviceCDCRequestGetEncapsulatedResponse = 0x01 // CDC request GET_ENCAPSULATED_RESPONSE + deviceCDCRequestSetCommFeature = 0x02 // CDC request SET_COMM_FEATURE + deviceCDCRequestGetCommFeature = 0x03 // CDC request GET_COMM_FEATURE + deviceCDCRequestClearCommFeature = 0x04 // CDC request CLEAR_COMM_FEATURE + deviceCDCRequestSetAuxLineState = 0x10 // CDC request SET_AUX_LINE_STATE + deviceCDCRequestSetHookState = 0x11 // CDC request SET_HOOK_STATE + deviceCDCRequestPulseSetup = 0x12 // CDC request PULSE_SETUP + deviceCDCRequestSendPulse = 0x13 // CDC request SEND_PULSE + deviceCDCRequestSetPulseTime = 0x14 // CDC request SET_PULSE_TIME + deviceCDCRequestRingAuxJack = 0x15 // CDC request RING_AUX_JACK + deviceCDCRequestSetLineCoding = 0x20 // CDC request SET_LINE_CODING + deviceCDCRequestGetLineCoding = 0x21 // CDC request GET_LINE_CODING + deviceCDCRequestSetControlLineState = 0x22 // CDC request SET_CONTROL_LINE_STATE + deviceCDCRequestSendBreak = 0x23 // CDC request SEND_BREAK + deviceCDCRequestSetRingerParams = 0x30 // CDC request SET_RINGER_PARAMS + deviceCDCRequestGetRingerParams = 0x31 // CDC request GET_RINGER_PARAMS + deviceCDCRequestSetOperationParam = 0x32 // CDC request SET_OPERATION_PARAM + deviceCDCRequestGetOperationParam = 0x33 // CDC request GET_OPERATION_PARAM + deviceCDCRequestSetLineParams = 0x34 // CDC request SET_LINE_PARAMS + deviceCDCRequestGetLineParams = 0x35 // CDC request GET_LINE_PARAMS + deviceCDCRequestDialDigits = 0x36 // CDC request DIAL_DIGITS + deviceCDCRequestSetUnitParameter = 0x37 // CDC request SET_UNIT_PARAMETER + deviceCDCRequestGetUnitParameter = 0x38 // CDC request GET_UNIT_PARAMETER + deviceCDCRequestClearUnitParameter = 0x39 // CDC request CLEAR_UNIT_PARAMETER + deviceCDCRequestSetEthernetMulticastFilters = 0x40 // CDC request SET_ETHERNET_MULTICAST_FILTERS + deviceCDCRequestSetEthernetPowPatternFilter = 0x41 // CDC request SET_ETHERNET_POW_PATTER_FILTER + deviceCDCRequestGetEthernetPowPatternFilter = 0x42 // CDC request GET_ETHERNET_POW_PATTER_FILTER + deviceCDCRequestSetEthernetPacketFilter = 0x43 // CDC request SET_ETHERNET_PACKET_FILTER + deviceCDCRequestGetEthernetStatistic = 0x44 // CDC request GET_ETHERNET_STATISTIC + deviceCDCRequestSetATMDataFormat = 0x50 // CDC request SET_ATM_DATA_FORMAT + deviceCDCRequestGetATMDeviceStatistics = 0x51 // CDC request GET_ATM_DEVICE_STATISTICS + deviceCDCRequestSetATMDefaultVC = 0x52 // CDC request SET_ATM_DEFAULT_VC + deviceCDCRequestGetATMVCStatistics = 0x53 // CDC request GET_ATM_VC_STATISTICS + deviceCDCRequestMDLMSpecificRequestsMask = 0x7F // CDC request MDLM_SPECIFIC_REQUESTS_MASK + + deviceCDCNotifyNetworkConnection = 0x00 // CDC notify NETWORK_CONNECTION + deviceCDCNotifyResponseAvail = 0x01 // CDC notify RESPONSE_AVAIL + deviceCDCNotifyAuxJackHookState = 0x08 // CDC notify AUX_JACK_HOOK_STATE + deviceCDCNotifyRingDetect = 0x09 // CDC notify RING_DETECT + deviceCDCNotifySerialState = 0x20 // CDC notify SERIAL_STATE + deviceCDCNotifyCallStateChange = 0x28 // CDC notify CALL_STATE_CHANGE + deviceCDCNotifyLineStateChange = 0x29 // CDC notify LINE_STATE_CHANGE + deviceCDCNotifyConnectionSpeedChange = 0x2A // CDC notify CONNECTION_SPEED_CHANGE + + deviceCDCFeatureAbstractState = 0x01 // CDC feature select ABSTRACT_STATE + deviceCDCFeatureCountrySetting = 0x02 // CDC feature select COUNTRY_SETTING + + deviceCDCControlSigBitmapCarrierActivation = 0x02 // CDC control signal CARRIER_ACTIVATION + deviceCDCControlSigBitmapDTEPresence = 0x01 // CDC control signal DTE_PRESENCE + deviceCDCUARTStateRxCarrier = 0x01 // UART state RX_CARRIER + deviceCDCUARTStateTxCarrier = 0x02 // UART state TX_CARRIER + deviceCDCUARTStateBreak = 0x04 // UART state BREAK + deviceCDCUARTStateRingSignal = 0x08 // UART state RING_SIGNAL + deviceCDCUARTStateFraming = 0x10 // UART state FRAMING + deviceCDCUARTStateParity = 0x20 // UART state PARITY + deviceCDCUARTStateOverrun = 0x40 // UART state OVERRUN + +) diff --git a/src/machine/usb/device_cdc_acm.go b/src/machine/usb/device_cdc_acm.go new file mode 100644 index 000000000..0b0052e1c --- /dev/null +++ b/src/machine/usb/device_cdc_acm.go @@ -0,0 +1,690 @@ +package usb + +import "bytes" + +type ( + deviceCDCACMEventID uint8 + + // deviceCDCACMConfig defines the platform-specific configuration settings for + // a single CDC-ACM device class on a given USB port. + // + // To connect the USB CDC-ACM device driver to a particular platform, there + // should be one instance for each USB CDC-ACM port, defined in global array + // configDeviceCDCACM, indexed by USB port. + deviceCDCACMConfig struct { + // USB configuration + interfaceSpeed uint8 // Low/Full/High-speed + // CDC-ACM Serial line configuration + lineCodingSize uint32 // Size of line-coding message + lineCodingBaudRate uint32 // Data terminal rate + lineCodingCharFormat uint32 // Character format + lineCodingParityType uint32 // Parity type + lineCodingDataBits uint32 // Data word size + // CDC-ACM Communication/control interface + commInterfaceIndex uint8 // Communication/control interface index + commInterruptInEndpoint uint8 // Interrupt input endpoint index (address) + commInterruptInPacketSize uint16 // Interrupt input packet size + commInterruptInInterval uint8 // Interrupt input interval + // CDC-ACM Data interface + dataInterfaceIndex uint8 // Data interface index + dataBulkInEndpoint uint8 // Bulk input endpoint index (address) + dataBulkInPacketSize uint16 // Bulk input packet size + dataBulkOutEndpoint uint8 // Bulk output endpoint index (address) + dataBulkOutPacketSize uint16 // Bulk output packet size + } + + deviceCDCACM struct { + device *device // The handle of the USB device. + config *deviceClassConfig // The class configure structure. + info deviceCDCACMInfo // The ACM serial state. + comm *deviceInterface // The CDC communication interface handle. + data *deviceInterface // The CDC data interface handle. + sendBuffer *deviceCDCACMDataBuffer // Pointer to the global send buffer + recvBuffer *deviceCDCACMDataBuffer // Pointer to the global receive buffer + bulkIn deviceCDCACMPipe // The bulk in pipe for sending packet to host. + bulkOut deviceCDCACMPipe // The bulk out pipe for receiving packet from host. + interruptIn deviceCDCACMPipe // The interrupt in pipe for notifying the device state to host. + interfaceNumber uint8 // The current interface number. + alternate uint8 // The alternate setting value of the interface. + hasSentState bool // The device has primed the state in interrupt pipe + speed uint8 // Speed of USB device (Full/Low/High) + lineCodingSize uint32 // Size of line-coding message + baudRate uint32 // Data terminal rate + charFormat uint32 // Character format + parityType uint32 // Parity type + dataBits uint32 // Data word size + sendSize uint32 // Number of bytes scheduled in global send buffer + recvSize uint32 // Number of bytes scheduled in global receive buffer + } + + deviceCDCACMDataBuffer [configDeviceBufferSize]uint8 + deviceCDCACMAlternateList [configDeviceCDCACMInterfaceCount]uint16 + deviceCDCACMSerialState [deviceCDCACMSerialStateSize]uint8 + + deviceCDCACMRequestParam struct { + buffer *[]uint8 // The pointer to the address of the buffer for CDC class request. + length *uint32 // The pointer to the length of the buffer for CDC class request. + interfaceIndex uint16 // The interface index of the setup packet. + setupValue uint16 // The wValue field of the setup packet. + isSetup bool // The flag indicates if it is a setup packet + } + + deviceCDCACMPipe struct { + pipeDataBuffer []uint8 // pipe data buffer backup when stall + pipeDataLen uint32 // pipe data length backup when stall + pipeStall bool // pipe is stall + ep uint8 // The endpoint number of the pipe. + isBusy bool // The pipe is transferring packet + } + + deviceCDCACMInfo struct { + serialState deviceCDCACMSerialState // Serial state buffer of the CDC device to notify the serial state to host. + dtePresent bool // A flag to indicate whether DTE is present. + breakDuration uint16 // Length of time in milliseconds of the break signal + dteStatus uint8 // Status of data terminal equipment + currentInterface uint8 // Current interface index. + uartState uint16 // UART state of the CDC device. + } +) + +const ( + deviceCDCACMEventSendResponse deviceCDCACMEventID = iota + 1 // This event indicates the bulk send transfer is complete or cancelled etc. + deviceCDCACMEventRecvResponse // This event indicates the bulk receive transfer is complete or cancelled etc.. + deviceCDCACMEventSerialStateNotify // This event indicates the serial state has been sent to the host. + deviceCDCACMEventSendEncapsulatedCommand // This event indicates the device received the SEND_ENCAPSULATED_COMMAND request. + deviceCDCACMEventGetEncapsulatedResponse // This event indicates the device received the GET_ENCAPSULATED_RESPONSE request. + deviceCDCACMEventSetCommFeature // This event indicates the device received the SET_COMM_FEATURE request. + deviceCDCACMEventGetCommFeature // This event indicates the device received the GET_COMM_FEATURE request. + deviceCDCACMEventClearCommFeature // This event indicates the device received the CLEAR_COMM_FEATURE request. + deviceCDCACMEventGetLineCoding // This event indicates the device received the GET_LINE_CODING request. + deviceCDCACMEventSetLineCoding // This event indicates the device received the SET_LINE_CODING request. + deviceCDCACMEventSetControlLineState // This event indicates the device received the SET_CONTRL_LINE_STATE request. + deviceCDCACMEventSendBreak // This event indicates the device received the SEND_BREAK request. + + deviceCDCACMRequestNotify = 0xA1 + + deviceCDCACMInfoNotifyPacketSize = 8 // (bytes) + deviceCDCACMInfoUARTBitmapSize = 2 // + deviceCDCACMSerialStateSize = deviceCDCACMInfoNotifyPacketSize + deviceCDCACMInfoUARTBitmapSize +) + +var ( + deviceCDCACMDataSendBuffer = [configDeviceCDCACMCount]deviceCDCACMDataBuffer{} + deviceCDCACMDataRecvBuffer = [configDeviceCDCACMCount]deviceCDCACMDataBuffer{} + + deviceCDCACMLineCoding = [configDeviceCDCACMCount][][]uint8{ + { // USB CDC-ACM port 0 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // configuration index 1 + }, + } + + deviceCDCACMConfigInstance = [configDeviceCDCACMCount][]deviceClassConfig{ + {{ + driver: &deviceCDCACM{}, + info: deviceClassInfo{ + classID: deviceClassCDC, + interfaceList: []deviceClassInterface{ + { // USB CDC-ACM communications/control interface + classCode: deviceCDCCommClass, + subclassCode: deviceCDCAbstractControlModel, + protocolCode: deviceCDCNoClassSpecificProtocol, + deviceInterface: []deviceInterface{{ + alternateSetting: 0, + endpoint: []deviceEndpoint{ + {transferType: specEndpointInterrupt}, + }, + }}, + }, + { // USB CDC-ACM serial data interface + classCode: deviceCDCDataClass, + subclassCode: 0x00, + protocolCode: deviceCDCNoClassSpecificProtocol, + deviceInterface: []deviceInterface{{ + alternateSetting: 0, + endpoint: []deviceEndpoint{ + {transferType: specEndpointBulk}, + {transferType: specEndpointBulk}, + }, + }}, + }, + }, + }, + }}, + } + + deviceCDCACMBufferInvalid32 = leU32(0xFFFFFFFF) +) + +func (acm *deviceCDCACM) init(device *device, config *deviceClassConfig, id uint8) status { + + // initialize our associated object references + acm.device = device + acm.config = config + + // grab references to the global data buffers + acm.sendBuffer = &deviceCDCACMDataSendBuffer[device.port] + acm.recvBuffer = &deviceCDCACMDataRecvBuffer[device.port] + + // initialize line-coding details from global configuration + acm.speed = configDeviceCDCACM[device.port][id-1].interfaceSpeed + acm.lineCodingSize = configDeviceCDCACM[device.port][id-1].lineCodingSize + acm.baudRate = configDeviceCDCACM[device.port][id-1].lineCodingBaudRate + acm.charFormat = configDeviceCDCACM[device.port][id-1].lineCodingCharFormat + acm.parityType = configDeviceCDCACM[device.port][id-1].lineCodingParityType + acm.dataBits = configDeviceCDCACM[device.port][id-1].lineCodingDataBits + + lineCoding := deviceCDCACMLineCoding[device.port][id-1] + lineCoding[0] = uint8(acm.baudRate >> 0) + lineCoding[1] = uint8(acm.baudRate >> 8) + lineCoding[2] = uint8(acm.baudRate >> 16) + lineCoding[3] = uint8(acm.baudRate >> 24) + lineCoding[4] = uint8(acm.charFormat) + lineCoding[5] = uint8(acm.parityType) + lineCoding[6] = uint8(acm.dataBits) + + // initialize remaining state data + acm.alternate = 0xFF + + return statusSuccess +} + +func (acm *deviceCDCACM) deinit() status { + return statusSuccess +} + +func (acm *deviceCDCACM) initEndpoints() status { + + // find the communication/control interface in our CDC-ACM configuration list + acm.interfaceNumber, acm.comm = acm.findInterface(deviceCDCCommClass) + + // verify we found a valid communication/control interface + if nil == acm.comm { + return statusInvalidHandle + } + + // initialize all of the communication/control input endpoints + if s := acm.initInterfaceEndpoints(specIn, specEndpointInterrupt); !s.OK() { + return s + } + + // find the data interface in our CDC-ACM configuration list + _, acm.data = acm.findInterface(deviceCDCDataClass) + + // verify we found a valid data interface + if nil == acm.data { + return statusInvalidHandle + } + + // initialize all of the data input endpoints + if s := acm.initInterfaceEndpoints(specIn, specEndpointBulk); !s.OK() { + return s + } + // initialize all of the data output endpoints + if s := acm.initInterfaceEndpoints(specOut, specEndpointBulk); !s.OK() { + return s + } + + return statusSuccess +} + +func (acm *deviceCDCACM) initInterfaceEndpoints(direction, transferType uint8) status { + + // select the relevant fields and callbacks for our given interface type + pipe, deviceInterface := acm.endpointInterface(direction, transferType) + if nil == deviceInterface { + return statusInvalidParameter + } + + // initialize each endpoint with given direction and transfer type + for _, ep := range deviceInterface.endpoint { + num, dir := unpackEndpoint(ep.address) + if dir == direction && ep.transferType == transferType { + pipe.pipeDataBuffer = deviceCDCACMBufferInvalid32 + pipe.pipeDataLen = 0 + pipe.pipeStall = false + pipe.ep = num + pipe.isBusy = false + + if s := acm.device.initEndpoint(&deviceEndpointConfig{ + maxPacketSize: ep.maxPacketSize, + address: ep.address, + transferType: ep.transferType, + zlt: 0, + interval: ep.interval, + }, acm, pipe); !s.OK() { + return s + } + } + } + + return statusSuccess +} + +func (acm *deviceCDCACM) deinitEndpoints() status { + + if nil == acm.device { + return statusInvalidHandle + } + + if nil != acm.comm { + for i := range acm.comm.endpoint { + _ = acm.device.deinitEndpoint(acm.comm.endpoint[i].address) + } + acm.comm = nil + } + if nil != acm.data { + for i := range acm.data.endpoint { + _ = acm.device.deinitEndpoint(acm.data.endpoint[i].address) + } + acm.data = nil + } + + return statusSuccess +} + +func (acm *deviceCDCACM) event(event deviceClassEventID, param interface{}) (s status) { + + // assume success unless error condition deliberately detected + s = statusSuccess + + switch event { + case deviceClassEventDeviceReset: + // bus reset, clear the selected configuration + acm.config = nil + + case deviceClassEventSetConfiguration: + if id, ok := param.(uint8); ok { + // configuration index is 1-based, meaning configuration 0 is invalid + if 0 == id || int(id) > len(acm.device.class.config) { + return statusInvalidParameter + } + if &acm.device.class.config[id-1] == acm.config { + break // configuration already selected + } + // de-initialize endpoints of current configuration + if s = acm.deinitEndpoints(); !s.OK() { + break + } + // select new configuration, reset alternate setting + acm.config = &acm.device.class.config[id-1] + acm.alternate = 0 + // initialize endpoints of new configuration + if s = acm.initEndpoints(); !s.OK() { + break + } + } else { + // unexpected parameter, should be uint8 (configuration index) + s = statusInvalidParameter + } + + case deviceClassEventSetInterface: + if interfaceAlternate, ok := param.(uint16); ok { + alternate := uint8(interfaceAlternate & 0xFF) + if acm.interfaceNumber != uint8(interfaceAlternate>>8) { + break // requested alternate from interface different than current + } + if alternate == acm.alternate { + break // requested alternate same as current + } + // de-initialize endpoints of current configuration + if s = acm.deinitEndpoints(); !s.OK() { + break + } + // select new alternate + acm.alternate = alternate + // initialize endpoints of new configuration + if s = acm.initEndpoints(); !s.OK() { + break + } + } else { + // unexpected parameter, should be uint16: interface (hi), alternate (lo) + s = statusInvalidParameter + } + + case deviceClassEventSetEndpointHalt: + // verify parameter and fields + if address, ok := param.(uint8); ok { + if nil != acm.config && nil != acm.comm && nil != acm.data { + // check if given endpoint is a communication/control endpoint + for _, e := range acm.comm.endpoint { + if address == e.address { + // found endpoint, set stall flag + acm.interruptIn.pipeStall = true + // notify device + c := acm.device.control(deviceControlEndpointStall, address) + if s.OK() && !c.OK() { + s = c + } + } + } + // check if given endpoint is a data endpoint + for _, e := range acm.data.endpoint { + if address == e.address { + _, direction := unpackEndpoint(address) + // found endpoint, set stall flag + if specIn == direction { + acm.bulkIn.pipeStall = true + } else { + acm.bulkOut.pipeStall = true + } + // notify device + c := acm.device.control(deviceControlEndpointStall, address) + if s.OK() && !c.OK() { + s = c + } + } + } + } else { + s = statusInvalidHandle + } + } else { + // unexpected parameter, should be uint8 (endpoint address) + s = statusInvalidParameter + } + + case deviceClassEventClearEndpointHalt: + // verify parameter and fields + if address, ok := param.(uint8); ok { + if nil != acm.config && nil != acm.comm && nil != acm.data { + // check if given endpoint is a communication/control endpoint + for _, e := range acm.comm.endpoint { + if address == e.address { + // found endpoint, notify device + c := acm.device.control(deviceControlEndpointUnstall, address) + if s.OK() && !c.OK() { + s = c + } + _, direction := unpackEndpoint(address) + if specIn == direction { + // flush any buffered data written to the stalled endpoint + if acm.interruptIn.pipeStall { + // clear stall flag + acm.interruptIn.pipeStall = false + // verify the buffer has valid data + if !bytes.Equal(acm.interruptIn.pipeDataBuffer, deviceCDCACMBufferInvalid32) { + // transmit + u := acm.device.send( + acm.interruptIn.ep, + acm.interruptIn.pipeDataBuffer, + acm.interruptIn.pipeDataLen) + if !u.OK() { + // notify upper layer driver of communication/control event + _ = acm.controlEndpoint( + deviceEndpointControlMessage{ + buffer: acm.interruptIn.pipeDataBuffer, + length: acm.interruptIn.pipeDataLen, + isSetup: false, + }, + &acm.interruptIn, + ) + if s.OK() { + s = u + } + } + // clear the stalled endpoint buffer + acm.interruptIn.pipeDataBuffer = deviceCDCACMBufferInvalid32 + acm.interruptIn.pipeDataLen = 0 + } + } + } + } + } + // check if given endpoint is a data endpoint + for _, e := range acm.data.endpoint { + if address == e.address { + // found endpoint, notify device + c := acm.device.control(deviceControlEndpointUnstall, address) + if s.OK() && !c.OK() { + s = c + } + // check if endpoint is an input or output + _, direction := unpackEndpoint(address) + if specIn == direction { + // flush any buffered data written to the stalled endpoint + if acm.bulkIn.pipeStall { + // clear stall flag + acm.bulkIn.pipeStall = false + // verify the buffer has valid data + if !bytes.Equal(acm.bulkIn.pipeDataBuffer, deviceCDCACMBufferInvalid32) { + // transmit + u := acm.device.send( + acm.bulkIn.ep, + acm.bulkIn.pipeDataBuffer, + acm.bulkIn.pipeDataLen) + if !u.OK() { + // notify upper layer driver of data input event + _ = acm.controlEndpoint( + deviceEndpointControlMessage{ + buffer: acm.bulkIn.pipeDataBuffer, + length: acm.bulkIn.pipeDataLen, + isSetup: false, + }, + &acm.bulkIn, + ) + if s.OK() { + s = u + } + } + // clear the stalled endpoint buffer + acm.bulkIn.pipeDataBuffer = deviceCDCACMBufferInvalid32 + acm.bulkIn.pipeDataLen = 0 + } + } + } else { + // flush any buffered data read from the stalled endpoint + if acm.bulkOut.pipeStall { + // clear stall flag + acm.bulkOut.pipeStall = false + // verify the buffer has valid data + if !bytes.Equal(acm.bulkOut.pipeDataBuffer, deviceCDCACMBufferInvalid32) { + // receive + u := acm.device.receive( + acm.bulkOut.ep, + acm.bulkOut.pipeDataBuffer, + acm.bulkOut.pipeDataLen) + if !u.OK() { + // notify upper layer driver of data output event + _ = acm.controlEndpoint( + deviceEndpointControlMessage{ + buffer: acm.bulkOut.pipeDataBuffer, + length: acm.bulkOut.pipeDataLen, + isSetup: false, + }, + &acm.bulkOut, + ) + if s.OK() { + s = u + } + } + // clear the stalled endpoint buffer + acm.bulkOut.pipeDataBuffer = deviceCDCACMBufferInvalid32 + acm.bulkOut.pipeDataLen = 0 + } + } + } + } + } + } else { + s = statusInvalidHandle + } + } else { + // unexpected parameter, should be uint8 (endpoint address) + s = statusInvalidParameter + } + case deviceClassEventClassRequest: + // verify parameter and fields + if request, ok := param.(*deviceControlRequest); ok { + // verify requested interface is receiver's interface and request is CDC + if (request.setup.wIndex&0xFF) != uint16(acm.interfaceNumber) || + (request.setup.bmRequestType&specRequestTypeTypeMsk) != specRequestTypeTypeClass { + // construct standard parameter to be passed on to upper layer drivevr + param := deviceCDCACMRequestParam{ + buffer: &request.buffer, + length: &request.length, + interfaceIndex: request.setup.wIndex, + setupValue: request.setup.wValue, + isSetup: request.isSetup, + } + // translate request code to event code + var event deviceCDCACMEventID + switch request.setup.bRequest { + case deviceCDCRequestSendEncapsulatedCommand: + event = deviceCDCACMEventSendEncapsulatedCommand + case deviceCDCRequestGetEncapsulatedResponse: + event = deviceCDCACMEventGetEncapsulatedResponse + case deviceCDCRequestSetCommFeature: + event = deviceCDCACMEventSetCommFeature + case deviceCDCRequestGetCommFeature: + event = deviceCDCACMEventGetCommFeature + case deviceCDCRequestClearCommFeature: + event = deviceCDCACMEventClearCommFeature + case deviceCDCRequestGetLineCoding: + event = deviceCDCACMEventGetLineCoding + case deviceCDCRequestSetLineCoding: + event = deviceCDCACMEventSetLineCoding + case deviceCDCRequestSetControlLineState: + event = deviceCDCACMEventSetControlLineState + case deviceCDCRequestSendBreak: + event = deviceCDCACMEventSendBreak + default: + s = statusInvalidRequest + } + // notify request to upper layer driver + if nil != acm.device && nil != acm.device.class && + nil != acm.device.class.handler { + acm.device.class.handler.classEvent(uint32(event), param) + } + } else { + s = statusInvalidRequest + } + } else { + s = statusInvalidParameter + } + } + + return s +} + +func (acm *deviceCDCACM) send(ep uint8, buffer []uint8, length uint32) status { + var pipe *deviceCDCACMPipe + // select which endpoint pipe to write into + switch ep { + case acm.interruptIn.ep: + pipe = &acm.interruptIn + case acm.bulkIn.ep: + pipe = &acm.bulkIn + default: + return statusInvalidParameter + } + if pipe.isBusy { + return statusBusy + } + // set pipe busy flag + pipe.isBusy = true + // check if pipe stall flag is set + if pipe.pipeStall { + // write data to pipe buffer + pipe.pipeDataBuffer = buffer + pipe.pipeDataLen = length + return statusSuccess + } + // pass data to device for transmission + if s := acm.device.send(ep, buffer, length); !s.OK() { + pipe.isBusy = false + return s + } + return statusSuccess +} + +func (acm *deviceCDCACM) receive(ep uint8, buffer []uint8, length uint32) status { + var pipe *deviceCDCACMPipe + // select which endpoint pipe to read from + switch ep { + case acm.bulkOut.ep: + pipe = &acm.bulkOut + default: + return statusInvalidParameter + } + if pipe.isBusy { + return statusBusy + } + // set pipe busy flag + pipe.isBusy = true + // check if pipe stall flag is set + if pipe.pipeStall { + // write data to pipe buffer + pipe.pipeDataBuffer = buffer + pipe.pipeDataLen = length + return statusSuccess + } + // pass data to device for reception + if s := acm.device.receive(ep, buffer, length); !s.OK() { + pipe.isBusy = false + return s + } + return statusSuccess +} + +// findInterface returns the interface number and deviceInterface from the +// receiver's CDC-ACM configuration list whose classCode and alternateSetting +// equals the given classCode and receiver's current alternateSetting; +// otherwise, no such interface is found, returns 0 and nil. +func (acm *deviceCDCACM) findInterface(classCode uint8) (uint8, *deviceInterface) { + if nil == acm.device || nil == acm.config { + return 0, nil + } + for c := range acm.config.info.interfaceList { + dci := &acm.config.info.interfaceList[c] + if classCode == dci.classCode { + for d := range dci.deviceInterface { + if acm.alternate == dci.deviceInterface[d].alternateSetting { + return dci.interfaceNumber, &dci.deviceInterface[d] + } + } + } + } + return 0, nil +} + +func (acm *deviceCDCACM) endpointInterface( + direction, transferType uint8) (*deviceCDCACMPipe, *deviceInterface) { + + switch transferType { + case specEndpointInterrupt: + switch direction { + case specIn: + return &acm.interruptIn, acm.comm + } + case specEndpointBulk: + switch direction { + case specIn: + return &acm.bulkIn, acm.data + case specOut: + return &acm.bulkOut, acm.data + } + } + return nil, nil +} + +func (acm *deviceCDCACM) controlEndpoint( + message deviceEndpointControlMessage, param interface{}) status { + + if pipe, ok := param.(*deviceCDCACMPipe); ok { + var event deviceCDCACMEventID + switch pipe { + case &acm.interruptIn: + event = deviceCDCACMEventSerialStateNotify + case &acm.bulkIn: + event = deviceCDCACMEventSendResponse + case &acm.bulkOut: + event = deviceCDCACMEventRecvResponse + } + pipe.isBusy = false + if nil != acm.device && nil != acm.device.class && + nil != acm.device.class.handler { + acm.device.class.handler.classEvent(uint32(event), message) + return statusSuccess + } + } + return statusInvalidParameter +} diff --git a/src/machine/usb/device_controller.go b/src/machine/usb/device_controller.go new file mode 100644 index 000000000..bf30719c2 --- /dev/null +++ b/src/machine/usb/device_controller.go @@ -0,0 +1,288 @@ +package usb + +import "unsafe" + +// deviceController provides hardware abstraction over a platform's physical +// USB device port controller (e.g., an EHCI-compliant peripheral). +// +// To connect the USB 2.0 device driver stack to a particular platform, the +// following method must be implemented: +// +// func (d *device) initController() deviceController +// +// This method shall return an implementation of deviceController, or nil if a +// controller cannot be allocated for the given port. +type ( + deviceController interface { + init() status // Controller initialization + deinit() status // Controller de-initialization + enable(enable bool) status // Controller enable runtime + interrupt() // Controller interrupt handler + process() status // Controller process interrupts + send(address uint8, buffer []uint8, length uint32) status // Controller send data + receive(address uint8, buffer []uint8, length uint32) status // Controller receive data + cancel(address uint8) status // Controller cancel transfer + control(command deviceControlID, param interface{}) status // Controller device control + critical(enter bool) status // Controller critical section + } + + // deviceControllerInterruptQueue represents a fixed-length, circular queue + // (FIFO) of interrupts. + // + // The enqueue and dequeue operations (methods enq and deq, respectively) are + // NOT interrupt-/thread-safe. The user must protect against race conditions + // using appropriate resources for their system. For example, the interface + // method (deviceController).critical(bool) implements the concept of critical + // sections, which could be used to prevent concurrent accesses. + // This restriction also applies to the higher-level methods fill and drain. + deviceControllerInterruptQueue struct { + queue [configInterruptQueueSize]uintptr // circular queue + head int + count int + } + + deviceControllerCapabilitiesBitmap uint32 + deviceControllerCapabilities struct { + reserved1 uint16 // 15 (bits) + ios uint8 // 1 + maxPacketSize uint16 // 11 + reserved2 uint8 // 2 + zlt uint8 // 1 + mult uint8 // 2 (= 32 bits) + } + + deviceControllerDTDTokenBitmap uint32 + deviceControllerDTDToken struct { + status uint8 // 8 (bits) + reserved1 uint8 // 2 + multiplierOverride uint8 // 2 + reserved2 uint8 // 3 + ioc uint8 // 1 + totalBytes uint16 // 15 + reserved3 uint8 // 1 (= 32 bits) + } + + deviceControllerEndpointStatusBitmap uint32 + deviceControllerEndpointStatus struct { + isOpened uint8 // 1 (bits) + zlt uint8 // 1 + _ uint32 // 30 (= 32 bits) + } + + deviceControllerOriginalBufferBitmap uint32 + deviceControllerOriginalBuffer struct { + originalBufferOffset uint16 // 12 (bits) + originalBufferLength uint32 // 19 + dtdInvalid uint8 // 1 (= 32 bits) + } + + deviceControllerQH struct { + capabilities deviceControllerCapabilitiesBitmap // 4 (bytes) + currentDTDPointer *deviceControllerDTD // 4 + nextDTDPointer *deviceControllerDTD // 4 + dtdToken deviceControllerDTDTokenBitmap // 4 + bufferPointerPage [5]uint32 // 20 + reserved1 uint32 // 4 + setupBuffer deviceSetupBuffer // 8 + setupBufferBack deviceSetupBuffer // 8 + endpointStatus deviceControllerEndpointStatusBitmap // 4 + reserved2 uint32 // 4 (= 64 bytes) + } + + deviceControllerDTDList [2 * configDeviceMaxEndpoints]*deviceControllerDTD + deviceControllerDTD struct { + nextDTDPointer *deviceControllerDTD // 4 (bytes) + dtdToken deviceControllerDTDTokenBitmap // 4 + bufferPointerPage [5]uint32 // 20 + originalBuffer deviceControllerOriginalBufferBitmap // 4 (= 32 bytes) + } +) + +const ( + + // device QH + deviceControllerQHSize = 64 // bytes + deviceControllerQHBufferSize = (configDeviceCount-1)*configDeviceControllerQHAlign + + 2*configDeviceMaxEndpoints*2*deviceControllerQHSize + + deviceControllerQHPointerMsk = 0xFFFFFFC0 + deviceControllerQHMultMsk = 0xC0000000 + deviceControllerQHZLTMsk = 0x20000000 + deviceControllerQHMaxPacketSizeMsk = 0x07FF0000 + deviceControllerQHMaxPacketSize = 0x00000800 + deviceControllerQHIOSMsk = 0x00008000 + + // device DTD + deviceControllerDTDSize = 32 // bytes + deviceControllerDTDBufferSize = (configDeviceCount-1)*configDeviceControllerDTDAlign + + configDeviceControllerMaxDTD*deviceControllerDTDSize + + deviceControllerDTDPointerMsk = 0xFFFFFFE0 + deviceControllerDTDTerminateMsk = 0x00000001 + deviceControllerDTDPageMsk = 0xFFFFF000 + deviceControllerDTDPageOffsetMsk = 0x00000FFF + deviceControllerDTDPageBlock = 0x00001000 + deviceControllerDTDTotalBytesMsk = 0x7FFF0000 + deviceControllerDTDTotalBytes = 0x00004000 + deviceControllerDTDIOCMsk = 0x00008000 + deviceControllerDTDMultIOMsk = 0x00000C00 + deviceControllerDTDStatusMsk = 0x000000FF + deviceControllerDTDStatusErrorMsk = 0x00000068 + deviceControllerDTDStatusActive = 0x00000080 + deviceControllerDTDStatusHalted = 0x00000040 + deviceControllerDTDStatusDataBufferError = 0x00000020 + deviceControllerDTDStatusTransactionError = 0x00000008 +) + +var ( + // special invalid pointer, indicating the end of a list of DTDs + deviceControllerDTDTerminate = (*deviceControllerDTD)(unsafe.Pointer(uintptr( + deviceControllerDTDTerminateMsk))) +) + +// enq enqueues the given mask into tail position of the receiver iq's queue, +// increasing queue length by 1. +// +// When the queue fills to capacity, any subsequent enqueue will overwrite the +// current head with the given value, positioning its following element at the +// front of the queue, and leaves queue length unaffected. +func (iq *deviceControllerInterruptQueue) enq(mask uintptr) { + if iq.count == configInterruptQueueSize { + // queue is full; overwrite oldest element (queue head) and increment head + iq.queue[iq.head] = mask + iq.head++ + iq.head %= configInterruptQueueSize + } else { + // queue is not full; place element at queue tail and increment count + iq.queue[(iq.head+iq.count)%configInterruptQueueSize] = mask + iq.count++ + } +} + +// deq dequeues the mask in tail position from the receiver iq's queue, reducing +// queue length by 1, and returns the dequeued value with true. +// +// When the queue is empty, queue length remains unaffected, and it returns 0 +// with false. +func (iq *deviceControllerInterruptQueue) deq() (uintptr, bool) { + if iq.count == 0 { + // queue is empty; reset head and return an invalid value + iq.head = 0 + return 0, false + } + // queue is not empty; decrement count, reset and return value at queue tail + iq.count-- + n := (iq.head + iq.count) % configInterruptQueueSize + m := iq.queue[n] + iq.queue[n] = 0 // ensure the queue contains no spurious data + return m, true +} + +// fill enqueues each given mask (in order) into tail position of the receiver +// iq's queue, increasing queue length up to capacity, if possible. +// +// If the number of values given is greater than available queue positions, each +// element in head position - at the time the value is enqueued - will be +// overwritten, so that queue length never exceeds capacity, and the element in +// tail position is always the final element given. +func (iq *deviceControllerInterruptQueue) fill(mask ...uintptr) { + for _, m := range mask { + iq.enq(m) + } +} + +// drain dequeues all elements in the receiver iq's queue, reducing queue length +// to 0, and returns the dequeued values (in order) and the number of number of +// values dequeued. +func (iq *deviceControllerInterruptQueue) drain() ( + q [configInterruptQueueSize]uintptr, n int, +) { + for { + m, ok := iq.deq() + if !ok { + return + } + q[n] = m + n++ + } +} + +func getQHBuffer(port uint8, qh int, ep int) *deviceControllerQH { + if port < configDeviceCount && qh < 2 && ep < 2*configDeviceMaxEndpoints { + return (*deviceControllerQH)(unsafe.Pointer( + &deviceControllerQHBuffer[int(port)*configDeviceControllerQHAlign+ + qh*2*configDeviceMaxEndpoints*deviceControllerQHSize+ + ep*deviceControllerQHSize])) + } + return nil +} + +func getDTDBuffer(port uint8, dtd int) *deviceControllerDTD { + if port < configDeviceCount && dtd < configDeviceControllerMaxDTD { + return (*deviceControllerDTD)(unsafe.Pointer( + &deviceControllerDTDBuffer[int(port)*configDeviceControllerDTDAlign+ + dtd*deviceControllerDTDSize])) + } + return nil +} + +func (s deviceControllerCapabilities) pack() deviceControllerCapabilitiesBitmap { + return deviceControllerCapabilitiesBitmap( + ((uint32(s.reserved1) & 0x7FFF) << 0) | // uint16 // 15 (bits) + ((uint32(s.ios) & 0x1) << 15) | // uint8 // 1 + ((uint32(s.maxPacketSize) & 0x7FF) << 16) | // uint16 // 11 + ((uint32(s.reserved2) & 0x3) << 27) | // uint8 // 2 + ((uint32(s.zlt) & 0x1) << 29) | // uint8 // 1 + ((uint32(s.mult) & 0x3) << 30)) // uint8 // 2 (= 32 bits) +} + +func (s deviceControllerDTDToken) pack() deviceControllerDTDTokenBitmap { + return deviceControllerDTDTokenBitmap( + ((uint32(s.status) & 0xFF) << 0) | // uint8 // 8 (bits) + ((uint32(s.reserved1) & 0x3) << 8) | // uint8 // 2 + ((uint32(s.multiplierOverride) & 0x3) << 10) | // uint8 // 2 + ((uint32(s.reserved2) & 0x7) << 12) | // uint8 // 3 + ((uint32(s.ioc) & 0x1) << 15) | // uint8 // 1 + ((uint32(s.totalBytes) & 0x7FFF) << 16) | // uint16 // 15 + ((uint32(s.reserved3) & 0x1) << 31)) // uint8 // 1 (= 32 bits) +} + +func (b deviceControllerDTDTokenBitmap) unpack() deviceControllerDTDToken { + return deviceControllerDTDToken{ + status: uint8(b>>0) & 0xFF, + reserved1: uint8(b>>8) & 0x3, + multiplierOverride: uint8(b>>10) & 0x3, + reserved2: uint8(b>>12) & 0x7, + ioc: uint8(b>>15) & 0x1, + totalBytes: uint16(b>>16) & 0x7FFF, + reserved3: uint8(b>>31) & 0x1, + } +} + +func (s deviceControllerEndpointStatus) pack() deviceControllerEndpointStatusBitmap { + return deviceControllerEndpointStatusBitmap( + ((uint32(s.isOpened) & 0x1) << 0) | // uint8 // 1 (bits) + ((uint32(s.zlt) & 0x1) << 1)) // uint8 // 1 +} + +func (s deviceControllerOriginalBuffer) pack() deviceControllerOriginalBufferBitmap { + return deviceControllerOriginalBufferBitmap( + ((uint32(s.originalBufferOffset) & 0xFFF) << 0) | // uint16 // 12 (bits) + ((uint32(s.originalBufferLength) & 0x7FFFF) << 12) | // uint32 // 19 + ((uint32(s.dtdInvalid) & 0x1) << 31)) // uint8 // 1 (= 32 bits) +} + +func (b deviceControllerOriginalBufferBitmap) unpack() deviceControllerOriginalBuffer { + return deviceControllerOriginalBuffer{ + originalBufferOffset: uint16(b>>0) & 0xFFF, + originalBufferLength: uint32(b>>12) & 0x7FFFF, + dtdInvalid: uint8(b>>31) & 0x1, + } +} + +// cycles converts the given number of microseconds to CPU cycles for a CPU with +// given frequency. +//go:inline +func cycles(microsec, cpuFreqHz uint32) uint32 { + return uint32((uint64(microsec) * uint64(cpuFreqHz)) / 1000000) +} diff --git a/src/machine/usb/device_controller_mimxrt1062.go b/src/machine/usb/device_controller_mimxrt1062.go new file mode 100644 index 000000000..90ad58f39 --- /dev/null +++ b/src/machine/usb/device_controller_mimxrt1062.go @@ -0,0 +1,709 @@ +// +build mimxrt1062 + +package usb + +// Implementation of a port controller for USB device mode on NXP iMXRT1062. + +import ( + "device/arm" + "device/nxp" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +// deviceControl represents a USB device controller for NXP iMXRT1062. +// It implements the USB API's deviceController interface. +type deviceControl struct { + port uint8 + device *device + irq interrupt.Interrupt + + messages deviceControllerInterruptQueue + + interruptMask uintptr // interrupt state upon entering critical section + criticalState volatile.Register8 // set to 1 if in critical section, else 0 + + bus *nxp.USB_Type + phy *nxp.USBPHY_Type + nc *nxp.USBNC_Type + qh *deviceControllerQH // The QH structure base address + dtd *deviceControllerDTD // The DTD structure base address + dtdFree *deviceControllerDTD // The idle DTD list head + dtdHead deviceControllerDTDList // The transferring DTD list head for each endpoint + dtdTail deviceControllerDTDList // The transferring DTD list tail for each endpoint + dtdCount uint8 // The idle DTD node count + endpointCount uint8 // The endpoint number of EHCI + isResetting bool // Whether a PORT reset is occurring or not + controllerId uint8 // Controller ID + speed uint8 // Current speed of EHCI + isSuspending bool // Is suspending of the PORT +} + +var ( + // deviceControlInstance holds instances for all USB device controllers + // available on the platform. + deviceControlInstance [configDeviceCount]deviceControl + + //go:align 2048 + deviceControllerQHBuffer [deviceControllerQHBufferSize]uint8 + //go:align 32 + deviceControllerDTDBuffer [deviceControllerDTDBufferSize]uint8 +) + +// We cannot use the sleep timer from this context (import cycle), but we need +// an approximate method to spin CPU cycles for short periods of time. +//go:inline +func delayMicrosec(microsec uint32) { + n := cycles(microsec, configCPUFrequencyHz) + for i := uint32(0); i < n; i++ { + arm.Asm(`nop`) + } +} + +// initController returns a deviceController for the receiver USB device. +// It allocates the registers and installs/enables an interrupt handler for +// the receiver USB device. It also initializes the shared buffers used by the +// USB controller hardware. +func (d *device) initController() deviceController { + + dc := &deviceControlInstance[d.port] + + dc.port = d.port + dc.device = d + + // based on the selected port, install interrupt handler and initialize + // register references + switch d.port { + case 0: + dc.irq = interrupt.New(nxp.IRQ_USB_OTG1, func(interrupt.Interrupt) { + portInstance[0].device.controller.interrupt() + }) + dc.bus = nxp.USB1 + dc.phy = nxp.USBPHY1 + dc.nc = nxp.USBNC1 + case 1: + dc.irq = interrupt.New(nxp.IRQ_USB_OTG2, func(interrupt.Interrupt) { + //portInstance[1].device.controller.interrupt() + }) + dc.bus = nxp.USB2 + dc.phy = nxp.USBPHY2 + dc.nc = nxp.USBNC2 + } + + // get base address of QH and DTD buffers + dc.qh = (*deviceControllerQH)(unsafe.Pointer( + &deviceControllerQHBuffer[int(d.port)*configDeviceControllerQHAlign])) + dc.dtd = (*deviceControllerDTD)(unsafe.Pointer( + &deviceControllerDTDBuffer[int(d.port)*configDeviceControllerDTDAlign])) + + dc.irq.SetPriority(configInterruptPriority) + dc.irq.Enable() + + return dc +} + +// init initializes the USB device subsystem for the receiver deviceControl. +func (dc *deviceControl) init() status { + + // reset the controller + dc.bus.USBCMD.SetBits(nxp.USB_USBCMD_RST) + for dc.bus.USBCMD.HasBits(nxp.USB_USBCMD_RST) { + } + + // get hardware's endpoint count + dc.endpointCount = uint8(dc.bus.DCCPARAMS.Get() & nxp.USB_DCCPARAMS_DEN_Msk) + if dc.endpointCount < configDeviceMaxEndpoints { + return statusError + } + + // clear the controller mode field and set to device mode: + // controller mode (CM) 0x0=idle, 0x2=device-only, 0x3=host-only + dc.bus.USBMODE.ReplaceBits(nxp.USB_USBMODE_CM_CM_2, + nxp.USB_USBMODE_CM_Msk>>nxp.USB_USBMODE_CM_Pos, nxp.USB_USBMODE_CM_Pos) + + // reset the constroller state to default + return dc.resetState() +} + +func (dc *deviceControl) deinit() status { + return statusSuccess +} + +func (dc *deviceControl) enable(enable bool) status { + if enable { + // ensure D+ pulled down long enough for host to detect previous disconnect + delayMicrosec(5000) + return dc.control(deviceControlRun, nil) + } else { + return dc.control(deviceControlStop, nil) + } +} + +// interrupt is the base interrupt handler for all USB device interrupts. +func (dc *deviceControl) interrupt() { + + // protect access to message queue + for statusRetry == dc.critical(true) { + } + + // read and clear the interrupts that fired + status := dc.bus.USBSTS.Get() & dc.bus.USBINTR.Get() + dc.bus.USBSTS.Set(status) + + // enqueue interrupts for runtime processing + dc.messages.enq(uintptr(status)) + + // release message queue + _ = dc.critical(false) +} + +func (dc *deviceControl) process() status { + + // protect access to message queue + if dc.critical(true).OK() { + + // dequeue oldest interrupt in message queue + status, ok := dc.messages.deq() + + // release message queue + _ = dc.critical(false) + + // process message if queue was not empty + if ok { + + if 0 != (status & nxp.USB_USBSTS_URI_Msk) { // USB reset + dc.reset() + } + + if 0 != (status & nxp.USB_USBSTS_UI_Msk) { // USB token done + dc.tokenDone() + } + + if 0 != (status & nxp.USB_USBSTS_PCI_Msk) { // USB port status change + dc.portChange() + } + + if 0 != (status & nxp.USB_USBSTS_SRI_Msk) { // USB start of frame (SOF) + dc.frameStart() + } + } + + // message queue read and processed + return statusSuccess + } + + // could not acquire lock on message queue + return statusBusy +} + +func (dc *deviceControl) transfer(address uint8, buffer []uint8, length uint32) status { + + if dc.isResetting { + return statusError + } + + endpoint, direction := unpackEndpoint(address) + endpointIndex := int((endpoint << 1) | direction) + currentIndex := 0 + + primeBit := uint32(1) << ((address & specDescriptorEndpointAddressNumberMsk) + + ((address & specDescriptorEndpointAddressDirectionMsk) >> 3)) + epStatus, qhIdle := primeBit, false + + qh := getQHBuffer(dc.port, 0, endpointIndex) + if 0 == qh.endpointStatus&0x1 { // bit 0: isOpened + return statusError + } + + dtdRequestCount := (length + deviceControllerDTDTotalBytes - 1) / + deviceControllerDTDTotalBytes + if 0 == dtdRequestCount { + dtdRequestCount = 1 + } + + if dtdRequestCount > uint32(dc.dtdCount) { + return statusBusy + } + + var ( + dtdHead *deviceControllerDTD + sendLength uint32 + ) + + for { + + // limit transfer length to total DTD bytes + sendLength = length + if length > deviceControllerDTDTotalBytes { + sendLength = deviceControllerDTDTotalBytes + } + length -= sendLength + + // select a free DTD + dtd := dc.dtdFree + dc.dtdFree = dtd.nextDTDPointer + dc.dtdCount-- + + // save DTD head when current active buffer offset is 0 + if 0 == currentIndex { + dtdHead = dtd + } + + // set DTD field + dtd.nextDTDPointer = deviceControllerDTDTerminate + dtd.bufferPointerPage[0] = + uint32(uintptr(unsafe.Pointer(&buffer[0]))) + uint32(currentIndex) + dtd.bufferPointerPage[1] = + (dtd.bufferPointerPage[0] + deviceControllerDTDPageBlock) & deviceControllerDTDPageMsk + dtd.bufferPointerPage[2] = + dtd.bufferPointerPage[1] + deviceControllerDTDPageBlock + dtd.bufferPointerPage[3] = + dtd.bufferPointerPage[2] + deviceControllerDTDPageBlock + dtd.bufferPointerPage[4] = + dtd.bufferPointerPage[3] + deviceControllerDTDPageBlock + + // save original buffer and length to transfer + dtd.originalBuffer = deviceControllerOriginalBuffer{ + originalBufferOffset: uint16(dtd.bufferPointerPage[0]) & + deviceControllerDTDPageOffsetMsk, + originalBufferLength: sendLength, + dtdInvalid: 0, + }.pack() + + // set IOC field in final DTD + ioc := uint8(0) + if 0 == length { + ioc = 1 + } + // set DTD active flag + dtd.dtdToken = deviceControllerDTDToken{ + status: deviceControllerDTDStatusActive, + ioc: ioc, + totalBytes: uint16(sendLength), + }.pack() + + // update buffer offset + currentIndex += int(sendLength) + + // add DTD to in-use queue + if nil != dc.dtdTail[endpointIndex] { + dc.dtdTail[endpointIndex].nextDTDPointer = dtd + dc.dtdTail[endpointIndex] = dtd + } else { + dc.dtdHead[endpointIndex] = dtd + dc.dtdTail[endpointIndex] = dtd + qhIdle = true + } + + if 0 == length { + break + } + } + + if specEndpointControl == endpoint && specIn == direction { + // get last setup packet + setupIndex := int(endpoint << 1) + setupQH := getQHBuffer(dc.port, 0, setupIndex) + setupMaxSize := uint32(setupQH.capabilities&0x07FF0000) >> 16 // bits 15-26: maxPacketSize + var setup deviceSetup + setup.parse(setupQH.setupBufferBack[:]) + if 0 != qh.endpointStatus&0x2 { // bit 1: ZLT + if (0 != sendLength) && (sendLength < uint32(setup.wLength)) && + (0 == (sendLength % setupMaxSize)) { + // enable ZLT (zlt==0) + setupQH.capabilities &^= deviceControllerCapabilities{zlt: 1}.pack() + } + } + } + + // check if QH is empty + if !qhIdle { + // if prime bit is set, nothing left to do + if dc.bus.ENDPTPRIME.HasBits(primeBit) { + return statusSuccess + } + // wait to safely transmit DTD + for { + dc.bus.USBCMD.SetBits(nxp.USB_USBCMD_ATDTW) + _ = dc.bus.ENDPTSTAT.Get() // read-clear endpoint status register + if dc.bus.USBCMD.HasBits(nxp.USB_USBCMD_ATDTW) { + break + } + } + dc.bus.USBCMD.ClearBits(nxp.USB_USBCMD_ATDTW) + } + + // if QH is empty or the endpoint is not primed, need to link current DTD head + // to the QH. if endpoint is not primed and qhIdle is false, QH is empty. + if qhIdle || 0 == epStatus&primeBit { + qh.nextDTDPointer = dtdHead + qh.dtdToken = 0 + dc.bus.ENDPTPRIME.Set(primeBit) + primeAttempt := 0 + for !dc.bus.ENDPTSTAT.HasBits(primeBit) { + if primeAttempt++; primeAttempt >= configDeviceControllerMaxPrimeAttempts { + return statusError + } + if dc.bus.ENDPTCOMPLETE.HasBits(primeBit) { + break + } + dc.bus.ENDPTPRIME.Set(primeBit) + } + } + + return statusSuccess +} + +func (dc *deviceControl) send(address uint8, buffer []uint8, length uint32) status { + return dc.transfer((address&specDescriptorEndpointAddressNumberMsk)| + (specDescriptorEndpointAddressDirectionIn), buffer, length) +} + +func (dc *deviceControl) receive(address uint8, buffer []uint8, length uint32) status { + return dc.transfer((address&specDescriptorEndpointAddressNumberMsk)| + (specDescriptorEndpointAddressDirectionOut), buffer, length) +} + +func (dc *deviceControl) cancel(address uint8) status { + return statusSuccess +} + +func (dc *deviceControl) control(command deviceControlID, param interface{}) (s status) { + + // assume success unless error condition deliberately detected + s = statusSuccess + + switch command { + case deviceControlRun: + dc.bus.USBCMD.SetBits(nxp.USB_USBCMD_RS) + + case deviceControlStop: + dc.bus.USBCMD.ClearBits(nxp.USB_USBCMD_RS) + + case deviceControlEndpointInit: + config, ok := param.(*deviceEndpointConfig) + if !ok { + return statusInvalidParameter + } + s = dc.initEndpoint(config) + + case deviceControlEndpointDeinit: + address, ok := param.(uint8) + if !ok { + return statusInvalidParameter + } + s = dc.deinitEndpoint(address) + + case deviceControlEndpointStall: + address, ok := param.(uint8) + if !ok { + return statusInvalidParameter + } + s = dc.stallEndpoint(address) + + case deviceControlEndpointUnstall: + address, ok := param.(uint8) + if !ok { + return statusInvalidParameter + } + s = dc.unstallEndpoint(address) + + case deviceControlGetDeviceStatus: + // param should be a pointer to uint16, acting as output parameter. + stat, ok := param.(*uint16) + if !ok { + return statusInvalidParameter + } + // configDeviceSelfPowered is a configuration constant on iMXRT1062 + *stat = configDeviceSelfPowered << + specRequestStandardGetStatusDeviceSelfPoweredPos + + case deviceControlGetEndpointStatus: + // param should be pointer to deviceEndpointStatus, acting as output + // parameter. + stat, ok := param.(*deviceEndpointStatus) + if !ok { + return statusInvalidParameter + } + endpoint, direction := unpackEndpoint(stat.address) + if endpoint >= configDeviceMaxEndpoints { + return statusInvalidParameter + } + mask := uint32(nxp.USB_ENDPTCTRL0_RXS) + if specOut != direction { + mask = nxp.USB_ENDPTCTRL0_TXS + } + ctrl := dc.endpointControlRegister(endpoint) + if nil == ctrl { + return statusInvalidParameter + } + if ctrl.HasBits(mask) { + stat.status = uint16(deviceEndpointStateStalled) + } else { + stat.status = uint16(deviceEndpointStateIdle) + } + + case deviceControlPreSetDeviceAddress: + address, ok := param.(uint8) + if !ok { + return statusInvalidParameter + } + dc.bus.DEVICEADDR.Set((uint32(address) << nxp.USB_DEVICEADDR_USBADR_Pos) | + nxp.USB_DEVICEADDR_USBADRA_Msk) + + case deviceControlSetDeviceAddress: + // TODO + + case deviceControlGetSynchFrame: + return statusNotSupported + + case deviceControlSetDefaultStatus: + for i := uint8(0); i < configDeviceMaxEndpoints; i++ { + _ = dc.deinitEndpoint(i | specDescriptorEndpointAddressDirectionIn) + _ = dc.deinitEndpoint(i | specDescriptorEndpointAddressDirectionOut) + } + s = dc.resetState() + + case deviceControlGetSpeed: + // param should be a pointer to uint8, acting as output parameter. + speed, ok := param.(*uint8) + if !ok { + return statusInvalidParameter + } + *speed = dc.speed + + case deviceControlGetOTGStatus: + return statusNotSupported + + case deviceControlSetOTGStatus: + return statusNotSupported + } + + return +} + +func (dc *deviceControl) critical(enter bool) status { + if enter { + // check if critical section already locked + if dc.criticalState.Get() != 0 { + return statusRetry + } + // lock critical section + dc.criticalState.Set(1) + // disable interrupts, storing state in receiver + dc.interruptMask = arm.DisableInterrupts() + } else { + // ensure critical section is locked + if dc.criticalState.Get() != 0 { + // re-enable interrupts, using state stored in receiver + arm.EnableInterrupts(dc.interruptMask) + // unlock critical section + dc.criticalState.Set(0) + } + } + return statusSuccess +} + +func (dc *deviceControl) resetState() status { + + dc.dtdFree = dc.dtd + p := dc.dtdFree + for i := 1; i < configDeviceControllerMaxDTD; i++ { + p.nextDTDPointer = getDTDBuffer(dc.port, i) + p = p.nextDTDPointer + } + p.nextDTDPointer = nil + dc.dtdCount = configDeviceControllerMaxDTD + + // no interrupt threshold + dc.bus.USBCMD.ClearBits(nxp.USB_USBCMD_ITC_Msk) + + // disable setup lockout + dc.bus.USBMODE.SetBits(nxp.USB_USBMODE_SLOM_Msk) + + // use little-endianness + dc.bus.USBMODE.ClearBits(nxp.USB_USBMODE_ES_Msk) + + for i := 0; i < 2*configDeviceMaxEndpoints; i++ { + qh := getQHBuffer(dc.port, 0, i) + qh.capabilities = + deviceControllerCapabilities{ + maxPacketSize: configDeviceControllerMaxPacketSize, + }.pack() + qh.endpointStatus = + deviceControllerEndpointStatus{ + isOpened: 0, + }.pack() + qh.nextDTDPointer = deviceControllerDTDTerminate + dc.dtdHead[i] = nil + dc.dtdTail[i] = nil + } + dc.bus.ASYNCLISTADDR.Set(uint32(uintptr(unsafe.Pointer( + getQHBuffer(dc.port, 0, 0))))) + + dc.bus.DEVICEADDR.Set(0) + + // enable interrupts: bus enable, bus error, port change detect, bus reset + dc.bus.USBINTR.Set(nxp.USB_USBINTR_UE_Msk | nxp.USB_USBINTR_UEE_Msk | + nxp.USB_USBINTR_PCE_Msk | nxp.USB_USBINTR_URE_Msk) + + dc.isResetting = false + + return statusSuccess +} + +func (dc *deviceControl) reset() { + + println("reset") + + // clear setup flag + dc.bus.ENDPTSETUPSTAT.Set(dc.bus.ENDPTSETUPSTAT.Get()) + // clear endpoint complete flag + dc.bus.ENDPTCOMPLETE.Set(dc.bus.ENDPTCOMPLETE.Get()) + + // flush any pending transfers + for dc.bus.ENDPTPRIME.HasBits(nxp.USB_ENDPTPRIME_PERB_Msk | nxp.USB_ENDPTPRIME_PETB_Msk) { + dc.bus.ENDPTFLUSH.Set(nxp.USB_ENDPTFLUSH_FERB_Msk | nxp.USB_ENDPTFLUSH_FETB_Msk) + } + + // set receiver flag if port reset bit is set; otherwise, notify device class. + if dc.bus.PORTSC1.HasBits(nxp.USB_PORTSC1_PR_Msk) { + dc.isResetting = true + } else { + // send reset notification to common device + dc.device.notify(deviceNotification{code: deviceNotifyBusReset}) + } +} + +func (dc *deviceControl) tokenDone() { + println("token done") +} + +func (dc *deviceControl) portChange() { + // check if port is resetting + if !dc.bus.PORTSC1.HasBits(nxp.USB_PORTSC1_PR) { + // not resetting, update bus speed + if dc.bus.PORTSC1.HasBits(nxp.USB_PORTSC1_HSP) { + dc.speed = specSpeedHigh + } else { + dc.speed = specSpeedFull + } + // if reset flag is set, notify device layer reset has finished + if dc.isResetting { + dc.device.notify( + deviceNotification{ + buffer: nil, + length: 0, + code: deviceNotifyBusReset, + isSetup: false, + }) + dc.isResetting = false + } + } +} + +func (dc *deviceControl) frameStart() { + println("frame start") +} + +func (dc *deviceControl) endpointControlRegister(endpoint uint8) *volatile.Register32 { + endpoint &= specDescriptorEndpointAddressNumberMsk + if endpoint < configDeviceMaxEndpoints { + switch endpoint { + case 0: + return &dc.bus.ENDPTCTRL0 + case 1: + return &dc.bus.ENDPTCTRL1 + case 2: + return &dc.bus.ENDPTCTRL2 + case 3: + return &dc.bus.ENDPTCTRL3 + case 4: + return &dc.bus.ENDPTCTRL4 + case 5: + return &dc.bus.ENDPTCTRL5 + case 6: + return &dc.bus.ENDPTCTRL6 + case 7: + return &dc.bus.ENDPTCTRL7 + } + } + return nil +} + +func (dc *deviceControl) cancelControlPipe(endpoint, direction uint8) status { + index := (endpoint << 1) + direction + message := deviceNotification{ + buffer: nil, + length: 0, + } + + // get DTD of control pipe + currentDTD := (*deviceControllerDTD)(unsafe.Pointer( + uintptr(unsafe.Pointer(dc.dtdHead[index])) & deviceControllerDTDPointerMsk)) + + for nil != currentDTD { + originalBuffer := currentDTD.originalBuffer.unpack() + dtdToken := currentDTD.dtdToken.unpack() + + // pass transfer buffer address + if nil == message.buffer { + message.buffer = *(*[]uint8)(unsafe.Pointer( + uintptr((currentDTD.bufferPointerPage[0] & deviceControllerDTDPageMsk) | + uint32(originalBuffer.originalBufferOffset)))) + } + if 0 != dtdToken.status&deviceControllerDTDStatusActive { + message.length = packU32(deviceCDCACMBufferInvalid32) + } else { + message.length += originalBuffer.originalBufferLength - uint32(dtdToken.totalBytes) + } + + if dc.dtdHead[index] == dc.dtdTail[index] { + dc.dtdHead[index] = nil + dc.dtdTail[index] = nil + qh := getQHBuffer(dc.port, 0, int(index)) + qh.nextDTDPointer = deviceControllerDTDTerminate + qh.dtdToken = 0 + } else { + dc.dtdHead[index] = dc.dtdHead[index].nextDTDPointer + } + + if 0 != currentDTD.dtdToken.unpack().ioc || + 0 == uintptr(unsafe.Pointer(dc.dtdHead[index]))&deviceControllerDTDPointerMsk { + message.code = deviceNotificationID(endpoint | + (direction << specDescriptorEndpointAddressDirectionPos)) + message.isSetup = false + dc.device.notify(message) + message.buffer = nil + message.length = 0 + } + + currentDTD.dtdToken = 0 + currentDTD.nextDTDPointer = dc.dtdFree + dc.dtdFree = currentDTD + dc.dtdCount++ + + // get DTD of control pipe + currentDTD = (*deviceControllerDTD)(unsafe.Pointer( + uintptr(unsafe.Pointer(dc.dtdHead[index])) & deviceControllerDTDPointerMsk)) + + } + return statusSuccess +} + +func (dc *deviceControl) initEndpoint(config *deviceEndpointConfig) status { + return statusSuccess +} + +func (dc *deviceControl) deinitEndpoint(address uint8) status { + return statusSuccess +} + +func (dc *deviceControl) stallEndpoint(address uint8) status { + return statusSuccess +} + +func (dc *deviceControl) unstallEndpoint(address uint8) status { + return statusSuccess +} diff --git a/src/machine/usb/host.go b/src/machine/usb/host.go new file mode 100644 index 000000000..61a13886c --- /dev/null +++ b/src/machine/usb/host.go @@ -0,0 +1,25 @@ +package usb + +// Unexported USB device type definitions. +// In device mode, a USB device represents the whole USB port controller. +// In host mode, a USB device represents a peripheral device connected to the +// USB port controller. +type ( + host struct { + port uint8 + } +) + +func (h *host) init(port uint8) (s status) { + + // initialize host + h.port = port + + return statusSuccess +} + +func (h *host) deinit() status { + + // de-initialize host + return statusSuccess +} diff --git a/src/machine/usb/host_controller.go b/src/machine/usb/host_controller.go new file mode 100644 index 000000000..c4a6ff719 --- /dev/null +++ b/src/machine/usb/host_controller.go @@ -0,0 +1,13 @@ +package usb + +// hostController provides hardware abstraction over a platform's physical +// USB host port controller (e.g., an EHCI-compliant peripheral). +// +// To connect the USB 2.0 host driver stack to a particular platform, the +// following method must be implemented: +// +// func (h *host) initController() hostController +// +// This method shall return an implementation of hostController, or nil if a +// controller cannot be allocated for the given port. +type hostController interface{} diff --git a/src/machine/usb/spec.go b/src/machine/usb/spec.go new file mode 100644 index 000000000..856deb36c --- /dev/null +++ b/src/machine/usb/spec.go @@ -0,0 +1,167 @@ +package usb + +// Constants defined per USB 2.0 specification. +const ( + // USB speed + specSpeedFull = 0x00 + specSpeedLow = 0x01 + specSpeedHigh = 0x02 + specSpeedSuper = 0x04 + + // USB standard descriptor endpoint type + specEndpointControl = 0x00 + specEndpointIsochronous = 0x01 + specEndpointBulk = 0x02 + specEndpointInterrupt = 0x03 + + // USB standard descriptor transfer direction + specOut = 0 + specIn = 1 + + // USB standard descriptor length + specDescriptorLengthDevice = 18 + specDescriptorLengthConfigure = 9 + specDescriptorLengthInterface = 9 + specDescriptorLengthEndpoint = 7 + specDescriptorLengthEndpointCompanion = 6 + specDescriptorLengthDeviceQualitier = 10 + specDescriptorLengthOTGDescriptor = 5 + specDescriptorLengthBOSDescriptor = 5 + specDescriptorLengthDeviceCapabilityUSB20Extension = 7 + specDescriptorLengthDeviceCapabilitySuperspeed = 10 + + // USB device capability type codes + specDescriptorTypeDeviceCapabilityWireless = 0x01 + specDescriptorTypeDeviceCapabilityUSB20Extension = 0x02 + specDescriptorTypeDeviceCapabilitySuperspeed = 0x03 + + // USB standard descriptor type + specDescriptorTypeDevice = 0x01 + specDescriptorTypeConfigure = 0x02 + specDescriptorTypeString = 0x03 + specDescriptorTypeInterface = 0x04 + specDescriptorTypeEndpoint = 0x05 + specDescriptorTypeDeviceQualitier = 0x06 + specDescriptorTypeOtherSpeedConfiguration = 0x07 + specDescriptorTypeInterfaacePower = 0x08 + specDescriptorTypeOTG = 0x09 + specDescriptorTypeInterfaceAssociation = 0x0B + specDescriptorTypeBOS = 0x0F + specDescriptorTypeDeviceCapability = 0x10 + + specDescriptorTypeHID = 0x21 + specDescriptorTypeHIDReport = 0x22 + specDescriptorTypeHIDPhysical = 0x23 + + specDescriptorTypeCDCInterface = 0x24 + specDescriptorTypeCDCEndpoint = 0x25 + + specDescriptorTypeEndpointCompanion = 0x30 + + // USB standard request type + specRequestTypeDirMsk = 0x80 + specRequestTypeDirPos = 7 + specRequestTypeDirOut = 0x00 + specRequestTypeDirIn = 0x80 + + specRequestTypeTypeMsk = 0x60 + specRequestTypeTypePos = 5 + specRequestTypeTypeStandard = 0 + specRequestTypeTypeClass = 0x20 + specRequestTypeTypeVendor = 0x40 + + specRequestTypeRecipientMsk = 0x1F + specRequestTypeRecipientPos = 0 + specRequestTypeRecipientDevice = 0x00 + specRequestTypeRecipientInterface = 0x01 + specRequestTypeRecipientEndpoint = 0x02 + specRequestTypeRecipientOther = 0x03 + + // USB standard request + specRequestStandardGetStatus = 0x00 + specRequestStandardClearFeature = 0x01 + specRequestStandardSetFeature = 0x03 + specRequestStandardSetAddress = 0x05 + specRequestStandardGetDescriptor = 0x06 + specRequestStandardSetDescriptor = 0x07 + specRequestStandardGetConfiguration = 0x08 + specRequestStandardSetConfiguration = 0x09 + specRequestStandardGetInterface = 0x0A + specRequestStandardSetInterface = 0x0B + specRequestStandardSynchFrame = 0x0C + + // USB standard request: GET status + specRequestStandardGetStatusDeviceSelfPoweredPos = 0 + specRequestStandardGetStatusDeviceRemoteWakeupPos = 1 + + specRequestStandardGetStatusEndpointHaltMsk = 0x01 + specRequestStandardGetStatusEndpointHaltPos = 0 + + specRequestStandardGetStatusOTGStatusSelector = 0xF000 + + // USB standard request: CLEAR/SET feature + specRequestStandardFeatureSelectorEndpointHalt = 0 + specRequestStandardFeatureSelectorDeviceRemoteWakeup = 1 + specRequestStandardFeatureSelectorDeviceTestMode = 2 + specRequestStandardFeatureSelectorBHNPEnable = 3 + specRequestStandardFeatureSelectorAHNPSupport = 4 + specRequestStandardFeatureSelectorAAltHNPSupport = 5 + + // USB standard descriptor: configure attributes + specDescriptorConfigureAttributeD7Msk = 0x80 + specDescriptorConfigureAttributeD7Pos = 7 + + specDescriptorConfigureAttributeSelfPoweredMsk = 0x40 + specDescriptorConfigureAttributeSelfPoweredPos = 6 + + specDescriptorConfigureAttributeRemoteWakeupMsk = 0x20 + specDescriptorConfigureAttributeRemoteWakeupPos = 5 + + // USB standard descriptor: endpoint attributes + specDescriptorEndpointAddressDirectionMsk = 0x80 + specDescriptorEndpointAddressDirectionPos = 7 + specDescriptorEndpointAddressDirectionOut = 0 + specDescriptorEndpointAddressDirectionIn = 0x80 + + specDescriptorEndpointAddressNumberMsk = 0x0F + specDescriptorEndpointAddressNumberPos = 0 + + specDescriptorEndpointAttributeTypeMsk = 0x03 + specDescriptorEndpointAttributeNumberPos = 0 + + specDescriptorEndpointAttributeSyncTypeMsk = 0x0C + specDescriptorEndpointAttributeSyncTypePos = 2 + specDescriptorEndpointAttributeSyncTypeNoSync = 0x00 + specDescriptorEndpointAttributeSyncTypeAsync = 0x04 + specDescriptorEndpointAttributeSyncTypeAdaptive = 0x08 + specDescriptorEndpointAttributeSyncTypeSync = 0x0C + + specDescriptorEndpointAttributeUsageTypeMsk = 0x30 + specDescriptorEndpointAttributeUsageTypePos = 4 + specDescriptorEndpointAttributeUsageTypeDataEndpoint = 0x00 + specDescriptorEndpointAttributeUsageTypeFeedbackEndpoint = 0x10 + specDescriptorEndpointAttributeUsageTypeImplicitFeedbackDataEndpoint = 0x20 + + specDescriptorEndpointMaxpacketsizeSizeMsk = 0x07FF + specDescriptorEndpointMaxpacketsizeMultTransactionsMsk = 0x1800 + specDescriptorEndpointMaxpacketsizeMultTransactionsPos = 11 + + // USB standard descriptor: OTG attributes + specDescriptorOTGAttributesSRPMsk = 0x01 + specDescriptorOTGAttributesHNPMsk = 0x02 + specDescriptorOTGAttributesADPMsk = 0x04 + + // USB standard descriptor: device capability attributes (USB 2.0 extension) + specDescriptorDeviceCapabilityUSB20ExtensionLPMMsk = 0x02 + specDescriptorDeviceCapabilityUSB20ExtensionLPMPos = 1 + specDescriptorDeviceCapabilityUSB20ExtensionBESLMsk = 0x04 + specDescriptorDeviceCapabilityUSB20ExtensionBESLPos = 2 +) + +//go:inline +func unpackEndpoint(address uint8) (number, direction uint8) { + return (address & specDescriptorEndpointAddressNumberMsk) >> + specDescriptorEndpointAddressNumberPos, + (address & specDescriptorEndpointAddressDirectionMsk) >> + specDescriptorEndpointAddressDirectionPos +} diff --git a/src/machine/usb/uart.go b/src/machine/usb/uart.go new file mode 100644 index 000000000..1925aea59 --- /dev/null +++ b/src/machine/usb/uart.go @@ -0,0 +1,366 @@ +package usb + +import ( + "errors" +) + +var ( + ErrInvalidPort = errors.New("invalid USB port") +) + +type ( + UARTConfig struct { + BaudRate uint32 + } + + // UART represents a virtual serial (UART) device emulation using the USB + // CDC-ACM device class driver. + UART struct { + port uint8 // USB port (core index, e.g., 0-1) + desc *ConfigDeviceDescriptor // User-provided USB device descriptor information + class *deviceClass // USB device class handle + acm *deviceCDCACM // USB CDC-ACM device class handle + + attached bool + transact bool + config uint8 // selected configuration index (1-based, 0=invalid) + speed uint8 // enumerated bus speed (low, full, high) + alternate deviceCDCACMAlternateList + } +) + +var ( + uartAbstractState = []uint8{0x00, 0x00} + uartCountryCode = []uint8{0x00, 0x00} +) + +func (uart *UART) SetPort(port uint8) { + if port >= ConfigPortCount || port >= configDeviceCount || + port >= configDeviceCDCACMCount { + return // invalid port for USB CDC-ACM device class + } + // TODO: if changed, may need to reset port controller and tell host to + // re-enumerate devices + uart.port = port +} + +func (uart *UART) SetDeviceDescriptor(desc *ConfigDeviceDescriptor) { + if nil == desc { + return // invalid device descriptor information + } + // TODO: if changed, may need to reset port controller and tell host to + // re-enumerate devices + uart.desc = desc +} + +func (uart *UART) Configure(config UARTConfig) error { + + if uart.port >= ConfigPortCount || uart.port >= configDeviceCount || + uart.port >= configDeviceCDCACMCount { + return ErrInvalidPort + } + + // use default configuration index (1-based index; 0=invalid) + uart.config = configDeviceCDCACMConfigurationIndex + + // modify the global basic configuration struct configDeviceCDCACM for our USB + // port and configuration index. + // + // these settings are copied into the real CDC-ACM object, using interface + // deviceClassDriver, via initialization method (*deviceCDCACM).init(). + + // change baud rate from default, if provided + if config.BaudRate != 0 { + configDeviceCDCACM[uart.port][uart.config-1].lineCodingBaudRate = + config.BaudRate + } + + // verify we have a free USB port and take ownership of it + port, status := initPort(uart.port, modeDevice) + if !status.OK() { + return status + } + + // apply the CDC-ACM configuration to our port + uart.acm, uart.class = port.initCDCACM(uart.config, uart) + + // enable USB device mode functionality, which allows a host to enumerate us + status = port.device.controller.enable(true) + if !status.OK() { + return status + } + + return nil +} + +func (uart *UART) deviceEvent(ev deviceEventID, param interface{}) status { + + switch ev { + case deviceEventBusReset: + uart.attached = false + uart.config = 0 // clear selected configuration (1-based index) + s := uart.acm.device.busSpeed(&uart.speed) + if s.OK() { + s = uart.acm.device.setBusSpeed(uart.speed) + } + return s + + case deviceEventSetConfiguration: + if id, ok := param.(uint8); ok { + if 0 == id { + uart.attached = false + uart.config = 0 // clear selected configuration (1-based index) + return statusSuccess + } + // verify we have a config struct at given index + if nil != uart.class && nil != uart.class.config && + int(id) <= len(uart.class.config) && + int(id) <= len(configDeviceCDCACM[uart.port]) { + config := uart.class.config[id-1] // receiver configuration + system := configDeviceCDCACM[uart.port][id-1] // platform configuration + // verify the config is a CDC device and has a defined driver + if deviceClassCDC == config.info.classID && nil != config.driver { + // finally, verify the config driver is an ACM device driver + if _, ok := config.driver.(*deviceCDCACM); ok { + uart.attached = true + uart.config = id + return uart.acm.receive(system.dataBulkOutEndpoint, uart.acm.recvBuffer[:], + uint32(system.dataBulkOutPacketSize)) + } + } + } + } + return statusNotSupported // all non-error paths return early + + case deviceEventSetInterface: + if uart.attached { + if setting, ok := param.(uint16); ok { + inf := (setting & 0xFF00) >> 8 + if inf < configDeviceCDCACMInterfaceCount { + uart.alternate[inf] = setting & 0x00FF + } + } + } + + case deviceEventGetConfiguration: + case deviceEventGetInterface: + case deviceEventGetDeviceDescriptor: + case deviceEventGetConfigurationDescriptor: + case deviceEventGetStringDescriptor: + } + + return statusSuccess +} + +func (uart *UART) classEvent(ev uint32, param interface{}) status { + + if nil == uart.acm { + return statusInvalidHandle + } + + switch deviceCDCACMEventID(ev) { + case deviceCDCACMEventSendResponse: + if ctl, ok := param.(deviceEndpointControlMessage); ok { + + // verify receiver configuration + if int(uart.port) >= len(configDeviceCDCACM) || uart.config == 0 || + int(uart.config) > len(configDeviceCDCACM[uart.port]) { + return statusNotSupported + } + // get platform configuration + system := configDeviceCDCACM[uart.port][uart.config-1] + + if ctl.length > 0 && 0 != ctl.length%uint32(system.dataBulkInPacketSize) { + // If the last packet is the size of endpoint, then send an additional + // zero-ended packet to notify the host it may flush output. + return uart.acm.send(system.dataBulkInEndpoint, nil, 0) + } + + if uart.attached && uart.transact && + (nil != ctl.buffer || (nil == ctl.buffer && 0 == ctl.length)) { + // send complete, schedule buffer for next receive event + return uart.acm.receive(system.dataBulkOutEndpoint, uart.acm.recvBuffer[:], + uint32(system.dataBulkOutPacketSize)) + } + } + return statusError // all non-error paths return early + + case deviceCDCACMEventRecvResponse: + if ctl, ok := param.(deviceEndpointControlMessage); ok { + if uart.attached && uart.transact { + uart.acm.recvSize = ctl.length + if 0 == ctl.length { + + // verify receiver configuration + if int(uart.port) >= len(configDeviceCDCACM) || uart.config == 0 || + int(uart.config) > len(configDeviceCDCACM[uart.port]) { + return statusNotSupported + } + // get platform configuration + system := configDeviceCDCACM[uart.port][uart.config-1] + + // schedule buffer for next receive event + return uart.acm.receive(system.dataBulkOutEndpoint, uart.acm.recvBuffer[:], + uint32(system.dataBulkOutPacketSize)) + } + } + } + return statusError // all non-error paths return early + + case deviceCDCACMEventSerialStateNotify: + uart.acm.hasSentState = false + return statusSuccess + + case deviceCDCACMEventSendEncapsulatedCommand: + return statusNotImplemented + + case deviceCDCACMEventGetEncapsulatedResponse: + return statusNotImplemented + + case deviceCDCACMEventSetCommFeature: + if req, ok := param.(deviceCDCACMRequestParam); ok { + switch req.setupValue { + case deviceCDCFeatureAbstractState: + if req.isSetup { + *(req.buffer) = uartAbstractState + } else { + *(req.length) = 0 + } + return statusSuccess + + case deviceCDCFeatureCountrySetting: + if req.isSetup { + *(req.buffer) = uartCountryCode + } else { + *(req.length) = 0 + } + return statusSuccess + } + return statusInvalidParameter // unrecognized request code + } + return statusError // all non-error paths return early + + case deviceCDCACMEventGetCommFeature: + if req, ok := param.(deviceCDCACMRequestParam); ok { + switch req.setupValue { + case deviceCDCFeatureAbstractState: + *(req.buffer) = uartAbstractState + *(req.length) = uint32(len(uartAbstractState)) + return statusSuccess + + case deviceCDCFeatureCountrySetting: + *(req.buffer) = uartCountryCode + *(req.length) = uint32(len(uartCountryCode)) + return statusSuccess + } + return statusInvalidParameter // unrecognized request code + } + return statusError // all non-error paths return early + + case deviceCDCACMEventClearCommFeature: + return statusNotImplemented + + case deviceCDCACMEventGetLineCoding: + if req, ok := param.(deviceCDCACMRequestParam); ok { + // verify receiver configuration + if int(uart.port) >= len(deviceCDCACMLineCoding) || uart.config == 0 || + int(uart.config) > len(deviceCDCACMLineCoding[uart.port]) { + return statusNotSupported + } + lineCoding := deviceCDCACMLineCoding[uart.port][uart.config-1] + *(req.buffer) = lineCoding + *(req.length) = uint32(len(lineCoding)) + return statusSuccess + } + return statusError // all non-error paths return early + + case deviceCDCACMEventSetLineCoding: + if req, ok := param.(deviceCDCACMRequestParam); ok { + // verify receiver configuration + if int(uart.port) >= len(deviceCDCACMLineCoding) || uart.config == 0 || + int(uart.config) > len(deviceCDCACMLineCoding[uart.port]) { + return statusNotSupported + } + lineCoding := deviceCDCACMLineCoding[uart.port][uart.config-1] + if req.isSetup { + *(req.buffer) = lineCoding + } else { + *(req.length) = uint32(len(lineCoding)) + } + return statusSuccess + } + return statusError // all non-error paths return early + + case deviceCDCACMEventSetControlLineState: + if req, ok := param.(deviceCDCACMRequestParam); ok { + // verify receiver configuration + if int(uart.port) >= len(configDeviceCDCACM) || uart.config == 0 || + int(uart.config) > len(configDeviceCDCACM[uart.port]) { + return statusNotSupported + } + // get platform configuration + system := configDeviceCDCACM[uart.port][uart.config-1] + + uart.acm.info.dteStatus = uint8(req.setupValue) + + // activate/deactivate Tx carrier + if 0 != uart.acm.info.dteStatus&deviceCDCControlSigBitmapCarrierActivation { + uart.acm.info.uartState |= deviceCDCUARTStateTxCarrier + } else { + uart.acm.info.uartState &^= deviceCDCUARTStateTxCarrier + } + + // activate/deactivate DTE and carrier + if 0 != uart.acm.info.dteStatus&deviceCDCControlSigBitmapDTEPresence { + uart.acm.info.uartState |= deviceCDCUARTStateRxCarrier + uart.acm.info.dtePresent = true // serial device is now open on host + } else { + uart.acm.info.uartState &^= deviceCDCUARTStateRxCarrier + uart.acm.info.dtePresent = false // serial device is now closed on host + } + + uart.acm.info.serialState[0] = deviceCDCACMRequestNotify // bmRequestType + uart.acm.info.serialState[1] = deviceCDCNotifySerialState // bNotification + uart.acm.info.serialState[2] = 0 // wValue (lo) + uart.acm.info.serialState[3] = 0 // wValue (hi) + uart.acm.info.serialState[4] = uint8(req.interfaceIndex) // wIndex (lo) + uart.acm.info.serialState[5] = uint8(req.interfaceIndex >> 8) // wIndex (hi) + uart.acm.info.serialState[6] = deviceCDCACMInfoUARTBitmapSize // wLength (lo) + uart.acm.info.serialState[7] = 0 // wLength (hi) + uart.acm.info.serialState[8] = uint8(uart.acm.info.uartState) // UART bitmap (lo) + uart.acm.info.serialState[9] = uint8(uart.acm.info.uartState >> 8) // UART bitmap (hi) + + s := statusSuccess + if !uart.acm.hasSentState { + s = uart.acm.send(system.commInterruptInEndpoint, + uart.acm.info.serialState[:], deviceCDCACMSerialStateSize) + uart.acm.hasSentState = true + } + + // update status + if 0 != uart.acm.info.dteStatus&deviceCDCControlSigBitmapCarrierActivation { + // carrier activated + } + if 0 != uart.acm.info.dteStatus&deviceCDCControlSigBitmapDTEPresence { + // DTE activated + if uart.attached { + uart.transact = true + } + } else { + // DTE deactivated + if uart.attached { + uart.transact = false + } + } + + return s + } + return statusError // all non-error paths return early + + case deviceCDCACMEventSendBreak: + return statusNotImplemented + + default: + return statusInvalidParameter + } +} diff --git a/src/machine/usb/usb.go b/src/machine/usb/usb.go new file mode 100644 index 000000000..888d2cfcd --- /dev/null +++ b/src/machine/usb/usb.go @@ -0,0 +1,416 @@ +package usb + +type ( + // status is the common error code used internally for package operations. + status int8 + // mode defines the operating mode of a USB port. + mode int8 + + // port represents a physical USB port, which may be configured as either a + // device or as a host. + port struct { + mode mode + device device + host host + } +) + +// Constants for unexported types shared across entire package. +const ( + statusSuccess status = iota // Success + statusError // Failed + statusBusy // Busy + statusInvalidHandle // Invalid handle + statusInvalidParameter // Invalid parameter + statusInvalidRequest // Invalid request + statusControllerNotFound // Controller cannot be found + statusInvalidController // Invalid controller interface + statusNotSupported // Configuration is not supported + statusRetry // Enumeration get configuration retry + statusTransferStall // Transfer stalled + statusTransferFailed // Transfer failed + statusAllocFail // Allocation failed + statusLackSwapBuffer // Insufficient swap buffer for KHCI + statusTransferCancel // The transfer cancelled + statusBandwidthFail // Allocate bandwidth failed + statusMSDStatusFail // For MSD, the CSW status means fail + statusEHCIAttached // EHCI attached + statusEHCIDetached // EHCI detached + statusDataOverRun // Endpoint data (Rx) exceeds max size + statusNotImplemented // Supported feature not implemented + + modeIdle mode = iota // USB core idle (unallocated) + modeDevice // USB device mode + modeHost // USB host mode +) + +var ( + // portInstance holds instances for all available ports on the platform. + portInstance [ConfigPortCount]port +) + +func init() { + // ensure all ports are in idle state by default + for i := range portInstance { + portInstance[i].mode = modeIdle + } +} + +func ProcessMessages() (err error) { + for i := range portInstance { + s := portInstance[i].process() + if !s.OK() && nil == err { + err = s + } + } + return +} + +// initPort configures the mode for a given USB port and initializes the +// hardware's port controller. If the port is invalid or not idle (it has +// already been configured), it returns nil and a status code. +func initPort(port uint8, mode mode) (*port, status) { + if port >= ConfigPortCount || int(port) >= len(portInstance) { + return nil, statusInvalidController + } + if modeIdle != portInstance[port].mode { + return nil, statusBusy + } + portInstance[port].mode = mode + switch mode { + case modeDevice: + if s := portInstance[port].device.init(port); !s.OK() { + return nil, s + } + case modeHost: + if s := portInstance[port].host.init(port); !s.OK() { + return nil, s + } + } + return &portInstance[port], statusSuccess +} + +// deinit disables the receiver USB port, changing its mode to idle, freeing it +// for reuse or reconfiguration. +func (p *port) deinit() status { + switch p.mode { + case modeDevice: + return p.device.deinit() + case modeHost: + return p.host.deinit() + default: + return statusInvalidController + } +} + +func (p *port) process() status { + switch p.mode { + case modeDevice: + return p.device.controller.process() + case modeHost: + return statusSuccess // TODO: not implemented + default: + return statusInvalidController + } +} + +// initCDCACM applies a CDC-ACM configuration to the receiver port p and then +// returns the configured deviceClassDriver and deviceClass that were assigned +// to the receiver. +// +// The given deviceClassEventHandler is called for any USB device-level event +// notifications received, which allows an upper-layer CDC-ACM driver (such as +// a UART interface implementation) the opportunity to handle device events. +func (p *port) initCDCACM(id uint8, handler deviceClassEventHandler) (*deviceCDCACM, *deviceClass) { + + // verify a valid port was provided + if nil == p || nil == p.device.controller || p.mode != modeDevice { + return nil, nil + } + + if 0 == id || int(id) > len(configDeviceCDCACM[p.device.port]) { + return nil, nil + } + + // get a reference to each of the class interfaces + comm := &deviceCDCACMConfigInstance[p.device.port][id-1].info.interfaceList[0] + data := &deviceCDCACMConfigInstance[p.device.port][id-1].info.interfaceList[1] + + // configDeviceCDCACM must be defined per package API. these settings will be + // platform-specific, and will probably be implemented in a build tag- + // constrained source file. the length of this array corresponds to the number + // of USB CDC-ACM ports that are being created, and the index of each element + // corresponds to the physical USB port (core index). Each element is a slice + // of alternate device configurations that may be selected for a given port. + + // CDC-ACM Communication/control interface + comm.interfaceNumber = + configDeviceCDCACM[p.device.port][id-1].commInterfaceIndex + + comm.deviceInterface[0].endpoint[0].address = + configDeviceCDCACM[p.device.port][id-1].commInterruptInEndpoint | + specDescriptorEndpointAddressDirectionIn + + comm.deviceInterface[0].endpoint[0].maxPacketSize = + configDeviceCDCACM[p.device.port][id-1].commInterruptInPacketSize + + comm.deviceInterface[0].endpoint[0].interval = + configDeviceCDCACM[p.device.port][id-1].commInterruptInInterval + + // CDC-ACM Data interface + data.interfaceNumber = + configDeviceCDCACM[p.device.port][id-1].dataInterfaceIndex + + data.deviceInterface[0].endpoint[0].address = + configDeviceCDCACM[p.device.port][id-1].dataBulkInEndpoint | + specDescriptorEndpointAddressDirectionIn + + data.deviceInterface[0].endpoint[0].maxPacketSize = + configDeviceCDCACM[p.device.port][id-1].dataBulkInPacketSize + + data.deviceInterface[0].endpoint[1].address = + configDeviceCDCACM[p.device.port][id-1].dataBulkOutEndpoint | + specDescriptorEndpointAddressDirectionOut + + data.deviceInterface[0].endpoint[1].maxPacketSize = + configDeviceCDCACM[p.device.port][id-1].dataBulkOutPacketSize + + // assign our configured CDC-ACM class to the receiver's device and call its + // class initialization routine(s). + cls := p.device.initClass(id, deviceCDCACMConfigInstance[p.device.port], handler) + acm := cls.config[0].driver.(*deviceCDCACM) + + return acm, cls +} + +// OK returns true if and only if the receiver s is equal to statusSuccess. +//go:inline +func (s status) OK() bool { return statusSuccess == s } + +// Error returns a simple descriptive error string of the receiver s. +func (s status) Error() string { + switch s { + case statusSuccess: + return "" + case statusError: + return "failed" + case statusBusy: + return "busy" + case statusInvalidHandle: + return "invalid handle" + case statusInvalidParameter: + return "invalid parameter" + case statusInvalidRequest: + return "invalid request" + case statusControllerNotFound: + return "controller not found" + case statusInvalidController: + return "invalid controller interface" + case statusNotSupported: + return "configuration not supported" + case statusRetry: + return "retry enumeration" + case statusTransferStall: + return "transfer stalled" + case statusTransferFailed: + return "transfer failed" + case statusAllocFail: + return "allocation failed" + case statusLackSwapBuffer: + return "insufficient swap buffer" + case statusTransferCancel: + return "transfer cancelled" + case statusBandwidthFail: + return "bandwidth allocation failed" + case statusMSDStatusFail: + return "mass-storage device failed" + case statusEHCIAttached: + return "host attached" + case statusEHCIDetached: + return "host detached" + case statusDataOverRun: + return "data overrun" + case statusNotImplemented: + return "feature not implemented" + default: + return "unknown error" + } +} + +// leU64 returns a slice containing 8 bytes from the given uint64 u. +// +// The returned bytes have little-endian ordering; that is, the first element +// at index 0 is the least-significant byte in u and index 7 is the most- +// significant byte. +//go:inline +func leU64(u uint64) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0, 0, 0, 0, 0, 0, 0} + } + return []uint8{ + uint8(u), uint8(u >> 8), uint8(u >> 16), uint8(u >> 24), + uint8(u >> 32), uint8(u >> 40), uint8(u >> 48), uint8(u >> 56), + } +} + +// leU32 returns a slice containing 4 bytes from the given uint32 u. +// +// The returned bytes have little-endian ordering; that is, the first element +// at index 0 is the least-significant byte in u and index 3 is the most- +// significant byte. +//go:inline +func leU32(u uint32) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0, 0, 0} + } + return []uint8{ + uint8(u), uint8(u >> 8), uint8(u >> 16), uint8(u >> 24), + } +} + +// leU16 returns a slice containing 2 bytes from the given uint16 u. +// +// The returned bytes have little-endian ordering; that is, the first element +// at index 0 is the least-significant byte in u and index 1 is the most- +// significant byte. +//go:inline +func leU16(u uint16) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0} + } + return []uint8{ + uint8(u), uint8(u >> 8), + } +} + +// beU64 returns a slice containing 8 bytes from the given uint64 u. +// +// The returned bytes have big-endian ordering; that is, the first element at +// index 0 is the most-significant byte in u and index 7 is the least- +// significant byte. +//go:inline +func beU64(u uint64) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0, 0, 0, 0, 0, 0, 0} + } + return []uint8{ + uint8(u >> 56), uint8(u >> 48), uint8(u >> 40), uint8(u >> 32), + uint8(u >> 24), uint8(u >> 16), uint8(u >> 8), uint8(u), + } +} + +// beU32 returns a slice containing 4 bytes from the given uint32 u. +// +// The returned bytes have big-endian ordering; that is, the first element at +// index 0 is the most-significant byte in u and index 3 is the least- +// significant byte. +//go:inline +func beU32(u uint32) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0, 0, 0} + } + return []uint8{ + uint8(u >> 24), uint8(u >> 16), uint8(u >> 8), uint8(u), + } +} + +// beU16 returns a slice containing 2 bytes from the given uint16 u. +// +// The returned bytes have big-endian ordering; that is, the first element at +// index 0 is the most-significant byte in u and index 1 is the least- +// significant byte. +//go:inline +func beU16(u uint16) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0} + } + return []uint8{ + uint8(u >> 8), uint8(u), + } +} + +// revU64 returns the given uint64 u with bytes in the reverse order. +//go:inline +func revU64(u uint64) uint64 { + if u == 0 { + // skip all processing for the common case (u = 0) + return 0 + } + return ((u & 0x00000000000000FF) << 56) | + ((u & 0x000000000000FF00) << 40) | + ((u & 0x0000000000FF0000) << 24) | + ((u & 0x00000000FF000000) << 8) | + ((u & 0x000000FF00000000) >> 8) | + ((u & 0x0000FF0000000000) >> 24) | + ((u & 0x00FF000000000000) >> 40) | + ((u & 0xFF00000000000000) >> 56) +} + +// revU32 returns the given uint32 u with bytes in the reverse order. +//go:inline +func revU32(u uint32) uint32 { + if u == 0 { + // skip all processing for the common case (u = 0) + return 0 + } + return ((u & 0x000000FF) << 24) | ((u & 0x0000FF00) << 8) | + ((u & 0x00FF0000) >> 8) | ((u & 0xFF000000) >> 24) +} + +// revU16 returns the given uint16 u with bytes in the reverse order. +//go:inline +func revU16(u uint16) uint16 { + if u == 0 { + // skip all processing for the common case (u = 0) + return 0 + } + return ((u & 0x00FF) << 8) | ((u & 0xFF00) >> 8) +} + +// packU64 returns a uint64 constructed by concatenating the bytes in slice b. +// +// The least-significant byte in the returned value is the first element at +// index 0 in b and the most significant byte is index 7, if given. If fewer +// than 8 elements are given in b, the corresponding bytes in the returned value +// are all 0. +//go:inline +func packU64(b []uint8) (u uint64) { + for i := 0; i < 8 && i < len(b); i++ { + u |= uint64(b[i]) << (i * 8) + } + return +} + +// packU32 returns a uint32 constructed by concatenating the bytes in slice b. +// +// The least-significant byte in the returned value is the first element at +// index 0 in b and the most significant byte is index 3, if given. If fewer +// than 4 elements are given in b, the corresponding bytes in the returned value +// are all 0. +//go:inline +func packU32(b []uint8) (u uint32) { + for i := 0; i < 4 && i < len(b); i++ { + u |= uint32(b[i]) << (i * 8) + } + return +} + +// packU16 returns a uint16 constructed by concatenating the bytes in slice b. +// +// The least-significant byte in the returned value is the first element at +// index 0 in b and the most significant byte is index 1, if given. If fewer +// than 2 elements are given in b, the corresponding bytes in the returned value +// are all 0. +//go:inline +func packU16(b []uint8) (u uint16) { + for i := 0; i < 2 && i < len(b); i++ { + u |= uint16(b[i]) << (i * 8) + } + return +} diff --git a/src/machine/usb2/dci.go b/src/machine/usb2/dci.go new file mode 100644 index 000000000..4ee63081d --- /dev/null +++ b/src/machine/usb2/dci.go @@ -0,0 +1,9 @@ +package usb2 + +type dci interface { + init() status + enable(enable bool) status + critical(enter bool) status + interrupt() + udelay(micros uint32) +} diff --git a/src/machine/usb2/dci_mimxrt1062.go b/src/machine/usb2/dci_mimxrt1062.go new file mode 100644 index 000000000..86e252f2c --- /dev/null +++ b/src/machine/usb2/dci_mimxrt1062.go @@ -0,0 +1,194 @@ +// +build mimxrt1062 + +package usb2 + +// Implementation of USB device controller interface (dci) for NXP iMXRT1062. + +import ( + "device/arm" + "device/nxp" + "runtime/interrupt" + "runtime/volatile" + "strconv" +) + +// dciCount defines the number of USB cores to configure for device mode. It is +// computed as the sum of all declared device configuration descriptors. +const dciCount = descCDCACMConfigCount + +// dciInterruptPriority defines the priority for all USB device interrupts. +const dciInterruptPriority = 3 + +// deviceController implements USB device controller interface (dci). +type deviceController struct { + core *core // Parent USB core this instance is attached to + port int // USB port index + id int // deviceControllerInstance index + + bus *nxp.USB_Type + phy *nxp.USBPHY_Type + irq interrupt.Interrupt + + cri volatile.Register8 // set to 1 if in critical section, else 0 + ivm uintptr // interrupt state when entering critical section +} + +// deviceControllerInstance provides statically-allocated instances of each USB +// device controller configured on this platform. +var deviceControllerInstance [dciCount]deviceController + +// initDCI initializes and assigns a free device controller instance to the +// given USB port. Returns the initialized device controller or nil if no free +// device controller instances remain. +func initDCI(port int) (dci, status) { + if 0 == dciCount { + return nil, statusInvalidArgument // must have defined device descriptors + } + // Return the first instance whose assigned core is currently nil. + for i := range deviceControllerInstance { + if nil == deviceControllerInstance[i].core { + // Initialize device controller. + deviceControllerInstance[i].core = &coreInstance[port] + deviceControllerInstance[i].port = port + deviceControllerInstance[i].id = i + switch port { + case 0: + deviceControllerInstance[i].bus = nxp.USB1 + deviceControllerInstance[i].phy = nxp.USBPHY1 + deviceControllerInstance[i].irq = + interrupt.New(nxp.IRQ_USB_OTG1, + func(interrupt.Interrupt) { + coreInstance[0].dc.interrupt() + }) + + case 1: + deviceControllerInstance[i].bus = nxp.USB2 + deviceControllerInstance[i].phy = nxp.USBPHY2 + deviceControllerInstance[i].irq = + interrupt.New(nxp.IRQ_USB_OTG2, + func(interrupt.Interrupt) { + //coreInstance[1].dc.interrupt() + }) + } + return &deviceControllerInstance[i], statusOK + } + } + return nil, statusBusy // No free device controller instances available. +} + +func (dc *deviceController) init() status { + + dc.bus.BURSTSIZE.Set(0x0404) + + // if dc.phy.PWD.HasBits((nxp.USBPHY_PWD_RXPWDRX | nxp.USBPHY_PWD_RXPWDDIFF | + // nxp.USBPHY_PWD_RXPWD1PT1 | nxp.USBPHY_PWD_RXPWDENV | + // nxp.USBPHY_PWD_TXPWDV2I | nxp.USBPHY_PWD_TXPWDIBIAS | + // nxp.USBPHY_PWD_TXPWDFS)) || + // dc.bus.USBMODE.HasBits(nxp.USB_USBMODE_CM_Msk) { + // // reset controller if it was already enabled + // dc.phy.CTRL_SET.Set(nxp.USBPHY_CTRL_SFTRST) + // dc.bus.USBCMD.SetBits(nxp.USB_USBCMD_RST) + // for dc.bus.USBCMD.HasBits(nxp.USB_USBCMD_RST) { + // } + // // clear interrupts + // m := arm.DisableInterrupts() + // switch dc.port { + // case 0: + // arm.EnableInterrupts(m & ^uintptr(nxp.IRQ_USB_OTG1)) + // case 1: + // arm.EnableInterrupts(m & ^uintptr(nxp.IRQ_USB_OTG2)) + // } + // dc.phy.CTRL_CLR.Set(nxp.USBPHY_CTRL_SFTRST) + // } + + // reset the controller + dc.phy.CTRL_SET.Set(nxp.USBPHY_CTRL_SFTRST) + dc.bus.USBCMD.SetBits(nxp.USB_USBCMD_RST) + for dc.bus.USBCMD.HasBits(nxp.USB_USBCMD_RST) { + } + // clear interrupts + m := arm.DisableInterrupts() + switch dc.port { + case 0: + arm.EnableInterrupts(m & ^uintptr(nxp.IRQ_USB_OTG1)) + case 1: + arm.EnableInterrupts(m & ^uintptr(nxp.IRQ_USB_OTG2)) + } + dc.phy.CTRL_CLR.Set(nxp.USBPHY_CTRL_CLKGATE | nxp.USBPHY_CTRL_SFTRST) + dc.phy.PWD.Set(0) + + // clear the controller mode field and set to device mode: + // controller mode (CM) 0x0=idle, 0x2=device-only, 0x3=host-only + dc.bus.USBMODE.ReplaceBits(nxp.USB_USBMODE_CM_CM_2, + nxp.USB_USBMODE_CM_Msk>>nxp.USB_USBMODE_CM_Pos, nxp.USB_USBMODE_CM_Pos) + + dc.bus.USBCMD.ClearBits(nxp.USB_USBCMD_ITC_Msk) // no interrupt threshold + dc.bus.USBMODE.SetBits(nxp.USB_USBMODE_SLOM_Msk) // disable setup lockout + dc.bus.USBMODE.ClearBits(nxp.USB_USBMODE_ES_Msk) // use little-endianness + + // configure ENDPOINTLISTADDR + + // enable interrupts + dc.bus.USBINTR.Set( + nxp.USB_USBINTR_UE_Msk | // bus enable + nxp.USB_USBINTR_UEE_Msk | // bus error + nxp.USB_USBINTR_PCE_Msk | // port change detect + nxp.USB_USBINTR_URE_Msk | // bus reset + nxp.USB_USBINTR_SLE) // sleep enable + + // ensure D+ pulled down long enough for host to detect previous disconnect + dc.udelay(5000) + + return statusOK +} + +func (dc *deviceController) enable(enable bool) status { + + dc.irq.SetPriority(dciInterruptPriority) + dc.irq.Enable() + + dc.bus.USBCMD.SetBits(nxp.USB_USBCMD_RS) + + return statusOK +} + +func (dc *deviceController) critical(enter bool) status { + if enter { + // check if critical section already locked + if dc.cri.Get() != 0 { + return statusRetry + } + // lock critical section + dc.cri.Set(1) + // disable interrupts, storing state in receiver + dc.ivm = arm.DisableInterrupts() + } else { + // ensure critical section is locked + if dc.cri.Get() != 0 { + // re-enable interrupts, using state stored in receiver + arm.EnableInterrupts(dc.ivm) + // unlock critical section + dc.cri.Set(0) + } + } + return statusOK +} + +func (dc *deviceController) interrupt() { + // read and clear the interrupts that fired + status := dc.bus.USBSTS.Get() & dc.bus.USBINTR.Get() + dc.bus.USBSTS.Set(status) + + println(strconv.FormatUint(uint64(status), 16)) +} + +// udelay waits for the given number of microseconds before returning. +// We cannot use the sleep timer from this context (import cycle), but we need +// an approximate method to spin CPU cycles for short periods of time. +//go:inline +func (dc *deviceController) udelay(microsec uint32) { + n := cycles(microsec, descCPUFrequencyHz) + for i := uint32(0); i < n; i++ { + arm.Asm(`nop`) + } +} diff --git a/src/machine/usb2/desc.go b/src/machine/usb2/desc.go new file mode 100644 index 000000000..9a5b0e890 --- /dev/null +++ b/src/machine/usb2/desc.go @@ -0,0 +1,334 @@ +package usb2 + +const descUSBSpecVersion = 0x0200 // USB 2.0 + +// USB constants defined per specification. +const ( + + // Descriptor length + descLengthDevice = 18 + descLengthConfigure = 9 + descLengthInterface = 9 + descLengthEndpoint = 7 + descLengthDeviceQualitier = 10 + descLengthOTG = 5 + descLengthBOS = 5 + descLengthEndpointCompanion = 6 + descLengthDevCapTypeUSB20Extension = 7 + descLengthDevCapTypeSuperspeed = 10 + + // Descriptor type + descTypeDevice = 0x01 + descTypeConfigure = 0x02 + descTypeString = 0x03 + descTypeInterface = 0x04 + descTypeEndpoint = 0x05 + descTypeDeviceQualitier = 0x06 + descTypeOtherSpeedConfiguration = 0x07 + descTypeInterfacePower = 0x08 + descTypeOTG = 0x09 + descTypeInterfaceAssociation = 0x0B + descTypeBOS = 0x0F + descTypeDeviceCapability = 0x10 + descTypeHID = 0x21 + descTypeHIDReport = 0x22 + descTypeHIDPhysical = 0x23 + descTypeCDCInterface = 0x24 + descTypeCDCEndpoint = 0x25 + descTypeEndpointCompanion = 0x30 + + // Configuration attributes + descConfigAttrD7Msk = 0x80 + descConfigAttrD7Pos = 7 + descConfigAttrSelfPoweredMsk = 0x40 + descConfigAttrSelfPoweredPos = 6 + descConfigAttrRemoteWakeupMsk = 0x20 + descConfigAttrRemoteWakeupPos = 5 + + // Endpoint type + descEndptTypeControl = 0x00 + descEndptTypeIsochronous = 0x01 + descEndptTypeBulk = 0x02 + descEndptTypeInterrupt = 0x03 + + // Endpoint address + descEndptAddrNumberMsk = 0x0F + descEndptAddrNumberPos = 0 + descEndptAddrDirectionMsk = 0x80 + descEndptAddrDirectionPos = 7 + descEndptAddrDirectionOut = 0 + descEndptAddrDirectionIn = 0x80 + + // Endpoint attributes + descEndptAttrTypeMsk = 0x03 + descEndptAttrNumberPos = 0 + descEndptAttrSyncTypeMsk = 0x0C + descEndptAttrSyncTypePos = 2 + descEndptAttrSyncTypeNoSync = 0x00 + descEndptAttrSyncTypeAsync = 0x04 + descEndptAttrSyncTypeAdaptive = 0x08 + descEndptAttrSyncTypeSync = 0x0C + descEndptAttrUsageTypeMsk = 0x30 + descEndptAttrUsageTypePos = 4 + descEndptAttrUsageTypeDataEndpoint = 0x00 + descEndptAttrUsageTypeFeedEndpoint = 0x10 + descEndptAttrUsageTypeFeedDataEndpoint = 0x20 + + // Endpoint max packet size + descEndptMaxPktSizeSizeMsk = 0x07FF + descEndptMaxPktSizeMultTransMsk = 0x1800 + descEndptMaxPktSizeMultTransPos = 11 + descEndptMaxPktSizeMaximum = 64 + + // OTG attributes + descOTGAttrSRPMsk = 0x01 + descOTGAttrHNPMsk = 0x02 + descOTGAttrADPMsk = 0x04 + + // Device capability type + descDevCapTypeWireless = 0x01 + descDevCapTypeUSB20Extension = 0x02 + descDevCapTypeSuperspeed = 0x03 + + // Device capability attributes (USB 2.0 extension) + descDevCapExtAttrLPMMsk = 0x02 + descDevCapExtAttrLPMPos = 1 + descDevCapExtAttrBESLMsk = 0x04 + descDevCapExtAttrBESLPos = 2 +) + +// USB CDC constants defined per specification. +const ( + + // Device class + descCDCComm = 0x02 // communication/control + descCDCData = 0x0A // data + + // Communication/control subclass + descCDCSubNone = 0x00 + descCDCSubDirectLineControl = 0x01 + descCDCSubAbstractControl = 0x02 + descCDCSubTelephoneControl = 0x03 + descCDCSubMultiChannelControl = 0x04 + descCDCSubCAPIControl = 0x05 + descCDCSubEthernetNetworkingControl = 0x06 + descCDCSubATMNetworkingControl = 0x07 + descCDCSubWirelessHandsetControl = 0x08 + descCDCSubDeviceManagement = 0x09 + descCDCSubMobileDirectLine = 0x0A + descCDCSubOBEX = 0x0B + descCDCSubEthernetEmulation = 0x0C + + // Communication/control protocol + descCDCProtoNone = 0x00 // also for data class + descCDCProtoAT250 = 0x01 + descCDCProtoATPCCA101 = 0x02 + descCDCProtoATPCCA101AnnexO = 0x03 + descCDCProtoATGSM707 = 0x04 + descCDCProtoAT3GPP27007 = 0x05 + descCDCProtoATTIACDMA = 0x06 + descCDCProtoEthernetEmulation = 0x07 + descCDCProtoExternal = 0xFE + descCDCProtoVendorSpecific = 0xFF // also for data class + + // Data protocol + descCDCProtoPyhsicalInterface = 0x30 + descCDCProtoHDLC = 0x31 + descCDCProtoTransparent = 0x32 + descCDCProtoManagement = 0x50 + descCDCProtoDataLinkQ931 = 0x51 + descCDCProtoDataLinkQ921 = 0x52 + descCDCProtoDataCompressionV42BIS = 0x90 + descCDCProtoEuroISDN = 0x91 + descCDCProtoRateAdaptionISDNV24 = 0x92 + descCDCProtoCAPICommands = 0x93 + descCDCProtoHostBasedDriver = 0xFD + descCDCProtoUnitFunctional = 0xFE + + // Functional descriptor length + descCDCLengthFuncHeader = 5 + descCDCLengthFuncCallManagement = 5 + descCDCLengthFuncAbstractControl = 4 + descCDCLengthFuncUnion = 5 + + // Functional descriptor type + descCDCTypeFuncHeader = 0x00 + descCDCTypeFuncCallManagement = 0x01 + descCDCTypeFuncAbstractControl = 0x02 + descCDCTypeFuncDirectLine = 0x03 + descCDCTypeFuncTelephoneRinger = 0x04 + descCDCTypeFuncTelephoneReport = 0x05 + descCDCTypeFuncUnion = 0x06 + descCDCTypeFuncCountrySelect = 0x07 + descCDCTypeFuncTelephoneModes = 0x08 + descCDCTypeFuncTerminal = 0x09 + descCDCTypeFuncNetworkChannel = 0x0A + descCDCTypeFuncProtocolUnit = 0x0B + descCDCTypeFuncExtensionUnit = 0x0C + descCDCTypeFuncMultiChannel = 0x0D + descCDCTypeFuncCAPIControl = 0x0E + descCDCTypeFuncEthernetNetworking = 0x0F + descCDCTypeFuncATMNetworking = 0x10 + descCDCTypeFuncWirelessControl = 0x11 + descCDCTypeFuncMobileDirectLine = 0x12 + descCDCTypeFuncMDLMDetail = 0x13 + descCDCTypeFuncDeviceManagement = 0x14 + descCDCTypeFuncOBEX = 0x15 + descCDCTypeFuncCommandSet = 0x16 + descCDCTypeFuncCommandSetDetail = 0x17 + descCDCTypeFuncTelephoneControl = 0x18 + descCDCTypeFuncOBEXServiceID = 0x19 +) + +// Common configuration constants for the USB CDC-ACM (single) device class. +const ( + // Interfaces for the first CDC-ACM device (index 1). + descCDCACM0InterfaceCount = 2 + descCDCACM0InterfaceCtrl = 0 + descCDCACM0InterfaceData = 1 + // Endpoints for the first CDC-ACM device (index 1). + descCDCACM0EndpointCount = 4 + descCDCACM0EndpointStatus = 2 // Communication/control interrupt input + descCDCACM0EndpointDataRx = 3 // Bulk data output + descCDCACM0EndpointDataTx = 4 // Bulk data input + + // Size of all CDC-ACM configuration descriptors. + descCDCACMConfigSize = uint16( + descLengthConfigure + // configuration + descLengthInterface + // communication/control interface + descCDCLengthFuncHeader + // CDC header + descCDCLengthFuncCallManagement + // CDC call management + descCDCLengthFuncAbstractControl + // CDC abstract control + descCDCLengthFuncUnion + // CDC union + descLengthEndpoint + // communication/control input endpoint + descLengthInterface + // data interface + descLengthEndpoint + // data input endpoint + descLengthEndpoint) // data output endpoint + // Attributes of all CDC-ACM configuration descriptors. + descCDCACMConfigAttr = descConfigAttrD7Msk | // Bit 7: reserved (1) + (1 << descConfigAttrSelfPoweredPos) | // Bit 6: self-powered + (0 << descConfigAttrRemoteWakeupPos) | // Bit 5: remote wakeup + 0 // Bits 0-4: reserved (0) +) + +// Common descriptors for the USB CDC-ACM (single) device class. +var ( + // descDeviceCDCACM holds the default device descriptors for CDC-ACM devices. + descDeviceCDCACM = [descCDCACMConfigCount][descLengthDevice]uint8{ + { + descLengthDevice, // Size of this descriptor in bytes + descTypeDevice, // DEVICE Descriptor Type + lsU8(descUSBSpecVersion), // USB Specification Release Number in BCD (low) + msU8(descUSBSpecVersion), // USB Specification Release Number in BCD (high) + descCDCComm, // Class code (assigned by the USB-IF). + descCDCSubNone, // Subclass code (assigned by the USB-IF). + descCDCProtoNone, // Protocol code (assigned by the USB-IF). + descEndptMaxPktSizeMaximum, // Maximum packet size for endpoint zero (8, 16, 32, or 64) + lsU8(descVendorID), // Vendor ID (low) (assigned by the USB-IF) + msU8(descVendorID), // Vendor ID (high) (assigned by the USB-IF) + lsU8(descProductID), // Product ID (low) (assigned by the manufacturer) + msU8(descProductID), // Product ID (high) (assigned by the manufacturer) + lsU8(descReleaseID), // Device release number in BCD (low) + msU8(descReleaseID), // Device release number in BCD (high) + 1, // Index of string descriptor describing manufacturer + 2, // Index of string descriptor describing product + 0, // Index of string descriptor describing the device's serial number + descCDCACMConfigCount, // Number of possible configurations + }, + } + + // descConfigCDCACM holds the default configuration descriptors for CDC-ACM + // devices. + descConfigCDCACM = [descCDCACMConfigCount][descCDCACMConfigSize]uint8{ + { + descLengthConfigure, // Size of this descriptor in bytes + descTypeConfigure, // Descriptor Type + lsU8(descCDCACMConfigSize), // Total length of data returned for this configuration (low) + msU8(descCDCACMConfigSize), // Total length of data returned for this configuration (high) + descCDCACM0InterfaceCount, // Number of interfaces supported by this configuration + 1, // Value to use to select this configuration (1 = CDC-ACM[0]) + 0, // Index of string descriptor describing this configuration + descCDCACMConfigAttr, // Configuration attributes + descCDCACMMaxPower, // Max power consumption when fully-operational (2 mA units) + + // Communication/Control Interface Descriptor + descLengthInterface, // Descriptor length + descTypeInterface, // Descriptor type + descCDCACM0InterfaceCtrl, // Interface index + 0, // Alternate setting + 1, // Number of endpoints + descCDCComm, // Class code + descCDCSubAbstractControl, // Subclass code + descCDCProtoNone, // Protocol code (NOTE: Teensyduino defines this as 1 [AT V.250]) + 0, // Interface Description String Index + + // CDC Header Functional Descriptor + descCDCLengthFuncHeader, // Size of this descriptor in bytes + descTypeCDCInterface, // Descriptor Type + descCDCTypeFuncHeader, // Descriptor Subtype + 0x10, // USB CDC specification version 1.10 (low) + 0x01, // USB CDC specification version 1.10 (high) + + // CDC Call Management Functional Descriptor + descCDCLengthFuncCallManagement, // Size of this descriptor in bytes + descTypeCDCInterface, // Descriptor Type + descCDCTypeFuncCallManagement, // Descriptor Subtype + 0x01, // Capabilities + descCDCACM0InterfaceData, // Data Interface + + // CDC Abstract Control Management Functional Descriptor + descCDCLengthFuncAbstractControl, // Size of this descriptor in bytes + descTypeCDCInterface, // Descriptor Type + descCDCTypeFuncAbstractControl, // Descriptor Subtype + 0x06, // Capabilities + + // CDC Union Functional Descriptor + descCDCLengthFuncUnion, // Size of this descriptor in bytes + descTypeCDCInterface, // Descriptor Type + descCDCTypeFuncUnion, // Descriptor Subtype + descCDCACM0InterfaceCtrl, // Controlling interface index + descCDCACM0InterfaceData, // Controlled interface index + + // Communication/Control Notification Endpoint descriptor + descLengthEndpoint, // Size of this descriptor in bytes + descTypeEndpoint, // Descriptor Type + descCDCACM0EndpointStatus | // Endpoint address + descEndptAddrDirectionIn, + descEndptTypeInterrupt, // Attributes + lsU8(descCDCACMStatusPacketSize), // Max packet size (low) + msU8(descCDCACMStatusPacketSize), // Max packet size (high) + 8, // Polling Interval + + // Data Interface Descriptor + descLengthInterface, // Interface length + descTypeInterface, // Interface type + descCDCACM0InterfaceData, // Interface index + 0, // Alternate setting + 2, // Number of endpoints + descCDCData, // Class code + descCDCSubNone, // Subclass code + descCDCProtoNone, // Protocol code + 0, // Interface Description String Index + + // Data Bulk Rx Endpoint descriptor + descLengthEndpoint, // Size of this descriptor in bytes + descTypeEndpoint, // Descriptor Type + descCDCACM0EndpointDataRx | // Endpoint address + descEndptAddrDirectionOut, + descEndptTypeBulk, // Attributes + lsU8(descCDCACMDataRxPacketSize), // Max packet size (low) + msU8(descCDCACMDataRxPacketSize), // Max packet size (high) + 0, // Polling Interval + + // Data Bulk Tx Endpoint descriptor + descLengthEndpoint, // Size of this descriptor in bytes + descTypeEndpoint, // Descriptor Type + descCDCACM0EndpointDataTx | // Endpoint address + descEndptAddrDirectionIn, + descEndptTypeBulk, // Attributes + lsU8(descCDCACMDataTxPacketSize), // Max packet size (low) + msU8(descCDCACMDataTxPacketSize), // Max packet size (high) + 0, // Polling Interval + }, + } +) diff --git a/src/machine/usb2/desc_mimxrt1062.go b/src/machine/usb2/desc_mimxrt1062.go new file mode 100644 index 000000000..ea4a40c99 --- /dev/null +++ b/src/machine/usb2/desc_mimxrt1062.go @@ -0,0 +1,32 @@ +package usb2 + +// descCPUFrequencyHz defines the target CPU frequency (Hz). +const descCPUFrequencyHz = 600000000 + +// General USB device identification constants. +const ( + descVendorID = 0xABCD + descProductID = 0x1234 + descReleaseID = 0x0101 + + descVendor = "NXP Semiconductors" + descProduct = "TinyGo USB" +) + +// Constants for USB CDC-ACM device classes. +const ( + // descCDCACMConfigCount defines the number of USB cores that will be + // configured as CDC-ACM (single) devices. + descCDCACMConfigCount = 1 + + descCDCACMMaxPower = 50 // 100 mA + + descCDCACMStatusPacketSize = 16 + descCDCACMDataRxPacketSize = descCDCACMDataRxFSPacketSize // full-speed + descCDCACMDataTxPacketSize = descCDCACMDataTxFSPacketSize // full-speed + + descCDCACMDataRxFSPacketSize = 64 // full-speed + descCDCACMDataTxFSPacketSize = 64 // full-speed + descCDCACMDataRxHSPacketSize = 512 // high-speed + descCDCACMDataTxHSPacketSize = 512 // high-speed +) diff --git a/src/machine/usb2/hci.go b/src/machine/usb2/hci.go new file mode 100644 index 000000000..7997c2351 --- /dev/null +++ b/src/machine/usb2/hci.go @@ -0,0 +1,9 @@ +package usb2 + +type hci interface { + init() status + enable(enable bool) status + critical(enter bool) status + interrupt() + udelay(micros uint32) +} diff --git a/src/machine/usb2/hci_mimxrt1062.go b/src/machine/usb2/hci_mimxrt1062.go new file mode 100644 index 000000000..85f66ecc2 --- /dev/null +++ b/src/machine/usb2/hci_mimxrt1062.go @@ -0,0 +1,126 @@ +// +build mimxrt1062 + +package usb2 + +// Implementation of USB host controller interface (hci) for NXP iMXRT1062. + +import ( + "device/arm" + "device/nxp" + "runtime/interrupt" + "runtime/volatile" +) + +// hciCount defines the number of USB cores to configure for host mode. It is +// computed as the sum of all declared host configuration descriptors. +const hciCount = 0 + +// hciInterruptPriority defines the priority for all USB host interrupts. +const hciInterruptPriority = 3 + +// hostController implements USB host controller interface (hci). +type hostController struct { + core *core // Parent USB core this instance is attached to + port int // USB port index + id int // hostControllerInstance index + + bus *nxp.USB_Type + phy *nxp.USBPHY_Type + irq interrupt.Interrupt + + cri volatile.Register8 // set to 1 if in critical section, else 0 + ivm uintptr // interrupt state when entering critical section +} + +// hostControllerInstance provides statically-allocated instances of each USB +// host controller configured on this platform. +var hostControllerInstance [hciCount]hostController + +// initHCI initializes and assigns a free host controller instance to the given +// USB port. Returns the initialized host controller or nil if no free host +// controller instances remain. +func initHCI(port int) (hci, status) { + if 0 == hciCount { + return nil, statusInvalidArgument // must have defined host descriptors + } + // Return the first instance whose assigned core is currently nil. + for i := range hostControllerInstance { + if nil == hostControllerInstance[i].core { + // Initialize host controller. + hostControllerInstance[i].core = &coreInstance[port] + hostControllerInstance[i].port = port + hostControllerInstance[i].id = i + switch port { + case 0: + hostControllerInstance[i].bus = nxp.USB1 + hostControllerInstance[i].phy = nxp.USBPHY1 + hostControllerInstance[i].irq = + interrupt.New(nxp.IRQ_USB_OTG1, + func(interrupt.Interrupt) { + coreInstance[0].hc.interrupt() + }) + + case 1: + hostControllerInstance[i].bus = nxp.USB2 + hostControllerInstance[i].phy = nxp.USBPHY2 + hostControllerInstance[i].irq = + interrupt.New(nxp.IRQ_USB_OTG2, + func(interrupt.Interrupt) { + //coreInstance[1].hc.interrupt() + }) + } + return &hostControllerInstance[i], statusOK + } + } + return nil, statusBusy // No free host controller instances available. +} + +func (hc *hostController) init() status { + + return statusOK +} + +func (hc *hostController) enable(enable bool) status { + + hc.irq.SetPriority(hciInterruptPriority) + hc.irq.Enable() + + return statusOK +} + +func (hc *hostController) critical(enter bool) status { + if enter { + // check if critical section already locked + if hc.cri.Get() != 0 { + return statusRetry + } + // lock critical section + hc.cri.Set(1) + // disable interrupts, storing state in receiver + hc.ivm = arm.DisableInterrupts() + } else { + // ensure critical section is locked + if hc.cri.Get() != 0 { + // re-enable interrupts, using state stored in receiver + arm.EnableInterrupts(hc.ivm) + // unlock critical section + hc.cri.Set(0) + } + } + return statusOK +} + +func (hc *hostController) interrupt() { + +} + +// udelay waits for the given number of microseconds before returning. +// We cannot use the sleep timer from this context (import cycle), but we need +// an approximate method to spin CPU cycles for short periods of time. +//go:inline +func (hc *hostController) udelay(microsec uint32) { + n := cycles(microsec, descCPUFrequencyHz) + for i := uint32(0); i < n; i++ { + arm.Asm(`nop`) + } +} diff --git a/src/machine/usb2/uart.go b/src/machine/usb2/uart.go new file mode 100644 index 000000000..83bd9ffe0 --- /dev/null +++ b/src/machine/usb2/uart.go @@ -0,0 +1,53 @@ +package usb2 + +import ( + "errors" +) + +var ( + ErrInvalidPort = errors.New("invalid USB port") +) + +type ( + UARTConfig struct { + BaudRate uint32 + } + + // UART represents a virtual serial (UART) device emulation using the USB + // CDC-ACM device class driver. + UART struct { + port int // USB port (core index, e.g., 0-1) + core *core + } +) + +func (uart *UART) Configure(config UARTConfig) error { + + if uart.port >= CoreCount || uart.port >= dciCount || + uart.port >= descCDCACMConfigCount { + return ErrInvalidPort + } + + // use default configuration index (1-based index; 0=invalid) + // uart.config = configDeviceCDCACMConfigurationIndex + + // modify the global basic configuration struct configDeviceCDCACM for our USB + // port and configuration index. + // + // these settings are copied into the real CDC-ACM object, using interface + // deviceClassDriver, via initialization method (*deviceCDCACM).init(). + + // change baud rate from default, if provided + //if config.BaudRate != 0 { + // configDeviceCDCACM[uart.port][uart.config-1].lineCodingBaudRate = + // config.BaudRate + //} + + // verify we have a free USB port and take ownership of it + var st status + uart.core, st = initCore(uart.port, modeDevice) + if !st.ok() { + return ErrInvalidPort + } + return nil +} diff --git a/src/machine/usb2/usb.go b/src/machine/usb2/usb.go new file mode 100644 index 000000000..c450f86f8 --- /dev/null +++ b/src/machine/usb2/usb.go @@ -0,0 +1,99 @@ +package usb2 + +// Hardware abstraction for USB ports configured as either host or device. + +// core represents the core of a USB port configured as either host or device. +type core struct { + port int + mode int + dc dci + hc hci +} + +// Constant definitions for USB core operating modes. +const ( + modeIdle = 0 + modeDevice = 1 + modeHost = 2 +) + +// CoreCount defines the total number of USB cores to configure in device or +// host mode. +const CoreCount = dciCount + hciCount + +// coreInstance provides statically-allocated instances of each USB core +// configured on this platform. +var coreInstance [CoreCount]core + +// status represents the return code of a subroutine. +type status uint8 + +// Constant definitions for all status codes used within the package. +const ( + statusOK status = iota // Success + statusBusy // Busy + statusRetry // Retry + statusInvalidArgument // Invalid argument +) + +func (st status) ok() bool { return statusOK == st } + +// initCore initializes a free USB core with given operating mode on the USB +// port at given index, if available. Returns a reference to the initialized +// core or nil if the core is unavailable. +func initCore(port, mode int) (*core, status) { + + if port < 0 || port >= CoreCount { + return nil, statusInvalidArgument + } + if modeIdle != coreInstance[port].mode { + return nil, statusBusy + } + + switch mode { + case modeDevice: + // Allocate a free device controller and install interrupts + dc, st := initDCI(port) + if !st.ok() { + return nil, st + } + // Initialize buffers and device descriptors + if st = dc.init(); !st.ok() { + return nil, st + } + coreInstance[port].port = port + coreInstance[port].mode = mode + coreInstance[port].dc = dc + // Enable interrupts and enter runtime + if st = dc.enable(true); !st.ok() { + coreInstance[port].mode = modeIdle + coreInstance[port].dc = nil + return nil, st + } + + case modeHost: + // Allocate a free host controller and install interrupts + hc, st := initHCI(port) + if !st.ok() { + return nil, st + } + // Initialize buffers and device descriptors + if st = hc.init(); !st.ok() { + return nil, st + } + coreInstance[port].port = port + coreInstance[port].mode = mode + coreInstance[port].hc = hc + // Enable interrupts and enter runtime + if st = hc.enable(true); !st.ok() { + coreInstance[port].mode = modeIdle + coreInstance[port].hc = nil + return nil, st + } + + default: + return nil, statusInvalidArgument + } + + return &coreInstance[port], statusOK +} diff --git a/src/machine/usb2/util.go b/src/machine/usb2/util.go new file mode 100644 index 000000000..4b78c9fb5 --- /dev/null +++ b/src/machine/usb2/util.go @@ -0,0 +1,194 @@ +package usb2 + +// leU64 returns a slice containing 8 bytes from the given uint64 u. +// +// The returned bytes have little-endian ordering; that is, the first element +// at index 0 is the least-significant byte in u and index 7 is the most- +// significant byte. +//go:inline +func leU64(u uint64) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0, 0, 0, 0, 0, 0, 0} + } + return []uint8{ + uint8(u), uint8(u >> 8), uint8(u >> 16), uint8(u >> 24), + uint8(u >> 32), uint8(u >> 40), uint8(u >> 48), uint8(u >> 56), + } +} + +// leU32 returns a slice containing 4 bytes from the given uint32 u. +// +// The returned bytes have little-endian ordering; that is, the first element +// at index 0 is the least-significant byte in u and index 3 is the most- +// significant byte. +//go:inline +func leU32(u uint32) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0, 0, 0} + } + return []uint8{ + uint8(u), uint8(u >> 8), uint8(u >> 16), uint8(u >> 24), + } +} + +// leU16 returns a slice containing 2 bytes from the given uint16 u. +// +// The returned bytes have little-endian ordering; that is, the first element +// at index 0 is the least-significant byte in u and index 1 is the most- +// significant byte. +//go:inline +func leU16(u uint16) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0} + } + return []uint8{ + uint8(u), uint8(u >> 8), + } +} + +// beU64 returns a slice containing 8 bytes from the given uint64 u. +// +// The returned bytes have big-endian ordering; that is, the first element at +// index 0 is the most-significant byte in u and index 7 is the least- +// significant byte. +//go:inline +func beU64(u uint64) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0, 0, 0, 0, 0, 0, 0} + } + return []uint8{ + uint8(u >> 56), uint8(u >> 48), uint8(u >> 40), uint8(u >> 32), + uint8(u >> 24), uint8(u >> 16), uint8(u >> 8), uint8(u), + } +} + +// beU32 returns a slice containing 4 bytes from the given uint32 u. +// +// The returned bytes have big-endian ordering; that is, the first element at +// index 0 is the most-significant byte in u and index 3 is the least- +// significant byte. +//go:inline +func beU32(u uint32) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0, 0, 0} + } + return []uint8{ + uint8(u >> 24), uint8(u >> 16), uint8(u >> 8), uint8(u), + } +} + +// beU16 returns a slice containing 2 bytes from the given uint16 u. +// +// The returned bytes have big-endian ordering; that is, the first element at +// index 0 is the most-significant byte in u and index 1 is the least- +// significant byte. +//go:inline +func beU16(u uint16) []uint8 { + if u == 0 { + // skip all processing for the common case (u = 0) + return []uint8{0, 0} + } + return []uint8{ + uint8(u >> 8), uint8(u), + } +} + +// revU64 returns the given uint64 u with bytes in the reverse order. +//go:inline +func revU64(u uint64) uint64 { + if u == 0 { + // skip all processing for the common case (u = 0) + return 0 + } + return ((u & 0x00000000000000FF) << 56) | + ((u & 0x000000000000FF00) << 40) | + ((u & 0x0000000000FF0000) << 24) | + ((u & 0x00000000FF000000) << 8) | + ((u & 0x000000FF00000000) >> 8) | + ((u & 0x0000FF0000000000) >> 24) | + ((u & 0x00FF000000000000) >> 40) | + ((u & 0xFF00000000000000) >> 56) +} + +// revU32 returns the given uint32 u with bytes in the reverse order. +//go:inline +func revU32(u uint32) uint32 { + if u == 0 { + // skip all processing for the common case (u = 0) + return 0 + } + return ((u & 0x000000FF) << 24) | ((u & 0x0000FF00) << 8) | + ((u & 0x00FF0000) >> 8) | ((u & 0xFF000000) >> 24) +} + +// revU16 returns the given uint16 u with bytes in the reverse order. +//go:inline +func revU16(u uint16) uint16 { + if u == 0 { + // skip all processing for the common case (u = 0) + return 0 + } + return ((u & 0x00FF) << 8) | ((u & 0xFF00) >> 8) +} + +// packU64 returns a uint64 constructed by concatenating the bytes in slice b. +// +// The least-significant byte in the returned value is the first element at +// index 0 in b and the most significant byte is index 7, if given. If fewer +// than 8 elements are given in b, the corresponding bytes in the returned value +// are all 0. +//go:inline +func packU64(b []uint8) (u uint64) { + for i := 0; i < 8 && i < len(b); i++ { + u |= uint64(b[i]) << (i * 8) + } + return +} + +// packU32 returns a uint32 constructed by concatenating the bytes in slice b. +// +// The least-significant byte in the returned value is the first element at +// index 0 in b and the most significant byte is index 3, if given. If fewer +// than 4 elements are given in b, the corresponding bytes in the returned value +// are all 0. +//go:inline +func packU32(b []uint8) (u uint32) { + for i := 0; i < 4 && i < len(b); i++ { + u |= uint32(b[i]) << (i * 8) + } + return +} + +// packU16 returns a uint16 constructed by concatenating the bytes in slice b. +// +// The least-significant byte in the returned value is the first element at +// index 0 in b and the most significant byte is index 1, if given. If fewer +// than 2 elements are given in b, the corresponding bytes in the returned value +// are all 0. +//go:inline +func packU16(b []uint8) (u uint16) { + for i := 0; i < 2 && i < len(b); i++ { + u |= uint16(b[i]) << (i * 8) + } + return +} + +// msU8 returns the most-significant byte of u. +//go:inline +func msU8(u uint16) uint8 { return uint8(u >> 8) } + +// lsU8 returns the least-significant byte of u. +//go:inline +func lsU8(u uint16) uint8 { return uint8(u) } + +// cycles converts the given number of microseconds to CPU cycles for a CPU with +// given frequency. +//go:inline +func cycles(microsec, cpuFreqHz uint32) uint32 { + return uint32((uint64(microsec) * uint64(cpuFreqHz)) / 1000000) +} diff --git a/src/runtime/runtime_cortexm_hardfault_debug.go b/src/runtime/runtime_cortexm_hardfault_debug.go index ee5e97eac..5722e4731 100644 --- a/src/runtime/runtime_cortexm_hardfault_debug.go +++ b/src/runtime/runtime_cortexm_hardfault_debug.go @@ -36,7 +36,7 @@ func handleHardFault(sp *interruptStack) { if fault.Mem().WhileUnstackingException() { print(" while unstacking exception") } - if fault.Mem().WileStackingException() { + if fault.Mem().WhileStackingException() { print(" while stacking exception") } if fault.Mem().DuringFPLazyStatePres() { @@ -162,13 +162,13 @@ func (fs MemFaultStatus) WhileUnstackingException() bool { return fs&arm.SCB_CFSR_MUNSTKERR != 0 } -// WileStackingException: stacking for an exception entry has caused one or more +// WhileStackingException: stacking for an exception entry has caused one or more // access violations // // "When this bit is 1, the SP is still adjusted but the values in the context // area on the stack might be incorrect. The processor has not written a fault // address to the MMAR." -func (fs MemFaultStatus) WileStackingException() bool { +func (fs MemFaultStatus) WhileStackingException() bool { return fs&arm.SCB_CFSR_MSTKERR != 0 } diff --git a/src/runtime/runtime_mimxrt1062.go b/src/runtime/runtime_mimxrt1062.go index fc0ec353a..7df0bdc7f 100644 --- a/src/runtime/runtime_mimxrt1062.go +++ b/src/runtime/runtime_mimxrt1062.go @@ -35,7 +35,7 @@ func main() { // initialize cache and MPU initCache() - // enable SysTick, GPIO, and peripherals + // enable SysTick, GPIO, USB, and other peripherals initPeripherals() // reenable interrupts @@ -107,7 +107,8 @@ func initPeripherals() { initPins() // configure GPIO enablePeripheralClocks() // activate peripheral clock gates - initUART() // configure UART (initialized first for debugging) + initUSB() // configure USB CDC-ACM (UART0) + initUART() // configure hardware UART (UART1) } func initPins() { @@ -122,6 +123,10 @@ func initUART() { machine.Serial.Configure(machine.UARTConfig{}) } +func initUSB() { + machine.USBCDC0.Configure(machine.UARTConfig{}) +} + func putchar(c byte) { machine.Serial.WriteByte(c) } diff --git a/src/runtime/runtime_mimxrt1062_clock.go b/src/runtime/runtime_mimxrt1062_clock.go index 9dcca934f..64cf01ac8 100644 --- a/src/runtime/runtime_mimxrt1062_clock.go +++ b/src/runtime/runtime_mimxrt1062_clock.go @@ -36,15 +36,31 @@ var ( Denominator: 1, // 30-bit DENOM of fractional loop divider Src: 0, // bypass clock source, 0=OSC24M, 1=CLK1_P & CLK1_N } - Usb1PllConfig = nxp.ClockConfigUsbPll{ - Instance: 1, // USB PLL instance - LoopDivider: 0, // PLL loop divider, Fout=Fin*20 - Src: 0, // bypass clock source, 0=OSC24M, 1=CLK1_P & CLK1_N + Usb1PhyConfig = nxp.ClockConfigUsbPhy{ + Instance: 1, // USB PHY number (1 or 2) + XtalFreq: OSC_FREQ, // External reference clock frequency (Hz) + DCal: 0xC, // Decode to trim nominal 17.78mA current source + TxCal45DP: 0x6, // Decode to trim nominal 45-Ohm series Rp on USB D+ + TxCal45DM: 0x6, // Decode to trim nominal 45-Ohm series Rp on USB D- + PllConfig: nxp.ClockConfigUsbPll{ + Instance: 1, // USB PLL number (1 or 2) + LoopDivider: 0, // PLL loop divider (0 [Fout=Fref*20] or 1 [Fout=Fref*22]) + Src: 0, // PLL bypass clock source (0 [OSC24M] or 1 [CLK1_P & CLK1_N]) + Pfd: nil, // Phase fractional divisors (len=4, or nil for boot default) + }, } - Usb2PllConfig = nxp.ClockConfigUsbPll{ - Instance: 2, // USB PLL instance - LoopDivider: 0, // PLL loop divider, Fout=Fin*20 - Src: 0, // bypass clock source, 0=OSC24M, 1=CLK1_P & CLK1_N + Usb2PhyConfig = nxp.ClockConfigUsbPhy{ + Instance: 2, // USB PHY number (1 or 2) + XtalFreq: OSC_FREQ, // External reference clock frequency (Hz) + DCal: 0xC, // Decode to trim the nominal 17.78mA current source + TxCal45DP: 0x6, // Decode to trim the nominal 45-Ohm series Rp on USB D+ + TxCal45DM: 0x6, // Decode to trim the nominal 45-Ohm series Rp on USB D- + PllConfig: nxp.ClockConfigUsbPll{ + Instance: 2, // USB PLL number (1 or 2) + LoopDivider: 0, // PLL loop divider (0 [Fout=Fref*20] or 1 [Fout=Fref*22]) + Src: 0, // PLL bypass clock source (0 [OSC24M] or 1 [CLK1_P & CLK1_N]) + Pfd: nil, // Phase fractional divisors (len=4, or nil for boot default) + }, } ) @@ -86,7 +102,7 @@ func initClocks() { // set VDD_SOC to 1.275V, necessary to config AHB to 600 MHz nxp.DCDC.REG3.Set((nxp.DCDC.REG3.Get() & ^uint32(nxp.DCDC_REG3_TRG_Msk)) | - ((13 << nxp.DCDC_REG3_TRG_Pos) & nxp.DCDC_REG3_TRG_Msk)) + ((0x13 << nxp.DCDC_REG3_TRG_Pos) & nxp.DCDC_REG3_TRG_Msk)) // wait until DCDC_STS_DC_OK bit is asserted for !nxp.DCDC.REG0.HasBits(nxp.DCDC_REG0_STS_DC_OK_Msk) { @@ -105,6 +121,8 @@ func initClocks() { nxp.DivIpArm.Div(1) // divide ARM_PODF (DIV2) nxp.DivIpPeriphClk2.Div(0) // divide PERIPH_CLK2_PODF (DIV1) + nxp.ClockIpUsbOh3.Enable(false) // disable USB + nxp.ClockIpGpt1.Enable(false) // disable GPT/PIT nxp.ClockIpGpt1S.Enable(false) // nxp.ClockIpGpt2.Enable(false) // @@ -113,6 +131,11 @@ func initClocks() { nxp.DivIpPerclk.Div(0) // divide PERCLK_PODF (DIV1) + nxp.ClockIpGpio1.Enable(false) // disable GPIO + nxp.ClockIpGpio2.Enable(false) // + nxp.ClockIpGpio3.Enable(false) // + nxp.ClockIpGpio4.Enable(false) // + nxp.ClockIpUsdhc1.Enable(false) // disable USDHC1 nxp.DivIpUsdhc1.Div(1) // divide USDHC1_PODF (DIV2) nxp.MuxIpUsdhc1.Mux(1) // USDHC1 select PLL2_PFD0 @@ -121,9 +144,9 @@ func initClocks() { nxp.MuxIpUsdhc2.Mux(1) // USDHC2 select PLL2_PFD0 nxp.ClockIpSemc.Enable(false) // disable SEMC - nxp.DivIpSemc.Div(1) // divide SEMC_PODF (DIV2) + nxp.DivIpSemc.Div(7) // divide SEMC_PODF (DIV8) nxp.MuxIpSemcAlt.Mux(0) // SEMC_ALT select PLL2_PFD2 - nxp.MuxIpSemc.Mux(1) // SEMC select SEMC_ALT + nxp.MuxIpSemc.Mux(0) // SEMC select PERIPH_CLK if false { // TODO: external flash is on this bus, configured via DCD block @@ -192,7 +215,7 @@ func initClocks() { nxp.ClockIpLcdPixel.Enable(false) // disable LCDIF nxp.DivIpLcdifPre.Div(1) // divide LCDIF_PRED (DIV2) nxp.DivIpLcdif.Div(3) // divide LCDIF_CLK_PODF (DIV4) - nxp.MuxIpLcdifPre.Mux(5) // LCDIF_PRE select PLL3_PFD1 + nxp.MuxIpLcdifPre.Mux(4) // LCDIF_PRE select PLL2_PFD1 nxp.ClockIpSpdif.Enable(false) // disable SPDIF nxp.DivIpSpdif0Pre.Div(1) // divide SPDIF0_CLK_PRED (DIV2) @@ -210,43 +233,43 @@ func initClocks() { nxp.MuxIpPll3Sw.Mux(0) // PLL3_SW select PLL3_MAIN + // Disable Audio/Video/Ethernet PLLs + nxp.CCM_ANALOG.PLL_AUDIO.Set(nxp.CCM_ANALOG_PLL_AUDIO_POWERDOWN_Msk) + nxp.CCM_ANALOG.PLL_VIDEO.Set(nxp.CCM_ANALOG_PLL_VIDEO_POWERDOWN_Msk) + nxp.CCM_ANALOG.PLL_ENET.Set(nxp.CCM_ANALOG_PLL_ENET_POWERDOWN_Msk) + ArmPllConfig.Configure() // init ARM PLL // SYS PLL (PLL2) @ 528 MHz // PFD0 = 396 MHz -> USDHC1/USDHC2(DIV2)=198 MHz // PFD1 = 594 MHz -> (currently unused) - // PFD2 = 327.72 MHz -> SEMC(DIV2)=163.86 MHz, FlexSPI/FlexSPI2=327.72 MHz - // PFD3 = 454.73 MHz -> (currently unused) + // PFD2 = 327.72 MHz -> FlexSPI/FlexSPI2=327.72 MHz + // PFD3 = 594 MHz -> (currently unused) SysPllConfig.Configure(24, 16, 29, 16) // init SYS PLL and PFDs - // USB1 PLL (PLL3) @ 480 MHz - // PFD0 -> (currently unused) - // PFD1 -> (currently unused) - // PFD2 -> (currently unused) - // PFD3 -> (currently unused) - Usb1PllConfig.Configure() // init USB1 PLL and PFDs - Usb2PllConfig.Configure() // init USB2 PLL + Usb1PhyConfig.Configure() // init USB1 HS PHY/PLL + Usb2PhyConfig.Configure() // init USB2 HS PHY/PLL nxp.MuxIpPrePeriph.Mux(3) // PRE_PERIPH select ARM_PLL nxp.MuxIpPeriph.Mux(0) // PERIPH select PRE_PERIPH nxp.MuxIpPeriphClk2.Mux(1) // PERIPH_CLK2 select OSC nxp.MuxIpPerclk.Mux(1) // PERCLK select OSC - // set LVDS1 clock source + // set LVDS1 clock source (ARM_PLL) nxp.CCM_ANALOG.MISC1.Set((nxp.CCM_ANALOG.MISC1.Get() & ^uint32(nxp.CCM_ANALOG_MISC1_LVDS1_CLK_SEL_Msk)) | ((0 << nxp.CCM_ANALOG_MISC1_LVDS1_CLK_SEL_Pos) & nxp.CCM_ANALOG_MISC1_LVDS1_CLK_SEL_Msk)) - // set CLOCK_OUT1 divider + // set CLOCK_OUT1 divider (DIV1) nxp.CCM.CCOSR.Set((nxp.CCM.CCOSR.Get() & ^uint32(nxp.CCM_CCOSR_CLKO1_DIV_Msk)) | ((0 << nxp.CCM_CCOSR_CLKO1_DIV_Pos) & nxp.CCM_CCOSR_CLKO1_DIV_Msk)) - // set CLOCK_OUT1 source + // set CLOCK_OUT1 source (PLL2/DIV2) nxp.CCM.CCOSR.Set((nxp.CCM.CCOSR.Get() & ^uint32(nxp.CCM_CCOSR_CLKO1_SEL_Msk)) | ((1 << nxp.CCM_CCOSR_CLKO1_SEL_Pos) & nxp.CCM_CCOSR_CLKO1_SEL_Msk)) - // set CLOCK_OUT2 divider + // set CLOCK_OUT2 divider (DIV1) nxp.CCM.CCOSR.Set((nxp.CCM.CCOSR.Get() & ^uint32(nxp.CCM_CCOSR_CLKO2_DIV_Msk)) | ((0 << nxp.CCM_CCOSR_CLKO2_DIV_Pos) & nxp.CCM_CCOSR_CLKO2_DIV_Msk)) - // set CLOCK_OUT2 source + // set CLOCK_OUT2 source (SPDIF0) nxp.CCM.CCOSR.Set((nxp.CCM.CCOSR.Get() & ^uint32(nxp.CCM_CCOSR_CLKO2_SEL_Msk)) | - ((18 << nxp.CCM_CCOSR_CLKO2_SEL_Pos) & nxp.CCM_CCOSR_CLKO2_SEL_Msk)) + ((29 << nxp.CCM_CCOSR_CLKO2_SEL_Pos) & nxp.CCM_CCOSR_CLKO2_SEL_Msk)) nxp.CCM.CCOSR.ClearBits(nxp.CCM_CCOSR_CLK_OUT_SEL_Msk) // set CLK_OUT1 drives CLK_OUT nxp.CCM.CCOSR.SetBits(nxp.CCM_CCOSR_CLKO1_EN_Msk) // enable CLK_OUT1 @@ -254,15 +277,10 @@ func initClocks() { nxp.ClockIpIomuxcGpr.Enable(false) // disable IOMUXC_GPR nxp.ClockIpIomuxc.Enable(false) // disable IOMUXC - // set GPT1 High frequency reference clock source + // set GPT1 High frequency reference clock source (PERCLK) nxp.IOMUXC_GPR.GPR5.ClearBits(nxp.IOMUXC_GPR_GPR5_VREF_1M_CLK_GPT1_Msk) - // set GPT2 High frequency reference clock source + // set GPT2 High frequency reference clock source (PERCLK) nxp.IOMUXC_GPR.GPR5.ClearBits(nxp.IOMUXC_GPR_GPR5_VREF_1M_CLK_GPT2_Msk) - - nxp.ClockIpGpio1.Enable(false) // disable GPIO - nxp.ClockIpGpio2.Enable(false) // - nxp.ClockIpGpio3.Enable(false) // - nxp.ClockIpGpio4.Enable(false) // } func enableTimerClocks() { diff --git a/src/runtime/runtime_mimxrt1062_time.go b/src/runtime/runtime_mimxrt1062_time.go index 9c4101be3..5363fe853 100644 --- a/src/runtime/runtime_mimxrt1062_time.go +++ b/src/runtime/runtime_mimxrt1062_time.go @@ -142,7 +142,7 @@ func timerSleep(cycles uint32) bool { nxp.PIT.TIMER[pitSleepTimer].TCTRL.Set(nxp.PIT_TIMER_TCTRL_TIE) // enable interrupts nxp.PIT.TIMER[pitSleepTimer].TCTRL.SetBits(nxp.PIT_TIMER_TCTRL_TEN) // start timer for { - //arm.Asm("wfi") // TODO: causes hardfault! why? + waitForEvents() if pitActive.Get() == 0 { return true }