mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +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
@@ -22,6 +22,7 @@ func main() {
|
||||
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
|
||||
kb := keyboard.Port()
|
||||
machine.USBDev.Configure(machine.UARTConfig{}) // no-op if already init'd by serial.usb
|
||||
|
||||
for {
|
||||
if !button.Get() {
|
||||
|
||||
@@ -6,8 +6,85 @@ import (
|
||||
"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() {
|
||||
msc.Port(machine.Flash)
|
||||
msc.Port(&ramDisk{})
|
||||
machine.USBDev.Configure(machine.UARTConfig{})
|
||||
|
||||
for {
|
||||
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
|
||||
|
||||
|
||||
@@ -86,6 +86,12 @@ func (p Pin) PortMaskClear() (*uint32, uint32) {
|
||||
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
|
||||
|
||||
// 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
|
||||
|
||||
@@ -184,7 +184,9 @@ func sendDescriptor(setup usb.Setup) {
|
||||
return
|
||||
}
|
||||
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:
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
usbDescriptor = desc
|
||||
|
||||
|
||||
@@ -165,6 +165,7 @@ func (m *msc) sendCSW(status csw.Status) {
|
||||
residue = expected - m.sentBytes
|
||||
}
|
||||
m.cbw.CSW(status, residue, m.cswBuf)
|
||||
m.queuedBytes = csw.MsgLen
|
||||
m.state = mscStateStatusSent
|
||||
m.queuedBytes = csw.MsgLen
|
||||
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)
|
||||
// (c) a Clear Feature HALT to the Bulk-Out endpoint (clear stall OUT)
|
||||
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
|
||||
|
||||
if m.state == mscStateNeedReset {
|
||||
wIndex := uint8(setup.WIndex & 0x7F)
|
||||
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_N = 216
|
||||
PLL_P = 2
|
||||
PLL_Q = 2
|
||||
PLL_Q = 9
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -55,7 +55,7 @@ func initCLK() {
|
||||
stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN)
|
||||
_ = 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.Get()
|
||||
|
||||
@@ -67,6 +67,9 @@ func initCLK() {
|
||||
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
|
||||
// within spec as the SYSCLK source changes.
|
||||
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)
|
||||
stm32.RCC.CFGR.ReplaceBits(0x1800, stm32.RCC_CFGR_PPRE1_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() {
|
||||
@@ -100,13 +106,13 @@ func initOsc() {
|
||||
for stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) {
|
||||
}
|
||||
|
||||
// Configure the PLL
|
||||
stm32.RCC.PLLCFGR.Set(0x20000000 |
|
||||
(1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | // 1 = HSE
|
||||
PLL_M |
|
||||
(PLL_N << stm32.RCC_PLLCFGR_PLLN_Pos) |
|
||||
(((PLL_P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) |
|
||||
(PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos))
|
||||
// Configure the PLL: HSE as source, use SVD constants for positions.
|
||||
stm32.RCC.PLLCFGR.Set(
|
||||
(stm32.RCC_PLLCFGR_PLLSRC_HSE << stm32.RCC_PLLCFGR_PLLSRC_Pos) |
|
||||
(PLL_M << stm32.RCC_PLLCFGR_PLLM_Pos) |
|
||||
(PLL_N << stm32.RCC_PLLCFGR_PLLN_Pos) |
|
||||
(((PLL_P >> 1) - 1) << stm32.RCC_PLLCFGR_PLLP_Pos) |
|
||||
(PLL_Q << stm32.RCC_PLLCFGR_PLLQ_Pos))
|
||||
|
||||
// Enable the PLL, wait until ready
|
||||
stm32.RCC.CR.SetBits(stm32.RCC_CR_PLLON)
|
||||
|
||||
Reference in New Issue
Block a user