From patchwork Wed Feb 5 02:03:12 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235969 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:12 -0700 Subject: [PATCH v3 01/17] dm: core: Allow iterating devices without uclass_get() In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-2-sjg@chromium.org> At present we have uclass_foreach_dev() which requires that uclass_get() be called beforehand to find the uclass. This is good if we suspect that that function might fail, but often we know that the uclass is available. Add a new helper which does this uclass_get() automatically, so that only the uclass ID is needed. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: - Add new patch to allow iterating devices without uclass_get() include/dm/uclass.h | 17 +++++++++++++++++ test/dm/test-fdt.c | 21 +++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/include/dm/uclass.h b/include/dm/uclass.h index 484d166013..74b8e2ecb5 100644 --- a/include/dm/uclass.h +++ b/include/dm/uclass.h @@ -365,6 +365,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 * diff --git a/test/dm/test-fdt.c b/test/dm/test-fdt.c index d59c449ce0..8ea536c309 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 * From patchwork Wed Feb 5 02:03:13 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235970 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:13 -0700 Subject: [PATCH v3 02/17] dm: core: Add a function to find a device by drvdata In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-3-sjg@chromium.org> It is sometimes useful to find a device in a uclass using only its driver data. The driver data often indicates the 'subtype' of the device, e,g, via its compatible string. Add a function to handle this. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: - Add new patch to find a device by drvdata drivers/core/uclass.c | 17 +++++++++++++++++ include/dm/test.h | 2 ++ include/dm/uclass.h | 14 ++++++++++++++ test/dm/test-fdt.c | 21 +++++++++++++++++++++ 4 files changed, 54 insertions(+) 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/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 74b8e2ecb5..70fca79b44 100644 --- a/include/dm/uclass.h +++ b/include/dm/uclass.h @@ -350,6 +350,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 * diff --git a/test/dm/test-fdt.c b/test/dm/test-fdt.c index 8ea536c309..698ca0e7cf 100644 --- a/test/dm/test-fdt.c +++ b/test/dm/test-fdt.c @@ -893,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); From patchwork Wed Feb 5 02:03:14 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235971 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:14 -0700 Subject: [PATCH v3 03/17] dm: core: Change syscon to use helper function In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-4-sjg@chromium.org> Now that we have uclass_first_device_drvdata(), use it from syscon to reduce code duplication. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: - Add new patch to change syscon to use helper function drivers/core/syscon-uclass.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) 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) From patchwork Wed Feb 5 02:03:15 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235972 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:15 -0700 Subject: [PATCH v3 04/17] tegra: i2c: Change driver to use helper function In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-5-sjg@chromium.org> Now that we have uclass_first_device_drvdata(), use it from the I2C driver to reduce code duplication. Signed-off-by: Simon Glass Reviewed-by: Heiko Schocher Reviewed-by: Bin Meng Acked-by: Stephen Warren --- Changes in v3: None Changes in v2: - Add new patch to change tegra driver to use helper function drivers/i2c/tegra_i2c.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) 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 = { From patchwork Wed Feb 5 02:03:16 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235973 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:16 -0700 Subject: [PATCH v3 05/17] x86: Add a clock driver for Intel devices In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-6-sjg@chromium.org> So far we have avoided adding a clock driver for Intel devices. But the Designware I2C driver needs a different clock (133MHz) on Intel devices than on others (166MHz). Add a simple driver that provides this information. This driver can be expanded later as needed. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: None drivers/clk/Kconfig | 10 ++++++ drivers/clk/Makefile | 1 + drivers/clk/intel/Makefile | 6 ++++ drivers/clk/intel/clk_intel.c | 41 +++++++++++++++++++++++++ include/dt-bindings/clock/intel-clock.h | 15 +++++++++ 5 files changed, 73 insertions(+) create mode 100644 drivers/clk/intel/Makefile create mode 100644 drivers/clk/intel/clk_intel.c create mode 100644 include/dt-bindings/clock/intel-clock.h 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 + */ + +#include +#include +#include +#include + +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/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 From patchwork Wed Feb 5 02:03:17 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235974 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:17 -0700 Subject: [PATCH v3 06/17] x86: apl: Use the clock driver In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-7-sjg@chromium.org> Enable the Intel clock driver and modify coral's device tree to use it. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: - Move intel-clock.h inclusion to the correct patch arch/x86/cpu/apollolake/Kconfig | 3 +++ arch/x86/dts/chromebook_coral.dts | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/arch/x86/cpu/apollolake/Kconfig b/arch/x86/cpu/apollolake/Kconfig index a760e0ac68..6d6e98a589 100644 --- a/arch/x86/cpu/apollolake/Kconfig +++ b/arch/x86/cpu/apollolake/Kconfig @@ -40,6 +40,9 @@ config INTEL_APOLLOLAKE imply INTEL_GPIO imply SMP imply HAVE_ITSS + imply CLK + imply CMD_CLK + imply CLK_INTEL if INTEL_APOLLOLAKE diff --git a/arch/x86/dts/chromebook_coral.dts b/arch/x86/dts/chromebook_coral.dts index a1820fa187..a4a9e949e6 100644 --- a/arch/x86/dts/chromebook_coral.dts +++ b/arch/x86/dts/chromebook_coral.dts @@ -39,6 +39,11 @@ stdout-path = &serial; }; + clk: clock { + compatible = "intel,apl-clk"; + #clock-cells = <1>; + }; + cpus { u-boot,dm-pre-reloc; #address-cells = <1>; From patchwork Wed Feb 5 02:03:18 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235975 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:18 -0700 Subject: [PATCH v3 07/17] x86: apl: Drop the I2C config in FSP-S In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-8-sjg@chromium.org> This config is not actually used here and in U-Boot it seems better to set this using the device tree for each individual controller. The monolithic config of the FSP-S is only necessary if the FSP is actually configuring something, but here it is not. The FSP-S does enable/disable the various I2C ports. It might be nice to handle this using the okay/disabled property of each port, but that can be considered later. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: None arch/x86/cpu/apollolake/fsp_s.c | 58 --------------------------------- 1 file changed, 58 deletions(-) diff --git a/arch/x86/cpu/apollolake/fsp_s.c b/arch/x86/cpu/apollolake/fsp_s.c index 9804227f80..f8fa4dec8f 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 at 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 { From patchwork Wed Feb 5 02:03:19 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235976 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:19 -0700 Subject: [PATCH v3 08/17] x86: coral: Update i2c and rtc status In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-9-sjg@chromium.org> These are actually working correctly, so update the status. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: None doc/board/google/chromebook_coral.rst | 2 -- 1 file changed, 2 deletions(-) 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 From patchwork Wed Feb 5 02:03:20 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235977 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:20 -0700 Subject: [PATCH v3 09/17] dm: irq: Add support for interrupt controller types In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-10-sjg@chromium.org> There can be different types of interrupt controllers in a system and some drivers may need to distinguish between these. In general this can be handled using the device tree by adding the interrupt information to device nodes. However on x86 devices we have interrupt controllers which are not tied to any particular device and not really used in U-Boot. These still need to be inited, so a convenient method is to give each controller a type and allow a particular controller type to be probed. Add support for this in sandbox along with a test. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: - Move 'success' comment into this patch - Update to use the new DM helper function arch/sandbox/include/asm/test.h | 4 ++++ drivers/misc/irq-uclass.c | 15 ++++++++++++++- drivers/misc/irq_sandbox.c | 2 +- include/irq.h | 23 +++++++++++++++++++++++ test/dm/irq.c | 14 ++++++++++++++ 5 files changed, 56 insertions(+), 2 deletions(-) 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/drivers/misc/irq-uclass.c b/drivers/misc/irq-uclass.c index d5182cf149..c52c813ff3 100644 --- a/drivers/misc/irq-uclass.c +++ b/drivers/misc/irq-uclass.c @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright (C) 2015, Bin Meng + * Copyright 2019 Google, LLC + * Written by Simon Glass */ #include #include #include +#include int irq_route_pmc_gpio_gpe(struct udevice *dev, uint pmc_gpe_num) { @@ -47,6 +49,17 @@ int irq_restore_polarities(struct udevice *dev) return ops->restore_polarities(dev); } +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..011022ac62 100644 --- a/drivers/misc/irq_sandbox.c +++ b/drivers/misc/irq_sandbox.c @@ -43,7 +43,7 @@ static const struct irq_ops sandbox_irq_ops = { }; static const struct udevice_id sandbox_irq_ids[] = { - { .compatible = "sandbox,irq"}, + { .compatible = "sandbox,irq", SANDBOX_IRQT_BASE }, { } }; diff --git a/include/irq.h b/include/irq.h index 01ded64f16..8b4e2ecfc0 100644 --- a/include/irq.h +++ b/include/irq.h @@ -8,6 +8,17 @@ #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_ops - Operations for the IRQ */ @@ -85,4 +96,16 @@ int irq_snapshot_polarities(struct udevice *dev); */ int irq_restore_polarities(struct udevice *dev); +/** + * 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/test/dm/irq.c b/test/dm/irq.c index 726189c59f..0f23f108a7 100644 --- a/test/dm/irq.c +++ b/test/dm/irq.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -30,3 +31,16 @@ 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); + From patchwork Wed Feb 5 02:03:21 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235978 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:21 -0700 Subject: [PATCH v3 10/17] x86: Give each driver an IRQ type In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-11-sjg@chromium.org> Add an IRQ type to each driver and use irq_first_device_type() to find and probe the correct one. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: - Move 'success' comment into previous patch arch/x86/cpu/apollolake/fsp_s.c | 4 ++-- arch/x86/cpu/i386/interrupt.c | 3 ++- arch/x86/cpu/intel_common/itss.c | 2 +- arch/x86/cpu/irq.c | 3 ++- drivers/pinctrl/intel/pinctrl.c | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/arch/x86/cpu/apollolake/fsp_s.c b/arch/x86/cpu/apollolake/fsp_s.c index f8fa4dec8f..b2d9130841 100644 --- a/arch/x86/cpu/apollolake/fsp_s.c +++ b/arch/x86/cpu/apollolake/fsp_s.c @@ -535,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); /* @@ -576,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 #include #include +#include #include #include #include @@ -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/itss.c b/arch/x86/cpu/intel_common/itss.c index 9df51adecc..33962cb9a0 100644 --- a/arch/x86/cpu/intel_common/itss.c +++ b/arch/x86/cpu/intel_common/itss.c @@ -199,7 +199,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 }, { } }; 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 #include #include +#include #include #include #include @@ -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/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; From patchwork Wed Feb 5 02:03:22 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235979 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:22 -0700 Subject: [PATCH v3 11/17] x86: itss: Add of-platdata support In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-12-sjg@chromium.org> Allow this driver to be used in TPL by setting up the interrupt type correctly. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: None arch/x86/cpu/intel_common/itss.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/x86/cpu/intel_common/itss.c b/arch/x86/cpu/intel_common/itss.c index 33962cb9a0..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); @@ -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), From patchwork Wed Feb 5 02:03:23 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235982 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:23 -0700 Subject: [PATCH v3 12/17] dm: irq: Add support for requesting interrupts In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-13-sjg@chromium.org> At present driver model supports the IRQ uclass but there is no way to request a particular interrupt for a driver. Add a mechanism, similar to clock and reset, to read the interrupts required by a device from the device tree and to request those interrupts. U-Boot itself does not have interrupt-driven handlers, so just provide a means to read and clear an interrupt. This can be useful to handle peripherals which must use an interrupt to determine when data is available, for example. Bring over the basic binding file as well, from Linux v5.4. Note that the older binding is not supported in U-Boot; the newer 'special form' must be used. Add a simple test of the new functionality. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: None arch/sandbox/dts/test.dts | 5 +- .../interrupt-controller/interrupts.txt | 131 ++++++++++++++++++ drivers/misc/irq-uclass.c | 116 ++++++++++++++++ drivers/misc/irq_sandbox.c | 41 ++++++ include/irq.h | 115 +++++++++++++++ test/dm/irq.c | 31 +++++ 6 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 doc/device-tree-bindings/interrupt-controller/interrupts.txt 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/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 at 10140000 { + compatible = "arm,versatile-vic"; + interrupt-controller; + #interrupt-cells = <1>; + reg = <0x10140000 0x1000>; + }; + + sic: intc at 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 at 7000c000 { + gpioext: gpio-adnp at 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 at 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 = ; + compatible = "intel,acpi-gpe"; + interrupt-controller; + #interrupt-cells = <2>; + }; + + tpm at 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/misc/irq-uclass.c b/drivers/misc/irq-uclass.c index c52c813ff3..61aa10e465 100644 --- a/drivers/misc/irq-uclass.c +++ b/drivers/misc/irq-uclass.c @@ -4,8 +4,11 @@ * Written by Simon Glass */ +#define LOG_CATEGORY UCLASS_IRQ + #include #include +#include #include #include @@ -49,6 +52,119 @@ 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; diff --git a/drivers/misc/irq_sandbox.c b/drivers/misc/irq_sandbox.c index 011022ac62..54bc47c8d8 100644 --- a/drivers/misc/irq_sandbox.c +++ b/drivers/misc/irq_sandbox.c @@ -8,6 +8,18 @@ #include #include #include +#include + +/** + * 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,11 +47,39 @@ 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[] = { @@ -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/include/irq.h b/include/irq.h index 8b4e2ecfc0..b71afe9bee 100644 --- a/include/irq.h +++ b/include/irq.h @@ -19,8 +19,21 @@ enum irq_dev_t { 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 { /** @@ -57,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) @@ -96,6 +158,59 @@ 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 * diff --git a/test/dm/irq.c b/test/dm/irq.c index 0f23f108a7..192d80d7e1 100644 --- a/test/dm/irq.c +++ b/test/dm/irq.c @@ -44,3 +44,34 @@ static int dm_test_irq_type(struct unit_test_state *uts) } 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); From patchwork Wed Feb 5 02:03:24 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235980 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:24 -0700 Subject: [PATCH v3 13/17] x86: Add support for ACPI general-purpose events In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-14-sjg@chromium.org> ACPI GPEs are used to signal interrupts from peripherals that are accessed via ACPI. In U-Boot these are modelled as interrupts using a separate interrupt controller. Configuration is via the device tree. Add a simple driver for this. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: None arch/x86/Kconfig | 33 +++++++ arch/x86/cpu/Makefile | 1 + arch/x86/cpu/acpi_gpe.c | 85 +++++++++++++++++++ .../interrupt-controller/intel,acpi-gpe.txt | 30 +++++++ 4 files changed, 149 insertions(+) create mode 100644 arch/x86/cpu/acpi_gpe.c create mode 100644 doc/device-tree-bindings/interrupt-controller/intel,acpi-gpe.txt diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index b733d2264e..412ba01db8 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -916,4 +916,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 + */ + +#include +#include +#include +#include + +/** + * 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/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 = ; + compatible = "intel,acpi-gpe"; + interrupt-controller; + #interrupt-cells = <2>; + }; + + ... + tpm at 50 { + reg = <0x50>; + compatible = "google,cr50"; + ready-gpio = <&gpio_n 0x1c GPIO_ACTIVE_LOW>; + interrupts-extended = <&acpi_gpe 0x3c 0>; + }; From patchwork Wed Feb 5 02:03:25 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235981 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:25 -0700 Subject: [PATCH v3 14/17] x86: coral: Add I2C and TPM device-tree definitions In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-15-sjg@chromium.org> Add nodes to the device tree for Cr50 and other available I2C ports. Also enable the ACPI interrupt driver. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: - Move intel-clock.h inclusion to the correct patch arch/x86/cpu/apollolake/Kconfig | 1 + arch/x86/dts/chromebook_coral.dts | 88 +++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/arch/x86/cpu/apollolake/Kconfig b/arch/x86/cpu/apollolake/Kconfig index 6d6e98a589..188d9ab255 100644 --- a/arch/x86/cpu/apollolake/Kconfig +++ b/arch/x86/cpu/apollolake/Kconfig @@ -43,6 +43,7 @@ config INTEL_APOLLOLAKE imply CLK imply CMD_CLK imply CLK_INTEL + imply ACPI_GPE if INTEL_APOLLOLAKE diff --git a/arch/x86/dts/chromebook_coral.dts b/arch/x86/dts/chromebook_coral.dts index a4a9e949e6..44a4619a66 100644 --- a/arch/x86/dts/chromebook_coral.dts +++ b/arch/x86/dts/chromebook_coral.dts @@ -20,6 +20,7 @@ #include #include #include +#include / { 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 { @@ -80,6 +89,13 @@ }; + acpi_gpe: general-purpose-events { + reg = ; + compatible = "intel,acpi-gpe"; + interrupt-controller; + #interrupt-cells = <2>; + }; + keyboard { intel,duplicate-por; }; @@ -248,6 +264,78 @@ }; }; + i2c_0: i2c2 at 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 at 16,1 { + compatible = "intel,apl-i2c"; + reg = <0x0200b110 0 0 0 0>; + clocks = <&clk CLK_I2C>; + status = "disabled"; + }; + + i2c_2: i2c2 at 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 at 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 at 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 at 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 at 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 at 17,2 { + compatible = "intel,apl-i2c"; + reg = <0x0200b110 0 0 0 0>; + clocks = <&clk CLK_I2C>; + status = "disabled"; + }; + + i2c_7: i2c2 at 17,3 { + compatible = "intel,apl-i2c"; + reg = <0x0200b110 0 0 0 0>; + clocks = <&clk CLK_I2C>; + status = "disabled"; + }; + serial: serial at 18,2 { reg = <0x0200c210 0 0 0 0>; u-boot,dm-pre-reloc; From patchwork Wed Feb 5 02:03:26 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235983 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:26 -0700 Subject: [PATCH v3 15/17] tpm: Add more TPM2 definitions In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-16-sjg@chromium.org> Add definitions for access and status. Need to drop the mixed case. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: None include/tpm-v2.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) 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. * From patchwork Wed Feb 5 02:03:27 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235985 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:27 -0700 Subject: [PATCH v3 16/17] tpm: Add a driver for H1/Cr50 In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-17-sjg@chromium.org> H1 is a Google security chip present in recent Chromebooks, Pixel phones and other devices. Cr50 is the name of the software that runs on H1 in Chromebooks. This chip is used to handle TPM-like functionality and also has quite a few additional features. Add a driver for this. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: - Fix 'devuce' typo - s/@tpm/@dev/ in function comments - Use log_debug() instead of debug() - Fix 'consceivably' typo - Rewrite comment for claim_locality() - Shorten comment in cr50_i2c_wait_tpm_ready() to fit on one line Changes in v2: - Significant rewrite of cr50 init procedure - Support use of interrupts drivers/tpm/Kconfig | 10 + drivers/tpm/Makefile | 1 + drivers/tpm/cr50_i2c.c | 659 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 670 insertions(+) create mode 100644 drivers/tpm/cr50_i2c.c 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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), +}; From patchwork Wed Feb 5 02:03:28 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 235984 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 4 Feb 2020 19:03:28 -0700 Subject: [PATCH v3 17/17] x86: coral: Enable TPM In-Reply-To: <20200205020328.193225-1-sjg@chromium.org> References: <20200205020328.193225-1-sjg@chromium.org> Message-ID: <20200205020328.193225-18-sjg@chromium.org> Enable TPM2 so that we can use cr50. Signed-off-by: Simon Glass Reviewed-by: Bin Meng --- Changes in v3: None Changes in v2: - Update the commit message - Add new patches to handle requesting interrupts and interrupt state configs/chromebook_coral_defconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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