|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * FU740 DesignWare PCIe Controller integration |
| 4 | + * Copyright (C) 2019-2021 SiFive, Inc. |
| 5 | + * Paul Walmsley |
| 6 | + * Greentime Hu |
| 7 | + * |
| 8 | + * Based in part on the i.MX6 PCIe host controller shim which is: |
| 9 | + * |
| 10 | + * Copyright (C) 2013 Kosagi |
| 11 | + * https://www.kosagi.com |
| 12 | + */ |
| 13 | + |
| 14 | +#include <linux/clk.h> |
| 15 | +#include <linux/delay.h> |
| 16 | +#include <linux/gpio.h> |
| 17 | +#include <linux/gpio/consumer.h> |
| 18 | +#include <linux/kernel.h> |
| 19 | +#include <linux/mfd/syscon.h> |
| 20 | +#include <linux/module.h> |
| 21 | +#include <linux/pci.h> |
| 22 | +#include <linux/platform_device.h> |
| 23 | +#include <linux/regulator/consumer.h> |
| 24 | +#include <linux/resource.h> |
| 25 | +#include <linux/types.h> |
| 26 | +#include <linux/interrupt.h> |
| 27 | +#include <linux/iopoll.h> |
| 28 | +#include <linux/reset.h> |
| 29 | + |
| 30 | +#include "pcie-designware.h" |
| 31 | + |
| 32 | +#define to_fu740_pcie(x) dev_get_drvdata((x)->dev) |
| 33 | + |
| 34 | +struct fu740_pcie { |
| 35 | + struct dw_pcie pci; |
| 36 | + void __iomem *mgmt_base; |
| 37 | + struct gpio_desc *reset; |
| 38 | + struct gpio_desc *pwren; |
| 39 | + struct clk *pcie_aux; |
| 40 | + struct reset_control *rst; |
| 41 | +}; |
| 42 | + |
| 43 | +#define SIFIVE_DEVICESRESETREG 0x28 |
| 44 | + |
| 45 | +#define PCIEX8MGMT_PERST_N 0x0 |
| 46 | +#define PCIEX8MGMT_APP_LTSSM_ENABLE 0x10 |
| 47 | +#define PCIEX8MGMT_APP_HOLD_PHY_RST 0x18 |
| 48 | +#define PCIEX8MGMT_DEVICE_TYPE 0x708 |
| 49 | +#define PCIEX8MGMT_PHY0_CR_PARA_ADDR 0x860 |
| 50 | +#define PCIEX8MGMT_PHY0_CR_PARA_RD_EN 0x870 |
| 51 | +#define PCIEX8MGMT_PHY0_CR_PARA_RD_DATA 0x878 |
| 52 | +#define PCIEX8MGMT_PHY0_CR_PARA_SEL 0x880 |
| 53 | +#define PCIEX8MGMT_PHY0_CR_PARA_WR_DATA 0x888 |
| 54 | +#define PCIEX8MGMT_PHY0_CR_PARA_WR_EN 0x890 |
| 55 | +#define PCIEX8MGMT_PHY0_CR_PARA_ACK 0x898 |
| 56 | +#define PCIEX8MGMT_PHY1_CR_PARA_ADDR 0x8a0 |
| 57 | +#define PCIEX8MGMT_PHY1_CR_PARA_RD_EN 0x8b0 |
| 58 | +#define PCIEX8MGMT_PHY1_CR_PARA_RD_DATA 0x8b8 |
| 59 | +#define PCIEX8MGMT_PHY1_CR_PARA_SEL 0x8c0 |
| 60 | +#define PCIEX8MGMT_PHY1_CR_PARA_WR_DATA 0x8c8 |
| 61 | +#define PCIEX8MGMT_PHY1_CR_PARA_WR_EN 0x8d0 |
| 62 | +#define PCIEX8MGMT_PHY1_CR_PARA_ACK 0x8d8 |
| 63 | + |
| 64 | +#define PCIEX8MGMT_PHY_CDR_TRACK_EN BIT(0) |
| 65 | +#define PCIEX8MGMT_PHY_LOS_THRSHLD BIT(5) |
| 66 | +#define PCIEX8MGMT_PHY_TERM_EN BIT(9) |
| 67 | +#define PCIEX8MGMT_PHY_TERM_ACDC BIT(10) |
| 68 | +#define PCIEX8MGMT_PHY_EN BIT(11) |
| 69 | +#define PCIEX8MGMT_PHY_INIT_VAL (PCIEX8MGMT_PHY_CDR_TRACK_EN|\ |
| 70 | + PCIEX8MGMT_PHY_LOS_THRSHLD|\ |
| 71 | + PCIEX8MGMT_PHY_TERM_EN|\ |
| 72 | + PCIEX8MGMT_PHY_TERM_ACDC|\ |
| 73 | + PCIEX8MGMT_PHY_EN) |
| 74 | + |
| 75 | +#define PCIEX8MGMT_PHY_LANEN_DIG_ASIC_RX_OVRD_IN_3 0x1008 |
| 76 | +#define PCIEX8MGMT_PHY_LANE_OFF 0x100 |
| 77 | +#define PCIEX8MGMT_PHY_LANE0_BASE (PCIEX8MGMT_PHY_LANEN_DIG_ASIC_RX_OVRD_IN_3 + 0x100 * 0) |
| 78 | +#define PCIEX8MGMT_PHY_LANE1_BASE (PCIEX8MGMT_PHY_LANEN_DIG_ASIC_RX_OVRD_IN_3 + 0x100 * 1) |
| 79 | +#define PCIEX8MGMT_PHY_LANE2_BASE (PCIEX8MGMT_PHY_LANEN_DIG_ASIC_RX_OVRD_IN_3 + 0x100 * 2) |
| 80 | +#define PCIEX8MGMT_PHY_LANE3_BASE (PCIEX8MGMT_PHY_LANEN_DIG_ASIC_RX_OVRD_IN_3 + 0x100 * 3) |
| 81 | + |
| 82 | +static void fu740_pcie_assert_reset(struct fu740_pcie *afp) |
| 83 | +{ |
| 84 | + /* Assert PERST_N GPIO */ |
| 85 | + gpiod_set_value_cansleep(afp->reset, 0); |
| 86 | + /* Assert controller PERST_N */ |
| 87 | + writel_relaxed(0x0, afp->mgmt_base + PCIEX8MGMT_PERST_N); |
| 88 | +} |
| 89 | + |
| 90 | +static void fu740_pcie_deassert_reset(struct fu740_pcie *afp) |
| 91 | +{ |
| 92 | + /* Deassert controller PERST_N */ |
| 93 | + writel_relaxed(0x1, afp->mgmt_base + PCIEX8MGMT_PERST_N); |
| 94 | + /* Deassert PERST_N GPIO */ |
| 95 | + gpiod_set_value_cansleep(afp->reset, 1); |
| 96 | +} |
| 97 | + |
| 98 | +static void fu740_pcie_power_on(struct fu740_pcie *afp) |
| 99 | +{ |
| 100 | + gpiod_set_value_cansleep(afp->pwren, 1); |
| 101 | + /* |
| 102 | + * Ensure that PERST has been asserted for at least 100 ms. |
| 103 | + * Section 2.2 of PCI Express Card Electromechanical Specification |
| 104 | + * Revision 3.0 |
| 105 | + */ |
| 106 | + msleep(100); |
| 107 | +} |
| 108 | + |
| 109 | +static void fu740_pcie_drive_reset(struct fu740_pcie *afp) |
| 110 | +{ |
| 111 | + fu740_pcie_assert_reset(afp); |
| 112 | + fu740_pcie_power_on(afp); |
| 113 | + fu740_pcie_deassert_reset(afp); |
| 114 | +} |
| 115 | + |
| 116 | +static void fu740_phyregwrite(const uint8_t phy, const uint16_t addr, |
| 117 | + const uint16_t wrdata, struct fu740_pcie *afp) |
| 118 | +{ |
| 119 | + struct device *dev = afp->pci.dev; |
| 120 | + void __iomem *phy_cr_para_addr; |
| 121 | + void __iomem *phy_cr_para_wr_data; |
| 122 | + void __iomem *phy_cr_para_wr_en; |
| 123 | + void __iomem *phy_cr_para_ack; |
| 124 | + int ret, val; |
| 125 | + |
| 126 | + /* Setup */ |
| 127 | + if (phy) { |
| 128 | + phy_cr_para_addr = afp->mgmt_base + PCIEX8MGMT_PHY1_CR_PARA_ADDR; |
| 129 | + phy_cr_para_wr_data = afp->mgmt_base + PCIEX8MGMT_PHY1_CR_PARA_WR_DATA; |
| 130 | + phy_cr_para_wr_en = afp->mgmt_base + PCIEX8MGMT_PHY1_CR_PARA_WR_EN; |
| 131 | + phy_cr_para_ack = afp->mgmt_base + PCIEX8MGMT_PHY1_CR_PARA_ACK; |
| 132 | + } else { |
| 133 | + phy_cr_para_addr = afp->mgmt_base + PCIEX8MGMT_PHY0_CR_PARA_ADDR; |
| 134 | + phy_cr_para_wr_data = afp->mgmt_base + PCIEX8MGMT_PHY0_CR_PARA_WR_DATA; |
| 135 | + phy_cr_para_wr_en = afp->mgmt_base + PCIEX8MGMT_PHY0_CR_PARA_WR_EN; |
| 136 | + phy_cr_para_ack = afp->mgmt_base + PCIEX8MGMT_PHY0_CR_PARA_ACK; |
| 137 | + } |
| 138 | + |
| 139 | + writel_relaxed(addr, phy_cr_para_addr); |
| 140 | + writel_relaxed(wrdata, phy_cr_para_wr_data); |
| 141 | + writel_relaxed(1, phy_cr_para_wr_en); |
| 142 | + |
| 143 | + /* Wait for wait_idle */ |
| 144 | + ret = readl_poll_timeout(phy_cr_para_ack, val, val, 10, 5000); |
| 145 | + if (ret) |
| 146 | + dev_warn(dev, "Wait for wait_idle state failed!\n"); |
| 147 | + |
| 148 | + /* Clear */ |
| 149 | + writel_relaxed(0, phy_cr_para_wr_en); |
| 150 | + |
| 151 | + /* Wait for ~wait_idle */ |
| 152 | + ret = readl_poll_timeout(phy_cr_para_ack, val, !val, 10, 5000); |
| 153 | + if (ret) |
| 154 | + dev_warn(dev, "Wait for !wait_idle state failed!\n"); |
| 155 | +} |
| 156 | + |
| 157 | +static void fu740_pcie_init_phy(struct fu740_pcie *afp) |
| 158 | +{ |
| 159 | + /* Enable phy cr_para_sel interfaces */ |
| 160 | + writel_relaxed(0x1, afp->mgmt_base + PCIEX8MGMT_PHY0_CR_PARA_SEL); |
| 161 | + writel_relaxed(0x1, afp->mgmt_base + PCIEX8MGMT_PHY1_CR_PARA_SEL); |
| 162 | + |
| 163 | + /* |
| 164 | + * Wait 10 cr_para cycles to guarantee that the registers are ready |
| 165 | + * to be edited. |
| 166 | + */ |
| 167 | + ndelay(10); |
| 168 | + |
| 169 | + /* Set PHY AC termination mode */ |
| 170 | + fu740_phyregwrite(0, PCIEX8MGMT_PHY_LANE0_BASE, PCIEX8MGMT_PHY_INIT_VAL, afp); |
| 171 | + fu740_phyregwrite(0, PCIEX8MGMT_PHY_LANE1_BASE, PCIEX8MGMT_PHY_INIT_VAL, afp); |
| 172 | + fu740_phyregwrite(0, PCIEX8MGMT_PHY_LANE2_BASE, PCIEX8MGMT_PHY_INIT_VAL, afp); |
| 173 | + fu740_phyregwrite(0, PCIEX8MGMT_PHY_LANE3_BASE, PCIEX8MGMT_PHY_INIT_VAL, afp); |
| 174 | + fu740_phyregwrite(1, PCIEX8MGMT_PHY_LANE0_BASE, PCIEX8MGMT_PHY_INIT_VAL, afp); |
| 175 | + fu740_phyregwrite(1, PCIEX8MGMT_PHY_LANE1_BASE, PCIEX8MGMT_PHY_INIT_VAL, afp); |
| 176 | + fu740_phyregwrite(1, PCIEX8MGMT_PHY_LANE2_BASE, PCIEX8MGMT_PHY_INIT_VAL, afp); |
| 177 | + fu740_phyregwrite(1, PCIEX8MGMT_PHY_LANE3_BASE, PCIEX8MGMT_PHY_INIT_VAL, afp); |
| 178 | +} |
| 179 | + |
| 180 | +static int fu740_pcie_start_link(struct dw_pcie *pci) |
| 181 | +{ |
| 182 | + struct device *dev = pci->dev; |
| 183 | + struct fu740_pcie *afp = dev_get_drvdata(dev); |
| 184 | + |
| 185 | + /* Enable LTSSM */ |
| 186 | + writel_relaxed(0x1, afp->mgmt_base + PCIEX8MGMT_APP_LTSSM_ENABLE); |
| 187 | + return 0; |
| 188 | +} |
| 189 | + |
| 190 | +static int fu740_pcie_host_init(struct pcie_port *pp) |
| 191 | +{ |
| 192 | + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); |
| 193 | + struct fu740_pcie *afp = to_fu740_pcie(pci); |
| 194 | + struct device *dev = pci->dev; |
| 195 | + int ret; |
| 196 | + |
| 197 | + /* Power on reset */ |
| 198 | + fu740_pcie_drive_reset(afp); |
| 199 | + |
| 200 | + /* Enable pcieauxclk */ |
| 201 | + ret = clk_prepare_enable(afp->pcie_aux); |
| 202 | + if (ret) { |
| 203 | + dev_err(dev, "unable to enable pcie_aux clock\n"); |
| 204 | + return ret; |
| 205 | + } |
| 206 | + |
| 207 | + /* |
| 208 | + * Assert hold_phy_rst (hold the controller LTSSM in reset after |
| 209 | + * power_up_rst_n for register programming with cr_para) |
| 210 | + */ |
| 211 | + writel_relaxed(0x1, afp->mgmt_base + PCIEX8MGMT_APP_HOLD_PHY_RST); |
| 212 | + |
| 213 | + /* Deassert power_up_rst_n */ |
| 214 | + ret = reset_control_deassert(afp->rst); |
| 215 | + if (ret) { |
| 216 | + dev_err(dev, "unable to deassert pcie_power_up_rst_n\n"); |
| 217 | + return ret; |
| 218 | + } |
| 219 | + |
| 220 | + fu740_pcie_init_phy(afp); |
| 221 | + |
| 222 | + /* Disable pcieauxclk */ |
| 223 | + clk_disable_unprepare(afp->pcie_aux); |
| 224 | + /* Clear hold_phy_rst */ |
| 225 | + writel_relaxed(0x0, afp->mgmt_base + PCIEX8MGMT_APP_HOLD_PHY_RST); |
| 226 | + /* Enable pcieauxclk */ |
| 227 | + ret = clk_prepare_enable(afp->pcie_aux); |
| 228 | + /* Set RC mode */ |
| 229 | + writel_relaxed(0x4, afp->mgmt_base + PCIEX8MGMT_DEVICE_TYPE); |
| 230 | + |
| 231 | + return 0; |
| 232 | +} |
| 233 | + |
| 234 | +static const struct dw_pcie_host_ops fu740_pcie_host_ops = { |
| 235 | + .host_init = fu740_pcie_host_init, |
| 236 | +}; |
| 237 | + |
| 238 | +static const struct dw_pcie_ops dw_pcie_ops = { |
| 239 | + .start_link = fu740_pcie_start_link, |
| 240 | +}; |
| 241 | + |
| 242 | +static int fu740_pcie_probe(struct platform_device *pdev) |
| 243 | +{ |
| 244 | + struct device *dev = &pdev->dev; |
| 245 | + struct dw_pcie *pci; |
| 246 | + struct fu740_pcie *afp; |
| 247 | + |
| 248 | + afp = devm_kzalloc(dev, sizeof(*afp), GFP_KERNEL); |
| 249 | + if (!afp) |
| 250 | + return -ENOMEM; |
| 251 | + pci = &afp->pci; |
| 252 | + pci->dev = dev; |
| 253 | + pci->ops = &dw_pcie_ops; |
| 254 | + pci->pp.ops = &fu740_pcie_host_ops; |
| 255 | + |
| 256 | + /* SiFive specific region: mgmt */ |
| 257 | + afp->mgmt_base = devm_platform_ioremap_resource_byname(pdev, "mgmt"); |
| 258 | + if (IS_ERR(afp->mgmt_base)) |
| 259 | + return PTR_ERR(afp->mgmt_base); |
| 260 | + |
| 261 | + /* Fetch GPIOs */ |
| 262 | + afp->reset = devm_gpiod_get_optional(dev, "reset-gpios", GPIOD_OUT_LOW); |
| 263 | + if (IS_ERR(afp->reset)) |
| 264 | + return dev_err_probe(dev, PTR_ERR(afp->reset), "unable to get reset-gpios\n"); |
| 265 | + |
| 266 | + afp->pwren = devm_gpiod_get_optional(dev, "pwren-gpios", GPIOD_OUT_LOW); |
| 267 | + if (IS_ERR(afp->pwren)) |
| 268 | + return dev_err_probe(dev, PTR_ERR(afp->pwren), "unable to get pwren-gpios\n"); |
| 269 | + |
| 270 | + /* Fetch clocks */ |
| 271 | + afp->pcie_aux = devm_clk_get(dev, "pcie_aux"); |
| 272 | + if (IS_ERR(afp->pcie_aux)) |
| 273 | + return dev_err_probe(dev, PTR_ERR(afp->pcie_aux), |
| 274 | + "pcie_aux clock source missing or invalid\n"); |
| 275 | + |
| 276 | + /* Fetch reset */ |
| 277 | + afp->rst = devm_reset_control_get_exclusive(dev, NULL); |
| 278 | + if (IS_ERR(afp->rst)) |
| 279 | + return dev_err_probe(dev, PTR_ERR(afp->rst), "unable to get reset\n"); |
| 280 | + |
| 281 | + platform_set_drvdata(pdev, afp); |
| 282 | + |
| 283 | + return dw_pcie_host_init(&pci->pp); |
| 284 | +} |
| 285 | + |
| 286 | +static void fu740_pcie_shutdown(struct platform_device *pdev) |
| 287 | +{ |
| 288 | + struct fu740_pcie *afp = platform_get_drvdata(pdev); |
| 289 | + |
| 290 | + /* Bring down link, so bootloader gets clean state in case of reboot */ |
| 291 | + fu740_pcie_assert_reset(afp); |
| 292 | +} |
| 293 | + |
| 294 | +static const struct of_device_id fu740_pcie_of_match[] = { |
| 295 | + { .compatible = "sifive,fu740-pcie", }, |
| 296 | + {}, |
| 297 | +}; |
| 298 | + |
| 299 | +static struct platform_driver fu740_pcie_driver = { |
| 300 | + .driver = { |
| 301 | + .name = "fu740-pcie", |
| 302 | + .of_match_table = fu740_pcie_of_match, |
| 303 | + .suppress_bind_attrs = true, |
| 304 | + }, |
| 305 | + .probe = fu740_pcie_probe, |
| 306 | + .shutdown = fu740_pcie_shutdown, |
| 307 | +}; |
| 308 | + |
| 309 | +builtin_platform_driver(fu740_pcie_driver); |
0 commit comments