[PATCH 2/2] pci: Add PCIe driver for Rockchip Soc
Shawn Lin
shawn.lin at rock-chips.com
Sun May 22 17:48:19 PDT 2016
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
More information about the Linux-rockchip
mailing list