blob: bbde9ab7e5cb9eb73b01bb34cf8a6cdc4cd6acfd [file] [log] [blame]
/*
* 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;
}