425 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			425 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * pcie_uniphier.c - Socionext UniPhier PCIe driver
 | |
|  * Copyright 2019-2021 Socionext, Inc.
 | |
|  */
 | |
| 
 | |
| #include <clk.h>
 | |
| #include <common.h>
 | |
| #include <dm.h>
 | |
| #include <dm/device_compat.h>
 | |
| #include <generic-phy.h>
 | |
| #include <linux/bitfield.h>
 | |
| #include <linux/bitops.h>
 | |
| #include <linux/compat.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/io.h>
 | |
| #include <pci.h>
 | |
| #include <reset.h>
 | |
| 
 | |
| DECLARE_GLOBAL_DATA_PTR;
 | |
| 
 | |
| /* DBI registers */
 | |
| #define PCIE_LINK_STATUS_REG		0x0080
 | |
| #define PCIE_LINK_STATUS_WIDTH_MASK	GENMASK(25, 20)
 | |
| #define PCIE_LINK_STATUS_SPEED_MASK	GENMASK(19, 16)
 | |
| 
 | |
| #define PCIE_MISC_CONTROL_1_OFF		0x08BC
 | |
| #define PCIE_DBI_RO_WR_EN		BIT(0)
 | |
| 
 | |
| /* DBI iATU registers */
 | |
| #define PCIE_ATU_VIEWPORT		0x0900
 | |
| #define PCIE_ATU_REGION_INBOUND		BIT(31)
 | |
| #define PCIE_ATU_REGION_OUTBOUND	0
 | |
| #define PCIE_ATU_REGION_INDEX_MASK	GENMASK(3, 0)
 | |
| 
 | |
| #define PCIE_ATU_CR1			0x0904
 | |
| #define PCIE_ATU_TYPE_MEM		0
 | |
| #define PCIE_ATU_TYPE_IO		2
 | |
| #define PCIE_ATU_TYPE_CFG0		4
 | |
| #define PCIE_ATU_TYPE_CFG1		5
 | |
| 
 | |
| #define PCIE_ATU_CR2			0x0908
 | |
| #define PCIE_ATU_ENABLE			BIT(31)
 | |
| #define PCIE_ATU_MATCH_MODE		BIT(30)
 | |
| #define PCIE_ATU_BAR_NUM_MASK		GENMASK(10, 8)
 | |
| 
 | |
| #define PCIE_ATU_LOWER_BASE		0x090C
 | |
| #define PCIE_ATU_UPPER_BASE		0x0910
 | |
| #define PCIE_ATU_LIMIT			0x0914
 | |
| #define PCIE_ATU_LOWER_TARGET		0x0918
 | |
| #define PCIE_ATU_BUS(x)			FIELD_PREP(GENMASK(31, 24), x)
 | |
| #define PCIE_ATU_DEV(x)			FIELD_PREP(GENMASK(23, 19), x)
 | |
| #define PCIE_ATU_FUNC(x)		FIELD_PREP(GENMASK(18, 16), x)
 | |
| #define PCIE_ATU_UPPER_TARGET		0x091C
 | |
| 
 | |
| /* Link Glue registers */
 | |
| #define PCL_PINCTRL0			0x002c
 | |
| #define PCL_PERST_PLDN_REGEN		BIT(12)
 | |
| #define PCL_PERST_NOE_REGEN		BIT(11)
 | |
| #define PCL_PERST_OUT_REGEN		BIT(8)
 | |
| #define PCL_PERST_PLDN_REGVAL		BIT(4)
 | |
| #define PCL_PERST_NOE_REGVAL		BIT(3)
 | |
| #define PCL_PERST_OUT_REGVAL		BIT(0)
 | |
| 
 | |
| #define PCL_MODE			0x8000
 | |
| #define PCL_MODE_REGEN			BIT(8)
 | |
| #define PCL_MODE_REGVAL			BIT(0)
 | |
| 
 | |
| #define PCL_APP_READY_CTRL		0x8008
 | |
