On Mon, 12 Jan 2026 at 18:21, Sean Anderson <[email protected]> wrote: > > Extend the PCI bridge driver to enable resources associated with PCI > slots like clocks, power rails, and resets. This is modeled off of the > PCI power control subsystem in Linux. The traditional compatible for PCI > slots in U-Boot is pci-bridge, but Linux uses the more-systematic > pciclass,0604 so add that as an option. > > Add a test to make sure the clock/gpio get enabled and that we skip some > of the delays when PERST is already asserted. > > Signed-off-by: Sean Anderson <[email protected]> > --- > > Changes in v3: > - Add a test > > Changes in v2: > - Return early if there's no PERST GPIO > - Only mdelay if we need to > - Use CONFIG_IS_ENABLED to set .probe directly > > arch/sandbox/dts/test.dts | 12 ++++++- > configs/sandbox64_defconfig | 2 ++ > configs/sandbox_defconfig | 2 ++ > drivers/pci/Kconfig | 8 +++++ > drivers/pci/pci-uclass.c | 65 +++++++++++++++++++++++++++++++++++++ > test/dm/pci.c | 57 ++++++++++++++++++++++++++++++++ > 6 files changed, 145 insertions(+), 1 deletion(-)
Reviewed-by: Simon Glass <[email protected]> At some point we should have a static inline to skip delays on sandbox, e.g. arch_skip_delays() like we have arch_reset_for_test() - that would avoid the #include and the #ifdef > > diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts > index 962d364f9b2..913dea30fb0 100644 > --- a/arch/sandbox/dts/test.dts > +++ b/arch/sandbox/dts/test.dts > @@ -632,6 +632,13 @@ > clocks = <&clk_fixed>; > }; > > + pci_refclk: clk-gpio { > + compatible = "gpio-gate-clock"; > + #clock-cells = <0>; > + clocks = <&clk_fixed>; > + enable-gpios = <&gpio_a 26>; > + }; > + > osc { > compatible = "fixed-clock"; > #clock-cells = <0>; > @@ -822,7 +829,7 @@ > gpio-controller; > #gpio-cells = <1>; > gpio-bank-name = "a"; > - sandbox,gpio-count = <25>; > + sandbox,gpio-count = <27>; > gpio-line-names = "", "eth1-reset", "rtc-irq"; > hog_input_active_low { > gpio-hog; > @@ -1308,6 +1315,9 @@ > compatible = "pciclass,0604"; > reg = <0x00002000 0 0 0 0>; > ranges; > + > + clocks = <&pci_refclk>; > + reset-gpios = <&gpio_a 25>; > }; > > pci@1e,0 { > diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig > index 440e5efa340..624a44eef59 100644 > --- a/configs/sandbox64_defconfig > +++ b/configs/sandbox64_defconfig > @@ -139,6 +139,7 @@ CONFIG_BUTTON_ADC=y > CONFIG_BUTTON_GPIO=y > CONFIG_CLK=y > CONFIG_CLK_COMPOSITE_CCF=y > +CONFIG_CLK_GPIO=y > CONFIG_CLK_K210=y > CONFIG_CLK_K210_SET_RATE=y > CONFIG_SANDBOX_CLK_CCF=y > @@ -201,6 +202,7 @@ CONFIG_SPI_FLASH_WINBOND=y > CONFIG_NVMXIP_QSPI=y > CONFIG_NVME_PCI=y > CONFIG_PCI_REGION_MULTI_ENTRY=y > +CONFIG_PCI_PWRCTRL_SLOT=y > CONFIG_PCI_SANDBOX=y > CONFIG_PHY=y > CONFIG_PHY_SANDBOX=y > diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig > index 92fb5f844b1..1403ca1d9d0 100644 > --- a/configs/sandbox_defconfig > +++ b/configs/sandbox_defconfig > @@ -191,6 +191,7 @@ CONFIG_BUTTON_ADC=y > CONFIG_BUTTON_GPIO=y > CONFIG_CLK=y > CONFIG_CLK_COMPOSITE_CCF=y > +CONFIG_CLK_GPIO=y > CONFIG_CLK_K210=y > CONFIG_CLK_K210_SET_RATE=y > CONFIG_SANDBOX_CLK_CCF=y > @@ -279,6 +280,7 @@ CONFIG_MULTIPLEXER=y > CONFIG_MUX_MMIO=y > CONFIG_NVME_PCI=y > CONFIG_PCI_REGION_MULTI_ENTRY=y > +CONFIG_PCI_PWRCTRL_SLOT=y > CONFIG_PCI_FTPCI100=y > CONFIG_PCI_SANDBOX=y > CONFIG_PHY=y > diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig > index ea9868425d0..efac18b33f6 100644 > --- a/drivers/pci/Kconfig > +++ b/drivers/pci/Kconfig > @@ -100,6 +100,14 @@ config PCI_ENHANCED_ALLOCATION > Enable support for Enhanced Allocation which can be used by > supported > devices in place of traditional BARS for allocation of resources. > > +config PCI_PWRCTRL_SLOT > + bool "PCI slot power control" > + help > + This is a generic driver that controls the power state of different > + PCI slots. The clocks and resets for the PCI slots are expected to > be > + defined in the devicetree node of the PCI bridge. Say N if your PCI > + busses don't have software-controlled clocks or power rails. > + > config PCI_ARID > bool "Enable Alternate Routing-ID support for PCI" > help > diff --git a/drivers/pci/pci-uclass.c b/drivers/pci/pci-uclass.c > index c370f8c6400..6f0e6c2f8cc 100644 > --- a/drivers/pci/pci-uclass.c > +++ b/drivers/pci/pci-uclass.c > @@ -6,6 +6,7 @@ > > #define LOG_CATEGORY UCLASS_PCI > > +#include <clk.h> > #include <dm.h> > #include <errno.h> > #include <init.h> > @@ -14,6 +15,7 @@ > #include <pci.h> > #include <spl.h> > #include <asm/global_data.h> > +#include <asm/gpio.h> > #include <asm/io.h> > #include <dm/device-internal.h> > #include <dm/lists.h> > @@ -1893,13 +1895,76 @@ static const struct dm_pci_ops pci_bridge_ops = { > > static const struct udevice_id pci_bridge_ids[] = { > { .compatible = "pci-bridge" }, > + { .compatible = "pciclass,0604" }, > { } > }; > > +#ifdef CONFIG_SANDBOX > +#include <asm/state.h> > +#endif > + > +static int __maybe_unused pci_bridge_probe(struct udevice *dev) > +{ > + struct clk clk; > + struct gpio_desc perst; > + unsigned long delay = 0; > + int ret; > + > + if (!clk_get_by_index(dev, 0, &clk)) { > + int ret = clk_enable(&clk); > + > + if (ret) > + return log_msg_ret("clk", ret); > + > + /* Delay for T_PERST-CLK (100 us for all slot types) */ > + udelay(100); > + } > + > + if (gpio_request_by_name(dev, "reset-gpios", 0, &perst, 0)) > + return 0; > + > + /* > + * If PERST is inactive, the following call to dm_gpio_clrset_flags > + * will be the first time we assert it and we will need to delay for > + * T_PERST. > + */ > + if (dm_gpio_get_value(&perst) != 1) > + delay = 100; > + > + ret = dm_gpio_clrset_flags(&perst, GPIOD_MASK_DIR, > + GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); > + if (ret) > + return log_msg_ret("set", ret); > + > +#ifdef CONFIG_SANDBOX > + if (!state_get_skip_delays()) > +#endif > + if (delay) > + mdelay(delay); > + > + ret = dm_gpio_set_value(&perst, 0); > + if (ret) > + return log_msg_ret("clr", ret); > + > + /* > + * PCIe section 6.6.1: > + * > ... software must wait a minimum of 100 ms before sending a > + * > Configuration Request to the device immediately below that Port. > + */ > +#ifdef CONFIG_SANDBOX > + if (!state_get_skip_delays()) > +#endif > + mdelay(100); > + > + return 0; > +} > + > U_BOOT_DRIVER(pci_bridge_drv) = { > .name = "pci_bridge_drv", > .id = UCLASS_PCI, > .of_match = pci_bridge_ids, > + .probe = CONFIG_IS_ENABLED(PCI_PWRCTRL_SLOT, > + (pci_bridge_probe), NULL), > .ops = &pci_bridge_ops, > }; > > diff --git a/test/dm/pci.c b/test/dm/pci.c > index b6fee7b3bb3..f594783444b 100644 > --- a/test/dm/pci.c > +++ b/test/dm/pci.c > @@ -5,6 +5,7 @@ > > #include <dm.h> > #include <dm/device_compat.h> > +#include <asm/gpio.h> > #include <asm/io.h> > #include <asm/test.h> > #include <dm/test.h> > @@ -16,6 +17,7 @@ static int dm_test_pci_base(struct unit_test_state *uts) > { > struct udevice *bus; > > + test_set_skip_delays(true); > ut_assertok(uclass_get_device(UCLASS_PCI, 0, &bus)); > > return 0; > @@ -30,6 +32,7 @@ static int dm_test_pci_busdev(struct unit_test_state *uts) > u16 vendor, device; > > /* Test bus#0 and its devices */ > + test_set_skip_delays(true); > ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 0, &bus)); > > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x00, 0), &swap)); > @@ -65,6 +68,7 @@ static int dm_test_pci_swapcase(struct unit_test_state *uts) > char *ptr; > > /* Check that asking for the device 0 automatically fires up PCI */ > + test_set_skip_delays(true); > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x00, 0), &swap)); > > /* First test I/O */ > @@ -116,6 +120,7 @@ static int dm_test_pci_drvdata(struct unit_test_state > *uts) > struct udevice *bus, *swap; > > /* Check that asking for the device automatically fires up PCI */ > + test_set_skip_delays(true); > ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 1, &bus)); > > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &swap)); > @@ -141,6 +146,7 @@ static int dm_test_pci_mixed(struct unit_test_state *uts) > ulong io_addr, mem_addr; > char *ptr; > > + test_set_skip_delays(true); > ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 2, &bus)); > > /* Test the dynamic device */ > @@ -201,6 +207,7 @@ static int dm_test_pci_cap(struct unit_test_state *uts) > struct udevice *bus, *swap; > int cap; > > + test_set_skip_delays(true); > ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 0, &bus)); > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &swap)); > > @@ -258,6 +265,7 @@ static int dm_test_pci_ea(struct unit_test_state *uts) > * use emulated device mapping function, we're not using real physical > * addresses in this test > */ > + test_set_skip_delays(true); > sandbox_set_enable_pci_map(true); > > ut_assertok(uclass_get_device_by_seq(UCLASS_PCI, 0, &bus)); > @@ -303,6 +311,7 @@ static int dm_test_pci_addr_flat(struct unit_test_state > *uts) > ulong io_addr, mem_addr; > fdt_addr_t size; > > + test_set_skip_delays(true); > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &swap1f)); > io_addr = dm_pci_read_bar32(swap1f, 0); > ut_asserteq(io_addr, dev_read_addr_pci(swap1f, &size)); > @@ -334,6 +343,7 @@ static int dm_test_pci_addr_live(struct unit_test_state > *uts) > struct udevice *swap1f, *swap1; > fdt_size_t size; > > + test_set_skip_delays(true); > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &swap1f)); > ut_asserteq_64(FDT_ADDR_T_NONE, dev_read_addr_pci(swap1f, &size)); > ut_asserteq(0, size); > @@ -351,6 +361,7 @@ static int dm_test_pci_on_bus(struct unit_test_state *uts) > { > struct udevice *dev; > > + test_set_skip_delays(true); > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x1f, 0), &dev)); > ut_asserteq(true, device_is_on_pci_bus(dev)); ut_assert(device_is_on_pci_bus(dev)); > ut_asserteq(false, device_is_on_pci_bus(dev_get_parent(dev))); ut_assert(!...) > @@ -374,6 +385,7 @@ static int dm_test_pci_region_multi(struct > unit_test_state *uts) > ulong mem_addr; > > /* Test memory BAR1 on bus#1 */ > + test_set_skip_delays(true); > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &dev)); > mem_addr = dm_pci_read_bar32(dev, 1); > ut_asserteq(mem_addr, 0x30000000); > @@ -393,6 +405,7 @@ static int dm_test_pci_bus_to_phys(struct unit_test_state > *uts) > struct udevice *dev; > phys_addr_t phys_addr; > > + test_set_skip_delays(true); > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &dev)); > > /* Before any of the ranges. */ > @@ -446,6 +459,7 @@ static int dm_test_pci_phys_to_bus(struct unit_test_state > *uts) > struct udevice *dev; > pci_addr_t pci_addr; > > + test_set_skip_delays(true); > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(1, 0x08, 0), &dev)); > > /* Before any of the ranges. */ > @@ -620,6 +634,7 @@ static int dm_test_pci_bridge_windows(struct > unit_test_state *uts) > { > struct udevice *bus; > > + test_set_skip_delays(true); > ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x03, 0), &bus)); > if (_dm_test_pci_bridge_windows(uts, bus)) > return CMD_RET_FAILURE; > @@ -628,3 +643,45 @@ static int dm_test_pci_bridge_windows(struct > unit_test_state *uts) > return _dm_test_pci_bridge_windows(uts, bus); > } > DM_TEST(dm_test_pci_bridge_windows, UTF_SCAN_PDATA | UTF_SCAN_FDT); > + > +#if IS_ENABLED(CONFIG_PCI_PWRCTRL_SLOT) > +/* GPIO 25 is PERST; GPIO 26 is REFCLK enable */ > +static int dm_test_pci_pwrseq_cold(struct unit_test_state *uts) > +{ > + struct udevice *bus, *gpio; > + unsigned long start, end; > + > + ut_assertok(uclass_get_device(UCLASS_GPIO, 1, &gpio)); > + ut_assertok(sandbox_gpio_set_value(gpio, 25, 1)); > + > + start = timer_get_us(); > + ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x04, 0), &bus)); > + end = timer_get_us(); > + ut_assert(end - start > 100000); > + ut_assert(end - start < 200000); We do have get_timer_us() so another option is: start = get_timer_us(0); ut_assert(get_timer_us(start) > 100000); > + > + ut_asserteq(0, sandbox_gpio_get_value(gpio, 25)); > + ut_asserteq(1, sandbox_gpio_get_value(gpio, 26)); > + > + return 0; > +} > +DM_TEST(dm_test_pci_pwrseq_cold, UTF_SCAN_PDATA | UTF_SCAN_FDT); > + > +static int dm_test_pci_pwrseq_warm(struct unit_test_state *uts) > +{ > + struct udevice *bus, *gpio; > + unsigned long start; > + > + ut_assertok(uclass_get_device(UCLASS_GPIO, 1, &gpio)); > + > + start = timer_get_us(); > + ut_assertok(dm_pci_bus_find_bdf(PCI_BDF(0, 0x04, 0), &bus)); > + ut_assert(timer_get_us() - start > 200000); > + > + ut_asserteq(0, sandbox_gpio_get_value(gpio, 25)); > + ut_asserteq(1, sandbox_gpio_get_value(gpio, 26)); > + > + return 0; > +} > +DM_TEST(dm_test_pci_pwrseq_warm, UTF_SCAN_PDATA | UTF_SCAN_FDT); > +#endif > -- > 2.35.1.1320.gc452695387.dirty > Regards, Simon

