/* * Copyright (C) 2012-2015 Apple Inc. All rights reserved. * * This document is the property of Apple Inc. * It is considered confidential and proprietary. * * This document may not be reproduced or transmitted in any form, * in whole or in part, without the express written permission of * Apple Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apcie_common_regs.h" static bool s3e_mode; static bool s3e_reset_on_enable; static uint32_t link_enable_count; struct apcie_dbi_overrides { uint32_t offset; uint32_t mask; uint32_t value; }; static struct apcie_dbi_overrides apcie_dbi_overrides[] = { // Enable 64-bit addresses { 0x024, 0x00000001, 1 }, // Don't advertise L0s support { 0x07c, 0x00000400, 0 }, // Set T_POWER_OFF field in the L1_substates register to 2 () { 0xb44, 0x00000003, 2 }, }; bool external_refclk; // selects internal vs external refclk. must be called before // enabling the first link void apcie_use_external_refclk(bool use) { external_refclk = use; } void apcie_set_s3e_mode(bool reset_on_enable) { s3e_mode = true; s3e_reset_on_enable = reset_on_enable; } #if !SUB_PLATFORM_T7000 static void apcie_enable_phy_clkreq(uint32_t phy) { // Note, the function number might change for future platforms if (phy == 0) { gpio_configure(GPIO_PCIE_PHY01_CLKREQ, GPIO_CFG_FUNC1); } else if (phy == 1) { gpio_configure(GPIO_PCIE_PHY23_CLKREQ, GPIO_CFG_FUNC1); } else { panic("apcie: unknown phy %u", phy); } rAPCIE_PHY_CLKREQ_CTRL(phy) |= 1; } static void apcie_disable_phy_clkreq(uint32_t phy) { rAPCIE_PHY_CLKREQ_CTRL(phy) &= ~1; if (phy == 0) { gpio_configure(GPIO_PCIE_PHY01_CLKREQ, GPIO_CFG_DFLT); } else if (phy == 1) { gpio_configure(GPIO_PCIE_PHY23_CLKREQ, GPIO_CFG_DFLT); } else { panic("apcie: unknown phy %u", phy); } } #endif static void apcie_enable_external_refclk(void) { // Sequence from section 6.1.5 of Fiji/Capri APCIe spec (updated to r1.04.44) // 1) Set APCIE_COMMON_APCIE_RST_CTRL_{0/1}.apcie_phy_sw_reset to place the PHY in reset. for (int i = 0; i < APCIE_NUM_LINKS; i += 2) rAPCIE_PHY_RST_CTRL(i / 2) |= APCIE_PHY_RST_CTRL_phy_sw_reset; // 2) To access the PHY, the link shall be enabled. for (int i = 0; i < APCIE_NUM_LINKS; i+= 2) rAPCIE_PORT_CTRL(i) |= APCIE_PORT_CTRL_port_en; // 3) De-assert PERST# to the root controller for (int i = 0; i < APCIE_NUM_LINKS; i+= 2) rAPCIE_PORT_RST_CTRL(i) |= APCIE_PORT_RST_CTRL_perst_n; // 4) Select the external REFCLK for (int i = 0; i < APCIE_NUM_LINKS; i+= 2) rAPCIE_PHY_PHY_REF_USE_PAD(i) = 1; // c) Insert a DMB barrier instruction to insure that the external REFCLK switch occurs // before the port is disabled. platform_memory_barrier(); // 5) Assert PERST# to the root controller for (int i = 0; i < APCIE_NUM_LINKS; i+= 2) rAPCIE_PORT_RST_CTRL(i) &= ~APCIE_PORT_RST_CTRL_perst_n; // 6) Disable the link(s) for (int i = 0; i < APCIE_NUM_LINKS; i+= 2) rAPCIE_PORT_CTRL(i) &= ~APCIE_PORT_CTRL_port_en; // 7) Release the PHY(s) from reset for (int i = 0; i < APCIE_NUM_LINKS; i += 2) rAPCIE_PHY_RST_CTRL(i / 2) &= ~APCIE_PHY_RST_CTRL_phy_sw_reset; } static void apcie_enable_root_complex(void) { clock_gate(CLK_PCIE, true); #if SUB_PLATFORM_T7000 // Fiji uses the clock mesh for refclk, Capri has a dedicated PLL for refclk if (!external_refclk) clock_gate(CLK_PCIE_REF, true); #endif clock_gate(CLK_PCIE_AUX, true); spin(10); if (external_refclk) apcie_enable_external_refclk(); if (s3e_mode) { if (s3e_reset_on_enable) { gpio_configure(GPIO_S3E_RESETN, GPIO_CFG_OUT_0); } gpio_configure(GPIO_NAND_SYS_CLK, GPIO_CFG_FUNC0); rAPCIE_NANDSYSCLK_CTRL = 1; // S3e spec requires 32 24 Mhz clock cycles from NANDSYSCLK to reset deassertion. // The Maui spec requires 100 microseconds, so we'll follow that to match the behavior // we'll be using in Maui spin(100); gpio_configure(GPIO_S3E_RESETN, GPIO_CFG_OUT_1); } } static void apcie_disable_root_complex(void) { clock_set_device_reset(CLK_PCIE, true); spin(1); clock_gate(CLK_PCIE, false); clock_set_device_reset(CLK_PCIE, false); } // Implements the enable sequence from APCIe spec section 6.1 as it applies to // APCIE_COMMON registers. The portion of the sequence that affects the APCIE_CONFIG // registers is handled in common code static void apcie_enable_link_hardware(uint32_t link) { ASSERT(link < APCIE_NUM_LINKS); dprintf(DEBUG_INFO, "apcie: Enabling link %d%s\n", link, external_refclk ? " with external refclk" : ""); // Make sure PERST# starts out as asserted rAPCIE_PORT_RST_CTRL(link) &= ~APCIE_PORT_RST_CTRL_perst_n; // 2) 100Mhz REFCLK is enabled if (!external_refclk) { rAPCIE_PORT_REFCLK_CTRL(link) |= APCIE_PORT_REFCLK_CTRL_refclk_en; #if !SUB_PLATFORM_T7000 while(!clock_get_pcie_refclk_good()) spin(10); #endif } else { // On Capri, the PHY asserts an external clkreq enable GPIO // when it wants the external refclk to be enabled. On Fiji the // external refclk is required to always be enabled #if !SUB_PLATFORM_T7000 apcie_enable_phy_clkreq(link / 2); #endif } // 3) 10 us wait spin(10); // 4) Target PCIE link is enabled rAPCIE_PORT_OVERRIDE_CTRL(link) &= ~APCIE_PORT_OVERRIDE_CTRL_pipe_ovr_en; rAPCIE_PORT_CTRL(link) |= APCIE_PORT_CTRL_port_en; #if SUPPORT_FPGA rAPCIE_PORT_CTRL(link) |= APCIE_PORT_CTRL_muxed_auxclk_auto_dis; #endif // 5) De-assert PERST# for the target PCIE link. rAPCIE_PORT_RST_CTRL(link) |= APCIE_PORT_RST_CTRL_perst_n; // 6b) DBI register overrides // Unlock access to the readonly registers rAPCIE_CONFIG_SPACE32(link, 0x8bc) |= 1; for (unsigned i = 0; i < sizeof(apcie_dbi_overrides) / sizeof(apcie_dbi_overrides[0]); i++) { uint32_t offset = apcie_dbi_overrides[i].offset; uint32_t mask = apcie_dbi_overrides[i].mask; uint32_t value = apcie_dbi_overrides[i].value; rAPCIE_CONFIG_SPACE32(link, offset) = (rAPCIE_CONFIG_SPACE32(link, offset) & ~mask) | value; } // Lock access to the readonly registers rAPCIE_CONFIG_SPACE32(link, 0x8bc) &= ~1; } static void apcie_disable_link_hardware(uint32_t link) { ASSERT(link < APCIE_NUM_LINKS); rAPCIE_PORT_RST_CTRL(link) &= ~APCIE_PORT_RST_CTRL_perst_n; rAPCIE_PORT_REFCLK_CTRL(link) &= ~APCIE_PORT_REFCLK_CTRL_refclk_en; rAPCIE_PORT_CTRL(link) &= ~APCIE_PORT_CTRL_port_en; rAPCIE_PORT_OVERRIDE_CTRL(link) |= APCIE_PORT_OVERRIDE_CTRL_pipe_ovr_en; #if !SUB_PLATFORM_T7000 if (external_refclk) { // If the other link on this PHY is already disabled, go ahead // and disable the external refclk settings and GPIO if ((rAPCIE_PORT_CTRL(link ^ 1) & APCIE_PORT_CTRL_port_en) == 0) { apcie_disable_phy_clkreq(link / 2); } } #endif } bool apcie_enable_link(uint32_t link) { struct apcie_link_config *config = &apcie_link_configs[link]; struct apcie_link_status *status = &apcie_link_statuses[link]; utime_t start; uint32_t force_width; ASSERT(link < APCIE_NUM_LINKS); if (status->enabled) return true; // Clocks to PCIe don't default to on since it isn't used in POR configurations if (link_enable_count == 0) { apcie_enable_root_complex(); } // Make sure PERST# starts out as asserted gpio_configure(config->perst_gpio, GPIO_CFG_OUT_0); gpio_configure(config->clkreq_gpio, GPIO_CFG_IN); // wait for CLKREQ# to be asserted dprintf(DEBUG_INFO, "apcie: waiting for clkreq..."); start = system_time(); while (gpio_read(config->clkreq_gpio) != 0) { if (time_has_elapsed(start, APCIE_ENABLE_TIMEOUT)) { dprintf(DEBUG_CRITICAL, "apcie: timeout waiting for CLKREQ# on link %u\n", link); goto cleanup; } spin(1); } dprintf(DEBUG_INFO, " done\n"); gpio_configure(config->clkreq_gpio, GPIO_CFG_FUNC0); // Do required initialization in APCIE_COMMON registers apcie_enable_link_hardware(link); // Apply tunables rAPCIE_CONFIG_INBCTRL(link) |= APCIE_CONFIG_INBCTRL_CPL_MUST_PUSH_EN; rAPCIE_CONFIG_OUTBCTRL(link) |= APCIE_CONFIG_OUTBCTRL_EARLY_PT_RESP_EN; rAPCIE_CONFIG_PMETOACK_TMOTLIM(link) = 0x28; // PCIe spec requires a 100 microsecond delay from REFCLK starting // to PERST being deasserted spin(100); // 5) De-assert PERST# for the target PCIE link. gpio_configure(config->perst_gpio, GPIO_CFG_OUT_1); // Limit link speed to Gen1 rAPCIE_PCIE_CAP_LINK_CONTROL2(link) = (rAPCIE_PCIE_CAP_LINK_CONTROL2(link) & ~0xfU) | 1; // Limit link width if requested by the platform. If the platform returns 0, // let the hardware autonegotiate the width using its default parameters force_width = platform_get_pcie_link_width(link); if (force_width != 0) { ASSERT(force_width <= 2); uint32_t gen2_ctrl; gen2_ctrl = rAPCIE_PORT_LOGIC_GEN2_CTRL(link); gen2_ctrl &= ~(0x1ff << 8); gen2_ctrl |= (force_width << 8); rAPCIE_PORT_LOGIC_GEN2_CTRL(link) = gen2_ctrl; uint32_t port_link_ctrl; port_link_ctrl = rAPCIE_PORT_LOGIC_PORT_LINK_CTRL(link); port_link_ctrl &= ~(0x3f << 16); port_link_ctrl |= ((1 << (force_width - 1)) << 16); rAPCIE_PORT_LOGIC_PORT_LINK_CTRL(link) = port_link_ctrl; } rAPCIE_COUNTER_COMMAND(link) = APCIE_COUNTER_CLEAR | APCIE_COUNTER_ENABLE; // 6c) Software sets LINKCFG.ltssm_en to start link training rAPCIE_CONFIG_LINKCFG(link) |= APCIE_CONFIG_LINKCFG_LTSSM_EN; start = system_time(); while ((rAPCIE_CONFIG_LINKSTS(link) & APCIE_CONFIG_LINKSTS_LINK_STATUS) == 0) { if (time_has_elapsed(start, APCIE_ENABLE_TIMEOUT)) { uint32_t linkpmgrsts; dprintf(DEBUG_CRITICAL, "apcie: Timeout waiting for LinkUp on link %u\n", link); dprintf(DEBUG_CRITICAL, "apcie: LINKSTS 0x%08x\n", rAPCIE_CONFIG_LINKSTS(link)); linkpmgrsts = rAPCIE_CONFIG_LINKPMGRSTS(link); dprintf(DEBUG_CRITICAL, "apcie: LINKPMGRSTS 0x%08x (LTSSM state %d)\n", linkpmgrsts, (linkpmgrsts >> 9) & 0x1F); goto cleanup; } spin(10); } status->enabled = true; link_enable_count++; dart_init(config->dart_id); apcie_setup_root_port_bridge(link, config); return true; cleanup: gpio_configure(config->perst_gpio, GPIO_CFG_OUT_0); spin(10); gpio_configure(config->perst_gpio, GPIO_CFG_DFLT); gpio_configure(config->clkreq_gpio, GPIO_CFG_DFLT); apcie_disable_link_hardware(link); if (link_enable_count == 0) { apcie_disable_root_complex(); } return false; } void apcie_disable_link(uint32_t link) { struct apcie_link_config *config = &apcie_link_configs[link]; struct apcie_link_status *status = &apcie_link_statuses[link]; utime_t start; ASSERT(link < APCIE_NUM_LINKS); if (!status->enabled) return; dprintf(DEBUG_INFO, "apcie: Disabling link %d\n", link); rAPCIE_COUNTER_COMMAND(link) = 0; // Request PMETO and clear the previous status indications rAPCIE_CONFIG_PMETO(link) = 1 | (3 << 4); start = system_time(); while (rAPCIE_CONFIG_PMETO(link) & 1) { if (time_has_elapsed(start, 10000)) { dprintf(DEBUG_CRITICAL, "apcie: timeout waiting for PME_To_Ack, continuing\n"); break; } spin(10); } start = system_time(); while ((rAPCIE_CONFIG_LINKSTS(link) & APCIE_CONFIG_LINKSTS_L2_STATE) == 0) { if (time_has_elapsed(start, 10000)) { dprintf(DEBUG_CRITICAL, "apcie: link did not go into L2, continuing\n"); break; } spin(10); } gpio_configure(config->perst_gpio, GPIO_CFG_OUT_0); // delay to allow PERST# signal to settle before letting the pulldown hold it low // and to allow the endpoint to handle PERST# before removing REFCLK spin(25); gpio_configure(config->perst_gpio, GPIO_CFG_DFLT); gpio_configure(config->clkreq_gpio, GPIO_CFG_DFLT); #if DEBUG_BUILD dart_assert_unmapped(config->dart_id); #endif apcie_disable_link_hardware(link); status->enabled = false; link_enable_count--; if (link_enable_count == 0) { apcie_disable_root_complex(); } apcie_free_port_bridge(link); } uint32_t apcie_get_link_enable_count(void) { return link_enable_count; } #if WITH_DEVICETREE void apcie_update_devicetree(DTNode *apcie_node) { uint32_t propSize; char *propName; void *propData; propName = "dbi-overrides"; if (FindProperty(apcie_node, &propName, &propData, &propSize)) { if (propSize < sizeof(apcie_dbi_overrides)) panic("dbi-overrides property is too small"); memcpy(propData, apcie_dbi_overrides, sizeof(apcie_dbi_overrides)); } } #endif struct apcie_link_config platform_apcie_link_configs[APCIE_NUM_LINKS] = { [0] = { .perst_gpio = GPIO_PCIE0_PERST, .clkreq_gpio = GPIO_PCIE0_CLKREQ, .dart_id = PCIE_PORT0_DART_ID, }, [1] = { .perst_gpio = GPIO_PCIE1_PERST, .clkreq_gpio = GPIO_PCIE1_CLKREQ, .dart_id = PCIE_PORT1_DART_ID, }, #if SUB_PLATFORM_T7001 [2] = { .perst_gpio = GPIO_PCIE2_PERST, .clkreq_gpio = GPIO_PCIE2_CLKREQ, .dart_id = PCIE_PORT2_DART_ID, }, [3] = { .perst_gpio = GPIO_PCIE3_PERST, .clkreq_gpio = GPIO_PCIE3_CLKREQ, .dart_id = PCIE_PORT3_DART_ID, }, #endif }; void platform_register_pci_busses(void) { apcie_init(); } uint64_t platform_map_host_to_pci_addr(uintptr_t addr) { // Right now we're only supporting 32-bit PCI addresses // for aPCIe, so just lop off the top 32 bits return addr & 0xFFFFFFFF; } uintptr_t platform_map_pci_to_host_addr(uint64_t addr) { // Right now we're only supporting 32-bit PCI addresses // for aPCIe ASSERT((addr & ~0xFFFFFFFFULL) == 0); return PCI_32BIT_BASE + (addr & (PCI_32BIT_LEN - 1)); }