| #define PCL_APP_LTSSM_ENABLE		BIT(0)
 | |
| 
 | |
| #define PCL_APP_PM0			0x8078
 | |
| #define PCL_SYS_AUX_PWR_DET		BIT(8)
 | |
| 
 | |
| #define PCL_STATUS_LINK			0x8140
 | |
| #define PCL_RDLH_LINK_UP		BIT(1)
 | |
| #define PCL_XMLH_LINK_UP		BIT(0)
 | |
| 
 | |
| #define LINK_UP_TIMEOUT_MS		100
 | |
| 
 | |
| struct uniphier_pcie_priv {
 | |
| 	void *base;
 | |
| 	void *dbi_base;
 | |
| 	void *cfg_base;
 | |
| 	fdt_size_t cfg_size;
 | |
| 	struct fdt_resource link_res;
 | |
| 	struct fdt_resource dbi_res;
 | |
| 	struct fdt_resource cfg_res;
 | |
| 
 | |
| 	struct clk clk;
 | |
| 	struct reset_ctl rst;
 | |
| 	struct phy phy;
 | |
| 
 | |
| 	struct pci_region io;
 | |
| 	struct pci_region mem;
 | |
| };
 | |
| 
 | |
| static int pcie_dw_get_link_speed(struct uniphier_pcie_priv *priv)
 | |
| {
 | |
| 	u32 val = readl(priv->dbi_base + PCIE_LINK_STATUS_REG);
 | |
| 
 | |
| 	return FIELD_GET(PCIE_LINK_STATUS_SPEED_MASK, val);
 | |
| }
 | |
| 
 | |
| static int pcie_dw_get_link_width(struct uniphier_pcie_priv *priv)
 | |
| {
 | |
| 	u32 val = readl(priv->dbi_base + PCIE_LINK_STATUS_REG);
 | |
| 
 | |
| 	return FIELD_GET(PCIE_LINK_STATUS_WIDTH_MASK, val);
 | |
| }
 | |
| 
 | |
| static void pcie_dw_prog_outbound_atu(struct uniphier_pcie_priv *priv,
 | |
| 				      int index, int type, u64 cpu_addr,
 | |
| 				      u64 pci_addr, u32 size)
 | |
| {
 | |
| 	writel(PCIE_ATU_REGION_OUTBOUND
 | |
| 	       | FIELD_PREP(PCIE_ATU_REGION_INDEX_MASK, index),
 | |
| 	       priv->dbi_base + PCIE_ATU_VIEWPORT);
 | |
| 	writel(lower_32_bits(cpu_addr),
 | |
| 	       priv->dbi_base + PCIE_ATU_LOWER_BASE);
 | |
| 	writel(upper_32_bits(cpu_addr),
 | |
| 	       priv->dbi_base + PCIE_ATU_UPPER_BASE);
 | |
| 	writel(lower_32_bits(cpu_addr + size - 1),
 | |
| 	       priv->dbi_base + PCIE_ATU_LIMIT);
 | |
| 	writel(lower_32_bits(pci_addr),
 | |
| 	       priv->dbi_base + PCIE_ATU_LOWER_TARGET);
 | |
| 	writel(upper_32_bits(pci_addr),
 | |
| 	       priv->dbi_base + PCIE_ATU_UPPER_TARGET);
 | |
| 
 | |
| 	writel(type, priv->dbi_base + PCIE_ATU_CR1);
 | |
| 	writel(PCIE_ATU_ENABLE, priv->dbi_base + PCIE_ATU_CR2);
 | |
| }
 | |
| 
 | |
| static int uniphier_pcie_addr_valid(pci_dev_t bdf, int first_busno)
 | |
