aboutsummaryrefslogtreecommitdiffstats
path: root/src/atsamd/usbserial.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/atsamd/usbserial.c')
-rw-r--r--src/atsamd/usbserial.c246
1 files changed, 246 insertions, 0 deletions
diff --git a/src/atsamd/usbserial.c b/src/atsamd/usbserial.c
new file mode 100644
index 00000000..0864a4c5
--- /dev/null
+++ b/src/atsamd/usbserial.c
@@ -0,0 +1,246 @@
+// 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 "autoconf.h" // CONFIG_FLASH_START
+#include "board/io.h" // readl
+#include "board/irq.h" // irq_disable
+#include "board/usb_cdc.h" // usb_notify_ep0
+#include "board/usb_cdc_ep.h" // USB_CDC_EP_BULK_IN
+#include "internal.h" // enable_pclock
+#include "samd21.h" // USB
+#include "sched.h" // DECL_INIT
+
+
+/****************************************************************
+ * 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_ep0(void *data, uint_fast8_t max_len)
+{
+ return usb_read_packet(0, 0, data, max_len);
+}
+
+int_fast8_t
+usb_read_ep0_setup(void *data, uint_fast8_t max_len)
+{
+ return usb_read_ep0(data, max_len);
+}
+
+int_fast8_t
+usb_send_ep0(const void *data, uint_fast8_t len)
+{
+ return usb_write_packet(0, 1, data, len);
+}
+
+void
+usb_stall_ep0(void)
+{
+ EP0.EPSTATUSSET.reg = USB_DEVICE_EPSTATUS_STALLRQ(3);
+}
+
+static uint8_t set_address;
+
+void
+usb_set_address(uint_fast8_t addr)
+{
+ writeb(&set_address, addr | USB_DEVICE_DADD_ADDEN);
+ usb_send_ep0(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
+usb_request_bootloader(void)
+{
+ if (CONFIG_FLASH_START) {
+ // Arduino Zero bootloader hack
+ irq_disable();
+ writel((void*)0x20007FFC, 0x07738135);
+ NVIC_SystemReset();
+ }
+}
+
+void
+usbserial_init(void)
+{
+ // configure usb clock
+ enable_pclock(USB_GCLK_ID, 0);
+ // configure USBD+ and USBD- pins
+ gpio_peripheral(GPIO('A', 24), 'G', 0);
+ gpio_peripheral(GPIO('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_ep0();
+ }
+ 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();
+ }
+}