/* * 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 "misc.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; #ifdef GPS_PPS_ON_CD /* PPS tracking */ struct { uint32_t last; bool set; } pps; #endif } 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; } #ifdef GPS_PPS_ON_CD /* IN EP CTL: Send PPS pulse */ ep_regs = &usb_ep_regs[USB_EP_GPS_CDC_CTL & 0x1f].in; if ((ep_regs->bd[0].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) { /* Default request */ /* Put as static to work around gcc aliasing bug ... */ static struct usb_cdc_notif_serial_state __attribute__((aligned(4))) notif = { .hdr = { .bmRequestType = USB_REQ_READ | USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF, .bRequest = USB_NOTIF_CDC_SERIAL_STATE, .wValue = 0, .wIndex = USB_INTF_GPS_CDC_CTL, .wLength = 2 }, .bits = 0x00 }; const int notif_len = sizeof(struct usb_cdc_notif_serial_state); /* Check if PPS occurred */ uint32_t pps_now = time_pps_read(); if (pps_now != g_usb_gps.pps.last) { /* Update last */ g_usb_gps.pps.last = pps_now; /* Queue CD Set */ notif.bits = 1; usb_data_write(ep_regs->bd[0].ptr, ¬if, notif_len); ep_regs->bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(notif_len); /* Need to clear in the future */ g_usb_gps.pps.set = true; } else if (g_usb_gps.pps.set) { /* Queue CD Clear */ notif.bits = 0; usb_data_write(ep_regs->bd[0].ptr, ¬if, notif_len); ep_regs->bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(notif_len); /* Cleared */ g_usb_gps.pps.set = false; } } #endif } 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); }