| {
 | |
| 	/* accept only device {0,1} on first bus */
 | |
| 	if ((PCI_BUS(bdf) != first_busno) || (PCI_DEV(bdf) > 1))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int uniphier_pcie_conf_address(const struct udevice *dev, pci_dev_t bdf,
 | |
| 				      uint offset, void **paddr)
 | |
| {
 | |
| 	struct uniphier_pcie_priv *priv = dev_get_priv(dev);
 | |
| 	u32 busdev;
 | |
| 	int seq = dev_seq(dev);
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = uniphier_pcie_addr_valid(bdf, seq);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	if ((PCI_BUS(bdf) == seq) && !PCI_DEV(bdf)) {
 | |
| 		*paddr = (void *)(priv->dbi_base + offset);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	busdev = PCIE_ATU_BUS(PCI_BUS(bdf) - seq)
 | |
| 		| PCIE_ATU_DEV(PCI_DEV(bdf))
 | |
| 		| PCIE_ATU_FUNC(PCI_FUNC(bdf));
 | |
| 
 | |
| 	pcie_dw_prog_outbound_atu(priv, 0,
 | |
| 				  PCIE_ATU_TYPE_CFG0, (u64)priv->cfg_base,
 | |
| 				  busdev, priv->cfg_size);
 | |
| 	*paddr = (void *)(priv->cfg_base + offset);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int uniphier_pcie_read_config(const struct udevice *dev, pci_dev_t bdf,
 | |
| 				     uint offset, ulong *valp,
 | |
| 				     enum pci_size_t size)
 | |
| {
 | |
| 	return pci_generic_mmap_read_config(dev, uniphier_pcie_conf_address,
 | |
| 					    bdf, offset, valp, size);
 | |
| }
 | |
| 
 | |
| static int uniphier_pcie_write_config(struct udevice *dev, pci_dev_t bdf,
 | |
| 				      uint offset, ulong val,
 | |
| 				      enum pci_size_t size)
 | |
| {
 | |
| 	return pci_generic_mmap_write_config(dev, uniphier_pcie_conf_address,
 | |
| 					     bdf, offset, val, size);
 | |
| }
 | |
| 
 | |
| static void uniphier_pcie_ltssm_enable(struct uniphier_pcie_priv *priv,
 | |
| 				       bool enable)
 | |
| {
 | |
| 	u32 val;
 | |
| 
 | |
| 	val = readl(priv->base + PCL_APP_READY_CTRL);
 | |
| 	if (enable)
 | |
| 		val |= PCL_APP_LTSSM_ENABLE;
 | |
| 	else
 | |
| 		val &= ~PCL_APP_LTSSM_ENABLE;
 | |
| 	writel(val, priv->base + PCL_APP_READY_CTRL);
 | |
| }
 | |
| 
 | |
| static int uniphier_pcie_link_up(struct uniphier_pcie_priv *priv)
 | |
| {
 | |
| 	u32 val, mask;
 | |
| 
 | |
| 	val = readl(priv->base + PCL_STATUS_LINK);
 | |
| 	mask = PCL_RDLH_LINK_UP | PCL_XMLH_LINK_UP;
 | |
| 
 | |
| 	return (val & mask) == mask;
 | |
| }
 | |
| 
 | |
| static int uniphier_pcie_wait_link(struct uniphier_pcie_priv *priv)
 | |
| {
 | |
| 	unsigned long timeout;
 | |
| 
 | |
| 	timeout = get_timer(0) + LINK_UP_TIMEOUT_MS;
 | |
| 
 | |
| 	while (get_timer(0) < timeout) {
 | |
| 		if (uniphier_pcie_link_up(priv))
 | |
| 			return 0;
 | |
| 	}
 | |
| 
 | |
| 	return -ETIMEDOUT;
 | |
| }
 | |
| 
 | |
| static int uniphier_pcie_establish_link(struct uniphier_pcie_priv *priv)
 | |
| {
 | |
| 	if (uniphier_pcie_link_up(priv))
 | |
| 		return 0;
 | |
| 
 | |
| 	uniphier_pcie_ltssm_enable(priv, true);
 | |
| 
 | |
| 	return uniphier_pcie_wait_link(priv);
 | |
| }
 | |
| 
 | |
| static void uniphier_pcie_init_rc(struct uniphier_pcie_priv *priv)
 | |
| {
 | |
| 	u32 val;
 | |
| 
 | |
| 	/* set RC mode */
 | |
| 	val = readl(priv->base + PCL_MODE);
 | |
| 	val |= PCL_MODE_REGEN;
 | |
| 	val &= ~PCL_MODE_REGVAL;
 | |
| 	writel(val, priv->base + PCL_MODE);
 | |
| 
 | |
| 	/* use auxiliary power detection */
 | |
| 	val = readl(priv->base + PCL_APP_PM0);
 | |
| 	val |= PCL_SYS_AUX_PWR_DET;
 | |
| 	writel(val, priv->base + PCL_APP_PM0);
 | |
| 
 | |
| 	/* assert PERST# */
 | |
| 	val = readl(priv->base + PCL_PINCTRL0);
 | |
| 	val &= ~(PCL_PERST_NOE_REGVAL | PCL_PERST_OUT_REGVAL
 | |
| 		 | PCL_PERST_PLDN_REGVAL);
 | |
| 	val |= PCL_PERST_NOE_REGEN | PCL_PERST_OUT_REGEN
 | |
| 		| PCL_PERST_PLDN_REGEN;
 | |
| 	writel(val, priv->base + PCL_PINCTRL0);
 | |
| 
 | |
| 	uniphier_pcie_ltssm_enable(priv, false);
 | |
| 
 | |
| 	mdelay(100);
 | |
| 
 | |
| 	/* deassert PERST# */
 | |
| 	val = readl(priv->base + PCL_PINCTRL0);
 | |
| 	val |= PCL_PERST_OUT_REGVAL | PCL_PERST_OUT_REGEN;
 | |
| 	writel(val, priv->base + PCL_PINCTRL0);
 | |
| }
 | |
| 
 | |
| static void uniphier_pcie_setup_rc(struct uniphier_pcie_priv *priv,
 | |
| 				   struct pci_controller *hose)
 | |
| {
 | |
| 	/* Store the IO and MEM windows settings for future use by the ATU */
 | |
| 	priv->io.phys_start  = hose->regions[0].phys_start; /* IO base */
 | |
| 	priv->io.bus_start   = hose->regions[0].bus_start;  /* IO_bus_addr */
 | |
| 	priv->io.size	     = hose->regions[0].size;	    /* IO size */
 | |
| 	priv->mem.phys_start = hose->regions[1].phys_start; /* MEM base */
 | |
| 	priv->mem.bus_start  = hose->regions[1].bus_start;  /* MEM_bus_addr */
 | |
| 	priv->mem.size	     = hose->regions[1].size;	    /* MEM size */
 | |
| 
 | |
| 	/* outbound: IO */
 | |
| 	pcie_dw_prog_outbound_atu(priv, 0,
 | |
| 				  PCIE_ATU_TYPE_IO, priv->io.phys_start,
 | |
| 				  priv->io.bus_start, priv->io.size);
 | |
| 
 | |
| 	/* outbound: MEM */
 | |
| 	pcie_dw_prog_outbound_atu(priv, 1,
 | |
| 				  PCIE_ATU_TYPE_MEM, priv->mem.phys_start,
 | |
| 				  priv->mem.bus_start, priv->mem.size);
 | |
| }
 | |
| 
 | |
| static int uniphier_pcie_probe(struct udevice *dev)
 | |
| {
 | |
| 	struct uniphier_pcie_priv *priv = dev_get_priv(dev);
 | |
| 	struct udevice *ctlr = pci_get_controller(dev);
 | |
| 	struct pci_controller *hose = dev_get_uclass_priv(ctlr);
 | |
| 	int ret;
 | |
| 
 | |
| 	priv->base = map_physmem(priv->link_res.start,
 | |
| 				 fdt_resource_size(&priv->link_res),
 | |
| 				 MAP_NOCACHE);
 | |
| 	priv->dbi_base = map_physmem(priv->dbi_res.start,
 | |
| 				     fdt_resource_size(&priv->dbi_res),
 | |
| 				     MAP_NOCACHE);
 | |
| 	priv->cfg_size = fdt_resource_size(&priv->cfg_res);
 | |
| 	priv->cfg_base = map_physmem(priv->cfg_res.start,
 | |
| 				     priv->cfg_size, MAP_NOCACHE);
 | |
| 
 | |
| 	ret = clk_enable(&priv->clk);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to enable clk: %d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 	ret = reset_deassert(&priv->rst);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to deassert reset: %d\n", ret);
 | |
| 		goto out_clk_release;
 | |
| 	}
 | |
| 
 | |
| 	ret = generic_phy_init(&priv->phy);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to initialize phy: %d\n", ret);
 | |
| 		goto out_reset_release;
 | |
| 	}
 | |
| 
 | |
| 	ret = generic_phy_power_on(&priv->phy);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to power on phy: %d\n", ret);
 | |
| 		goto out_phy_exit;
 | |
| 	}
 | |
| 
 | |
| 	uniphier_pcie_init_rc(priv);
 | |
| 
 | |
| 	/* set DBI to read only */
 | |
| 	writel(0, priv->dbi_base + PCIE_MISC_CONTROL_1_OFF);
 | |
| 
 | |
| 	uniphier_pcie_setup_rc(priv, hose);
 | |
| 
 | |
| 	if (uniphier_pcie_establish_link(priv)) {
 | |
| 		printf("PCIE-%d: Link down\n", dev_seq(dev));
 | |
| 	} else {
 | |
| 		printf("PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n",
 | |
| 		       dev_seq(dev), pcie_dw_get_link_speed(priv),
 | |
| 		       pcie_dw_get_link_width(priv), hose->first_busno);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| out_phy_exit:
 | |
| 	generic_phy_exit(&priv->phy);
 | |
| out_reset_release:
 | |
| 	reset_release_all(&priv->rst, 1);
 | |
| out_clk_release:
 | |
| 	clk_release_all(&priv->clk, 1);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int uniphier_pcie_of_to_plat(struct udevice *dev)
 | |
| {
 | |
| 	struct uniphier_pcie_priv *priv = dev_get_priv(dev);
 | |
| 	const void *fdt = gd->fdt_blob;
 | |
| 	int node = dev_of_offset(dev);
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = fdt_get_named_resource(fdt, node, "reg", "reg-names",
 | |
| 				     "link", &priv->link_res);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to get link regs: %d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = fdt_get_named_resource(fdt, node, "reg", "reg-names",
 | |
| 				     "dbi", &priv->dbi_res);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to get dbi regs: %d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = fdt_get_named_resource(fdt, node, "reg", "reg-names",
 | |
| 				     "config", &priv->cfg_res);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to get config regs: %d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = clk_get_by_index(dev, 0, &priv->clk);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to get clocks property: %d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = reset_get_by_index(dev, 0, &priv->rst);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to get resets property: %d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = generic_phy_get_by_index(dev, 0, &priv->phy);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to get phy property: %d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct dm_pci_ops uniphier_pcie_ops = {
 | |
| 	.read_config	= uniphier_pcie_read_config,
 | |
| 	.write_config	= uniphier_pcie_write_config,
 | |
| };
 | |
| 
 | |
| static const struct udevice_id uniphier_pcie_ids[] = {
 | |
| 	{ .compatible = "socionext,uniphier-pcie", },
 | |
| 	{ /* Sentinel */ }
 | |
| };
 | |
| 
 | |
| U_BOOT_DRIVER(pcie_uniphier) = {
 | |
| 	.name     = "uniphier-pcie",
 | |
| 	.id       = UCLASS_PCI,
 | |
| 	.of_match = uniphier_pcie_ids,
 | |
| 	.probe    = uniphier_pcie_probe,
 | |
| 	.ops      = &uniphier_pcie_ops,
 | |
| 	.of_to_plat = uniphier_pcie_of_to_plat,
 | |
| 	.priv_auto = sizeof(struct uniphier_pcie_priv),
 | |
| };
 |