diff --git a/firmware/ice40-riscv/icE1usb/Makefile b/firmware/ice40-riscv/icE1usb/Makefile index 6725f6a..a6e9138 100644 --- a/firmware/ice40-riscv/icE1usb/Makefile +++ b/firmware/ice40-riscv/icE1usb/Makefile @@ -54,6 +54,7 @@ HEADERS_app=\ usb_desc_ids.h \ usb_dev.h \ usb_e1.h \ + usb_gps.h \ usb_str_app.gen.h \ $(NULL) @@ -65,6 +66,7 @@ SOURCES_app=\ usb_desc_app.c \ usb_dev.c \ usb_e1.c \ + usb_gps.c \ $(NULL) diff --git a/firmware/ice40-riscv/icE1usb/fw_app.c b/firmware/ice40-riscv/icE1usb/fw_app.c index c5c2b7c..78da8b6 100644 --- a/firmware/ice40-riscv/icE1usb/fw_app.c +++ b/firmware/ice40-riscv/icE1usb/fw_app.c @@ -21,6 +21,7 @@ #include "spi.h" #include "usb_dev.h" #include "usb_e1.h" +#include "usb_gps.h" #include "utils.h" @@ -105,6 +106,7 @@ void main() usb_dev_init(); usb_dfu_rt_init(); usb_e1_init(); + usb_gps_init(); /* Start */ led_state(true); @@ -168,5 +170,6 @@ void main() /* GPS poll */ gps_poll(); + usb_gps_poll(); } } diff --git a/firmware/ice40-riscv/icE1usb/gps.c b/firmware/ice40-riscv/icE1usb/gps.c index 2fd6ab8..c1ad3a8 100644 --- a/firmware/ice40-riscv/icE1usb/gps.c +++ b/firmware/ice40-riscv/icE1usb/gps.c @@ -13,6 +13,7 @@ #include "console.h" #include "gps.h" #include "misc.h" +#include "usb_gps.h" #include "utils.h" #include "config.h" @@ -217,6 +218,9 @@ gps_poll(void) /* If we do, process it locally to update our state */ _gps_parse_nmea(nmea); + + /* And queue it for USB */ + usb_gps_puts(nmea); } void diff --git a/firmware/ice40-riscv/icE1usb/usb_desc_app.c b/firmware/ice40-riscv/icE1usb/usb_desc_app.c index 22a81d3..e0edf80 100644 --- a/firmware/ice40-riscv/icE1usb/usb_desc_app.c +++ b/firmware/ice40-riscv/icE1usb/usb_desc_app.c @@ -42,7 +42,6 @@ static const struct { } __attribute__ ((packed)) e1[2]; /* CDC */ -#if 0 struct { struct usb_intf_desc intf_ctl; struct usb_cdc_hdr_desc cdc_hdr; @@ -53,7 +52,6 @@ static const struct { struct usb_ep_desc ep_data_out; struct usb_ep_desc ep_data_in; } __attribute__ ((packed)) cdc; -#endif /* DFU Runtime */ struct { @@ -207,7 +205,6 @@ static const struct { }, }, }, -#if 0 .cdc = { .intf_ctl = { .bLength = sizeof(struct usb_intf_desc), @@ -230,21 +227,24 @@ static const struct { .bLength = sizeof(struct usb_cdc_acm_desc), .bDescriptorType = USB_CS_DT_INTF, .bDescriptorsubtype = USB_CDC_DST_ACM, + /* Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, */ + /* and the notification Serial_State */ .bmCapabilities = 0x02, }, .cdc_union = { .bLength = sizeof(struct usb_cdc_union_desc) + 1, .bDescriptorType = USB_CS_DT_INTF, .bDescriptorsubtype = USB_CDC_DST_UNION, - .bMasterInterface = 1, - .bSlaveInterface = { 2 }, + .bMasterInterface = USB_INTF_GPS_CDC_CTL, + .bSlaveInterface = { USB_INTF_GPS_CDC_DATA }, }, .ep_ctl = { .bLength = sizeof(struct usb_ep_desc), .bDescriptorType = USB_DT_EP, .bEndpointAddress = USB_EP_GPS_CDC_CTL, .bmAttributes = 0x03, - .wMaxPacketSize = 64, + /* Longest notif is SERIAL_STATE with 2 data bytes */ + .wMaxPacketSize = sizeof(struct usb_ctrl_req) + 2, .bInterval = 0x40, }, .intf_data = { @@ -275,7 +275,6 @@ static const struct { .bInterval = 0x00, }, }, -#endif .dfu = { .intf = { .bLength = sizeof(struct usb_intf_desc), diff --git a/firmware/ice40-riscv/icE1usb/usb_desc_ids.h b/firmware/ice40-riscv/icE1usb/usb_desc_ids.h index 00dbb34..3fbc417 100644 --- a/firmware/ice40-riscv/icE1usb/usb_desc_ids.h +++ b/firmware/ice40-riscv/icE1usb/usb_desc_ids.h @@ -9,9 +9,15 @@ #define USB_INTF_E1(p) (0 + (p)) #define USB_INTF_DFU 2 -#define USB_INTF_NUM 3 +#define USB_INTF_GPS_CDC_CTL 3 +#define USB_INTF_GPS_CDC_DATA 4 +#define USB_INTF_NUM 5 #define USB_EP_E1_IN(p) (0x82 + (3 * (p))) #define USB_EP_E1_OUT(p) (0x01 + (3 * (p))) #define USB_EP_E1_FB(p) (0x81 + (3 * (p))) #define USB_EP_E1_INT(p) (0x83 + (3 * (p))) + +#define USB_EP_GPS_CDC_CTL 0x88 +#define USB_EP_GPS_CDC_OUT 0x09 +#define USB_EP_GPS_CDC_IN 0x89 diff --git a/firmware/ice40-riscv/icE1usb/usb_gps.c b/firmware/ice40-riscv/icE1usb/usb_gps.c new file mode 100644 index 0000000..1c10033 --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/usb_gps.c @@ -0,0 +1,211 @@ +/* + * usb_gps.c + * + * Copyright (C) 2019-2022 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "console.h" +#include "usb_desc_ids.h" + + +#define BUF_SIZE_LOG 9 +#define BUF_SIZE (1 << BUF_SIZE_LOG) +#define BUF_MASK (BUF_SIZE - 1) + +static struct { + /* State */ + bool active; + + /* Buffer */ + struct { + unsigned int wr; + unsigned int rd; + char data[BUF_SIZE] __attribute__((aligned(4))); + } buf; +} g_usb_gps; + + +static void +_usb_gps_set_active(bool active) +{ + /* Save new state */ + g_usb_gps.active = active; + + /* Reset FIFO if disabled */ + if (!active) + g_usb_gps.buf.wr = g_usb_gps.buf.rd = 0; +} + +static int +_usb_gps_fill_packet(unsigned int dst_ofs) +{ + unsigned int len; + + /* Len available, limited to 64 */ + len = (g_usb_gps.buf.wr - g_usb_gps.buf.rd) & BUF_MASK; + + if (len > 64) + len = 64; + + /* Copy block */ + usb_data_write(dst_ofs, &g_usb_gps.buf.data[g_usb_gps.buf.rd], (len + 3) & ~3); + + /* Increment read pointer */ + g_usb_gps.buf.rd = (g_usb_gps.buf.rd + len) & BUF_MASK; + + /* If length was not multiple of 4, we emptied the FIFO, + * so we reset it to 0 so we're aligned again */ + if (len & 3) + g_usb_gps.buf.wr = g_usb_gps.buf.rd = 0; + + return len; +} + + +void +usb_gps_puts(const char *str) +{ + unsigned int nxt; + char c; + + if (!g_usb_gps.active) + return; + + while ((c = *str++) != '\0') + { + /* Next write pointer pos and full check */ + nxt = (g_usb_gps.buf.wr + 1) & BUF_MASK; + if (nxt == g_usb_gps.buf.rd) + /* If overflow, we keep the latest content ... */ + g_usb_gps.buf.rd = (g_usb_gps.buf.rd + 1) & BUF_MASK; + + /* Write data */ + g_usb_gps.buf.data[g_usb_gps.buf.wr] = c; + + /* Update write pointer */ + g_usb_gps.buf.wr = nxt; + } +} + +void +usb_gps_poll(void) +{ + volatile struct usb_ep *ep_regs; + + /* OUT EP: We accept data and throw it away */ + ep_regs = &usb_ep_regs[USB_EP_GPS_CDC_OUT & 0x1f].out; + ep_regs->bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(64); + + /* IN EP: Send whatever is queued */ + ep_regs = &usb_ep_regs[USB_EP_GPS_CDC_OUT & 0x1f].in; + + if ((ep_regs->bd[0].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) + { + int len = _usb_gps_fill_packet(ep_regs->bd[0].ptr); + + if (len) + ep_regs->bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(len); + else + ep_regs->bd[0].csr = 0; + } +} + + +static enum usb_fnd_resp +_cdc_set_conf(const struct usb_conf_desc *conf) +{ + const struct usb_intf_desc *intf; + + if (!conf) + return USB_FND_SUCCESS; + + /* Configure control interface */ + intf = usb_desc_find_intf(conf, USB_INTF_GPS_CDC_CTL, 0, NULL); + if (!intf) + return USB_FND_ERROR; + + usb_ep_boot(intf, USB_EP_GPS_CDC_CTL, false); + + /* Configure data interface */ + intf = usb_desc_find_intf(conf, USB_INTF_GPS_CDC_DATA, 0, NULL); + if (!intf) + return USB_FND_ERROR; + + usb_ep_boot(intf, USB_EP_GPS_CDC_OUT, false); + usb_ep_boot(intf, USB_EP_GPS_CDC_IN, false); + + return USB_FND_SUCCESS; +} + +static enum usb_fnd_resp +_cdc_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer) +{ + static const struct usb_cdc_line_coding linecoding = { + .dwDTERate = 115200, + .bCharFormat = 2, + .bParityType = 0, + .bDataBits = 8, + }; + + /* Check this is handled here */ + if (USB_REQ_TYPE_RCPT(req) != (USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF)) + return USB_FND_CONTINUE; + + if (req->wIndex != USB_INTF_GPS_CDC_CTL) + return USB_FND_CONTINUE; + + /* Process request */ + switch (req->bRequest) { + case USB_REQ_CDC_SEND_ENCAPSULATED_COMMAND: + /* We don't support any, so just accept and don't care */ + return USB_FND_SUCCESS; + + case USB_REQ_CDC_GET_ENCAPSULATED_RESPONSE: + /* Never anything to return */ + xfer->len = 0; + return USB_FND_SUCCESS; + + case USB_REQ_CDC_SET_LINE_CODING: + /* We only support 1 config, doesn't matter what the hosts sends */ + return USB_FND_SUCCESS; + + case USB_REQ_CDC_GET_LINE_CODING: + /* We only support 1 config, send that back */ + xfer->data = (void*)&linecoding; + xfer->len = sizeof(linecoding); + return USB_FND_ERROR; + + case USB_REQ_CDC_SET_CONTROL_LINE_STATE: + /* Enable if DTR is set */ + _usb_gps_set_active((req->wValue & 1) != 0); + return USB_FND_SUCCESS; + } + + /* Anything else is not handled */ + return USB_FND_ERROR; +} + + +static struct usb_fn_drv _cdc_drv = { + .set_conf = _cdc_set_conf, + .ctrl_req = _cdc_ctrl_req, +}; + +void +usb_gps_init(void) +{ + memset(&g_usb_gps, 0x00, sizeof(g_usb_gps)); + usb_register_function_driver(&_cdc_drv); +} diff --git a/firmware/ice40-riscv/icE1usb/usb_gps.h b/firmware/ice40-riscv/icE1usb/usb_gps.h new file mode 100644 index 0000000..3324a45 --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/usb_gps.h @@ -0,0 +1,13 @@ +/* + * usb_gps.h + * + * Copyright (C) 2019-2022 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +void usb_gps_puts(const char *str); + +void usb_gps_poll(void); +void usb_gps_init(void); diff --git a/gateware/cores/no2usb b/gateware/cores/no2usb index e9d1fbb..fdf42a6 160000 --- a/gateware/cores/no2usb +++ b/gateware/cores/no2usb @@ -1 +1 @@ -Subproject commit e9d1fbb8a1796444091cd8a39e2a4318530b4daf +Subproject commit fdf42a6571a4ae49556626e6fffca1582796f7e8