diff --git a/src/samd21/Kconfig b/src/samd21/Kconfig index d8108f71..d2ddc503 100644 --- a/src/samd21/Kconfig +++ b/src/samd21/Kconfig @@ -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 diff --git a/src/samd21/Makefile b/src/samd21/Makefile index 49311b0d..c71a22df 100644 --- a/src/samd21/Makefile +++ b/src/samd21/Makefile @@ -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 diff --git a/src/samd21/usbserial.c b/src/samd21/usbserial.c new file mode 100644 index 00000000..2b2e0798 --- /dev/null +++ b/src/samd21/usbserial.c @@ -0,0 +1,232 @@ +// Hardware interface to USB on samd21 +// +// Copyright (C) 2018 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // 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<