mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
Merge branch 'pci/controller/dwc-imx6'
- Move IMX6SX_GPR12_PCIE_TEST_POWERDOWN handling into the core reset functions (Richard Zhu) - Add pci_host_common_parse_ports() for use by any native driver to parse Root Port properties (currently only reset GPIOs) (Sherry Sun) - Assert PERST# before enabling regulators to ensure that even if power is enabled, endpoint stays inactive until REFCLK is stable (Sherry Sun) - Parse reset properties in Root Port nodes (falling back to host bridge) to help support Key E connectors and the pwrctrl framework (Sherry Sun) - Configure i.MX95 REF_USE_PAD before PHY reset (Richard Zhu) - Assert i.MX95 ref_clk_en after reference clock stabilizes (Richard Zhu) - Integrate new pwrctrl API for DTs with Root Port-level power supplies (Sherry Sun) * pci/controller/dwc-imx6: PCI: imx6: Integrate new pwrctrl API PCI: imx6: Assert ref_clk_en after reference clock stabilizes on i.MX95 PCI: imx6: Configure REF_USE_PAD before PHY reset for i.MX95 PCI: imx6: Parse 'reset-gpios' in Root Port nodes PCI: imx6: Assert PERST# before enabling regulators PCI: host-generic: Add common helpers for parsing Root Port properties dt-bindings: PCI: fsl,imx6q-pcie: Add reset GPIO in Root Port node PCI: imx6: Fix IMX6SX_GPR12_PCIE_TEST_POWERDOWN handling
This commit is contained in:
commit
878f37d6de
|
|
@ -66,16 +66,34 @@ properties:
|
|||
- const: dma
|
||||
|
||||
reset-gpio:
|
||||
deprecated: true
|
||||
description: Should specify the GPIO for controlling the PCI bus device
|
||||
reset signal. It's not polarity aware and defaults to active-low reset
|
||||
sequence (L=reset state, H=operation state) (optional required).
|
||||
This property is deprecated, instead of referencing this property from the
|
||||
host bridge node, use the reset-gpios property from the root port node.
|
||||
|
||||
reset-gpio-active-high:
|
||||
deprecated: true
|
||||
description: If present then the reset sequence using the GPIO
|
||||
specified in the "reset-gpio" property is reversed (H=reset state,
|
||||
L=operation state) (optional required).
|
||||
This property is deprecated along with the reset-gpio property above, use
|
||||
the reset-gpios property from the root port node.
|
||||
type: boolean
|
||||
|
||||
pcie@0:
|
||||
description:
|
||||
Describe the i.MX6 PCIe Root Port.
|
||||
type: object
|
||||
$ref: /schemas/pci/pci-pci-bridge.yaml#
|
||||
|
||||
properties:
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
|
@ -236,6 +254,7 @@ unevaluatedProperties: false
|
|||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/imx6qdl-clock.h>
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
pcie: pcie@1ffc000 {
|
||||
|
|
@ -262,5 +281,18 @@ examples:
|
|||
<&clks IMX6QDL_CLK_LVDS1_GATE>,
|
||||
<&clks IMX6QDL_CLK_PCIE_REF_125M>;
|
||||
clock-names = "pcie", "pcie_bus", "pcie_phy";
|
||||
|
||||
pcie_port0: pcie@0 {
|
||||
compatible = "pciclass,0604";
|
||||
device_type = "pci";
|
||||
reg = <0x0 0x0 0x0 0x0 0x0>;
|
||||
bus-range = <0x01 0xff>;
|
||||
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
ranges;
|
||||
|
||||
reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
};
|
||||
...
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ config PCI_IMX6_HOST
|
|||
depends on PCI_MSI
|
||||
select PCIE_DW_HOST
|
||||
select PCI_IMX6
|
||||
select PCI_PWRCTRL_GENERIC
|
||||
help
|
||||
Enables support for the PCIe controller in the i.MX SoCs to
|
||||
work in Root Complex mode. The PCI controller on i.MX is based
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci-pwrctrl.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "../../pci.h"
|
||||
#include "../pci-host-common.h"
|
||||
#include "pcie-designware.h"
|
||||
|
||||
#define IMX8MQ_GPR_PCIE_REF_USE_PAD BIT(9)
|
||||
|
|
@ -137,6 +139,7 @@ struct imx_pcie_drvdata {
|
|||
const u32 mode_off[IMX_PCIE_MAX_INSTANCES];
|
||||
const u32 mode_mask[IMX_PCIE_MAX_INSTANCES];
|
||||
const struct pci_epc_features *epc_features;
|
||||
int (*select_ref_clk_src)(struct imx_pcie *pcie);
|
||||
int (*init_phy)(struct imx_pcie *pcie);
|
||||
int (*enable_ref_clk)(struct imx_pcie *pcie, bool enable);
|
||||
int (*core_reset)(struct imx_pcie *pcie, bool assert);
|
||||
|
|
@ -152,7 +155,6 @@ struct imx_lut_data {
|
|||
|
||||
struct imx_pcie {
|
||||
struct dw_pcie *pci;
|
||||
struct gpio_desc *reset_gpiod;
|
||||
struct clk_bulk_data *clks;
|
||||
int num_clks;
|
||||
bool supports_clkreq;
|
||||
|
|
@ -168,6 +170,8 @@ struct imx_pcie {
|
|||
u32 tx_swing_full;
|
||||
u32 tx_swing_low;
|
||||
struct regulator *vpcie;
|
||||
struct regulator *vpcie_aux;
|
||||
bool vpcie_aux_enabled;
|
||||
struct regulator *vph;
|
||||
void __iomem *phy_base;
|
||||
|
||||
|
|
@ -247,10 +251,26 @@ static unsigned int imx_pcie_grp_offset(const struct imx_pcie *imx_pcie)
|
|||
return imx_pcie->controller_id == 1 ? IOMUXC_GPR16 : IOMUXC_GPR14;
|
||||
}
|
||||
|
||||
static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie)
|
||||
static int imx95_pcie_select_ref_clk_src(struct imx_pcie *imx_pcie)
|
||||
{
|
||||
bool ext = imx_pcie->enable_ext_refclk;
|
||||
|
||||
/*
|
||||
* Regarding the Signal Descriptions of i.MX95 PCIe PHY, ref_use_pad is
|
||||
* used to select reference clock connected to a pair of pads.
|
||||
*
|
||||
* Any change in this input must be followed by phy_reset assertion.
|
||||
*/
|
||||
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL,
|
||||
IMX95_PCIE_REF_USE_PAD,
|
||||
ext ? IMX95_PCIE_REF_USE_PAD : 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie)
|
||||
{
|
||||
/*
|
||||
* ERR051624: The Controller Without Vaux Cannot Exit L23 Ready
|
||||
* Through Beacon or PERST# De-assertion
|
||||
|
|
@ -269,13 +289,6 @@ static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie)
|
|||
IMX95_PCIE_PHY_CR_PARA_SEL,
|
||||
IMX95_PCIE_PHY_CR_PARA_SEL);
|
||||
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL,
|
||||
IMX95_PCIE_REF_USE_PAD,
|
||||
ext ? IMX95_PCIE_REF_USE_PAD : 0);
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0,
|
||||
IMX95_PCIE_REF_CLKEN,
|
||||
ext ? 0 : IMX95_PCIE_REF_CLKEN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -665,14 +678,6 @@ static int imx_pcie_attach_pd(struct device *dev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int imx6sx_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable)
|
||||
{
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6SX_GPR12_PCIE_TEST_POWERDOWN,
|
||||
enable ? 0 : IMX6SX_GPR12_PCIE_TEST_POWERDOWN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx6q_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
|
|
@ -732,7 +737,29 @@ static void imx95_pcie_clkreq_override(struct imx_pcie *imx_pcie, bool enable)
|
|||
|
||||
static int imx95_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable)
|
||||
{
|
||||
bool ext = imx_pcie->enable_ext_refclk;
|
||||
|
||||
imx95_pcie_clkreq_override(imx_pcie, enable);
|
||||
/*
|
||||
* The ref_clk_en signal must remain de-asserted until the
|
||||
* reference clock is running at appropriate frequency, at which
|
||||
* point this bit can be asserted. For lower power states where
|
||||
* the reference clock to the PHY is disabled, it may also be
|
||||
* de-asserted.
|
||||
* +------------------- -+--------+----------------+
|
||||
* | External clock mode | Enable | PCIE_REF_CLKEN |
|
||||
* +---------------------+--------+----------------+
|
||||
* | TRUE | X | 1b'0 |
|
||||
* +---------------------+--------+----------------+
|
||||
* | FALSE | TRUE | 1b'1 |
|
||||
* +---------------------+--------+----------------+
|
||||
* | FALSE | FALSE | 1b'0 |
|
||||
* +---------------------+--------+----------------+
|
||||
*/
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0,
|
||||
IMX95_PCIE_REF_CLKEN,
|
||||
ext || !enable ? 0 : IMX95_PCIE_REF_CLKEN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -786,6 +813,9 @@ static int imx6sx_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert)
|
|||
if (assert)
|
||||
regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6SX_GPR12_PCIE_TEST_POWERDOWN);
|
||||
else
|
||||
regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6SX_GPR12_PCIE_TEST_POWERDOWN);
|
||||
|
||||
/* Force PCIe PHY reset */
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR5, IMX6SX_GPR5_PCIE_BTNRST_RESET,
|
||||
|
|
@ -1222,16 +1252,70 @@ static void imx_pcie_disable_device(struct pci_host_bridge *bridge,
|
|||
imx_pcie_remove_lut(imx_pcie, pci_dev_id(pdev));
|
||||
}
|
||||
|
||||
static int imx_pcie_parse_legacy_binding(struct imx_pcie *pcie)
|
||||
{
|
||||
struct device *dev = pcie->pci->dev;
|
||||
struct pci_host_bridge *bridge = pcie->pci->pp.bridge;
|
||||
struct pci_host_port *port;
|
||||
struct pci_host_perst *perst;
|
||||
struct gpio_desc *reset;
|
||||
|
||||
reset = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS);
|
||||
if (IS_ERR(reset))
|
||||
return PTR_ERR(reset);
|
||||
|
||||
if (!reset)
|
||||
return 0;
|
||||
|
||||
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
|
||||
if (!port)
|
||||
return -ENOMEM;
|
||||
|
||||
perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL);
|
||||
if (!perst)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&port->perst);
|
||||
perst->desc = reset;
|
||||
INIT_LIST_HEAD(&perst->list);
|
||||
list_add_tail(&perst->list, &port->perst);
|
||||
|
||||
INIT_LIST_HEAD(&port->list);
|
||||
list_add_tail(&port->list, &bridge->ports);
|
||||
|
||||
return devm_add_action_or_reset(dev, pci_host_common_delete_ports,
|
||||
&bridge->ports);
|
||||
}
|
||||
|
||||
static void imx_pcie_vpcie_aux_disable(void *data)
|
||||
{
|
||||
struct regulator *vpcie_aux = data;
|
||||
|
||||
regulator_disable(vpcie_aux);
|
||||
}
|
||||
|
||||
static void imx_pcie_assert_perst(struct imx_pcie *imx_pcie, bool assert)
|
||||
{
|
||||
struct dw_pcie *pci = imx_pcie->pci;
|
||||
struct pci_host_bridge *bridge = pci->pp.bridge;
|
||||
struct pci_host_perst *perst;
|
||||
struct pci_host_port *port;
|
||||
|
||||
if (!bridge || list_empty(&bridge->ports))
|
||||
return;
|
||||
|
||||
if (assert) {
|
||||
gpiod_set_value_cansleep(imx_pcie->reset_gpiod, 1);
|
||||
} else {
|
||||
if (imx_pcie->reset_gpiod) {
|
||||
msleep(PCIE_T_PVPERL_MS);
|
||||
gpiod_set_value_cansleep(imx_pcie->reset_gpiod, 0);
|
||||
msleep(PCIE_RESET_CONFIG_WAIT_MS);
|
||||
list_for_each_entry(port, &bridge->ports, list) {
|
||||
list_for_each_entry(perst, &port->perst, list)
|
||||
gpiod_direction_output(perst->desc, 1);
|
||||
}
|
||||
} else {
|
||||
mdelay(PCIE_T_PVPERL_MS);
|
||||
list_for_each_entry(port, &bridge->ports, list) {
|
||||
list_for_each_entry(perst, &port->perst, list)
|
||||
gpiod_direction_output(perst->desc, 0);
|
||||
}
|
||||
mdelay(PCIE_RESET_CONFIG_WAIT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1240,8 +1324,47 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
|
|||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||||
struct device *dev = pci->dev;
|
||||
struct imx_pcie *imx_pcie = to_imx_pcie(pci);
|
||||
struct pci_host_bridge *bridge = pp->bridge;
|
||||
int ret;
|
||||
|
||||
if (bridge && list_empty(&bridge->ports)) {
|
||||
/* Parse Root Port nodes if present */
|
||||
ret = pci_host_common_parse_ports(dev, bridge);
|
||||
if (ret) {
|
||||
if (ret != -ENODEV) {
|
||||
dev_err(dev, "Failed to parse Root Port nodes: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fall back to legacy binding for DT backwards
|
||||
* compatibility
|
||||
*/
|
||||
ret = imx_pcie_parse_legacy_binding(imx_pcie);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
imx_pcie_assert_perst(imx_pcie, true);
|
||||
|
||||
/* Keep 3.3Vaux supply enabled for entire PCIe controller lifecycle */
|
||||
if (imx_pcie->vpcie_aux && !imx_pcie->vpcie_aux_enabled) {
|
||||
ret = regulator_enable(imx_pcie->vpcie_aux);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable vpcie_aux regulator: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
imx_pcie->vpcie_aux_enabled = true;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, imx_pcie_vpcie_aux_disable,
|
||||
imx_pcie->vpcie_aux);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Legacy regulator handling for DT backward compatibility. */
|
||||
if (imx_pcie->vpcie) {
|
||||
ret = regulator_enable(imx_pcie->vpcie);
|
||||
if (ret) {
|
||||
|
|
@ -1251,25 +1374,39 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
|
|||
}
|
||||
}
|
||||
|
||||
ret = pci_pwrctrl_create_devices(dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to create pwrctrl devices\n");
|
||||
goto err_reg_disable;
|
||||
}
|
||||
|
||||
ret = pci_pwrctrl_power_on_devices(dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to power on pwrctrl devices\n");
|
||||
goto err_pwrctrl_destroy;
|
||||
}
|
||||
|
||||
ret = imx_pcie_clk_enable(imx_pcie);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to enable pcie clocks: %d\n", ret);
|
||||
goto err_pwrctrl_power_off;
|
||||
}
|
||||
|
||||
if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) {
|
||||
pp->bridge->enable_device = imx_pcie_enable_device;
|
||||
pp->bridge->disable_device = imx_pcie_disable_device;
|
||||
}
|
||||
|
||||
if (imx_pcie->drvdata->select_ref_clk_src)
|
||||
imx_pcie->drvdata->select_ref_clk_src(imx_pcie);
|
||||
|
||||
imx_pcie_assert_core_reset(imx_pcie);
|
||||
imx_pcie_assert_perst(imx_pcie, true);
|
||||
|
||||
if (imx_pcie->drvdata->init_phy)
|
||||
imx_pcie->drvdata->init_phy(imx_pcie);
|
||||
|
||||
imx_pcie_configure_type(imx_pcie);
|
||||
|
||||
ret = imx_pcie_clk_enable(imx_pcie);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to enable pcie clocks: %d\n", ret);
|
||||
goto err_reg_disable;
|
||||
}
|
||||
|
||||
if (imx_pcie->phy) {
|
||||
ret = phy_init(imx_pcie->phy);
|
||||
if (ret) {
|
||||
|
|
@ -1314,6 +1451,11 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
|
|||
phy_exit(imx_pcie->phy);
|
||||
err_clk_disable:
|
||||
imx_pcie_clk_disable(imx_pcie);
|
||||
err_pwrctrl_power_off:
|
||||
pci_pwrctrl_power_off_devices(dev);
|
||||
err_pwrctrl_destroy:
|
||||
if (ret != -EPROBE_DEFER)
|
||||
pci_pwrctrl_destroy_devices(dev);
|
||||
err_reg_disable:
|
||||
if (imx_pcie->vpcie)
|
||||
regulator_disable(imx_pcie->vpcie);
|
||||
|
|
@ -1332,6 +1474,7 @@ static void imx_pcie_host_exit(struct dw_pcie_rp *pp)
|
|||
}
|
||||
imx_pcie_clk_disable(imx_pcie);
|
||||
|
||||
pci_pwrctrl_power_off_devices(pci->dev);
|
||||
if (imx_pcie->vpcie)
|
||||
regulator_disable(imx_pcie->vpcie);
|
||||
}
|
||||
|
|
@ -1678,13 +1821,6 @@ static int imx_pcie_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(imx_pcie->phy_base);
|
||||
}
|
||||
|
||||
/* Fetch GPIOs */
|
||||
imx_pcie->reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(imx_pcie->reset_gpiod))
|
||||
return dev_err_probe(dev, PTR_ERR(imx_pcie->reset_gpiod),
|
||||
"unable to get reset gpio\n");
|
||||
gpiod_set_consumer_name(imx_pcie->reset_gpiod, "PCIe reset");
|
||||
|
||||
/* Fetch clocks */
|
||||
imx_pcie->num_clks = devm_clk_bulk_get_all(dev, &imx_pcie->clks);
|
||||
if (imx_pcie->num_clks < 0)
|
||||
|
|
@ -1782,9 +1918,13 @@ static int imx_pcie_probe(struct platform_device *pdev)
|
|||
of_property_read_u32(node, "fsl,max-link-speed", &pci->max_link_speed);
|
||||
imx_pcie->supports_clkreq = of_property_read_bool(node, "supports-clkreq");
|
||||
|
||||
ret = devm_regulator_get_enable_optional(&pdev->dev, "vpcie3v3aux");
|
||||
if (ret < 0 && ret != -ENODEV)
|
||||
return dev_err_probe(dev, ret, "failed to enable Vaux supply\n");
|
||||
imx_pcie->vpcie_aux = devm_regulator_get_optional(&pdev->dev,
|
||||
"vpcie3v3aux");
|
||||
if (IS_ERR(imx_pcie->vpcie_aux)) {
|
||||
if (PTR_ERR(imx_pcie->vpcie_aux) != -ENODEV)
|
||||
return PTR_ERR(imx_pcie->vpcie_aux);
|
||||
imx_pcie->vpcie_aux = NULL;
|
||||
}
|
||||
|
||||
imx_pcie->vpcie = devm_regulator_get_optional(&pdev->dev, "vpcie");
|
||||
if (IS_ERR(imx_pcie->vpcie)) {
|
||||
|
|
@ -1846,6 +1986,8 @@ static void imx_pcie_shutdown(struct platform_device *pdev)
|
|||
/* bring down link, so bootloader gets clean state in case of reboot */
|
||||
imx_pcie_assert_core_reset(imx_pcie);
|
||||
imx_pcie_assert_perst(imx_pcie, true);
|
||||
pci_pwrctrl_power_off_devices(&pdev->dev);
|
||||
pci_pwrctrl_destroy_devices(&pdev->dev);
|
||||
}
|
||||
|
||||
static const struct imx_pcie_drvdata drvdata[] = {
|
||||
|
|
@ -1877,7 +2019,6 @@ static const struct imx_pcie_drvdata drvdata[] = {
|
|||
.mode_off[0] = IOMUXC_GPR12,
|
||||
.mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE,
|
||||
.init_phy = imx6sx_pcie_init_phy,
|
||||
.enable_ref_clk = imx6sx_pcie_enable_ref_clk,
|
||||
.core_reset = imx6sx_pcie_core_reset,
|
||||
.ops = &imx_pcie_host_ops,
|
||||
},
|
||||
|
|
@ -1967,6 +2108,7 @@ static const struct imx_pcie_drvdata drvdata[] = {
|
|||
.mode_mask[0] = IMX95_PCIE_DEVICE_TYPE,
|
||||
.core_reset = imx95_pcie_core_reset,
|
||||
.init_phy = imx95_pcie_init_phy,
|
||||
.select_ref_clk_src = imx95_pcie_select_ref_clk_src,
|
||||
.wait_pll_lock = imx95_pcie_wait_for_phy_pll_lock,
|
||||
.enable_ref_clk = imx95_pcie_enable_ref_clk,
|
||||
.clr_clkreq_override = imx95_pcie_clr_clkreq_override,
|
||||
|
|
@ -2022,6 +2164,7 @@ static const struct imx_pcie_drvdata drvdata[] = {
|
|||
.ltssm_mask = IMX95_PCIE_LTSSM_EN,
|
||||
.mode_off[0] = IMX95_PE0_GEN_CTRL_1,
|
||||
.mode_mask[0] = IMX95_PCIE_DEVICE_TYPE,
|
||||
.select_ref_clk_src = imx95_pcie_select_ref_clk_src,
|
||||
.init_phy = imx95_pcie_init_phy,
|
||||
.core_reset = imx95_pcie_core_reset,
|
||||
.wait_pll_lock = imx95_pcie_wait_for_phy_pll_lock,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
|
|
@ -17,6 +18,174 @@
|
|||
|
||||
#include "pci-host-common.h"
|
||||
|
||||
/**
|
||||
* pci_host_common_delete_ports - Cleanup function for port list
|
||||
* @data: Pointer to the port list head
|
||||
*/
|
||||
void pci_host_common_delete_ports(void *data)
|
||||
{
|
||||
struct list_head *ports = data;
|
||||
struct pci_host_perst *perst, *tmp_perst;
|
||||
struct pci_host_port *port, *tmp_port;
|
||||
|
||||
list_for_each_entry_safe(port, tmp_port, ports, list) {
|
||||
list_for_each_entry_safe(perst, tmp_perst, &port->perst, list)
|
||||
list_del(&perst->list);
|
||||
list_del(&port->list);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_host_common_delete_ports);
|
||||
|
||||
/**
|
||||
* pci_host_common_parse_perst - Parse PERST# from all nodes, depth first
|
||||
* @dev: Device pointer
|
||||
* @port: PCI host port
|
||||
* @np: Device tree node to start parsing from
|
||||
*
|
||||
* Recursively parse PERST# GPIO from all PCIe bridge nodes starting from
|
||||
* @np in a depth-first manner.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
static int pci_host_common_parse_perst(struct device *dev,
|
||||
struct pci_host_port *port,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct pci_host_perst *perst;
|
||||
struct gpio_desc *reset;
|
||||
int ret;
|
||||
|
||||
if (!of_property_present(np, "reset-gpios"))
|
||||
goto parse_child_node;
|
||||
|
||||
reset = devm_fwnode_gpiod_get(dev, of_fwnode_handle(np), "reset",
|
||||
GPIOD_ASIS, "PERST#");
|
||||
if (IS_ERR(reset)) {
|
||||
/*
|
||||
* FIXME: GPIOLIB currently supports exclusive GPIO access only.
|
||||
* Non exclusive access is broken. But shared PERST# requires
|
||||
* non-exclusive access. So once GPIOLIB properly supports it,
|
||||
* implement it here.
|
||||
*/
|
||||
if (PTR_ERR(reset) == -EBUSY)
|
||||
dev_err(dev, "Shared PERST# is not supported\n");
|
||||
|
||||
return PTR_ERR(reset);
|
||||
}
|
||||
|
||||
perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL);
|
||||
if (!perst)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&perst->list);
|
||||
perst->desc = reset;
|
||||
list_add_tail(&perst->list, &port->perst);
|
||||
|
||||
parse_child_node:
|
||||
for_each_available_child_of_node_scoped(np, child) {
|
||||
ret = pci_host_common_parse_perst(dev, port, child);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_host_common_parse_port - Parse a single Root Port node
|
||||
* @dev: Device pointer
|
||||
* @bridge: PCI host bridge
|
||||
* @node: Device tree node of the Root Port
|
||||
*
|
||||
* Parse Root Port properties from the device tree. Currently it only
|
||||
* handles the PERST# GPIO (including PERST# GPIOs from all PCIe bridge
|
||||
* nodes under this Root Port), which is optional.
|
||||
*
|
||||
* NOTE: This helper fetches resources (like PERST# GPIO) optionally. If a
|
||||
* controller driver has a hard dependency on certain resources (PHY,
|
||||
* clocks, regulators, etc.), those resources MUST be modeled correctly in
|
||||
* the DT binding and validated in DTS. This helper cannot enforce such
|
||||
* dependencies and the driver may fail to operate if required resources
|
||||
* are missing.
|
||||
*
|
||||
* Return: 0 on success, -ENODEV if PERST# found in RC node (legacy binding
|
||||
* should be used), Other negative error codes on failure.
|
||||
*/
|
||||
static int pci_host_common_parse_port(struct device *dev,
|
||||
struct pci_host_bridge *bridge,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct pci_host_port *port;
|
||||
int ret;
|
||||
|
||||
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
|
||||
if (!port)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&port->perst);
|
||||
|
||||
ret = pci_host_common_parse_perst(dev, port, node);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* 1. PERST# found in RP or its child nodes - list is not empty,
|
||||
* continue
|
||||
*
|
||||
* 2. PERST# not found in RP/children, but found in RC node -
|
||||
* return -ENODEV to fallback legacy binding
|
||||
*
|
||||
* 3. PERST# not found anywhere - list is empty, continue (optional
|
||||
* PERST#)
|
||||
*/
|
||||
if (list_empty(&port->perst)) {
|
||||
if (of_property_present(dev->of_node, "reset-gpios") ||
|
||||
of_property_present(dev->of_node, "reset-gpio"))
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&port->list);
|
||||
list_add_tail(&port->list, &bridge->ports);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_host_common_parse_ports - Parse Root Port nodes from device tree
|
||||
* @dev: Device pointer
|
||||
* @bridge: PCI host bridge
|
||||
*
|
||||
* Iterate through child nodes of the host bridge and parse Root Port
|
||||
* properties (currently only reset GPIOs).
|
||||
*
|
||||
* Return: 0 on success, -ENODEV if no ports found or PERST# found in RC
|
||||
* node (legacy binding should be used), Other negative error codes on
|
||||
* failure.
|
||||
*/
|
||||
int pci_host_common_parse_ports(struct device *dev, struct pci_host_bridge *bridge)
|
||||
{
|
||||
int ret = -ENODEV;
|
||||
|
||||
for_each_available_child_of_node_scoped(dev->of_node, of_port) {
|
||||
if (!of_node_is_type(of_port, "pci"))
|
||||
continue;
|
||||
ret = pci_host_common_parse_port(dev, bridge, of_port);
|
||||
if (ret)
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_add_action_or_reset(dev, pci_host_common_delete_ports,
|
||||
&bridge->ports);
|
||||
|
||||
err_cleanup:
|
||||
pci_host_common_delete_ports(&bridge->ports);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_host_common_parse_ports);
|
||||
|
||||
static void gen_pci_unmap_cfg(void *ptr)
|
||||
{
|
||||
pci_ecam_free((struct pci_config_window *)ptr);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,34 @@
|
|||
|
||||
struct pci_ecam_ops;
|
||||
|
||||
/**
|
||||
* struct pci_host_perst - PERST# GPIO descriptor
|
||||
* @list: List node for linking multiple PERST# GPIOs
|
||||
* @desc: GPIO descriptor for PERST# signal
|
||||
*
|
||||
* This structure holds a single PERST# GPIO descriptor.
|
||||
*/
|
||||
struct pci_host_perst {
|
||||
struct list_head list;
|
||||
struct gpio_desc *desc;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pci_host_port - Generic Root Port properties
|
||||
* @list: List node for linking multiple ports
|
||||
* @perst: List of PERST# GPIO descriptors for this port and its children
|
||||
*
|
||||
* This structure contains common properties that can be parsed from
|
||||
* Root Port device tree nodes.
|
||||
*/
|
||||
struct pci_host_port {
|
||||
struct list_head list;
|
||||
struct list_head perst;
|
||||
};
|
||||
|
||||
void pci_host_common_delete_ports(void *data);
|
||||
int pci_host_common_parse_ports(struct device *dev,
|
||||
struct pci_host_bridge *bridge);
|
||||
int pci_host_common_probe(struct platform_device *pdev);
|
||||
int pci_host_common_init(struct platform_device *pdev,
|
||||
struct pci_host_bridge *bridge,
|
||||
|
|
|
|||
|
|
@ -660,6 +660,7 @@ static void pci_init_host_bridge(struct pci_host_bridge *bridge)
|
|||
{
|
||||
INIT_LIST_HEAD(&bridge->windows);
|
||||
INIT_LIST_HEAD(&bridge->dma_ranges);
|
||||
INIT_LIST_HEAD(&bridge->ports);
|
||||
|
||||
/*
|
||||
* We assume we can manage these PCIe features. Some systems may
|
||||
|
|
|
|||
|
|
@ -635,6 +635,7 @@ struct pci_host_bridge {
|
|||
int domain_nr;
|
||||
struct list_head windows; /* resource_entry */
|
||||
struct list_head dma_ranges; /* dma ranges resource list */
|
||||
struct list_head ports; /* Root Port list (pci_host_port) */
|
||||
#ifdef CONFIG_PCI_IDE
|
||||
u16 nr_ide_streams; /* Max streams possibly active in @ide_stream_ida */
|
||||
struct ida ide_stream_ida;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user