| /* |
| * This file is part of the libpayload project. |
| * |
| * Copyright (C) 2013 secunet Security Networks AG |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR 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. |
| */ |
| |
| //#define USB_DEBUG |
| |
| #include <usb/usb.h> |
| #include "generic_hub.h" |
| #include "xhci_private.h" |
| #include "xhci.h" |
| |
| static int |
| xhci_rh_hub_status_changed(usbdev_t *const dev) |
| { |
| xhci_t *const xhci = XHCI_INST(dev->controller); |
| const int changed = !!(xhci->opreg->usbsts & USBSTS_PCD); |
| if (changed) |
| xhci->opreg->usbsts = |
| (xhci->opreg->usbsts & USBSTS_PRSRV_MASK) | USBSTS_PCD; |
| return changed; |
| } |
| |
| static int |
| xhci_rh_port_status_changed(usbdev_t *const dev, const int port) |
| { |
| xhci_t *const xhci = XHCI_INST(dev->controller); |
| volatile u32 *const portsc = &xhci->opreg->prs[port - 1].portsc; |
| |
| const int changed = !!(*portsc & (PORTSC_CSC | PORTSC_PRC)); |
| /* always clear all the status change bits */ |
| *portsc = (*portsc & PORTSC_RW_MASK) | 0x00fe0000; |
| return changed; |
| } |
| |
| static int |
| xhci_rh_port_connected(usbdev_t *const dev, const int port) |
| { |
| xhci_t *const xhci = XHCI_INST(dev->controller); |
| volatile u32 *const portsc = &xhci->opreg->prs[port - 1].portsc; |
| |
| return *portsc & PORTSC_CCS; |
| } |
| |
| static int |
| xhci_rh_port_in_reset(usbdev_t *const dev, const int port) |
| { |
| xhci_t *const xhci = XHCI_INST(dev->controller); |
| volatile u32 *const portsc = &xhci->opreg->prs[port - 1].portsc; |
| |
| return !!(*portsc & PORTSC_PR); |
| } |
| |
| static int |
| xhci_rh_port_enabled(usbdev_t *const dev, const int port) |
| { |
| xhci_t *const xhci = XHCI_INST(dev->controller); |
| volatile u32 *const portsc = &xhci->opreg->prs[port - 1].portsc; |
| |
| return !!(*portsc & PORTSC_PED); |
| } |
| |
| static usb_speed |
| xhci_rh_port_speed(usbdev_t *const dev, const int port) |
| { |
| xhci_t *const xhci = XHCI_INST(dev->controller); |
| volatile u32 *const portsc = &xhci->opreg->prs[port - 1].portsc; |
| |
| if (*portsc & PORTSC_PED) { |
| return ((*portsc & PORTSC_PORT_SPEED_MASK) |
| >> PORTSC_PORT_SPEED_START) |
| - 1; |
| } else { |
| return -1; |
| } |
| } |
| |
| static int |
| xhci_rh_reset_port(usbdev_t *const dev, const int port) |
| { |
| xhci_t *const xhci = XHCI_INST(dev->controller); |
| volatile u32 *const portsc = &xhci->opreg->prs[port - 1].portsc; |
| |
| /* Trigger port reset. */ |
| *portsc = (*portsc & PORTSC_RW_MASK) | PORTSC_PR; |
| |
| /* Wait for port_in_reset == 0, up to 150 * 1000us = 150ms */ |
| if (generic_hub_wait_for_port(dev, port, 0, xhci_rh_port_in_reset, |
| 150, 1000) == 0) |
| usb_debug("xhci_rh: Reset timed out at port %d\n", port); |
| else |
| /* Clear reset status bits, since port is out of reset. */ |
| *portsc = (*portsc & PORTSC_RW_MASK) | PORTSC_PRC | PORTSC_WRC; |
| |
| return 0; |
| } |
| |
| static int |
| xhci_rh_enable_port(usbdev_t *const dev, int port) |
| { |
| if (IS_ENABLED(CONFIG_LP_USB_XHCI_MTK_QUIRK)) { |
| xhci_t *const xhci = XHCI_INST(dev->controller); |
| volatile u32 *const portsc = |
| &xhci->opreg->prs[port - 1].portsc; |
| |
| /* |
| * Before sending commands to a port, the Port Power in |
| * PORTSC register should be enabled on MTK's xHCI. |
| */ |
| *portsc = (*portsc & PORTSC_RW_MASK) | PORTSC_PP; |
| } |
| return 0; |
| } |
| |
| |
| static const generic_hub_ops_t xhci_rh_ops = { |
| .hub_status_changed = xhci_rh_hub_status_changed, |
| .port_status_changed = xhci_rh_port_status_changed, |
| .port_connected = xhci_rh_port_connected, |
| .port_in_reset = xhci_rh_port_in_reset, |
| .port_enabled = xhci_rh_port_enabled, |
| .port_speed = xhci_rh_port_speed, |
| .enable_port = xhci_rh_enable_port, |
| .disable_port = NULL, |
| .start_port_reset = NULL, |
| .reset_port = xhci_rh_reset_port, |
| }; |
| |
| void |
| xhci_rh_init (usbdev_t *dev) |
| { |
| /* we can set them here because a root hub _really_ shouldn't |
| appear elsewhere */ |
| dev->address = 0; |
| dev->hub = -1; |
| dev->port = -1; |
| |
| const int num_ports = /* TODO: maybe we need to read extended caps */ |
| (XHCI_INST(dev->controller)->capreg->hcsparams1 >> 24) & 0xff; |
| generic_hub_init(dev, num_ports, &xhci_rh_ops); |
| |
| usb_debug("xHCI: root hub init done\n"); |
| } |