mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2024-12-29 17:22:07 +00:00
PCI: rockchip-ep: Handle PERST# signal in EP mode
Currently, the Rockchip PCIe endpoint controller driver does not handle the PERST# signal, which prevents detecting when link training should actually be started or if the host resets the device. This however can be supported using the controller reset_gpios property set as an input GPIO for endpoint mode. Modify the Rockchip PCI endpoint controller driver to get the reset_gpio and its associated interrupt which is serviced using a threaded IRQ with the function rockchip_pcie_ep_perst_irq_thread() as handler. This handler function notifies a link down event corresponding to the RC side asserting the PERST# signal using pci_epc_linkdown() when the gpio is high. Once the gpio value goes down, corresponding to the RC de-asserting the PERST# signal, link training is started. The polarity of the gpio interrupt trigger is changed from high to low after the RC asserted PERST#, and conversely changed from low to high after the RC de-asserts PERST#. Also, given that the host mode controller and the endpoint mode controller use two different property names for the same PERST# signal (ep_gpios property and reset_gpios property respectively), for clarity, rename the ep_gpio field of struct rockchip_pcie to perst_gpio. Link: https://lore.kernel.org/r/20241017015849.190271-14-dlemoal@kernel.org Signed-off-by: Damien Le Moal <dlemoal@kernel.org> [kwilczynski: make log messages consistent, add missing include] Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
This commit is contained in:
parent
bd6e61df4b
commit
a7137cbf6b
@ -10,8 +10,10 @@
|
||||
|
||||
#include <linux/configfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/platform_device.h>
|
||||
@ -50,6 +52,9 @@ struct rockchip_pcie_ep {
|
||||
u64 irq_pci_addr;
|
||||
u8 irq_pci_fn;
|
||||
u8 irq_pending;
|
||||
int perst_irq;
|
||||
bool perst_asserted;
|
||||
bool link_up;
|
||||
struct delayed_work link_training;
|
||||
};
|
||||
|
||||
@ -470,13 +475,17 @@ static int rockchip_pcie_ep_start(struct pci_epc *epc)
|
||||
|
||||
rockchip_pcie_write(rockchip, cfg, PCIE_CORE_PHY_FUNC_CFG);
|
||||
|
||||
if (rockchip->perst_gpio)
|
||||
enable_irq(ep->perst_irq);
|
||||
|
||||
/* Enable configuration and start link training */
|
||||
rockchip_pcie_write(rockchip,
|
||||
PCIE_CLIENT_LINK_TRAIN_ENABLE |
|
||||
PCIE_CLIENT_CONF_ENABLE,
|
||||
PCIE_CLIENT_CONFIG);
|
||||
|
||||
schedule_delayed_work(&ep->link_training, 0);
|
||||
if (!rockchip->perst_gpio)
|
||||
schedule_delayed_work(&ep->link_training, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -486,6 +495,11 @@ static void rockchip_pcie_ep_stop(struct pci_epc *epc)
|
||||
struct rockchip_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct rockchip_pcie *rockchip = &ep->rockchip;
|
||||
|
||||
if (rockchip->perst_gpio) {
|
||||
ep->perst_asserted = true;
|
||||
disable_irq(ep->perst_irq);
|
||||
}
|
||||
|
||||
cancel_delayed_work_sync(&ep->link_training);
|
||||
|
||||
/* Stop link training and disable configuration */
|
||||
@ -551,6 +565,13 @@ static void rockchip_pcie_ep_link_training(struct work_struct *work)
|
||||
if (!rockchip_pcie_ep_link_up(rockchip))
|
||||
goto again;
|
||||
|
||||
/*
|
||||
* If PERST# was asserted while polling the link, do not notify
|
||||
* the function.
|
||||
*/
|
||||
if (ep->perst_asserted)
|
||||
return;
|
||||
|
||||
val = rockchip_pcie_read(rockchip, PCIE_CLIENT_BASIC_STATUS0);
|
||||
dev_info(dev,
|
||||
"link up (negotiated speed: %sGT/s, width: x%lu)\n",
|
||||
@ -560,6 +581,7 @@ static void rockchip_pcie_ep_link_training(struct work_struct *work)
|
||||
|
||||
/* Notify the function */
|
||||
pci_epc_linkup(ep->epc);
|
||||
ep->link_up = true;
|
||||
|
||||
return;
|
||||
|
||||
@ -567,6 +589,103 @@ static void rockchip_pcie_ep_link_training(struct work_struct *work)
|
||||
schedule_delayed_work(&ep->link_training, msecs_to_jiffies(5));
|
||||
}
|
||||
|
||||
static void rockchip_pcie_ep_perst_assert(struct rockchip_pcie_ep *ep)
|
||||
{
|
||||
struct rockchip_pcie *rockchip = &ep->rockchip;
|
||||
|
||||
dev_dbg(rockchip->dev, "PERST# asserted, link down\n");
|
||||
|
||||
if (ep->perst_asserted)
|
||||
return;
|
||||
|
||||
ep->perst_asserted = true;
|
||||
|
||||
cancel_delayed_work_sync(&ep->link_training);
|
||||
|
||||
if (ep->link_up) {
|
||||
pci_epc_linkdown(ep->epc);
|
||||
ep->link_up = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void rockchip_pcie_ep_perst_deassert(struct rockchip_pcie_ep *ep)
|
||||
{
|
||||
struct rockchip_pcie *rockchip = &ep->rockchip;
|
||||
|
||||
dev_dbg(rockchip->dev, "PERST# de-asserted, starting link training\n");
|
||||
|
||||
if (!ep->perst_asserted)
|
||||
return;
|
||||
|
||||
ep->perst_asserted = false;
|
||||
|
||||
/* Enable link re-training */
|
||||
rockchip_pcie_ep_retrain_link(rockchip);
|
||||
|
||||
/* Start link training */
|
||||
schedule_delayed_work(&ep->link_training, 0);
|
||||
}
|
||||
|
||||
static irqreturn_t rockchip_pcie_ep_perst_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct pci_epc *epc = data;
|
||||
struct rockchip_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct rockchip_pcie *rockchip = &ep->rockchip;
|
||||
u32 perst = gpiod_get_value(rockchip->perst_gpio);
|
||||
|
||||
if (perst)
|
||||
rockchip_pcie_ep_perst_assert(ep);
|
||||
else
|
||||
rockchip_pcie_ep_perst_deassert(ep);
|
||||
|
||||
irq_set_irq_type(ep->perst_irq,
|
||||
(perst ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW));
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int rockchip_pcie_ep_setup_irq(struct pci_epc *epc)
|
||||
{
|
||||
struct rockchip_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct rockchip_pcie *rockchip = &ep->rockchip;
|
||||
struct device *dev = rockchip->dev;
|
||||
int ret;
|
||||
|
||||
if (!rockchip->perst_gpio)
|
||||
return 0;
|
||||
|
||||
/* PCIe reset interrupt */
|
||||
ep->perst_irq = gpiod_to_irq(rockchip->perst_gpio);
|
||||
if (ep->perst_irq < 0) {
|
||||
dev_err(dev,
|
||||
"failed to get IRQ for PERST# GPIO: %d\n",
|
||||
ep->perst_irq);
|
||||
|
||||
return ep->perst_irq;
|
||||
}
|
||||
|
||||
/*
|
||||
* The perst_gpio is active low, so when it is inactive on start, it
|
||||
* is high and will trigger the perst_irq handler. So treat this initial
|
||||
* IRQ as a dummy one by faking the host asserting PERST#.
|
||||
*/
|
||||
ep->perst_asserted = true;
|
||||
irq_set_status_flags(ep->perst_irq, IRQ_NOAUTOEN);
|
||||
ret = devm_request_threaded_irq(dev, ep->perst_irq, NULL,
|
||||
rockchip_pcie_ep_perst_irq_thread,
|
||||
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||
"pcie-ep-perst", epc);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"failed to request IRQ for PERST# GPIO: %d\n",
|
||||
ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pci_epc_features rockchip_pcie_epc_features = {
|
||||
.linkup_notifier = true,
|
||||
.msi_capable = true,
|
||||
@ -730,7 +849,7 @@ static int rockchip_pcie_ep_probe(struct platform_device *pdev)
|
||||
|
||||
epc = devm_pci_epc_create(dev, &rockchip_pcie_epc_ops);
|
||||
if (IS_ERR(epc)) {
|
||||
dev_err(dev, "failed to create epc device\n");
|
||||
dev_err(dev, "failed to create EPC device\n");
|
||||
return PTR_ERR(epc);
|
||||
}
|
||||
|
||||
@ -760,11 +879,17 @@ static int rockchip_pcie_ep_probe(struct platform_device *pdev)
|
||||
|
||||
pci_epc_init_notify(epc);
|
||||
|
||||
err = rockchip_pcie_ep_setup_irq(epc);
|
||||
if (err < 0)
|
||||
goto err_uninit_port;
|
||||
|
||||
return 0;
|
||||
err_exit_ob_mem:
|
||||
rockchip_pcie_ep_exit_ob_mem(ep);
|
||||
err_uninit_port:
|
||||
rockchip_pcie_deinit_phys(rockchip);
|
||||
err_disable_clocks:
|
||||
rockchip_pcie_disable_clocks(rockchip);
|
||||
err_exit_ob_mem:
|
||||
rockchip_pcie_ep_exit_ob_mem(ep);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -294,7 +294,7 @@ static int rockchip_pcie_host_init_port(struct rockchip_pcie *rockchip)
|
||||
int err, i = MAX_LANE_NUM;
|
||||
u32 status;
|
||||
|
||||
gpiod_set_value_cansleep(rockchip->ep_gpio, 0);
|
||||
gpiod_set_value_cansleep(rockchip->perst_gpio, 0);
|
||||
|
||||
err = rockchip_pcie_init_port(rockchip);
|
||||
if (err)
|
||||
@ -323,7 +323,7 @@ static int rockchip_pcie_host_init_port(struct rockchip_pcie *rockchip)
|
||||
PCIE_CLIENT_CONFIG);
|
||||
|
||||
msleep(PCIE_T_PVPERL_MS);
|
||||
gpiod_set_value_cansleep(rockchip->ep_gpio, 1);
|
||||
gpiod_set_value_cansleep(rockchip->perst_gpio, 1);
|
||||
|
||||
msleep(PCIE_T_RRS_READY_MS);
|
||||
|
||||
|
@ -119,13 +119,15 @@ int rockchip_pcie_parse_dt(struct rockchip_pcie *rockchip)
|
||||
return PTR_ERR(rockchip->aclk_rst);
|
||||
}
|
||||
|
||||
if (rockchip->is_rc) {
|
||||
rockchip->ep_gpio = devm_gpiod_get_optional(dev, "ep",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(rockchip->ep_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(rockchip->ep_gpio),
|
||||
"failed to get ep GPIO\n");
|
||||
}
|
||||
if (rockchip->is_rc)
|
||||
rockchip->perst_gpio = devm_gpiod_get_optional(dev, "ep",
|
||||
GPIOD_OUT_LOW);
|
||||
else
|
||||
rockchip->perst_gpio = devm_gpiod_get_optional(dev, "reset",
|
||||
GPIOD_IN);
|
||||
if (IS_ERR(rockchip->perst_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(rockchip->perst_gpio),
|
||||
"failed to get PERST# GPIO\n");
|
||||
|
||||
rockchip->aclk_pcie = devm_clk_get(dev, "aclk");
|
||||
if (IS_ERR(rockchip->aclk_pcie)) {
|
||||
|
@ -329,7 +329,7 @@ struct rockchip_pcie {
|
||||
struct regulator *vpcie3v3; /* 3.3V power supply */
|
||||
struct regulator *vpcie1v8; /* 1.8V power supply */
|
||||
struct regulator *vpcie0v9; /* 0.9V power supply */
|
||||
struct gpio_desc *ep_gpio;
|
||||
struct gpio_desc *perst_gpio;
|
||||
u32 lanes;
|
||||
u8 lanes_map;
|
||||
int link_gen;
|
||||
|
Loading…
Reference in New Issue
Block a user