samd21: Add support for USB
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
cf2393efc8
commit
0daf19c003
|
@ -31,7 +31,11 @@ config FLASH_START
|
|||
default 0x2000 if FLASH_START_2000
|
||||
default 0x0000
|
||||
|
||||
config USBSERIAL
|
||||
bool "Use USB for communication (instead of serial)"
|
||||
default y
|
||||
config SERIAL
|
||||
depends on !USBSERIAL
|
||||
bool
|
||||
default y
|
||||
config SERIAL_BAUD
|
||||
|
|
|
@ -18,6 +18,7 @@ src-y += samd21/main.c samd21/timer.c samd21/clock.c samd21/gpio.c
|
|||
src-y += generic/crc16_ccitt.c generic/alloc.c
|
||||
src-y += generic/armcm_irq.c generic/timer_irq.c
|
||||
src-y += ../lib/samd21/samd21a/gcc/gcc/startup_samd21.c
|
||||
src-$(CONFIG_USBSERIAL) += samd21/usbserial.c generic/usb_cdc.c
|
||||
src-$(CONFIG_SERIAL) += samd21/serial.c generic/serial_irq.c
|
||||
|
||||
# Support bootloader offset address
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
// Hardware interface to USB on samd21
|
||||
//
|
||||
// Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include <string.h> // memcpy
|
||||
#include "board/io.h" // readl
|
||||
#include "board/irq.h" // irq_save
|
||||
#include "board/usb_cdc.h" // usb_notify_setup
|
||||
#include "board/usb_cdc_ep.h" // USB_CDC_EP_BULK_IN
|
||||
#include "internal.h" // enable_pclock
|
||||
#include "samd21.h" // SERCOM0
|
||||
#include "sched.h" // DECL_INIT
|
||||
|
||||
#include "command.h" // output
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* USB transfer memory
|
||||
****************************************************************/
|
||||
|
||||
static uint8_t __aligned(4) ep0out[USB_CDC_EP0_SIZE];
|
||||
static uint8_t __aligned(4) ep0in[USB_CDC_EP0_SIZE];
|
||||
static uint8_t __aligned(4) acmin[USB_CDC_EP_ACM_SIZE];
|
||||
static uint8_t __aligned(4) bulkout[USB_CDC_EP_BULK_OUT_SIZE];
|
||||
static uint8_t __aligned(4) bulkin[USB_CDC_EP_BULK_IN_SIZE];
|
||||
|
||||
static UsbDeviceDescriptor usb_desc[USB_CDC_EP_BULK_IN + 1] = {
|
||||
[0] = { {
|
||||
{
|
||||
.ADDR.reg = (uint32_t)ep0out,
|
||||
.PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(ep0out) >> 4),
|
||||
}, {
|
||||
.ADDR.reg = (uint32_t)ep0in,
|
||||
.PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(ep0in) >> 4),
|
||||
},
|
||||
} },
|
||||
[USB_CDC_EP_ACM] = { {
|
||||
{
|
||||
}, {
|
||||
.ADDR.reg = (uint32_t)acmin,
|
||||
.PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(acmin) >> 4),
|
||||
},
|
||||
} },
|
||||
[USB_CDC_EP_BULK_OUT] = { {
|
||||
{
|
||||
.ADDR.reg = (uint32_t)bulkout,
|
||||
.PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(bulkout) >> 4),
|
||||
}, {
|
||||
},
|
||||
} },
|
||||
[USB_CDC_EP_BULK_IN] = { {
|
||||
{
|
||||
}, {
|
||||
.ADDR.reg = (uint32_t)bulkin,
|
||||
.PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(bulkin) >> 4),
|
||||
},
|
||||
} },
|
||||
};
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Interface
|
||||
****************************************************************/
|
||||
|
||||
#define EP0 USB->DEVICE.DeviceEndpoint[0]
|
||||
#define EP_ACM USB->DEVICE.DeviceEndpoint[USB_CDC_EP_ACM]
|
||||
#define EP_BULKOUT USB->DEVICE.DeviceEndpoint[USB_CDC_EP_BULK_OUT]
|
||||
#define EP_BULKIN USB->DEVICE.DeviceEndpoint[USB_CDC_EP_BULK_IN]
|
||||
|
||||
static int_fast8_t
|
||||
usb_write_packet(uint32_t ep, uint32_t bank, const void *data, uint_fast8_t len)
|
||||
{
|
||||
// Check if there is room for this packet
|
||||
UsbDeviceEndpoint *ude = &USB->DEVICE.DeviceEndpoint[ep];
|
||||
uint8_t sts = ude->EPSTATUS.reg;
|
||||
uint8_t bkrdy = (bank ? USB_DEVICE_EPSTATUS_BK1RDY
|
||||
: USB_DEVICE_EPSTATUS_BK0RDY);
|
||||
if (sts & bkrdy)
|
||||
return -1;
|
||||
// Copy the packet to the given buffer
|
||||
UsbDeviceDescBank *uddb = &usb_desc[ep].DeviceDescBank[bank];
|
||||
memcpy((void*)uddb->ADDR.reg, data, len);
|
||||
// Inform the USB hardware of the available packet
|
||||
uint32_t pcksize = uddb->PCKSIZE.reg;
|
||||
uint32_t c = pcksize & ~USB_DEVICE_PCKSIZE_BYTE_COUNT_Msk;
|
||||
uddb->PCKSIZE.reg = c | USB_DEVICE_PCKSIZE_BYTE_COUNT(len);
|
||||
ude->EPSTATUSSET.reg = bkrdy;
|
||||
return len;
|
||||
}
|
||||
|
||||
static int_fast8_t
|
||||
usb_read_packet(uint32_t ep, uint32_t bank, void *data, uint_fast8_t max_len)
|
||||
{
|
||||
// Check if there is a packet ready
|
||||
UsbDeviceEndpoint *ude = &USB->DEVICE.DeviceEndpoint[ep];
|
||||
uint8_t sts = ude->EPSTATUS.reg;
|
||||
uint8_t bkrdy = (bank ? USB_DEVICE_EPSTATUS_BK1RDY
|
||||
: USB_DEVICE_EPSTATUS_BK0RDY);
|
||||
if (!(sts & bkrdy))
|
||||
return -1;
|
||||
// Copy the packet to the given buffer
|
||||
UsbDeviceDescBank *uddb = &usb_desc[ep].DeviceDescBank[bank];
|
||||
uint32_t pcksize = uddb->PCKSIZE.reg;
|
||||
uint32_t c = pcksize & USB_DEVICE_PCKSIZE_BYTE_COUNT_Msk;
|
||||
if (c > max_len)
|
||||
c = max_len;
|
||||
memcpy(data, (void*)uddb->ADDR.reg, c);
|
||||
// Notify the USB hardware that the space is now available
|
||||
ude->EPSTATUSCLR.reg = bkrdy;
|
||||
return c;
|
||||
}
|
||||
|
||||
int_fast8_t
|
||||
usb_read_bulk_out(void *data, uint_fast8_t max_len)
|
||||
{
|
||||
return usb_read_packet(USB_CDC_EP_BULK_OUT, 0, data, max_len);
|
||||
}
|
||||
|
||||
int_fast8_t
|
||||
usb_send_bulk_in(void *data, uint_fast8_t len)
|
||||
{
|
||||
return usb_write_packet(USB_CDC_EP_BULK_IN, 1, data, len);
|
||||
}
|
||||
|
||||
int_fast8_t
|
||||
usb_read_setup(void *data, uint_fast8_t max_len)
|
||||
{
|
||||
return usb_read_packet(0, 0, data, max_len);
|
||||
}
|
||||
|
||||
int_fast8_t
|
||||
usb_send_setup(const void *data, uint_fast8_t len)
|
||||
{
|
||||
return usb_write_packet(0, 1, data, len);
|
||||
}
|
||||
|
||||
void
|
||||
usb_set_stall(void)
|
||||
{
|
||||
EP0.EPSTATUSSET.reg = USB_DEVICE_EPSTATUS_STALLRQ(3);
|
||||
}
|
||||
|
||||
static uint8_t set_address;
|
||||
|
||||
void
|
||||
usb_set_address(uint_fast8_t addr)
|
||||
{
|
||||
irqstatus_t flag = irq_save();
|
||||
set_address = addr | USB_DEVICE_DADD_ADDEN;
|
||||
irq_restore(flag);
|
||||
usb_send_setup(NULL, 0);
|
||||
}
|
||||
|
||||
void
|
||||
usb_set_configure(void)
|
||||
{
|
||||
EP_ACM.EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE1(4);
|
||||
|
||||
EP_BULKOUT.EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE0(3);
|
||||
EP_BULKOUT.EPINTENSET.reg = (
|
||||
USB_DEVICE_EPINTENSET_TRCPT0 | USB_DEVICE_EPINTENSET_TRCPT1);
|
||||
|
||||
EP_BULKIN.EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE1(3);
|
||||
EP_BULKIN.EPINTENSET.reg = (
|
||||
USB_DEVICE_EPINTENSET_TRCPT0 | USB_DEVICE_EPINTENSET_TRCPT1);
|
||||
}
|
||||
|
||||
void
|
||||
usbserial_init(void)
|
||||
{
|
||||
// configure usb clock
|
||||
enable_pclock(USB_GCLK_ID, 0);
|
||||
// configure USBD+ and USBD- pins
|
||||
gpio_peripheral('A', 24, 'G', 0);
|
||||
gpio_peripheral('A', 25, 'G', 0);
|
||||
uint16_t trim = (readl((void*)USB_FUSES_TRIM_ADDR)
|
||||
& USB_FUSES_TRIM_Msk) >> USB_FUSES_TRIM_Pos;
|
||||
uint16_t transp = (readl((void*)USB_FUSES_TRANSP_ADDR)
|
||||
& USB_FUSES_TRANSP_Msk) >> USB_FUSES_TRANSP_Pos;
|
||||
uint16_t transn = (readl((void*)USB_FUSES_TRANSN_ADDR)
|
||||
& USB_FUSES_TRANSN_Msk) >> USB_FUSES_TRANSN_Pos;
|
||||
USB->DEVICE.PADCAL.reg = (USB_PADCAL_TRIM(trim) | USB_PADCAL_TRANSP(transp)
|
||||
| USB_PADCAL_TRANSN(transn));
|
||||
// Enable USB in device mode
|
||||
USB->DEVICE.CTRLA.reg = USB_CTRLA_ENABLE;
|
||||
USB->DEVICE.DESCADD.reg = (uint32_t)usb_desc;
|
||||
EP0.EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE0(1) | USB_DEVICE_EPCFG_EPTYPE1(1);
|
||||
EP_ACM.EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE1(4);
|
||||
USB->DEVICE.CTRLB.reg = 0;
|
||||
// enable irqs
|
||||
USB->DEVICE.INTENSET.reg = USB_DEVICE_INTENSET_EORST;
|
||||
NVIC_SetPriority(USB_IRQn, 1);
|
||||
NVIC_EnableIRQ(USB_IRQn);
|
||||
}
|
||||
DECL_INIT(usbserial_init);
|
||||
|
||||
void __visible
|
||||
USB_Handler(void)
|
||||
{
|
||||
uint8_t s = USB->DEVICE.INTFLAG.reg;
|
||||
if (s & USB_DEVICE_INTFLAG_EORST) {
|
||||
USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_EORST;
|
||||
// Enable endpoint 0 irqs
|
||||
EP0.EPINTENSET.reg = (
|
||||
USB_DEVICE_EPINTENSET_TRCPT0 | USB_DEVICE_EPINTENSET_TRCPT1
|
||||
| USB_DEVICE_EPINTENSET_RXSTP);
|
||||
}
|
||||
|
||||
uint16_t ep = USB->DEVICE.EPINTSMRY.reg;
|
||||
if (ep & (1<<0)) {
|
||||
uint8_t sts = EP0.EPINTFLAG.reg;
|
||||
EP0.EPINTFLAG.reg = sts;
|
||||
if (set_address && sts & USB_DEVICE_EPINTFLAG_TRCPT1) {
|
||||
// Apply address after last "in" message transmitted
|
||||
USB->DEVICE.DADD.reg = set_address;
|
||||
set_address = 0;
|
||||
}
|
||||
usb_notify_setup();
|
||||
}
|
||||
if (ep & (1<<USB_CDC_EP_BULK_OUT)) {
|
||||
uint8_t sts = EP_BULKOUT.EPINTFLAG.reg;
|
||||
EP_BULKOUT.EPINTFLAG.reg = sts;
|
||||
usb_notify_bulk_out();
|
||||
}
|
||||
if (ep & (1<<USB_CDC_EP_BULK_IN)) {
|
||||
uint8_t sts = EP_BULKIN.EPINTFLAG.reg;
|
||||
EP_BULKIN.EPINTFLAG.reg = sts;
|
||||
usb_notify_bulk_in();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue