[rpi3][usb][dwc] Raspberry PI 3 USB Root Hub
This commit adds a fake root hub to the low-level Raspberry PI USB driver. Since the root hub host port is onboard the USB controller, we intercept any requests destined for the root hub in the low level driver and handle them without scheduling them on the bus. To the driver, this hub appears as an ordinary hub. Change-Id: I665f5c636dd362de7866b88878c639fd410274a9
Esse commit está contido em:
@@ -316,6 +316,8 @@ mx_status_t mailbox_bind(mx_driver_t* driver, mx_device_t* parent) {
|
||||
return status;
|
||||
}
|
||||
|
||||
bcm_vc_poweron(bcm_dev_usb);
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2016 The Fuchsia Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if TRACE
|
||||
#define xprintf(fmt...) printf(fmt)
|
||||
#else
|
||||
#define xprintf(fmt...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif
|
||||
@@ -0,0 +1,711 @@
|
||||
// Copyright 2016 The Fuchsia Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Standard Includes
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <threads.h>
|
||||
|
||||
// DDK includes
|
||||
#include <ddk/binding.h>
|
||||
#include <ddk/common/usb.h>
|
||||
#include <ddk/completion.h>
|
||||
#include <ddk/device.h>
|
||||
#include <ddk/protocol/bcm.h>
|
||||
#include <ddk/protocol/usb-bus.h>
|
||||
#include <ddk/protocol/usb-hci.h>
|
||||
#include <ddk/protocol/usb.h>
|
||||
|
||||
#include <magenta/hw/usb-hub.h>
|
||||
#include <magenta/hw/usb.h>
|
||||
|
||||
#include "../bcm-common/bcm28xx.h"
|
||||
#include "bcm28xx/usb_dwc_regs.h"
|
||||
|
||||
#define dev_to_usb_dwc(dev) containerof(dev, usb_dwc_t, device)
|
||||
|
||||
#define NUM_HOST_CHANNELS 8
|
||||
#define PAGE_MASK_4K (0xFFF)
|
||||
#define USB_PAGE_START (USB_BASE & (~PAGE_MASK_4K))
|
||||
#define USB_PAGE_SIZE (0x4000)
|
||||
#define PAGE_REG_DELTA (USB_BASE - USB_PAGE_START)
|
||||
|
||||
#define MAX_DEVICE_COUNT 65
|
||||
#define ROOT_HUB_DEVICE_ID (MAX_DEVICE_COUNT - 1)
|
||||
|
||||
static volatile struct dwc_regs* regs;
|
||||
|
||||
#define TRACE 1
|
||||
#include "bcm-usb-dwc-debug.h"
|
||||
|
||||
typedef struct usb_dwc_transfer_request {
|
||||
list_node_t node;
|
||||
|
||||
iotxn_t* txn;
|
||||
} usb_dwc_transfer_request_t;
|
||||
|
||||
typedef struct usb_dwc {
|
||||
mx_device_t device;
|
||||
mx_device_t* bus_device;
|
||||
usb_bus_protocol_t* bus_protocol;
|
||||
mx_handle_t irq_handle;
|
||||
thrd_t irq_thread;
|
||||
mx_device_t* parent;
|
||||
|
||||
// Pertaining to root hub transactions.
|
||||
mtx_t rh_txn_mtx;
|
||||
completion_t rh_txn_completion;
|
||||
list_node_t rh_txn_head;
|
||||
} usb_dwc_t;
|
||||
|
||||
static mtx_t rh_status_mtx;
|
||||
static usb_dwc_transfer_request_t* rh_intr_req;
|
||||
static usb_port_status_t root_port_status;
|
||||
|
||||
#define MANUFACTURER_STRING 1
|
||||
#define PRODUCT_STRING_2 2
|
||||
|
||||
static const uint8_t dwc_language_list[] =
|
||||
{4, /* bLength */ USB_DT_STRING, 0x09, 0x04, /* language ID */};
|
||||
static const uint8_t dwc_manufacturer_string[] = // "Magenta"
|
||||
{18, /* bLength */ USB_DT_STRING, 'M', 0, 'a', 0, 'g', 0, 'e', 0, 'n', 0, 't', 0, 'a', 0, 0, 0};
|
||||
static const uint8_t dwc_product_string_2[] = // "USB 2.0 Root Hub"
|
||||
{
|
||||
36, /* bLength */ USB_DT_STRING, 'U', 0, 'S', 0, 'B', 0, ' ', 0, '2', 0, '.', 0, '0', 0, ' ', 0,
|
||||
'R', 0, 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, 'u', 0, 'b', 0, 0, 0,
|
||||
};
|
||||
|
||||
static const uint8_t* dwc_rh_string_table[] = {
|
||||
dwc_language_list,
|
||||
dwc_manufacturer_string,
|
||||
dwc_product_string_2,
|
||||
};
|
||||
|
||||
// device descriptor for USB 2.0 root hub
|
||||
// represented as a byte array to avoid endianness issues
|
||||
static const uint8_t dwc_rh_descriptor[sizeof(usb_device_descriptor_t)] = {
|
||||
sizeof(usb_device_descriptor_t), // bLength
|
||||
USB_DT_DEVICE, // bDescriptorType
|
||||
0x00, 0x02, // bcdUSB = 2.0
|
||||
USB_CLASS_HUB, // bDeviceClass
|
||||
0, // bDeviceSubClass
|
||||
1, // bDeviceProtocol = Single TT
|
||||
64, // bMaxPacketSize0
|
||||
0xD1, 0x18, // idVendor = 0x18D1 (Google)
|
||||
0x02, 0xA0, // idProduct = 0xA002
|
||||
0x00, 0x01, // bcdDevice = 1.0
|
||||
MANUFACTURER_STRING, // iManufacturer
|
||||
PRODUCT_STRING_2, // iProduct
|
||||
0, // iSerialNumber
|
||||
1, // bNumConfigurations
|
||||
};
|
||||
|
||||
#define CONFIG_DESC_SIZE sizeof(usb_configuration_descriptor_t) + \
|
||||
sizeof(usb_interface_descriptor_t) + \
|
||||
sizeof(usb_endpoint_descriptor_t)
|
||||
|
||||
// we are currently using the same configuration descriptors for both USB 2.0 and 3.0 root hubs
|
||||
// this is not actually correct, but our usb-hub driver isn't sophisticated enough to notice
|
||||
static const uint8_t dwc_rh_config_descriptor[CONFIG_DESC_SIZE] = {
|
||||
// config descriptor
|
||||
sizeof(usb_configuration_descriptor_t), // bLength
|
||||
USB_DT_CONFIG, // bDescriptorType
|
||||
CONFIG_DESC_SIZE, 0, // wTotalLength
|
||||
1, // bNumInterfaces
|
||||
1, // bConfigurationValue
|
||||
0, // iConfiguration
|
||||
0xE0, // bmAttributes = self powered
|
||||
0, // bMaxPower
|
||||
// interface descriptor
|
||||
sizeof(usb_interface_descriptor_t), // bLength
|
||||
USB_DT_INTERFACE, // bDescriptorType
|
||||
0, // bInterfaceNumber
|
||||
0, // bAlternateSetting
|
||||
1, // bNumEndpoints
|
||||
USB_CLASS_HUB, // bInterfaceClass
|
||||
0, // bInterfaceSubClass
|
||||
0, // bInterfaceProtocol
|
||||
0, // iInterface
|
||||
// endpoint descriptor
|
||||
sizeof(usb_endpoint_descriptor_t), // bLength
|
||||
USB_DT_ENDPOINT, // bDescriptorType
|
||||
USB_ENDPOINT_IN | 1, // bEndpointAddress
|
||||
USB_ENDPOINT_INTERRUPT, // bmAttributes
|
||||
4, 0, // wMaxPacketSize
|
||||
12, // bInterval
|
||||
};
|
||||
|
||||
static inline bool is_roothub_request(usb_dwc_transfer_request_t* req) {
|
||||
iotxn_t* txn = req->txn;
|
||||
usb_protocol_data_t* data = iotxn_pdata(txn, usb_protocol_data_t);
|
||||
return data->device_id == ROOT_HUB_DEVICE_ID;
|
||||
}
|
||||
|
||||
static inline bool is_control_request(usb_dwc_transfer_request_t* req) {
|
||||
iotxn_t* txn = req->txn;
|
||||
usb_protocol_data_t* data = iotxn_pdata(txn, usb_protocol_data_t);
|
||||
return data->ep_address == 0;
|
||||
}
|
||||
|
||||
// Completes the iotxn associated with a request then cleans up the request.
|
||||
static void complete_request(
|
||||
usb_dwc_transfer_request_t* req,
|
||||
mx_status_t status,
|
||||
size_t length) {
|
||||
iotxn_t* txn = req->txn;
|
||||
txn->ops->complete(txn, status, length);
|
||||
|
||||
// TODO(gkalsi): Just for diagnostics.
|
||||
memset(req, 0xC, sizeof(*req));
|
||||
|
||||
free(req);
|
||||
}
|
||||
|
||||
static void dwc_complete_root_port_status_txn(void) {
|
||||
|
||||
mtx_lock(&rh_status_mtx);
|
||||
|
||||
if (root_port_status.wPortChange) {
|
||||
if (rh_intr_req && rh_intr_req->txn) {
|
||||
iotxn_t* txn = rh_intr_req->txn;
|
||||
uint16_t val = 0x2;
|
||||
txn->ops->copyto(txn, (void*)&val, sizeof(val), 0);
|
||||
complete_request(rh_intr_req, NO_ERROR, sizeof(val));
|
||||
rh_intr_req = NULL;
|
||||
}
|
||||
}
|
||||
mtx_unlock(&rh_status_mtx);
|
||||
}
|
||||
|
||||
static void dwc_reset_host_port(void) {
|
||||
union dwc_host_port_ctrlstatus hw_status = regs->host_port_ctrlstatus;
|
||||
hw_status.enabled = 0;
|
||||
hw_status.connected_changed = 0;
|
||||
hw_status.enabled_changed = 0;
|
||||
hw_status.overcurrent_changed = 0;
|
||||
|
||||
hw_status.reset = 1;
|
||||
regs->host_port_ctrlstatus = hw_status;
|
||||
|
||||
mx_nanosleep(MX_MSEC(60));
|
||||
|
||||
hw_status.reset = 0;
|
||||
regs->host_port_ctrlstatus = hw_status;
|
||||
}
|
||||
|
||||
static void dwc_host_port_power_on(void) {
|
||||
union dwc_host_port_ctrlstatus hw_status = regs->host_port_ctrlstatus;
|
||||
hw_status.enabled = 0;
|
||||
hw_status.connected_changed = 0;
|
||||
hw_status.enabled_changed = 0;
|
||||
hw_status.overcurrent_changed = 0;
|
||||
|
||||
hw_status.powered = 1;
|
||||
regs->host_port_ctrlstatus = hw_status;
|
||||
}
|
||||
|
||||
static mx_status_t usb_dwc_softreset_core(void) {
|
||||
while (!(regs->core_reset & DWC_AHB_MASTER_IDLE))
|
||||
;
|
||||
|
||||
regs->core_reset = DWC_SOFT_RESET;
|
||||
while (regs->core_reset & DWC_SOFT_RESET)
|
||||
;
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static mx_status_t usb_dwc_setupcontroller(void) {
|
||||
const uint32_t rx_words = 1024;
|
||||
const uint32_t tx_words = 1024;
|
||||
const uint32_t ptx_words = 1024;
|
||||
|
||||
regs->rx_fifo_size = rx_words;
|
||||
regs->nonperiodic_tx_fifo_size = (tx_words << 16) | rx_words;
|
||||
regs->host_periodic_tx_fifo_size = (ptx_words << 16) | (rx_words + tx_words);
|
||||
|
||||
regs->ahb_configuration |= DWC_AHB_DMA_ENABLE | BCM_DWC_AHB_AXI_WAIT;
|
||||
|
||||
union dwc_core_interrupts core_interrupt_mask;
|
||||
|
||||
regs->core_interrupt_mask.val = 0;
|
||||
regs->core_interrupts.val = 0xffffffff;
|
||||
|
||||
core_interrupt_mask.val = 0;
|
||||
core_interrupt_mask.host_channel_intr = 1;
|
||||
core_interrupt_mask.port_intr = 1;
|
||||
regs->core_interrupt_mask = core_interrupt_mask;
|
||||
|
||||
regs->ahb_configuration |= DWC_AHB_INTERRUPT_ENABLE;
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// Queue a transaction on the DWC root hub.
|
||||
static void dwc_iotxn_queue_rh(usb_dwc_t* dwc,
|
||||
usb_dwc_transfer_request_t* req) {
|
||||
mtx_lock(&dwc->rh_txn_mtx);
|
||||
|
||||
list_add_tail(&dwc->rh_txn_head, &req->node);
|
||||
|
||||
mtx_unlock(&dwc->rh_txn_mtx);
|
||||
|
||||
// Signal to the processor thread to wake up and process this request.
|
||||
completion_signal(&dwc->rh_txn_completion);
|
||||
}
|
||||
|
||||
// Queue a transaction on external peripherals using the DWC host channels.
|
||||
static void dwc_iotxn_queue_hw(usb_dwc_t* dwc,
|
||||
usb_dwc_transfer_request_t* req) {
|
||||
}
|
||||
|
||||
static void do_dwc_iotxn_queue(usb_dwc_t* dwc, iotxn_t* txn) {
|
||||
// Once an iotxn enters the low-level DWC stack, it is always encapsulated
|
||||
// by a usb_dwc_transfer_request_t.
|
||||
usb_dwc_transfer_request_t* req = calloc(1, sizeof(*req));
|
||||
if (!req) {
|
||||
// If we can't allocate memory for the request, complete the iotxn with
|
||||
// a failure.
|
||||
txn->ops->complete(txn, ERR_NO_MEMORY, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the request.
|
||||
req->txn = txn;
|
||||
|
||||
if (is_roothub_request(req)) {
|
||||
dwc_iotxn_queue_rh(dwc, req);
|
||||
} else {
|
||||
dwc_iotxn_queue_hw(dwc, req);
|
||||
}
|
||||
}
|
||||
|
||||
static void dwc_iotxn_queue(mx_device_t* hci_device, iotxn_t* txn) {
|
||||
usb_dwc_t* dwc = dev_to_usb_dwc(hci_device);
|
||||
do_dwc_iotxn_queue(dwc, txn);
|
||||
}
|
||||
|
||||
static void dwc_unbind(mx_device_t* dev) {
|
||||
xprintf("usb dwc_unbind not implemented\n");
|
||||
}
|
||||
|
||||
static mx_status_t dwc_release(mx_device_t* device) {
|
||||
xprintf("usb dwc_release not implemented\n");
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static mx_protocol_device_t dwc_device_proto = {
|
||||
.iotxn_queue = dwc_iotxn_queue,
|
||||
.unbind = dwc_unbind,
|
||||
.release = dwc_release,
|
||||
};
|
||||
|
||||
static void dwc_set_bus_device(mx_device_t* device, mx_device_t* busdev) {
|
||||
usb_dwc_t* dwc = dev_to_usb_dwc(device);
|
||||
dwc->bus_device = busdev;
|
||||
if (busdev) {
|
||||
device_get_protocol(busdev, MX_PROTOCOL_USB_BUS,
|
||||
(void**)&dwc->bus_protocol);
|
||||
dwc_reset_host_port();
|
||||
dwc->bus_protocol->add_device(dwc->bus_device, ROOT_HUB_DEVICE_ID, 0,
|
||||
USB_SPEED_HIGH);
|
||||
} else {
|
||||
dwc->bus_protocol = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t dwc_get_max_device_count(mx_device_t* device) {
|
||||
return MAX_DEVICE_COUNT;
|
||||
}
|
||||
|
||||
static mx_status_t dwc_enable_ep(mx_device_t* hci_device, uint32_t device_id,
|
||||
usb_endpoint_descriptor_t* ep_desc, bool enable) {
|
||||
xprintf("usb dwc_enable_ep not implemented\n");
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static uint64_t dwc_get_frame(mx_device_t* hci_device) {
|
||||
xprintf("usb dwc_get_frame not implemented\n");
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
mx_status_t dwc_config_hub(mx_device_t* hci_device, uint32_t device_id, usb_speed_t speed,
|
||||
usb_hub_descriptor_t* descriptor) {
|
||||
xprintf("usb dwc_config_hub not implemented\n");
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
mx_status_t dwc_hub_device_added(mx_device_t* hci_device, uint32_t hub_address, int port,
|
||||
usb_speed_t speed) {
|
||||
xprintf("usb dwc_hub_device_added not implemented\n");
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
mx_status_t dwc_hub_device_removed(mx_device_t* hci_device, uint32_t hub_address, int port) {
|
||||
xprintf("usb dwc_hub_device_removed not implemented\n");
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static usb_hci_protocol_t dwc_hci_protocol = {
|
||||
.set_bus_device = dwc_set_bus_device,
|
||||
.get_max_device_count = dwc_get_max_device_count,
|
||||
.enable_endpoint = dwc_enable_ep,
|
||||
.get_current_frame = dwc_get_frame,
|
||||
.configure_hub = dwc_config_hub,
|
||||
.hub_device_added = dwc_hub_device_added,
|
||||
.hub_device_removed = dwc_hub_device_removed,
|
||||
};
|
||||
|
||||
void dwc_handle_irq(void) {
|
||||
union dwc_core_interrupts interrupts = regs->core_interrupts;
|
||||
|
||||
if (interrupts.port_intr) {
|
||||
// Clear the interrupt.
|
||||
union dwc_host_port_ctrlstatus hw_status = regs->host_port_ctrlstatus;
|
||||
|
||||
mtx_lock(&rh_status_mtx);
|
||||
|
||||
root_port_status.wPortChange = 0;
|
||||
root_port_status.wPortStatus = 0;
|
||||
|
||||
// This device only has one port.
|
||||
if (hw_status.connected)
|
||||
root_port_status.wPortStatus |= USB_PORT_CONNECTION;
|
||||
if (hw_status.enabled)
|
||||
root_port_status.wPortStatus |= USB_PORT_ENABLE;
|
||||
if (hw_status.suspended)
|
||||
root_port_status.wPortStatus |= USB_PORT_SUSPEND;
|
||||
if (hw_status.overcurrent)
|
||||
root_port_status.wPortStatus |= USB_PORT_OVER_CURRENT;
|
||||
if (hw_status.reset)
|
||||
root_port_status.wPortStatus |= USB_PORT_RESET;
|
||||
if (hw_status.speed == USB_SPEED_LOW)
|
||||
root_port_status.wPortStatus |= USB_PORT_LOW_SPEED;
|
||||
if (hw_status.speed == USB_SPEED_HIGH)
|
||||
root_port_status.wPortStatus |= USB_PORT_HIGH_SPEED;
|
||||
|
||||
if (hw_status.connected_changed)
|
||||
root_port_status.wPortChange |= USB_PORT_CONNECTION;
|
||||
if (hw_status.enabled_changed)
|
||||
root_port_status.wPortChange |= USB_PORT_ENABLE;
|
||||
if (hw_status.overcurrent_changed)
|
||||
root_port_status.wPortChange |= USB_PORT_OVER_CURRENT;
|
||||
|
||||
mtx_unlock(&rh_status_mtx);
|
||||
|
||||
// Clear the interrupt.
|
||||
hw_status.enabled = 0;
|
||||
regs->host_port_ctrlstatus = hw_status;
|
||||
|
||||
dwc_complete_root_port_status_txn();
|
||||
}
|
||||
}
|
||||
|
||||
// Thread to handle interrupts.
|
||||
static int dwc_irq_thread(void* arg) {
|
||||
usb_dwc_t* dwc = (usb_dwc_t*)arg;
|
||||
|
||||
device_add(&dwc->device, dwc->parent);
|
||||
dwc->parent = NULL;
|
||||
|
||||
while (1) {
|
||||
mx_status_t wait_res;
|
||||
|
||||
wait_res = mx_handle_wait_one(dwc->irq_handle, MX_SIGNAL_SIGNALED,
|
||||
MX_TIME_INFINITE, NULL);
|
||||
if (wait_res != NO_ERROR)
|
||||
printf("dwc_irq_thread::mx_handle_wait_one(irq_handle) returned "
|
||||
"error code = %d\n",
|
||||
wait_res);
|
||||
|
||||
dwc_handle_irq();
|
||||
|
||||
mx_interrupt_complete(dwc->irq_handle);
|
||||
}
|
||||
|
||||
printf("dwc_irq_thread done.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static mx_status_t dwc_host_port_set_feature(uint16_t feature) {
|
||||
if (feature == USB_FEATURE_PORT_POWER) {
|
||||
dwc_host_port_power_on();
|
||||
return NO_ERROR;
|
||||
} else if (feature == USB_FEATURE_PORT_RESET) {
|
||||
dwc_reset_host_port();
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
return ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static void dwc_root_hub_get_descriptor(usb_dwc_transfer_request_t* req) {
|
||||
iotxn_t* txn = req->txn;
|
||||
usb_protocol_data_t* data = iotxn_pdata(txn, usb_protocol_data_t);
|
||||
usb_setup_t* setup = &data->setup;
|
||||
|
||||
uint16_t value = le16toh(setup->wValue);
|
||||
uint16_t index = le16toh(setup->wIndex);
|
||||
uint16_t length = le16toh(setup->wLength);
|
||||
|
||||
uint8_t desc_type = value >> 8;
|
||||
if (desc_type == USB_DT_DEVICE && index == 0) {
|
||||
if (length > sizeof(usb_device_descriptor_t))
|
||||
length = sizeof(usb_device_descriptor_t);
|
||||
txn->ops->copyto(txn, dwc_rh_descriptor, length, 0);
|
||||
complete_request(req, NO_ERROR, length);
|
||||
} else if (desc_type == USB_DT_CONFIG && index == 0) {
|
||||
usb_configuration_descriptor_t* config_desc =
|
||||
(usb_configuration_descriptor_t*)dwc_rh_config_descriptor;
|
||||
uint16_t desc_length = le16toh(config_desc->wTotalLength);
|
||||
if (length > desc_length)
|
||||
length = desc_length;
|
||||
txn->ops->copyto(txn, dwc_rh_config_descriptor, length, 0);
|
||||
complete_request(req, NO_ERROR, length);
|
||||
} else if (value >> 8 == USB_DT_STRING) {
|
||||
uint8_t string_index = value & 0xFF;
|
||||
if (string_index < countof(dwc_rh_string_table)) {
|
||||
const uint8_t* string = dwc_rh_string_table[string_index];
|
||||
if (length > string[0])
|
||||
length = string[0];
|
||||
|
||||
txn->ops->copyto(txn, string, length, 0);
|
||||
complete_request(req, NO_ERROR, length);
|
||||
} else {
|
||||
complete_request(req, ERR_NOT_SUPPORTED, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void dwc_process_root_hub_std_req(usb_dwc_transfer_request_t* req) {
|
||||
iotxn_t* txn = req->txn;
|
||||
usb_protocol_data_t* data = iotxn_pdata(txn, usb_protocol_data_t);
|
||||
usb_setup_t* setup = &data->setup;
|
||||
|
||||
uint8_t request = setup->bRequest;
|
||||
|
||||
if (request == USB_REQ_SET_ADDRESS) {
|
||||
complete_request(req, NO_ERROR, 0);
|
||||
} else if (request == USB_REQ_GET_DESCRIPTOR) {
|
||||
dwc_root_hub_get_descriptor(req);
|
||||
} else if (request == USB_REQ_SET_CONFIGURATION) {
|
||||
complete_request(req, NO_ERROR, 0);
|
||||
} else {
|
||||
complete_request(req, ERR_NOT_SUPPORTED, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void dwc_process_root_hub_class_req(usb_dwc_transfer_request_t* req) {
|
||||
iotxn_t* txn = req->txn;
|
||||
usb_protocol_data_t* data = iotxn_pdata(txn, usb_protocol_data_t);
|
||||
usb_setup_t* setup = &data->setup;
|
||||
|
||||
uint8_t request = setup->bRequest;
|
||||
uint16_t value = le16toh(setup->wValue);
|
||||
uint16_t index = le16toh(setup->wIndex);
|
||||
uint16_t length = le16toh(setup->wLength);
|
||||
|
||||
if (request == USB_REQ_GET_DESCRIPTOR) {
|
||||
if (value == USB_HUB_DESC_TYPE << 8 && index == 0) {
|
||||
usb_hub_descriptor_t desc;
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
desc.bDescLength = sizeof(desc);
|
||||
desc.bDescriptorType = value >> 8;
|
||||
desc.bNbrPorts = 1;
|
||||
desc.bPowerOn2PwrGood = 0;
|
||||
|
||||
if (length > sizeof(desc))
|
||||
length = sizeof(desc);
|
||||
txn->ops->copyto(txn, &desc, length, 0);
|
||||
complete_request(req, NO_ERROR, length);
|
||||
return;
|
||||
}
|
||||
} else if (request == USB_REQ_SET_FEATURE) {
|
||||
mx_status_t res = dwc_host_port_set_feature(value);
|
||||
complete_request(req, res, 0);
|
||||
} else if (request == USB_REQ_CLEAR_FEATURE) {
|
||||
mtx_lock(&rh_status_mtx);
|
||||
uint16_t* change_bits = &(root_port_status.wPortChange);
|
||||
switch (value) {
|
||||
case USB_FEATURE_C_PORT_CONNECTION:
|
||||
*change_bits &= ~USB_PORT_CONNECTION;
|
||||
break;
|
||||
case USB_FEATURE_C_PORT_ENABLE:
|
||||
*change_bits &= ~USB_PORT_ENABLE;
|
||||
break;
|
||||
case USB_FEATURE_C_PORT_SUSPEND:
|
||||
*change_bits &= ~USB_PORT_SUSPEND;
|
||||
break;
|
||||
case USB_FEATURE_C_PORT_OVER_CURRENT:
|
||||
*change_bits &= ~USB_PORT_OVER_CURRENT;
|
||||
break;
|
||||
case USB_FEATURE_C_PORT_RESET:
|
||||
*change_bits &= ~USB_PORT_RESET;
|
||||
break;
|
||||
}
|
||||
mtx_unlock(&rh_status_mtx);
|
||||
complete_request(req, NO_ERROR, 0);
|
||||
} else if (request == USB_REQ_GET_STATUS) {
|
||||
size_t length = txn->length;
|
||||
if (length > sizeof(root_port_status)) {
|
||||
length = sizeof(root_port_status);
|
||||
}
|
||||
|
||||
mtx_lock(&rh_status_mtx);
|
||||
txn->ops->copyto(txn, &root_port_status, length, 0);
|
||||
mtx_unlock(&rh_status_mtx);
|
||||
|
||||
complete_request(req, NO_ERROR, length);
|
||||
} else {
|
||||
complete_request(req, ERR_NOT_SUPPORTED, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void dwc_process_root_hub_ctrl_req(usb_dwc_transfer_request_t* req) {
|
||||
iotxn_t* txn = req->txn;
|
||||
usb_protocol_data_t* data = iotxn_pdata(txn, usb_protocol_data_t);
|
||||
usb_setup_t* setup = &data->setup;
|
||||
|
||||
if ((setup->bmRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
|
||||
dwc_process_root_hub_std_req(req);
|
||||
} else if ((setup->bmRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) {
|
||||
dwc_process_root_hub_class_req(req);
|
||||
} else {
|
||||
// Some unknown request type?
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void dwc_process_root_hub_request(usb_dwc_t* dwc,
|
||||
usb_dwc_transfer_request_t* req) {
|
||||
assert(req);
|
||||
|
||||
if (is_control_request(req)) {
|
||||
dwc_process_root_hub_ctrl_req(req);
|
||||
} else {
|
||||
mtx_lock(&rh_status_mtx);
|
||||
rh_intr_req = req;
|
||||
mtx_unlock(&rh_status_mtx);
|
||||
|
||||
dwc_complete_root_port_status_txn();
|
||||
}
|
||||
}
|
||||
|
||||
// Thread to handle queues transactions on the root hub.
|
||||
static int dwc_root_hub_txn_worker(void* arg) {
|
||||
usb_dwc_t* dwc = (usb_dwc_t*)arg;
|
||||
|
||||
dwc->rh_txn_completion = COMPLETION_INIT;
|
||||
|
||||
while (true) {
|
||||
completion_wait(&dwc->rh_txn_completion, MX_TIME_INFINITE);
|
||||
|
||||
mtx_lock(&dwc->rh_txn_mtx);
|
||||
|
||||
usb_dwc_transfer_request_t* req =
|
||||
list_remove_head_type(&dwc->rh_txn_head,
|
||||
usb_dwc_transfer_request_t, node);
|
||||
|
||||
if (list_is_empty(&dwc->rh_txn_head)) {
|
||||
completion_reset(&dwc->rh_txn_completion);
|
||||
}
|
||||
|
||||
mtx_unlock(&dwc->rh_txn_mtx);
|
||||
|
||||
dwc_process_root_hub_request(dwc, req);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Bind is the entry point for this driver.
|
||||
static mx_status_t usb_dwc_bind(mx_driver_t* drv, mx_device_t* dev) {
|
||||
xprintf("usb_dwc_bind drv = %p, dev = %p\n", drv, dev);
|
||||
|
||||
usb_dwc_t* usb_dwc = NULL;
|
||||
mx_handle_t irq_handle = MX_HANDLE_INVALID;
|
||||
mx_status_t st = ERR_INTERNAL;
|
||||
|
||||
// Allocate a new device object for the bus.
|
||||
usb_dwc = calloc(1, sizeof(*usb_dwc));
|
||||
if (!usb_dwc) {
|
||||
xprintf("usb_dwc_bind failed to allocated usb_dwc struct.\n");
|
||||
return ERR_NO_MEMORY;
|
||||
}
|
||||
|
||||
// Carve out some address space for this device.
|
||||
st = mx_mmap_device_memory(
|
||||
get_root_resource(), USB_PAGE_START, (uint32_t)USB_PAGE_SIZE,
|
||||
MX_CACHE_POLICY_UNCACHED_DEVICE, (void*)(®s));
|
||||
if (st != NO_ERROR) {
|
||||
xprintf("usb_dwc_bind failed to mx_mmap_device_memory.\n");
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
// Create an IRQ Handle for this device.
|
||||
irq_handle = mx_interrupt_create(get_root_resource(), INTERRUPT_VC_USB,
|
||||
MX_FLAG_REMAP_IRQ);
|
||||
if (irq_handle < 0) {
|
||||
xprintf("usb_dwc_bind failed to map usb irq.\n");
|
||||
st = ERR_NO_RESOURCES;
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
usb_dwc->irq_handle = irq_handle;
|
||||
usb_dwc->parent = dev;
|
||||
list_initialize(&usb_dwc->rh_txn_head);
|
||||
|
||||
// TODO(gkalsi):
|
||||
// The BCM Mailbox Driver currently turns on USB power but it should be
|
||||
// done here instead.
|
||||
|
||||
if ((st = usb_dwc_softreset_core()) != NO_ERROR) {
|
||||
xprintf("usb_dwc_bind failed to reset core.\n");
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
if ((st = usb_dwc_setupcontroller()) != NO_ERROR) {
|
||||
xprintf("usb_dwc_bind failed setup controller.\n");
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
device_init(&usb_dwc->device, drv, "bcm-usb-dwc", &dwc_device_proto);
|
||||
|
||||
usb_dwc->device.protocol_id = MX_PROTOCOL_USB_HCI;
|
||||
usb_dwc->device.protocol_ops = &dwc_hci_protocol;
|
||||
|
||||
// Thread that responds to requests for the root hub.
|
||||
thrd_t root_hub_txn_worker;
|
||||
thrd_create_with_name(&root_hub_txn_worker, dwc_root_hub_txn_worker, usb_dwc, "dwc_root_hub_txn_worker");
|
||||
thrd_detach(root_hub_txn_worker);
|
||||
|
||||
thrd_t irq_thread;
|
||||
thrd_create_with_name(&irq_thread, dwc_irq_thread, usb_dwc, "dwc_irq_thread");
|
||||
thrd_detach(irq_thread);
|
||||
|
||||
xprintf("usb_dwc_bind success!\n");
|
||||
return NO_ERROR;
|
||||
error_return:
|
||||
if (usb_dwc)
|
||||
free(usb_dwc);
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
mx_driver_t _driver_usb_dwc = {
|
||||
.ops = {
|
||||
.bind = usb_dwc_bind,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// The formatter does not play nice with these macros.
|
||||
// clang-format off
|
||||
MAGENTA_DRIVER_BEGIN(_driver_usb_dwc, "bcm-usb-dwc", "magenta", "0.1", 3)
|
||||
BI_ABORT_IF(NE, BIND_SOC_VID, SOC_VID_BROADCOMM),
|
||||
BI_MATCH_IF(EQ, BIND_SOC_DID, SOC_DID_BROADCOMM_MAILBOX),
|
||||
MAGENTA_DRIVER_END(_driver_usb_dwc)
|
||||
// clang-format on
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright 2016 The Fuchsia Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
LOCAL_DIR := $(GET_LOCAL_DIR)
|
||||
|
||||
MODULE := $(LOCAL_DIR)
|
||||
|
||||
MODULE_TYPE := driver
|
||||
|
||||
MODULE_SRCS += \
|
||||
$(LOCAL_DIR)/bcm-usb-dwc.c
|
||||
|
||||
MODULE_STATIC_LIBS := ulib/ddk ulib/hexdump udev/bcm-usb-dwc-regs
|
||||
|
||||
MODULE_LIBS := ulib/driver ulib/musl ulib/magenta ulib/mxio
|
||||
|
||||
include make/module.mk
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
* Copyright (c) 2008, Douglas Comer and Dennis Brylow
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted for use in any lawful way, provided that
|
||||
* the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the names of the authors nor their contributors may be
|
||||
* used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS AND CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,879 @@
|
||||
/**
|
||||
* @file usb_dwc_regs.h
|
||||
*
|
||||
* Registers of the DesignWare Hi-Speed USB 2.0 On-The-Go Controller.
|
||||
*/
|
||||
/* Embedded Xinu, Copyright (C) 2013. All rights reserved. */
|
||||
|
||||
#ifndef _USB_DWC_REGS_H_
|
||||
#define _USB_DWC_REGS_H_
|
||||
|
||||
/**
|
||||
* Number of DWC host channels, each of which can be used for an independent
|
||||
* USB transfer. On the BCM2835 (Raspberry Pi), 8 are available. This is
|
||||
* documented on page 201 of the BCM2835 ARM Peripherals document.
|
||||
*/
|
||||
#define DWC_NUM_CHANNELS 8
|
||||
|
||||
/**
|
||||
* Layout of the registers of the DesignWare Hi-Speed USB 2.0 On-The-Go
|
||||
* Controller. There is no official documentation for these; however, the
|
||||
* register locations (and to some extent the meanings) can be found in other
|
||||
* code, such as the Linux driver for this hardware that Synopsys contributed.
|
||||
*
|
||||
* We do not explicitly define every bit in the registers because the majority
|
||||
* are not used by our driver and would complicate this file. For example, we
|
||||
* do not attempt to document any features that are specific to suspend,
|
||||
* hibernation, the OTG protocol, or to the core acting in device mode rather
|
||||
* than host mode.
|
||||
*
|
||||
* The bits and fields we do use in our driver we have tried to completely
|
||||
* document based on our understanding of what they do. We cannot guarantee
|
||||
* that all the information is correct, as we do not have access to any official
|
||||
* documentation.
|
||||
*/
|
||||
struct dwc_regs {
|
||||
|
||||
/* 0x000 : OTG Control */
|
||||
uint32_t otg_control;
|
||||
|
||||
/* 0x004 : OTG Interrupt */
|
||||
uint32_t otg_interrupt;
|
||||
|
||||
/**
|
||||
* 0x008 : AHB Configuration Register.
|
||||
*
|
||||
* This register configures some of the interactions the DWC has with the
|
||||
* rest of the system. */
|
||||
uint32_t ahb_configuration;
|
||||
|
||||
/** Enable interrupts from the USB controller. Disabled by default. */
|
||||
#define DWC_AHB_INTERRUPT_ENABLE (1 << 0)
|
||||
|
||||
/**
|
||||
* Bits [4:1] of the AHB Configuration register were redefined by Broadcom for
|
||||
* the BCM2835; hence this flag is only valid on the BCM2835.
|
||||
*
|
||||
* This bit is documented as:
|
||||
*
|
||||
* 1 = Wait for all outstanding AXI writes to complete before signalling
|
||||
* (internally) that DMA is done.
|
||||
* 0 = don't wait.
|
||||
*
|
||||
* We set this bit because the Linux driver does, although we did not observe a
|
||||
* difference in behavior.
|
||||
*/
|
||||
#define BCM_DWC_AHB_AXI_WAIT (1 << 4)
|
||||
|
||||
/**
|
||||
* Writing 1 to this bit in the AHB Configuration Register allows the USB
|
||||
* controller to perform DMA (Direct Memory Access). Disabled by default.
|
||||
*/
|
||||
#define DWC_AHB_DMA_ENABLE (1 << 5)
|
||||
|
||||
/* 0x01c : Core USB configuration */
|
||||
uint32_t core_usb_configuration;
|
||||
|
||||
|
||||
/**
|
||||
* 0x010 : Core Reset Register.
|
||||
*
|
||||
* Software can use this register to cause the DWC to reset itself.
|
||||
*/
|
||||
uint32_t core_reset;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
#define DWC_AHB_MASTER_IDLE (1 << 31)
|
||||
|
||||
/**
|
||||
* Write 1 to this location in the Core Reset Register to start a soft
|
||||
* reset. This bit will then be cleared by the hardware when the reset is
|
||||
* complete.
|
||||
*/
|
||||
#define DWC_SOFT_RESET (1 << 0)
|
||||
|
||||
/**
|
||||
* 0x014 : Core Interrupt Register.
|
||||
*
|
||||
* This register contains the state of pending top-level DWC interrupts. 1
|
||||
* means interrupt pending while 0 means no interrupt pending.
|
||||
*
|
||||
* Note that at least for port_intr and host_channel_intr, software must
|
||||
* clear the interrupt somewhere else rather than by writing to this
|
||||
* register.
|
||||
*/
|
||||
union dwc_core_interrupts {
|
||||
uint32_t val;
|
||||
struct {
|
||||
uint32_t stuff : 3;
|
||||
|
||||
/**
|
||||
* Start of Frame. TODO
|
||||
*/
|
||||
uint32_t sof_intr : 1;
|
||||
|
||||
uint32_t morestuff : 20;
|
||||
|
||||
/**
|
||||
* Host port status changed. Software must examine the Host Port
|
||||
* Control and Status Register to determine the current status of
|
||||
* the host port and clear any flags in it that indicate a status
|
||||
* change.
|
||||
*/
|
||||
uint32_t port_intr : 1; /* Bit 24 */
|
||||
|
||||
/**
|
||||
* Channel interrupt occurred. Software must examine the Host All
|
||||
* Channels Interrupt Register to determine which channel(s) have
|
||||
* pending interrupts, then handle and clear the interrupts for
|
||||
* these channels.
|
||||
*/
|
||||
uint32_t host_channel_intr : 1; /* Bit 25 */
|
||||
|
||||
uint32_t evenmorestuff : 6;
|
||||
};
|
||||
} core_interrupts;
|
||||
|
||||
/**
|
||||
* 0x018 : Core Interrupt Mask Register.
|
||||
*
|
||||
* This register has the same format as the Core Interrupt Register and
|
||||
* configures whether the corresponding interrupt is enabled (1) or disabled
|
||||
* (0). Initial state after reset is all 0's.
|
||||
*/
|
||||
union dwc_core_interrupts core_interrupt_mask;
|
||||
|
||||
/* 0x01c : Receive Status Queue Read */
|
||||
uint32_t receive_status;
|
||||
|
||||
/* 0x020 : Receive Status Queue Read & Pop */
|
||||
uint32_t receive_status_pop;
|
||||
|
||||
/**
|
||||
* 0x024 : Receive FIFO Size Register.
|
||||
*
|
||||
* This register contains the size of the Receive FIFO, in 4-byte words.
|
||||
*
|
||||
* This register must be set by software before using the controller; see
|
||||
* the note in the documentation for the hwcfg3 register about configuring
|
||||
* the dynamic FIFOs.
|
||||
*/
|
||||
uint32_t rx_fifo_size;
|
||||
|
||||
/**
|
||||
* 0x028 : Non Periodic Transmit FIFO Size Register.
|
||||
*
|
||||
* The low 16 bits of this register contain the offset of the Nonperiodic
|
||||
* Transmit FIFO, in 4-byte words, from the start of the memory reserved by
|
||||
* the controller for dynamic FIFOs. The high 16 bits of this register
|
||||
* contain its size, in 4-byte words.
|
||||
*
|
||||
* This register must be set by software before using the controller; see
|
||||
* the note in the documentation for the hwcfg3 register about configuring
|
||||
* the dynamic FIFOs.
|
||||
*/
|
||||
uint32_t nonperiodic_tx_fifo_size;
|
||||
|
||||
/* 0x02c : Non Periodic Transmit FIFO/Queue Status Register */
|
||||
uint32_t nonperiodic_tx_fifo_status;
|
||||
|
||||
/* 0x030 */
|
||||
uint32_t i2c_control;
|
||||
|
||||
/* 0x034 */
|
||||
uint32_t phy_vendor_control;
|
||||
|
||||
/* 0x038 */
|
||||
uint32_t gpio;
|
||||
|
||||
/* 0x03c */
|
||||
uint32_t user_id;
|
||||
|
||||
/* 0x040 */
|
||||
uint32_t vendor_id;
|
||||
|
||||
/* 0x044 */
|
||||
uint32_t hwcfg1;
|
||||
|
||||
/* 0x048 */
|
||||
uint32_t hwcfg2;
|
||||
|
||||
/**
|
||||
* 0x04c : Hardware Configuration 3 Register.
|
||||
*
|
||||
* The high 16 bits of this read-only register contain the maximum total
|
||||
* size, in words, of the dynamic FIFOs (Rx, Nonperiodic Tx, and Periodic
|
||||
* Tx). Software must set up these three dynamic FIFOs in the rx_fifo_size,
|
||||
* nonperiodic_tx_fifo_size, and host_periodic_tx_fifo_size registers such
|
||||
* that their total size does not exceed this maximum total size and no
|
||||
* FIFOs overlap.
|
||||
*
|
||||
* Note: Software must explicitly configure the dynamic FIFOs even if the
|
||||
* controller is operating in DMA mode, since the default values for the
|
||||
* FIFO sizes and offsets may be invalid. For example, in Broadcom's
|
||||
* instantiation of this controller for the BCM2835, only 4080 words are
|
||||
* available for dynamic FIFOs, but the dynamic FIFO sizes are set to 4096,
|
||||
* 32, and 0, which are invalid as they add up to more than 4080. <b>IF YOU
|
||||
* DO NOT DO THIS YOU WILL GET SILENT MEMORY CORRUPTION</b>.
|
||||
*
|
||||
* The low 16 bits of this register contain various flags that are not
|
||||
* documented here as we don't use any in our driver.
|
||||
*/
|
||||
uint32_t hwcfg3;
|
||||
|
||||
/* 0x050 */
|
||||
uint32_t hwcfg4;
|
||||
|
||||
/* 0x054 */
|
||||
uint32_t core_lpm_configuration;
|
||||
|
||||
/* 0x058 */
|
||||
uint32_t global_powerDn;
|
||||
|
||||
/* 0x05c */
|
||||
uint32_t global_fifo_config;
|
||||
|
||||
/* 0x060 */
|
||||
uint32_t adp_control;
|
||||
|
||||
/* 0x064 */
|
||||
uint32_t reserved_0x64[39];
|
||||
|
||||
/**
|
||||
* 0x100 : Host Periodic Transmit FIFO Size Register.
|
||||
*
|
||||
* The low 16 bits of this register configure the offset of the Periodic
|
||||
* Transmit FIFO, in 4-byte words, from the start of the memory reserved by
|
||||
* the controller for dynamic FIFOs. The high 16 bits of this register
|
||||
* configure its size, in 4-byte words.
|
||||
*
|
||||
* This register should be set by software before using the controller; see
|
||||
* the note in the documentation for the hwcfg3 register about configuring
|
||||
* the dynamic FIFOs.
|
||||
*/
|
||||
uint32_t host_periodic_tx_fifo_size;
|
||||
|
||||
/* TODO */
|
||||
uint32_t stuff[191];
|
||||
|
||||
/**
|
||||
* @name Host registers
|
||||
*
|
||||
* The registers beginning at this point are considered to be the "Host"
|
||||
* registers. These are used for the "Host" half of the OTG (On-The-Go)
|
||||
* protocol, which allows this hardware to act as either a USB host or a USB
|
||||
* device. This is the only half we are concerned with in this driver and
|
||||
* we do not declare the corresponding Device registers. */
|
||||
/**@{*/
|
||||
|
||||
/* 0x400 */
|
||||
uint32_t host_configuration;
|
||||
|
||||
/* 0x404 */
|
||||
uint32_t host_frame_interval;
|
||||
|
||||
/* 0x408 */
|
||||
uint32_t host_frame_number;
|
||||
|
||||
/* 0x40c */
|
||||
uint32_t host_reserved_0x40c;
|
||||
|
||||
/* 0x410 */
|
||||
uint32_t host_fifo_status;
|
||||
|
||||
/**
|
||||
* 0x414 : Host All Channels Interrupt Register.
|
||||
*
|
||||
* This register contains a bit for each host channel that indicates whether
|
||||
* an interrupt has occurred on that host channel. You cannot clear the
|
||||
* interrupts by writing to this register; use the channel-specific
|
||||
* interrupt registers instead.
|
||||
*/
|
||||
uint32_t host_channels_interrupt;
|
||||
|
||||
/**
|
||||
* 0x418 : Host All Channels Interrupt Mask Register.
|
||||
*
|
||||
* Same format as the Host All Channels Interrupt Register, but a 1 in this
|
||||
* register indicates that the corresponding host channel interrupt is
|
||||
* enabled. Software can change this register. Defaults to all 0's after a
|
||||
* reset.
|
||||
*/
|
||||
uint32_t host_channels_interrupt_mask;
|
||||
|
||||
/* 0x41c */
|
||||
uint32_t host_frame_list;
|
||||
|
||||
/* 0x420 */
|
||||
uint32_t host_reserved_0x420[8];
|
||||
|
||||
/**
|
||||
* 0x440 : Host Port Control and Status Register.
|
||||
*
|
||||
* This register provides the information needed to respond to status
|
||||
* queries about the "host port", which is the port that is logically
|
||||
* attached to the root hub.
|
||||
*
|
||||
* When changing this register, software must read its value, then clear the
|
||||
* enabled, connected_changed, enabled_changed, and overcurrent_changed
|
||||
* members to avoid changing them, as those particular bits are cleared by
|
||||
* writing 1.
|
||||
*/
|
||||
union dwc_host_port_ctrlstatus {
|
||||
uint32_t val;
|
||||
struct {
|
||||
/**
|
||||
* 1: a device is connected to this port.
|
||||
* 0: no device is connected to this port.
|
||||
*
|
||||
* Changed by hardware only.
|
||||
*/
|
||||
uint32_t connected : 1; /* Bit 0 */
|
||||
|
||||
/**
|
||||
* Set by hardware when connected bit changes. Software can write
|
||||
* 1 to acknowledge and clear. The setting of this bit by hardware
|
||||
* generates an interrupt that can be enabled by setting port_intr
|
||||
* in the core_interrupt_mask register.
|
||||
*/
|
||||
uint32_t connected_changed : 1; /* Bit 1 */
|
||||
|
||||
/**
|
||||
* 1: port is enabled.
|
||||
* 0: port is disabled.
|
||||
*
|
||||
* Note: the host port is enabled by default after it is reset.
|
||||
*
|
||||
* Note: Writing 1 here appears to disable the port.
|
||||
*/
|
||||
uint32_t enabled : 1; /* Bit 2 */
|
||||
|
||||
/**
|
||||
* Set by hardware when enabled bit changes. Software can write 1
|
||||
* to acknowledge and clear. The setting of this bit by hardware
|
||||
* generates an interrupt that can be enabled by setting port_intr
|
||||
* in the core_interrupt_mask register.
|
||||
*/
|
||||
uint32_t enabled_changed : 1; /* Bit 3 */
|
||||
|
||||
/**
|
||||
* 1: overcurrent condition active on this port
|
||||
* 0: no overcurrent condition active on this port
|
||||
*
|
||||
* Changed by hardware only.
|
||||
*/
|
||||
uint32_t overcurrent : 1; /* Bit 4 */
|
||||
|
||||
/**
|
||||
* Set by hardware when the overcurrent bit changes. The software
|
||||
* can write 1 to acknowledge and clear. The setting of this bit by
|
||||
* hardware generates the interrupt that can be enabled by setting
|
||||
* port_intr in the core_interrupt_mask register.
|
||||
*/
|
||||
uint32_t overcurrent_changed : 1; /* Bit 5 */
|
||||
|
||||
/**
|
||||
* Set by software to set resume signalling.
|
||||
*/
|
||||
uint32_t resume : 1; /* Bit 6 */
|
||||
|
||||
/**
|
||||
* Set by software to suspend the port.
|
||||
*/
|
||||
uint32_t suspended : 1; /* Bit 7 */
|
||||
|
||||
/**
|
||||
* Software can set this to start a reset on this port. Software
|
||||
* must clear this after waiting 60 milliseconds for the reset is
|
||||
* complete.
|
||||
*/
|
||||
uint32_t reset : 1; /* Bit 8 */
|
||||
|
||||
uint32_t reserved : 1; /* Bit 9 */
|
||||
|
||||
/**
|
||||
* Current logic of data lines (10: logic of D+; 11: logic of D-).
|
||||
*
|
||||
* Changed by hardware only.
|
||||
*/
|
||||
uint32_t line_status : 2; /* Bits 10-11*/
|
||||
|
||||
/**
|
||||
* 1: port is powered.
|
||||
* 0: port is not powered.
|
||||
*
|
||||
* Software can change this bit to power on (1) or power off (0)
|
||||
* the port.
|
||||
*/
|
||||
uint32_t powered : 1; /* Bit 12 */
|
||||
|
||||
uint32_t test_control : 4; /* Bits 13-16 */
|
||||
|
||||
/**
|
||||
* Speed of attached device (if any). This should only be
|
||||
* considered meaningful if the connected bit is set.
|
||||
*
|
||||
* 00: high speed; 01: full speed; 10: low speed
|
||||
*
|
||||
* Changed by hardware only.
|
||||
*/
|
||||
uint32_t speed : 2; /* Bits 17-18 */
|
||||
|
||||
uint32_t reserved2 : 13; /* Bits 19-32 */
|
||||
|
||||
};
|
||||
} host_port_ctrlstatus;
|
||||
|
||||
uint32_t host_reserved_0x444[47];
|
||||
|
||||
/**
|
||||
* 0x500 : Array of host channels. Each host channel can be used to
|
||||
* execute an independent USB transfer or transaction simultaneously. A USB
|
||||
* transfer may consist of multiple transactions, or packets. To avoid
|
||||
* having to re-program the channel, it may be useful to use one channel for
|
||||
* all transactions of a transfer before allowing other transfers to be
|
||||
* scheduled on it.
|
||||
*/
|
||||
struct dwc_host_channel {
|
||||
|
||||
/**
|
||||
* Channel Characteristics Register -
|
||||
*
|
||||
* Contains various fields that must be set to prepare this channel for
|
||||
* a transfer to or from a particular endpoint on a particular USB
|
||||
* device.
|
||||
*
|
||||
* This register only needs to be programmed one time when doing a
|
||||
* transfer, regardless of how many packets it consists of, unless the
|
||||
* channel is re-programmed for a different transfer or the transfer is
|
||||
* moved to a different channel.
|
||||
*/
|
||||
union dwc_host_channel_characteristics {
|
||||
uint32_t val;
|
||||
struct {
|
||||
/**
|
||||
* Maximum packet size the endpoint is capable of sending or
|
||||
* receiving. Must be programmed by software before starting
|
||||
* the transfer.
|
||||
*/
|
||||
uint32_t max_packet_size : 11; /* Bits 0-10 */
|
||||
|
||||
/**
|
||||
* Endpoint number (low 4 bits of bEndpointAddress). Must be
|
||||
* programmed by software before starting the transfer.
|
||||
*/
|
||||
uint32_t endpoint_number : 4; /* Bits 11-14 */
|
||||
|
||||
/**
|
||||
* Endpoint direction (high bit of bEndpointAddress). Must be
|
||||
* programmed by software before starting the transfer.
|
||||
*/
|
||||
uint32_t endpoint_direction : 1; /* Bit 15 */
|
||||
|
||||
uint32_t reserved : 1; /* Bit 16 */
|
||||
|
||||
/**
|
||||
* 1 when the device being communicated with is attached at low
|
||||
* speed; 0 otherwise. Must be programmed by software before
|
||||
* starting the transfer.
|
||||
*/
|
||||
uint32_t low_speed : 1; /* Bit 17 */
|
||||
|
||||
/**
|
||||
* Endpoint type (low 2 bits of bmAttributes). Must be
|
||||
* programmed by software before starting the transfer.
|
||||
*/
|
||||
uint32_t endpoint_type : 2; /* Bits 18-19 */
|
||||
|
||||
/**
|
||||
* Maximum number of transactions that can be executed per
|
||||
* microframe as part of this transfer. Normally 1, but should
|
||||
* be set to 1 + (bits 11 and 12 of wMaxPacketSize) for
|
||||
* high-speed interrupt and isochronous endpoints. Must be
|
||||
* programmed by software before starting the transfer.
|
||||
*/
|
||||
uint32_t packets_per_frame : 2; /* Bits 20-21 */
|
||||
|
||||
/**
|
||||
* USB device address of the device on which the endpoint is
|
||||
* located. Must be programmed by software before starting the
|
||||
* transfer.
|
||||
*/
|
||||
uint32_t device_address : 7; /* Bits 22-28 */
|
||||
|
||||
/**
|
||||
* Just before enabling the channel (for all transactions),
|
||||
* software needs to set this to the opposite of the low bit of
|
||||
* the host_frame_number register. Otherwise the hardware will
|
||||
* issue frame overrun errors on some transactions. TODO: what
|
||||
* exactly does this do?
|
||||
*/
|
||||
uint32_t odd_frame : 1; /* Bit 29 */
|
||||
|
||||
/**
|
||||
* Software can set this to 1 to halt the channel. Not needed
|
||||
* during normal operation as the channel halts automatically
|
||||
* when a transaction completes or an error occurs.
|
||||
*/
|
||||
uint32_t channel_disable : 1; /* Bit 30 */
|
||||
|
||||
/**
|
||||
* Software can set this to 1 to enable the channel, thereby
|
||||
* actually starting the transaction on the USB. This must only
|
||||
* be done after the characteristics, split_control, and
|
||||
* transfer registers, and possibly other registers (depending
|
||||
* on the transfer) have been programmed.
|
||||
*/
|
||||
uint32_t channel_enable : 1; /* Bit 31 */
|
||||
};
|
||||
} characteristics;
|
||||
|
||||
/**
|
||||
* Channel Split Control Register -
|
||||
*
|
||||
* This register is used to set up Split Transactions for communicating
|
||||
* with low or full-speed devices attached to a high-speed hub. When
|
||||
* doing so, set split_enable to 1 and the other fields as documented.
|
||||
* Otherwise, software must clear this register before starting the
|
||||
* transfer.
|
||||
*
|
||||
* Like the Channel Characteristics register, this register only
|
||||
* needs to be programmed one time if the channel is enabled multiple
|
||||
* times to send all the packets of a single transfer.
|
||||
*/
|
||||
union dwc_host_channel_split_control {
|
||||
uint32_t val;
|
||||
struct {
|
||||
/**
|
||||
* 0-based index of the port on the high-speed hub on which the
|
||||
* low or full-speed device is attached.
|
||||
*/
|
||||
uint32_t port_address : 7; /* Bits 0-6 */
|
||||
|
||||
/**
|
||||
* USB device address of the high-speed hub that acts as the
|
||||
* Transaction Translator for this low or full-speed device.
|
||||
* This is not necessarily the hub the device is physically
|
||||
* connected to, since that could be a full-speed or low-speed
|
||||
* hub. Instead, software must walk up the USB device tree
|
||||
* (towards the root hub) until a high-speed hub is found and
|
||||
* use its device address here.
|
||||
*/
|
||||
uint32_t hub_address : 7; /* Bits 7-13 */
|
||||
|
||||
/**
|
||||
* TODO: what exactly does this do?
|
||||
*/
|
||||
uint32_t transaction_position : 2; /* Bits 14-15 */
|
||||
|
||||
/**
|
||||
* 0: Do a Start Split transaction
|
||||
* 1: Do a Complete Split transaction.
|
||||
*
|
||||
* When split transactions are enabled, this must be programmed
|
||||
* by software before enabling the channel. Note that you must
|
||||
* begin with a Start Split transaction and alternate this bit
|
||||
* for each transaction until the transfer is complete.
|
||||
*/
|
||||
uint32_t complete_split : 1; /* Bit 16 */
|
||||
|
||||
uint32_t reserved : 14; /* Bits 17-30 */
|
||||
|
||||
/**
|
||||
* Set to 1 to enable Split Transactions.
|
||||
*/
|
||||
uint32_t split_enable : 1; /* Bit 31 */
|
||||
};
|
||||
} split_control;
|
||||
|
||||
/**
|
||||
* Channel Interrupts Register -
|
||||
*
|
||||
* Bitmask of status conditions that have occurred on this channel.
|
||||
*
|
||||
* These bits can be used with or without "real" interrupts. To have
|
||||
* the CPU get a real interrupt when one of these bits gets set, set the
|
||||
* appropriate bit in the interrupt_mask, and also ensure that
|
||||
* interrupts from the channel are enabled in the
|
||||
* host_channels_interrupt_mask register, channel interrupts overall are
|
||||
* enabled in the core_interrupt_mask register, and interrupts from the
|
||||
* DWC hardware overall are enabled in the ahb_configuration register
|
||||
* and by any system-specific interrupt controller.
|
||||
*/
|
||||
union dwc_host_channel_interrupts {
|
||||
uint32_t val;
|
||||
struct {
|
||||
/**
|
||||
* The requested USB transfer has successfully completed.
|
||||
*
|
||||
* Exceptions and caveats:
|
||||
*
|
||||
* - When doing split transactions, this bit will be set after a
|
||||
* Complete Split transaction has finished, even though the
|
||||
* overall transfer may not actually be complete.
|
||||
*
|
||||
* - The transfer will only be complete up to the extent that
|
||||
* data was programmed into the channel. For example, control
|
||||
* transfers have 3 phases, each of which must be programmed
|
||||
* into the channel separately. This flag will be set after
|
||||
* each of these phases has successfully completed.
|
||||
*
|
||||
* - An OUT transfer is otherwise considered complete when
|
||||
* exactly the requested number of bytes of data have been
|
||||
* successfully transferred, while an IN transfer is otherwise
|
||||
* considered complete when exactly the requested number of
|
||||
* bytes of data have been successfully transferred or a
|
||||
* shorter-than-expected packet was received.
|
||||
*/
|
||||
uint32_t transfer_completed : 1; /* Bit 0 */
|
||||
|
||||
/**
|
||||
* The channel has halted. After this bit has been set, the
|
||||
* channel sits idle and nothing else will happen until software
|
||||
* takes action.
|
||||
*
|
||||
* Channels may halt for several reasons. From our experience
|
||||
* these cover all possible situations in which software needs
|
||||
* to take action, so this is the only channel interrupt that
|
||||
* actually needs to be enabled. At least in DMA mode, the
|
||||
* controller to some extent will act autonomously to complete
|
||||
* transfers and only issue this interrupt when software needs
|
||||
* to take action.
|
||||
*
|
||||
* Situations in which a channel will halt include but probably
|
||||
* are not limited to:
|
||||
*
|
||||
* - The transfer has completed, thereby setting the
|
||||
* transfer_completed flag as documented above.
|
||||
*
|
||||
* - A Start Split or Complete Split transaction has finished.
|
||||
*
|
||||
* - The hub sent a NYET packet when trying to execute a
|
||||
* Complete Split transaction, thereby signalling that the
|
||||
* Split transaction is not yet complete.
|
||||
*
|
||||
* - The device sent a NAK packet, thereby signalling it had no
|
||||
* data to send at the time, when trying to execute an IN
|
||||
* interrupt transfer.
|
||||
*
|
||||
* - One of several errors has occurred, such as an AHB error,
|
||||
* data toggle error, tranasction error, stall condition, or
|
||||
* frame overrun error.
|
||||
*/
|
||||
uint32_t channel_halted : 1; /* Bit 1 */
|
||||
|
||||
/**
|
||||
* An error occurred on the ARM Advanced High-Performance Bus
|
||||
* (AHB).
|
||||
*/
|
||||
uint32_t ahb_error : 1; /* Bit 2 */
|
||||
|
||||
/**
|
||||
* The device issued a STALL handshake packet (endpoint is
|
||||
* halted or control pipe request is not supported).
|
||||
*/
|
||||
uint32_t stall_response_received : 1; /* Bit 3 */
|
||||
|
||||
/**
|
||||
* The device issued a NAK handshake packet (receiving device
|
||||
* cannot accept data or transmitting device cannot send data).
|
||||
*
|
||||
* The channel will halt with this bit set when performing an IN
|
||||
* transfer from an interrupt endpoint that has no data to send.
|
||||
* As this requires software intervention to restart the
|
||||
* channel, this means that polling of interrupt endpoints (e.g.
|
||||
* on hubs and HID devices) must be done in software, even if
|
||||
* the actual transactions themselves are interrupt-driven.
|
||||
*/
|
||||
uint32_t nak_response_received : 1; /* Bit 4 */
|
||||
|
||||
/**
|
||||
* The device issued an ACK handshake packet (receiving device
|
||||
* acknowledged error-free packet).
|
||||
*/
|
||||
uint32_t ack_response_received : 1; /* Bit 5 */
|
||||
|
||||
/**
|
||||
* The device issued a NYET handshake packet.
|
||||
*/
|
||||
uint32_t nyet_response_received : 1; /* Bit 6 */
|
||||
|
||||
/**
|
||||
* From our experience this seems to usually indicate that
|
||||
* software programmed the channel incorrectly.
|
||||
*/
|
||||
uint32_t transaction_error : 1; /* Bit 7 */
|
||||
|
||||
/**
|
||||
* Unexpected bus activity occurred.
|
||||
*/
|
||||
uint32_t babble_error : 1; /* Bit 8 */
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
uint32_t frame_overrun : 1; /* Bit 9 */
|
||||
|
||||
/**
|
||||
* When issuing a series of DATA transactions to an endpoint,
|
||||
* the correct DATA0 or DATA1 packet ID was not specified in the
|
||||
* packet_id member of the transfer register.
|
||||
*/
|
||||
uint32_t data_toggle_error : 1; /* Bit 10 */
|
||||
|
||||
uint32_t buffer_not_available : 1; /* Bit 11 */
|
||||
uint32_t excess_transaction_error : 1; /* Bit 12 */
|
||||
uint32_t frame_list_rollover : 1; /* Bit 13 */
|
||||
uint32_t reserved : 18; /* Bits 14-31 */
|
||||
};
|
||||
} interrupts;
|
||||
|
||||
/**
|
||||
* Channel Interrupts Mask Register -
|
||||
*
|
||||
* This has the same format as the Channel Interrupts Register, but
|
||||
* software uses this to enable (1) or disable (0) the corresponding
|
||||
* interrupt. Defaults to all 0's after a reset.
|
||||
*/
|
||||
union dwc_host_channel_interrupts interrupt_mask;
|
||||
|
||||
/**
|
||||
* Channel Transfer Register:
|
||||
*
|
||||
* Used to store additional information about the transfer. This must
|
||||
* be programmed before beginning the transfer.
|
||||
*/
|
||||
union dwc_host_channel_transfer {
|
||||
uint32_t val;
|
||||
struct {
|
||||
/**
|
||||
* Size of the data to send or receive, in bytes. Software must
|
||||
* program this before beginning the transfer. This can be
|
||||
* greater than the maximum packet length.
|
||||
*
|
||||
* For IN transfers, the hardware decrements this field for each
|
||||
* packet received by the number of bytes received. For split
|
||||
* transactions, the decrement happens after the Complete Split
|
||||
* rather than the Start Split. Software can subtract this
|
||||
* field from the original transfer size in order to determine
|
||||
* the number of bytes received at any given point, including
|
||||
* when the transfer has encountered an error or has completed
|
||||
* with either the full size or a short size.
|
||||
*
|
||||
* For OUT transfers, the hardware does not update this field as
|
||||
* expected. It will not be decremented when data is
|
||||
* transmitted, at least not in every case; hence, software
|
||||
* cannot rely on its value to indicate how many bytes of data
|
||||
* have been transmitted so far. Instead, software must inspect
|
||||
* the packet_count field and assume that all data was
|
||||
* transmitted if packet_count is 0, or that the amount of data
|
||||
* transmitted is equal to the endpoint's maximum packet size
|
||||
* times [the original packet count minus packet_count] if
|
||||
* packet_count is nonzero.
|
||||
*/
|
||||
uint32_t size : 19; /* Bits 0-18 */
|
||||
|
||||
/**
|
||||
* Number of packets left to transmit or maximum number of
|
||||
* packets left to receive. Software must program this before
|
||||
* beginning the transfer. The packet count is calculated as
|
||||
* the size divided by the maximum packet size, rounded up to
|
||||
* the nearest whole packet. As a special case, if the transfer
|
||||
* size is 0 bytes, the packet count must be set to 1.
|
||||
*
|
||||
* The hardware will decrement this register when a packet is
|
||||
* successfully sent or received. In the case of split
|
||||
* transactions, this happens after the Complete Split rather
|
||||
* than after the Start Split. If the final received packet of
|
||||
* an IN transfer is short, it is still counted.
|
||||
*/
|
||||
uint32_t packet_count : 10; /* Bits 19-28 */
|
||||
|
||||
/**
|
||||
* High 2 bits of the Packet ID used in the USB protocol.
|
||||
*
|
||||
* When performing the SETUP phase of a control transfer,
|
||||
* specify 0x3 here to generate the needed SETUP token.
|
||||
*
|
||||
* When performing the DATA phase of a control transfer,
|
||||
* initially specify 0x2 here to begin the DATA packets with the
|
||||
* needed DATA1 Packet ID.
|
||||
*
|
||||
* When performing the STATUS phase of a control transfer,
|
||||
* specify 0x2 here to generate the neeed DATA1 Packet ID.
|
||||
*
|
||||
* When starting a bulk, isochronous, or interrupt transfer,
|
||||
* specify 0x0 here to generate the needed DATA0 Packet ID.
|
||||
*
|
||||
* In the case of a transfer consisting of multiple DATA
|
||||
* packets, the hardware will update this field with the Packet
|
||||
* ID to use for the next packet. This field therefore only
|
||||
* needs to be re-programmed if the transfer is moved to a
|
||||
* different channel or the channel is re-used before the
|
||||
* transfer is complete. When doing so, software must save this
|
||||
* field so that it can be re-programmed correctly.
|
||||
*/
|
||||
uint32_t packet_id : 2; /* Bits 29-30 */
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
uint32_t do_ping : 1; /* Bit 31 */
|
||||
};
|
||||
} transfer;
|
||||
|
||||
/**
|
||||
* Channel DMA Address Register -
|
||||
*
|
||||
* Word-aligned address at which the hardware will read or write data
|
||||
* using Direct Memory Access. This must be programmed before beginning
|
||||
* the transfer, unless the size of the data to send or receive is 0.
|
||||
* The hardware will increment this address by the number of bytes
|
||||
* successfully received or sent, which will correspond to the size
|
||||
* decrease in transfer.size.
|
||||
*
|
||||
* Note: DMA must be enabled in the AHB Configuration Register before
|
||||
* this register can be used. Otherwise, the hardware is considered to
|
||||
* be in Slave mode and must be controlled a different way, which we do
|
||||
* not use in our driver and do not attempt to document.
|
||||
*
|
||||
* BCM2835-specific note: Theoretically, addresses written to this
|
||||
* register must be bus addresses, not ARM physical addresses. However,
|
||||
* in our experience the behavior is the same when simply using ARM
|
||||
* physical addresses.
|
||||
*/
|
||||
uint32_t dma_address;
|
||||
|
||||
uint32_t reserved_1;
|
||||
uint32_t reserved_2;
|
||||
} host_channels[DWC_NUM_CHANNELS];
|
||||
|
||||
uint32_t host_reserved_after_channels[(0x800 - 0x500 -
|
||||
(DWC_NUM_CHANNELS * sizeof(struct dwc_host_channel))) /
|
||||
sizeof(uint32_t)];
|
||||
|
||||
/**@}*/
|
||||
|
||||
/* 0x800 */
|
||||
|
||||
uint32_t reserved_0x800[(0xe00 - 0x800) / sizeof(uint32_t)];
|
||||
|
||||
/* 0xe00 : Power and Clock Gating Control Register */
|
||||
uint32_t power;
|
||||
};
|
||||
|
||||
/* Make sure the registers are declared correctly. This is dummy code that will
|
||||
* be compiled into nothing. */
|
||||
// static inline void _dwc_check_regs(void)
|
||||
// {
|
||||
// STATIC_ASSERT(offsetof(struct dwc_regs, vendor_id) == 0x40);
|
||||
// STATIC_ASSERT(offsetof(struct dwc_regs, host_periodic_tx_fifo_size) == 0x100);
|
||||
// STATIC_ASSERT(offsetof(struct dwc_regs, host_configuration) == 0x400);
|
||||
// STATIC_ASSERT(offsetof(struct dwc_regs, host_port_ctrlstatus) == 0x440);
|
||||
// STATIC_ASSERT(offsetof(struct dwc_regs, reserved_0x800) == 0x800);
|
||||
// STATIC_ASSERT(offsetof(struct dwc_regs, power) == 0xe00);
|
||||
// }
|
||||
|
||||
#endif /* _USB_DWC_REGS_H_ */
|
||||
@@ -0,0 +1,9 @@
|
||||
LOCAL_DIR := $(GET_LOCAL_DIR)
|
||||
|
||||
MODULE := $(LOCAL_DIR)
|
||||
|
||||
MODULE_TYPE := userlib
|
||||
|
||||
MODULE_CFLAGS += -I$(LOCAL_DIR)/include/bcm28xx
|
||||
|
||||
include make/module.mk
|
||||
Referência em uma Nova Issue
Bloquear um usuário