mirror of
https://github.com/dougg3/mac-rom-simm-programmer.git
synced 2024-12-01 19:50:18 +00:00
7d83075f10
This implements a USB CDC serial port using the Nuvoton USBD driver. The USB handling is based on Nuvoton's BSP sample code, especially the IRQ handlers and descriptor buffer configuration. The descriptors have been adapted to be similar to the AVR version, and RX/TX functions have been written to implement an API closer to LUFA, which is what the SIMM programmer common code needs.
469 lines
12 KiB
C
469 lines
12 KiB
C
/*
|
|
* usbcdc.c
|
|
*
|
|
* Created on: Jun 19, 2023
|
|
* Author: Doug
|
|
*
|
|
* Copyright (C) 2011-2023 Doug Brown
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* Portions of this code also came from Nuvoton's BSP, originally
|
|
* licensed as Apache-2.0:
|
|
* @copyright (C) 2019 Nuvoton Technology Corp. All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#include "usbcdc_hw.h"
|
|
#include <stdbool.h>
|
|
|
|
// Undocumented register for HIRC trim from Nuvoton's samples
|
|
#define TRIM_INIT (SYS_BASE + 0x118)
|
|
|
|
#define SET_LINE_CODE 0x20
|
|
#define GET_LINE_CODE 0x21
|
|
#define SET_CONTROL_LINE_STATE 0x22
|
|
|
|
/// Setup buffer uses first 8 bytes of USB SRAM
|
|
#define SETUP_BUF_BASE 0
|
|
#define SETUP_BUF_LEN 8
|
|
/// EP0 and EP1 share the same USB SRAM, 64 bytes of control
|
|
#define EP0_BUF_BASE (SETUP_BUF_BASE + SETUP_BUF_LEN)
|
|
#define EP0_BUF_LEN EP0_MAX_PKT_SIZE
|
|
#define EP1_BUF_BASE (SETUP_BUF_BASE + SETUP_BUF_LEN)
|
|
#define EP1_BUF_LEN EP1_MAX_PKT_SIZE
|
|
/// EP2 uses 8 bytes for interrupt IN (not really used though)
|
|
#define EP2_BUF_BASE (EP1_BUF_BASE + EP1_BUF_LEN)
|
|
#define EP2_BUF_LEN EP2_MAX_PKT_SIZE
|
|
/// EP3 uses 64 bytes for bulk IN
|
|
#define EP3_BUF_BASE (EP2_BUF_BASE + EP2_BUF_LEN)
|
|
#define EP3_BUF_LEN EP3_MAX_PKT_SIZE
|
|
/// EP4 uses 64 bytes for bulk OUT
|
|
#define EP4_BUF_BASE (EP3_BUF_BASE + EP3_BUF_LEN)
|
|
#define EP4_BUF_LEN EP4_MAX_PKT_SIZE
|
|
|
|
/// Struct to represent the current line coding
|
|
typedef struct
|
|
{
|
|
uint32_t baudRate; // Baud rate
|
|
uint8_t stopBits; // stop bit
|
|
uint8_t parity; // parity
|
|
uint8_t dataBits; // data bits
|
|
} CDCLineCoding;
|
|
|
|
static void USBCDC_SendDataInBuffer(void);
|
|
static void USBCDC_InitEndpoints(void);
|
|
static void USBCDC_ClassRequest(void);
|
|
|
|
/// Default HIRC trim value in case of errors
|
|
static uint32_t trimInit;
|
|
/// Toggle flag for ensuring the received endpoint 4 packet is the expected one
|
|
static volatile uint32_t ep4OutToggle = 0;
|
|
/// Line coding for CDC device, not used by this firmware
|
|
static CDCLineCoding cdcLineCoding = {0, 0, 0, 0};
|
|
/// Control signal for CDC device, not used by this firmware
|
|
static uint16_t cdcCtrlSignal;
|
|
|
|
/// Buffer to send to the CDC serial port
|
|
static uint8_t cdcTxBuf[EP3_MAX_PKT_SIZE];
|
|
/// Current position in the TX buffer
|
|
static uint32_t cdcTxBufPos = 0;
|
|
/// Flag that is true if a TX is currently active
|
|
static volatile bool cdcTxActive = false;
|
|
/// Buffer we read into
|
|
static uint8_t cdcRxBuf[EP4_MAX_PKT_SIZE];
|
|
/// Current length of RX buffer
|
|
static uint32_t cdcRxLen = 0;
|
|
/// Current position into RX buffer
|
|
static uint32_t cdcRxPos = 0;
|
|
/// Flag that is true if new RX data is ready to read
|
|
static volatile bool cdcRxReady = false;
|
|
|
|
/** Initializes the USB CDC serial port
|
|
*
|
|
*/
|
|
void USBCDC_Init(void)
|
|
{
|
|
// Set up USB
|
|
USBD_Open(&gsInfo, USBCDC_ClassRequest);
|
|
USBCDC_InitEndpoints();
|
|
USBD_Start();
|
|
NVIC_EnableIRQ(USBD_IRQn);
|
|
|
|
// Backup the default trim value, go back to it if there's an error
|
|
trimInit = M32(TRIM_INIT);
|
|
|
|
// Clear USB SOF flag; it will be used to detect if we have a USB
|
|
// signal available to use for HIRC trim
|
|
USBD_CLR_INT_FLAG(USBD_INTSTS_SOFIF_Msk);
|
|
}
|
|
|
|
/** Performs any necessary periodic tasks for the USB CDC serial port
|
|
*
|
|
*/
|
|
void USBCDC_Check(void)
|
|
{
|
|
// If we haven't enabled auto trim, and we have a USB signal available,
|
|
// then do it!
|
|
if (((SYS->HIRCTRIMCTL & SYS_HIRCTRIMCTL_FREQSEL_Msk) != 0x1) &&
|
|
(USBD->INTSTS & USBD_INTSTS_SOFIF_Msk))
|
|
{
|
|
// Clear SOF flag for next time
|
|
USBD_CLR_INT_FLAG(USBD_INTSTS_SOFIF_Msk);
|
|
|
|
// Start USB trim:
|
|
// - HIRC trim reference is USB clock
|
|
// - Enable auto trim, and trim to 48 MHz
|
|
// - Use 4 cycles for trimming
|
|
// - Boundary enabled (value = 10)
|
|
SYS->HIRCTRIMCTL = (0x1UL << SYS_HIRCTRIMCTL_REFCKSEL_Pos)
|
|
| (0x1UL << SYS_HIRCTRIMCTL_FREQSEL_Pos)
|
|
| (0x0UL << SYS_HIRCTRIMCTL_LOOPSEL_Pos)
|
|
| (0x1UL << SYS_HIRCTRIMCTL_BOUNDEN_Pos)
|
|
| (10UL << SYS_HIRCTRIMCTL_BOUNDARY_Pos);
|
|
}
|
|
|
|
// If we detect a clock error or trim failure, disable auto trim. We can try again later.
|
|
if (SYS->HIRCTRIMSTS & (SYS_HIRCTRIMSTS_CLKERIF_Msk | SYS_HIRCTRIMSTS_TFAILIF_Msk))
|
|
{
|
|
// Restore original trim value we read at startup
|
|
M32(TRIM_INIT) = trimInit;
|
|
|
|
// Disable USB trim
|
|
SYS->HIRCTRIMCTL = 0;
|
|
|
|
// Clear the error flags
|
|
SYS->HIRCTRIMSTS = SYS_HIRCTRIMSTS_CLKERIF_Msk | SYS_HIRCTRIMSTS_TFAILIF_Msk;
|
|
|
|
// Clear the SOF flag so the next time it's set we know we have a USB signal
|
|
USBD_CLR_INT_FLAG(USBD_INTSTS_SOFIF_Msk);
|
|
}
|
|
|
|
// Flush the USB CDC port every main loop just like LUFA does
|
|
USBCDC_Flush();
|
|
}
|
|
|
|
/** Sends a byte out the USB serial port
|
|
*
|
|
* @param b The byte
|
|
*/
|
|
void USBCDC_SendByte(uint8_t b)
|
|
{
|
|
// Fill up our buffer to send out the USB serial port
|
|
cdcTxBuf[cdcTxBufPos++] = b;
|
|
if (cdcTxBufPos == EP3_MAX_PKT_SIZE)
|
|
{
|
|
// If we reached a full packet size, send the data in the buffer
|
|
USBCDC_SendDataInBuffer();
|
|
}
|
|
}
|
|
|
|
/** Reads a byte from the USB serial port, if available
|
|
*
|
|
* @return The byte, or -1 if there is nothing available
|
|
*/
|
|
int16_t USBCDC_ReadByte(void)
|
|
{
|
|
// Assume not ready
|
|
int16_t ret = -1;
|
|
|
|
// If we have data ready to read out of the USB controller's endpoint buffer, grab it now
|
|
if (cdcRxLen == 0 && cdcRxReady)
|
|
{
|
|
// Flag that we handled the read event
|
|
cdcRxReady = false;
|
|
|
|
// Reset our read pointer just in case
|
|
cdcRxPos = 0;
|
|
|
|
// Read all of the data out from the USB controller
|
|
cdcRxLen = USBD_GET_PAYLOAD_LEN(EP4);
|
|
USBD_MemCopy(cdcRxBuf, (uint8_t *)(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP4)), cdcRxLen);
|
|
|
|
// We grabbed all of the packet data, so tell the USB controller we're done with it
|
|
USBD_SET_PAYLOAD_LEN(EP4, EP4_MAX_PKT_SIZE);
|
|
}
|
|
|
|
// If we have something left in our buffer since the last read, use it
|
|
if (cdcRxLen > 0)
|
|
{
|
|
ret = cdcRxBuf[cdcRxPos++];
|
|
|
|
// If we finished reading from the buffer, mark it as finished.
|
|
if (cdcRxPos == cdcRxLen)
|
|
{
|
|
cdcRxPos = 0;
|
|
cdcRxLen = 0;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** Sends out any remaining data in the TX buffer
|
|
*
|
|
*/
|
|
void USBCDC_Flush(void)
|
|
{
|
|
// If we have something waiting to send, send it out now
|
|
if (cdcTxBufPos > 0)
|
|
{
|
|
bool needsZLP = cdcTxBufPos == EP3_MAX_PKT_SIZE;
|
|
USBCDC_SendDataInBuffer();
|
|
|
|
// If we are flushing after sending a full packet of data,
|
|
// send a ZLP afterward to indicate end of transmit to host
|
|
if (needsZLP)
|
|
{
|
|
USBCDC_SendDataInBuffer();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Sends the data in the EP3 TX buffer to the host
|
|
*
|
|
*/
|
|
static void USBCDC_SendDataInBuffer(void)
|
|
{
|
|
// Wait for any previous transmit to finish first
|
|
while (cdcTxActive);
|
|
|
|
// Send out the packet
|
|
USBD_MemCopy((uint8_t *)(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP3)), cdcTxBuf, cdcTxBufPos);
|
|
cdcTxActive = true;
|
|
USBD_SET_PAYLOAD_LEN(EP3, cdcTxBufPos);
|
|
|
|
// Reset our buffer position; we've given all the data to the USB controller
|
|
cdcTxBufPos = 0;
|
|
}
|
|
|
|
/** IRQ handler called when USB endpoint 3 is ready (CDC TX data finished transferring)
|
|
*
|
|
*/
|
|
void EP3_Handler(void)
|
|
{
|
|
// All we have to do is flag that we no longer have an active transmission.
|
|
// USBCDC_SendDataInBuffer() will handle the rest.
|
|
cdcTxActive = false;
|
|
}
|
|
|
|
/** IRQ handler called when USB endpoint 4 is ready (CDC RX data ready to read)
|
|
*
|
|
*/
|
|
void EP4_Handler(void)
|
|
{
|
|
// Verify the toggle has changed. If it's still the same, re-request the data.
|
|
if (ep4OutToggle == (USBD->EPSTS0 & USBD_EPSTS0_EPSTS4_Msk))
|
|
{
|
|
USBD_SET_PAYLOAD_LEN(EP4, EP4_MAX_PKT_SIZE);
|
|
}
|
|
// Toggle changed, so we're good to go. Toggle for next time and flag that we
|
|
// have data ready to read. USBCDC_ReadByte() will handle the rest.
|
|
else
|
|
{
|
|
ep4OutToggle = USBD->EPSTS0 & USBD_EPSTS0_EPSTS4_Msk;
|
|
cdcRxReady = true;
|
|
}
|
|
}
|
|
|
|
/** Main IRQ handler for USB device controller
|
|
*
|
|
*/
|
|
void USBD_IRQHandler(void)
|
|
{
|
|
uint32_t intStatus = USBD_GET_INT_FLAG();
|
|
uint32_t busState = USBD_GET_BUS_STATE();
|
|
|
|
// Bus interrupt
|
|
if (intStatus & USBD_INTSTS_BUSIF_Msk)
|
|
{
|
|
// Clear the flag
|
|
USBD_CLR_INT_FLAG(USBD_INTSTS_BUSIF_Msk);
|
|
|
|
// The bus was reset - SE0 status for long enough
|
|
if (busState & USBD_STATE_USBRST)
|
|
{
|
|
// Enable USB, reset everything
|
|
USBD_ENABLE_USB();
|
|
USBD_SwReset();
|
|
ep4OutToggle = 0;
|
|
}
|
|
|
|
// Entered suspend status (bus idle)
|
|
if (busState & USBD_STATE_SUSPEND)
|
|
{
|
|
// Disable PHY while we're suspended
|
|
USBD_DISABLE_PHY();
|
|
}
|
|
|
|
if (busState & USBD_STATE_RESUME)
|
|
{
|
|
// Re-enable USB (mainly just the PHY is what we want)
|
|
USBD_ENABLE_USB();
|
|
}
|
|
}
|
|
|
|
// USB event
|
|
if (intStatus & USBD_INTSTS_USBIF_Msk)
|
|
{
|
|
// Setup packet?
|
|
if (intStatus & USBD_INTSTS_SETUP_Msk)
|
|
{
|
|
// Clear the flag...
|
|
USBD_CLR_INT_FLAG(USBD_INTSTS_SETUP);
|
|
|
|
// Stop EP0 and EP1 which are the control endpoints
|
|
USBD_STOP_TRANSACTION(EP0);
|
|
USBD_STOP_TRANSACTION(EP1);
|
|
|
|
// Now handle the received setup packet
|
|
USBD_ProcessSetupPacket();
|
|
}
|
|
|
|
// EP0 - control in
|
|
if (intStatus & USBD_INTSTS_EPEVT0_Msk)
|
|
{
|
|
USBD_CLR_INT_FLAG(USBD_INTSTS_EPEVT0_Msk);
|
|
USBD_CtrlIn();
|
|
}
|
|
|
|
// EP1 - control out
|
|
if (intStatus & USBD_INTSTS_EPEVT1_Msk)
|
|
{
|
|
USBD_CLR_INT_FLAG(USBD_INTSTS_EPEVT1_Msk);
|
|
USBD_CtrlOut();
|
|
}
|
|
|
|
// Do nothing for EP2 for now
|
|
|
|
// EP3 - bulk in for CDC data
|
|
if (intStatus & USBD_INTSTS_EPEVT3_Msk)
|
|
{
|
|
USBD_CLR_INT_FLAG(USBD_INTSTS_EPEVT3_Msk);
|
|
EP3_Handler();
|
|
}
|
|
|
|
// EP4 - bulk out for CDC data
|
|
if (intStatus & USBD_INTSTS_EPEVT4_Msk)
|
|
{
|
|
USBD_CLR_INT_FLAG(USBD_INTSTS_EPEVT4_Msk);
|
|
EP4_Handler();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Initializes endpoint buffers in USB SRAM
|
|
*
|
|
*/
|
|
void USBCDC_InitEndpoints(void)
|
|
{
|
|
// First 8 bytes are for setup packets
|
|
USBD->STBUFSEG = SETUP_BUF_BASE;
|
|
|
|
// EP0 = control IN, address 0, 64 bytes
|
|
USBD_CONFIG_EP(EP0, USBD_CFG_CSTALL | USBD_CFG_EPMODE_IN | 0);
|
|
USBD_SET_EP_BUF_ADDR(EP0, EP0_BUF_BASE);
|
|
|
|
// EP1 = control OUT, address 0, uses same 64 bytes as control IN
|
|
USBD_CONFIG_EP(EP1, USBD_CFG_CSTALL | USBD_CFG_EPMODE_OUT | 0);
|
|
USBD_SET_EP_BUF_ADDR(EP1, EP1_BUF_BASE);
|
|
|
|
// EP2 = interrupt IN, address 2, 8 bytes
|
|
USBD_CONFIG_EP(EP2, USBD_CFG_EPMODE_IN | INT_IN_EP_NUM);
|
|
USBD_SET_EP_BUF_ADDR(EP2, EP2_BUF_BASE);
|
|
|
|
// EP3 = bulk IN, address 3, 64 bytes
|
|
USBD_CONFIG_EP(EP3, USBD_CFG_EPMODE_IN | BULK_IN_EP_NUM);
|
|
USBD_SET_EP_BUF_ADDR(EP3, EP3_BUF_BASE);
|
|
|
|
// EP4 = bulk OUT, address 4, 64 bytes
|
|
USBD_CONFIG_EP(EP4, USBD_CFG_EPMODE_OUT | BULK_OUT_EP_NUM);
|
|
USBD_SET_EP_BUF_ADDR(EP4, EP4_BUF_BASE);
|
|
|
|
// Mark that we are able to receive on EP4
|
|
USBD_SET_PAYLOAD_LEN(EP4, EP4_MAX_PKT_SIZE);
|
|
}
|
|
|
|
/** USB class request callback for CDC device
|
|
*
|
|
*/
|
|
void USBCDC_ClassRequest(void)
|
|
{
|
|
uint8_t buf[8];
|
|
|
|
// Grab the setup packet...
|
|
USBD_GetSetupPacket(buf);
|
|
|
|
// Device to host?
|
|
if (buf[0] & 0x80)
|
|
{
|
|
switch (buf[1])
|
|
{
|
|
case GET_LINE_CODE:
|
|
if (buf[4] == 0)
|
|
{
|
|
USBD_MemCopy((uint8_t *)(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)), (uint8_t *)&cdcLineCoding, 7);
|
|
}
|
|
|
|
// Data stage
|
|
USBD_SET_DATA1(EP0);
|
|
USBD_SET_PAYLOAD_LEN(EP0, 7);
|
|
|
|
// Status stage
|
|
USBD_PrepareCtrlOut(0, 0);
|
|
break;
|
|
|
|
default:
|
|
// Stall
|
|
USBD_SET_EP_STALL(EP0);
|
|
USBD_SET_EP_STALL(EP1);
|
|
break;
|
|
}
|
|
}
|
|
// Host to device
|
|
else
|
|
{
|
|
switch (buf[1])
|
|
{
|
|
case SET_CONTROL_LINE_STATE:
|
|
if (buf[4] == 0)
|
|
{
|
|
cdcCtrlSignal = (buf[3] << 8) | buf[2];
|
|
}
|
|
|
|
// Status stage
|
|
USBD_SET_DATA1(EP0);
|
|
USBD_SET_PAYLOAD_LEN(EP0, 0);
|
|
break;
|
|
|
|
case SET_LINE_CODE:
|
|
if (buf[4] == 0)
|
|
{
|
|
USBD_PrepareCtrlOut((uint8_t *)&cdcLineCoding, 7);
|
|
}
|
|
|
|
// Status stage
|
|
USBD_SET_DATA1(EP0);
|
|
USBD_SET_PAYLOAD_LEN(EP0, 0);
|
|
break;
|
|
|
|
default:
|
|
// Stall
|
|
USBD_SET_EP_STALL(EP0);
|
|
USBD_SET_EP_STALL(EP1);
|
|
break;
|
|
}
|
|
}
|
|
}
|