diff options
author | Jaehoon Chung <jh80.chung@samsung.com> | 2015-03-25 16:36:20 +0900 |
---|---|---|
committer | Seung-Woo Kim <sw0312.kim@samsung.com> | 2016-12-14 13:44:33 +0900 |
commit | ecbb3455a80e69109577bde1194356c3ac434962 (patch) | |
tree | e8b35174da0ca7e0868f2c1d8e972eb987a717dc /drivers/pci | |
parent | bb27d58f6012f3308d1ae8de05a24b39e1124b5d (diff) |
local/pci: pci-exynos5433: add host controller driver for exynos5433
To support exynos5433 added the pci-exynos5433.c file.(WIP)
Signed-off-by: Jaehoon Chung <jh80.chung@samsung.com>
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/host/pci-exynos5433.c | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/drivers/pci/host/pci-exynos5433.c b/drivers/pci/host/pci-exynos5433.c new file mode 100644 index 000000000000..31b3e1537061 --- /dev/null +++ b/drivers/pci/host/pci-exynos5433.c @@ -0,0 +1,571 @@ +/* + * PCIe host controller driver for Samsung EXYNOS5433 SoCs + * + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/resource.h> +#include <linux/signal.h> +#include <linux/types.h> +#include <linux/mfd/syscon.h> + +#include "pcie-designware.h" + +#define to_exynos_pcie(x) container_of(x, struct exynos_pcie, pp) + +enum pcie_exynos_type { + PCIE_TYPE_EXYNOS5433, +}; + +static struct pcie_exynos_compatible { + char *compatible; + enum pcie_exynos_type ctrl_type; +} exynos_compat[] = { + { + .compatible = "samsung,exynos5433-pcie", + .ctrl_type = PCIE_TYPE_EXYNOS5433, + }, +}; + +/* Pcie structure for Exynos specific data */ +struct exynos_pcie { + void __iomem *elbi_base; + void __iomem *phy_base; + void __iomem *block_base; + int reset_gpio; + int perst_gpio; + struct clk *clk; + struct clk *bus_clk; + struct pcie_port pp; + struct regmap *pmureg; +}; + +/* PCIe ELBI registers */ +#define PCIE_IRQ_PULSE 0x000 +#define IRQ_INTA_ASSERT BIT(0) +#define IRQ_INTB_ASSERT BIT(2) +#define IRQ_INTC_ASSERT BIT(4) +#define IRQ_INTD_ASSERT BIT(6) +#define IRQ_RADM_PM_TO_ACK BIT(18) +#define PCIE_IRQ_LEVEL 0x004 +#define PCIE_IRQ_SPECIAL 0x008 +#define PCIE_IRQ_EN_PULSE 0x00c +#define PCIE_IRQ_EN_LEVEL 0x010 +#define IRQ_MSI_ENABLE BIT(2) +#define PCIE_IRQ_EN_SPECIAL 0x014 +#define PCIE_SW_WAKE 0x018 +#define PCIE_APP_INIT_RESET 0x028 +#define PCIE_APP_LTSSM_ENABLE 0x02c +#define PCIE_ELBI_LTSSM_DISABLE 0x0 +#define PCIE_ELBI_LTSSM_ENABLE 0x1 +#define PCIE_L1_BUG_FIX_ENABLE 0x038 +#define PCIE_APP_REQ_EXIT_L1 0x040 +#define PCIE_APPS_PM_XMT_TURNOFF 0x04c +#define PCIE_ELBI_RDLH_LINKUP 0x074 +#define PCIE_AUX_PM_EN 0x0A4 +#define AUX_PM_DISABLE 0x0 +#define AUX_PM_ENABLE 0x1 +#define PCIE_ELBI_SLV_AWMISC 0x11c +#define PCIE_ELBI_SLV_ARMISC 0x120 +#define PCIE_ELBI_SLV_DBI_ENABLE BIT(21) + +/* Workaround : PMU phy register offset */ +#define PCIE_PMU_PHY_OFFSET 0x730 + +/* Workaround : Sysreg Fsys register offset and bit */ +#define PCIE_PHY_MAC_RESET 0x208 +#define PCIE_MAC_RESET BIT(4) +#define PCIE_L1SUB_CM_CON 0x1010 +#define PCIE_REFCLK_GATING_EN BIT(0) +#define PCIE_PHY_COMMON_RESET 0x1020 +#define PCIE_PHY_RESET BIT(0) +#define PCIE_PHY_GLOBAL_RESET 0x1040 +#define PCIE_GLOBAL_RESET BIT(0) +#define PCIE_REFCLK BIT(1) +#define PCIE_REFCLK_MASK 0x16 +#define PCIE_APP_REQ_EXIT_L1_MODE BIT(5) + +/* + * Workaround : PHY register offset + * This register naming based on Exynos5433 TRM(Rev0.1) + * Meaning of offset naming and functionality can be difference. + * (In future, needs to fix as exactly naming.) + * So it makes the offset naming with offset's number. + */ +#define PCIE_PHY_OFFSET(x) ((x) * 0x4) + +void exynos_pcie_register_dump(struct pcie_port *pp) +{ + struct exynos_pcie *g_pcie = to_exynos_pcie(pp); + u32 i; + + for (i = 0; i < 0x134; i = i + 4) + printk("link reg %x : %x\n", i, readl(g_pcie->elbi_base + i)); + + for (i = 0; i < 0x60; i++) + printk("phy reg %x * 4: %x\n", i, readl(g_pcie->phy_base + (i * 4))); +} + +static inline void exynos_pcie_writel(void __iomem *base, u32 val, u32 offset) +{ + writel(val, base + offset); +} + +static inline u32 exynos_pcie_readl(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +static void exynos_pcie_clear_irq_pulse(struct pcie_port *pp) +{ + u32 val; + struct exynos_pcie *ep = to_exynos_pcie(pp); + + val = exynos_pcie_readl(ep->elbi_base, PCIE_IRQ_PULSE); + exynos_pcie_writel(ep->elbi_base, val, PCIE_IRQ_PULSE); +} + +static void exynos_pcie_enable_irq_pulse(struct pcie_port *pp) +{ + u32 val; + struct exynos_pcie *ep = to_exynos_pcie(pp); + + /* enable INTX interrupt */ + val = IRQ_INTA_ASSERT | IRQ_INTB_ASSERT | + IRQ_INTC_ASSERT | IRQ_INTD_ASSERT; + exynos_pcie_writel(ep->elbi_base, val, PCIE_IRQ_EN_PULSE); + + exynos_pcie_writel(ep->elbi_base, 0, PCIE_IRQ_EN_LEVEL); + + exynos_pcie_writel(ep->elbi_base, 0, PCIE_IRQ_EN_SPECIAL); +} + +static irqreturn_t exynos_pcie_irq_handler(int irq, void *arg) +{ + struct pcie_port *pp = arg; + + exynos_pcie_clear_irq_pulse(pp); + + return IRQ_HANDLED; +} + +static void exynos_pcie_sideband_dbi_w_mode(struct pcie_port *pp, bool on) +{ + u32 val; + struct exynos_pcie *ep = to_exynos_pcie(pp); + + val = exynos_pcie_readl(ep->elbi_base, PCIE_ELBI_SLV_AWMISC); + if (on) + val |= PCIE_ELBI_SLV_DBI_ENABLE; + else + val &= ~PCIE_ELBI_SLV_DBI_ENABLE; + exynos_pcie_writel(ep->elbi_base, val, PCIE_ELBI_SLV_AWMISC); +} + +static void exynos_pcie_sideband_dbi_r_mode(struct pcie_port *pp, bool on) +{ + u32 val; + struct exynos_pcie *ep = to_exynos_pcie(pp); + + val = exynos_pcie_readl(ep->elbi_base, PCIE_ELBI_SLV_ARMISC); + if (on) + val |= PCIE_ELBI_SLV_DBI_ENABLE; + else + val &= ~PCIE_ELBI_SLV_DBI_ENABLE; + exynos_pcie_writel(ep->elbi_base, val, PCIE_ELBI_SLV_ARMISC); +} + +static void exynos_pcie_init_phy(struct pcie_port *pp) +{ + struct exynos_pcie *ep = to_exynos_pcie(pp); + + exynos_pcie_writel(ep->phy_base, 0x11, PCIE_PHY_OFFSET(0x3)); + + /* band gap reference on */ + exynos_pcie_writel(ep->phy_base, 0, PCIE_PHY_OFFSET(0x20)); + exynos_pcie_writel(ep->phy_base, 0, PCIE_PHY_OFFSET(0x4b)); + + /* jitter tunning */ + exynos_pcie_writel(ep->phy_base, 0x34, PCIE_PHY_OFFSET(0x4)); + exynos_pcie_writel(ep->phy_base, 0x02, PCIE_PHY_OFFSET(0x7)); + exynos_pcie_writel(ep->phy_base, 0x41, PCIE_PHY_OFFSET(0x21)); + exynos_pcie_writel(ep->phy_base, 0x7F, PCIE_PHY_OFFSET(0x14)); + exynos_pcie_writel(ep->phy_base, 0xC0, PCIE_PHY_OFFSET(0x15)); + exynos_pcie_writel(ep->phy_base, 0x61, PCIE_PHY_OFFSET(0x36)); + + /* D0 uninit.. */ + exynos_pcie_writel(ep->phy_base, 0x44, PCIE_PHY_OFFSET(0x3D)); + + /* 24MHz */ + exynos_pcie_writel(ep->phy_base, 0x94, PCIE_PHY_OFFSET(0x8)); + exynos_pcie_writel(ep->phy_base, 0xA7, PCIE_PHY_OFFSET(0x9)); + exynos_pcie_writel(ep->phy_base, 0x93, PCIE_PHY_OFFSET(0xA)); + exynos_pcie_writel(ep->phy_base, 0x6B, PCIE_PHY_OFFSET(0xC)); + exynos_pcie_writel(ep->phy_base, 0xA5, PCIE_PHY_OFFSET(0xF)); + exynos_pcie_writel(ep->phy_base, 0x34, PCIE_PHY_OFFSET(0x16)); + exynos_pcie_writel(ep->phy_base, 0xA3, PCIE_PHY_OFFSET(0x17)); + exynos_pcie_writel(ep->phy_base, 0xA7, PCIE_PHY_OFFSET(0x1A)); + exynos_pcie_writel(ep->phy_base, 0x71, PCIE_PHY_OFFSET(0x23)); + exynos_pcie_writel(ep->phy_base, 0x4C, PCIE_PHY_OFFSET(0x24)); + + exynos_pcie_writel(ep->phy_base, 0x0E, PCIE_PHY_OFFSET(0x26)); + exynos_pcie_writel(ep->phy_base, 0x14, PCIE_PHY_OFFSET(0x7)); + exynos_pcie_writel(ep->phy_base, 0x48, PCIE_PHY_OFFSET(0x43)); + exynos_pcie_writel(ep->phy_base, 0x44, PCIE_PHY_OFFSET(0x44)); + exynos_pcie_writel(ep->phy_base, 0x03, PCIE_PHY_OFFSET(0x45)); + exynos_pcie_writel(ep->phy_base, 0xA7, PCIE_PHY_OFFSET(0x48)); + exynos_pcie_writel(ep->phy_base, 0x13, PCIE_PHY_OFFSET(0x54)); + exynos_pcie_writel(ep->phy_base, 0x04, PCIE_PHY_OFFSET(0x31)); + exynos_pcie_writel(ep->phy_base, 0, PCIE_PHY_OFFSET(0x32)); +} + +static void exynos_pcie_assert_phy_reset(struct pcie_port *pp) +{ + struct exynos_pcie *ep = to_exynos_pcie(pp); + u32 val; + + val = exynos_pcie_readl(ep->block_base, PCIE_PHY_COMMON_RESET); + val |= PCIE_PHY_RESET; + exynos_pcie_writel(ep->block_base, val, PCIE_PHY_COMMON_RESET); + + /* PHY Mac Reset */ + val = exynos_pcie_readl(ep->block_base, PCIE_PHY_MAC_RESET); + val &= ~PCIE_MAC_RESET; + exynos_pcie_writel(ep->block_base, val, PCIE_PHY_MAC_RESET); + + + /* PHY refclk 24MHz */ + val = exynos_pcie_readl(ep->block_base, PCIE_PHY_GLOBAL_RESET); + val &= ~PCIE_REFCLK_MASK; + val |= PCIE_REFCLK; + exynos_pcie_writel(ep->block_base, val, PCIE_PHY_GLOBAL_RESET); + + /* PHY Global Reset */ + val = exynos_pcie_readl(ep->block_base, PCIE_PHY_GLOBAL_RESET); + val &= ~PCIE_GLOBAL_RESET; + exynos_pcie_writel(ep->block_base, val, PCIE_PHY_GLOBAL_RESET); + + /* initialize phy */ + exynos_pcie_init_phy(pp); + + /* PHY Common Reset */ + val = exynos_pcie_readl(ep->block_base, PCIE_PHY_COMMON_RESET); + val &= ~PCIE_PHY_RESET; + exynos_pcie_writel(ep->block_base, val, PCIE_PHY_COMMON_RESET); + + /* PHY Mac Reset */ + val = exynos_pcie_readl(ep->block_base, PCIE_PHY_MAC_RESET); + val |= PCIE_MAC_RESET; + exynos_pcie_writel(ep->block_base, val, PCIE_PHY_MAC_RESET); + + + val = exynos_pcie_readl(ep->elbi_base, PCIE_SW_WAKE); + val &= ~(0x1 << 1); + exynos_pcie_writel(ep->elbi_base, val, PCIE_SW_WAKE); +} + +static int exynos_pcie_reset(struct pcie_port *pp) +{ + struct exynos_pcie *ep = to_exynos_pcie(pp); + u32 val; + + if (ep->perst_gpio) { + gpio_set_value(ep->perst_gpio, 0); + udelay(20); + gpio_set_value(ep->perst_gpio, 1); + } + + + exynos_pcie_assert_phy_reset(pp); + + /* Setup root complext */ + dw_pcie_setup_rc(pp); + + if (ep->reset_gpio) { + gpio_set_value(ep->reset_gpio, 1); + msleep(80); + } + + /* assert LTSSM enable */ + exynos_pcie_writel(ep->elbi_base, PCIE_ELBI_LTSSM_ENABLE, + PCIE_APP_LTSSM_ENABLE); + + val = exynos_pcie_readl(ep->elbi_base, PCIE_IRQ_LEVEL); + if (val & 0x10) { + writel(0x0c200004, ep->pp.va_cfg0_base + 0x10); + writel(0x0c400004, ep->pp.va_cfg0_base + 0x18); + return 0; + } + + return -EPIPE; +} + +static int exynos_pcie_establish_link(struct pcie_port *pp) +{ + struct exynos_pcie *ep = to_exynos_pcie(pp); + u32 val; + int count = 10, ret; + + if (dw_pcie_link_up(pp)) { + dev_info(pp->dev, "Link already up\n"); + return 0; + } + + /* PMU control at here */ + if (ep->pmureg) { + if (regmap_update_bits(ep->pmureg, PCIE_PMU_PHY_OFFSET, BIT(0), 1)) + dev_warn(pp->dev, "Failed to update regmap bit.\n"); + } + + val = exynos_pcie_readl(ep->block_base, PCIE_PHY_GLOBAL_RESET); + val &= ~PCIE_APP_REQ_EXIT_L1_MODE; + exynos_pcie_writel(ep->block_base, val, PCIE_PHY_GLOBAL_RESET); + + val = exynos_pcie_readl(ep->block_base, PCIE_L1SUB_CM_CON); + val &= ~PCIE_REFCLK_GATING_EN; + exynos_pcie_writel(ep->block_base, val, PCIE_L1SUB_CM_CON); + + exynos_pcie_assert_phy_reset(pp); + + /* Setup root complext */ + dw_pcie_setup_rc(pp); + + /* assert LTSSM enable */ + exynos_pcie_writel(ep->elbi_base, PCIE_ELBI_LTSSM_ENABLE, + PCIE_APP_LTSSM_ENABLE); + + while (!dw_pcie_link_up(pp)) { + usleep_range(100, 1000); + if (--count) { + ret = exynos_pcie_reset(pp); + if (!ret) + break; + continue; + } + + /* Workaround */ + exynos_pcie_register_dump(pp); + dev_err(pp->dev, "Failed PCIe Link up!\n"); + return -EPIPE; + } + + dev_info(pp->dev, "Link up!\n"); + return 0; +} + +static int exynos_pcie_link_up(struct pcie_port *pp) +{ + struct exynos_pcie *ep = to_exynos_pcie(pp); + u32 val; + + val = exynos_pcie_readl(ep->elbi_base, PCIE_ELBI_RDLH_LINKUP); + if (val == PCIE_ELBI_LTSSM_ENABLE) + return 1; + + return 0; +} + +static int exynos_pcie_host_init(struct pcie_port *pp) +{ + exynos_pcie_enable_irq_pulse(pp); + + return exynos_pcie_establish_link(pp); +} + +static inline void exynos_pcie_readl_rc(struct pcie_port *pp, + void __iomem *dbi_base, u32 *val) +{ + exynos_pcie_sideband_dbi_r_mode(pp, true); + *val = readl(dbi_base); + exynos_pcie_sideband_dbi_r_mode(pp, false); +} + +static inline void exynos_pcie_writel_rc(struct pcie_port *pp, + u32 val, void __iomem *dbi_base) +{ + exynos_pcie_sideband_dbi_w_mode(pp, true); + writel(val, dbi_base); + exynos_pcie_sideband_dbi_w_mode(pp, false); +} + +static int exynos_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, + u32 *val) +{ + int ret; + + exynos_pcie_sideband_dbi_r_mode(pp, true); + ret = dw_pcie_cfg_read(pp->dbi_base + (where & ~0x3), where, size, val); + exynos_pcie_sideband_dbi_r_mode(pp, false); + return ret; +} + +static int exynos_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, + u32 val) +{ + int ret; + + exynos_pcie_sideband_dbi_w_mode(pp, true); + ret = dw_pcie_cfg_write(pp->dbi_base + (where & ~0x3), + where, size, val); + exynos_pcie_sideband_dbi_w_mode(pp, false); + return ret; +} + +static struct pcie_host_ops exynos_pcie_host_ops = { + .readl_rc = exynos_pcie_readl_rc, + .writel_rc = exynos_pcie_writel_rc, + .rd_own_conf = exynos_pcie_rd_own_conf, + .wr_own_conf = exynos_pcie_wr_own_conf, + .host_init = exynos_pcie_host_init, + .link_up = exynos_pcie_link_up, +}; + +static int exynos_pcie_probe(struct platform_device *pdev) +{ + struct exynos_pcie *exynos_pcie; + struct device_node *np = pdev->dev.of_node; + struct pcie_port *pp; + struct resource *res; + int ret; + + exynos_pcie = devm_kzalloc(&pdev->dev, sizeof(*exynos_pcie), + GFP_KERNEL); + if (!exynos_pcie) + return -ENOMEM; + + pp = &exynos_pcie->pp; + pp->dev = &pdev->dev; + + exynos_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0); + exynos_pcie->perst_gpio = of_get_named_gpio(np, "perst-gpio", 0); + + exynos_pcie->clk = devm_clk_get(&pdev->dev, "pcie"); + if (IS_ERR(exynos_pcie->clk)) { + dev_err(&pdev->dev, "Failed to get pcie rc clock\n"); + return PTR_ERR(exynos_pcie->clk); + } + ret = clk_prepare_enable(exynos_pcie->clk); + if (ret) + return ret; + + exynos_pcie->bus_clk = devm_clk_get(&pdev->dev, "pcie_bus"); + if (IS_ERR(exynos_pcie->bus_clk)) { + dev_err(&pdev->dev, "Failed to get pcie bus clock\n"); + ret = PTR_ERR(exynos_pcie->bus_clk); + goto fail_clk; + } + ret = clk_prepare_enable(exynos_pcie->bus_clk); + if (ret) + goto fail_clk; + + /* Application Register */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + exynos_pcie->elbi_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(exynos_pcie->elbi_base)) { + ret = PTR_ERR(exynos_pcie->elbi_base); + goto fail_bus_clk; + } + + /* Physical Layer Register */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + exynos_pcie->phy_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(exynos_pcie->phy_base)) { + ret = PTR_ERR(exynos_pcie->phy_base); + goto fail_bus_clk; + } + + /* Workaround Block register(System register) */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + exynos_pcie->block_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(exynos_pcie->block_base)) + goto fail_bus_clk; + + exynos_pcie->pmureg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "samsung,pmureg-phandle"); + if (IS_ERR(exynos_pcie->pmureg)) { + dev_warn(&pdev->dev, "Pmureg syscon regmap lookup failed.\n"); + exynos_pcie->pmureg = NULL; + } + + pp->irq = platform_get_irq(pdev, 0); + if (!pp->irq) { + dev_err(&pdev->dev, "failed to get irq\n"); + ret = -ENODEV; + goto fail_bus_clk; + } + ret = devm_request_irq(&pdev->dev, pp->irq, exynos_pcie_irq_handler, + IRQF_SHARED, "exynos-pcie", pp); + if (ret) { + dev_err(&pdev->dev, "failed to request irq\n"); + goto fail_bus_clk; + } + + pp->root_bus_nr = -1; + pp->ops = &exynos_pcie_host_ops; + + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(&pdev->dev, "failed to initialize host\n"); + goto fail_bus_clk; + } + + platform_set_drvdata(pdev, exynos_pcie); + + return 0; + +fail_bus_clk: + clk_disable_unprepare(exynos_pcie->bus_clk); +fail_clk: + clk_disable_unprepare(exynos_pcie->clk); + return ret; + +} + +static int exynos_pcie_remove(struct platform_device *pdev) +{ + struct exynos_pcie *ep = platform_get_drvdata(pdev); + + clk_disable_unprepare(ep->bus_clk); + clk_disable_unprepare(ep->clk); + + return 0; +} + +static const struct of_device_id exynos_pcie_of_match[] = { + { .compatible = "samsung,exynos5433-pcie", }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_pcie_of_match); + +static struct platform_driver exynos_pcie_driver = { + .remove = exynos_pcie_remove, + .driver = { + .name = "exynos-pcie", + .of_match_table = exynos_pcie_of_match, + }, +}; + +static int __init exynos_pcie_init(void) +{ + return platform_driver_probe(&exynos_pcie_driver, exynos_pcie_probe); +} +late_initcall(exynos_pcie_init); + +MODULE_AUTHOR("Jingoo Han<jg1.han@samsung.com>"); +MODULE_AUTHOR("Jaehoon CHung <jh80.chung@samsung.com>"); +MODULE_LICENSE("GPL v2"); |