On 2016/5/21 5:13, Heiko Stuebner wrote: > Hi Shawn, > > I haven't had any contact with PCI yet, so my comments below will likely > address more generic findings only. > > As you might've guessed from the binding comments, to me it looks like the > phy handling should be in a separate phy driver and looking below all phy > accesses seem very separate from the actual pci controller interactions - > they are even in port_init only as well. > yes, the main reason for not to seperate a new pcie-phy driver for this prototype design is that I just wonder whether it's worth to create a new driver for just a small piece of code. And a bit more forward is that I think phy api is no so scalable for just init/power_on in case of too much interactions between controller and phy from which I suffer a bit for emmc. Should I really need to seperate the phy part into pcie-phy? ;) > While I already added some comments about that below, the driver seems full > of raw register bit handling (including wild shifts). Please abstract that > using contants, so that stuff stays readable for the future. okay, let me migrate these magic number and raw reg bit handling into a new header file. > > > Am Freitag, 20. Mai 2016, 18:29:16 schrieb Shawn Lin: >> RK3399 has a PCIe controller which can be used as Root Complex. >> This driver supports a PCIe controller as Root Complex mode. >> >> Signed-off-by: Shawn Lin <shawn.lin at rock-chips.com> >> --- > > [...] > >> diff --git a/drivers/pci/host/pcie-rockchip.c >> b/drivers/pci/host/pcie-rockchip.c new file mode 100644 >> index 0000000..4063fd3 >> --- /dev/null >> +++ b/drivers/pci/host/pcie-rockchip.c >> @@ -0,0 +1,1181 @@ >> +/* >> + * Rockchip AXI PCIe host controller driver >> + * >> + * Copyright (c) 2016 Rockchip, Inc. >> + * >> + * Author: Shawn Lin <shawn.lin at rock-chips.com> >> + * Wenrui Li <wenrui.li at rock-chips.com> >> + * Bits taken from Synopsys Designware Host controller driver and >> + * ARM PCI Host generic driver. >> + * >> + * This program is free software: you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License as published by >> + * the Free Software Foundation, either version 2 of the License, or >> + * (at your option) any later version. >> + */ >> +#include <linux/clk.h> >> +#include <linux/delay.h> >> +#include <linux/gpio/consumer.h> >> +#include <linux/interrupt.h> >> +#include <linux/irq.h> >> +#include <linux/irqdomain.h> >> +#include <linux/kernel.h> >> +#include <linux/mfd/syscon.h> >> +#include <linux/module.h> >> +#include <linux/msi.h> >> +#include <linux/of_address.h> >> +#include <linux/of_device.h> >> +#include <linux/of_pci.h> >> +#include <linux/of_platform.h> >> +#include <linux/of_irq.h> >> +#include <linux/pci.h> >> +#include <linux/platform_device.h> >> +#include <linux/reset.h> >> +#include <linux/regmap.h> >> + >> +#define REF_CLK_100MHZ (100 * 1000 * 1000) > > seems unused will be removed. > >> +#define PCIE_CLIENT_BASE 0x0 >> +#define PCIE_RC_CONFIG_BASE 0xa00000 >> +#define PCIE_CORE_CTRL_MGMT_BASE 0x900000 >> +#define PCIE_CORE_AXI_CONF_BASE 0xc00000 >> +#define PCIE_CORE_AXI_INBOUND_BASE 0xc00800 >> + >> +#define PCIE_CLIENT_BASIC_STATUS0 0x44 >> +#define PCIE_CLIENT_BASIC_STATUS1 0x48 >> +#define PCIE_CLIENT_INT_MASK 0x4c >> +#define PCIE_CLIENT_INT_STATUS 0x50 >> +#define PCIE_CORE_INT_MASK 0x900210 >> +#define PCIE_CORE_INT_STATUS 0x90020c >> + >> +/** Size of one AXI Region (not Region 0) */ >> +#define AXI_REGION_SIZE (0x1 << 20) > > for those generic (1 << x) things please use BIT(x) instead > Also constants intertwined with constants is hard to read, > so ideally add a blank line above each comment and comment style for single > line comments only has one * in the beginning Ahh yep. > /* foo */ > >> +/** Overall size of AXI area */ >> +#define AXI_OVERALL_SIZE (64 * (0x1 << 20)) >> +/** Size of Region 0, equal to sum of sizes of other regions */ >> +#define AXI_REGION_0_SIZE (32 * (0x1 << 20)) >> +#define OB_REG_SIZE_SHIFT 5 >> +#define IB_ROOT_PORT_REG_SIZE_SHIFT 3 >> + >> +#define AXI_WRAPPER_IO_WRITE 0x6 >> +#define AXI_WRAPPER_MEM_WRITE 0x2 >> +#define MAX_AXI_IB_ROOTPORT_REGION_NUM 3 >> +#define MIN_AXI_ADDR_BITS_PASSED 8 > > strange spacing after #define Generally I use one space after #define, but I donnot somehow... I will check it twice. > > [...] > >> +/** >> + * rockchip_pcie_init_port - Initialize hardware >> + * @port: PCIe port information >> + */ >> +static int rockchip_pcie_init_port(struct rockchip_pcie_port *port) >> +{ >> + int err; >> + u32 status; >> + unsigned long timeout = jiffies + msecs_to_jiffies(1000); > > this timeout only seems to be initialized once here but used for multiple > loops below resulting in all waitloops combined together being allowed to > take 1 second ... is this intended? a big timeout value to make sure we leave enough margin. No any data is provided about how long should we wait for pll lock/re-lock/tainning finish stuff. As it also related to the Socs/devices. Currently I test pci-ethernet/wifi/SATA-bridge/USB-bridge, and it seems can be reduced. But as you know, I cannot guarantee not to augment it once we find some SSD/GPU in the failure state of trainning in the future. Anyway I think here we use loop-break method, so it should be not harmful? > >> + gpiod_set_value(port->ep_gpio, 0); >> + >> + /* Make sure PCIe relate block is in reset state */ >> + err = reset_control_assert(port->phy_rst); >> + if (err) { >> + dev_err(port->dev, "assert phy_rst err %d\n", err); >> + return err; >> + } > > should move to phy driver probe or so > >> + err = reset_control_assert(port->core_rst); >> + if (err) { >> + dev_err(port->dev, "assert core_rst err %d\n", err); >> + return err; >> + } > > blank line > >> + err = reset_control_assert(port->mgmt_rst); >> + if (err) { >> + dev_err(port->dev, "assert mgmt_rst err %d\n", err); >> + return err; >> + } > > blank line > >> + err = reset_control_assert(port->mgmt_sticky_rst); >> + if (err) { >> + dev_err(port->dev, "assert mgmt_sticky_rst err %d\n", err); >> + return err; >> + } > > blank line > >> + err = reset_control_assert(port->pipe_rst); >> + if (err) { >> + dev_err(port->dev, "assert pipe_rst err %d\n", err); >> + return err; >> + } >> + >> + pcie_write(port, (0xf << 20) | (0x1 << 16) | PCIE_CLIENT_GEN_SEL_2 | >> + (0x1 << 19) | (0x1 << 3) | >> + PCIE_CLIENT_MODE_RC | >> + PCIE_CLIENT_CONF_LANE_NUM(port->lanes) | >> + PCIE_CLIENT_CONF_ENABLE, PCIE_CLIENT_BASE); >> + > > ------ phy_enable begin ------ > > As mentioned above, the whole phy handling seems pretty separate, so should > be easily being able to live in a real phy driver? > > This of course also needs to handle the phy clock in it. > >> + err = reset_control_deassert(port->phy_rst); >> + if (err) { >> + dev_err(port->dev, "deassert phy_rst err %d\n", err); >> + return err; >> + } >> + regmap_write(port->grf, port->pcie_conf, >> + (0x3f << 17) | (0x10 << 1)); > > the bits above should use some sort of constant / description. Also see > HIWORD_UPDATE in other drivers for the write-enable mask > > also add blank line here > >> + err = -EINVAL; >> + while (time_before(jiffies, timeout)) { >> + regmap_read(port->grf, port->pcie_status, &status); >> + if ((status & (1 << 9))) { > > use a constant for the (1 << 9) [aka BIT(9)] please > >> + dev_info(port->dev, "pll locked!\n"); > > dev_dbg instead? yep > >> + err = 0; >> + break; >> + } >> + } >> + if (err) { >> + dev_err(port->dev, "pll lock timeout!\n"); >> + return err; >> + } >> + pcie_pb_wr_cfg(port, 0x10, 0x8); >> + pcie_pb_wr_cfg(port, 0x12, 0x8); >> + >> + err = -ETIMEDOUT; >> + while (time_before(jiffies, timeout)) { >> + regmap_read(port->grf, port->pcie_status, &status); >> + if (!(status & (1 << 10))) { > > constant again > >> + dev_info(port->dev, "pll output enable done!\n"); > > dev_dbg or leave it out dev_dbg should be fine. > >> + err = 0; >> + break; >> + } >> + } >> + >> + if (err) { >> + dev_err(port->dev, "pll output enable timeout!\n"); >> + return err; >> + } >> + >> + regmap_write(port->grf, port->pcie_conf, >> + (0x3f << 17) | (0x10 << 1)); >> + err = -EINVAL; >> + while (time_before(jiffies, timeout)) { >> + regmap_read(port->grf, port->pcie_status, &status); >> + if ((status & (1 << 9))) { >> + dev_info(port->dev, "pll relocked!\n"); >> + err = 0; >> + break; >> + } >> + } >> + if (err) { >> + dev_err(port->dev, "pll relock timeout!\n"); >> + return err; >> + } > > ------ phy_enable end ------ > > >> + err = reset_control_deassert(port->core_rst); >> + if (err) { >> + dev_err(port->dev, "deassert core_rst err %d\n", err); >> + return err; >> + } >> + err = reset_control_deassert(port->mgmt_rst); >> + if (err) { >> + dev_err(port->dev, "deassert mgmt_rst err %d\n", err); >> + return err; >> + } >> + err = reset_control_deassert(port->mgmt_sticky_rst); >> + if (err) { >> + dev_err(port->dev, "deassert mgmt_sticky_rst err %d\n", err); >> + return err; >> + } >> + err = reset_control_deassert(port->pipe_rst); >> + if (err) { >> + dev_err(port->dev, "deassert pipe_rst err %d\n", err); >> + return err; >> + } >> + >> + pcie_write(port, 1 << 17 | 1 << 1, PCIE_CLIENT_BASE); >> + >> + gpiod_set_value(port->ep_gpio, 1); >> + err = -ETIMEDOUT; >> + while (time_before(jiffies, timeout)) { >> + status = pcie_read(port, PCIE_CLIENT_BASIC_STATUS1); >> + if (((status >> 20) & 0x3) == 0x3) { >> + dev_info(port->dev, "pcie link training gen1 pass!\n"); >> + err = 0; >> + break; >> + } >> + } >> + if (err) { >> + dev_err(port->dev, "pcie link training gen1 timeout!\n"); >> + return err; >> + } >> + >> + status = pcie_read(port, 0x9000d0); >> + status |= 0x20; >> + pcie_write(port, status, 0x9000d0); > > just to mention it again, bit handling as descriptive constant please and > also please give that register a name :-) will address this magic number all above using a new header file :) > > [...] > >> +static int rockchip_pcie_parse_dt(struct rockchip_pcie_port *port) >> +{ > > [...] > >> + port->lanes = 1; >> + err = of_property_read_u32(node, "num-lanes", &port->lanes); >> + if (!err && ((port->lanes == 0) || >> + (port->lanes == 3) || >> + (port->lanes > 4))) { >> + dev_info(dev, "invalid num-lanes, default use one lane\n"); > > dev_warn might be more appropriate sure. > >> + port->lanes = 1; >> + } > > [...] > >> +static void rockchip_pcie_msi_enable(struct rockchip_pcie_port *pp) >> +{ >> + struct device_node *msi_node; >> + >> + msi_node = of_parse_phandle(pp->dev->of_node, >> + "msi-parent", 0); > > I would assume this should live in the general parse_dt function? > Also is that MSI enablement really allow to fail silently without any affect > on the PCI functionality? yes, we should check this as well as CONFIG_PCI_MSI. Will fix it. > >> + if (!msi_node) >> + return; >> + >> + pp->msi = of_pci_find_msi_chip_by_node(msi_node); >> + of_node_put(msi_node); >> + >> + if (pp->msi) >> + pp->msi->dev = pp->dev; >> +} > > [...] > >> +static int rockchip_pcie_probe(struct platform_device *pdev) >> +{ >> + struct rockchip_pcie_port *port; >> + struct device *dev = &pdev->dev; >> + struct pci_bus *bus, *child; >> + struct resource_entry *win; >> + int reg_no; >> + int err = 0; >> + int irq; >> + LIST_HEAD(res); >> + >> + if (!dev->of_node) >> + return -ENODEV; >> + >> + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); >> + if (!port) >> + return -ENOMEM; >> + >> + irq = platform_get_irq_byname(pdev, "pcie-sys"); >> + if (irq < 0) { >> + dev_err(dev, "missing pcie_sys IRQ resource\n"); >> + return -EINVAL; >> + } > > blank line > >> + err = devm_request_irq(dev, irq, rockchip_pcie_subsys_irq_handler, >> + IRQF_SHARED, "pcie-sys", port); >> + if (err) { >> + dev_err(dev, "failed to request pcie subsystem irq\n"); >> + return err; >> + } >> + >> + port->irq = platform_get_irq_byname(pdev, "pcie-legacy"); >> + if (port->irq < 0) { >> + dev_err(dev, "missing pcie_legacy IRQ resource\n"); >> + return -EINVAL; >> + } > > blank line > >> + err = devm_request_irq(dev, port->irq, >> + rockchip_pcie_legacy_int_handler, >> + IRQF_SHARED, >> + "pcie-legacy", >> + port); >> + if (err) { >> + dev_err(&pdev->dev, "failed to request pcie-legacy irq\n"); >> + return err; >> + } >> + >> + irq = platform_get_irq_byname(pdev, "pcie-client"); >> + if (irq < 0) { >> + dev_err(dev, "missing pcie-client IRQ resource\n"); >> + return -EINVAL; >> + } > > blank line > >> + err = devm_request_irq(dev, irq, rockchip_pcie_client_irq_handler, >> + IRQF_SHARED, "pcie-client", port); >> + if (err) { >> + dev_err(dev, "failed to request pcie client irq\n"); >> + return err; >> + } >> + >> + port->dev = dev; >> + err = rockchip_pcie_parse_dt(port); >> + if (err) { >> + dev_err(dev, "Parsing DT failed\n"); >> + return err; >> + } > > rockchip_pcie_parse_dt already emits error messages that are a lot more > specific to the actual problem, so you don't need another error message here > and can just return the error code. > okay. > >> + err = rockchip_pcie_init_port(port); >> + if (err) >> + return err; >> + >> + platform_set_drvdata(pdev, port); >> + >> + rockchip_pcie_enable_interrupts(port); >> + if (!IS_ENABLED(CONFIG_PCI_MSI)) { >> + err = rockchip_pcie_init_irq_domain(port); >> + if (err < 0) >> + return err; >> + } >> + >> + err = of_pci_get_host_bridge_resources(dev->of_node, 0, 0xff, >> + &res, &port->io_base); >> + if (err) >> + return err; > > add blank line > > > Heiko > > > -- Best Regards Shawn Lin