diff options
author | Tom Rini <trini@konsulko.com> | 2020-02-10 12:27:31 -0500 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2020-02-10 12:27:31 -0500 |
commit | 4e5c4683b7a54090323043ab9a67772baeecb1b1 (patch) | |
tree | f92a230d39f521cdeafc45a8ac9eb036e76983b2 | |
parent | 5f2fe7d4617bbddc45b6a0cbf21cd468c57f4eba (diff) | |
parent | 0f6a70e971b2d87de3e58e8f0b51b0cd6723bc96 (diff) |
Merge https://gitlab.denx.de/u-boot/custodians/u-boot-x86
- Move P2SB from Apollo Lake to a more generic location
- Add a function to find a device by drvdata in DM core
- Enhancement of DM IRQ uclass driver
- Add a clock driver for Intel devices
- Add support for ACPI general-purpose events
- Add a TPM driver for H1/Cr50
- Enable TPM on Google Chromebook Coral
38 files changed, 1640 insertions, 94 deletions
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index e529c54d8d..c228447431 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -93,6 +93,7 @@ <&gpio_b 9 0xc 3 2 1>; int-value = <1234>; uint-value = <(-1234)>; + interrupts-extended = <&irq 3 0>; }; junk { @@ -357,8 +358,10 @@ vss-microvolts = <0>; }; - irq { + irq: irq { compatible = "sandbox,irq"; + interrupt-controller; + #interrupt-cells = <2>; }; lcd { diff --git a/arch/sandbox/include/asm/test.h b/arch/sandbox/include/asm/test.h index 2421922c9e..7775794eaa 100644 --- a/arch/sandbox/include/asm/test.h +++ b/arch/sandbox/include/asm/test.h @@ -45,6 +45,10 @@ #define PCI_EA_BAR2_MAGIC 0x72727272 #define PCI_EA_BAR4_MAGIC 0x74747474 +enum { + SANDBOX_IRQN_PEND = 1, /* Interrupt number for 'pending' test */ +}; + /* System controller driver data */ enum { SYSCON0 = 32, diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index b733d2264e..c8eae24c07 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -715,6 +715,13 @@ config HAVE_ITSS Select this to include the driver for the Interrupt Timer Subsystem (ITSS) which is found on several Intel devices. +config HAVE_P2SB + bool "Enable P2SB" + help + Select this to include the driver for the Primary to + Sideband Bridge (P2SB) which is found on several Intel + devices. + menu "System tables" depends on !EFI && !SYS_COREBOOT @@ -916,4 +923,37 @@ config X86_OFFSET_SPL depends on SPL && X86 default SPL_TEXT_BASE +config ACPI_GPE + bool "Support ACPI general-purpose events" + help + Enable a driver for ACPI GPEs to allow peripherals to send interrupts + via ACPI to the OS. In U-Boot this is only used when U-Boot itself + needs access to these interrupts. This can happen when it uses a + peripheral that is set up to use GPEs and so cannot use the normal + GPIO mechanism for polling an input. + + See https://queue.acm.org/blogposting.cfm?id=18977 for more info + +config SPL_ACPI_GPE + bool "Support ACPI general-purpose events in SPL" + help + Enable a driver for ACPI GPEs to allow peripherals to send interrupts + via ACPI to the OS. In U-Boot this is only used when U-Boot itself + needs access to these interrupts. This can happen when it uses a + peripheral that is set up to use GPEs and so cannot use the normal + GPIO mechanism for polling an input. + + See https://queue.acm.org/blogposting.cfm?id=18977 for more info + +config TPL_ACPI_GPE + bool "Support ACPI general-purpose events in TPL" + help + Enable a driver for ACPI GPEs to allow peripherals to send interrupts + via ACPI to the OS. In U-Boot this is only used when U-Boot itself + needs access to these interrupts. This can happen when it uses a + peripheral that is set up to use GPEs and so cannot use the normal + GPIO mechanism for polling an input. + + See https://queue.acm.org/blogposting.cfm?id=18977 for more info + endmenu diff --git a/arch/x86/cpu/Makefile b/arch/x86/cpu/Makefile index 5b40838e60..307267a8fb 100644 --- a/arch/x86/cpu/Makefile +++ b/arch/x86/cpu/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_INTEL_QUEENSBAY) += queensbay/ obj-$(CONFIG_INTEL_TANGIER) += tangier/ obj-$(CONFIG_APIC) += lapic.o ioapic.o obj-$(CONFIG_$(SPL_TPL_)X86_32BIT_INIT) += irq.o +obj-$(CONFIG_$(SPL_TPL_)ACPI_GPE) += acpi_gpe.o obj-$(CONFIG_QFW) += qfw_cpu.o ifndef CONFIG_$(SPL_)X86_64 obj-$(CONFIG_SMP) += mp_init.o diff --git a/arch/x86/cpu/acpi_gpe.c b/arch/x86/cpu/acpi_gpe.c new file mode 100644 index 0000000000..55005455c0 --- /dev/null +++ b/arch/x86/cpu/acpi_gpe.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019 Google, LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <dm.h> +#include <irq.h> +#include <asm/io.h> + +/** + * struct acpi_gpe_priv - private driver information + * + * @acpi_base: Base I/O address of ACPI registers + */ +struct acpi_gpe_priv { + ulong acpi_base; +}; + +#define GPE0_STS(x) (0x20 + ((x) * 4)) + +static int acpi_gpe_read_and_clear(struct irq *irq) +{ + struct acpi_gpe_priv *priv = dev_get_priv(irq->dev); + u32 mask, sts; + ulong start; + int ret = 0; + int bank; + + bank = irq->id / 32; + mask = 1 << (irq->id % 32); + + /* Wait up to 1ms for GPE status to clear */ + start = get_timer(0); + do { + if (get_timer(start) > 1) + return ret; + + sts = inl(priv->acpi_base + GPE0_STS(bank)); + if (sts & mask) { + outl(mask, priv->acpi_base + GPE0_STS(bank)); + ret = 1; + } + } while (sts & mask); + + return ret; +} + +static int acpi_gpe_ofdata_to_platdata(struct udevice *dev) +{ + struct acpi_gpe_priv *priv = dev_get_priv(dev); + + priv->acpi_base = dev_read_addr(dev); + if (!priv->acpi_base || priv->acpi_base == FDT_ADDR_T_NONE) + return log_msg_ret("acpi_base", -EINVAL); + + return 0; +} + +static int acpi_gpe_of_xlate(struct irq *irq, struct ofnode_phandle_args *args) +{ + irq->id = args->args[0]; + + return 0; +} + +static const struct irq_ops acpi_gpe_ops = { + .read_and_clear = acpi_gpe_read_and_clear, + .of_xlate = acpi_gpe_of_xlate, +}; + +static const struct udevice_id acpi_gpe_ids[] = { + { .compatible = "intel,acpi-gpe", .data = X86_IRQT_ACPI_GPE }, + { } +}; + +U_BOOT_DRIVER(acpi_gpe_drv) = { + .name = "acpi_gpe", + .id = UCLASS_IRQ, + .of_match = acpi_gpe_ids, + .ops = &acpi_gpe_ops, + .ofdata_to_platdata = acpi_gpe_ofdata_to_platdata, + .priv_auto_alloc_size = sizeof(struct acpi_gpe_priv), +}; diff --git a/arch/x86/cpu/apollolake/Kconfig b/arch/x86/cpu/apollolake/Kconfig index a760e0ac68..942f11f566 100644 --- a/arch/x86/cpu/apollolake/Kconfig +++ b/arch/x86/cpu/apollolake/Kconfig @@ -40,6 +40,11 @@ config INTEL_APOLLOLAKE imply INTEL_GPIO imply SMP imply HAVE_ITSS + imply HAVE_P2SB + imply CLK + imply CMD_CLK + imply CLK_INTEL + imply ACPI_GPE if INTEL_APOLLOLAKE diff --git a/arch/x86/cpu/apollolake/Makefile b/arch/x86/cpu/apollolake/Makefile index f99f2c6473..578e15c4bf 100644 --- a/arch/x86/cpu/apollolake/Makefile +++ b/arch/x86/cpu/apollolake/Makefile @@ -20,7 +20,6 @@ endif obj-y += hostbridge.o obj-y += lpc.o -obj-y += p2sb.o obj-y += pch.o obj-y += pmc.o obj-y += uart.o diff --git a/arch/x86/cpu/apollolake/fsp_s.c b/arch/x86/cpu/apollolake/fsp_s.c index 9804227f80..b2d9130841 100644 --- a/arch/x86/cpu/apollolake/fsp_s.c +++ b/arch/x86/cpu/apollolake/fsp_s.c @@ -24,7 +24,6 @@ #define HIDE_BIT BIT(0) #define INTEL_GSPI_MAX 3 -#define INTEL_I2C_DEV_MAX 8 #define MAX_USB2_PORTS 8 enum { @@ -32,36 +31,6 @@ enum { CHIPSET_LOCKDOWN_COREBOOT, /* coreboot handles locking */ }; -enum i2c_speed { - I2C_SPEED_STANDARD = 100000, - I2C_SPEED_FAST = 400000, - I2C_SPEED_FAST_PLUS = 1000000, - I2C_SPEED_HIGH = 3400000, - I2C_SPEED_FAST_ULTRA = 5000000, -}; - -/* - * Timing values are in units of clock period, with the clock speed - * provided by the SOC - * - * TODO(sjg@chromium.org): Connect this up to the I2C driver - */ -struct dw_i2c_speed_config { - enum i2c_speed speed; - /* SCL high and low period count */ - u16 scl_lcnt; - u16 scl_hcnt; - /* - * SDA hold time should be 300ns in standard and fast modes - * and long enough for deterministic logic level change in - * fast-plus and high speed modes. - * - * [15:0] SDA TX Hold Time - * [23:16] SDA RX Hold Time - */ - u32 sda_hold; -}; - /* Serial IRQ control. SERIRQ_QUIET is the default (0) */ enum serirq_mode { SERIRQ_QUIET, @@ -69,32 +38,6 @@ enum serirq_mode { SERIRQ_OFF, }; -/* - * This I2C controller has support for 3 independent speed configs but can - * support both FAST_PLUS and HIGH speeds through the same set of speed - * config registers. These are treated separately so the speed config values - * can be provided via ACPI to the OS. - */ -#define DW_I2C_SPEED_CONFIG_COUNT 4 - -struct dw_i2c_bus_config { - /* Bus should be enabled in TPL with temporary base */ - int early_init; - /* Bus speed in Hz, default is I2C_SPEED_FAST (400 KHz) */ - enum i2c_speed speed; - /* - * If rise_time_ns is non-zero the calculations for lcnt and hcnt - * registers take into account the times of the bus. However, if - * there is a match in speed_config those register values take - * precedence - */ - int rise_time_ns; - int fall_time_ns; - int data_hold_time_ns; - /* Specific bus speed configuration */ - struct dw_i2c_speed_config speed_config[DW_I2C_SPEED_CONFIG_COUNT]; -}; - struct gspi_cfg { /* Bus speed in MHz */ u32 speed_mhz; @@ -110,7 +53,6 @@ struct gspi_cfg { struct soc_intel_common_config { int chipset_lockdown; struct gspi_cfg gspi[INTEL_GSPI_MAX]; - struct dw_i2c_bus_config i2c[INTEL_I2C_DEV_MAX]; }; enum pnp_settings { @@ -593,7 +535,7 @@ int arch_fsps_preinit(void) struct udevice *itss; int ret; - ret = uclass_first_device_err(UCLASS_IRQ, &itss); + ret = irq_first_device_type(X86_IRQT_ITSS, &itss); if (ret) return log_msg_ret("no itss", ret); /* @@ -634,7 +576,7 @@ int arch_fsp_init_r(void) if (ret) return ret; - ret = uclass_first_device_err(UCLASS_IRQ, &itss); + ret = irq_first_device_type(X86_IRQT_ITSS, &itss); if (ret) return log_msg_ret("no itss", ret); /* Restore GPIO IRQ polarities back to previous settings */ diff --git a/arch/x86/cpu/i386/interrupt.c b/arch/x86/cpu/i386/interrupt.c index 70edbe06e4..4c7e9ea215 100644 --- a/arch/x86/cpu/i386/interrupt.c +++ b/arch/x86/cpu/i386/interrupt.c @@ -15,6 +15,7 @@ #include <efi_loader.h> #include <hang.h> #include <init.h> +#include <irq.h> #include <irq_func.h> #include <asm/control_regs.h> #include <asm/i8259.h> @@ -264,7 +265,7 @@ int interrupt_init(void) int ret; /* Try to set up the interrupt router, but don't require one */ - ret = uclass_first_device_err(UCLASS_IRQ, &dev); + ret = irq_first_device_type(X86_IRQT_BASE, &dev); if (ret && ret != -ENODEV) return ret; diff --git a/arch/x86/cpu/intel_common/Makefile b/arch/x86/cpu/intel_common/Makefile index e22c70781d..1736bd2b53 100644 --- a/arch/x86/cpu/intel_common/Makefile +++ b/arch/x86/cpu/intel_common/Makefile @@ -28,6 +28,7 @@ endif endif obj-y += pch.o obj-$(CONFIG_HAVE_ITSS) += itss.o +obj-$(CONFIG_HAVE_P2SB) += p2sb.o ifdef CONFIG_SPL ifndef CONFIG_SPL_BUILD diff --git a/arch/x86/cpu/intel_common/itss.c b/arch/x86/cpu/intel_common/itss.c index 9df51adecc..b6b57cc9a0 100644 --- a/arch/x86/cpu/intel_common/itss.c +++ b/arch/x86/cpu/intel_common/itss.c @@ -146,6 +146,15 @@ static int route_pmc_gpio_gpe(struct udevice *dev, uint pmc_gpe_num) return -ENOENT; } +static int itss_bind(struct udevice *dev) +{ + /* This is not set with of-platdata, so set it manually */ + if (CONFIG_IS_ENABLED(OF_PLATDATA)) + dev->driver_data = X86_IRQT_ITSS; + + return 0; +} + static int itss_ofdata_to_platdata(struct udevice *dev) { struct itss_priv *priv = dev_get_priv(dev); @@ -199,7 +208,7 @@ static const struct irq_ops itss_ops = { }; static const struct udevice_id itss_ids[] = { - { .compatible = "intel,itss"}, + { .compatible = "intel,itss", .data = X86_IRQT_ITSS }, { } }; @@ -208,6 +217,7 @@ U_BOOT_DRIVER(itss_drv) = { .id = UCLASS_IRQ, .of_match = itss_ids, .ops = &itss_ops, + .bind = itss_bind, .ofdata_to_platdata = itss_ofdata_to_platdata, .platdata_auto_alloc_size = sizeof(struct itss_platdata), .priv_auto_alloc_size = sizeof(struct itss_priv), diff --git a/arch/x86/cpu/apollolake/p2sb.c b/arch/x86/cpu/intel_common/p2sb.c index b72f50a627..b72f50a627 100644 --- a/arch/x86/cpu/apollolake/p2sb.c +++ b/arch/x86/cpu/intel_common/p2sb.c diff --git a/arch/x86/cpu/irq.c b/arch/x86/cpu/irq.c index ed9938f7f7..bafa031082 100644 --- a/arch/x86/cpu/irq.c +++ b/arch/x86/cpu/irq.c @@ -7,6 +7,7 @@ #include <dm.h> #include <errno.h> #include <fdtdec.h> +#include <irq.h> #include <malloc.h> #include <asm/io.h> #include <asm/irq.h> @@ -351,7 +352,7 @@ int irq_router_probe(struct udevice *dev) } static const struct udevice_id irq_router_ids[] = { - { .compatible = "intel,irq-router" }, + { .compatible = "intel,irq-router", .data = X86_IRQT_BASE }, { } }; diff --git a/arch/x86/dts/chromebook_coral.dts b/arch/x86/dts/chromebook_coral.dts index a1820fa187..44a4619a66 100644 --- a/arch/x86/dts/chromebook_coral.dts +++ b/arch/x86/dts/chromebook_coral.dts @@ -20,6 +20,7 @@ #include <asm/arch-apollolake/gpio.h> #include <asm/arch-apollolake/iomap.h> #include <asm/arch-apollolake/pm.h> +#include <dt-bindings/clock/intel-clock.h> / { model = "Google Coral"; @@ -29,6 +30,14 @@ cros-ec0 = &cros_ec; fsp = &fsp_s; spi0 = &spi; + i2c0 = &i2c_0; + i2c1 = &i2c_1; + i2c2 = &i2c_2; + i2c3 = &i2c_3; + i2c4 = &i2c_4; + i2c5 = &i2c_5; + i2c6 = &i2c_6; + i2c7 = &i2c_7; }; config { @@ -39,6 +48,11 @@ stdout-path = &serial; }; + clk: clock { + compatible = "intel,apl-clk"; + #clock-cells = <1>; + }; + cpus { u-boot,dm-pre-reloc; #address-cells = <1>; @@ -75,6 +89,13 @@ }; + acpi_gpe: general-purpose-events { + reg = <IOMAP_ACPI_BASE IOMAP_ACPI_SIZE>; + compatible = "intel,acpi-gpe"; + interrupt-controller; + #interrupt-cells = <2>; + }; + keyboard { intel,duplicate-por; }; @@ -243,6 +264,78 @@ }; }; + i2c_0: i2c2@16,0 { + compatible = "intel,apl-i2c"; + reg = <0x0200b010 0 0 0 0>; + clocks = <&clk CLK_I2C>; + i2c-scl-rising-time-ns = <104>; + i2c-scl-falling-time-ns = <52>; + }; + + i2c_1: i2c2@16,1 { + compatible = "intel,apl-i2c"; + reg = <0x0200b110 0 0 0 0>; + clocks = <&clk CLK_I2C>; + status = "disabled"; + }; + + i2c_2: i2c2@16,2 { + compatible = "intel,apl-i2c"; + reg = <0x0200b210 0 0 0 0>; + #address-cells = <1>; + #size-cells = <0>; + clock-frequency = <400000>; + clocks = <&clk CLK_I2C>; + i2c-scl-rising-time-ns = <57>; + i2c-scl-falling-time-ns = <28>; + tpm@50 { + reg = <0x50>; + compatible = "google,cr50"; + u-boot,i2c-offset-len = <0>; + ready-gpio = <&gpio_n 28 GPIO_ACTIVE_LOW>; + interrupts-extended = <&acpi_gpe 0x3c 0>; + }; + }; + + i2c_3: i2c2@16,3 { + compatible = "intel,apl-i2c"; + reg = <0x0200b110 0 0 0 0>; + clocks = <&clk CLK_I2C>; + i2c-scl-rising-time-ns = <76>; + i2c-scl-falling-time-ns = <164>; + }; + + i2c_4: i2c2@17,0 { + compatible = "intel,apl-i2c"; + reg = <0x0200b110 0 0 0 0>; + clocks = <&clk CLK_I2C>; + i2c-sda-hold-time-ns = <350>; + i2c-scl-rising-time-ns = <114>; + i2c-scl-falling-time-ns = <164>; + }; + + i2c_5: i2c2@17,1 { + compatible = "intel,apl-i2c"; + reg = <0x0200b110 0 0 0 0>; + clocks = <&clk CLK_I2C>; + i2c-scl-rising-time-ns = <76>; + i2c-scl-falling-time-ns = <164>; + }; + + i2c_6: i2c2@17,2 { + compatible = "intel,apl-i2c"; + reg = <0x0200b110 0 0 0 0>; + clocks = <&clk CLK_I2C>; + status = "disabled"; + }; + + i2c_7: i2c2@17,3 { + compatible = "intel,apl-i2c"; + reg = <0x0200b110 0 0 0 0>; + clocks = <&clk CLK_I2C>; + status = "disabled"; + }; + serial: serial@18,2 { reg = <0x0200c210 0 0 0 0>; u-boot,dm-pre-reloc; diff --git a/configs/chromebook_coral_defconfig b/configs/chromebook_coral_defconfig index b156e837ee..a7b71d99a1 100644 --- a/configs/chromebook_coral_defconfig +++ b/configs/chromebook_coral_defconfig @@ -53,7 +53,6 @@ CONFIG_CMD_TIME=y CONFIG_CMD_SOUND=y CONFIG_CMD_BOOTSTAGE=y CONFIG_CMD_TPM=y -CONFIG_CMD_TPM_TEST=y CONFIG_CMD_EXT2=y CONFIG_CMD_EXT4=y CONFIG_CMD_EXT4_WRITE=y @@ -90,6 +89,8 @@ CONFIG_SPI=y CONFIG_ICH_SPI=y CONFIG_TPL_SYSRESET=y CONFIG_TPM_TIS_LPC=y +# CONFIG_TPM_V1 is not set +CONFIG_TPM2_CR50_I2C=y CONFIG_USB_XHCI_HCD=y CONFIG_USB_STORAGE=y CONFIG_USB_KEYBOARD=y diff --git a/doc/board/google/chromebook_coral.rst b/doc/board/google/chromebook_coral.rst index 5dc3c97c3d..d10e0c4954 100644 --- a/doc/board/google/chromebook_coral.rst +++ b/doc/board/google/chromebook_coral.rst @@ -213,9 +213,7 @@ To do - left-side USB - USB-C - Cr50 (security chip: a basic driver is running but not included here) - - I2C (driver exists but not enabled in device tree) - Sound (Intel I2S support exists, but need da7219 driver) - - RTC (driver exists but not enabled in device tree) - Various minor features supported by LPC, etc. - Booting Chrome OS, e.g. with verified boot - Integrate with Chrome OS vboot diff --git a/doc/device-tree-bindings/interrupt-controller/intel,acpi-gpe.txt b/doc/device-tree-bindings/interrupt-controller/intel,acpi-gpe.txt new file mode 100644 index 0000000000..d9252bf29f --- /dev/null +++ b/doc/device-tree-bindings/interrupt-controller/intel,acpi-gpe.txt @@ -0,0 +1,30 @@ +* Intel Advanced Configuration and Power Interface General Purpose Events + +This describes an interrupt controller which provides access to GPEs supported +by the SoC. + +Required properties: + +- compatible : "intel,acpi-gpe" +- interrupt-controller : Identifies the node as an interrupt controller +- #interrupt-cells : The number of cells to define the interrupts. Must be 2: + cell 0: interrupt number (normally >=32 since GPEs below that are reserved) + cell 1: 0 (flags, but none are currently defined) +- reg : The register bank for the controller (set this to the ACPI base). + +Example: + + general-purpose-events { + reg = <IOMAP_ACPI_BASE IOMAP_ACPI_SIZE>; + compatible = "intel,acpi-gpe"; + interrupt-controller; + #interrupt-cells = <2>; + }; + + ... + tpm@50 { + reg = <0x50>; + compatible = "google,cr50"; + ready-gpio = <&gpio_n 0x1c GPIO_ACTIVE_LOW>; + interrupts-extended = <&acpi_gpe 0x3c 0>; + }; diff --git a/doc/device-tree-bindings/interrupt-controller/interrupts.txt b/doc/device-tree-bindings/interrupt-controller/interrupts.txt new file mode 100644 index 0000000000..38a399a6b1 --- /dev/null +++ b/doc/device-tree-bindings/interrupt-controller/interrupts.txt @@ -0,0 +1,131 @@ +Specifying interrupt information for devices +============================================ + +1) Interrupt client nodes +------------------------- + +Nodes that describe devices which generate interrupts must contain an +"interrupts" property, an "interrupts-extended" property, or both. If both are +present, the latter should take precedence; the former may be provided simply +for compatibility with software that does not recognize the latter. These +properties contain a list of interrupt specifiers, one per output interrupt. The +format of the interrupt specifier is determined by the interrupt controller to +which the interrupts are routed; see section 2 below for details. + + Example: + interrupt-parent = <&intc1>; + interrupts = <5 0>, <6 0>; + +The "interrupt-parent" property is used to specify the controller to which +interrupts are routed and contains a single phandle referring to the interrupt +controller node. This property is inherited, so it may be specified in an +interrupt client node or in any of its parent nodes. Interrupts listed in the +"interrupts" property are always in reference to the node's interrupt parent. + +The "interrupts-extended" property is a special form; useful when a node needs +to reference multiple interrupt parents or a different interrupt parent than +the inherited one. Each entry in this property contains both the parent phandle +and the interrupt specifier. + + Example: + interrupts-extended = <&intc1 5 1>, <&intc2 1 0>; + +(NOTE: only this 'special form' is supported in U-Boot) + + +2) Interrupt controller nodes +----------------------------- + +A device is marked as an interrupt controller with the "interrupt-controller" +property. This is a empty, boolean property. An additional "#interrupt-cells" +property defines the number of cells needed to specify a single interrupt. + +It is the responsibility of the interrupt controller's binding to define the +length and format of the interrupt specifier. The following two variants are +commonly used: + + a) one cell + ----------- + The #interrupt-cells property is set to 1 and the single cell defines the + index of the interrupt within the controller. + + Example: + + vic: intc@10140000 { + compatible = "arm,versatile-vic"; + interrupt-controller; + #interrupt-cells = <1>; + reg = <0x10140000 0x1000>; + }; + + sic: intc@10003000 { + compatible = "arm,versatile-sic"; + interrupt-controller; + #interrupt-cells = <1>; + reg = <0x10003000 0x1000>; + interrupt-parent = <&vic>; + interrupts = <31>; /* Cascaded to vic */ + }; + + b) two cells + ------------ + The #interrupt-cells property is set to 2 and the first cell defines the + index of the interrupt within the controller, while the second cell is used + to specify any of the following flags: + - bits[3:0] trigger type and level flags + 1 = low-to-high edge triggered + 2 = high-to-low edge triggered + 4 = active high level-sensitive + 8 = active low level-sensitive + + Example: + + i2c@7000c000 { + gpioext: gpio-adnp@41 { + compatible = "ad,gpio-adnp"; + reg = <0x41>; + + interrupt-parent = <&gpio>; + interrupts = <160 1>; + + gpio-controller; + #gpio-cells = <1>; + + interrupt-controller; + #interrupt-cells = <2>; + + nr-gpios = <64>; + }; + + sx8634@2b { + compatible = "smtc,sx8634"; + reg = <0x2b>; + + interrupt-parent = <&gpioext>; + interrupts = <3 0x8>; + + #address-cells = <1>; + #size-cells = <0>; + + threshold = <0x40>; + sensitivity = <7>; + }; + }; + + +Example of special form (supported by U-Boot): + + acpi_gpe: general-purpose-events { + reg = <IOMAP_ACPI_BASE IOMAP_ACPI_SIZE>; + compatible = "intel,acpi-gpe"; + interrupt-controller; + #interrupt-cells = <2>; + }; + + tpm@50 { + reg = <0x50>; + compatible = "google,cr50"; + u-boot,i2c-offset-len = <0>; + ready-gpio = <&gpio_n 28 GPIO_ACTIVE_LOW>; + interrupts-extended = <&acpi_gpe 0x3c 0>; + }; diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 16d4237f89..1992d4a4b4 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -73,6 +73,16 @@ config CLK_COMPOSITE_CCF Enable this option if you want to (re-)use the Linux kernel's Common Clock Framework [CCF] composite code in U-Boot's clock driver. +config CLK_INTEL + bool "Enable clock driver for Intel x86" + depends on CLK && X86 + help + This provides very basic support for clocks on Intel SoCs. The driver + is barely used at present but could be expanded as needs arise. + Much clock configuration in U-Boot is either set up by the FSP, or + set up by U-Boot itself but only statically. Thus the driver does not + support changing clock rates, only querying them. + config CLK_STM32F bool "Enable clock driver support for STM32F family" depends on CLK && (STM32F7 || STM32F4) diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 06131edb9f..e01783391d 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_CLK_MVEBU) += mvebu/ obj-$(CONFIG_CLK_BCM6345) += clk_bcm6345.o obj-$(CONFIG_CLK_BOSTON) += clk_boston.o obj-$(CONFIG_CLK_EXYNOS) += exynos/ +obj-$(CONFIG_$(SPL_TPL_)CLK_INTEL) += intel/ obj-$(CONFIG_CLK_HSDK) += clk-hsdk-cgu.o obj-$(CONFIG_CLK_MPC83XX) += mpc83xx_clk.o obj-$(CONFIG_CLK_OWL) += owl/ diff --git a/drivers/clk/intel/Makefile b/drivers/clk/intel/Makefile new file mode 100644 index 0000000000..45e93d7024 --- /dev/null +++ b/drivers/clk/intel/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2010 Google LLC +# + +obj-y += clk_intel.o diff --git a/drivers/clk/intel/clk_intel.c b/drivers/clk/intel/clk_intel.c new file mode 100644 index 0000000000..d2e15491a3 --- /dev/null +++ b/drivers/clk/intel/clk_intel.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <dm.h> +#include <clk-uclass.h> +#include <dt-bindings/clock/intel-clock.h> + +static ulong intel_clk_get_rate(struct clk *clk) +{ + ulong rate; + + switch (clk->id) { + case CLK_I2C: + /* Hard-coded to 133MHz on current platforms */ + return 133333333; + default: + return -ENODEV; + } + + return rate; +} + +static struct clk_ops intel_clk_ops = { + .get_rate = intel_clk_get_rate, +}; + +static const struct udevice_id intel_clk_ids[] = { + { .compatible = "intel,apl-clk" }, + { } +}; + +U_BOOT_DRIVER(clk_intel) = { + .name = "clk_intel", + .id = UCLASS_CLK, + .of_match = intel_clk_ids, + .ops = &intel_clk_ops, +}; diff --git a/drivers/core/syscon-uclass.c b/drivers/core/syscon-uclass.c index 5bb38e329c..b9ae82174e 100644 --- a/drivers/core/syscon-uclass.c +++ b/drivers/core/syscon-uclass.c @@ -128,22 +128,15 @@ struct regmap *syscon_regmap_lookup_by_phandle(struct udevice *dev, int syscon_get_by_driver_data(ulong driver_data, struct udevice **devp) { - struct udevice *dev; - struct uclass *uc; int ret; *devp = NULL; - ret = uclass_get(UCLASS_SYSCON, &uc); + + ret = uclass_first_device_drvdata(UCLASS_SYSCON, driver_data, devp); if (ret) - return ret; - uclass_foreach_dev(dev, uc) { - if (dev->driver_data == driver_data) { - *devp = dev; - return device_probe(dev); - } - } + return log_msg_ret("find", ret); - return -ENODEV; + return 0; } struct regmap *syscon_get_regmap_by_driver_data(ulong driver_data) diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c index c520ef113a..61192d8a9f 100644 --- a/drivers/core/uclass.c +++ b/drivers/core/uclass.c @@ -629,6 +629,23 @@ int uclass_next_device_check(struct udevice **devp) return device_probe(*devp); } +int uclass_first_device_drvdata(enum uclass_id id, ulong driver_data, + struct udevice **devp) +{ + struct udevice *dev; + struct uclass *uc; + + uclass_id_foreach_dev(id, dev, uc) { + if (dev_get_driver_data(dev) == driver_data) { + *devp = dev; + + return device_probe(dev); + } + } + + return -ENODEV; +} + int uclass_bind_device(struct udevice *dev) { struct uclass *uc; diff --git a/drivers/i2c/tegra_i2c.c b/drivers/i2c/tegra_i2c.c index 4be41ddbf0..142463ef44 100644 --- a/drivers/i2c/tegra_i2c.c +++ b/drivers/i2c/tegra_i2c.c @@ -499,18 +499,7 @@ static int tegra_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, int tegra_i2c_get_dvc_bus(struct udevice **busp) { - struct udevice *bus; - - for (uclass_first_device(UCLASS_I2C, &bus); - bus; - uclass_next_device(&bus)) { - if (dev_get_driver_data(bus) == TYPE_DVC) { - *busp = bus; - return 0; - } - } - - return -ENODEV; + return uclass_first_device_drvdata(UCLASS_I2C, TYPE_DVC, busp); } static const struct dm_i2c_ops tegra_i2c_ops = { diff --git a/drivers/misc/irq-uclass.c b/drivers/misc/irq-uclass.c index d5182cf149..61aa10e465 100644 --- a/drivers/misc/irq-uclass.c +++ b/drivers/misc/irq-uclass.c @@ -1,11 +1,16 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright (C) 2015, Bin Meng <bmeng.cn@gmail.com> + * Copyright 2019 Google, LLC + * Written by Simon Glass <sjg@chromium.org> */ +#define LOG_CATEGORY UCLASS_IRQ + #include <common.h> #include <dm.h> +#include <dt-structs.h> #include <irq.h> +#include <dm/device-internal.h> int irq_route_pmc_gpio_gpe(struct udevice *dev, uint pmc_gpe_num) { @@ -47,6 +52,130 @@ int irq_restore_polarities(struct udevice *dev) return ops->restore_polarities(dev); } +int irq_read_and_clear(struct irq *irq) +{ + const struct irq_ops *ops = irq_get_ops(irq->dev); + + if (!ops->read_and_clear) + return -ENOSYS; + + return ops->read_and_clear(irq); +} + +#if CONFIG_IS_ENABLED(OF_PLATDATA) +int irq_get_by_index_platdata(struct udevice *dev, int index, + struct phandle_1_arg *cells, struct irq *irq) +{ + int ret; + + if (index != 0) + return -ENOSYS; + ret = uclass_get_device(UCLASS_IRQ, 0, &irq->dev); + if (ret) + return ret; + irq->id = cells[0].arg[0]; + + return 0; +} +#else +static int irq_of_xlate_default(struct irq *irq, + struct ofnode_phandle_args *args) +{ + log_debug("(irq=%p)\n", irq); + + if (args->args_count > 1) { + log_debug("Invaild args_count: %d\n", args->args_count); + return -EINVAL; + } + + if (args->args_count) + irq->id = args->args[0]; + else + irq->id = 0; + + return 0; +} + +static int irq_get_by_index_tail(int ret, ofnode node, + struct ofnode_phandle_args *args, + const char *list_name, int index, + struct irq *irq) +{ + struct udevice *dev_irq; + const struct irq_ops *ops; + + assert(irq); + irq->dev = NULL; + if (ret) + goto err; + + ret = uclass_get_device_by_ofnode(UCLASS_IRQ, args->node, &dev_irq); + if (ret) { + log_debug("uclass_get_device_by_ofnode failed: err=%d\n", ret); + return ret; + } + + irq->dev = dev_irq; + + ops = irq_get_ops(dev_irq); + + if (ops->of_xlate) + ret = ops->of_xlate(irq, args); + else + ret = irq_of_xlate_default(irq, args); + if (ret) { + log_debug("of_xlate() failed: %d\n", ret); + return ret; + } + + return irq_request(dev_irq, irq); +err: + log_debug("Node '%s', property '%s', failed to request IRQ index %d: %d\n", + ofnode_get_name(node), list_name, index, ret); + return ret; +} + +int irq_get_by_index(struct udevice *dev, int index, struct irq *irq) +{ + struct ofnode_phandle_args args; + int ret; + + ret = dev_read_phandle_with_args(dev, "interrupts-extended", + "#interrupt-cells", 0, index, &args); + + return irq_get_by_index_tail(ret, dev_ofnode(dev), &args, + "interrupts-extended", index > 0, irq); +} +#endif /* OF_PLATDATA */ + +int irq_request(struct udevice *dev, struct irq *irq) +{ + const struct irq_ops *ops; + + log_debug("(dev=%p, irq=%p)\n", dev, irq); + if (!irq) + return 0; + ops = irq_get_ops(dev); + + irq->dev = dev; + + if (!ops->request) + return 0; + + return ops->request(irq); +} + +int irq_first_device_type(enum irq_dev_t type, struct udevice **devp) +{ + int ret; + + ret = uclass_first_device_drvdata(UCLASS_IRQ, type, devp); + if (ret) + return log_msg_ret("find", ret); + + return 0; +} + UCLASS_DRIVER(irq) = { .id = UCLASS_IRQ, .name = "irq", diff --git a/drivers/misc/irq_sandbox.c b/drivers/misc/irq_sandbox.c index 6dda1a4c44..54bc47c8d8 100644 --- a/drivers/misc/irq_sandbox.c +++ b/drivers/misc/irq_sandbox.c @@ -8,6 +8,18 @@ #include <common.h> #include <dm.h> #include <irq.h> +#include <asm/test.h> + +/** + * struct sandbox_irq_priv - private data for this driver + * + * @count: Counts the number calls to the read_and_clear() method + * @pending: true if an interrupt is pending, else false + */ +struct sandbox_irq_priv { + int count; + bool pending; +}; static int sandbox_set_polarity(struct udevice *dev, uint irq, bool active_low) { @@ -35,15 +47,43 @@ static int sandbox_restore_polarities(struct udevice *dev) return 0; } +static int sandbox_irq_read_and_clear(struct irq *irq) +{ + struct sandbox_irq_priv *priv = dev_get_priv(irq->dev); + + if (irq->id != SANDBOX_IRQN_PEND) + return -EINVAL; + priv->count++; + if (priv->pending) { + priv->pending = false; + return 1; + } + + if (!(priv->count % 3)) + priv->pending = true; + + return 0; +} + +static int sandbox_irq_of_xlate(struct irq *irq, + struct ofnode_phandle_args *args) +{ + irq->id = args->args[0]; + + return 0; +} + static const struct irq_ops sandbox_irq_ops = { .route_pmc_gpio_gpe = sandbox_route_pmc_gpio_gpe, .set_polarity = sandbox_set_polarity, .snapshot_polarities = sandbox_snapshot_polarities, .restore_polarities = sandbox_restore_polarities, + .read_and_clear = sandbox_irq_read_and_clear, + .of_xlate = sandbox_irq_of_xlate, }; static const struct udevice_id sandbox_irq_ids[] = { - { .compatible = "sandbox,irq"}, + { .compatible = "sandbox,irq", SANDBOX_IRQT_BASE }, { } }; @@ -52,4 +92,5 @@ U_BOOT_DRIVER(sandbox_irq_drv) = { .id = UCLASS_IRQ, .of_match = sandbox_irq_ids, .ops = &sandbox_irq_ops, + .priv_auto_alloc_size = sizeof(struct sandbox_irq_priv), }; diff --git a/drivers/pinctrl/intel/pinctrl.c b/drivers/pinctrl/intel/pinctrl.c index 5bf5d8b0e2..f4cc55aa3b 100644 --- a/drivers/pinctrl/intel/pinctrl.c +++ b/drivers/pinctrl/intel/pinctrl.c @@ -613,7 +613,7 @@ int intel_pinctrl_ofdata_to_platdata(struct udevice *dev, log_err("Cannot find community for pid %d\n", pplat->pid); return -EDOM; } - ret = uclass_first_device_err(UCLASS_IRQ, &priv->itss); + ret = irq_first_device_type(X86_IRQT_ITSS, &priv->itss); if (ret) return log_msg_ret("Cannot find ITSS", ret); priv->comm = comm; diff --git a/drivers/tpm/Kconfig b/drivers/tpm/Kconfig index 94629dffd2..555a76bb1e 100644 --- a/drivers/tpm/Kconfig +++ b/drivers/tpm/Kconfig @@ -127,6 +127,16 @@ config TPM_V2 if TPM_V2 +config TPM2_CR50_I2C + bool "Enable support for Google cr50 TPM" + depends on DM_I2C + help + Cr50 is an implementation of a TPM on Google's H1 security chip. + This uses the same open-source firmware as the Chromium OS EC. + While Cr50 has other features, its primary role is as the root of + trust for a device, It operates like a TPM and can be used with + verified boot. Cr50 is used on recent Chromebooks (since 2017). + config TPM2_TIS_SANDBOX bool "Enable sandbox TPMv2.x driver" depends on TPM_V2 && SANDBOX diff --git a/drivers/tpm/Makefile b/drivers/tpm/Makefile index 94c337b8ed..4c866b37c5 100644 --- a/drivers/tpm/Makefile +++ b/drivers/tpm/Makefile @@ -10,5 +10,6 @@ obj-$(CONFIG_TPM_TIS_SANDBOX) += tpm_tis_sandbox.o obj-$(CONFIG_TPM_ST33ZP24_I2C) += tpm_tis_st33zp24_i2c.o obj-$(CONFIG_TPM_ST33ZP24_SPI) += tpm_tis_st33zp24_spi.o +obj-$(CONFIG_TPM2_CR50_I2C) += cr50_i2c.o obj-$(CONFIG_TPM2_TIS_SANDBOX) += tpm2_tis_sandbox.o obj-$(CONFIG_TPM2_TIS_SPI) += tpm2_tis_spi.o diff --git a/drivers/tpm/cr50_i2c.c b/drivers/tpm/cr50_i2c.c new file mode 100644 index 0000000000..b904a7d426 --- /dev/null +++ b/drivers/tpm/cr50_i2c.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cr50 / H1 TPM support + * + * Copyright 2018 Google LLC + */ + +#define LOG_CATEGORY UCLASS_TPM + +#include <common.h> +#include <dm.h> +#include <i2c.h> +#include <irq.h> +#include <spl.h> +#include <tpm-v2.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch/iomap.h> +#include <asm/arch/pm.h> + +enum { + TIMEOUT_INIT_MS = 30000, /* Very long timeout for TPM init */ + TIMEOUT_LONG_US = 2 * 1000 * 1000, + TIMEOUT_SHORT_US = 2 * 1000, + TIMEOUT_NO_IRQ_US = 20 * 1000, + TIMEOUT_IRQ_US = 100 * 1000, +}; + +enum { + CR50_DID_VID = 0x00281ae0L +}; + +enum { + CR50_MAX_BUF_SIZE = 63, +}; + +struct cr50_priv { + struct gpio_desc ready_gpio; + struct irq irq; + int locality; + uint vendor; + bool use_irq; +}; + +/* Wait for interrupt to indicate TPM is ready */ +static int cr50_i2c_wait_tpm_ready(struct udevice *dev) +{ + struct cr50_priv *priv = dev_get_priv(dev); + ulong timeout, base; + int i; + + if (!priv->use_irq && !dm_gpio_is_valid(&priv->ready_gpio)) { + /* Fixed delay if interrupt not supported */ + udelay(TIMEOUT_NO_IRQ_US); + return 0; + } + + base = timer_get_us(); + timeout = base + TIMEOUT_IRQ_US; + + i = 0; + while (priv->use_irq ? !irq_read_and_clear(&priv->irq) : + !dm_gpio_get_value(&priv->ready_gpio)) { + i++; + if ((int)(timer_get_us() - timeout) >= 0) { + log_warning("Timeout\n"); + /* Use this instead of the -ETIMEDOUT used by i2c */ + return -ETIME; + } + } + log_debug("i=%d\n", i); + + return 0; +} + +/* Clear pending interrupts */ +static void cr50_i2c_clear_tpm_irq(struct udevice *dev) +{ + struct cr50_priv *priv = dev_get_priv(dev); + + if (priv->use_irq) + irq_read_and_clear(&priv->irq); +} + +/* + * cr50_i2c_read() - read from TPM register + * + * @dev: TPM chip information + * @addr: register address to read from + * @buffer: provided by caller + * @len: number of bytes to read + * + * 1) send register address byte 'addr' to the TPM + * 2) wait for TPM to indicate it is ready + * 3) read 'len' bytes of TPM response into the provided 'buffer' + * + * Return 0 on success. -ve on error + */ +static int cr50_i2c_read(struct udevice *dev, u8 addr, u8 *buffer, + size_t len) +{ + int ret; + + /* Clear interrupt before starting transaction */ + cr50_i2c_clear_tpm_irq(dev); + + /* Send the register address byte to the TPM */ + ret = dm_i2c_write(dev, 0, &addr, 1); + if (ret) { + log_err("Address write failed (err=%d)\n", ret); + return ret; + } + + /* Wait for TPM to be ready with response data */ + ret = cr50_i2c_wait_tpm_ready(dev); + if (ret) + return ret; + + /* Read response data frrom the TPM */ + ret = dm_i2c_read(dev, 0, buffer, len); + if (ret) { + log_err("Read response failed (err=%d)\n", ret); + return ret; + } + + return 0; +} + +/* + * cr50_i2c_write() - write to TPM register + * + * @dev: TPM chip information + * @addr: register address to write to + * @buffer: data to write + * @len: number of bytes to write + * + * 1) prepend the provided address to the provided data + * 2) send the address+data to the TPM + * 3) wait for TPM to indicate it is done writing + * + * Returns -1 on error, 0 on success. + */ +static int cr50_i2c_write(struct udevice *dev, u8 addr, const u8 *buffer, + size_t len) +{ + u8 buf[len + 1]; + int ret; + + if (len > CR50_MAX_BUF_SIZE) { + log_err("Length %zd is too large\n", len); + return -E2BIG; + } + + /* Prepend the 'register address' to the buffer */ + buf[0] = addr; + memcpy(buf + 1, buffer, len); + + /* Clear interrupt before starting transaction */ + cr50_i2c_clear_tpm_irq(dev); + + /* Send write request buffer with address */ + ret = dm_i2c_write(dev, 0, buf, len + 1); + if (ret) { + log_err("Error writing to TPM (err=%d)\n", ret); + return ret; + } + + /* Wait for TPM to be ready */ + return cr50_i2c_wait_tpm_ready(dev); +} + +static inline u8 tpm_access(u8 locality) +{ + return 0x0 | (locality << 4); +} + +static inline u8 tpm_sts(u8 locality) +{ + return 0x1 | (locality << 4); +} + +static inline u8 tpm_data_fifo(u8 locality) +{ + return 0x5 | (locality << 4); +} + +static inline u8 tpm_did_vid(u8 locality) +{ + return 0x6 | (locality << 4); +} + +static int release_locality(struct udevice *dev, int force) +{ + struct cr50_priv *priv = dev_get_priv(dev); + u8 mask = TPM_ACCESS_VALID | TPM_ACCESS_REQUEST_PENDING; + u8 addr = tpm_access(priv->locality); + int ret; + u8 buf; + + ret = cr50_i2c_read(dev, addr, &buf, 1); + if (ret) + return ret; + + if (force || (buf & mask) == mask) { + buf = TPM_ACCESS_ACTIVE_LOCALITY; + cr50_i2c_write(dev, addr, &buf, 1); + } + + priv->locality = 0; + + return 0; +} + +/* cr50 requires all 4 bytes of status register to be read */ +static int cr50_i2c_status(struct udevice *dev) +{ + struct cr50_priv *priv = dev_get_priv(dev); + u8 buf[4]; + int ret; + + ret = cr50_i2c_read(dev, tpm_sts(priv->locality), buf, sizeof(buf)); + if (ret) { + log_warning("%s: Failed to read status\n", __func__); + return ret; + } + + return buf[0]; +} + +/* cr50 requires all 4 bytes of status register to be written */ +static int cr50_i2c_ready(struct udevice *dev) +{ + struct cr50_priv *priv = dev_get_priv(dev); + u8 buf[4] = { TPM_STS_COMMAND_READY }; + int ret; + + ret = cr50_i2c_write(dev, tpm_sts(priv->locality), buf, sizeof(buf)); + if (ret) + return ret; + + udelay(TIMEOUT_SHORT_US); + + return 0; +} + +static int cr50_i2c_wait_burststs(struct udevice *dev, u8 mask, + size_t *burst, int *status) +{ + struct cr50_priv *priv = dev_get_priv(dev); + ulong timeout; + u32 buf; + + /* + * cr50 uses bytes 3:2 of status register for burst count and all 4 + * bytes must be read + */ + timeout = timer_get_us() + TIMEOUT_LONG_US; + while (timer_get_us() < timeout) { + if (cr50_i2c_read(dev, tpm_sts(priv->locality), + (u8 *)&buf, sizeof(buf)) < 0) { + udelay(TIMEOUT_SHORT_US); + continue; + } + + *status = buf & 0xff; + *burst = le16_to_cpu((buf >> 8) & 0xffff); + + if ((*status & mask) == mask && + *burst > 0 && *burst <= CR50_MAX_BUF_SIZE) + return 0; + + udelay(TIMEOUT_SHORT_US); + } + + log_warning("Timeout reading burst and status\n"); + + return -ETIMEDOUT; +} + +static int cr50_i2c_recv(struct udevice *dev, u8 *buf, size_t buf_len) +{ + struct cr50_priv *priv = dev_get_priv(dev); + size_t burstcnt, expected, current, len; + u8 addr = tpm_data_fifo(priv->locality); + u8 mask = TPM_STS_VALID | TPM_STS_DATA_AVAIL; + u32 expected_buf; + int status; + int ret; + + log_debug("%s: len=%x\n", __func__, buf_len); + if (buf_len < TPM_HEADER_SIZE) + return -E2BIG; + + ret = cr50_i2c_wait_burststs(dev, mask, &burstcnt, &status); + if (ret < 0) { + log_warning("First chunk not available\n"); + goto out_err; + } + + /* Read first chunk of burstcnt bytes */ + if (cr50_i2c_read(dev, addr, buf, burstcnt) < 0) { + log_warning("Read failed\n"); + goto out_err; + } + + /* Determine expected data in the return buffer */ + memcpy(&expected_buf, buf + TPM_CMD_COUNT_OFFSET, sizeof(expected_buf)); + expected = be32_to_cpu(expected_buf); + if (expected > buf_len) { + log_warning("Too much data: %zu > %zu\n", expected, buf_len); + goto out_err; + } + + /* Now read the rest of the data */ + current = burstcnt; + while (current < expected) { + /* Read updated burst count and check status */ + if (cr50_i2c_wait_burststs(dev, mask, &burstcnt, &status) < 0) { + log_warning("- burst failure1\n"); + goto out_err; + } + + len = min(burstcnt, expected - current); + if (cr50_i2c_read(dev, addr, buf + current, len) != 0) { + log_warning("Read failed\n"); + goto out_err; + } + + current += len; + } + + if (cr50_i2c_wait_burststs(dev, TPM_STS_VALID, &burstcnt, + &status) < 0) { + log_warning("- burst failure2\n"); + goto out_err; + } + if (status & TPM_STS_DATA_AVAIL) { + log_warning("Data still available\n"); + goto out_err; + } + + return current; + +out_err: + /* Abort current transaction if still pending */ + ret = cr50_i2c_status(dev); + if (ret < 0) + return ret; + if (ret & TPM_STS_COMMAND_READY) { + ret = cr50_i2c_ready(dev); + if (ret) + return ret; + } + + return -EIO; +} + +static int cr50_i2c_send(struct udevice *dev, const u8 *buf, size_t len) +{ + struct cr50_priv *priv = dev_get_priv(dev); + + int status; + size_t burstcnt, limit, sent = 0; + u8 tpm_go[4] = { TPM_STS_GO }; + ulong timeout; + int ret; + + log_debug("%s: len=%x\n", __func__, len); + timeout = timer_get_us() + TIMEOUT_LONG_US; + do { + ret = cr50_i2c_status(dev); + if (ret < 0) + goto out_err; + if (ret & TPM_STS_COMMAND_READY) + break; + + if (timer_get_us() > timeout) + goto out_err; + + ret = cr50_i2c_ready(dev); + if (ret) + goto out_err; + } while (1); + + while (len > 0) { + u8 mask = TPM_STS_VALID; + + /* Wait for data if this is not the first chunk */ + if (sent > 0) + mask |= TPM_STS_DATA_EXPECT; + + if (cr50_i2c_wait_burststs(dev, mask, &burstcnt, &status) < 0) + goto out_err; + + /* + * Use burstcnt - 1 to account for the address byte + * that is inserted by cr50_i2c_write() + */ + limit = min(burstcnt - 1, len); + if (cr50_i2c_write(dev, tpm_data_fifo(priv->locality), + &buf[sent], limit) != 0) { + log_warning("Write failed\n"); + goto out_err; + } + + sent += limit; + len -= limit; + } + + /* Ensure TPM is not expecting more data */ + if (cr50_i2c_wait_burststs(dev, TPM_STS_VALID, &burstcnt, &status) < 0) + goto out_err; + if (status & TPM_STS_DATA_EXPECT) { + log_warning("Data still expected\n"); + goto out_err; + } + + /* Start the TPM command */ + ret = cr50_i2c_write(dev, tpm_sts(priv->locality), tpm_go, + sizeof(tpm_go)); + if (ret) { + log_warning("Start command failed\n"); + goto out_err; + } + + return sent; + +out_err: + /* Abort current transaction if still pending */ + ret = cr50_i2c_status(dev); + + if (ret < 0 || (ret & TPM_STS_COMMAND_READY)) { + ret = cr50_i2c_ready(dev); + if (ret) + return ret; + } + + return -EIO; +} + +/** + * process_reset() - Wait for the Cr50 to reset + * + * Cr50 processes reset requests asynchronously and conceivably could be busy + * executing a long command and not reacting to the reset pulse for a while. + * + * This function will make sure that the AP does not proceed with boot until + * TPM finished reset processing. + * + * @dev: Cr50 device + * @return 0 if OK, -EPERM if locality could not be taken + */ +static int process_reset(struct udevice *dev) +{ + const int loc = 0; + u8 access; + ulong start; + + /* + * Locality is released by TPM reset. + * + * If locality is taken at this point, this could be due to the fact + * that the TPM is performing a long operation and has not processed + * reset request yet. We'll wait up to CR50_TIMEOUT_INIT_MS and see if + * it releases locality when reset is processed. + */ + start = get_timer(0); + do { + const u8 mask = TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY; + int ret; + + ret = cr50_i2c_read(dev, tpm_access(loc), + &access, sizeof(access)); + if (ret || ((access & mask) == mask)) { + /* + * Don't bombard the chip with traffic; let it keep + * processing the command. + */ + mdelay(2); + continue; + } + + log_warning("TPM ready after %ld ms\n", get_timer(start)); + + return 0; + } while (get_timer(start) < TIMEOUT_INIT_MS); + + log_warning("TPM failed to reset after %ld ms, status: %#x\n", + get_timer(start), access); + + return -EPERM; +} + +/* + * Locality could be already claimed (if this is a later U-Boot phase and the + * read-only U-Boot did not release it), or not yet claimed, if this is TPL or + * the older read-only U-Boot did release it. + */ +static int claim_locality(struct udevice *dev, int loc) +{ + const u8 mask = TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY; + u8 access; + int ret; + + ret = cr50_i2c_read(dev, tpm_access(loc), &access, sizeof(access)); + if (ret) + return log_msg_ret("read1", ret); + + if ((access & mask) == mask) { + log_warning("Locality already claimed\n"); + return 0; + } + + access = TPM_ACCESS_REQUEST_USE; + ret = cr50_i2c_write(dev, tpm_access(loc), &access, sizeof(access)); + if (ret) + return log_msg_ret("write", ret); + + ret = cr50_i2c_read(dev, tpm_access(loc), &access, sizeof(access)); + if (ret) + return log_msg_ret("read2", ret); + + if ((access & mask) != mask) { + log_err("Failed to claim locality\n"); + return -EPERM; + } + log_info("Claimed locality %d\n", loc); + + return 0; +} + +static int cr50_i2c_get_desc(struct udevice *dev, char *buf, int size) +{ + struct dm_i2c_chip *chip = dev_get_parent_platdata(dev); + struct cr50_priv *priv = dev_get_priv(dev); + + return snprintf(buf, size, "cr50 TPM 2.0 (i2c %02x id %x) irq=%d", + chip->chip_addr, priv->vendor >> 16, priv->use_irq); +} + +static int cr50_i2c_open(struct udevice *dev) +{ + char buf[80]; + int ret; + + ret = process_reset(dev); + if (ret) + return log_msg_ret("reset", ret); + + ret = claim_locality(dev, 0); + if (ret) + return log_msg_ret("claim", ret); + + cr50_i2c_get_desc(dev, buf, sizeof(buf)); + log_debug("%s\n", buf); + + return 0; +} + +static int cr50_i2c_cleanup(struct udevice *dev) +{ + release_locality(dev, 1); + + return 0; +} + +enum { + TPM_TIMEOUT_MS = 5, + SHORT_TIMEOUT_MS = 750, + LONG_TIMEOUT_MS = 2000, +}; + +static int cr50_i2c_ofdata_to_platdata(struct udevice *dev) +{ + struct tpm_chip_priv *upriv = dev_get_uclass_priv(dev); + struct cr50_priv *priv = dev_get_priv(dev); + struct irq irq; + int ret; + + upriv->version = TPM_V2; + upriv->duration_ms[TPM_SHORT] = SHORT_TIMEOUT_MS; + upriv->duration_ms[TPM_MEDIUM] = LONG_TIMEOUT_MS; + upriv->duration_ms[TPM_LONG] = LONG_TIMEOUT_MS; + upriv->retry_time_ms = TPM_TIMEOUT_MS; + + upriv->pcr_count = 32; + upriv->pcr_select_min = 2; + + /* Optional GPIO to track when cr50 is ready */ + ret = irq_get_by_index(dev, 0, &irq); + if (!ret) { + priv->irq = irq; + priv->use_irq = true; + } else { + ret = gpio_request_by_name(dev, "ready-gpio", 0, + &priv->ready_gpio, GPIOD_IS_IN); + if (ret) { + log_warning("Cr50 does not have an ready GPIO/interrupt (err=%d)\n", + ret); + } + } + + return 0; +} + +static int cr50_i2c_probe(struct udevice *dev) +{ + struct cr50_priv *priv = dev_get_priv(dev); + u32 vendor = 0; + ulong start; + + /* + * 150ms should be enough to synchronise with the TPM even under the + * worst nested-reset-request conditions. In the vast majority of cases + * there will be no wait at all. + */ + start = get_timer(0); + while (get_timer(start) < 150) { + int ret; + + /* Exit once DID and VID verified */ + ret = cr50_i2c_read(dev, tpm_did_vid(0), (u8 *)&vendor, 4); + if (!ret && vendor == CR50_DID_VID) + break; + + /* TPM might be resetting; let's retry in a bit */ + mdelay(10); + } + if (vendor != CR50_DID_VID) { + log_debug("DID_VID %08x not recognised\n", vendor); + return log_msg_ret("vendor-id", -EXDEV); + } + priv->vendor = vendor; + + return 0; +} + +static const struct tpm_ops cr50_i2c_ops = { + .open = cr50_i2c_open, + .get_desc = cr50_i2c_get_desc, + .send = cr50_i2c_send, + .recv = cr50_i2c_recv, + .cleanup = cr50_i2c_cleanup, +}; + +static const struct udevice_id cr50_i2c_ids[] = { + { .compatible = "google,cr50" }, + { } +}; + +U_BOOT_DRIVER(cr50_i2c) = { + .name = "cr50_i2c", + .id = UCLASS_TPM, + .of_match = cr50_i2c_ids, + .ops = &cr50_i2c_ops, + .ofdata_to_platdata = cr50_i2c_ofdata_to_platdata, + .probe = cr50_i2c_probe, + .priv_auto_alloc_size = sizeof(struct cr50_priv), +}; diff --git a/include/dm/test.h b/include/dm/test.h index 07385cd531..f0f36624ce 100644 --- a/include/dm/test.h +++ b/include/dm/test.h @@ -56,6 +56,8 @@ enum { enum { DM_TEST_TYPE_FIRST = 0, DM_TEST_TYPE_SECOND, + + DM_TEST_TYPE_COUNT, }; /* The number added to the ping total on each probe */ diff --git a/include/dm/uclass.h b/include/dm/uclass.h index 484d166013..70fca79b44 100644 --- a/include/dm/uclass.h +++ b/include/dm/uclass.h @@ -351,6 +351,20 @@ int uclass_first_device_check(enum uclass_id id, struct udevice **devp); int uclass_next_device_check(struct udevice **devp); /** + * uclass_first_device_drvdata() - Find the first device with given driver data + * + * This searches through the devices for a particular uclass looking for one + * that has the given driver data. + * + * @id: Uclass ID to check + * @driver_data: Driver data to search for + * @devp: Returns pointer to the first matching device in that uclass, if found + * @return 0 if found, -ENODEV if not found, other -ve on error + */ +int uclass_first_device_drvdata(enum uclass_id id, ulong driver_data, + struct udevice **devp); + +/** * uclass_resolve_seq() - Resolve a device's sequence number * * On entry dev->seq is -1, and dev->req_seq may be -1 (to allocate a @@ -366,6 +380,23 @@ int uclass_next_device_check(struct udevice **devp); int uclass_resolve_seq(struct udevice *dev); /** + * uclass_id_foreach_dev() - Helper function to iteration through devices + * + * This creates a for() loop which works through the available devices in + * a uclass ID in order from start to end. + * + * If for some reason the uclass cannot be found, this does nothing. + * + * @id: enum uclass_id ID to use + * @pos: struct udevice * to hold the current device. Set to NULL when there + * are no more devices. + * @uc: temporary uclass variable (struct udevice *) + */ +#define uclass_id_foreach_dev(id, pos, uc) \ + if (!uclass_get(id, &uc)) \ + list_for_each_entry(pos, &uc->dev_head, uclass_node) + +/** * uclass_foreach_dev() - Helper function to iteration through devices * * This creates a for() loop which works through the available devices in diff --git a/include/dt-bindings/clock/intel-clock.h b/include/dt-bindings/clock/intel-clock.h new file mode 100644 index 0000000000..e1edd3c71d --- /dev/null +++ b/include/dt-bindings/clock/intel-clock.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * This header provides constants for Intel clocks. + * + * The constants defined in this header are used in the device tree + * + * Copyright 2019 Google LLC + */ + +#ifndef _DT_BINDINGS_CLK_INTEL_H +#define _DT_BINDINGS_CLK_INTEL_H + +#define CLK_I2C 1 + +#endif diff --git a/include/irq.h b/include/irq.h index 01ded64f16..b71afe9bee 100644 --- a/include/irq.h +++ b/include/irq.h @@ -8,8 +8,32 @@ #ifndef __irq_H #define __irq_H +/* + * Interrupt controller types available. You can find a particular one with + * irq_first_device_type() + */ +enum irq_dev_t { + X86_IRQT_BASE, /* Base controller */ + X86_IRQT_ITSS, /* ITSS controller, e.g. on APL */ + X86_IRQT_ACPI_GPE, /* ACPI General-Purpose Events controller */ + SANDBOX_IRQT_BASE, /* Sandbox testing */ +}; + +/** + * struct irq - A single irq line handled by an interrupt controller + * + * @dev: IRQ device that handles this irq + * @id: ID to identify this irq with the device + */ +struct irq { + struct udevice *dev; + ulong id; +}; + /** * struct irq_ops - Operations for the IRQ + * + * Each IRQ device can handle mulitple IRQ lines */ struct irq_ops { /** @@ -46,6 +70,55 @@ struct irq_ops { * @return 0 */ int (*restore_polarities)(struct udevice *dev); + + /** + * read_and_clear() - get the value of an interrupt and clear it + * + * Clears the interrupt if pending + * + * @irq: IRQ line + * @return 0 if interrupt is not pending, 1 if it was (and so has been + * cleared), -ve on error + */ + int (*read_and_clear)(struct irq *irq); + /** + * of_xlate - Translate a client's device-tree (OF) irq specifier. + * + * The irq core calls this function as the first step in implementing + * a client's irq_get_by_*() call. + * + * If this function pointer is set to NULL, the irq core will use a + * default implementation, which assumes #interrupt-cells = <1>, and + * that the DT cell contains a simple integer irq ID. + * + * @irq: The irq struct to hold the translation result. + * @args: The irq specifier values from device tree. + * @return 0 if OK, or a negative error code. + */ + int (*of_xlate)(struct irq *irq, struct ofnode_phandle_args *args); + /** + * request - Request a translated irq. + * + * The irq core calls this function as the second step in + * implementing a client's irq_get_by_*() call, following a successful + * xxx_xlate() call, or as the only step in implementing a client's + * irq_request() call. + * + * @irq: The irq struct to request; this has been fille in by + * a previoux xxx_xlate() function call, or by the caller + * of irq_request(). + * @return 0 if OK, or a negative error code. + */ + int (*request)(struct irq *irq); + /** + * free - Free a previously requested irq. + * + * This is the implementation of the client irq_free() API. + * + * @irq: The irq to free. + * @return 0 if OK, or a negative error code. + */ + int (*free)(struct irq *irq); }; #define irq_get_ops(dev) ((struct irq_ops *)(dev)->driver->ops) @@ -85,4 +158,69 @@ int irq_snapshot_polarities(struct udevice *dev); */ int irq_restore_polarities(struct udevice *dev); +/** + * read_and_clear() - get the value of an interrupt and clear it + * + * Clears the interrupt if pending + * + * @dev: IRQ device + * @return 0 if interrupt is not pending, 1 if it was (and so has been + * cleared), -ve on error + */ +int irq_read_and_clear(struct irq *irq); + +/** + * irq_get_by_index - Get/request an irq by integer index. + * + * This looks up and requests an irq. The index is relative to the client + * device; each device is assumed to have n irqs associated with it somehow, + * and this function finds and requests one of them. The mapping of client + * device irq indices to provider irqs may be via device-tree + * properties, board-provided mapping tables, or some other mechanism. + * + * @dev: The client device. + * @index: The index of the irq to request, within the client's list of + * irqs. + * @irq: A pointer to a irq struct to initialise. + * @return 0 if OK, or a negative error code. + */ +int irq_get_by_index(struct udevice *dev, int index, struct irq *irq); + +/** + * irq_request - Request a irq by provider-specific ID. + * + * This requests a irq using a provider-specific ID. Generally, this function + * should not be used, since irq_get_by_index/name() provide an interface that + * better separates clients from intimate knowledge of irq providers. + * However, this function may be useful in core SoC-specific code. + * + * @dev: The irq provider device. + * @irq: A pointer to a irq struct to initialise. The caller must + * have already initialised any field in this struct which the + * irq provider uses to identify the irq. + * @return 0 if OK, or a negative error code. + */ +int irq_request(struct udevice *dev, struct irq *irq); + +/** + * irq_free - Free a previously requested irq. + * + * @irq: A irq struct that was previously successfully requested by + * irq_request/get_by_*(). + * @return 0 if OK, or a negative error code. + */ +int irq_free(struct irq *irq); + +/** + * irq_first_device_type() - Get a particular interrupt controller + * + * On success this returns an activated interrupt device. + * + * @type: Type to find + * @devp: Returns the device, if found + * @return 0 if OK, -ENODEV if not found, other -ve error if uclass failed to + * probe + */ +int irq_first_device_type(enum irq_dev_t type, struct udevice **devp); + #endif diff --git a/include/tpm-v2.h b/include/tpm-v2.h index ae00803f6d..d53d2e4023 100644 --- a/include/tpm-v2.h +++ b/include/tpm-v2.h @@ -161,6 +161,37 @@ enum tpm_index_attrs { TPMA_NV_AUTHWRITE | TPMA_NV_POLICYWRITE, }; +enum { + TPM_ACCESS_VALID = 1 << 7, + TPM_ACCESS_ACTIVE_LOCALITY = 1 << 5, + TPM_ACCESS_REQUEST_PENDING = 1 << 2, + TPM_ACCESS_REQUEST_USE = 1 << 1, + TPM_ACCESS_ESTABLISHMENT = 1 << 0, +}; + +enum { + TPM_STS_FAMILY_SHIFT = 26, + TPM_STS_FAMILY_MASK = 0x3 << TPM_STS_FAMILY_SHIFT, + TPM_STS_FAMILY_TPM2 = 1 << TPM_STS_FAMILY_SHIFT, + TPM_STS_RESE_TESTABLISMENT_BIT = 1 << 25, + TPM_STS_COMMAND_CANCEL = 1 << 24, + TPM_STS_BURST_COUNT_SHIFT = 8, + TPM_STS_BURST_COUNT_MASK = 0xffff << TPM_STS_BURST_COUNT_SHIFT, + TPM_STS_VALID = 1 << 7, + TPM_STS_COMMAND_READY = 1 << 6, + TPM_STS_GO = 1 << 5, + TPM_STS_DATA_AVAIL = 1 << 4, + TPM_STS_DATA_EXPECT = 1 << 3, + TPM_STS_SELF_TEST_DONE = 1 << 2, + TPM_STS_RESPONSE_RETRY = 1 << 1, +}; + +enum { + TPM_CMD_COUNT_OFFSET = 2, + TPM_CMD_ORDINAL_OFFSET = 6, + TPM_MAX_BUF_SIZE = 1260, +}; + /** * Issue a TPM2_Startup command. * diff --git a/test/dm/irq.c b/test/dm/irq.c index 726189c59f..192d80d7e1 100644 --- a/test/dm/irq.c +++ b/test/dm/irq.c @@ -8,6 +8,7 @@ #include <common.h> #include <dm.h> #include <irq.h> +#include <asm/test.h> #include <dm/test.h> #include <test/ut.h> @@ -30,3 +31,47 @@ static int dm_test_irq_base(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_irq_base, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + +/* Test of irq_first_device_type() */ +static int dm_test_irq_type(struct unit_test_state *uts) +{ + struct udevice *dev; + + ut_assertok(irq_first_device_type(SANDBOX_IRQT_BASE, &dev)); + ut_asserteq(-ENODEV, irq_first_device_type(X86_IRQT_BASE, &dev)); + + return 0; +} +DM_TEST(dm_test_irq_type, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + +/* Test of irq_read_and_clear() */ +static int dm_test_read_and_clear(struct unit_test_state *uts) +{ + struct irq irq; + + ut_assertok(irq_first_device_type(SANDBOX_IRQT_BASE, &irq.dev)); + irq.id = SANDBOX_IRQN_PEND; + ut_asserteq(0, irq_read_and_clear(&irq)); + ut_asserteq(0, irq_read_and_clear(&irq)); + ut_asserteq(0, irq_read_and_clear(&irq)); + ut_asserteq(1, irq_read_and_clear(&irq)); + ut_asserteq(0, irq_read_and_clear(&irq)); + + return 0; +} +DM_TEST(dm_test_read_and_clear, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + +/* Test of irq_request() */ +static int dm_test_request(struct unit_test_state *uts) +{ + struct udevice *dev; + struct irq irq; + + ut_assertok(uclass_first_device_err(UCLASS_TEST_FDT, &dev)); + ut_asserteq_str("a-test", dev->name); + ut_assertok(irq_get_by_index(dev, 0, &irq)); + ut_asserteq(3, irq.id); + + return 0; +} +DM_TEST(dm_test_request, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); diff --git a/test/dm/test-fdt.c b/test/dm/test-fdt.c index d59c449ce0..698ca0e7cf 100644 --- a/test/dm/test-fdt.c +++ b/test/dm/test-fdt.c @@ -448,6 +448,27 @@ static int dm_test_first_next_device(struct unit_test_state *uts) } DM_TEST(dm_test_first_next_device, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); +/* Test iteration through devices in a uclass */ +static int dm_test_uclass_foreach(struct unit_test_state *uts) +{ + struct udevice *dev; + struct uclass *uc; + int count; + + count = 0; + uclass_id_foreach_dev(UCLASS_TEST_FDT, dev, uc) + count++; + ut_asserteq(8, count); + + count = 0; + uclass_foreach_dev(dev, uc) + count++; + ut_asserteq(8, count); + + return 0; +} +DM_TEST(dm_test_uclass_foreach, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + /** * check_devices() - Check return values and pointers * @@ -872,3 +893,24 @@ static int dm_test_read_int(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_read_int, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + +/* Test iteration through devices by drvdata */ +static int dm_test_uclass_drvdata(struct unit_test_state *uts) +{ + struct udevice *dev; + + ut_assertok(uclass_first_device_drvdata(UCLASS_TEST_FDT, + DM_TEST_TYPE_FIRST, &dev)); + ut_asserteq_str("a-test", dev->name); + + ut_assertok(uclass_first_device_drvdata(UCLASS_TEST_FDT, + DM_TEST_TYPE_SECOND, &dev)); + ut_asserteq_str("d-test", dev->name); + + ut_asserteq(-ENODEV, uclass_first_device_drvdata(UCLASS_TEST_FDT, + DM_TEST_TYPE_COUNT, + &dev)); + + return 0; +} +DM_TEST(dm_test_uclass_drvdata, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); |