mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-14 07:53:40 +00:00
machine/stm32: add OTG FS USB driver for F4/F7
STM32F4 and F7 share the same OTG FS IP but have no USB driver in TinyGo. This adds a full device-mode driver covering CDC, HID, and MSC with working examples. - OTG FS has 4 physical EPs (0–3); virtual indices 4–7 fold onto them via physEP() so the existing machine/usb API is unchanged - F4 bypasses VBUS sensing via GCCFG.NOVBUSSENS; F7 uses the USB voltage regulator + GOTGCTL B-valid override instead - STM32F7 PLL_Q changed 2→9 to produce the 48 MHz clock required by USB/RNG/SDMMC; CK48MSEL cleared to select main PLL as source - HID and MSC descriptors remapped at init() to physical endpoint addresses (EP2/EP1 for HID, EP2/EP3 for MSC) - usb-storage example replaced machine.Flash with a FAT12 RAM disk so the host mounts without reformatting - MSC sendCSW sets queuedBytes before state transition to fix a missed byte-count on the status phase
This commit is contained in:
committed by
Ron Evans
parent
140c82e012
commit
c1a4ed1489
@@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) {
|
|||||||
// microcontrollers
|
// microcontrollers
|
||||||
{"hifive1b", "examples/echo", 3771, 309, 0, 2260},
|
{"hifive1b", "examples/echo", 3771, 309, 0, 2260},
|
||||||
{"microbit", "examples/serial", 2832, 368, 8, 2256},
|
{"microbit", "examples/serial", 2832, 368, 8, 2256},
|
||||||
{"wioterminal", "examples/pininterrupt", 8053, 1663, 132, 7488},
|
{"wioterminal", "examples/pininterrupt", 8065, 1663, 132, 7488},
|
||||||
|
|
||||||
// TODO: also check wasm. Right now this is difficult, because
|
// TODO: also check wasm. Right now this is difficult, because
|
||||||
// wasm binaries are run through wasm-opt and therefore the
|
// wasm binaries are run through wasm-opt and therefore the
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ func main() {
|
|||||||
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
kb := keyboard.Port()
|
kb := keyboard.Port()
|
||||||
|
machine.USBDev.Configure(machine.UARTConfig{}) // no-op if already init'd by serial.usb
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if !button.Get() {
|
if !button.Get() {
|
||||||
|
|||||||
@@ -6,8 +6,85 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Disk geometry.
|
||||||
|
const (
|
||||||
|
sectorSize = 512
|
||||||
|
diskSectors = 128 // 64 KB total
|
||||||
|
)
|
||||||
|
|
||||||
|
var diskData [diskSectors * sectorSize]byte
|
||||||
|
|
||||||
|
type ramDisk struct{}
|
||||||
|
|
||||||
|
func (r *ramDisk) ReadAt(p []byte, off int64) (int, error) {
|
||||||
|
return copy(p, diskData[off:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ramDisk) WriteAt(p []byte, off int64) (int, error) {
|
||||||
|
return copy(diskData[off:], p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ramDisk) Size() int64 { return int64(diskSectors * sectorSize) }
|
||||||
|
func (r *ramDisk) WriteBlockSize() int64 { return sectorSize }
|
||||||
|
func (r *ramDisk) EraseBlockSize() int64 { return sectorSize }
|
||||||
|
func (r *ramDisk) EraseBlocks(start, len int64) error { return nil }
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
formatFAT12(diskData[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatFAT12 writes a minimal FAT12 volume boot record and FAT tables so the
|
||||||
|
// host OS can mount the disk without reformatting.
|
||||||
|
func formatFAT12(d []byte) {
|
||||||
|
// --- Sector 0: Volume Boot Record ---
|
||||||
|
s := d[0:]
|
||||||
|
s[0] = 0xEB
|
||||||
|
s[1] = 0x3C
|
||||||
|
s[2] = 0x90 // short JMP + NOP
|
||||||
|
copy(s[3:11], "MSDOS5.0")
|
||||||
|
// BPB fields (little-endian)
|
||||||
|
s[11] = 0x00
|
||||||
|
s[12] = 0x02 // bytesPerSector = 512
|
||||||
|
s[13] = 0x01 // sectorsPerCluster = 1
|
||||||
|
s[14] = 0x01
|
||||||
|
s[15] = 0x00 // reservedSectors = 1
|
||||||
|
s[16] = 0x02 // numFATs = 2
|
||||||
|
s[17] = 0x20
|
||||||
|
s[18] = 0x00 // rootEntryCount = 32
|
||||||
|
s[19] = 0x80
|
||||||
|
s[20] = 0x00 // totalSectors16 = 128
|
||||||
|
s[21] = 0xF8 // mediaType = fixed disk
|
||||||
|
s[22] = 0x01
|
||||||
|
s[23] = 0x00 // sectorsPerFAT = 1
|
||||||
|
s[24] = 0x80
|
||||||
|
s[25] = 0x00 // sectorsPerTrack = 128
|
||||||
|
s[26] = 0x01
|
||||||
|
s[27] = 0x00 // numHeads = 1
|
||||||
|
// hiddenSectors[28:32] = 0
|
||||||
|
// totalSectors32[32:36] = 0
|
||||||
|
s[38] = 0x29 // extBootSig
|
||||||
|
s[39] = 0x47
|
||||||
|
s[40] = 0x4F
|
||||||
|
s[41] = 0x30
|
||||||
|
s[42] = 0x31 // volumeID "GO01"
|
||||||
|
copy(s[43:54], "TINYGO ") // volumeLabel (11 bytes)
|
||||||
|
copy(s[54:62], "FAT12 ") // fsType
|
||||||
|
s[510] = 0x55
|
||||||
|
s[511] = 0xAA // boot sector signature
|
||||||
|
|
||||||
|
// --- Sector 1: FAT1 ---
|
||||||
|
// Entry 0 = 0xFF8 (media byte), entry 1 = 0xFFF (EOC); all others = free.
|
||||||
|
d[512] = 0xF8
|
||||||
|
d[513] = 0xFF
|
||||||
|
d[514] = 0xFF
|
||||||
|
|
||||||
|
// --- Sector 2: FAT2 (identical copy) ---
|
||||||
|
copy(d[1024:1027], d[512:515])
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
msc.Port(machine.Flash)
|
msc.Port(&ramDisk{})
|
||||||
|
machine.USBDev.Configure(machine.UARTConfig{})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build esp32c3 || nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l0 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350
|
//go:build esp32c3 || nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32f7 || stm32l0 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ func (p Pin) PortMaskClear() (*uint32, uint32) {
|
|||||||
return &port.BSRR.Reg, 1 << (pin + 16)
|
return &port.BSRR.Reg, 1 << (pin + 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnterBootloader resets the chip into the bootloader.
|
||||||
|
// This is currently a stub for STM32, required to satisfy machine.EnterBootloader
|
||||||
|
// called by machine/usb/cdc.
|
||||||
|
func EnterBootloader() {
|
||||||
|
}
|
||||||
|
|
||||||
var deviceID [12]byte
|
var deviceID [12]byte
|
||||||
|
|
||||||
// DeviceID returns an identifier that is unique within
|
// DeviceID returns an identifier that is unique within
|
||||||
|
|||||||
@@ -0,0 +1,828 @@
|
|||||||
|
//go:build stm32f4 || stm32f7
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/stm32"
|
||||||
|
"machine/usb"
|
||||||
|
"runtime/interrupt"
|
||||||
|
"runtime/volatile"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NumberOfUSBEndpoints is sized to cover TinyGo's full endpoint index space
|
||||||
|
// (0=control, 1=CDC ACM, 2=CDC OUT, 3=CDC IN, 4=HID IN, 5=HID OUT, 6=MIDI IN, 7=MIDI OUT).
|
||||||
|
// Physical OTG FS hardware has 4 IN + 4 OUT endpoints (0–3).
|
||||||
|
const NumberOfUSBEndpoints = 8
|
||||||
|
|
||||||
|
// Default USB identifiers; board files with USB support should override these.
|
||||||
|
const (
|
||||||
|
usb_VID = uint16(0x239A)
|
||||||
|
usb_PID = uint16(0x0001)
|
||||||
|
usb_STRING_MANUFACTURER = "TinyGo"
|
||||||
|
usb_STRING_PRODUCT = "STM32 USB Device"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OTG FS register blocks.
|
||||||
|
var (
|
||||||
|
otgDevice = (*usbDeviceRegs)(unsafe.Pointer(stm32.OTG_FS_DEVICE))
|
||||||
|
otgPower = (*usbPowerRegs)(unsafe.Pointer(stm32.OTG_FS_PWRCLK))
|
||||||
|
)
|
||||||
|
|
||||||
|
// usbDeviceRegs represents the USB device-mode control block at base+0x800.
|
||||||
|
type usbDeviceRegs struct {
|
||||||
|
DCFG volatile.Register32 // 0x800
|
||||||
|
DCTL volatile.Register32 // 0x804
|
||||||
|
DSTS volatile.Register32 // 0x808
|
||||||
|
_ uint32 // 0x80C
|
||||||
|
DIEPMSK volatile.Register32 // 0x810
|
||||||
|
DOEPMSK volatile.Register32 // 0x814
|
||||||
|
DAINT volatile.Register32 // 0x818
|
||||||
|
DAINTMSK volatile.Register32 // 0x81C
|
||||||
|
_ [5]uint32 // 0x820 - 0x830
|
||||||
|
DIEPEMPMSK volatile.Register32 // 0x834
|
||||||
|
}
|
||||||
|
|
||||||
|
// usbInEndpointRegs represents the registers for a single IN endpoint at 0x900 + ep*0x20.
|
||||||
|
type usbInEndpointRegs struct {
|
||||||
|
CTL volatile.Register32 // 0x00
|
||||||
|
_ uint32 // 0x04
|
||||||
|
INT volatile.Register32 // 0x08
|
||||||
|
_ uint32 // 0x0C
|
||||||
|
TSIZ volatile.Register32 // 0x10
|
||||||
|
_ uint32 // 0x14
|
||||||
|
TXFST volatile.Register32 // 0x18
|
||||||
|
_ uint32 // 0x1C
|
||||||
|
}
|
||||||
|
|
||||||
|
// usbOutEndpointRegs represents the registers for a single OUT endpoint at 0xB00 + ep*0x20.
|
||||||
|
type usbOutEndpointRegs struct {
|
||||||
|
CTL volatile.Register32 // 0x00
|
||||||
|
_ uint32 // 0x04
|
||||||
|
INT volatile.Register32 // 0x08
|
||||||
|
_ uint32 // 0x0C
|
||||||
|
TSIZ volatile.Register32 // 0x10
|
||||||
|
_ [3]uint32 // 0x14 - 0x1C
|
||||||
|
}
|
||||||
|
|
||||||
|
// usbPowerRegs represents the power and clock gating block at base+0xE00.
|
||||||
|
type usbPowerRegs struct {
|
||||||
|
PCGCCTL volatile.Register32 // 0xE00
|
||||||
|
}
|
||||||
|
|
||||||
|
// otgInEP returns the IN endpoint registers for physical endpoint ep.
|
||||||
|
func otgInEP(ep uint32) *usbInEndpointRegs {
|
||||||
|
return (*usbInEndpointRegs)(unsafe.Pointer(uintptr(unsafe.Pointer(stm32.OTG_FS_GLOBAL)) + 0x900 + uintptr(ep)*0x20))
|
||||||
|
}
|
||||||
|
|
||||||
|
// otgOutEP returns the OUT endpoint registers for physical endpoint ep.
|
||||||
|
func otgOutEP(ep uint32) *usbOutEndpointRegs {
|
||||||
|
return (*usbOutEndpointRegs)(unsafe.Pointer(uintptr(unsafe.Pointer(stm32.OTG_FS_GLOBAL)) + 0xB00 + uintptr(ep)*0x20))
|
||||||
|
}
|
||||||
|
|
||||||
|
// otgDFIFO returns a volatile pointer to the data FIFO for physical endpoint ep.
|
||||||
|
// DFIFO[ep] is located at 0x50001000 + ep*0x1000.
|
||||||
|
func otgDFIFO(ep uint32) *volatile.Register32 {
|
||||||
|
return (*volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(stm32.OTG_FS_GLOBAL)) + 0x1000 + uintptr(ep)*0x1000))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DCFG bit positions and masks.
|
||||||
|
const (
|
||||||
|
dcfgDSPD = uint32(0x3) // FS PHY speed (bits [1:0] = 0b11)
|
||||||
|
dcfgDAD_Pos = uint32(4) // device address field start bit
|
||||||
|
dcfgDAD_Msk = uint32(0x7F << 4)
|
||||||
|
)
|
||||||
|
|
||||||
|
// DCTL bits.
|
||||||
|
const (
|
||||||
|
dctlSDIS = uint32(1 << 1) // soft disconnect
|
||||||
|
dctlSGINAK = uint32(1 << 7) // set global IN NAK
|
||||||
|
dctlCGINAK = uint32(1 << 8) // clear global IN NAK
|
||||||
|
dctlSGONAK = uint32(1 << 9) // set global OUT NAK
|
||||||
|
dctlCGONAK = uint32(1 << 10) // clear global OUT NAK
|
||||||
|
)
|
||||||
|
|
||||||
|
// DSTS bits.
|
||||||
|
const (
|
||||||
|
dstsSUSPSTS = uint32(1 << 0)
|
||||||
|
dstsENUMSPD_Pos = uint32(1)
|
||||||
|
dstsENUMSPD_Msk = uint32(0x3 << 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
// DIEPCTLn / DOEPCTLn bits (shared between IN and OUT endpoint control registers).
|
||||||
|
const (
|
||||||
|
depctlEPENA = uint32(1 << 31) // endpoint enable
|
||||||
|
depctlEPDIS = uint32(1 << 30) // endpoint disable
|
||||||
|
depctlSD0PID = uint32(1 << 28) // set DATA0 PID
|
||||||
|
depctlSNAK = uint32(1 << 27) // set NAK
|
||||||
|
depctlCNAK = uint32(1 << 26) // clear NAK
|
||||||
|
depctlSTALL = uint32(1 << 21) // STALL handshake
|
||||||
|
depctlETYP_Pos = uint32(18) // endpoint type field start
|
||||||
|
depctlUSBAEP = uint32(1 << 15) // USB active endpoint
|
||||||
|
depctlTXFNUM_Pos = uint32(22) // TX FIFO number field start (IN eps only)
|
||||||
|
depctlMPSIZ_Pos = uint32(0) // max packet size field start
|
||||||
|
)
|
||||||
|
|
||||||
|
// EP0 max packet size encoding in DIEPCTL0 / DOEPCTL0 bits [1:0].
|
||||||
|
const ep0Mps64 = uint32(0x0) // 64 bytes (FS default)
|
||||||
|
|
||||||
|
// DIEPINTn / DOEPINTn bits.
|
||||||
|
const (
|
||||||
|
depintXFRC = uint32(1 << 0) // transfer complete
|
||||||
|
depintSTUP = uint32(1 << 3) // SETUP phase done (DOEPINTn)
|
||||||
|
)
|
||||||
|
|
||||||
|
// GRXSTSP_Device PKTSTS field values.
|
||||||
|
const (
|
||||||
|
rxPktstsGNAK = uint32(0x1) // global OUT NAK
|
||||||
|
rxPktstsOUTData = uint32(0x2) // OUT data packet received
|
||||||
|
rxPktstsOUTDone = uint32(0x3) // OUT transfer complete
|
||||||
|
rxPktstsSetupDone = uint32(0x4) // SETUP transaction complete
|
||||||
|
rxPktstsSetupData = uint32(0x6) // SETUP data packet received (always 8 bytes)
|
||||||
|
)
|
||||||
|
|
||||||
|
// GINTSTS / GINTMSK bits (values taken from stm32f405 SVD constants).
|
||||||
|
const (
|
||||||
|
gintRXFLVL = uint32(stm32.USB_OTG_FS_GINTSTS_RXFLVL) // 0x10
|
||||||
|
gintUSBRST = uint32(stm32.USB_OTG_FS_GINTSTS_USBRST) // 0x1000
|
||||||
|
gintENUMDNE = uint32(stm32.USB_OTG_FS_GINTSTS_ENUMDNE) // 0x2000
|
||||||
|
gintUSBSUSP = uint32(stm32.USB_OTG_FS_GINTSTS_USBSUSP) // 0x800
|
||||||
|
gintWKUPINT = uint32(stm32.USB_OTG_FS_GINTSTS_WKUPINT) // 0x80000000
|
||||||
|
gintIEPINT = uint32(stm32.USB_OTG_FS_GINTSTS_IEPINT) // 0x40000
|
||||||
|
gintOEPINT = uint32(stm32.USB_OTG_FS_GINTSTS_OEPINT) // 0x80000
|
||||||
|
)
|
||||||
|
|
||||||
|
// GRSTCTL bits.
|
||||||
|
const (
|
||||||
|
grstCSRST = uint32(stm32.USB_OTG_FS_GRSTCTL_CSRST) // core soft reset
|
||||||
|
grstRXFFLSH = uint32(stm32.USB_OTG_FS_GRSTCTL_RXFFLSH) // RX FIFO flush
|
||||||
|
grstTXFFLSH = uint32(stm32.USB_OTG_FS_GRSTCTL_TXFFLSH) // TX FIFO flush
|
||||||
|
grstTXFNUM_Pos = uint32(stm32.USB_OTG_FS_GRSTCTL_TXFNUM_Pos)
|
||||||
|
grstAHBIDL = uint32(stm32.USB_OTG_FS_GRSTCTL_AHBIDL) // AHB master idle
|
||||||
|
)
|
||||||
|
|
||||||
|
// FIFO size layout in 32-bit words (total budget = 320 words).
|
||||||
|
const (
|
||||||
|
rxFIFODepth = uint32(128) // shared RX FIFO
|
||||||
|
ep0TxFIFODepth = uint32(16) // EP0 TX FIFO
|
||||||
|
ep1TxFIFODepth = uint32(64) // EP1 TX FIFO
|
||||||
|
ep2TxFIFODepth = uint32(64) // EP2 TX FIFO
|
||||||
|
ep3TxFIFODepth = uint32(48) // EP3 TX FIFO
|
||||||
|
|
||||||
|
ep0TxFIFOStart = rxFIFODepth
|
||||||
|
ep1TxFIFOStart = ep0TxFIFOStart + ep0TxFIFODepth
|
||||||
|
ep2TxFIFOStart = ep1TxFIFOStart + ep1TxFIFODepth
|
||||||
|
ep3TxFIFOStart = ep2TxFIFOStart + ep2TxFIFODepth
|
||||||
|
)
|
||||||
|
|
||||||
|
// Driver state.
|
||||||
|
var (
|
||||||
|
// sendOnEP0DATADONE tracks multi-chunk EP0 IN transfers.
|
||||||
|
sendOnEP0DATADONE struct {
|
||||||
|
ptr *byte
|
||||||
|
count int
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
// usbSetupBuf holds the 8-byte SETUP packet from the RX FIFO.
|
||||||
|
usbSetupBuf [8]byte
|
||||||
|
|
||||||
|
// usbRxBufLen tracks the byte count of the most recently received OUT packet
|
||||||
|
// per physical endpoint (index 0–3).
|
||||||
|
usbRxBufLen [4]uint32
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configure initialises the OTG FS USB peripheral in device mode.
|
||||||
|
// The config parameter is unused (present for interface compatibility).
|
||||||
|
func (dev *USBDevice) Configure(config UARTConfig) {
|
||||||
|
if dev.initcomplete {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 1. Enable peripheral clocks ----------------------------------------
|
||||||
|
|
||||||
|
// GPIOA clock (PA11 = D-, PA12 = D+)
|
||||||
|
stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOAEN)
|
||||||
|
// OTG FS peripheral clock
|
||||||
|
stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_OTGFSEN)
|
||||||
|
|
||||||
|
// ---- 2. Configure GPIO pins (PA11 D-, PA12 D+) as AF, very high speed ----
|
||||||
|
|
||||||
|
for _, pin := range [2]Pin{PA11, PA12} {
|
||||||
|
pos := uint8(pin%16) * 2
|
||||||
|
port := pin.getPort()
|
||||||
|
port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos)
|
||||||
|
port.OSPEEDR.ReplaceBits(gpioOutputSpeedVeryHigh, gpioOutputSpeedMask, pos)
|
||||||
|
port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos)
|
||||||
|
// OTYPER remains 0 (push-pull)
|
||||||
|
pin.SetAltFunc(10) // AF10 = OTG FS on both F4 and F7
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 3. OTG core reset --------------------------------------------------
|
||||||
|
|
||||||
|
// Wait for AHB master idle before core reset.
|
||||||
|
for stm32.OTG_FS_GLOBAL.GRSTCTL.Get()&grstAHBIDL == 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core soft reset
|
||||||
|
stm32.OTG_FS_GLOBAL.GRSTCTL.SetBits(grstCSRST)
|
||||||
|
for stm32.OTG_FS_GLOBAL.GRSTCTL.HasBits(grstCSRST) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for AHB idle again after reset
|
||||||
|
for stm32.OTG_FS_GLOBAL.GRSTCTL.Get()&grstAHBIDL == 0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 4. Force device mode, set turnaround time --------------------------
|
||||||
|
|
||||||
|
gusbcfg := stm32.OTG_FS_GLOBAL.GUSBCFG.Get()
|
||||||
|
gusbcfg &^= stm32.USB_OTG_FS_GUSBCFG_FHMOD |
|
||||||
|
stm32.USB_OTG_FS_GUSBCFG_FDMOD |
|
||||||
|
stm32.USB_OTG_FS_GUSBCFG_TRDT_Msk
|
||||||
|
gusbcfg |= stm32.USB_OTG_FS_GUSBCFG_FDMOD |
|
||||||
|
(9 << stm32.USB_OTG_FS_GUSBCFG_TRDT_Pos) // turnaround time = 9 for 216MHz HCLK
|
||||||
|
stm32.OTG_FS_GLOBAL.GUSBCFG.Set(gusbcfg)
|
||||||
|
|
||||||
|
// ---- 5. PHY / VBUS configuration (platform-specific) --------------------
|
||||||
|
|
||||||
|
initOTGFSPHY()
|
||||||
|
|
||||||
|
// ---- 6. Enable PHY clock, soft-disconnect before further init -----------
|
||||||
|
|
||||||
|
// Clear stop-clock / stop-phy-clock bits so the PHY clock runs.
|
||||||
|
// If a bootloader left these set, USB would hang silently.
|
||||||
|
otgPower.PCGCCTL.Set(0)
|
||||||
|
|
||||||
|
// Soft-disconnect now (after CSRST reset DCTL to its default connected state).
|
||||||
|
otgDevice.DCTL.SetBits(dctlSDIS)
|
||||||
|
|
||||||
|
// ---- 7. Configure data FIFOs --------------------------------------------
|
||||||
|
|
||||||
|
// RX FIFO (shared for all OUT + SETUP packets)
|
||||||
|
stm32.OTG_FS_GLOBAL.GRXFSIZ.Set(rxFIFODepth)
|
||||||
|
|
||||||
|
// EP0 TX FIFO: start = rxFIFODepth, depth = ep0TxFIFODepth
|
||||||
|
stm32.OTG_FS_GLOBAL.DIEPTXF0.Set(
|
||||||
|
(ep0TxFIFODepth << 16) | ep0TxFIFOStart,
|
||||||
|
)
|
||||||
|
|
||||||
|
// EP1–3 TX FIFOs
|
||||||
|
stm32.OTG_FS_GLOBAL.DIEPTXF1.Set(
|
||||||
|
(ep1TxFIFODepth << 16) | ep1TxFIFOStart,
|
||||||
|
)
|
||||||
|
stm32.OTG_FS_GLOBAL.DIEPTXF2.Set(
|
||||||
|
(ep2TxFIFODepth << 16) | ep2TxFIFOStart,
|
||||||
|
)
|
||||||
|
stm32.OTG_FS_GLOBAL.DIEPTXF3.Set(
|
||||||
|
(ep3TxFIFODepth << 16) | ep3TxFIFOStart,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---- 8. Flush FIFOs ----------------------------------------------------
|
||||||
|
|
||||||
|
flushRxFIFO()
|
||||||
|
flushTxFIFO(0x10) // flush all TX FIFOs (TXFNUM = 0x10 = all)
|
||||||
|
|
||||||
|
// ---- 9. Configure device: full-speed, no SOF output --------------------
|
||||||
|
|
||||||
|
otgDevice.DCFG.Set(dcfgDSPD) // FS PHY speed
|
||||||
|
|
||||||
|
// Clear any stale interrupts
|
||||||
|
stm32.OTG_FS_GLOBAL.GINTSTS.Set(0xFFFFFFFF)
|
||||||
|
|
||||||
|
// ---- 10. Enable interrupts ----------------------------------------------
|
||||||
|
|
||||||
|
stm32.OTG_FS_GLOBAL.GINTMSK.Set(
|
||||||
|
gintUSBRST | gintENUMDNE | gintRXFLVL | gintIEPINT | gintOEPINT |
|
||||||
|
gintUSBSUSP | gintWKUPINT,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enable device-level IN and OUT endpoint interrupt masks
|
||||||
|
otgDevice.DIEPMSK.Set(depintXFRC)
|
||||||
|
otgDevice.DOEPMSK.Set(depintXFRC | depintSTUP)
|
||||||
|
|
||||||
|
// Enable global interrupt
|
||||||
|
stm32.OTG_FS_GLOBAL.GAHBCFG.SetBits(stm32.USB_OTG_FS_GAHBCFG_GINT)
|
||||||
|
|
||||||
|
// ---- 11. Register and enable NVIC interrupt -----------------------------
|
||||||
|
|
||||||
|
intr := interrupt.New(stm32.IRQ_OTG_FS, handleUSBIRQ)
|
||||||
|
intr.SetPriority(0) // Highest priority
|
||||||
|
intr.Enable()
|
||||||
|
|
||||||
|
// ---- 12. Connect to host (clear soft-disconnect) -----------------------
|
||||||
|
|
||||||
|
otgDevice.DCTL.ClearBits(dctlSDIS)
|
||||||
|
|
||||||
|
dev.initcomplete = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleUSBIRQ is the OTG FS interrupt handler, dispatching on GINTSTS bits.
|
||||||
|
func handleUSBIRQ(intr interrupt.Interrupt) {
|
||||||
|
status := stm32.OTG_FS_GLOBAL.GINTSTS.Get() &
|
||||||
|
stm32.OTG_FS_GLOBAL.GINTMSK.Get()
|
||||||
|
|
||||||
|
if status&gintUSBSUSP != 0 {
|
||||||
|
stm32.OTG_FS_GLOBAL.GINTSTS.Set(gintUSBSUSP)
|
||||||
|
// Stop PHY clock during suspend. Only STPPCLK (bit 0); do NOT set
|
||||||
|
// GATEHCLK (bit 1) — that gates the AHB bus, which prevents the ISR
|
||||||
|
// from reading GINTSTS when WKUPINT fires.
|
||||||
|
otgPower.PCGCCTL.SetBits(1) // STPPCLK
|
||||||
|
}
|
||||||
|
|
||||||
|
if status&gintWKUPINT != 0 {
|
||||||
|
stm32.OTG_FS_GLOBAL.GINTSTS.Set(gintWKUPINT)
|
||||||
|
// Restart clocks before any endpoint activity can resume.
|
||||||
|
otgPower.PCGCCTL.ClearBits(1 | 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status&gintUSBRST != 0 {
|
||||||
|
stm32.OTG_FS_GLOBAL.GINTSTS.Set(gintUSBRST)
|
||||||
|
otgPower.PCGCCTL.ClearBits(1 | 2) // ensure clocks running after reset-from-suspend
|
||||||
|
handleUSBReset()
|
||||||
|
}
|
||||||
|
|
||||||
|
if status&gintRXFLVL != 0 {
|
||||||
|
// RXFLVL is level-triggered; drain entire FIFO in a loop.
|
||||||
|
stm32.OTG_FS_GLOBAL.GINTMSK.ClearBits(gintRXFLVL)
|
||||||
|
handleRxFIFO()
|
||||||
|
stm32.OTG_FS_GLOBAL.GINTMSK.SetBits(gintRXFLVL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status&gintENUMDNE != 0 {
|
||||||
|
stm32.OTG_FS_GLOBAL.GINTSTS.Set(gintENUMDNE)
|
||||||
|
handleEnumDone()
|
||||||
|
}
|
||||||
|
|
||||||
|
if status&gintIEPINT != 0 {
|
||||||
|
handleInEndpoints()
|
||||||
|
}
|
||||||
|
|
||||||
|
if status&gintOEPINT != 0 {
|
||||||
|
handleOutEndpoints()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleUSBReset is called on USB bus reset (USBRST interrupt).
|
||||||
|
func handleUSBReset() {
|
||||||
|
// Set NAK on all OUT endpoints.
|
||||||
|
for ep := uint32(0); ep < 4; ep++ {
|
||||||
|
otgOutEP(ep).CTL.SetBits(depctlSNAK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush RX and all TX FIFOs.
|
||||||
|
flushRxFIFO()
|
||||||
|
flushTxFIFO(0x10)
|
||||||
|
|
||||||
|
// Clear all endpoint interrupts.
|
||||||
|
otgDevice.DAINT.Set(0xFFFFFFFF)
|
||||||
|
otgDevice.DAINTMSK.Set(0)
|
||||||
|
|
||||||
|
// Enable EP0 IN and OUT interrupt sources.
|
||||||
|
otgDevice.DAINTMSK.Set((1 << 0) | (1 << 16)) // DIEP0 + DOEP0
|
||||||
|
|
||||||
|
// Re-arm EP0 OUT for up to 3 back-to-back SETUP packets.
|
||||||
|
armEP0Out()
|
||||||
|
|
||||||
|
// Signal upper layer: device is no longer configured.
|
||||||
|
usbConfiguration = 0
|
||||||
|
USBDev.InitEndpointComplete = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEnumDone is called after USB enumeration speed is detected (ENUMDNE).
|
||||||
|
func handleEnumDone() {
|
||||||
|
// Activate EP0 (max packet 64, type control, TX FIFO 0).
|
||||||
|
ep0Ctl := ep0Mps64 | depctlUSBAEP | (0 << depctlETYP_Pos) // control type
|
||||||
|
otgInEP(0).CTL.SetBits(ep0Ctl)
|
||||||
|
otgOutEP(0).CTL.SetBits(ep0Mps64 | depctlUSBAEP)
|
||||||
|
|
||||||
|
// Clear global IN NAK so EP0 IN can send.
|
||||||
|
otgDevice.DCTL.SetBits(dctlCGINAK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRxFIFO drains the RX FIFO completely, processing each pop via GRXSTSP.
|
||||||
|
// RXFLVL is level-triggered, so this must loop until the FIFO is empty.
|
||||||
|
func handleRxFIFO() {
|
||||||
|
for stm32.OTG_FS_GLOBAL.GINTSTS.HasBits(gintRXFLVL) {
|
||||||
|
status := stm32.OTG_FS_GLOBAL.GRXSTSP_Device.Get()
|
||||||
|
|
||||||
|
ep := status & stm32.USB_OTG_FS_GRXSTSP_Device_EPNUM_Msk
|
||||||
|
bcnt := (status & stm32.USB_OTG_FS_GRXSTSP_Device_BCNT_Msk) >>
|
||||||
|
stm32.USB_OTG_FS_GRXSTSP_Device_BCNT_Pos
|
||||||
|
pktsts := (status >> stm32.USB_OTG_FS_GRXSTSP_Device_PKTSTS_Pos) & 0xF
|
||||||
|
|
||||||
|
pep := ep // GRXSTSP.EPNUM is already a physical endpoint (0–3)
|
||||||
|
|
||||||
|
switch pktsts {
|
||||||
|
case rxPktstsSetupData:
|
||||||
|
// 8-byte SETUP packet: read exactly 2 words from DFIFO[0].
|
||||||
|
w0 := otgDFIFO(0).Get()
|
||||||
|
w1 := otgDFIFO(0).Get()
|
||||||
|
usbSetupBuf[0] = byte(w0)
|
||||||
|
usbSetupBuf[1] = byte(w0 >> 8)
|
||||||
|
usbSetupBuf[2] = byte(w0 >> 16)
|
||||||
|
usbSetupBuf[3] = byte(w0 >> 24)
|
||||||
|
usbSetupBuf[4] = byte(w1)
|
||||||
|
usbSetupBuf[5] = byte(w1 >> 8)
|
||||||
|
usbSetupBuf[6] = byte(w1 >> 16)
|
||||||
|
usbSetupBuf[7] = byte(w1 >> 24)
|
||||||
|
|
||||||
|
case rxPktstsSetupDone:
|
||||||
|
// SETUP transaction complete: process the buffered SETUP packet.
|
||||||
|
setup := usb.Setup{
|
||||||
|
BmRequestType: usbSetupBuf[0],
|
||||||
|
BRequest: usbSetupBuf[1],
|
||||||
|
WValueL: usbSetupBuf[2],
|
||||||
|
WValueH: usbSetupBuf[3],
|
||||||
|
WIndex: uint16(usbSetupBuf[4]) | (uint16(usbSetupBuf[5]) << 8),
|
||||||
|
WLength: uint16(usbSetupBuf[6]) | (uint16(usbSetupBuf[7]) << 8),
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := false
|
||||||
|
if setup.BmRequestType&usb.REQUEST_TYPE == usb.REQUEST_STANDARD {
|
||||||
|
ok = handleStandardSetup(setup)
|
||||||
|
} else {
|
||||||
|
if setup.WIndex < uint16(len(usbSetupHandler)) &&
|
||||||
|
usbSetupHandler[setup.WIndex] != nil {
|
||||||
|
ok = usbSetupHandler[setup.WIndex](setup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
// Stall EP0 IN and OUT on unrecognised requests.
|
||||||
|
otgInEP(0).CTL.SetBits(depctlSTALL)
|
||||||
|
otgOutEP(0).CTL.SetBits(depctlSTALL)
|
||||||
|
}
|
||||||
|
// Re-arm EP0 OUT for the next SETUP.
|
||||||
|
armEP0Out()
|
||||||
|
|
||||||
|
case rxPktstsOUTData:
|
||||||
|
// OUT data: read bcnt bytes from DFIFO[pep] into cache buffer.
|
||||||
|
if bcnt > 0 && pep < 4 {
|
||||||
|
readFIFO(pep, bcnt)
|
||||||
|
usbRxBufLen[pep] = bcnt
|
||||||
|
}
|
||||||
|
|
||||||
|
case rxPktstsOUTDone:
|
||||||
|
// OUT transfer complete: nothing to do here; handled in handleOutEndpoints.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readFIFO reads bcnt bytes from the shared RX FIFO (DFIFO 0) into udd_ep_out_cache_buffer[ep].
|
||||||
|
func readFIFO(ep, bcnt uint32) {
|
||||||
|
buf := udd_ep_out_cache_buffer[ep][:]
|
||||||
|
words := (bcnt + 3) / 4
|
||||||
|
for i := uint32(0); i < words; i++ {
|
||||||
|
w := otgDFIFO(0).Get() // Always read from FIFO 0
|
||||||
|
b := i * 4
|
||||||
|
buf[b] = byte(w)
|
||||||
|
if b+1 < bcnt {
|
||||||
|
buf[b+1] = byte(w >> 8)
|
||||||
|
}
|
||||||
|
if b+2 < bcnt {
|
||||||
|
buf[b+2] = byte(w >> 16)
|
||||||
|
}
|
||||||
|
if b+3 < bcnt {
|
||||||
|
buf[b+3] = byte(w >> 24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleInEndpoints handles IEPINT: checks each active IN endpoint for XFRC.
|
||||||
|
func handleInEndpoints() {
|
||||||
|
daint := otgDevice.DAINT.Get() & 0x0000FFFF // lower 16 bits = IN EPs
|
||||||
|
daintmsk := otgDevice.DAINTMSK.Get() & 0x0000FFFF
|
||||||
|
active := daint & daintmsk
|
||||||
|
|
||||||
|
for ep := uint32(0); ep < 4; ep++ {
|
||||||
|
if active&(1<<ep) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
diep := otgInEP(ep)
|
||||||
|
diepint := diep.INT.Get()
|
||||||
|
diepintmsk := otgDevice.DIEPMSK.Get()
|
||||||
|
fired := diepint & diepintmsk
|
||||||
|
|
||||||
|
if fired&depintXFRC != 0 {
|
||||||
|
// Clear XFRC.
|
||||||
|
diep.INT.Set(depintXFRC)
|
||||||
|
|
||||||
|
if ep == 0 {
|
||||||
|
// EP0 IN transfer complete.
|
||||||
|
if sendOnEP0DATADONE.ptr != nil {
|
||||||
|
// More data to send.
|
||||||
|
ptr := sendOnEP0DATADONE.ptr
|
||||||
|
count := sendOnEP0DATADONE.count
|
||||||
|
if count > usb.EndpointPacketSize {
|
||||||
|
sendOnEP0DATADONE.offset += usb.EndpointPacketSize
|
||||||
|
sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[sendOnEP0DATADONE.offset]
|
||||||
|
count = usb.EndpointPacketSize
|
||||||
|
}
|
||||||
|
sendOnEP0DATADONE.count -= count
|
||||||
|
sendViaEPIn(0, ptr, count)
|
||||||
|
if sendOnEP0DATADONE.count == 0 {
|
||||||
|
sendOnEP0DATADONE.ptr = nil
|
||||||
|
sendOnEP0DATADONE.offset = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// All EP0 IN data sent; arm EP0 OUT for the status ZLP from host.
|
||||||
|
armEP0Out()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Non-EP0 IN: find the virtual endpoint(s) mapped to this physical EP
|
||||||
|
// and call the registered TX handler. Multiple virtual EPs may share
|
||||||
|
// a physical EP (e.g., HID_IN=4 and CDC_IN=3 both → physical 1 or 3).
|
||||||
|
for vep := uint32(0); vep < NumberOfUSBEndpoints; vep++ {
|
||||||
|
if vep == ep && usbTxHandler[vep] != nil {
|
||||||
|
usbTxHandler[vep]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleOutEndpoints handles OEPINT: checks each active OUT endpoint for STUP / XFRC.
|
||||||
|
func handleOutEndpoints() {
|
||||||
|
daint := otgDevice.DAINT.Get() >> 16 // upper 16 bits = OUT EPs
|
||||||
|
daintmsk := otgDevice.DAINTMSK.Get() >> 16
|
||||||
|
active := daint & daintmsk
|
||||||
|
|
||||||
|
for ep := uint32(0); ep < 4; ep++ {
|
||||||
|
if active&(1<<ep) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
doep := otgOutEP(ep)
|
||||||
|
doepint := doep.INT.Get()
|
||||||
|
doepintmsk := otgDevice.DOEPMSK.Get()
|
||||||
|
fired := doepint & doepintmsk
|
||||||
|
|
||||||
|
if fired&depintSTUP != 0 {
|
||||||
|
// EP0 SETUP phase done (already processed in handleRxFIFO).
|
||||||
|
doep.INT.Set(depintSTUP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fired&depintXFRC != 0 {
|
||||||
|
doep.INT.Set(depintXFRC)
|
||||||
|
if ep > 0 {
|
||||||
|
buf := handleEndpointRx(ep)
|
||||||
|
// Find the virtual endpoint(s) mapped to this physical EP and call the RX handler.
|
||||||
|
for vep := uint32(0); vep < NumberOfUSBEndpoints; vep++ {
|
||||||
|
if vep == ep && usbRxHandler[vep] != nil {
|
||||||
|
if usbRxHandler[vep](buf) {
|
||||||
|
AckUsbOutTransfer(ep)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initEndpoint configures a USB endpoint for the given type and direction.
|
||||||
|
// MPS is hardcoded to 64 bytes; the caller (usb.go) does not pass a descriptor.
|
||||||
|
func initEndpoint(ep, config uint32) {
|
||||||
|
pep := ep
|
||||||
|
if pep == 0 {
|
||||||
|
return // EP0 is always active; configured in handleEnumDone
|
||||||
|
}
|
||||||
|
|
||||||
|
txFIFONum := pep // TX FIFO number matches physical EP
|
||||||
|
|
||||||
|
switch config {
|
||||||
|
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
|
||||||
|
ctl := (64 << depctlMPSIZ_Pos) | depctlUSBAEP |
|
||||||
|
(txFIFONum << depctlTXFNUM_Pos) |
|
||||||
|
(3 << depctlETYP_Pos) | // interrupt type
|
||||||
|
depctlSD0PID
|
||||||
|
otgInEP(pep).CTL.Set(ctl)
|
||||||
|
otgDevice.DAINTMSK.SetBits(1 << pep)
|
||||||
|
|
||||||
|
case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn:
|
||||||
|
ctl := (64 << depctlMPSIZ_Pos) | depctlUSBAEP |
|
||||||
|
(txFIFONum << depctlTXFNUM_Pos) |
|
||||||
|
(2 << depctlETYP_Pos) | // bulk type
|
||||||
|
depctlSD0PID
|
||||||
|
otgInEP(pep).CTL.Set(ctl)
|
||||||
|
otgDevice.DAINTMSK.SetBits(1 << pep)
|
||||||
|
|
||||||
|
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut:
|
||||||
|
ctl := uint32(64) | depctlUSBAEP | depctlSD0PID |
|
||||||
|
(3 << depctlETYP_Pos) // interrupt type
|
||||||
|
otgOutEP(pep).CTL.Set(ctl)
|
||||||
|
otgOutEP(pep).TSIZ.Set((1 << 19) | 64)
|
||||||
|
otgOutEP(pep).CTL.SetBits(depctlEPENA | depctlCNAK)
|
||||||
|
otgDevice.DAINTMSK.SetBits(1 << (pep + 16))
|
||||||
|
|
||||||
|
case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut:
|
||||||
|
ctl := uint32(64) | depctlUSBAEP | depctlSD0PID |
|
||||||
|
(2 << depctlETYP_Pos) // bulk type
|
||||||
|
otgOutEP(pep).CTL.Set(ctl)
|
||||||
|
otgOutEP(pep).TSIZ.Set((1 << 19) | 64)
|
||||||
|
otgOutEP(pep).CTL.SetBits(depctlEPENA | depctlCNAK)
|
||||||
|
otgDevice.DAINTMSK.SetBits(1 << (pep + 16))
|
||||||
|
|
||||||
|
case usb.ENDPOINT_TYPE_CONTROL:
|
||||||
|
// EP0 activated in handleEnumDone.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendUSBInPacket sends data on a USB IN endpoint (interrupt or bulk).
|
||||||
|
func SendUSBInPacket(ep uint32, data []byte) bool {
|
||||||
|
sendUSBPacket(ep, data)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendUSBPacket copies data into the endpoint cache buffer then initiates the transfer.
|
||||||
|
//
|
||||||
|
//go:noinline
|
||||||
|
func sendUSBPacket(ep uint32, data []byte) {
|
||||||
|
count := len(data)
|
||||||
|
var buf []byte
|
||||||
|
if ep == 0 {
|
||||||
|
buf = udd_ep_control_cache_buffer[:]
|
||||||
|
if count > usb.EndpointPacketSize {
|
||||||
|
// Large response: queue continuation via sendOnEP0DATADONE.
|
||||||
|
sendOnEP0DATADONE.offset = usb.EndpointPacketSize
|
||||||
|
sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[usb.EndpointPacketSize]
|
||||||
|
sendOnEP0DATADONE.count = count - usb.EndpointPacketSize
|
||||||
|
count = usb.EndpointPacketSize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pep := ep
|
||||||
|
buf = udd_ep_in_cache_buffer[pep][:]
|
||||||
|
}
|
||||||
|
copy(buf[:len(data)], data)
|
||||||
|
sendViaEPIn(ep, &buf[0], count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendViaEPIn arms the IN endpoint and writes count bytes from ptr into the TX FIFO.
|
||||||
|
func sendViaEPIn(ep uint32, ptr *byte, count int) {
|
||||||
|
pep := ep
|
||||||
|
diep := otgInEP(pep)
|
||||||
|
|
||||||
|
// Verify TX FIFO has enough space before writing.
|
||||||
|
// DTXFSTS[15:0] = INEPTFSAV: available words. Stall if insufficient.
|
||||||
|
if count > 0 {
|
||||||
|
need := uint32((count + 3) / 4)
|
||||||
|
avail := diep.TXFST.Get() & 0xFFFF
|
||||||
|
if avail < need {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Program transfer size: 1 packet, count bytes.
|
||||||
|
diep.TSIZ.Set(
|
||||||
|
uint32(count) | (1 << 19), // XFRSIZ = count, PKTCNT = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enable endpoint and clear NAK (starts transfer).
|
||||||
|
diep.CTL.SetBits(depctlEPENA | depctlCNAK)
|
||||||
|
|
||||||
|
// Write bytes to FIFO in 32-bit words (last word padded if needed).
|
||||||
|
fifo := otgDFIFO(pep)
|
||||||
|
data := unsafe.Slice(ptr, count)
|
||||||
|
words := (count + 3) / 4
|
||||||
|
for i := 0; i < words; i++ {
|
||||||
|
b := i * 4
|
||||||
|
var w uint32
|
||||||
|
w = uint32(data[b])
|
||||||
|
if b+1 < count {
|
||||||
|
w |= uint32(data[b+1]) << 8
|
||||||
|
}
|
||||||
|
if b+2 < count {
|
||||||
|
w |= uint32(data[b+2]) << 16
|
||||||
|
}
|
||||||
|
if b+3 < count {
|
||||||
|
w |= uint32(data[b+3]) << 24
|
||||||
|
}
|
||||||
|
fifo.Set(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendZlp sends a zero-length packet on EP0 IN (status stage for OUT control transfers).
|
||||||
|
func SendZlp() {
|
||||||
|
// PKTCNT=1, XFRSIZ=0
|
||||||
|
otgInEP(0).TSIZ.Set(1 << 19)
|
||||||
|
otgInEP(0).CTL.SetBits(depctlEPENA | depctlCNAK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEndpointRx returns the bytes received on the given physical endpoint.
|
||||||
|
func handleEndpointRx(ep uint32) []byte {
|
||||||
|
pep := ep
|
||||||
|
return udd_ep_out_cache_buffer[pep][:usbRxBufLen[pep]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AckUsbOutTransfer re-arms the OUT endpoint to receive the next packet.
|
||||||
|
func AckUsbOutTransfer(ep uint32) {
|
||||||
|
pep := ep
|
||||||
|
usbRxBufLen[pep] = 0
|
||||||
|
doep := otgOutEP(pep)
|
||||||
|
doep.TSIZ.Set(
|
||||||
|
(1 << 19) | 64, // PKTCNT=1, XFRSIZ=64
|
||||||
|
)
|
||||||
|
doep.CTL.SetBits(depctlEPENA | depctlCNAK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleUSBSetAddress applies the new device address from a SET_ADDRESS request
|
||||||
|
// and sends the status ZLP. The address is written to DCFG before the ZLP is
|
||||||
|
// enqueued: the OTG FS core has already committed the current IN token to
|
||||||
|
// address 0, so the ZLP goes out at the old address while the new address is
|
||||||
|
// already in DCFG and ready for the host's next transaction.
|
||||||
|
func handleUSBSetAddress(setup usb.Setup) bool {
|
||||||
|
addr := uint8(setup.WValueL) & 0x7F
|
||||||
|
|
||||||
|
dcfg := otgDevice.DCFG.Get()
|
||||||
|
dcfg &^= dcfgDAD_Msk
|
||||||
|
dcfg |= uint32(addr) << dcfgDAD_Pos
|
||||||
|
otgDevice.DCFG.Set(dcfg)
|
||||||
|
|
||||||
|
SendZlp()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiveUSBControlPacket synchronously receives a CDC control OUT packet on EP0.
|
||||||
|
func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
|
||||||
|
var b [cdcLineInfoSize]byte
|
||||||
|
|
||||||
|
// Arm EP0 OUT for up to 64 bytes.
|
||||||
|
armEP0Out()
|
||||||
|
|
||||||
|
// Busy-wait for data to arrive. We call handleRxFIFO() manually to drain
|
||||||
|
// the shared RX FIFO because we are currently in an interrupt context
|
||||||
|
// (this is called from the setup handler) and the hardware-triggered
|
||||||
|
// handleRxFIFO loop is blocked waiting for us to return.
|
||||||
|
const timeout = 300000
|
||||||
|
for i := 0; i < timeout; i++ {
|
||||||
|
if stm32.OTG_FS_GLOBAL.GINTSTS.HasBits(gintRXFLVL) {
|
||||||
|
handleRxFIFO()
|
||||||
|
}
|
||||||
|
if usbRxBufLen[0] > 0 {
|
||||||
|
n := usbRxBufLen[0]
|
||||||
|
if n > cdcLineInfoSize {
|
||||||
|
n = cdcLineInfoSize
|
||||||
|
}
|
||||||
|
copy(b[:n], udd_ep_out_cache_buffer[0][:n])
|
||||||
|
usbRxBufLen[0] = 0
|
||||||
|
SendZlp()
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b, ErrUSBReadTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStallEPIn stalls an IN endpoint.
|
||||||
|
func (dev *USBDevice) SetStallEPIn(ep uint32) {
|
||||||
|
pep := ep
|
||||||
|
otgInEP(pep).CTL.SetBits(depctlSTALL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearStallEPIn clears the stall condition on an IN endpoint.
|
||||||
|
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
|
||||||
|
pep := ep
|
||||||
|
// Clear STALL and reset DATA0 PID.
|
||||||
|
ctl := &otgInEP(pep).CTL
|
||||||
|
ctl.ClearBits(depctlSTALL)
|
||||||
|
ctl.SetBits(depctlSD0PID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStallEPOut stalls an OUT endpoint.
|
||||||
|
func (dev *USBDevice) SetStallEPOut(ep uint32) {
|
||||||
|
pep := ep
|
||||||
|
otgOutEP(pep).CTL.SetBits(depctlSTALL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearStallEPOut clears the stall condition on an OUT endpoint.
|
||||||
|
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
|
||||||
|
pep := ep
|
||||||
|
ctl := &otgOutEP(pep).CTL
|
||||||
|
ctl.ClearBits(depctlSTALL)
|
||||||
|
ctl.SetBits(depctlSD0PID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// armEP0Out re-arms EP0 OUT to receive the next SETUP or status ZLP from the host.
|
||||||
|
func armEP0Out() {
|
||||||
|
// STUPCNT=3 (bits[30:29]=11): accept up to 3 back-to-back SETUPs.
|
||||||
|
// PKTCNT=1 (bit[19]): one packet.
|
||||||
|
// XFRSIZ=64 (bits[6:0]): max 64 bytes.
|
||||||
|
otgOutEP(0).TSIZ.Set((3 << 29) | (1 << 19) | 64)
|
||||||
|
otgOutEP(0).CTL.SetBits(depctlEPENA | depctlCNAK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushTxFIFO flushes the selected TX FIFO(s).
|
||||||
|
// txfnum: 0–3 for a specific FIFO, 0x10 to flush all TX FIFOs.
|
||||||
|
func flushTxFIFO(txfnum uint32) {
|
||||||
|
stm32.OTG_FS_GLOBAL.GRSTCTL.Set(
|
||||||
|
grstTXFFLSH | (txfnum << grstTXFNUM_Pos),
|
||||||
|
)
|
||||||
|
for stm32.OTG_FS_GLOBAL.GRSTCTL.HasBits(grstTXFFLSH) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushRxFIFO flushes the shared RX FIFO.
|
||||||
|
func flushRxFIFO() {
|
||||||
|
stm32.OTG_FS_GLOBAL.GRSTCTL.Set(grstRXFFLSH)
|
||||||
|
for stm32.OTG_FS_GLOBAL.GRSTCTL.HasBits(grstRXFFLSH) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build stm32f4 && !(stm32f429 || stm32f427 || stm32f411 || stm32f407 || stm32f405 || stm32f401)
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import "device/stm32"
|
||||||
|
|
||||||
|
// initOTGFSPHY enables the STM32F4 OTG FS PHY and bypasses VBUS sensing.
|
||||||
|
// GCCFG.PWRDWN deactivates the PHY power-down; NOVBUSSENS skips the VBUS pin
|
||||||
|
// check so boards without PA9 connected to VBUS still enumerate.
|
||||||
|
func initOTGFSPHY() {
|
||||||
|
stm32.OTG_FS_GLOBAL.GCCFG.Set(stm32.USB_OTG_FS_GCCFG_PWRDWN)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//go:build stm32f4 && (stm32f429 || stm32f427 || stm32f411 || stm32f407 || stm32f405 || stm32f401)
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import "device/stm32"
|
||||||
|
|
||||||
|
// initOTGFSPHY enables the STM32F4 OTG FS PHY and bypasses VBUS sensing.
|
||||||
|
// GCCFG.PWRDWN deactivates the PHY power-down; NOVBUSSENS skips the VBUS pin
|
||||||
|
// check so boards without PA9 connected to VBUS still enumerate.
|
||||||
|
func initOTGFSPHY() {
|
||||||
|
stm32.OTG_FS_GLOBAL.GCCFG.Set(
|
||||||
|
stm32.USB_OTG_FS_GCCFG_PWRDWN | // enable FS PHY
|
||||||
|
stm32.USB_OTG_FS_GCCFG_NOVBUSSENS, // bypass VBUS sensing
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
//go:build stm32f7
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
// eraseBlockSize returns the smallest erasable unit for the STM32F7 internal
|
||||||
|
// flash. The first sectors are 32 KB; return that as the nominal page size.
|
||||||
|
// Flash write/erase via machine.Flash is not implemented for STM32F7; this
|
||||||
|
// stub satisfies the flash.go interface so that BlockDevice (needed by MSC)
|
||||||
|
// compiles on this target.
|
||||||
|
func eraseBlockSize() int64 { return 32768 }
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//go:build stm32f7
|
||||||
|
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/arm"
|
||||||
|
"device/stm32"
|
||||||
|
)
|
||||||
|
|
||||||
|
// initOTGFSPHY enables the STM32F7 OTG FS PHY and overrides B-session VBUS
|
||||||
|
// detection via GOTGCTL so boards without VBUS sensing still enumerate.
|
||||||
|
func initOTGFSPHY() {
|
||||||
|
// Enable USB voltage regulator (specific to F72x/F73x).
|
||||||
|
// Bit 14 in PWR_CR2 is USBREGEN.
|
||||||
|
stm32.PWR.CR2.SetBits(1 << 14)
|
||||||
|
|
||||||
|
// Stabilization delay for the regulator (~100us is plenty).
|
||||||
|
for i := 0; i < 10000; i++ {
|
||||||
|
arm.Asm("nop")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable FS PHY.
|
||||||
|
stm32.OTG_FS_GLOBAL.GCCFG.SetBits(stm32.USB_OTG_FS_GCCFG_PWRDWN)
|
||||||
|
|
||||||
|
// Disable hardware VBUS detection (F7 uses VBDEN, opposite polarity to F4's NOVBUSSENS).
|
||||||
|
// Clearing this prevents the peripheral from gating enumeration on PA9 VBUS level.
|
||||||
|
stm32.OTG_FS_GLOBAL.GCCFG.ClearBits(stm32.USB_OTG_FS_GCCFG_VBDEN)
|
||||||
|
|
||||||
|
// Override B-session valid so GOTGCTL-based detection reports device connected.
|
||||||
|
stm32.OTG_FS_GLOBAL.GOTGCTL.SetBits(
|
||||||
|
stm32.USB_OTG_FS_GOTGCTL_BVALOEN | // enable B-valid override
|
||||||
|
stm32.USB_OTG_FS_GOTGCTL_BVALOVAL, // set B-valid = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
+8
-2
@@ -1,4 +1,4 @@
|
|||||||
//go:build sam || nrf52840 || rp2040 || rp2350
|
//go:build sam || nrf52840 || rp2040 || rp2350 || stm32f4 || stm32f7
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
@@ -184,7 +184,9 @@ func sendDescriptor(setup usb.Setup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case descriptor.TypeDeviceQualifier:
|
case descriptor.TypeDeviceQualifier:
|
||||||
// skip
|
// Full-speed-only device: STALL to signal no high-speed capability (USB 2.0 §9.6.2).
|
||||||
|
USBDev.SetStallEPIn(0)
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,6 +371,10 @@ func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.S
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PhysicalEndpoint maps a virtual endpoint index to the physical endpoint number
|
||||||
|
// used by the hardware. This is an identity mapping on all currently supported platforms.
|
||||||
|
func PhysicalEndpoint(ep uint32) uint32 { return ep }
|
||||||
|
|
||||||
func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) {
|
func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) {
|
||||||
usbDescriptor = desc
|
usbDescriptor = desc
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ func (m *msc) sendCSW(status csw.Status) {
|
|||||||
residue = expected - m.sentBytes
|
residue = expected - m.sentBytes
|
||||||
}
|
}
|
||||||
m.cbw.CSW(status, residue, m.cswBuf)
|
m.cbw.CSW(status, residue, m.cswBuf)
|
||||||
|
m.queuedBytes = csw.MsgLen
|
||||||
m.state = mscStateStatusSent
|
m.state = mscStateStatusSent
|
||||||
m.queuedBytes = csw.MsgLen
|
m.queuedBytes = csw.MsgLen
|
||||||
m.sendUSBPacket(m.cswBuf)
|
m.sendUSBPacket(m.cswBuf)
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ func (m *msc) handleClearFeature(setup usb.Setup, wValue uint16) bool {
|
|||||||
// (b) a Clear Feature HALT to the Bulk-In endpoint (clear stall IN)
|
// (b) a Clear Feature HALT to the Bulk-In endpoint (clear stall IN)
|
||||||
// (c) a Clear Feature HALT to the Bulk-Out endpoint (clear stall OUT)
|
// (c) a Clear Feature HALT to the Bulk-Out endpoint (clear stall OUT)
|
||||||
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
|
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
|
||||||
|
|
||||||
if m.state == mscStateNeedReset {
|
if m.state == mscStateNeedReset {
|
||||||
wIndex := uint8(setup.WIndex & 0x7F)
|
wIndex := uint8(setup.WIndex & 0x7F)
|
||||||
if wIndex == usb.MSC_ENDPOINT_IN {
|
if wIndex == usb.MSC_ENDPOINT_IN {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build stm32 && serial.usb
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import _ "machine/usb/cdc"
|
||||||
@@ -23,7 +23,7 @@ const (
|
|||||||
PLL_M = 4
|
PLL_M = 4
|
||||||
PLL_N = 216
|
PLL_N = 216
|
||||||
PLL_P = 2
|
PLL_P = 2
|
||||||
PLL_Q = 2
|
PLL_Q = 9
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -55,7 +55,7 @@ func initCLK() {
|
|||||||
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN)
|
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN)
|
||||||
_ = stm32.RCC.APB1ENR.Get()
|
_ = stm32.RCC.APB1ENR.Get()
|
||||||
|
|
||||||
// PWR_VOLTAGESCALING_CONFIG
|
// PWR_VOLTAGESCALING_CONFIG: Set VOS to Scale 1 (max performance)
|
||||||
stm32.PWR.CR1.ReplaceBits(0x3<<stm32.PWR_CR1_VOS_Pos, stm32.PWR_CR1_VOS_Msk, 0)
|
stm32.PWR.CR1.ReplaceBits(0x3<<stm32.PWR_CR1_VOS_Pos, stm32.PWR_CR1_VOS_Msk, 0)
|
||||||
_ = stm32.PWR.CR1.Get()
|
_ = stm32.PWR.CR1.Get()
|
||||||
|
|
||||||
@@ -67,6 +67,9 @@ func initCLK() {
|
|||||||
stm32.FLASH.ACR.ReplaceBits(7, stm32.FLASH_ACR_LATENCY_Msk, 0)
|
stm32.FLASH.ACR.ReplaceBits(7, stm32.FLASH_ACR_LATENCY_Msk, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable ART Accelerator and Prefetch (ARTEN also enables I-Cache on F7)
|
||||||
|
stm32.FLASH.ACR.SetBits(stm32.FLASH_ACR_ARTEN | stm32.FLASH_ACR_PRFTEN)
|
||||||
|
|
||||||
// HCLK (0x1C00 = DIV_16, 0x0 = RCC_SYSCLK_DIV1) - ensure timers remain
|
// HCLK (0x1C00 = DIV_16, 0x0 = RCC_SYSCLK_DIV1) - ensure timers remain
|
||||||
// within spec as the SYSCLK source changes.
|
// within spec as the SYSCLK source changes.
|
||||||
stm32.RCC.CFGR.ReplaceBits(0x00001C00, stm32.RCC_CFGR_PPRE1_Msk, 0)
|
stm32.RCC.CFGR.ReplaceBits(0x00001C00, stm32.RCC_CFGR_PPRE1_Msk, 0)
|
||||||
@@ -87,6 +90,9 @@ func initCLK() {
|
|||||||
// Set APB1 and APB2 clocks (0x1800 = DIV8, 0x1000 = DIV2)
|
// Set APB1 and APB2 clocks (0x1800 = DIV8, 0x1000 = DIV2)
|
||||||
stm32.RCC.CFGR.ReplaceBits(0x1800, stm32.RCC_CFGR_PPRE1_Msk, 0)
|
stm32.RCC.CFGR.ReplaceBits(0x1800, stm32.RCC_CFGR_PPRE1_Msk, 0)
|
||||||
stm32.RCC.CFGR.ReplaceBits(0x1000<<3, stm32.RCC_CFGR_PPRE2_Msk, 0)
|
stm32.RCC.CFGR.ReplaceBits(0x1000<<3, stm32.RCC_CFGR_PPRE2_Msk, 0)
|
||||||
|
|
||||||
|
// Select Main PLL as the 48MHz clock source for USB/RNG/SDMMC (CK48MSEL = 0).
|
||||||
|
stm32.RCC.DCKCFGR2.ClearBits(stm32.RCC_DCKCFGR2_CK48MSEL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initOsc() {
|
func initOsc() {
|
||||||
@@ -100,13 +106,13 @@ func initOsc() {
|
|||||||
for stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) {
|
for stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the PLL
|
// Configure the PLL: HSE as source, use SVD constants for positions.
|
||||||
stm32.RCC.PLLCFGR.Set(0x20000000 |
|
stm32.RCC.PLLCFGR.Set(
|
||||||
(1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | // 1 = HSE
|
(stm32.RCC_PLLCFGR_PLLSRC_HSE << stm32.RCC_PLLCFGR_PLLSRC_Pos) |
|
||||||
PLL_M |
|
(PLL_M << stm32.RCC_PLLCFGR_PLLM_Pos) |
|
||||||
(PLL_N << stm32.RCC_PLLCFGR_PLLN_Pos) |
|
(PLL_N << stm32.RCC_PLLCFGR_PLLN_Pos) |
|
||||||
(((PLL_P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) |
|
(((PLL_P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) |
|
||||||
(PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos))
|
(PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos))
|
||||||
|
|
||||||
// Enable the PLL, wait until ready
|
// Enable the PLL, wait until ready
|
||||||
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON)
|
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON)
|
||||||
|
|||||||
Reference in New Issue
Block a user