osmo-e1-hardware/firmware/ice40-riscv/icE1usb/usb_gps.c

270 lines
5.9 KiB
C

/*
* usb_gps.c
*
* Copyright (C) 2019-2022 Sylvain Munaut <tnt@246tNt.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <no2usb/usb.h>
#include <no2usb/usb_hw.h>
#include <no2usb/usb_priv.h>
#include <no2usb/usb_proto.h>
#include <no2usb/usb_cdc_proto.h>
#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, &notif, 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, &notif, 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);
}