| // Copyright 2017 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. |
| |
| #include <ddk/debug.h> |
| #include <ddk/protocol/platform-defs.h> |
| #include <hw/reg.h> |
| |
| #include <soc/aml-common/aml-usb-phy.h> |
| #include <soc/aml-a113/a113-hw.h> |
| |
| #include "gauss.h" |
| #include "gauss-hw.h" |
| |
| #define BIT_MASK(start, count) (((1 << (count)) - 1) << (start)) |
| #define SET_BITS(dest, start, count, value) \ |
| ((dest & ~BIT_MASK(start, count)) | (((value) << (start)) & BIT_MASK(start, count))) |
| |
| static const pbus_mmio_t dwc3_mmios[] = { |
| { |
| .base = DWC3_MMIO_BASE, |
| .length = DWC3_MMIO_LENGTH, |
| }, |
| }; |
| |
| static const pbus_irq_t dwc3_irqs[] = { |
| { |
| .irq = DWC3_IRQ, |
| .mode = ZX_INTERRUPT_MODE_EDGE_HIGH, |
| }, |
| }; |
| |
| static const pbus_bti_t usb_btis[] = { |
| { |
| .iommu_index = 0, |
| .bti_id = BTI_USB_XHCI, |
| }, |
| }; |
| |
| static const pbus_dev_t dwc3_dev = { |
| .name = "dwc3", |
| .vid = PDEV_VID_GENERIC, |
| .pid = PDEV_PID_GENERIC, |
| .did = PDEV_DID_USB_DWC3, |
| .mmios = dwc3_mmios, |
| .mmio_count = countof(dwc3_mmios), |
| .irqs = dwc3_irqs, |
| .irq_count = countof(dwc3_irqs), |
| .btis = usb_btis, |
| .bti_count = countof(usb_btis), |
| }; |
| |
| static const pbus_dev_t xhci_dev = { |
| .name = "dwc3-xhci", |
| .vid = PDEV_VID_GENERIC, |
| .pid = PDEV_PID_GENERIC, |
| .did = PDEV_DID_USB_XHCI, |
| .mmios = dwc3_mmios, |
| .mmio_count = countof(dwc3_mmios), |
| .irqs = dwc3_irqs, |
| .irq_count = countof(dwc3_irqs), |
| .btis = usb_btis, |
| .bti_count = countof(usb_btis), |
| }; |
| |
| // based on code from phy-aml-new-usb3.c |
| static int phy_irq_thread(void* arg) { |
| gauss_bus_t* bus = arg; |
| volatile void* addr = io_buffer_virt(&bus->usb_phy); |
| volatile void* u2p_regs = addr + PHY_REGISTER_SIZE; |
| volatile void* usb_regs = addr + (4 * PHY_REGISTER_SIZE); |
| uint32_t temp; |
| |
| gpio_config(&bus->gpio, USB_VBUS_GPIO, GPIO_DIR_OUT); |
| |
| while (1) { |
| #if ENABLE_NEW_IRQ_API |
| zx_status_t status = zx_irq_wait(bus->usb_phy_irq_handle, NULL); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "phy_irq_thread: zx_irq_wait returned %d\n", status); |
| break; |
| } |
| #else |
| uint64_t slots; |
| zx_status_t status = zx_interrupt_wait(bus->usb_phy_irq_handle, &slots); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "phy_irq_thread: zx_interrupt_wait returned %d\n", status); |
| break; |
| } |
| if (slots & (1ul << ZX_INTERRUPT_SLOT_USER)) { |
| break; |
| } |
| #endif |
| temp = readl(usb_regs + USB_R5_OFFSET); |
| temp &= ~USB_R5_IDDIG_IRQ; |
| writel(temp, usb_regs + USB_R5_OFFSET); |
| |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(10))); |
| |
| temp = readl(usb_regs + USB_R5_OFFSET); |
| bool host = !(temp & USB_R5_IDDIG_CURR); |
| zxlogf(INFO, "phy_irq_thread setting mode %s\n", (host ? "HOST" : "DEVICE")); |
| |
| if (host) { |
| gpio_write(&bus->gpio, USB_VBUS_GPIO, 1); |
| } |
| |
| temp = readl(usb_regs + USB_R0_OFFSET); |
| if (host) { |
| temp &= ~USB_R0_U2D_ACT; |
| } else { |
| temp |= USB_R0_U2D_ACT; |
| } |
| writel(temp, usb_regs + USB_R0_OFFSET); |
| |
| temp = readl(usb_regs + USB_R4_OFFSET); |
| if (host) { |
| temp &= ~USB_R4_P21_SLEEPM0; |
| } else { |
| temp |= USB_R4_P21_SLEEPM0; |
| } |
| writel(temp, usb_regs + USB_R4_OFFSET); |
| |
| |
| temp = readl(u2p_regs + U2P_R0_OFFSET); |
| if (host) { |
| temp |= U2P_R0_DMPULLDOWN; |
| temp |= U2P_R0_DPPULLDOWN; |
| temp |= U2P_R0_POR; |
| } else { |
| temp &= ~U2P_R0_DMPULLDOWN; |
| temp &= ~U2P_R0_DPPULLDOWN; |
| temp |= U2P_R0_POR; |
| } |
| writel(temp, u2p_regs + U2P_R0_OFFSET); |
| |
| zx_nanosleep(zx_deadline_after(ZX_USEC(500))); |
| |
| temp = readl(u2p_regs + U2P_R0_OFFSET); |
| temp &= ~U2P_R0_POR; |
| writel(temp, u2p_regs + U2P_R0_OFFSET); |
| |
| if (!host) { |
| gpio_write(&bus->gpio, USB_VBUS_GPIO, 0); |
| } |
| } |
| return 0; |
| } |
| |
| zx_status_t gauss_usb_init(gauss_bus_t* bus) { |
| zx_status_t status = io_buffer_init_physical(&bus->usb_phy, bus->bti_handle, 0xffe09000, 4096, |
| get_root_resource(), |
| ZX_CACHE_POLICY_UNCACHED_DEVICE); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "gauss_usb_init io_buffer_init_physical failed %d\n", status); |
| return status; |
| } |
| |
| #if ENABLE_NEW_IRQ_API |
| status = zx_irq_create(get_root_resource(), USB_PHY_IRQ, |
| ZX_INTERRUPT_MODE_DEFAULT, &bus->usb_phy_irq_handle); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "gauss_usb_init zx_irq_create failed %d\n", status); |
| io_buffer_release(&bus->usb_phy); |
| return status; |
| } |
| #else |
| status = zx_interrupt_create(get_root_resource(), 0, &bus->usb_phy_irq_handle); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "gauss_usb_init zx_interrupt_create failed %d\n", status); |
| io_buffer_release(&bus->usb_phy); |
| return status; |
| } |
| status = zx_interrupt_bind(bus->usb_phy_irq_handle, 0, get_root_resource(), USB_PHY_IRQ, |
| ZX_INTERRUPT_MODE_DEFAULT); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "gauss_usb_init zx_interrupt_bind failed %d\n", status); |
| zx_handle_close(bus->usb_phy_irq_handle); |
| io_buffer_release(&bus->usb_phy); |
| return status; |
| } |
| #endif |
| |
| volatile void* regs = io_buffer_virt(&bus->usb_phy); |
| |
| // amlogic_new_usb2_init |
| for (int i = 0; i < 4; i++) { |
| volatile void* addr = regs + (i * PHY_REGISTER_SIZE) + U2P_R0_OFFSET; |
| uint32_t temp = readl(addr); |
| temp |= U2P_R0_POR; |
| temp |= U2P_R0_DMPULLDOWN; |
| temp |= U2P_R0_DPPULLDOWN; |
| if (i == 1) { |
| temp |= U2P_R0_IDPULLUP; |
| } |
| writel(temp, addr); |
| zx_nanosleep(zx_deadline_after(ZX_USEC(500))); |
| temp = readl(addr); |
| temp &= ~U2P_R0_POR; |
| writel(temp, addr); |
| } |
| |
| // amlogic_new_usb3_init |
| volatile void* addr = regs + (4 * PHY_REGISTER_SIZE); |
| |
| uint32_t temp = readl(addr + USB_R1_OFFSET); |
| temp = SET_BITS(temp, USB_R1_U3H_FLADJ_30MHZ_REG_START, USB_R1_U3H_FLADJ_30MHZ_REG_BITS, 0x20); |
| writel(temp, addr + USB_R1_OFFSET); |
| |
| temp = readl(addr + USB_R5_OFFSET); |
| temp |= USB_R5_IDDIG_EN0; |
| temp |= USB_R5_IDDIG_EN1; |
| temp = SET_BITS(temp, USB_R5_IDDIG_TH_START, USB_R5_IDDIG_TH_BITS, 255); |
| writel(temp, addr + USB_R5_OFFSET); |
| |
| // add dwc3 device |
| if ((status = pbus_device_add(&bus->pbus, &dwc3_dev, 0)) != ZX_OK) { |
| zxlogf(ERROR, "a113_usb_init could not add dwc3_dev: %d\n", status); |
| return status; |
| } |
| // xhci_dev is enabled/disabled dynamically, so don't enable it here |
| if ((status = pbus_device_add(&bus->pbus, &xhci_dev, PDEV_ADD_DISABLED)) != ZX_OK) { |
| zxlogf(ERROR, "a113_usb_init could not add xhci_dev: %d\n", status); |
| return status; |
| } |
| |
| thrd_create_with_name(&bus->phy_irq_thread, phy_irq_thread, bus, "phy_irq_thread"); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t gauss_usb_set_mode(gauss_bus_t* bus, usb_mode_t mode) { |
| // TODO(voydanoff) more work will be needed here for switching to peripheral mode |
| |
| // add or remove XHCI device |
| pbus_device_enable(&bus->pbus, PDEV_VID_GENERIC, PDEV_PID_GENERIC, PDEV_DID_USB_XHCI, |
| mode == USB_MODE_HOST); |
| return ZX_OK; |
| } |