Hi Vidya,Done.
Wow, there's a lot of nice work here! Thanks for that!
On Tue, Mar 26, 2019 at 08:43:26PM +0530, Vidya Sagar wrote:
Add support for Synopsys DesignWare core IP based PCIe host controller
present in Tegra194 SoC.
General comments:
- There are a few multi-line comments that don't match the
prevailing style:
/*
* Text...
*/
- Comments and dev_info()/dev_err() messages are inconsistent about
starting with upper-case or lower-case letters.
- Use "MSI", "IRQ", "PCIe", "CPU", etc in comments and messages.
- There are a few functions that use "&pdev->dev" many times; can
you add a "struct device *dev = &pdev->dev" to reduce the
redundancy?
This is for pcie_pme_disable_msi() API. Since this is defined in portdrv.h
+#include "../../pcie/portdrv.h"
What's this for? I didn't see any obvious uses of things from
portdrv.h, but I didn't actually try to build without it.
Done.
+struct tegra_pcie_dw {
+ struct device *dev;
+ struct resource *appl_res;
+ struct resource *dbi_res;
+ struct resource *atu_dma_res;
+ void __iomem *appl_base;
+ struct clk *core_clk;
+ struct reset_control *core_apb_rst;
+ struct reset_control *core_rst;
+ struct dw_pcie pci;
+ enum dw_pcie_device_mode mode;
+
+ bool disable_clock_request;
+ bool power_down_en;
+ u8 init_link_width;
+ bool link_state;
+ u32 msi_ctrl_int;
+ u32 num_lanes;
+ u32 max_speed;
+ u32 init_speed;
+ bool cdm_check;
+ u32 cid;
+ int pex_wake;
+ bool update_fc_fixup;
+ int n_gpios;
+ int *gpios;
+#if defined(CONFIG_PCIEASPM)
+ u32 cfg_link_cap_l1sub;
+ u32 event_cntr_ctrl;
+ u32 event_cntr_data;
+ u32 aspm_cmrt;
+ u32 aspm_pwr_on_t;
+ u32 aspm_l0s_enter_lat;
+ u32 disabled_aspm_states;
+#endif
The above could be indented the same as the rest of the struct?
Done.
+ struct regulator *pex_ctl_reg;
+
+ int phy_count;
+ struct phy **phy;
+
+ struct dentry *debugfs;
+};
+static void apply_bad_link_workaround(struct pcie_port *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ struct tegra_pcie_dw *pcie = dw_pcie_to_tegra_pcie(pci);
+ u16 val;
+
+ /*
+ * NOTE:- Since this scenario is uncommon and link as
+ * such is not stable anyway, not waiting to confirm
+ * if link is really transiting to Gen-2 speed
s/transiting/transitioning/
I think there are other uses of "transit" when you mean "transition".
Done.
+static int tegra_pcie_dw_rd_own_conf(struct pcie_port *pp, int where, int size,
+ u32 *val)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+
+ /*
+ * This is an endpoint mode specific register happen to appear even
+ * when controller is operating in root port mode and system hangs
+ * when it is accessed with link being in ASPM-L1 state.
+ * So skip accessing it altogether
+ */
+ if (where == PORT_LOGIC_MSIX_DOORBELL) {
+ *val = 0x00000000;
+ return PCIBIOS_SUCCESSFUL;
+ } else {
+ return dw_pcie_read(pci->dbi_base + where, size, val);
+ }
+}
+
+static int tegra_pcie_dw_wr_own_conf(struct pcie_port *pp, int where, int size,
+ u32 val)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+
+ /* This is EP specific register and system hangs when it is
+ * accessed with link being in ASPM-L1 state.
+ * So skip accessing it altogether
+ */
+ if (where == PORT_LOGIC_MSIX_DOORBELL)
+ return PCIBIOS_SUCCESSFUL;
+ else
+ return dw_pcie_write(pci->dbi_base + where, size, val);
These two functions are almost identical and they could look more
similar. This one has the wrong multi-line comment style, uses "EP"
instead of "endpoint", etc. Use this style for the "if" since the
first case is really an error case:
if (where == PORT_LOGIC_MSIX_DOORBELL) {
...
return ...;
}
return dw_pcie_...();
Done.
+static int tegra_pcie_dw_host_init(struct pcie_port *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ struct tegra_pcie_dw *pcie = dw_pcie_to_tegra_pcie(pci);
+ int count = 200;
+ u32 val, tmp, offset;
+ u16 val_w;
+
+#if defined(CONFIG_PCIEASPM)
+ pcie->cfg_link_cap_l1sub =
+ dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_L1SS) +
+ PCI_L1SS_CAP;
+#endif
+ val = dw_pcie_readl_dbi(pci, PCI_IO_BASE);
+ val &= ~(IO_BASE_IO_DECODE | IO_BASE_IO_DECODE_BIT8);
+ dw_pcie_writel_dbi(pci, PCI_IO_BASE, val);
+
+ val = dw_pcie_readl_dbi(pci, PCI_PREF_MEMORY_BASE);
+ val |= CFG_PREF_MEM_LIMIT_BASE_MEM_DECODE;
+ val |= CFG_PREF_MEM_LIMIT_BASE_MEM_LIMIT_DECODE;
+ dw_pcie_writel_dbi(pci, PCI_PREF_MEMORY_BASE, val);
+
+ dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0);
+
+ /* Configure FTS */
+ val = dw_pcie_readl_dbi(pci, PORT_LOGIC_ACK_F_ASPM_CTRL);
+ val &= ~(N_FTS_MASK << N_FTS_SHIFT);
+ val |= N_FTS_VAL << N_FTS_SHIFT;
+ dw_pcie_writel_dbi(pci, PORT_LOGIC_ACK_F_ASPM_CTRL, val);
+
+ val = dw_pcie_readl_dbi(pci, PORT_LOGIC_GEN2_CTRL);
+ val &= ~FTS_MASK;
+ val |= FTS_VAL;
+ dw_pcie_writel_dbi(pci, PORT_LOGIC_GEN2_CTRL, val);
+
+ /* Enable as 0xFFFF0001 response for CRS */
+ val = dw_pcie_readl_dbi(pci, PORT_LOGIC_AMBA_ERROR_RESPONSE_DEFAULT);
+ val &= ~(AMBA_ERROR_RESPONSE_CRS_MASK << AMBA_ERROR_RESPONSE_CRS_SHIFT);
+ val |= (AMBA_ERROR_RESPONSE_CRS_OKAY_FFFF0001 <<
+ AMBA_ERROR_RESPONSE_CRS_SHIFT);
+ dw_pcie_writel_dbi(pci, PORT_LOGIC_AMBA_ERROR_RESPONSE_DEFAULT, val);
+
+ /* Set MPS to 256 in DEV_CTL */
+ val = dw_pcie_readl_dbi(pci, CFG_DEV_STATUS_CONTROL);
+ val &= ~PCI_EXP_DEVCTL_PAYLOAD;
+ val |= (1 << CFG_DEV_STATUS_CONTROL_MPS_SHIFT);
+ dw_pcie_writel_dbi(pci, CFG_DEV_STATUS_CONTROL, val);
+
+ /* Configure Max Speed from DT */
+ val = dw_pcie_readl_dbi(pci, CFG_LINK_CAP);
+ val &= ~PCI_EXP_LNKCAP_SLS;
+ val |= pcie->max_speed;
+ dw_pcie_writel_dbi(pci, CFG_LINK_CAP, val);
+
+ val = dw_pcie_readw_dbi(pci, CFG_LINK_CONTROL_2);
+ val &= ~PCI_EXP_LNKCTL2_TLS;
+ val |= pcie->init_speed;
+ dw_pcie_writew_dbi(pci, CFG_LINK_CONTROL_2, val);
+
+ /* Configure Max lane width from DT */
+ val = dw_pcie_readl_dbi(pci, CFG_LINK_CAP);
+ val &= ~PCI_EXP_LNKCAP_MLW;
+ val |= (pcie->num_lanes << PCI_EXP_LNKSTA_NLW_SHIFT);
+ dw_pcie_writel_dbi(pci, CFG_LINK_CAP, val);
+
+ config_gen3_gen4_eq_presets(pcie);
+
+#if defined(CONFIG_PCIEASPM)
+ /* Enable ASPM counters */
+ val = EVENT_COUNTER_ENABLE_ALL << EVENT_COUNTER_ENABLE_SHIFT;
+ val |= EVENT_COUNTER_GROUP_5 << EVENT_COUNTER_GROUP_SEL_SHIFT;
+ dw_pcie_writel_dbi(pci, pcie->event_cntr_ctrl, val);
+
+ /* Program T_cmrt and T_pwr_on values */
+ val = dw_pcie_readl_dbi(pci, pcie->cfg_link_cap_l1sub);
+ val &= ~(PCI_L1SS_CAP_CM_RESTORE_TIME | PCI_L1SS_CAP_P_PWR_ON_VALUE);
+ val |= (pcie->aspm_cmrt << PCI_L1SS_CAP_CM_RTM_SHIFT);
+ val |= (pcie->aspm_pwr_on_t << PCI_L1SS_CAP_PWRN_VAL_SHIFT);
+ dw_pcie_writel_dbi(pci, pcie->cfg_link_cap_l1sub, val);
+
+ /* Program L0s and L1 entrance latencies */
+ val = dw_pcie_readl_dbi(pci, PORT_LOGIC_ACK_F_ASPM_CTRL);
+ val &= ~L0S_ENTRANCE_LAT_MASK;
+ val |= (pcie->aspm_l0s_enter_lat << L0S_ENTRANCE_LAT_SHIFT);
+ val |= ENTER_ASPM;
+ dw_pcie_writel_dbi(pci, PORT_LOGIC_ACK_F_ASPM_CTRL, val);
+
+ /* Program what ASPM states sould get advertised */
s/sould/should/
This is the only place where DLF offset is needed and gets calculated and this
+ if (pcie->disabled_aspm_states & 0x1)
+ disable_aspm_l0s(pcie); /* Disable L0s */
+ if (pcie->disabled_aspm_states & 0x2) {
+ disable_aspm_l10(pcie); /* Disable L1 */
+ disable_aspm_l11(pcie); /* Disable L1.1 */
+ disable_aspm_l12(pcie); /* Disable L1.2 */
+ }
+ if (pcie->disabled_aspm_states & 0x4)
+ disable_aspm_l11(pcie); /* Disable L1.1 */
+ if (pcie->disabled_aspm_states & 0x8)
+ disable_aspm_l12(pcie); /* Disable L1.2 */
+#endif
+ val = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF);
+ val &= ~GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL;
+ dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val);
+
+ if (pcie->update_fc_fixup) {
+ val = dw_pcie_readl_dbi(pci, CFG_TIMER_CTRL_MAX_FUNC_NUM_OFF);
+ val |= 0x1 << CFG_TIMER_CTRL_ACK_NAK_SHIFT;
+ dw_pcie_writel_dbi(pci, CFG_TIMER_CTRL_MAX_FUNC_NUM_OFF, val);
+ }
+
+ /* CDM check enable */
+ if (pcie->cdm_check) {
+ val = dw_pcie_readl_dbi(pci,
+ PORT_LOGIC_PL_CHK_REG_CONTROL_STATUS);
+ val |= PORT_LOGIC_PL_CHK_REG_CHK_REG_CONTINUOUS;
+ val |= PORT_LOGIC_PL_CHK_REG_CHK_REG_START;
+ dw_pcie_writel_dbi(pci, PORT_LOGIC_PL_CHK_REG_CONTROL_STATUS,
+ val);
+ }
+
+ dw_pcie_setup_rc(pp);
+
+ clk_set_rate(pcie->core_clk, GEN4_CORE_CLK_FREQ);
+
+ /* assert RST */
+ val = readl(pcie->appl_base + APPL_PINMUX);
+ val &= ~APPL_PINMUX_PEX_RST;
+ writel(val, pcie->appl_base + APPL_PINMUX);
+
+ usleep_range(100, 200);
+
+ /* enable LTSSM */
+ val = readl(pcie->appl_base + APPL_CTRL);
+ val |= APPL_CTRL_LTSSM_EN;
+ writel(val, pcie->appl_base + APPL_CTRL);
+
+ /* de-assert RST */
+ val = readl(pcie->appl_base + APPL_PINMUX);
+ val |= APPL_PINMUX_PEX_RST;
+ writel(val, pcie->appl_base + APPL_PINMUX);
+
+ msleep(100);
+
+ val_w = dw_pcie_readw_dbi(pci, CFG_LINK_STATUS);
+ while (!(val_w & PCI_EXP_LNKSTA_DLLLA)) {
+ if (!count) {
+ val = readl(pcie->appl_base + APPL_DEBUG);
+ val &= APPL_DEBUG_LTSSM_STATE_MASK;
+ val >>= APPL_DEBUG_LTSSM_STATE_SHIFT;
+ tmp = readl(pcie->appl_base + APPL_LINK_STATUS);
+ tmp &= APPL_LINK_STATUS_RDLH_LINK_UP;
+ if (val == 0x11 && !tmp) {
+ dev_info(pci->dev, "link is down in DLL");
+ dev_info(pci->dev,
+ "trying again with DLFE disabled\n");
+ /* disable LTSSM */
+ val = readl(pcie->appl_base + APPL_CTRL);
+ val &= ~APPL_CTRL_LTSSM_EN;
+ writel(val, pcie->appl_base + APPL_CTRL);
+
+ reset_control_assert(pcie->core_rst);
+ reset_control_deassert(pcie->core_rst);
+
+ offset =
+ dw_pcie_find_ext_capability(pci,
+ PCI_EXT_CAP_ID_DLF)
+ + PCI_DLF_CAP;
This capability offset doesn't change, does it? Could it be computed
outside the loop?
Again, this recursive calling comes into picture only for a legacy ASMedia
+ val = dw_pcie_readl_dbi(pci, offset);
+ val &= ~DL_FEATURE_EXCHANGE_EN;
+ dw_pcie_writel_dbi(pci, offset, val);
+
+ tegra_pcie_dw_host_init(&pcie->pci.pp);
This looks like some sort of "wait for link up" retry loop, but a
recursive call seems a little unusual. My 5 second analysis is that
the loop could run this 200 times, and you sure don't want the
possibility of a 200-deep call chain. Is there way to split out the
host init from the link-up polling?
Agree. At least whatever I'm going here as part of scan_bus can be moved to
+ return 0;
+ }
+ dev_info(pci->dev, "link is down\n");
+ return 0;
+ }
+ dev_dbg(pci->dev, "polling for link up\n");
+ usleep_range(1000, 2000);
+ val_w = dw_pcie_readw_dbi(pci, CFG_LINK_STATUS);
+ count--;
+ }
+ dev_info(pci->dev, "link is up\n");
+
+ tegra_pcie_enable_interrupts(pp);
+
+ return 0;
+}
+static void tegra_pcie_dw_scan_bus(struct pcie_port *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ struct tegra_pcie_dw *pcie = dw_pcie_to_tegra_pcie(pci);
+ u32 speed;
+
+ if (!tegra_pcie_dw_link_up(pci))
+ return;
+
+ speed = (dw_pcie_readw_dbi(pci, CFG_LINK_STATUS) & PCI_EXP_LNKSTA_CLS);
+ clk_set_rate(pcie->core_clk, pcie_gen_freq[speed - 1]);
I don't understand what's happening here. This is named
tegra_pcie_dw_scan_bus(), but it doesn't actually scan anything.
Maybe it's just a bad name for the dw_pcie_host_ops hook
(ks_pcie_v3_65_scan_bus() is the only other .scan_bus()
implementation, and it doesn't scan anything either).
dw_pcie_host_init() calls pci_scan_root_bus_bridge(), which actually
*does* scan the bus, but it does it before calling
pp->ops->scan_bus(). I'd say by the time we get to
pci_scan_root_bus_bridge(), the device-specific init should be all
done and we should be using only generic PCI core interfaces.
Maybe this stuff could/should be done in the ->host_init() hook? The
code between ->host_init() and ->scan_bus() is all generic code with
no device-specific stuff, so I don't know why we need both hooks.
Done
+static int tegra_pcie_enable_phy(struct tegra_pcie_dw *pcie)
+{
+ int phy_count = pcie->phy_count;
+ int ret;
+ int i;
+
+ for (i = 0; i < phy_count; i++) {
+ ret = phy_init(pcie->phy[i]);
+ if (ret < 0)
+ goto err_phy_init;
+
+ ret = phy_power_on(pcie->phy[i]);
+ if (ret < 0) {
+ phy_exit(pcie->phy[i]);
+ goto err_phy_power_on;
+ }
+ }
+
+ return 0;
+
+ while (i >= 0) {
+ phy_power_off(pcie->phy[i]);
+err_phy_power_on:
+ phy_exit(pcie->phy[i]);
+err_phy_init:
+ i--;
+ }
Wow, jumping into the middle of that loop is clever ;) Can't decide
what I think of it, but it's certainly clever!
+ return ret;
+}
+
+static int tegra_pcie_dw_parse_dt(struct tegra_pcie_dw *pcie)
+{
+ struct device_node *np = pcie->dev->of_node;
+ int ret;
+
+#if defined(CONFIG_PCIEASPM)
+ ret = of_property_read_u32(np, "nvidia,event-cntr-ctrl",
+ &pcie->event_cntr_ctrl);
+ if (ret < 0) {
+ dev_err(pcie->dev, "fail to read event-cntr-ctrl: %d\n", ret);
+ return ret;
+ }
The fact that you return error here if DT lacks the
"nvidia,event-cntr-ctrl" property, but only if CONFIG_PCIEASPM=y,
means that you have a revlock between the DT and the kernel: if you
update the kernel to enable CONFIG_PCIEASPM, you may also have to
update your DT.
Maybe that's OK, but I think it'd be nicer if you always required the
presence of these properties, even if you only actually *use* them
when CONFIG_PCIEASPM=y.
Done
+static int tegra_pcie_dw_probe(struct platform_device *pdev)
+{
+ struct tegra_pcie_dw *pcie;
+ struct pcie_port *pp;
+ struct dw_pcie *pci;
+ struct phy **phy;
+ struct resource *dbi_res;
+ struct resource *atu_dma_res;
+ const struct of_device_id *match;
+ const struct tegra_pcie_of_data *data;
+ char *name;
+ int ret, i;
+
+ pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL);
+ if (!pcie)
+ return -ENOMEM;
+
+ pci = &pcie->pci;
+ pci->dev = &pdev->dev;
+ pci->ops = &tegra_dw_pcie_ops;
+ pp = &pci->pp;
+ pcie->dev = &pdev->dev;
+
+ match = of_match_device(of_match_ptr(tegra_pcie_dw_of_match),
+ &pdev->dev);
+ if (!match)
+ return -EINVAL;
Logically could be the first thing in the function since it doesn't
depend on anything.
Done.
+ data = (struct tegra_pcie_of_data *)match->data;
+ pcie->mode = (enum dw_pcie_device_mode)data->mode;
+
+ ret = tegra_pcie_dw_parse_dt(pcie);
+ if (ret < 0) {
+ dev_err(pcie->dev, "device tree parsing failed: %d\n", ret);
+ return ret;
+ }
+
+ if (gpio_is_valid(pcie->pex_wake)) {
+ ret = devm_gpio_request(pcie->dev, pcie->pex_wake,
+ "pcie_wake");
+ if (ret < 0) {
+ if (ret == -EBUSY) {
+ dev_err(pcie->dev,
+ "pex_wake already in use\n");
+ pcie->pex_wake = -EINVAL;
This looks strange. "pex_wake == -EINVAL" doesn't look right, and
you're about to pass it to gpio_direction_input(), which looks wrong.
Done.
+ } else {
+ dev_err(pcie->dev,
+ "pcie_wake gpio_request failed %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ ret = gpio_direction_input(pcie->pex_wake);
+ if (ret < 0) {
+ dev_err(pcie->dev,
+ "setting pcie_wake input direction failed %d\n",
+ ret);
+ return ret;
+ }
+ device_init_wakeup(pcie->dev, true);
+ }
+
+ pcie->pex_ctl_reg = devm_regulator_get(&pdev->dev, "vddio-pex-ctl");
+ if (IS_ERR(pcie->pex_ctl_reg)) {
+ dev_err(&pdev->dev, "fail to get regulator: %ld\n",
+ PTR_ERR(pcie->pex_ctl_reg));
+ return PTR_ERR(pcie->pex_ctl_reg);
+ }
+
+ pcie->core_clk = devm_clk_get(&pdev->dev, "core_clk");
+ if (IS_ERR(pcie->core_clk)) {
+ dev_err(&pdev->dev, "Failed to get core clock\n");
+ return PTR_ERR(pcie->core_clk);
+ }
+
+ pcie->appl_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "appl");
+ if (!pcie->appl_res) {
+ dev_err(&pdev->dev, "missing appl space\n");
+ return PTR_ERR(pcie->appl_res);
+ }
+ pcie->appl_base = devm_ioremap_resource(&pdev->dev, pcie->appl_res);
+ if (IS_ERR(pcie->appl_base)) {
+ dev_err(&pdev->dev, "mapping appl space failed\n");
+ return PTR_ERR(pcie->appl_base);
+ }
+
+ pcie->core_apb_rst = devm_reset_control_get(pcie->dev, "core_apb_rst");
+ if (IS_ERR(pcie->core_apb_rst)) {
+ dev_err(pcie->dev, "PCIE : core_apb_rst reset is missing\n");
This error message looks different from the others ("PCIE :" prefix).
Done.
+ return PTR_ERR(pcie->core_apb_rst);
+ }
+
+ phy = devm_kcalloc(pcie->dev, pcie->phy_count, sizeof(*phy),
+ GFP_KERNEL);
+ if (!phy)
+ return PTR_ERR(phy);
+
+ for (i = 0; i < pcie->phy_count; i++) {
+ name = kasprintf(GFP_KERNEL, "pcie-p2u-%u", i);
+ if (!name) {
+ dev_err(pcie->dev, "failed to create p2u string\n");
+ return -ENOMEM;
+ }
+ phy[i] = devm_phy_get(pcie->dev, name);
+ kfree(name);
+ if (IS_ERR(phy[i])) {
+ ret = PTR_ERR(phy[i]);
+ dev_err(pcie->dev, "phy_get error: %d\n", ret);
+ return ret;
+ }
+ }
+
+ pcie->phy = phy;
+
+ dbi_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
+ if (!dbi_res) {
+ dev_err(&pdev->dev, "missing config space\n");
+ return PTR_ERR(dbi_res);
+ }
+ pcie->dbi_res = dbi_res;
+
+ pci->dbi_base = devm_ioremap_resource(&pdev->dev, dbi_res);
+ if (IS_ERR(pci->dbi_base)) {
+ dev_err(&pdev->dev, "mapping dbi space failed\n");
+ return PTR_ERR(pci->dbi_base);
+ }
+
+ /* Tegra HW locates DBI2 at a fixed offset from DBI */
+ pci->dbi_base2 = pci->dbi_base + 0x1000;
+
+ atu_dma_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "atu_dma");
+ if (!atu_dma_res) {
+ dev_err(&pdev->dev, "missing atu_dma space\n");
+ return PTR_ERR(atu_dma_res);
+ }
+ pcie->atu_dma_res = atu_dma_res;
+ pci->atu_base = devm_ioremap_resource(&pdev->dev, atu_dma_res);
+ if (IS_ERR(pci->atu_base)) {
+ dev_err(&pdev->dev, "mapping atu space failed\n");
+ return PTR_ERR(pci->atu_base);
+ }
+
+ pcie->core_rst = devm_reset_control_get(pcie->dev, "core_rst");
+ if (IS_ERR(pcie->core_rst)) {
+ dev_err(pcie->dev, "PCIE : core_rst reset is missing\n");
Different message format again.
Done.
+ return PTR_ERR(pcie->core_rst);
+ }
+
+ pp->irq = platform_get_irq_byname(pdev, "intr");
+ if (!pp->irq) {
+ dev_err(pcie->dev, "failed to get intr interrupt\n");
+ return -ENODEV;
+ }
+
+ ret = devm_request_irq(&pdev->dev, pp->irq, tegra_pcie_irq_handler,
+ IRQF_SHARED, "tegra-pcie-intr", pcie);
+ if (ret) {
+ dev_err(pcie->dev, "failed to request \"intr\" irq\n");
It'd be nice to include the actual IRQ, i.e., "IRQ %d", pp->irq.
Done.
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, pcie);
+
+ if (pcie->mode == DW_PCIE_RC_TYPE) {
+ ret = tegra_pcie_config_rp(pcie);
+ if (ret == -ENOMEDIUM)
+ ret = 0;
+ }
+
+ return ret;
+}
+static void tegra_pcie_downstream_dev_to_D0(struct tegra_pcie_dw *pcie)
+{
+ struct pci_dev *pdev = NULL;
Unnecessary initialization.
Yes.
+ struct pci_bus *child;
+ struct pcie_port *pp = &pcie->pci.pp;
+
+ list_for_each_entry(child, &pp->bus->children, node) {
+ /* Bring downstream devices to D0 if they are not already in */
+ if (child->parent == pp->bus) {
+ pdev = pci_get_slot(child, PCI_DEVFN(0, 0));
+ pci_dev_put(pdev);
+ if (!pdev)
+ break;
I don't really like this dance with iterating over the bus children,
comparing parent to pp->bus, pci_get_slot(), pci_dev_put(), etc.
I guess the idea is to bring only the directly-downstream devices to
D0, not to do it for things deeper in the hierarchy?
With Tegra PCIe controller, if the downstream device is in non-D0 states,
Is this some Tegra-specific wrinkle? I don't think other drivers do
this.
All non-DesignWare based implementations save their private data structure
I see that an earlier patch added "bus" to struct pcie_port. I think
it would be better to somehow connect to the pci_host_bridge struct.
Several other drivers already do this; see uses of
pci_host_bridge_from_priv().
Yeah. This seems to be a better method. I'll implement this.
That would give you the bus, as well as flags like no_ext_tags,
native_aer, etc, which this driver, being a host bridge driver that's
responsible for this part of the firmware/OS interface, may
conceivably need.
Rather than pci_get_slot(), couldn't you iterate over bus->devices and
just skip the non-PCI_DEVFN(0, 0) devices?
+
+ if (pci_set_power_state(pdev, PCI_D0))
+ dev_err(pcie->dev, "D0 transition failed\n");
+ }
+ }
+}
Done.
+static int tegra_pcie_dw_remove(struct platform_device *pdev)
+{
+ struct tegra_pcie_dw *pcie = platform_get_drvdata(pdev);
+
+ if (pcie->mode == DW_PCIE_RC_TYPE) {
Return early if it's not RC and unindent the rest of the function.
Since this API is called by remove, I'm removing the hierarchy to safely
+ if (!pcie->link_state && pcie->power_down_en)
+ return 0;
+
+ debugfs_remove_recursive(pcie->debugfs);
+ pm_runtime_put_sync(pcie->dev);
+ pm_runtime_disable(pcie->dev);
+ }
+
+ return 0;
+}
+static int tegra_pcie_dw_runtime_suspend(struct device *dev)
+{
+ struct tegra_pcie_dw *pcie = dev_get_drvdata(dev);
+
+ tegra_pcie_downstream_dev_to_D0(pcie);
+
+ pci_stop_root_bus(pcie->pci.pp.bus);
+ pci_remove_root_bus(pcie->pci.pp.bus);
Why are you calling these? No other drivers do this except in their
.remove() methods. Is there something special about Tegra, or is this
something the other drivers *should* be doing?
Done.
+ tegra_pcie_dw_pme_turnoff(pcie);
+
+ reset_control_assert(pcie->core_rst);
+ tegra_pcie_disable_phy(pcie);
+ reset_control_assert(pcie->core_apb_rst);
+ clk_disable_unprepare(pcie->core_clk);
+ regulator_disable(pcie->pex_ctl_reg);
+ config_plat_gpio(pcie, 0);
+
+ if (pcie->cid != CTRL_5)
+ tegra_pcie_bpmp_set_ctrl_state(pcie, false);
+
+ return 0;
+}
+
+static int tegra_pcie_dw_runtime_resume(struct device *dev)
+{
+ struct tegra_pcie_dw *pcie = dev_get_drvdata(dev);
+ struct dw_pcie *pci = &pcie->pci;
+ struct pcie_port *pp = &pci->pp;
+ int ret = 0;
+
+ ret = tegra_pcie_config_controller(pcie, false);
+ if (ret < 0)
+ return ret;
+
+ /* program to use MPS of 256 whereever possible */
s/whereever/wherever/
Removed it.
+ pcie_bus_config = PCIE_BUS_SAFE;
+
+ pp->root_bus_nr = -1;
+ pp->ops = &tegra_pcie_dw_host_ops;
+
+ /* Disable MSI interrupts for PME messages */
Superfluous comment; it repeats the function name.
Done.
+static int tegra_pcie_dw_suspend_noirq(struct device *dev)
+{
+ struct tegra_pcie_dw *pcie = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (!pcie->link_state)
+ return 0;
+
+ /* save MSI interrutp vector*/
s/interrutp/interrupt/
s/vector/vector /
Done.
+static int tegra_pcie_dw_resume_noirq(struct device *dev)
+{
+ struct tegra_pcie_dw *pcie = dev_get_drvdata(dev);
+ int ret;
+
+ if (!pcie->link_state)
+ return 0;
+
+ if (gpio_is_valid(pcie->pex_wake) && device_may_wakeup(dev)) {
+ ret = disable_irq_wake(gpio_to_irq(pcie->pex_wake));
+ if (ret < 0)
+ dev_err(dev, "disable wake irq failed: %d\n", ret);
+ }
+
+ ret = tegra_pcie_config_controller(pcie, true);
+ if (ret < 0)
+ return ret;
+
+ ret = tegra_pcie_dw_host_init(&pcie->pci.pp);
+ if (ret < 0) {
+ dev_err(dev, "failed to init host: %d\n", ret);
+ goto fail_host_init;
+ }
+
+ /* restore MSI interrutp vector*/
s/interrutp/interrupt/
s/vector/vector /
Done.
+static void tegra_pcie_dw_shutdown(struct platform_device *pdev)
+{
+ struct tegra_pcie_dw *pcie = platform_get_drvdata(pdev);
+
+ if (pcie->mode == DW_PCIE_RC_TYPE) {
if (pcie->mode != DW_PCIE_RC_TYPE)
return;
Then you can unindent the whole function.
Removed it.
+ if (!pcie->link_state && pcie->power_down_en)
+ return;
+
+ debugfs_remove_recursive(pcie->debugfs);
+ tegra_pcie_downstream_dev_to_D0(pcie);
+
+ /* Disable interrupts */
Superfluous comment.
+ disable_irq(pcie->pci.pp.irq);