diff options
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/clk/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/clk/Makefile | 1 | ||||
| -rw-r--r-- | drivers/clk/intel/Makefile | 6 | ||||
| -rw-r--r-- | drivers/clk/intel/clk_intel.c | 41 | ||||
| -rw-r--r-- | drivers/core/syscon-uclass.c | 15 | ||||
| -rw-r--r-- | drivers/core/uclass.c | 17 | ||||
| -rw-r--r-- | drivers/i2c/tegra_i2c.c | 13 | ||||
| -rw-r--r-- | drivers/misc/irq-uclass.c | 131 | ||||
| -rw-r--r-- | drivers/misc/irq_sandbox.c | 43 | ||||
| -rw-r--r-- | drivers/pinctrl/intel/pinctrl.c | 2 | ||||
| -rw-r--r-- | drivers/tpm/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/tpm/Makefile | 1 | ||||
| -rw-r--r-- | drivers/tpm/cr50_i2c.c | 659 | 
13 files changed, 923 insertions, 26 deletions
| diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 16d4237f891..1992d4a4b47 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 06131edb9fe..e01783391dc 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 00000000000..45e93d70248 --- /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 00000000000..d2e15491a3d --- /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 5bb38e329cb..b9ae82174ee 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 c520ef113a2..61192d8a9ff 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 4be41ddbf05..142463ef440 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 d5182cf1497..61aa10e4658 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 6dda1a4c442..54bc47c8d8a 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 5bf5d8b0e24..f4cc55aa3b4 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 94629dffd2e..555a76bb1e3 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 94c337b8ed3..4c866b37c50 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 00000000000..b904a7d426e --- /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), +}; | 
