diff --git a/src/generic/usb_cdc.c b/src/generic/usb_cdc.c new file mode 100644 index 00000000..fea567ec --- /dev/null +++ b/src/generic/usb_cdc.c @@ -0,0 +1,453 @@ +// Support for standard serial port over USB emulation +// +// Copyright (C) 2018 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memmove +#include "board/pgm.h" // PROGMEM +#include "board/usb_cdc.h" // usb_notify_setup +#include "byteorder.h" // cpu_to_le16 +#include "command.h" // output +#include "generic/usbstd.h" // struct usb_device_descriptor +#include "generic/usbstd_cdc.h" // struct usb_cdc_header_descriptor +#include "sched.h" // sched_wake_task + +// XXX - move to Kconfig +#define CONFIG_USB_VENDOR_ID 0x2341 +#define CONFIG_USB_PRODUCT_ID 0xabcd + + +/**************************************************************** + * Message block sending + ****************************************************************/ + +static struct task_wake usb_bulk_in_wake; +static char transmit_buf[96]; +static uint8_t transmit_pos; + +void +usb_notify_bulk_in(void) +{ + sched_wake_task(&usb_bulk_in_wake); +} + +void +usb_bulk_in_task(void) +{ + if (!sched_check_wake(&usb_bulk_in_wake)) + return; + uint_fast8_t tpos = transmit_pos; + if (!tpos) + return; + uint_fast8_t max_tpos = (tpos > USB_CDC_EP_BULK_IN_SIZE + ? USB_CDC_EP_BULK_IN_SIZE : tpos); + int_fast8_t ret = usb_send_bulk_in(transmit_buf, max_tpos); + if (ret <= 0) + return; + uint_fast8_t needcopy = tpos - ret; + if (needcopy) { + memmove(transmit_buf, &transmit_buf[ret], needcopy); + usb_notify_bulk_in(); + } + transmit_pos = needcopy; +} +DECL_TASK(usb_bulk_in_task); + +// Encode and transmit a "response" message +void +console_sendf(const struct command_encoder *ce, va_list args) +{ + // Verify space for message + uint_fast8_t tpos = transmit_pos, max_size = READP(ce->max_size); + if (tpos + max_size > sizeof(transmit_buf)) + // Not enough space for message + return; + + // Generate message + char *buf = &transmit_buf[tpos]; + uint8_t msglen = command_encodef(buf, ce, args); + command_add_frame(buf, msglen); + + // Start message transmit + transmit_pos = tpos + msglen; + usb_notify_bulk_in(); +} + + +/**************************************************************** + * Message block reading + ****************************************************************/ + +static struct task_wake usb_bulk_out_wake; +static char receive_buf[128]; +static uint8_t receive_pos; + +void +usb_notify_bulk_out(void) +{ + sched_wake_task(&usb_bulk_out_wake); +} + +void +usb_bulk_out_task(void) +{ + if (!sched_check_wake(&usb_bulk_out_wake)) + return; + // Process any existing message blocks + uint_fast8_t rpos = receive_pos; + uint8_t pop_count; + int_fast8_t ret = command_find_block(receive_buf, rpos, &pop_count); + if (ret > 0) + command_dispatch(receive_buf, pop_count); + if (ret) { + // Move buffer + uint_fast8_t needcopy = rpos - pop_count; + if (needcopy) { + memmove(receive_buf, &receive_buf[pop_count], needcopy); + usb_notify_bulk_out(); + } + rpos = needcopy; + } + // Read more data + if (rpos + USB_CDC_EP_BULK_OUT_SIZE <= sizeof(receive_buf)) { + ret = usb_read_bulk_out(&receive_buf[rpos], USB_CDC_EP_BULK_OUT_SIZE); + if (ret > 0) { + rpos += ret; + usb_notify_bulk_out(); + } + } + receive_pos = rpos; +} +DECL_TASK(usb_bulk_out_task); + + +/**************************************************************** + * USB descriptors + ****************************************************************/ + +// XXX - move to Kconfig +#define USB_STR_MANUFACTURER u"Klipper" +#define USB_STR_PRODUCT u"Klipper firmware" +#define USB_STR_SERIAL u"12345" + +// String descriptors +enum { + USB_STR_ID_MANUFACTURER = 1, USB_STR_ID_PRODUCT, USB_STR_ID_SERIAL, +}; + +#define SIZE_cdc_string_langids (sizeof(cdc_string_langids) + 2) + +static const struct usb_string_descriptor cdc_string_langids PROGMEM = { + .bLength = SIZE_cdc_string_langids, + .bDescriptorType = USB_DT_STRING, + .data = { cpu_to_le16(USB_LANGID_ENGLISH_US) }, +}; + +#define SIZE_cdc_string_manufacturer \ + (sizeof(cdc_string_manufacturer) + sizeof(USB_STR_MANUFACTURER) - 2) + +static const struct usb_string_descriptor cdc_string_manufacturer PROGMEM = { + .bLength = SIZE_cdc_string_manufacturer, + .bDescriptorType = USB_DT_STRING, + .data = USB_STR_MANUFACTURER, +}; + +#define SIZE_cdc_string_product \ + (sizeof(cdc_string_product) + sizeof(USB_STR_PRODUCT) - 2) + +static const struct usb_string_descriptor cdc_string_product PROGMEM = { + .bLength = SIZE_cdc_string_product, + .bDescriptorType = USB_DT_STRING, + .data = USB_STR_PRODUCT, +}; + +#define SIZE_cdc_string_serial \ + (sizeof(cdc_string_serial) + sizeof(USB_STR_SERIAL) - 2) + +static const struct usb_string_descriptor cdc_string_serial PROGMEM = { + .bLength = SIZE_cdc_string_serial, + .bDescriptorType = USB_DT_STRING, + .data = USB_STR_SERIAL, +}; + +// Device descriptor +static const struct usb_device_descriptor cdc_device_descriptor PROGMEM = { + .bLength = sizeof(cdc_device_descriptor), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_COMM, + .bMaxPacketSize0 = USB_CDC_EP0_SIZE, + .idVendor = cpu_to_le16(CONFIG_USB_VENDOR_ID), + .idProduct = cpu_to_le16(CONFIG_USB_PRODUCT_ID), + .bcdDevice = cpu_to_le16(0x0100), + .iManufacturer = USB_STR_ID_MANUFACTURER, + .iProduct = USB_STR_ID_PRODUCT, + .iSerialNumber = USB_STR_ID_SERIAL, + .bNumConfigurations = 1, +}; + +// Config descriptor +static const struct config_s { + struct usb_config_descriptor config; + struct usb_interface_descriptor iface0; + struct usb_cdc_header_descriptor cdc_hdr; + struct usb_cdc_acm_descriptor cdc_acm; + struct usb_cdc_union_descriptor cdc_union; + struct usb_endpoint_descriptor ep1; + struct usb_interface_descriptor iface1; + struct usb_endpoint_descriptor ep2; + struct usb_endpoint_descriptor ep3; +} PACKED cdc_config_descriptor PROGMEM = { + .config = { + .bLength = sizeof(cdc_config_descriptor.config), + .bDescriptorType = USB_DT_CONFIG, + .wTotalLength = cpu_to_le16(sizeof(cdc_config_descriptor)), + .bNumInterfaces = 2, + .bConfigurationValue = 1, + .bmAttributes = 0xC0, + .bMaxPower = 50, + }, + .iface0 = { + .bLength = sizeof(cdc_config_descriptor.iface0), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + }, + .cdc_hdr = { + .bLength = sizeof(cdc_config_descriptor.cdc_hdr), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = 0x0110, + }, + .cdc_acm = { + .bLength = sizeof(cdc_config_descriptor.cdc_acm), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = 0x06, + }, + .cdc_union = { + .bLength = sizeof(cdc_config_descriptor.cdc_union), + .bDescriptorType = USB_CDC_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + .bMasterInterface0 = 0, + .bSlaveInterface0 = 1, + }, + .ep1 = { + .bLength = sizeof(cdc_config_descriptor.ep1), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_CDC_EP_ACM | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(USB_CDC_EP_ACM_SIZE), + .bInterval = 255, + }, + .iface1 = { + .bLength = sizeof(cdc_config_descriptor.iface1), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 1, + .bNumEndpoints = 2, + .bInterfaceClass = 0x0A, + }, + .ep2 = { + .bLength = sizeof(cdc_config_descriptor.ep2), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_CDC_EP_BULK_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(USB_CDC_EP_BULK_OUT_SIZE), + }, + .ep3 = { + .bLength = sizeof(cdc_config_descriptor.ep3), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_CDC_EP_BULK_IN | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(USB_CDC_EP_BULK_IN_SIZE), + }, +}; + +// List of available descriptors +static const struct descriptor_s { + uint_fast16_t wValue; + uint_fast16_t wIndex; + const void *desc; + uint_fast8_t size; +} cdc_descriptors[] PROGMEM = { + { USB_DT_DEVICE<<8, 0x0000, + &cdc_device_descriptor, sizeof(cdc_device_descriptor) }, + { USB_DT_CONFIG<<8, 0x0000, + &cdc_config_descriptor, sizeof(cdc_config_descriptor) }, + { USB_DT_STRING<<8, 0x0000, + &cdc_string_langids, SIZE_cdc_string_langids }, + { (USB_DT_STRING<<8) | USB_STR_ID_MANUFACTURER, USB_LANGID_ENGLISH_US, + &cdc_string_manufacturer, SIZE_cdc_string_manufacturer }, + { (USB_DT_STRING<<8) | USB_STR_ID_PRODUCT, USB_LANGID_ENGLISH_US, + &cdc_string_product, SIZE_cdc_string_product }, + { (USB_DT_STRING<<8) | USB_STR_ID_SERIAL, USB_LANGID_ENGLISH_US, + &cdc_string_serial, SIZE_cdc_string_serial }, +}; + + +/**************************************************************** + * USB endpoint 0 control message handling + ****************************************************************/ + +// State tracking +enum { + US_READY, US_SEND, US_READ +}; + +static uint_fast8_t usb_state; +static void *usb_xfer; +static uint_fast8_t usb_xfer_size; + +static void +usb_do_stall(void) +{ + usb_set_stall(); + usb_state = US_READY; +} + +// Sending data from device to host +static void +usb_state_xfer(void) +{ + for (;;) { + uint_fast8_t xs = usb_xfer_size; + if (xs > USB_CDC_EP0_SIZE) + xs = USB_CDC_EP0_SIZE; + int_fast8_t ret; + if (usb_state == US_SEND) + ret = usb_send_setup(usb_xfer, xs); + else + ret = usb_read_setup(usb_xfer, xs); + if (ret == xs) { + // Success + usb_xfer += xs; + usb_xfer_size -= xs; + if (!usb_xfer_size && xs < USB_CDC_EP0_SIZE) { + // Transfer completed successfully + if (usb_state == US_READ) + usb_send_setup(NULL, 0); + usb_state = US_READY; + return; + } + continue; + } + if (ret == -1) + // Interface busy - retry later + return; + // Error + usb_do_stall(); + return; + } +} + +static void +usb_req_get_descriptor(struct usb_ctrlrequest *req) +{ + // XXX - validate req + uint_fast8_t i; + for (i=0; iwValue) == req->wValue + && READP(d->wIndex) == req->wIndex) { + usb_state = US_SEND; + usb_xfer = (void*)READP(d->desc); + usb_xfer_size = READP(d->size); + if (usb_xfer_size > req->wLength) + usb_xfer_size = req->wLength; + usb_state_xfer(); + return; + } + } + usb_do_stall(); +} + +static void +usb_req_set_address(struct usb_ctrlrequest *req) +{ + usb_set_address(req->wValue); +} + +static void +usb_req_set_configuration(struct usb_ctrlrequest *req) +{ + usb_set_configure(); + usb_send_setup(NULL, 0); + usb_notify_bulk_in(); +} + +static struct usb_cdc_line_coding line_coding; + +static void +usb_req_set_line_coding(struct usb_ctrlrequest *req) +{ + usb_state = US_READ; + usb_xfer = &line_coding; + usb_xfer_size = sizeof(line_coding); +} + +static void +usb_req_get_line_coding(struct usb_ctrlrequest *req) +{ + usb_state = US_SEND; + usb_xfer = &line_coding; + usb_xfer_size = sizeof(line_coding); +} + +static void +usb_req_line_state(struct usb_ctrlrequest *req) +{ + usb_send_setup(NULL, 0); +} + +static void +usb_state_ready(void) +{ + struct usb_ctrlrequest req; + int_fast8_t ret = usb_read_setup(&req, sizeof(req)); + if (ret != sizeof(req)) + // XXX - should verify that packet was sent with a setup token + return; + switch (req.bRequest) { + case USB_REQ_GET_DESCRIPTOR: usb_req_get_descriptor(&req); break; + case USB_REQ_SET_ADDRESS: usb_req_set_address(&req); break; + case USB_REQ_SET_CONFIGURATION: usb_req_set_configuration(&req); break; + case USB_CDC_REQ_SET_LINE_CODING: usb_req_set_line_coding(&req); break; + case USB_CDC_REQ_GET_LINE_CODING: usb_req_get_line_coding(&req); break; + case USB_CDC_REQ_SET_CONTROL_LINE_STATE: usb_req_line_state(&req); break; + default: usb_do_stall(); break; + } +} + +// State tracking dispatch +static struct task_wake usb_setup_wake; + +void +usb_notify_setup(void) +{ + sched_wake_task(&usb_setup_wake); +} + +void +usb_setup_task(void) +{ + if (!sched_check_wake(&usb_setup_wake)) + return; + switch (usb_state) { + case US_READY: usb_state_ready(); break; + case US_SEND: usb_state_xfer(); break; + case US_READ: usb_state_xfer(); break; + } +} +DECL_TASK(usb_setup_task); + +void +usb_shutdown(void) +{ + usb_notify_bulk_in(); + usb_notify_setup(); +} +DECL_SHUTDOWN(usb_shutdown); diff --git a/src/generic/usb_cdc.h b/src/generic/usb_cdc.h new file mode 100644 index 00000000..c5450bfa --- /dev/null +++ b/src/generic/usb_cdc.h @@ -0,0 +1,35 @@ +#ifndef __GENERIC_USB_CDC_H +#define __GENERIC_USB_CDC_H + +#include // uint_fast8_t + +enum { + USB_CDC_EP0_SIZE = 16, + + // XXX - endpoint ids may need to changed per-board + USB_CDC_EP_ACM = 1, + USB_CDC_EP_ACM_SIZE = 8, + + USB_CDC_EP_BULK_OUT = 2, + USB_CDC_EP_BULK_OUT_SIZE = 64, + + USB_CDC_EP_BULK_IN = 5, + USB_CDC_EP_BULK_IN_SIZE = 64, +}; + +// callbacks provided by board specific code +int_fast8_t usb_read_bulk_out(void *data, uint_fast8_t max_len); +int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len); +int_fast8_t usb_read_setup(void *data, uint_fast8_t max_len); +int_fast8_t usb_send_setup(const void *data, uint_fast8_t len); +void usb_send_pgm_setup(void *data, uint_fast8_t len); +void usb_set_stall(void); +void usb_set_address(uint_fast8_t addr); +void usb_set_configure(void); + +// usb_cdc.c +void usb_notify_bulk_in(void); +void usb_notify_bulk_out(void); +void usb_notify_setup(void); + +#endif // usb_cdc.h diff --git a/src/generic/usbstd.h b/src/generic/usbstd.h new file mode 100644 index 00000000..86199992 --- /dev/null +++ b/src/generic/usbstd.h @@ -0,0 +1,121 @@ +// Standard definitions for USB commands and data structures +#ifndef __GENERIC_USBSTD_H +#define __GENERIC_USBSTD_H + +#include // uint8_t +#include "compiler.h" // PACKED + +#define USB_DIR_OUT 0 /* to device */ +#define USB_DIR_IN 0x80 /* to host */ + +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + +struct usb_ctrlrequest { + uint8_t bRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} PACKED; + +#define USB_DT_DEVICE 0x01 +#define USB_DT_CONFIG 0x02 +#define USB_DT_STRING 0x03 +#define USB_DT_INTERFACE 0x04 +#define USB_DT_ENDPOINT 0x05 +#define USB_DT_DEVICE_QUALIFIER 0x06 +#define USB_DT_OTHER_SPEED_CONFIG 0x07 +#define USB_DT_ENDPOINT_COMPANION 0x30 + +struct usb_device_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} PACKED; + +#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */ +#define USB_CLASS_AUDIO 1 +#define USB_CLASS_COMM 2 +#define USB_CLASS_HID 3 +#define USB_CLASS_PHYSICAL 5 +#define USB_CLASS_STILL_IMAGE 6 +#define USB_CLASS_PRINTER 7 +#define USB_CLASS_MASS_STORAGE 8 +#define USB_CLASS_HUB 9 + +struct usb_config_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; +} PACKED; + +struct usb_interface_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; +} PACKED; + +struct usb_endpoint_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; +} PACKED; + +#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress */ +#define USB_ENDPOINT_DIR_MASK 0x80 + +#define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* in bmAttributes */ +#define USB_ENDPOINT_XFER_CONTROL 0 +#define USB_ENDPOINT_XFER_ISOC 1 +#define USB_ENDPOINT_XFER_BULK 2 +#define USB_ENDPOINT_XFER_INT 3 +#define USB_ENDPOINT_MAX_ADJUSTABLE 0x80 + +struct usb_string_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t data[]; +} PACKED; + +#define USB_LANGID_ENGLISH_US 0x0409 + +#endif // usbstd.h diff --git a/src/generic/usbstd_cdc.h b/src/generic/usbstd_cdc.h new file mode 100644 index 00000000..e6873a31 --- /dev/null +++ b/src/generic/usbstd_cdc.h @@ -0,0 +1,49 @@ +// Standard definitions for USB CDC devices +#ifndef __GENERIC_USBSTD_CDC_H +#define __GENERIC_USBSTD_CDC_H + +#define USB_CDC_SUBCLASS_ACM 0x02 + +#define USB_CDC_ACM_PROTO_AT_V25TER 1 + +struct usb_cdc_header_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint16_t bcdCDC; +} PACKED; + +#define USB_CDC_HEADER_TYPE 0x00 +#define USB_CDC_ACM_TYPE 0x02 +#define USB_CDC_UNION_TYPE 0x06 + +#define USB_CDC_CS_INTERFACE 0x24 +#define USB_CDC_CS_ENDPOINT 0x25 + +struct usb_cdc_acm_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bmCapabilities; +} PACKED; + +struct usb_cdc_union_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bMasterInterface0; + uint8_t bSlaveInterface0; +} PACKED; + +#define USB_CDC_REQ_SET_LINE_CODING 0x20 +#define USB_CDC_REQ_GET_LINE_CODING 0x21 +#define USB_CDC_REQ_SET_CONTROL_LINE_STATE 0x22 + +struct usb_cdc_line_coding { + uint32_t dwDTERate; + uint8_t bCharFormat; + uint8_t bParityType; + uint8_t bDataBits; +} PACKED; + +#endif // usbstd_cdc.h diff --git a/src/lpc176x/Kconfig b/src/lpc176x/Kconfig index cdfad6cd..fa80c463 100644 --- a/src/lpc176x/Kconfig +++ b/src/lpc176x/Kconfig @@ -25,7 +25,11 @@ config CLOCK_FREQ default 25000000 if MACH_LPC1768 # 100000000 / 4 default 30000000 if MACH_LPC1769 # 120000000 / 4 +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/lpc176x/Makefile b/src/lpc176x/Makefile index 2ff7d174..94e6a268 100644 --- a/src/lpc176x/Makefile +++ b/src/lpc176x/Makefile @@ -17,6 +17,7 @@ src-y += lpc176x/main.c lpc176x/timer.c lpc176x/gpio.c src-y += generic/crc16_ccitt.c generic/alloc.c src-y += generic/armcm_irq.c generic/timer_irq.c src-y += ../lib/lpc176x/device/system_LPC17xx.c +src-$(CONFIG_USBSERIAL) += lpc176x/usbserial.c generic/usb_cdc.c src-$(CONFIG_SERIAL) += lpc176x/serial.c generic/serial_irq.c # Add the TOOLCHAIN_GCC_ARM files to the build diff --git a/src/lpc176x/timer.c b/src/lpc176x/timer.c index d6d6f9ed..d8196329 100644 --- a/src/lpc176x/timer.c +++ b/src/lpc176x/timer.c @@ -38,7 +38,7 @@ timer_init(void) // Disable timer LPC_TIM0->TCR = 0x02; // Enable interrupts - NVIC_SetPriority(TIMER0_IRQn, 1); + NVIC_SetPriority(TIMER0_IRQn, 2); NVIC_EnableIRQ(TIMER0_IRQn); LPC_TIM0->MCR = 0x01; // Clear counter value diff --git a/src/lpc176x/usbserial.c b/src/lpc176x/usbserial.c new file mode 100644 index 00000000..28c0881f --- /dev/null +++ b/src/lpc176x/usbserial.c @@ -0,0 +1,297 @@ +// Hardware interface to USB on lpc176x +// +// Copyright (C) 2018 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memcpy +#include "LPC17xx.h" // LPC_SC +#include "board/usb_cdc.h" // usb_notify_setup +#include "byteorder.h" // cpu_to_le32 +#include "command.h" // output +#include "internal.h" // gpio_peripheral +#include "sched.h" // DECL_INIT + +// Internal endpoint addresses +#define EP0OUT 0x00 +#define EP0IN 0x01 +#define EP1IN 0x03 +#define EP2OUT 0x04 +#define EP5IN 0x0b + +// USB device interupt status flags +#define EP_SLOW (1<<2) +#define DEV_STAT (1<<3) +#define CCEMPTY (1<<4) +#define CDFULL (1<<5) +#define EP_RLZED (1<<8) + +#define RD_EN (1<<0) +#define WR_EN (1<<1) + +static void +usb_irq_disable(void) +{ + NVIC_DisableIRQ(USB_IRQn); +} + +static void +usb_irq_enable(void) +{ + NVIC_EnableIRQ(USB_IRQn); +} + +static void +usb_wait(uint32_t flag) +{ + while (!(LPC_USB->USBDevIntSt & flag)) + ; + LPC_USB->USBDevIntClr = flag; +} + + +/**************************************************************** + * Serial Interface Engine (SIE) functions + ****************************************************************/ + +#define SIE_CMD_SELECT 0x00 +#define SIE_CMD_SET_ENDPOINT_STATUS 0x40 +#define SIE_CMD_SET_ADDRESS 0xD0 +#define SIE_CMD_CONFIGURE 0xD8 +#define SIE_CMD_SET_DEVICE_STATUS 0xFE +#define SIE_CMD_CLEAR_BUFFER 0xF2 +#define SIE_CMD_VALIDATE_BUFFER 0xFA + +static void +sie_cmd(uint32_t cmd) +{ + LPC_USB->USBDevIntClr = CDFULL | CCEMPTY; + LPC_USB->USBCmdCode = 0x00000500 | (cmd << 16); + usb_wait(CCEMPTY); +} + +static void +sie_cmd_write(uint32_t cmd, uint32_t data) +{ + sie_cmd(cmd); + LPC_USB->USBCmdCode = 0x00000100 | (data << 16); + usb_wait(CCEMPTY); +} + +static uint32_t +sie_cmd_read(uint32_t cmd) +{ + sie_cmd(cmd); + LPC_USB->USBCmdCode = 0x00000200 | (cmd << 16); + usb_wait(CDFULL); + return LPC_USB->USBCmdData; +} + +static uint32_t +sie_select_and_clear(uint32_t idx) +{ + LPC_USB->USBEpIntClr = 1<USBCmdData; +} + + +/**************************************************************** + * Interface + ****************************************************************/ + +static int_fast8_t +usb_write_packet(uint32_t ep, const void *data, uint_fast8_t len) +{ + usb_irq_disable(); + uint32_t sts = sie_cmd_read(SIE_CMD_SELECT | ep); + if (sts & 0x01) { + // Output buffers full + usb_irq_enable(); + return -1; + } + + LPC_USB->USBCtrl = WR_EN | ((ep/2) << 2); + LPC_USB->USBTxPLen = len; + if (!len) + LPC_USB->USBTxData = 0; + int i; + for (i = 0; iUSBTxData = cpu_to_le32(d); + } + sie_cmd(SIE_CMD_VALIDATE_BUFFER); + usb_irq_enable(); + + return len; +} + +static int_fast8_t +usb_read_packet(uint32_t ep, void *data, uint_fast8_t max_len) +{ + usb_irq_disable(); + uint32_t sts = sie_cmd_read(SIE_CMD_SELECT | ep); + if (!(sts & 0x01)) { + // No data available + usb_irq_enable(); + return -1; + } + + // Determine packet size + LPC_USB->USBCtrl = RD_EN | ((ep/2) << 2); + uint32_t plen = LPC_USB->USBRxPLen; + while (!(plen & (1<<11))) + plen = LPC_USB->USBRxPLen; + plen &= 0x3FF; + if (plen > max_len) + // XXX - return error code? must keep reading? + plen = max_len; + // Copy data + uint32_t xfer = plen; + for (;;) { + uint32_t d = le32_to_cpu(LPC_USB->USBRxData); + if (xfer <= sizeof(d)) { + memcpy(data, &d, xfer); + break; + } + memcpy(data, &d, sizeof(d)); + data += sizeof(d); + xfer -= sizeof(d); + } + // Clear space for next packet + sts = sie_cmd_read(SIE_CMD_CLEAR_BUFFER); + usb_irq_enable(); + if (sts & 0x01) + // Packet overwritten + return -1; + + return plen; +} + +int_fast8_t +usb_read_bulk_out(void *data, uint_fast8_t max_len) +{ + return usb_read_packet(EP2OUT, data, max_len); +} + +int_fast8_t +usb_send_bulk_in(void *data, uint_fast8_t len) +{ + return usb_write_packet(EP5IN, data, len); +} + +int_fast8_t +usb_read_setup(void *data, uint_fast8_t max_len) +{ + return usb_read_packet(EP0OUT, data, max_len); +} + +int_fast8_t +usb_send_setup(const void *data, uint_fast8_t len) +{ + return usb_write_packet(EP0IN, data, len); +} + +void +usb_set_stall(void) +{ + usb_irq_disable(); + sie_cmd_write(SIE_CMD_SET_ENDPOINT_STATUS | 0, (1<<7)); + usb_irq_enable(); +} + +void +usb_set_address(uint_fast8_t addr) +{ + usb_irq_disable(); + sie_cmd_write(SIE_CMD_SET_ADDRESS, addr | (1<<7)); + usb_irq_enable(); + usb_send_setup(NULL, 0); +} + +static void +realize_endpoint(uint32_t idx, uint32_t packet_size) +{ + LPC_USB->USBDevIntClr = EP_RLZED; + LPC_USB->USBReEp |= 1<USBEpInd = idx; + LPC_USB->USBMaxPSize = packet_size; + usb_wait(EP_RLZED); + LPC_USB->USBEpIntEn |= 1<PCONP |= (1<<31); + // enable clock + LPC_USB->USBClkCtrl = 0x12; + while (LPC_USB->USBClkSt != 0x12) + ; + // configure USBD+, USBD-, and USB Connect pins + gpio_peripheral(0, 29, 1, 0); + gpio_peripheral(0, 30, 1, 0); + gpio_peripheral(2, 9, 1, 0); + // setup endpoints + realize_endpoint(EP0OUT, USB_CDC_EP0_SIZE); + realize_endpoint(EP0IN, USB_CDC_EP0_SIZE); + sie_cmd_write(SIE_CMD_SET_DEVICE_STATUS, 1); + // enable irqs + LPC_USB->USBDevIntEn = DEV_STAT | EP_SLOW; + NVIC_SetPriority(USB_IRQn, 1); + usb_irq_enable(); +} +DECL_INIT(usbserial_init); + +void +usbserial_shutdown(void) +{ + usb_irq_enable(); +} +DECL_SHUTDOWN(usbserial_shutdown); + +void __visible +USB_IRQHandler(void) +{ + uint32_t udis = LPC_USB->USBDevIntSt; + if (udis & DEV_STAT) { + LPC_USB->USBDevIntClr = DEV_STAT; + // XXX - should handle reset and other states + } + if (udis & EP_SLOW) { + uint32_t ueis = LPC_USB->USBEpIntSt; + if (ueis & (1<USBDevIntClr = EP_SLOW; + } +}