On Mon, Dec 16, 2019 at 12:01:09PM +0100, Nicolas Saenz Julienne wrote: > From: Jim Quinlan <james.quinlan@xxxxxxxxxxxx> > > This adds a basic driver for Broadcom's STB PCIe controller, for now > aimed at Raspberry Pi 4's SoC, bcm2711. > > Signed-off-by: Jim Quinlan <james.quinlan@xxxxxxxxxxxx> > Co-developed-by: Nicolas Saenz Julienne <nsaenzjulienne@xxxxxxx> > Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne@xxxxxxx> > Reviewed-by: Andrew Murray <andrew.murray@xxxxxxx> > Reviewed-by: Jeremy Linton <jeremy.linton@xxxxxxx> > > --- > > Changes since v3: > - Update commit message > - rollback roundup_pow_two usage, it'll be updated later down the line > - Remove comment in register definition > > Changes since v2: > - Correct rc_bar2_offset sign In relation to this change. [...] > +static inline int brcm_pcie_get_rc_bar2_size_and_offset(struct brcm_pcie *pcie, > + u64 *rc_bar2_size, > + u64 *rc_bar2_offset) > +{ > + struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie); > + struct device *dev = pcie->dev; > + struct resource_entry *entry; > + > + entry = resource_list_first_type(&bridge->dma_ranges, IORESOURCE_MEM); > + if (!entry) > + return -ENODEV; > + > + *rc_bar2_offset = -entry->offset; I think this deserves a comment - I guess it has to do with how the controller expects CPU<->PCI offsets to be expressed compared to how it is computed in dma_ranges entries. I will try to complete the review shortly and try to apply it given that it has already been reviewed by others. Lorenzo > + *rc_bar2_size = 1ULL << fls64(entry->res->end - entry->res->start); > + > + /* > + * We validate the inbound memory view even though we should trust > + * whatever the device-tree provides. This is because of an HW issue on > + * early Raspberry Pi 4's revisions (bcm2711). It turns out its > + * firmware has to dynamically edit dma-ranges due to a bug on the > + * PCIe controller integration, which prohibits any access above the > + * lower 3GB of memory. Given this, we decided to keep the dma-ranges > + * in check, avoiding hard to debug device-tree related issues in the > + * future: > + * > + * The PCIe host controller by design must set the inbound viewport to > + * be a contiguous arrangement of all of the system's memory. In > + * addition, its size mut be a power of two. To further complicate > + * matters, the viewport must start on a pcie-address that is aligned > + * on a multiple of its size. If a portion of the viewport does not > + * represent system memory -- e.g. 3GB of memory requires a 4GB > + * viewport -- we can map the outbound memory in or after 3GB and even > + * though the viewport will overlap the outbound memory the controller > + * will know to send outbound memory downstream and everything else > + * upstream. > + * > + * For example: > + * > + * - The best-case scenario, memory up to 3GB, is to place the inbound > + * region in the first 4GB of pcie-space, as some legacy devices can > + * only address 32bits. We would also like to put the MSI under 4GB > + * as well, since some devices require a 32bit MSI target address. > + * > + * - If the system memory is 4GB or larger we cannot start the inbound > + * region at location 0 (since we have to allow some space for > + * outbound memory @ 3GB). So instead it will start at the 1x > + * multiple of its size > + */ > + if (!*rc_bar2_size || *rc_bar2_offset % *rc_bar2_size || > + (*rc_bar2_offset < SZ_4G && *rc_bar2_offset > SZ_2G)) { > + dev_err(dev, "Invalid rc_bar2_offset/size: size 0x%llx, off 0x%llx\n", > + *rc_bar2_size, *rc_bar2_offset); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int brcm_pcie_setup(struct brcm_pcie *pcie) > +{ > + struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie); > + u64 rc_bar2_offset, rc_bar2_size; > + void __iomem *base = pcie->base; > + struct device *dev = pcie->dev; > + struct resource_entry *entry; > + unsigned int scb_size_val; > + bool ssc_good = false; > + struct resource *res; > + int num_out_wins = 0; > + u16 nlw, cls, lnksta; > + int i, ret; > + u32 tmp; > + > + /* Reset the bridge */ > + brcm_pcie_bridge_sw_init_set(pcie, 1); > + > + usleep_range(100, 200); > + > + /* Take the bridge out of reset */ > + brcm_pcie_bridge_sw_init_set(pcie, 0); > + > + tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + tmp &= ~PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK; > + writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + /* Wait for SerDes to be stable */ > + usleep_range(100, 200); > + > + /* Set SCB_MAX_BURST_SIZE, CFG_READ_UR_MODE, SCB_ACCESS_EN */ > + u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK); > + u32p_replace_bits(&tmp, 1, PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK); > + u32p_replace_bits(&tmp, PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_128, > + PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK); > + writel(tmp, base + PCIE_MISC_MISC_CTRL); > + > + ret = brcm_pcie_get_rc_bar2_size_and_offset(pcie, &rc_bar2_size, > + &rc_bar2_offset); > + if (ret) > + return ret; > + > + tmp = lower_32_bits(rc_bar2_offset); > + u32p_replace_bits(&tmp, brcm_pcie_encode_ibar_size(rc_bar2_size), > + PCIE_MISC_RC_BAR2_CONFIG_LO_SIZE_MASK); > + writel(tmp, base + PCIE_MISC_RC_BAR2_CONFIG_LO); > + writel(upper_32_bits(rc_bar2_offset), > + base + PCIE_MISC_RC_BAR2_CONFIG_HI); > + > + scb_size_val = rc_bar2_size ? > + ilog2(rc_bar2_size) - 15 : 0xf; /* 0xf is 1GB */ > + tmp = readl(base + PCIE_MISC_MISC_CTRL); > + u32p_replace_bits(&tmp, scb_size_val, > + PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK); > + writel(tmp, base + PCIE_MISC_MISC_CTRL); > + > + /* disable the PCIe->GISB memory window (RC_BAR1) */ > + tmp = readl(base + PCIE_MISC_RC_BAR1_CONFIG_LO); > + tmp &= ~PCIE_MISC_RC_BAR1_CONFIG_LO_SIZE_MASK; > + writel(tmp, base + PCIE_MISC_RC_BAR1_CONFIG_LO); > + > + /* disable the PCIe->SCB memory window (RC_BAR3) */ > + tmp = readl(base + PCIE_MISC_RC_BAR3_CONFIG_LO); > + tmp &= ~PCIE_MISC_RC_BAR3_CONFIG_LO_SIZE_MASK; > + writel(tmp, base + PCIE_MISC_RC_BAR3_CONFIG_LO); > + > + /* Mask all interrupts since we are not handling any yet */ > + writel(0xffffffff, pcie->base + PCIE_MSI_INTR2_MASK_SET); > + > + /* clear any interrupts we find on boot */ > + writel(0xffffffff, pcie->base + PCIE_MSI_INTR2_CLR); > + > + if (pcie->gen) > + brcm_pcie_set_gen(pcie, pcie->gen); > + > + /* Unassert the fundamental reset */ > + brcm_pcie_perst_set(pcie, 0); > + > + /* > + * Give the RC/EP time to wake up, before trying to configure RC. > + * Intermittently check status for link-up, up to a total of 100ms. > + */ > + for (i = 0; i < 100 && !brcm_pcie_link_up(pcie); i += 5) > + msleep(5); > + > + if (!brcm_pcie_link_up(pcie)) { > + dev_err(dev, "link down\n"); > + return -ENODEV; > + } > + > + if (!brcm_pcie_rc_mode(pcie)) { > + dev_err(dev, "PCIe misconfigured; is in EP mode\n"); > + return -EINVAL; > + } > + > + resource_list_for_each_entry(entry, &bridge->windows) { > + res = entry->res; > + > + if (resource_type(res) != IORESOURCE_MEM) > + continue; > + > + if (num_out_wins >= BRCM_NUM_PCIE_OUT_WINS) { > + dev_err(pcie->dev, "too many outbound wins\n"); > + return -EINVAL; > + } > + > + brcm_pcie_set_outbound_win(pcie, num_out_wins, res->start, > + res->start - entry->offset, > + res->end - res->start + 1); > + num_out_wins++; > + } > + > + /* > + * For config space accesses on the RC, show the right class for > + * a PCIe-PCIe bridge (the default setting is to be EP mode). > + */ > + tmp = readl(base + PCIE_RC_CFG_PRIV1_ID_VAL3); > + u32p_replace_bits(&tmp, 0x060400, > + PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK); > + writel(tmp, base + PCIE_RC_CFG_PRIV1_ID_VAL3); > + > + if (pcie->ssc) { > + ret = brcm_pcie_set_ssc(pcie); > + if (ret == 0) > + ssc_good = true; > + else > + dev_err(dev, "failed attempt to enter ssc mode\n"); > + } > + > + lnksta = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKSTA); > + cls = FIELD_GET(PCI_EXP_LNKSTA_CLS, lnksta); > + nlw = FIELD_GET(PCI_EXP_LNKSTA_NLW, lnksta); > + dev_info(dev, "link up, %s x%u %s\n", > + PCIE_SPEED2STR(cls + PCI_SPEED_133MHz_PCIX_533), > + nlw, ssc_good ? "(SSC)" : "(!SSC)"); > + > + /* PCIe->SCB endian mode for BAR */ > + tmp = readl(base + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1); > + u32p_replace_bits(&tmp, PCIE_RC_CFG_VENDOR_SPCIFIC_REG1_LITTLE_ENDIAN, > + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1_ENDIAN_MODE_BAR2_MASK); > + writel(tmp, base + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1); > + > + /* > + * Refclk from RC should be gated with CLKREQ# input when ASPM L0s,L1 > + * is enabled => setting the CLKREQ_DEBUG_ENABLE field to 1. > + */ > + tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + tmp |= PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK; > + writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + > + return 0; > +} > + > +/* L23 is a low-power PCIe link state */ > +static void brcm_pcie_enter_l23(struct brcm_pcie *pcie) > +{ > + void __iomem *base = pcie->base; > + int l23, i; > + u32 tmp; > + > + /* Assert request for L23 */ > + tmp = readl(base + PCIE_MISC_PCIE_CTRL); > + u32p_replace_bits(&tmp, 1, PCIE_MISC_PCIE_CTRL_PCIE_L23_REQUEST_MASK); > + writel(tmp, base + PCIE_MISC_PCIE_CTRL); > + > + /* Wait up to 36 msec for L23 */ > + tmp = readl(base + PCIE_MISC_PCIE_STATUS); > + l23 = FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_LINK_IN_L23_MASK, tmp); > + for (i = 0; i < 15 && !l23; i++) { > + usleep_range(2000, 2400); > + tmp = readl(base + PCIE_MISC_PCIE_STATUS); > + l23 = FIELD_GET(PCIE_MISC_PCIE_STATUS_PCIE_LINK_IN_L23_MASK, > + tmp); > + } > + > + if (!l23) > + dev_err(pcie->dev, "failed to enter low-power link state\n"); > +} > + > +static void brcm_pcie_turn_off(struct brcm_pcie *pcie) > +{ > + void __iomem *base = pcie->base; > + int tmp; > + > + if (brcm_pcie_link_up(pcie)) > + brcm_pcie_enter_l23(pcie); > + /* Assert fundamental reset */ > + brcm_pcie_perst_set(pcie, 1); > + > + /* Deassert request for L23 in case it was asserted */ > + tmp = readl(base + PCIE_MISC_PCIE_CTRL); > + u32p_replace_bits(&tmp, 0, PCIE_MISC_PCIE_CTRL_PCIE_L23_REQUEST_MASK); > + writel(tmp, base + PCIE_MISC_PCIE_CTRL); > + > + /* Turn off SerDes */ > + tmp = readl(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + u32p_replace_bits(&tmp, 1, PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK); > + writel(tmp, base + PCIE_MISC_HARD_PCIE_HARD_DEBUG); > + > + /* Shutdown PCIe bridge */ > + brcm_pcie_bridge_sw_init_set(pcie, 1); > +} > + > +static void __brcm_pcie_remove(struct brcm_pcie *pcie) > +{ > + brcm_pcie_turn_off(pcie); > + clk_disable_unprepare(pcie->clk); > + clk_put(pcie->clk); > +} > + > +static int brcm_pcie_remove(struct platform_device *pdev) > +{ > + struct brcm_pcie *pcie = platform_get_drvdata(pdev); > + > + pci_stop_root_bus(pcie->root_bus); > + pci_remove_root_bus(pcie->root_bus); > + __brcm_pcie_remove(pcie); > + > + return 0; > +} > + > +static int brcm_pcie_probe(struct platform_device *pdev) > +{ > + struct device_node *np = pdev->dev.of_node; > + struct pci_host_bridge *bridge; > + struct brcm_pcie *pcie; > + struct pci_bus *child; > + struct resource *res; > + int ret; > + > + bridge = devm_pci_alloc_host_bridge(&pdev->dev, sizeof(*pcie)); > + if (!bridge) > + return -ENOMEM; > + > + pcie = pci_host_bridge_priv(bridge); > + pcie->dev = &pdev->dev; > + pcie->np = np; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + pcie->base = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(pcie->base)) > + return PTR_ERR(pcie->base); > + > + pcie->clk = devm_clk_get_optional(&pdev->dev, "sw_pcie"); > + if (IS_ERR(pcie->clk)) > + return PTR_ERR(pcie->clk); > + > + ret = of_pci_get_max_link_speed(np); > + pcie->gen = (ret < 0) ? 0 : ret; > + > + pcie->ssc = of_property_read_bool(np, "brcm,enable-ssc"); > + > + ret = pci_parse_request_of_pci_ranges(pcie->dev, &bridge->windows, > + &bridge->dma_ranges, NULL); > + if (ret) > + return ret; > + > + ret = clk_prepare_enable(pcie->clk); > + if (ret) { > + dev_err(&pdev->dev, "could not enable clock\n"); > + return ret; > + } > + > + ret = brcm_pcie_setup(pcie); > + if (ret) > + goto fail; > + > + bridge->dev.parent = &pdev->dev; > + bridge->busnr = 0; > + bridge->ops = &brcm_pcie_ops; > + bridge->sysdata = pcie; > + bridge->map_irq = of_irq_parse_and_map_pci; > + bridge->swizzle_irq = pci_common_swizzle; > + > + ret = pci_scan_root_bus_bridge(bridge); > + if (ret < 0) { > + dev_err(pcie->dev, "Scanning root bridge failed\n"); > + goto fail; > + } > + > + pci_assign_unassigned_bus_resources(bridge->bus); > + list_for_each_entry(child, &bridge->bus->children, node) > + pcie_bus_configure_settings(child); > + pci_bus_add_devices(bridge->bus); > + platform_set_drvdata(pdev, pcie); > + pcie->root_bus = bridge->bus; > + > + return 0; > +fail: > + __brcm_pcie_remove(pcie); > + return ret; > +} > + > +static const struct of_device_id brcm_pcie_match[] = { > + { .compatible = "brcm,bcm2711-pcie" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, brcm_pcie_match); > + > +static struct platform_driver brcm_pcie_driver = { > + .probe = brcm_pcie_probe, > + .remove = brcm_pcie_remove, > + .driver = { > + .name = "brcm-pcie", > + .of_match_table = brcm_pcie_match, > + }, > +}; > +module_platform_driver(brcm_pcie_driver); > + > +MODULE_LICENSE("GPL"); > +MODULE_DESCRIPTION("Broadcom STB PCIe RC driver"); > +MODULE_AUTHOR("Broadcom"); > -- > 2.24.0 >