Re: [PATCH v6 8/9] PCI: pwrctrl: Add power control driver for tc9563

From: Krishna Chaitanya Chundru

Date: Fri Sep 26 2025 - 12:43:35 EST




On 9/25/2025 8:09 PM, Bjorn Helgaas wrote:
On Thu, Aug 28, 2025 at 05:39:05PM +0530, Krishna Chaitanya Chundru wrote:
TC9563 is a PCIe switch which has one upstream and three downstream
ports. To one of the downstream ports integrated ethernet MAC is connected
as endpoint device. Other two downstream ports are supposed to connect to
external device. One Host can connect to TC9563 by upstream port. TC9563
switch needs to be configured after powering on and before the PCIe link
was up.

The PCIe controller driver already enables link training at the host side
even before this driver probe happens, due to this when driver enables
power to the switch it participates in the link training and PCIe link
may come up before configuring the switch through i2c. Once the link is
up the configuration done through i2c will not have any affect.To prevent
the host from participating in link training, disable link training on the
host side to ensure the link does not come up before the switch is
configured via I2C.

s/any affect/any effect/
s/.To prevent/. To prevent/

Based up on dt property and type of the port, tc9563 is configured
through i2c.

s/up on/on/

Pick "i2c" or "I2C" and use it consistently.

+config PCI_PWRCTRL_TC9563
+ tristate "PCI Power Control driver for TC9563 PCIe switch"
+ select PCI_PWRCTRL
+ help
+ Say Y here to enable the PCI Power Control driver of TC9563 PCIe
+ switch.
+
+ This driver enables power and configures the TC9563 PCIe switch
+ through i2c.TC9563 is a PCIe switch which has one upstream and three
+ downstream ports. To one of the downstream ports integrated ethernet
+ MAC is connected as endpoint device. Other two downstream ports are
+ supposed to connect to external device.

s/i2c.TC9563/i2c. TC9563/

+static int tc9563_pwrctrl_bring_up(struct tc9563_pwrctrl_ctx *ctx)
+{
+ struct tc9563_pwrctrl_cfg *cfg;
+ int ret, i;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret < 0)
+ return dev_err_probe(ctx->pwrctrl.dev, ret, "cannot enable regulators\n");
+
+ gpiod_set_value(ctx->reset_gpio, 0);
+
+ /*
+ * From TC9563 PORSYS rev 0.2, figure 1.1 POR boot sequence
+ * wait for 10ms for the internal osc frequency to stabilize.
+ */
+ usleep_range(10000, 10500);

Possible place for fsleep() unless you have a specific reason for the
+500us interval?

+static int tc9563_pwrctrl_probe(struct platform_device *pdev)
+{
+ struct pci_host_bridge *bridge = to_pci_host_bridge(pdev->dev.parent);
+ struct pci_dev *pci_dev = to_pci_dev(pdev->dev.parent);
+ struct pci_bus *bus = bridge->bus;
+ struct device *dev = &pdev->dev;
+ enum tc9563_pwrctrl_ports port;
+ struct tc9563_pwrctrl_ctx *ctx;
+ struct device_node *i2c_node;
+ int ret, addr;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ret = of_property_read_u32_index(pdev->dev.of_node, "i2c-parent", 1, &addr);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read i2c-parent property\n");
+
+ i2c_node = of_parse_phandle(dev->of_node, "i2c-parent", 0);
+ ctx->adapter = of_find_i2c_adapter_by_node(i2c_node);
+ of_node_put(i2c_node);
+ if (!ctx->adapter)
+ return dev_err_probe(dev, -EPROBE_DEFER, "Failed to find I2C adapter\n");
+
+ ctx->client = i2c_new_dummy_device(ctx->adapter, addr);
+ if (IS_ERR(ctx->client)) {
+ dev_err(dev, "Failed to create I2C client\n");
+ i2c_put_adapter(ctx->adapter);
+ return PTR_ERR(ctx->client);
+ }
+
+ for (int i = 0; i < TC9563_PWRCTL_MAX_SUPPLY; i++)
+ ctx->supplies[i].supply = tc9563_supply_names[i];
+
+ ret = devm_regulator_bulk_get(dev, TC9563_PWRCTL_MAX_SUPPLY, ctx->supplies);
+ if (ret) {
+ dev_err_probe(dev, ret,
+ "failed to get supply regulator\n");
+ goto remove_i2c;
+ }
+
+ ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(ctx->reset_gpio)) {
+ ret = dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), "failed to get reset GPIO\n");
+ goto remove_i2c;
+ }
+
+ pci_pwrctrl_init(&ctx->pwrctrl, dev);
+
+ port = TC9563_USP;
+ ret = tc9563_pwrctrl_parse_device_dt(ctx, pdev->dev.of_node, port);
+ if (ret) {
+ dev_err(dev, "failed to parse device tree properties: %d\n", ret);
+ goto remove_i2c;
+ }
+
+ /*
+ * Downstream ports are always children of the upstream port.
+ * The first node represents DSP1, the second node represents DSP2, and so on.
+ */
+ for_each_child_of_node_scoped(pdev->dev.of_node, child) {
+ ret = tc9563_pwrctrl_parse_device_dt(ctx, child, port++);
+ if (ret)
+ break;
+ /* Embedded ethernet device are under DSP3 */
+ if (port == TC9563_DSP3)
+ for_each_child_of_node_scoped(child, child1) {
+ ret = tc9563_pwrctrl_parse_device_dt(ctx, child1, port++);
+ if (ret)
+ break;
+ }
+ }
+ if (ret) {
+ dev_err(dev, "failed to parse device tree properties: %d\n", ret);
+ goto remove_i2c;
+ }
+
+ if (!pcie_link_is_active(pci_dev) && bridge->ops->stop_link)
+ bridge->ops->stop_link(bus);

Is this pcie_link_is_active() test backwards? Seems like you would
want to stop the link if it *is* active.

you are right pci_dev extracted seems wrong and not pointing to root
port dev, and returning always zero. It is pointing to pci_dev in host
bridge which doesn't point root port.

Thanks for pointing this.

I will fix it in next version.
pcie_link_is_active() is racy, and this looks like a situation where
that could be an issue. Would something break if you omitted the test
and *always* stopped and started the link here?
if we stop the link if the pcie link is active then we might see AER's &
linkdown.

- Krishna Chaitanya.

+ ret = tc9563_pwrctrl_bring_up(ctx);
+ if (ret)
+ goto remove_i2c;
+
+ if (!pcie_link_is_active(pci_dev) && bridge->ops->start_link) {
+ ret = bridge->ops->start_link(bus);
+ if (ret)
+ goto power_off;
+ }
+
+ ret = devm_pci_pwrctrl_device_set_ready(dev, &ctx->pwrctrl);
+ if (ret)
+ goto power_off;
+
+ platform_set_drvdata(pdev, ctx);
+
+ return 0;
+
+power_off:
+ tc9563_pwrctrl_power_off(ctx);
+remove_i2c:
+ i2c_unregister_device(ctx->client);
+ i2c_put_adapter(ctx->adapter);
+ return ret;
+}