From 6e29c17a8b286610bc99dfb7cc54ab6723dc7bfd Mon Sep 17 00:00:00 2001 From: ardnew Date: Wed, 19 Jan 2022 08:07:11 -0600 Subject: [PATCH] initial file structure for SAMx51 USB device support --- src/machine/usb/dcd.go | 14 +- src/machine/usb/desc_atsamd51.go | 431 ++++++++++++++ src/machine/usb/dhw_atsamd51.go | 972 +++++++++++++++++++++++++++++++ src/machine/usb/hhw_atsamd51.go | 61 ++ 4 files changed, 1471 insertions(+), 7 deletions(-) create mode 100644 src/machine/usb/desc_atsamd51.go create mode 100644 src/machine/usb/dhw_atsamd51.go create mode 100644 src/machine/usb/hhw_atsamd51.go diff --git a/src/machine/usb/dcd.go b/src/machine/usb/dcd.go index 85b43cdbc..db0189f62 100644 --- a/src/machine/usb/dcd.go +++ b/src/machine/usb/dcd.go @@ -64,7 +64,7 @@ func initDCD(port int, speed Speed, class class) (*dcd, status) { func (d *dcd) class() class { return d.cc } // dcdSetupSize defines the size (bytes) of a USB standard setup packet. -const dcdSetupSize = 8 // bytes +const dcdSetupSize = unsafe.Sizeof(dcdSetup{}) // 8 bytes // dcdSetup contains the USB standard setup packet used to configure a device. type dcdSetup struct { @@ -75,6 +75,8 @@ type dcdSetup struct { wLength uint16 } +// setupFrom decodes and returns a USB standard setup packet located at the +// memory address pointed to by addr. func setupFrom(addr uintptr) dcdSetup { var u uint64 for i := uintptr(0); i < 8; i++ { @@ -89,6 +91,8 @@ func setupFrom(addr uintptr) dcdSetup { } } +// setup decodes and returns a USB standard setup packet stored in the given +// byte slice b. func setup(b []uint8) dcdSetup { if len(b) >= 8 { return dcdSetup{ @@ -102,7 +106,7 @@ func setup(b []uint8) dcdSetup { return dcdSetup{} } -// pack returns the receiver setup packet encoded as uint64. +// pack returns the receiver USB standard setup packet s encoded as uint64. func (s dcdSetup) pack() uint64 { return ((uint64(s.bmRequestType) & 0xFF) << 0) | ((uint64(s.bRequest) & 0xFF) << 8) | @@ -467,7 +471,7 @@ func (d *dcd) controlSetup(sup dcdSetup) dcdStage { // HID case classDeviceHID: - if sup.wLength <= descHIDCxCount { + if sup.wLength <= descHIDCxSize { d.setup = sup descHID[d.cc.config-1].cx[0] = 0xE9 d.controlReceive( @@ -489,8 +493,6 @@ func (d *dcd) controlSetup(sup dcdSetup) dcdStage { // HID case classDeviceHID: idleRate := sup.wValue >> 8 - // TBD: do we need to handle this request? wIndex contains the target - // interface of the request. _ = idleRate d.controlReceive(uintptr(0), 0, false) return dcdStageSetup @@ -519,8 +521,6 @@ func (d *dcd) controlSetup(sup dcdSetup) dcdStage { case classDeviceHID: reportType := uint8(sup.wValue >> 8) reportID := uint8(sup.wValue) - // TBD: do we need to handle this request? wIndex contains the target - // interface of the request. _, _ = reportType, reportID d.controlReply[0] = 0 d.controlReply[1] = 0 diff --git a/src/machine/usb/desc_atsamd51.go b/src/machine/usb/desc_atsamd51.go new file mode 100644 index 000000000..2899c19bf --- /dev/null +++ b/src/machine/usb/desc_atsamd51.go @@ -0,0 +1,431 @@ +//go:build (sam && atsamd51) || (sam && atsame5x) +// +build sam,atsamd51 sam,atsame5x + +package usb + +// descCPUFrequencyHz defines the target CPU frequency (Hz). +const descCPUFrequencyHz = 120000000 + +// descCoreCount defines the number of USB PHY cores available on this platform, +// independent of the number of cores which shall be configured as TinyGo USB +// host/device controller instances. +const descCoreCount = 1 // SAMx51 has a single, full-speed USB PHY + +// descCDCACMCount defines the number of USB cores that may be configured as +// CDC-ACM (single) devices. +const descCDCACMCount = 1 + +// descHIDCount defines the number of USB cores that may be configured as a +// composite (keyboard + mouse + joystick) human interface device (HID). +const descHIDCount = 0 + +// General USB device identification constants. +const ( + descCommonVendorID = 0x03EB + descCommonProductID = 0x2421 + descCommonReleaseID = 0x0101 // BCD (1.1) + + descCommonLanguage = descLanguageEnglish + descCommonManufacturer = "TinyGo" + descCommonProduct = "USB" + descCommonSerialNumber = "00000" +) + +// Constants for all USB device classes. +const ( + + // USB endpoints parameters + + descMaxEndpoints = 8 // SAMx51 maximum number of endpoints + + descBankOut = 0 // descriptor bank 0 holds OUT endpoints + descBankIn = 1 // descriptor bank 1 holds IN endpoints + + descControlPacketSize = 16 +) + +// Constants for USB CDC-ACM device classes. +const ( + + // USB Bus Configuration Attributes + + descCDCACMMaxPowerMa = 100 // Maximum current (mA) requested from host + + // CDC-ACM Endpoint Descriptor Buffers + + descCDCACMEDCount = descMaxEndpoints + + // Setup packet is only 8 bytes in length. However, under certain scenarios, + // USB DMA controller may decide to overwrite/overflow the buffer with 2 extra + // bytes of CRC. From datasheet's "Management of SETUP Transactions" section: + // | If the number of received data bytes is the maximum data payload + // | specified by PCKSIZE.SIZE minus one, only the first CRC data is written + // | to the data buffer. If the number of received data is equal or less + // | than the data payload specified by PCKSIZE.SIZE minus two, both CRC + // | data bytes are written to the data buffer. + // Thus, we need to allocate 2 extra bytes for control endpoint 0 Rx (OUT). + descCDCACMCxSize = 8 + 2 + + // CDC-ACM Data Buffers + + descCDCACMRxSize = 4 * descCDCACMDataRxPacketSize + descCDCACMTxSize = 4 * descCDCACMDataTxPacketSize + + descCDCACMTxTimeoutMs = 120 // millisec + descCDCACMTxSyncUs = 75 // microsec + + // Default CDC-ACM Endpoint Configurations (Full-Speed) + + descCDCACMStatusInterval = descCDCACMStatusFSInterval // Status + descCDCACMStatusPacketSize = descCDCACMStatusFSPacketSize // + + descCDCACMDataRxPacketSize = descCDCACMDataRxFSPacketSize // Data Rx + descCDCACMDataTxPacketSize = descCDCACMDataTxFSPacketSize // Data Tx + + // CDC-ACM Endpoint Configurations for Full-Speed Device + + descCDCACMStatusFSInterval = 5 // Status + descCDCACMStatusFSPacketSize = 16 // (full-speed) + + descCDCACMDataRxFSPacketSize = 64 // Data Rx (full-speed) + descCDCACMDataTxFSPacketSize = 64 // Data Tx (full-speed) + + // CDC-ACM Endpoint Configurations for High-Speed Device + + // - N/A, SAMx51 only has a full-speed PHY +) + +// Constants for USB HID (keyboard, mouse, joystick) device classes. +const ( + + // USB Bus Configuration Attributes + + descHIDMaxPowerMa = 100 // Maximum current (mA) requested from host + + // HID Endpoint Descriptor Buffers + + descHIDEDCount = descMaxEndpoints + + // Setup packet is only 8 bytes in length. However, under certain scenarios, + // USB DMA controller may decide to overwrite/overflow the buffer with 2 extra + // bytes of CRC. From datasheet's "Management of SETUP Transactions" section: + // | If the number of received data bytes is the maximum data payload + // | specified by PCKSIZE.SIZE minus one, only the first CRC data is written + // | to the data buffer. If the number of received data is equal or less + // | than the data payload specified by PCKSIZE.SIZE minus two, both CRC + // | data bytes are written to the data buffer. + // Thus, we need to allocate 2 extra bytes for control endpoint 0 Rx (OUT). + descHIDCxSize = 8 + 2 + + // HID Serial Buffers + + descHIDSerialRxSize = descHIDSerialRxPacketSize + descHIDSerialTxSize = descHIDSerialTxPacketSize + + descHIDSerialTxTimeoutMs = 50 // millisec + descHIDSerialTxSyncUs = 75 // microsec + + // HID Keyboard Buffers + + descHIDKeyboardTxSize = 4 * descHIDKeyboardTxPacketSize + + descHIDKeyboardTxTimeoutMs = 50 // millisec + + // HID Mouse Buffers + + descHIDMouseTxSize = 4 * descHIDMouseTxPacketSize + + descHIDMouseTxTimeoutMs = 30 // millisec + + // HID Joystick Buffers + + descHIDJoystickTxSize = 4 * descHIDJoystickTxPacketSize + + descHIDJoystickTxTimeoutMs = 30 // millisec + + // Default HID Endpoint Configurations (Full-Speed) + + descHIDSerialRxInterval = descHIDSerialRxFSInterval // Serial Rx + descHIDSerialRxPacketSize = descHIDSerialRxFSPacketSize // + + descHIDSerialTxInterval = descHIDSerialTxFSInterval // Serial Tx + descHIDSerialTxPacketSize = descHIDSerialTxFSPacketSize // + + descHIDKeyboardTxInterval = descHIDKeyboardTxFSInterval // Keyboard + descHIDKeyboardTxPacketSize = descHIDKeyboardTxFSPacketSize // + + descHIDMediaKeyTxInterval = descHIDMediaKeyTxFSInterval // Keyboard Media Keys + descHIDMediaKeyTxPacketSize = descHIDMediaKeyTxFSPacketSize // + + descHIDMouseTxInterval = descHIDMouseTxFSInterval // Mouse + descHIDMouseTxPacketSize = descHIDMouseTxFSPacketSize // + + descHIDJoystickTxInterval = descHIDJoystickTxFSInterval // Joystick + descHIDJoystickTxPacketSize = descHIDJoystickTxFSPacketSize // + + // HID Endpoint Configurations for Full-Speed Device + + descHIDSerialRxFSInterval = 2 // Serial Rx + descHIDSerialRxFSPacketSize = 8 // (full-speed) + + descHIDSerialTxFSInterval = 1 // Serial Tx + descHIDSerialTxFSPacketSize = 16 // (full-speed) + + descHIDKeyboardTxFSInterval = 4 // Keyboard + descHIDKeyboardTxFSPacketSize = 8 // (full-speed) + + descHIDMediaKeyTxFSInterval = 4 // Keyboard Media Keys + descHIDMediaKeyTxFSPacketSize = 8 // (full-speed) + + descHIDMouseTxFSInterval = 4 // Mouse + descHIDMouseTxFSPacketSize = 8 // (full-speed) + + descHIDJoystickTxFSInterval = 4 // Joystick + descHIDJoystickTxFSPacketSize = 12 // (full-speed) + + // HID Endpoint Configurations for High-Speed Device + + // - N/A, SAMx51 only has a full-speed PHY +) + +// descCDCACM0ED is an array of endpoint descriptors, which describes to the USB +// DMA controller the buffer and transfer properties for each endpoint, for the +// default CDC-ACM (single) device class configuration (index 1). +//go:align 32 +var descCDCACM0ED [descCDCACMEDCount]dhwEndptAddrDesc + +// descCDCACM0Cx is the buffer for control/status data received on endpoint 0 of +// the default CDC-ACM (single) device class configuration (index 1). +//go:align 32 +var descCDCACM0Cx [descCDCACMCxSize]uint8 + +// descCDCACM0Dx is the transmit (Tx) buffer of descriptor data on endpoint 0 +// for the default CDC-ACM (single) device class configuration (index 1). +//go:align 32 +var descCDCACM0Dx [descCDCACMConfigSize]uint8 + +// descCDCACM0Rx is the receive (Rx) transfer buffer for the default CDC-ACM +// (single) device class configuration (index 1). +//go:align 32 +var descCDCACM0Rx [descCDCACMRxSize]uint8 + +// descCDCACM0Tx is the transmit (Tx) transfer buffer for the default CDC-ACM +// (single) device class configuration (index 1). +//go:align 32 +var descCDCACM0Tx [descCDCACMTxSize]uint8 + +// descCDCACMClassData holds the buffers and control states for all CDC-ACM +// (single) device class configurations, ordered by index (offset by -1), for +// SAMx51 targets only. +// +// Instances of this type (elements of descCDCACMData) are embedded in elements +// of the common/target-agnostic CDC-ACM class configurations (descCDCACM). +// Methods defined on this type implement target-specific functionality, and +// some of these methods are required by the common device controller driver. +// Thus, this type functions as a hardware abstraction layer (HAL). +type descCDCACMClassData struct { + + // CDC-ACM Control Buffers + + ed *[descCDCACMEDCount]dhwEndptAddrDesc // endpoint descriptors + + cx *[descCDCACMCxSize]uint8 // control endpoint 0 Rx/Tx transfer buffer + dx *[descCDCACMConfigSize]uint8 // control endpoint 0 Tx (IN) descriptor transfer buffer + + // CDC-ACM Data Buffers + + rx *[descCDCACMRxSize]uint8 // bulk data endpoint Rx (OUT) transfer buffer + tx *[descCDCACMTxSize]uint8 // bulk data endpoint Tx (IN) transfer buffer + + sxSize uint16 + rxSize uint16 + txSize uint16 +} + +// descCDCACMData holds statically-allocated instances for each of the target- +// specific (SAMx51) CDC-ACM (single) device class configurations' control and +// data structures, ordered by configuration index (offset by -1). Each element +// is embedded in a corresponding element of descCDCACM. +var descCDCACMData = [dcdCount]descCDCACMClassData{ + + { // -- CDC-ACM (single) Class Configuration Index 1 -- + + // CDC-ACM Control Buffers + + ed: &descCDCACM0ED, + + cx: &descCDCACM0Cx, + dx: &descCDCACM0Dx, + + // CDC-ACM Data Buffers + + rx: &descCDCACM0Rx, + tx: &descCDCACM0Tx, + + sxSize: descCDCACMStatusPacketSize, + rxSize: descCDCACMDataRxPacketSize, + txSize: descCDCACMDataTxPacketSize, + }, +} + +// descHID0ED is an array of endpoint descriptors, which describes to the USB +// DMA controller the buffer and transfer properties for each endpoint, for the +// default HID device class configuration (index 1). +//go:align 32 +var descHID0ED [descHIDEDCount]dhwEndptAddrDesc + +// descHID0Cx is the buffer for control/status data received on endpoint 0 of +// the default HID device class configuration (index 1). +//go:align 32 +var descHID0Cx [descHIDCxSize]uint8 + +// descHID0Dx is the transmit (Tx) buffer of descriptor data on endpoint 0 for +// the default HID device class configuration (index 1). +//go:align 32 +var descHID0Dx [descHIDConfigSize]uint8 + +// descHID0SerialRx is the serial receive (Rx) transfer buffer for the default +// HID device class configuration (index 1). +//go:align 32 +var descHID0SerialRx [descHIDSerialRxSize]uint8 + +// descHID0SerialTx is the serial transmit (Tx) transfer buffer for the default +// HID device class configuration (index 1). +//go:align 32 +var descHID0SerialTx [descHIDSerialTxSize]uint8 + +// descHID0KeyboardTx is the keyboard transmit (Tx) transfer buffer for the +// default HID device class configuration (index 1). +//go:align 32 +var descHID0KeyboardTx [descHIDKeyboardTxSize]uint8 + +// descHID0KeyboardTp is the keyboard HID report transmit (Tx) transfer buffer +// for the default HID device class configuration (index 1). +//go:align 32 +var descHID0KeyboardTp [descHIDKeyboardTxPacketSize]uint8 + +// descHID0MouseTx is the mouse transmit (Tx) transfer buffer for the default +// HID device class configuration (index 1). +//go:align 32 +var descHID0MouseTx [descHIDMouseTxSize]uint8 + +// descHID0JoystickTx is the joystick transmit (Tx) transfer buffer for the +// default HID device class configuration (index 1). +//go:align 32 +var descHID0JoystickTx [descHIDJoystickTxSize]uint8 + +var descHID0KeyboardTxKey [hidKeyboardKeyCount]uint8 +var descHID0KeyboardTxCon [hidKeyboardConCount]uint16 +var descHID0KeyboardTxSys [hidKeyboardSysCount]uint8 + +// descHID0Keyboard is the Keyboard instance with which the user may interact +// when using the default HID device class configuration (index 1). +var descHID0Keyboard = Keyboard{ + key: &descHID0KeyboardTxKey, + con: &descHID0KeyboardTxCon, + sys: &descHID0KeyboardTxSys, +} + +// descHIDClassData holds the buffers and control states for all of the HID +// device class configurations, ordered by index (offset by -1), for SAMx51 +// targets only. +// +// Instances of this type (elements of descHIDData) are embedded in elements +// of the common/target-agnostic HID class configurations (descHID). +// Methods defined on this type implement target-specific functionality, and +// some of these methods are required by the common device controller driver. +// Thus, this type functions as a hardware abstraction layer (HAL). +type descHIDClassData struct { + + // HID Control Buffers + + ed *[descHIDEDCount]dhwEndptAddrDesc // endpoint descriptors + + cx *[descHIDCxSize]uint8 // control endpoint 0 Rx/Tx transfer buffer + dx *[descHIDConfigSize]uint8 // control endpoint 0 Tx (IN) descriptor transfer buffer + + // HID Serial Buffers + + rxSerial *[descHIDSerialRxSize]uint8 // interrupt endpoint serial Rx (OUT) transfer buffer + txSerial *[descHIDSerialTxSize]uint8 // interrupt endpoint serial Tx (IN) transfer buffer + + rxSerialSize uint16 + txSerialSize uint16 + + // HID Keyboard Buffers + + txKeyboard *[descHIDKeyboardTxSize]uint8 // interrupt endpoint keyboard Tx (IN) transfer buffer + tpKeyboard *[descHIDKeyboardTxPacketSize]uint8 // interrupt endpoint keyboard Tx (IN) HID report bbuffer + + txKeyboardSize uint16 + + // HID Mouse Buffers + + txMouse *[descHIDMouseTxSize]uint8 // interrupt endpoint mouse Tx (IN) transfer buffer + + txMouseSize uint16 + + // HID Joystick Buffers + + txJoystick *[descHIDJoystickTxSize]uint8 // interrupt endpoint joystick Tx (IN) transfer buffer + + txJoystickSize uint16 + + // HID Device Instances + + //serial *Serial + keyboard *Keyboard + //mouse *Mouse + //joystick *Joystick +} + +// descHIDData holds statically-allocated instances for each of the target- +// specific (SAMx51) HID device class configurations' control and data +// structures, ordered by configuration index (offset by -1). Each element is +// embedded in a corresponding element of descHID. +var descHIDData = [dcdCount]descHIDClassData{ + + { // -- HID Class Configuration Index 1 -- + + // HID Control Buffers + + ed: &descHID0ED, + + cx: &descHID0Cx, + dx: &descHID0Dx, + + // HID Serial Buffers + + rxSerial: &descHID0SerialRx, + txSerial: &descHID0SerialTx, + + rxSerialSize: descHIDSerialRxPacketSize, + txSerialSize: descHIDSerialTxPacketSize, + + // HID Keyboard Buffers + + txKeyboard: &descHID0KeyboardTx, + tpKeyboard: &descHID0KeyboardTp, + + txKeyboardSize: descHIDKeyboardTxPacketSize, + + // HID Mouse Buffers + + txMouse: &descHID0MouseTx, + + txMouseSize: descHIDMouseTxPacketSize, + + // HID Joystick Buffers + + txJoystick: &descHID0JoystickTx, + + txJoystickSize: descHIDJoystickTxPacketSize, + + // HID Device Instances + + //serial: &descHID0Serial, + keyboard: &descHID0Keyboard, + //mouse: &descHID0Mouse, + //joystick: &descHID0Joystick, + }, +} diff --git a/src/machine/usb/dhw_atsamd51.go b/src/machine/usb/dhw_atsamd51.go new file mode 100644 index 000000000..ae5c8778a --- /dev/null +++ b/src/machine/usb/dhw_atsamd51.go @@ -0,0 +1,972 @@ +//go:build (sam && atsamd51) || (sam && atsame5x) +// +build sam,atsamd51 sam,atsame5x + +package usb + +// Implementation of USB device controller hardware abstraction (dhw) for +// Microchip SAMx51. + +import ( + "device/arm" + "device/sam" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +// dhwInterruptPriority defines the priority for all USB device interrupts. +const dhwInterruptPriority = 3 + +// dhw implements USB device controller hardware abstraction for iMXRT1062. +type dhw struct { + *dcd // USB device controller driver + + bus *sam.USB_DEVICE_Type // USB core registers + + irqEVT interrupt.Interrupt // USB IRQs + irqSOF interrupt.Interrupt + irqTC0 interrupt.Interrupt + irqTC1 interrupt.Interrupt + + speed Speed + + controlReply [8]uint8 + controlMask uint32 + endpointMask uint32 + setup dcdSetup + stage dcdStage +} + +func runBootloader() {} + +// allocDHW returns a reference to the USB hardware abstraction for the given +// device controller driver. Should be called only one time and during device +// controller initialization. +func allocDHW(port, instance int, speed Speed, dc *dcd) *dhw { + switch port { + case 0: + dhwInstance[instance].dcd = dc + dhwInstance[instance].bus = sam.USB_DEVICE + dhwInstance[instance].irqEVT = + interrupt.New(sam.IRQ_USB_OTHER, + func(interrupt.Interrupt) { coreInstance[0].dc.interrupt() }) + dhwInstance[instance].irqSOF = + interrupt.New(sam.IRQ_USB_SOF_HSOF, + func(interrupt.Interrupt) { coreInstance[0].dc.startOfFrame() }) + dhwInstance[instance].irqTC0 = + interrupt.New(sam.IRQ_USB_TRCPT0, + func(interrupt.Interrupt) { coreInstance[0].dc.complete(0) }) + dhwInstance[instance].irqTC1 = + interrupt.New(sam.IRQ_USB_TRCPT1, + func(interrupt.Interrupt) { coreInstance[0].dc.complete(1) }) + } + + // SAMx51 has only one USB PHY, which is full-speed + if 0 == speed { + speed = FullSpeed + } + dhwInstance[instance].speed = speed + + return &dhwInstance[instance] +} + +// Calibrate DP/DM pads using value from NVM. Based on the following from +// Atmel's CMSIS 1.2.2 for SAMD51: +// +// ... NOTE: These register defines are used to obtain calibration parameters +// | +// | #define NVMCTRL_SW0 (0x00800080UL) /**< \brief (NVMCTRL) SW0 Base Address * +// | +// ... +// | +// | #define USB_FUSES_TRANSN_ADDR (NVMCTRL_SW0 + 4) +// | #define USB_FUSES_TRANSN_Pos 0 /**< \brief (NVMCTRL_SW0) USB pad Transn calibration */ +// | #define USB_FUSES_TRANSN_Msk (_Ul(0x1F) << USB_FUSES_TRANSN_Pos) +// | #define USB_FUSES_TRANSN(value) (USB_FUSES_TRANSN_Msk & ((value) << USB_FUSES_TRANSN_Pos)) +// | +// | #define USB_FUSES_TRANSP_ADDR (NVMCTRL_SW0 + 4) +// | #define USB_FUSES_TRANSP_Pos 5 /**< \brief (NVMCTRL_SW0) USB pad Transp calibration */ +// | #define USB_FUSES_TRANSP_Msk (_Ul(0x1F) << USB_FUSES_TRANSP_Pos) +// | #define USB_FUSES_TRANSP(value) (USB_FUSES_TRANSP_Msk & ((value) << USB_FUSES_TRANSP_Pos)) +// | +// | #define USB_FUSES_TRIM_ADDR (NVMCTRL_SW0 + 4) +// | #define USB_FUSES_TRIM_Pos 10 /**< \brief (NVMCTRL_SW0) USB pad Trim calibration */ +// | #define USB_FUSES_TRIM_Msk (_Ul(0x7) << USB_FUSES_TRIM_Pos) +// | #define USB_FUSES_TRIM(value) (USB_FUSES_TRIM_Msk & ((value) << USB_FUSES_TRIM_Pos)) +// | +// ... +// | +// | typedef union { +// | struct { +// | uint16_t TRANSP:5; /*!< bit: 0.. 4 USB Pad Transp calibration */ +// | uint16_t :1; /*!< bit: 5 Reserved */ +// | uint16_t TRANSN:5; /*!< bit: 6..10 USB Pad Transn calibration */ +// | uint16_t :1; /*!< bit: 11 Reserved */ +// | uint16_t TRIM:3; /*!< bit: 12..14 USB Pad Trim calibration */ +// | uint16_t :1; /*!< bit: 15 Reserved */ +// | } bit; /*!< Structure used for bit access */ +// | uint16_t reg; /*!< Type used for register access */ +// | } USB_PADCAL_Type; +// | +// ... NOTE: The following is where USB pad calibration actually occurrs: +// | +// | USB->DEVICE.PADCAL.bit.TRANSP = (*((uint32_t*) USB_FUSES_TRANSP_ADDR) & USB_FUSES_TRANSP_Msk) >> USB_FUSES_TRANSP_Pos; +// | USB->DEVICE.PADCAL.bit.TRANSN = (*((uint32_t*) USB_FUSES_TRANSN_ADDR) & USB_FUSES_TRANSN_Msk) >> USB_FUSES_TRANSN_Pos; +// | USB->DEVICE.PADCAL.bit.TRIM = (*((uint32_t*) USB_FUSES_TRIM_ADDR) & USB_FUSES_TRIM_Msk) >> USB_FUSES_TRIM_Pos; +// | +// ... +// +func (d *dhw) calibrate() { + const reg = 0x00800080 + 4 // NVMCTRL_SW0 + 4 + cal := *(*uint16)(unsafe.Pointer(uintptr(reg))) + msk := uint16(sam.USB_DEVICE_PADCAL_TRANSP_Msk | + sam.USB_DEVICE_PADCAL_TRANSN_Msk | + sam.USB_DEVICE_PADCAL_TRIM_Msk) + d.bus.PADCAL.ReplaceBits(cal, msk, 0) +} + +// init configures the USB port for device mode operation by initializing all +// endpoint and transfer descriptor data structures, initializing core registers +// and interrupts, resetting the USB PHY, and enabling power on the bus. +func (d *dhw) init() status { + + // Enable USB clocks + const clockGenerator = sam.GCLK_PCHCTRL_GEN_GCLK10 + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_USB_) + sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_USB_) + sam.GCLK.PCHCTRL[clockGenerator].Set(clockGenerator | sam.GCLK_PCHCTRL_CHEN) + + // Initialize USB interrupt priorities + d.irqEVT.SetPriority(dhwInterruptPriority) + d.irqSOF.SetPriority(dhwInterruptPriority) + d.irqTC0.SetPriority(dhwInterruptPriority) + d.irqTC1.SetPriority(dhwInterruptPriority) + + // Clear interrupts + m := arm.DisableInterrupts() & + ^uintptr(sam.IRQ_USB_OTHER|sam.IRQ_USB_SOF_HSOF| + sam.IRQ_USB_TRCPT0|sam.IRQ_USB_TRCPT1) + arm.EnableInterrupts(m) + + // Reset USB peripheral + d.bus.CTRLA.Set(sam.USB_DEVICE_CTRLA_SWRST) + for !d.bus.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_SWRST) { + } + for d.bus.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_SWRST) { + } + d.calibrate() + + // USB Quality of Service: High Quality (3) + d.bus.QOSCTRL.Set((3 << sam.USB_DEVICE_QOSCTRL_CQOS_Pos) | + (3 << sam.USB_DEVICE_QOSCTRL_DQOS_Pos)) + + // Install USB endpoint descriptor table (USB_DEVICE.DESCADD) + var addr uintptr + switch d.cc.id { + case classDeviceCDCACM: + addr = uintptr(unsafe.Pointer(descCDCACM[d.cc.config-1].ed)) + case classDeviceHID: + addr = uintptr(unsafe.Pointer(descHID[d.cc.config-1].ed)) + } + d.bus.DESCADD.Set(uint32(addr)) + + // Configure bus speed (always full-speed (FS)), device mode, enable PHY, and + // put finite-state machine (FSM) in standby. + d.bus.CTRLB.Set(sam.USB_DEVICE_CTRLB_SPDCONF_FS) + d.bus.CTRLA.Set(sam.USB_DEVICE_CTRLA_MODE_DEVICE | + sam.USB_DEVICE_CTRLA_ENABLE | sam.USB_DEVICE_CTRLA_RUNSTDBY) + for d.bus.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_ENABLE) { + } + + // Clear and enable interrupts in USB core + d.bus.INTFLAG.Set(d.bus.INTFLAG.Get()) + d.bus.INTENSET.Set(sam.USB_DEVICE_INTENSET_SOF | sam.USB_DEVICE_INTENSET_EORST) + + // Ensure D+ pulled down long enough for host to detect a previous disconnect + udelay(5000) + + return statusOK +} + +// enable causes the USB core to enter (or exit) the normal run state and +// enables/disables all interrupts on the receiver's USB port. +func (d *dhw) enable(enable bool) { + d.enableInterrupts(enable) +} + +// enableInterrupts enables/disables all interrupts on the receiver's USB port. +func (d *dhw) enableInterrupts(enable bool) { + if enable { + d.irqEVT.Enable() // Enable USB interrupts + d.irqSOF.Enable() + d.irqTC0.Enable() + d.irqTC1.Enable() + } else { + d.irqEVT.Disable() // Disable USB interrupts + d.irqSOF.Disable() + d.irqTC0.Disable() + d.irqTC1.Disable() + } +} + +// enableSOF enables or disables start-of-frame (SOF) interrupts on the given +// USB device interface. +func (d *dhw) enableSOF(enable bool, iface uint8) { +} + +// interrupt handles the USB hardware interrupt events on the "OTHER" IRQ line +// and notifies the device controller driver using a common "virtual interrupt" +// code. +func (d *dhw) interrupt() { + + // read and clear the interrupts that fired + status := d.bus.USBSTS.Get() & d.bus.USBINTR.Get() + d.bus.USBSTS.Set(status) + +} + +// startOfFrame handles the USB hardware interrupt events on the "SOF_HSOF" IRQ +// lines and notifies the device controller driver using a common "virtual +// interrupt" code. +func (d *dhw) startOfFrame() { +} + +// complete handles the USB hardware interrupt events on the "TRCPT0" and +// "TRCPT1" IRQ lines and notifies the device controller driver using a common +// "virtual interrupt" code. +// +// When bank is 0, the interrupt occurred on "TRCPT0". Otherwise, bank is 1, and +// the interrupt occurred on "TRCPT1". +func (d *dhw) complete(bank int) { +} + +func (d *dhw) prepareSetup() { + + // Configure control endpoint 0 OUT only + endpoint := d.controlEndpoint() + out, _ := d.endpointAddressDescriptor(endpoint) + + out.address.Set(uint32(d.controlBuffer())) + out.packetSize.ReplaceBits( + pcksize(0, 0, uint32(dcdSetupSize)), + (USB_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Msk< descCDCACMEndpointCount { + return + } + + // HID + case classDeviceHID: + if endpoint < descHIDEndpointSerialRx || + endpoint > descHIDEndpointCount { + return + } + + default: + // Unhandled device class + } + +} + +// endpointConfigureTx configures the given bulk data transmit (Tx, IN) endpoint +// for transfer. +func (d *dhw) endpointConfigureTx( + endpoint uint8, packetSize uint16, zlp bool, callback func(transfer *dhwTransfer)) { + + // Configure based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + if endpoint < descCDCACMEndpointStatus || + endpoint > descCDCACMEndpointCount { + return + } + + // HID + case classDeviceHID: + if endpoint < descHIDEndpointSerialRx || + endpoint > descHIDEndpointCount { + return + } + + default: + // Unhandled device class + } + +} + +// endpointReceive schedules a receive (Rx, OUT) transfer on the given endpoint. +func (d *dhw) endpointReceive(endpoint uint8, transfer *dhwTransfer) { + + // Configure based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + if endpoint < descCDCACMEndpointStatus || + endpoint > descCDCACMEndpointCount { + return + } + + // HID + case classDeviceHID: + if endpoint < descHIDEndpointSerialRx || + endpoint > descHIDEndpointCount { + return + } + + default: + // Unhandled device class + } + +} + +// endpointTransmit schedules a transmit (Tx, IN) transfer on the given +// endpoint. +func (d *dhw) endpointTransmit(endpoint uint8, transfer *dhwTransfer) { + + // Configure based on our device class configuration + switch d.cc.id { + + // CDC-ACM (single) + case classDeviceCDCACM: + if endpoint < descCDCACMEndpointStatus || + endpoint > descCDCACMEndpointCount { + return + } + + // HID + case classDeviceHID: + if endpoint < descHIDEndpointSerialRx || + endpoint > descHIDEndpointCount { + return + } + + default: + // Unhandled device class + } + +} + +// endpointComplete handles transfer completion of a data endpoint. +func (d *dhw) endpointComplete(endpoint uint8) { + +} + +// ============================================================================= +// Transfer Descriptor +// ============================================================================= + +// dhwTransfer describes the size and location of data to be transferred to or +// from a USB endpoint. +type dhwTransfer struct { + next *dhwTransfer + token uint32 + pointer [5]uintptr + param uint32 +} + +// dhwTransferSize defines the size (bytes) of a USB standard transfer packet. +const dhwTransferSize = 32 // bytes + +// dhwTransferEOL is a sentinel value used to indicate the final node in a +// linked list of transfer descriptors. +var dhwTransferEOL = (*dhwTransfer)(unsafe.Pointer(uintptr(1))) + +// nextTransfer returns the next transfer descriptor pointed to by the receiver +// transfer descriptor, and whether or not that next descriptor is the final +// descriptor in the list. +func (t dhwTransfer) nextTransfer() (*dhwTransfer, bool) { + return t.next, 1 == uintptr(unsafe.Pointer(t.next)) +} + +func (d *dhw) transferPrepare( + transfer *dhwTransfer, data *uint8, size uint16, param uint32) { + +} + +func (d *dhw) transferSchedule( + endpoint *dhwEndptDesc, mask uint32, transfer *dhwTransfer) { + +} + +// ============================================================================= +// General-Purpose (GP) Timer +// ============================================================================= + +func (d *dhw) timerConfigure(timer int, usec uint32, fn func()) { + +} + +func (d *dhw) timerOneShot(timer int) { + +} + +func (d *dhw) timerStop(timer int) { + +} + +// ============================================================================= +// [CDC-ACM] Serial UART (Virtual COM Port) +// ============================================================================= + +func (d *dhw) uartConfigure() { + + acm := &descCDCACM[d.cc.config-1] + + // SAMx51 only supports USB full-speed (FS) operation + acm.rxSize = descCDCACMDataRxFSPacketSize + acm.txSize = descCDCACMDataTxFSPacketSize + + d.endpointEnable(descCDCACMEndpointStatus, + false, descCDCACMConfigAttrStatus) + d.endpointEnable(descCDCACMEndpointDataRx, + false, descCDCACMConfigAttrDataRx) + d.endpointEnable(descCDCACMEndpointDataTx, + false, descCDCACMConfigAttrDataTx) + + d.endpointConfigureTx(descCDCACMEndpointStatus, + acm.sxSize, false, nil) + d.endpointConfigureRx(descCDCACMEndpointDataRx, + acm.rxSize, false, d.uartNotify) + d.endpointConfigureTx(descCDCACMEndpointDataTx, + acm.txSize, true, nil) + for i := range acm.rd { + d.uartReceive(uint8(i)) + } + d.timerConfigure(0, descCDCACMTxSyncUs, d.uartSync) +} + +func (d *dhw) uartSetLineState(dtr, rts bool) { +} + +func (d *dhw) uartSetLineCoding(coding descCDCACMLineCoding) { + if 134 == coding.baud { + d.enableSOF(true, descCDCACMInterfaceCount) + } +} + +func (d *dhw) uartReceive(endpoint uint8) { + acm := &descCDCACM[d.cc.config-1] + num := uint16(endpoint) & descEndptAddrNumberMsk +} + +func (d *dhw) uartNotify(transfer *dhwTransfer) { + acm := &descCDCACM[d.cc.config-1] + +} + +// uartFlush discards all buffered input (Rx) data. +func (d *dhw) uartFlush() { + acm := &descCDCACM[d.cc.config-1] +} + +func (d *dhw) uartAvailable() int { + return 0 +} + +func (d *dhw) uartPeek() (uint8, bool) { + acm := &descCDCACM[d.cc.config-1] +} + +func (d *dhw) uartReadByte() (uint8, bool) { + b := []uint8{0} + ok := d.uartRead(b) > 0 + return b[0], ok +} + +func (d *dhw) uartRead(data []uint8) int { + acm := &descCDCACM[d.cc.config-1] + read := uint16(0) + size := uint16(len(data)) + return int(read) +} + +func (d *dhw) uartWriteByte(c uint8) bool { + return 1 == d.uartWrite([]uint8{c}) +} + +func (d *dhw) uartWrite(data []uint8) int { + acm := &descCDCACM[d.cc.config-1] + sent := 0 + size := len(data) + return sent +} + +func (d *dhw) uartSync() { + +} + +// ============================================================================= +// [HID] Serial +// ============================================================================= + +func (d *dhw) serialConfigure() { + + hid := &descHID[d.cc.config-1] + + // SAMx51 only supports USB full-speed (FS) operation + hid.rxSerialSize = descHIDSerialRxFSPacketSize + hid.txSerialSize = descHIDSerialTxFSPacketSize + + // Rx and Tx are on same endpoint + d.endpointEnable(descHIDEndpointSerialRx, + false, descHIDConfigAttrSerial) + + d.endpointConfigureRx(descHIDEndpointSerialRx, + hid.rxSerialSize, false, d.serialNotify) + d.endpointConfigureTx(descHIDEndpointSerialTx, + hid.txSerialSize, false, nil) + for i := range hid.rdSerial { + d.serialReceive(uint8(i)) + } + d.timerConfigure(0, descHIDSerialTxSyncUs, d.serialSync) +} + +func (d *dhw) serialReceive(endpoint uint8) { + hid := &descHID[d.cc.config-1] + num := uint16(endpoint) & descEndptAddrNumberMsk +} + +func (d *dhw) serialTransmit() { + hid := &descHID[d.cc.config-1] +} + +func (d *dhw) serialNotify(transfer *dhwTransfer) { + hid := &descHID[d.cc.config-1] + len := hid.rxSerialSize - (uint16(transfer.token>>16) & 0x7FFF) +} + +// serialFlush discards all buffered input (Rx) data. +func (d *dhw) serialFlush() { + hid := &descHID[d.cc.config-1] +} + +func (d *dhw) serialSync() { + +} + +// ============================================================================= +// [HID] Keyboard +// ============================================================================= + +func (d *dhw) keyboard() *Keyboard { return descHID[d.cc.config-1].keyboard } + +func (d *dhw) keyboardConfigure() { + + hid := &descHID[d.cc.config-1] + + // Initialize keyboard + hid.keyboard.configure(d.dcd, hid) + + // SAMx51 only supports USB full-speed (FS) operation + hid.txKeyboardSize = descHIDKeyboardTxFSPacketSize + + d.endpointEnable(descHIDEndpointKeyboard, + false, descHIDConfigAttrKeyboard) + d.endpointEnable(descHIDEndpointMediaKey, + false, descHIDConfigAttrMediaKey) + + d.endpointConfigureTx(descHIDEndpointKeyboard, + hid.txKeyboardSize, false, nil) + d.endpointConfigureTx(descHIDEndpointMediaKey, + hid.txKeyboardSize, false, nil) +} + +func (d *dhw) keyboardSendKeys(consumer bool) bool { + + hid := &descHID[d.cc.config-1] + + if !consumer { + + hid.tpKeyboard[0] = hid.keyboard.mod + hid.tpKeyboard[1] = 0 + hid.tpKeyboard[2] = hid.keyboard.key[0] + hid.tpKeyboard[3] = hid.keyboard.key[1] + hid.tpKeyboard[4] = hid.keyboard.key[2] + hid.tpKeyboard[5] = hid.keyboard.key[3] + hid.tpKeyboard[6] = hid.keyboard.key[4] + hid.tpKeyboard[7] = hid.keyboard.key[5] + + return d.keyboardWrite(descHIDEndpointKeyboard, hid.tpKeyboard[:]) + + } else { + + // 44444444 44333333 33332222 22222211 11111111 [ word ] + // 98765432 10987654 32109876 54321098 76543210 [ index ] (right-to-left) + + hid.tpKeyboard[1] = uint8((hid.keyboard.con[1] << 2) | ((hid.keyboard.con[0] >> 8) & 0x03)) + hid.tpKeyboard[2] = uint8((hid.keyboard.con[2] << 4) | ((hid.keyboard.con[1] >> 6) & 0x0F)) + hid.tpKeyboard[3] = uint8((hid.keyboard.con[3] << 6) | ((hid.keyboard.con[2] >> 4) & 0x3F)) + hid.tpKeyboard[4] = uint8(hid.keyboard.con[3] >> 2) + hid.tpKeyboard[5] = hid.keyboard.sys[0] + hid.tpKeyboard[6] = hid.keyboard.sys[1] + hid.tpKeyboard[7] = hid.keyboard.sys[2] + + return d.keyboardWrite(descHIDEndpointMediaKey, hid.tpKeyboard[:]) + + } +} + +func (d *dhw) keyboardWrite(endpoint uint8, data []uint8) bool { + + hid := &descHID[d.cc.config-1] + + size := uint16(len(data)) + xfer := &hid.tdKeyboard[hid.txKeyboardHead] + when := ticks() + for { + if 0 == xfer.token&0x80 { + if 0 != xfer.token&0x68 { + // TODO: token contains error, how to handle? + } + hid.txKeyboardPrev = false + break + } + if hid.txKeyboardPrev { + return false + } + if ticks()-when > descHIDKeyboardTxTimeoutMs { + // Waited too long, assume host connection dropped + hid.txKeyboardPrev = true + return false + } + } + // Without this delay, the order packets are transmitted is seriously screwy. + udelay(60) + buff := hid.txKeyboard[hid.txKeyboardHead*descHIDKeyboardTxSize:] + _ = copy(buff, data) + d.transferPrepare(xfer, &buff[0], size, 0) + flushCache(uintptr(unsafe.Pointer(&buff[0])), descHIDKeyboardTxSize) + d.endpointTransmit(endpoint, xfer) + hid.txKeyboardHead += 1 + if hid.txKeyboardHead >= descHIDKeyboardTDCount { + hid.txKeyboardHead = 0 + } + return true +} + +// ============================================================================= +// [HID] Mouse +// ============================================================================= + +func (d *dhw) mouseConfigure() { + + hid := &descHID[d.cc.config-1] + + // SAMx51 only supports USB full-speed (FS) operation + hid.txMouseSize = descHIDMouseTxFSPacketSize + + d.endpointEnable(descHIDEndpointMouse, + false, descHIDConfigAttrMouse) + + d.endpointConfigureTx(descHIDEndpointMouse, + hid.txMouseSize, false, nil) +} + +// ============================================================================= +// [HID] Joystick +// ============================================================================= + +func (d *dhw) joystickConfigure() { + + hid := &descHID[d.cc.config-1] + + // SAMx51 only supports USB full-speed (FS) operation + hid.txJoystickSize = descHIDJoystickTxFSPacketSize + + d.endpointEnable(descHIDEndpointJoystick, + false, descHIDConfigAttrJoystick) + + d.endpointConfigureTx(descHIDEndpointJoystick, + hid.txJoystickSize, false, nil) +} diff --git a/src/machine/usb/hhw_atsamd51.go b/src/machine/usb/hhw_atsamd51.go new file mode 100644 index 000000000..c737233cb --- /dev/null +++ b/src/machine/usb/hhw_atsamd51.go @@ -0,0 +1,61 @@ +//go:build (sam && atsamd51) || (sam && atsame5x) +// +build sam,atsamd51 sam,atsame5x + +package usb + +// Implementation of USB host controller driver (hcd) for Microchip SAMD51. + +import ( + "device/sam" + "runtime/interrupt" +) + +// hhwInterruptPriority defines the priority for all USB host interrupts. +const hhwInterruptPriority = 3 + +// hhw implements USB host controller hardware abstraction interface. +type hhw struct { + *hcd // USB host controller driver + + bus *sam.USB_HOST_Type // USB core registers + irq interrupt.Interrupt // USB IRQ, only a single interrupt on SAMx51 + + speed Speed +} + +// allocHHW returns a reference to the USB hardware abstraction for the given +// host controller driver. Should be called only one time and during host +// controller initialization. +func allocHHW(port, instance int, speed Speed, hc *hcd) *hhw { + switch port { + case 0: + hhwInstance[instance].hcd = hc + hhwInstance[instance].bus = sam.USB_HOST + } + + // Port defaults to full-speed (12 Mbit/sec) on SAMx51 + if 0 == speed { + speed = HighSpeed + } + hhwInstance[instance].speed = speed + + return &hhwInstance[instance] +} + +// init configures the USB port for host mode operation by initializing all +// endpoint and transfer descriptor data structures, initializing core registers +// and interrupts, resetting the USB PHY, and enabling power on the bus. +func (h *hhw) init() status { + + return statusOK +} + +// enable causes the USB core to enter (or exit) the normal run state and +// enables/disables all interrupts on the receiver's USB port. +func (h *hhw) enable(enable bool) { + if enable { + h.irq.Enable() // Enable USB interrupts + } else { + h.irq.Disable() // Disable USB interrupts + } +}