| /* |
| * Copyright 2025 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "sw/device/lib/usbdev/usbdev.h" |
| |
| #include "hw/top_matcha/sw/autogen/top_matcha.h" |
| #include "sw/device/lib/arch/device.h" |
| #include "sw/device/lib/dif/dif_pinmux.h" |
| #include "sw/device/lib/testing/pinmux_testutils.h" |
| #include "sw/device/lib/testing/test_framework/check.h" |
| #include "sw/device/lib/testing/usb_testutils_controlep.h" |
| |
| const unsigned kDataEp = 1; |
| const unsigned kControlEp = 2; |
| |
| typedef struct { |
| uint32_t size; |
| } ControlPkt; |
| |
| static dif_pinmux_t pinmux; |
| |
| static const uint8_t kUsbdevProfileBulkDescriptors[] = { |
| USB_CFG_DSCR_HEAD(USB_CFG_DSCR_LEN + (2 * (USB_INTERFACE_DSCR_LEN + |
| 2 * (USB_EP_DSCR_LEN))), |
| 2), |
| VEND_INTERFACE_DSCR(0, 2, 0x51, 1), |
| USB_BULK_EP_DSCR(0, kDataEp, USBDEV_MAX_PACKET_SIZE, 0), |
| USB_BULK_EP_DSCR(1, kDataEp, USBDEV_MAX_PACKET_SIZE, 0), |
| |
| VEND_INTERFACE_DSCR(1, 2, 0x51, 1), |
| USB_BULK_EP_DSCR(0, kControlEp, USBDEV_MAX_PACKET_SIZE, 0), |
| USB_BULK_EP_DSCR(1, kControlEp, USBDEV_MAX_PACKET_SIZE, 0), |
| }; |
| |
| static void rx_data(void* ctx, dif_usbdev_rx_packet_info_t packet_info, |
| dif_usbdev_buffer_t buf) { |
| UsbdevContext* uctx = (UsbdevContext*)ctx; |
| size_t bytes_written; |
| CHECK_DIF_OK(dif_usbdev_buffer_read( |
| uctx->usbdev.dev, uctx->usbdev.buffer_pool, &buf, |
| uctx->dst + uctx->bytes_received, /*dst_len=*/1024, &bytes_written)); |
| uctx->bytes_received += packet_info.length; |
| } |
| |
| static void rx_control(void* ctx, dif_usbdev_rx_packet_info_t packet_info, |
| dif_usbdev_buffer_t buf) { |
| ControlPkt control; |
| size_t bytes_written; |
| UsbdevContext* uctx = (UsbdevContext*)ctx; |
| CHECK(packet_info.length == sizeof(control)); |
| CHECK_DIF_OK(dif_usbdev_buffer_read( |
| uctx->usbdev.dev, uctx->usbdev.buffer_pool, &buf, (uint8_t*)&control, |
| sizeof(control), &bytes_written)); |
| CHECK(bytes_written == sizeof(control)); |
| uctx->bytes_to_receive = control.size; |
| if (uctx->verbose) { |
| LOG_INFO("The host will send %d bytes of data.", uctx->bytes_to_receive); |
| } |
| } |
| |
| dif_result_t UsbdevInit(UsbdevContext* ctx, UsbdevProfile profile) { |
| if (ctx == NULL) { |
| return kDifBadArg; |
| } |
| if (profile != kUsbdevProfileBulk) { |
| return kDifBadArg; |
| } |
| |
| memset(&ctx->usbdev, 0, sizeof(ctx->usbdev)); |
| memset(&ctx->usbdev_control, 0, sizeof(ctx->usbdev_control)); |
| ctx->finished = false; |
| ctx->bytes_received = 0; |
| ctx->bytes_to_receive = ~0U; |
| |
| CHECK_DIF_OK(dif_pinmux_init( |
| mmio_region_from_addr(TOP_MATCHA_PINMUX_AON_BASE_ADDR), &pinmux)); |
| pinmux_testutils_init(&pinmux); |
| |
| CHECK_DIF_OK(dif_pinmux_input_select(&pinmux, |
| kTopMatchaPinmuxPeripheralInUsbdevSense, |
| kTopMatchaPinmuxInselIoc7)); |
| // VBUS SENSE on Nexus is active-low. USBDEV wants active high, so invert the |
| // signal via pinmux. |
| if (kDeviceType == kDeviceFpgaNexus) { |
| dif_pinmux_pad_attr_t attrs; |
| CHECK_DIF_OK(dif_pinmux_pad_get_attrs(&pinmux, kTopMatchaMuxedPadsIoc7, |
| kDifPinmuxPadKindMio, &attrs)); |
| attrs.flags |= kDifPinmuxPadAttrInvertLevel; |
| CHECK_DIF_OK(dif_pinmux_pad_write_attrs( |
| &pinmux, kTopMatchaMuxedPadsIoc7, kDifPinmuxPadKindMio, attrs, &attrs)); |
| } |
| |
| bool en_diff_rcvr = kDeviceType == kDeviceFpgaNexus ? true : false; |
| usb_testutils_init(&ctx->usbdev, false, en_diff_rcvr, false); |
| usb_testutils_controlep_init(&ctx->usbdev_control, &ctx->usbdev, /*ep=*/0, |
| kUsbdevProfileBulkDescriptors, |
| sizeof(kUsbdevProfileBulkDescriptors), NULL, 0); |
| |
| if (ctx->verbose) { |
| LOG_INFO("Waiting for USB to finish configuring..."); |
| } |
| while (ctx->usbdev_control.device_state != kUsbTestutilsDeviceConfigured) { |
| usb_testutils_poll(&ctx->usbdev); |
| } |
| if (ctx->verbose) { |
| LOG_INFO("USB finished configuration!"); |
| } |
| |
| usb_testutils_endpoint_setup(&ctx->usbdev, kDataEp, kUsbdevOutStream, ctx, |
| /*tx_done=*/NULL, rx_data, /*flush=*/NULL, |
| /*reset=*/NULL); |
| usb_testutils_endpoint_setup(&ctx->usbdev, kControlEp, kUsbdevOutStream, ctx, |
| /*tx_done=*/NULL, rx_control, /*flush=*/NULL, |
| /*reset=*/NULL); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t UsbdevPoll(UsbdevContext* ctx) { |
| usb_testutils_poll(&ctx->usbdev); |
| if (ctx->bytes_received >= ctx->bytes_to_receive) { |
| ctx->finished = true; |
| } |
| return kDifOk; |
| } |