| /* |
| * usb_e1.c |
| * |
| * Copyright (C) 2019-2020 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_hw.h> |
| #include <no2usb/usb_priv.h> |
| |
| #include "console.h" |
| #include "misc.h" |
| #include "e1.h" |
| #include "e1_hw.h" |
| |
| #include "ice1usb_proto.h" |
| |
| struct { |
| bool running; /* are we running (transceiving USB data)? */ |
| int out_bdi; /* buffer descriptor index for OUT EP */ |
| int in_bdi; /* buffer descriptor index for IN EP */ |
| struct ice1usb_tx_config tx_cfg; |
| struct ice1usb_rx_config rx_cfg; |
| struct e1_error_count last_err; |
| } g_usb_e1; |
| |
| /* default configuration at power-up */ |
| static const struct ice1usb_tx_config tx_cfg_default = { |
| .mode = ICE1USB_TX_MODE_TS0_CRC4_E, |
| .timing = ICE1USB_TX_TIME_SRC_LOCAL, |
| .ext_loopback = ICE1USB_TX_EXT_LOOPBACK_OFF, |
| .alarm = 0, |
| }; |
| |
| static const struct ice1usb_rx_config rx_cfg_default = { |
| .mode = ICE1USB_RX_MODE_MULTIFRAME, |
| }; |
| |
| |
| /* Hack */ |
| unsigned int e1_rx_need_data(unsigned int usb_addr, unsigned int max_len, unsigned int *pos); |
| unsigned int e1_tx_feed_data(unsigned int usb_addr, unsigned int len); |
| unsigned int e1_tx_level(void); |
| unsigned int e1_rx_level(void); |
| /* ---- */ |
| |
| static void |
| _usb_fill_feedback_ep(void) |
| { |
| static uint16_t ticks_prev = 0; |
| uint16_t ticks; |
| uint32_t val = 8192; |
| unsigned int level; |
| |
| /* Compute real E1 tick count (with safety against bad values) */ |
| ticks = e1_tick_read(); |
| val = (ticks - ticks_prev) & 0xffff; |
| ticks_prev = ticks; |
| if ((val < 7168) | (val > 9216)) |
| val = 8192; |
| |
| /* Bias depending on TX fifo level */ |
| level = e1_tx_level(); |
| if (level < (3 * 16)) |
| val += 256; |
| else if (level > (8 * 16)) |
| val -= 256; |
| |
| /* Prepare buffer */ |
| usb_data_write(usb_ep_regs[1].in.bd[0].ptr, &val, 4); |
| usb_ep_regs[1].in.bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(3); |
| } |
| |
| |
| void |
| usb_e1_run(void) |
| { |
| int bdi; |
| |
| if (!g_usb_e1.running) |
| return; |
| |
| /* EP3 IRQ */ |
| if ((usb_ep_regs[3].in.bd[0].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) { |
| const struct e1_error_count *cur_err = e1_get_error_count(); |
| if (memcmp(cur_err, &g_usb_e1.last_err, sizeof(*cur_err))) { |
| struct ice1usb_irq errmsg = { |
| .type = ICE1USB_IRQ_T_ERRCNT, |
| .u = { |
| .errors = { |
| .crc = cur_err->crc, |
| .align = cur_err->align, |
| .ovfl = cur_err->ovfl, |
| .unfl = cur_err->unfl, |
| .flags = cur_err->flags, |
| } |
| } |
| }; |
| printf("E"); |
| usb_data_write(usb_ep_regs[3].in.bd[0].ptr, &errmsg, sizeof(errmsg)); |
| usb_ep_regs[3].in.bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(sizeof(errmsg)); |
| g_usb_e1.last_err = *cur_err; |
| } |
| } |
| |
| /* EP2 IN */ |
| bdi = g_usb_e1.in_bdi; |
| |
| while ((usb_ep_regs[2].in.bd[bdi].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) |
| { |
| uint32_t ptr = usb_ep_regs[2].in.bd[bdi].ptr; |
| uint32_t hdr; |
| unsigned int pos; |
| |
| /* Error check */ |
| if ((usb_ep_regs[2].in.bd[bdi].csr & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_ERR) |
| puts("Err EP2 IN\n"); |
| |
| /* Get some data from E1 */ |
| int n = e1_rx_level(); |
| |
| if (n > 64) |
| n = 12; |
| else if (n > 32) |
| n = 10; |
| else if (n > 8) |
| n = 8; |
| else if (!n) |
| break; |
| |
| n = e1_rx_need_data((ptr >> 2) + 1, n, &pos); |
| |
| /* Write header: currently version and pos (mfr/fr number) */ |
| hdr = (0 << 28) | (pos & 0xff); |
| usb_data_write(ptr, &hdr, 4); |
| |
| /* Resubmit */ |
| usb_ep_regs[2].in.bd[bdi].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN((n * 32) + 4); |
| |
| /* Next BDI */ |
| bdi ^= 1; |
| g_usb_e1.in_bdi = bdi; |
| } |
| |
| /* EP1 OUT */ |
| bdi = g_usb_e1.out_bdi; |
| |
| while ((usb_ep_regs[1].out.bd[bdi].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) |
| { |
| uint32_t ptr = usb_ep_regs[1].out.bd[bdi].ptr; |
| uint32_t csr = usb_ep_regs[1].out.bd[bdi].csr; |
| uint32_t hdr; |
| |
| /* Error check */ |
| if ((csr & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_ERR) { |
| puts("Err EP1 OUT\n"); |
| goto refill; |
| } |
| |
| /* Grab header */ |
| usb_data_read(&hdr, ptr, 4); |
| |
| /* Empty data into the FIFO */ |
| int n = ((csr & USB_BD_LEN_MSK) - 4) / 32; |
| n = e1_tx_feed_data((ptr >> 2) + 1, n); |
| |
| refill: |
| /* Refill it */ |
| usb_ep_regs[1].out.bd[bdi].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(388); |
| |
| /* Next BDI */ |
| bdi ^= 1; |
| g_usb_e1.out_bdi = bdi; |
| |
| static int x = 0; |
| if ((x++ & 0xff) == 0xff) |
| puts("."); |
| } |
| |
| /* EP1 IN */ |
| if ((usb_ep_regs[1].in.bd[0].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) |
| { |
| _usb_fill_feedback_ep(); |
| } |
| } |
| |
| static enum usb_fnd_resp |
| _e1_set_conf(const struct usb_conf_desc *conf) |
| { |
| const struct usb_intf_desc *intf; |
| |
| printf("e1 set_conf %08x\n", conf); |
| if (!conf) |
| return USB_FND_SUCCESS; |
| |
| intf = usb_desc_find_intf(conf, 0, 0, NULL); |
| if (!intf) |
| return USB_FND_ERROR; |
| |
| printf("e1 set_conf %08x\n", intf); |
| |
| usb_ep_boot(intf, 0x01, true); |
| usb_ep_boot(intf, 0x81, false); |
| usb_ep_boot(intf, 0x82, true); |
| usb_ep_boot(intf, 0x83, false); |
| |
| return USB_FND_SUCCESS; |
| } |
| |
| static void _perform_tx_config(void) |
| { |
| const struct ice1usb_tx_config *cfg = &g_usb_e1.tx_cfg; |
| e1_tx_config( ((cfg->mode & 3) << 1) | |
| ((cfg->timing & 1) << 3) | |
| ((cfg->alarm & 1) << 4) | |
| ((cfg->ext_loopback & 3) << 5) ); |
| } |
| |
| static void _perform_rx_config(void) |
| { |
| const struct ice1usb_rx_config *cfg = &g_usb_e1.rx_cfg; |
| e1_rx_config((cfg->mode << 1)); |
| } |
| |
| static enum usb_fnd_resp |
| _e1_set_intf(const struct usb_intf_desc *base, const struct usb_intf_desc *sel) |
| { |
| /* Validity checks */ |
| if (base->bInterfaceNumber != 0) |
| return USB_FND_CONTINUE; |
| |
| if (sel->bAlternateSetting > 1) |
| return USB_FND_ERROR; |
| |
| /* Don't do anything if no change */ |
| if (g_usb_e1.running == (sel->bAlternateSetting != 0)) |
| return USB_FND_SUCCESS; |
| |
| g_usb_e1.running = (sel->bAlternateSetting != 0); |
| |
| /* Reconfigure the endpoints */ |
| usb_ep_reconf(sel, 0x01); |
| usb_ep_reconf(sel, 0x81); |
| usb_ep_reconf(sel, 0x82); |
| usb_ep_reconf(sel, 0x83); |
| |
| /* Update E1 and USB state */ |
| switch (g_usb_e1.running) { |
| case false: |
| /* Disable E1 rx/tx */ |
| e1_init(0, 0); |
| break; |
| |
| case true: |
| /* Reset and Re-Enable E1 */ |
| e1_init(0, 0); |
| _perform_rx_config(); |
| _perform_tx_config(); |
| |
| /* Reset BDI */ |
| g_usb_e1.in_bdi = 0; |
| g_usb_e1.out_bdi = 0; |
| |
| /* EP1 OUT: Queue two buffers */ |
| usb_ep_regs[1].out.bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(388); |
| usb_ep_regs[1].out.bd[1].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(388); |
| |
| /* EP1 IN: Queue buffer */ |
| _usb_fill_feedback_ep(); |
| |
| break; |
| } |
| |
| return USB_FND_SUCCESS; |
| } |
| |
| static enum usb_fnd_resp |
| _e1_get_intf(const struct usb_intf_desc *base, uint8_t *alt) |
| { |
| if (base->bInterfaceNumber != 0) |
| return USB_FND_CONTINUE; |
| |
| *alt = g_usb_e1.running ? 1 : 0; |
| |
| return USB_FND_SUCCESS; |
| } |
| |
| static bool |
| _set_tx_mode_done(struct usb_xfer *xfer) |
| { |
| const struct ice1usb_tx_config *cfg = (const struct ice1usb_tx_config *) xfer->data; |
| printf("set_tx_mode %02x%02x%02x%02x\r\n", |
| xfer->data[0], xfer->data[1], xfer->data[2], xfer->data[3]); |
| g_usb_e1.tx_cfg = *cfg; |
| _perform_tx_config(); |
| return true; |
| } |
| |
| static bool |
| _set_rx_mode_done(struct usb_xfer *xfer) |
| { |
| const struct ice1usb_rx_config *cfg = (const struct ice1usb_rx_config *) xfer->data; |
| printf("set_rx_mode %02x\r\n", xfer->data[0]); |
| g_usb_e1.rx_cfg = *cfg; |
| _perform_rx_config(); |
| return true; |
| } |
| |
| /* per-interface requests */ |
| static enum usb_fnd_resp |
| _e1_ctrl_req_intf(struct usb_ctrl_req *req, struct usb_xfer *xfer) |
| { |
| unsigned int i; |
| |
| switch (req->bRequest) { |
| case ICE1USB_INTF_GET_CAPABILITIES: |
| /* no optional capabilities yet */ |
| xfer->len = 0; |
| break; |
| case ICE1USB_INTF_SET_TX_CFG: |
| if (req->wLength < sizeof(struct ice1usb_tx_config)) |
| return USB_FND_ERROR; |
| xfer->cb_done = _set_tx_mode_done; |
| xfer->cb_ctx = req; |
| xfer->len = sizeof(struct ice1usb_tx_config); |
| break; |
| case ICE1USB_INTF_GET_TX_CFG: |
| if (req->wLength < sizeof(struct ice1usb_tx_config)) |
| return USB_FND_ERROR; |
| memcpy(xfer->data, &g_usb_e1.tx_cfg, sizeof(struct ice1usb_tx_config)); |
| xfer->len = sizeof(struct ice1usb_tx_config); |
| break; |
| case ICE1USB_INTF_SET_RX_CFG: |
| if (req->wLength < sizeof(struct ice1usb_rx_config)) |
| return USB_FND_ERROR; |
| xfer->cb_done = _set_rx_mode_done; |
| xfer->cb_ctx = req; |
| xfer->len = sizeof(struct ice1usb_rx_config); |
| break; |
| case ICE1USB_INTF_GET_RX_CFG: |
| if (req->wLength < sizeof(struct ice1usb_rx_config)) |
| return USB_FND_ERROR; |
| memcpy(xfer->data, &g_usb_e1.rx_cfg, sizeof(struct ice1usb_rx_config)); |
| xfer->len = sizeof(struct ice1usb_rx_config); |
| break; |
| default: |
| return USB_FND_ERROR; |
| } |
| |
| return USB_FND_SUCCESS; |
| } |
| |
| /* device-global requests */ |
| static enum usb_fnd_resp |
| _e1_ctrl_req_dev(struct usb_ctrl_req *req, struct usb_xfer *xfer) |
| { |
| switch (req->bRequest) { |
| case ICE1USB_DEV_GET_CAPABILITIES: |
| xfer->data[0] = (1 << ICE1USB_DEV_CAP_GPSDO); |
| xfer->len = 1; |
| break; |
| default: |
| return USB_FND_ERROR; |
| } |
| |
| return USB_FND_SUCCESS; |
| } |
| |
| |
| /* USB host issues a control request to us */ |
| static enum usb_fnd_resp |
| _e1_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer) |
| { |
| if (USB_REQ_TYPE(req) != USB_REQ_TYPE_VENDOR) |
| return USB_FND_CONTINUE; |
| |
| switch (USB_REQ_RCPT(req)) { |
| case USB_REQ_RCPT_DEV: |
| return _e1_ctrl_req_dev(req, xfer); |
| case USB_REQ_RCPT_INTF: |
| if (req->wIndex != 0) |
| return USB_FND_ERROR; |
| return _e1_ctrl_req_intf(req, xfer); |
| case USB_REQ_RCPT_EP: |
| case USB_REQ_RCPT_OTHER: |
| default: |
| return USB_FND_ERROR; |
| } |
| } |
| |
| |
| static struct usb_fn_drv _e1_drv = { |
| .set_conf = _e1_set_conf, |
| .set_intf = _e1_set_intf, |
| .get_intf = _e1_get_intf, |
| .ctrl_req = _e1_ctrl_req, |
| }; |
| |
| void |
| usb_e1_init(void) |
| { |
| memset(&g_usb_e1, 0x00, sizeof(g_usb_e1)); |
| g_usb_e1.tx_cfg = tx_cfg_default; |
| g_usb_e1.rx_cfg = rx_cfg_default; |
| usb_register_function_driver(&_e1_drv); |
| } |