mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH] i2c: add EFI i2c master driver
@ 2024-03-20 12:55 Tomas Marek
  2024-03-25 10:39 ` Sascha Hauer
  0 siblings, 1 reply; 3+ messages in thread
From: Tomas Marek @ 2024-03-20 12:55 UTC (permalink / raw)
  To: barebox

Add an I2C master driver which uses EFI interface to implement access
to I2C bus.

Signed-off-by: Tomas Marek <tomas.marek@elrest.cz>
---
 drivers/i2c/busses/Kconfig   |   6 +
 drivers/i2c/busses/Makefile  |   1 +
 drivers/i2c/busses/i2c-efi.c | 258 +++++++++++++++++++++++++++++++++++
 efi/guid.c                   |   1 +
 include/efi.h                |   3 +
 5 files changed, 269 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-efi.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index a274baf4b6..093b12b2ef 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -89,4 +89,10 @@ config I2C_CADENCE
 	  Say Y here to include support for the Cadence I2C host controller found
 	  in Zynq UltraScale+ MPSoCs.
 
+config I2C_EFI
+	bool "EFI I2C Master driver"
+	depends on EFI_PAYLOAD
+	help
+	  Say Y here to include support for the EFI I2C Master driver.
+
 endmenu
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index b4225995c0..30005c2bf8 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_I2C_AT91)		+= i2c-at91.o
 obj-$(CONFIG_I2C_BCM283X)	+= i2c-bcm283x.o
+obj-$(CONFIG_I2C_EFI)		+= i2c-efi.o
 obj-$(CONFIG_I2C_GPIO)		+= i2c-gpio.o
 obj-$(CONFIG_I2C_IMX)		+= i2c-imx.o
 lwl-$(CONFIG_I2C_IMX_EARLY)	+= i2c-imx-early.o
diff --git a/drivers/i2c/busses/i2c-efi.c b/drivers/i2c/busses/i2c-efi.c
new file mode 100644
index 0000000000..e647efd9d3
--- /dev/null
+++ b/drivers/i2c/busses/i2c-efi.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * EFI I2C master driver
+ *
+ * Copyright (C) 2024 Elrest Solutions Company s.r.o.
+ * Author: Tomas Marek <tomas.marek@elrest.cz>
+ */
+
+#include <common.h>
+#include <i2c/i2c.h>
+#include <driver.h>
+#include <efi.h>
+#include <efi/efi-device.h>
+#include <efi/efi-util.h>
+#include <linux/kernel.h>
+
+/* Define EFI I2C transfer control flags */
+#define EFI_I2C_FLAG_READ           0x00000001
+
+#define EFI_I2C_ADDRESSING_10_BIT   0x80000000
+
+struct efi_i2c_capabilities {
+	u32 StructureSizeInBytes;
+	u32 MaximumReceiveBytes;
+	u32 MaximumTransmitBytes;
+	u32 MaximumTotalBytes;
+};
+
+struct efi_i2c_operation {
+	u32 Flags;
+	u32 LengthInBytes;
+	u8  *Buffer;
+};
+
+struct efi_i2c_request_packet {
+	unsigned int OperationCount;
+	struct efi_i2c_operation Operation[];
+};
+
+struct efi_i2c_one_request_packet {
+	unsigned int OperationCount;
+	struct efi_i2c_operation Operation[1];
+};
+
+struct efi_i2c_master_protocol {
+	efi_status_t(EFIAPI * set_bus_frequency)(
+		struct efi_i2c_master_protocol *this,
+		unsigned int bus_clock
+	);
+	efi_status_t(EFIAPI * reset)(
+		struct efi_i2c_master_protocol *this
+	);
+	efi_status_t(EFIAPI * start_request)(
+		struct efi_i2c_master_protocol *this,
+		unsigned int slave_address,
+		struct efi_i2c_request_packet *request_packet,
+		void *event,
+		efi_status_t *status
+	);
+	struct efi_i2c_capabilities *capabilities;
+};
+
+struct efi_i2c_priv {
+	struct efi_i2c_master_protocol *efi_protocol;
+	struct i2c_adapter adapter;
+};
+
+static inline struct efi_i2c_priv *
+adapter_to_efi_i2c_priv(struct i2c_adapter *a)
+{
+	return container_of(a, struct efi_i2c_priv, adapter);
+}
+
+static efi_status_t efi_i2c_request(
+		struct efi_i2c_request_packet *request,
+		struct efi_i2c_priv *i2c_priv,
+		const struct i2c_msg *msg)
+{
+	struct device *dev = &i2c_priv->adapter.dev;
+	unsigned int slave_address = msg->addr;
+	efi_status_t efiret;
+
+	if (msg->flags & I2C_M_DATA_ONLY)
+		return -ENOTSUPP;
+
+	if (msg->flags & I2C_M_TEN)
+		slave_address |= EFI_I2C_ADDRESSING_10_BIT;
+
+	efiret = i2c_priv->efi_protocol->start_request(
+		i2c_priv->efi_protocol,
+		slave_address,
+		request,
+		NULL,
+		NULL
+	);
+
+	if (EFI_ERROR(efiret))
+		dev_err(dev, "I2C operation failed - %s (%lx)\n",
+			efi_strerror(efiret), -efiret);
+
+	return efiret;
+}
+
+static efi_status_t efi_i2c_write(const struct i2c_msg *msg,
+				  struct efi_i2c_priv *i2c_priv,
+				  u8 *buf, u32 len)
+{
+	struct efi_i2c_one_request_packet write_request = {
+		.OperationCount = 1,
+		.Operation = { {
+			.Flags = 0,
+			.LengthInBytes = len,
+			.Buffer = buf,
+		} },
+	};
+
+	return efi_i2c_request(
+		(struct efi_i2c_request_packet *)&write_request,
+		i2c_priv, msg);
+}
+
+static efi_status_t efi_i2c_read(struct i2c_msg *msg,
+				 struct efi_i2c_priv *i2c_priv,
+				 u8 *buf, u32 len)
+{
+	struct efi_i2c_one_request_packet read_request = {
+		.OperationCount = 1,
+		.Operation = { {
+			.Flags = EFI_I2C_FLAG_READ,
+			.LengthInBytes = len,
+			.Buffer = buf,
+		} },
+	};
+
+	return efi_i2c_request(
+		(struct efi_i2c_request_packet *)&read_request,
+		i2c_priv, msg);
+}
+
+static int efi_i2c_max_len(struct efi_i2c_priv *i2c_priv,
+			   struct i2c_msg *msg)
+{
+	const struct efi_i2c_capabilities *capabilities =
+		i2c_priv->efi_protocol->capabilities;
+
+	if (msg->flags & I2C_M_RD)
+		return capabilities->MaximumReceiveBytes;
+	else
+		return capabilities->MaximumTransmitBytes;
+}
+
+static int efi_i2c_message_xfer(struct efi_i2c_priv *i2c_priv,
+				struct i2c_msg *msg)
+{
+	const u32 max_len = efi_i2c_max_len(i2c_priv, msg);
+
+	efi_status_t efiret;
+	u32 offset = 0;
+	u32 len;
+	u8 *buf = msg->buf;
+
+	do {
+		len = min(max_len, msg->len - offset);
+
+		if (msg->flags & I2C_M_RD)
+			efiret = efi_i2c_read(msg, i2c_priv, buf, len);
+		else
+			efiret = efi_i2c_write(msg, i2c_priv, buf, len);
+
+		if (EFI_ERROR(efiret))
+			break;
+
+		offset += len;
+		buf += len;
+	} while (offset < msg->len);
+
+	return -efi_errno(efiret);
+}
+
+
+static int efi_i2c_xfer(struct i2c_adapter *adapter,
+			struct i2c_msg *msg, int nmsgs)
+{
+	struct efi_i2c_priv *i2c_priv = adapter_to_efi_i2c_priv(adapter);
+	int i, ret;
+
+	for (i = nmsgs; i > 0; i--, msg++) {
+		ret = efi_i2c_message_xfer(i2c_priv, msg);
+		if (ret)
+			return ret;
+	}
+
+	return nmsgs;
+}
+
+
+static int efi_i2c_probe(struct efi_device *efidev)
+{
+	struct i2c_platform_data *pdata;
+	struct efi_i2c_priv *efi_i2c;
+	struct i2c_timings timings;
+	efi_status_t efiret;
+	int ret;
+
+	efi_i2c = xzalloc(sizeof(*efi_i2c));
+
+	efi_i2c->efi_protocol = efidev->protocol;
+
+	efi_i2c->adapter.master_xfer = efi_i2c_xfer;
+	efi_i2c->adapter.nr = efidev->dev.id;
+	efi_i2c->adapter.dev.parent = &efidev->dev;
+	efi_i2c->adapter.dev.of_node = efidev->dev.of_node;
+
+	i2c_parse_fw_timings(&efidev->dev, &timings, true);
+
+	pdata = efidev->dev.platform_data;
+	if (pdata && pdata->bitrate)
+		timings.bus_freq_hz = pdata->bitrate;
+
+	efiret = efi_i2c->efi_protocol->reset(efi_i2c->efi_protocol);
+	if (EFI_ERROR(efiret)) {
+		dev_err(&efidev->dev, "controller reset failed - %ld\n",
+			efiret);
+		ret = -efi_errno(efiret);
+		goto out_free;
+	}
+
+	efiret = efi_i2c->efi_protocol->set_bus_frequency(
+			efi_i2c->efi_protocol,
+			timings.bus_freq_hz);
+	if (EFI_ERROR(efiret)) {
+		dev_err(&efidev->dev, "clock frequency update failed - %ld\n",
+			efiret);
+		ret = -efi_errno(efiret);
+		goto out_free;
+	}
+
+	ret = i2c_add_numbered_adapter(&efi_i2c->adapter);
+
+out_free:
+	if (ret < 0)
+		kfree(efi_i2c);
+
+	return ret;
+}
+
+static struct efi_driver efi_i2c_driver = {
+	.driver = {
+		.name  = "efi-i2c",
+	},
+	.probe = efi_i2c_probe,
+	.guid = EFI_I2C_MASTER_PROTOCOL_GUID
+};
+device_efi_driver(efi_i2c_driver);
+
+MODULE_AUTHOR("Tomas Marek <tomas.marek@elrest.cz>");
+MODULE_DESCRIPTION("EFI I2C master driver");
+MODULE_LICENSE("GPL");
diff --git a/efi/guid.c b/efi/guid.c
index f6bd4a24e2..ef230a2b24 100644
--- a/efi/guid.c
+++ b/efi/guid.c
@@ -115,6 +115,7 @@ const char *efi_guid_string(efi_guid_t *g)
 	EFI_GUID_STRING(EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_GUID_31, "Network Interface Identifier Protocol_31",  "EFI1.1 Network Interface Identifier Protocol");
 	EFI_GUID_STRING(EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_GUID, "Network Interface Identifier Protocol", "EFI Network Interface Identifier Protocol");
 	EFI_GUID_STRING(EFI_TIMESTAMP_PROTOCOL_GUID, "Timestamp", "Timestamp");
+	EFI_GUID_STRING(EFI_I2C_MASTER_PROTOCOL_GUID, "I2C Master Protocol", "EFI I2C Master Protocol");
 
 	/* TPM 1.2 */
 	EFI_GUID_STRING( EFI_TCG_PROTOCOL_GUID, "TcgService", "TCGServices Protocol");
diff --git a/include/efi.h b/include/efi.h
index 6bb5f8cb0a..a27cbe1f49 100644
--- a/include/efi.h
+++ b/include/efi.h
@@ -562,6 +562,9 @@ extern struct efi_runtime_services *RT;
 #define EFI_TIMESTAMP_PROTOCOL_GUID \
     EFI_GUID(0xafbfde41, 0x2e6e, 0x4262, 0xba, 0x65, 0x62, 0xb9, 0x23, 0x6e, 0x54, 0x95)
 
+#define EFI_I2C_MASTER_PROTOCOL_GUID \
+	EFI_GUID(0xcd72881f, 0x45b5, 0x4feb, 0x98, 0xc8, 0x31, 0x3d, 0xa8, 0x11, 0x74, 0x62)
+
 /* barebox specific GUIDs */
 #define EFI_BAREBOX_VENDOR_GUID \
     EFI_GUID(0x5b91f69c, 0x8b88, 0x4a2b, 0x92, 0x69, 0x5f, 0x1d, 0x80, 0x2b, 0x51, 0x75)
-- 
2.39.2




^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] i2c: add EFI i2c master driver
  2024-03-20 12:55 [PATCH] i2c: add EFI i2c master driver Tomas Marek
@ 2024-03-25 10:39 ` Sascha Hauer
  2024-03-26  6:08   ` Tomas Marek
  0 siblings, 1 reply; 3+ messages in thread
From: Sascha Hauer @ 2024-03-25 10:39 UTC (permalink / raw)
  To: Tomas Marek; +Cc: barebox

Hi Tomas,

Thanks for the patch. Looks mostly fine, but this needs fixing:

On Wed, Mar 20, 2024 at 01:55:38PM +0100, Tomas Marek wrote:
> +static int efi_i2c_xfer(struct i2c_adapter *adapter,
> +			struct i2c_msg *msg, int nmsgs)
> +{
> +	struct efi_i2c_priv *i2c_priv = adapter_to_efi_i2c_priv(adapter);
> +	int i, ret;
> +
> +	for (i = nmsgs; i > 0; i--, msg++) {
> +		ret = efi_i2c_message_xfer(i2c_priv, msg);
> +		if (ret)
> +			return ret;
> +	}

Each of the messages should start with a (repeated) start bit and only
the last message should be followed by a stop bit. By separating the
full message into segments and pushing each segment into EFI separately
you would instead send a stop bit after each segment, see the comment
above EFI_I2C_REQUEST_PACKET:

///
/// I2C device request
///
/// The EFI_I2C_REQUEST_PACKET describes a single I2C transaction.  The
/// transaction starts with a start bit followed by the first operation
/// in the operation array.  Subsequent operations are separated with
/// repeated start bits and the last operation is followed by a stop bit
/// which concludes the transaction.  Each operation is described by one
/// of the elements in the Operation array.
///

Instead you have to compile all nmsgs into a single EFI_I2C_REQUEST_PACKET
and push it into EFI as a single request.

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] i2c: add EFI i2c master driver
  2024-03-25 10:39 ` Sascha Hauer
@ 2024-03-26  6:08   ` Tomas Marek
  0 siblings, 0 replies; 3+ messages in thread
From: Tomas Marek @ 2024-03-26  6:08 UTC (permalink / raw)
  To: Sascha Hauer; +Cc: barebox

Hi Sascha,

On Mon, Mar 25, 2024 at 11:39:46AM +0100, Sascha Hauer wrote:
> Hi Tomas,
> 
> Thanks for the patch. Looks mostly fine, but this needs fixing:
> 
> On Wed, Mar 20, 2024 at 01:55:38PM +0100, Tomas Marek wrote:
> > +static int efi_i2c_xfer(struct i2c_adapter *adapter,
> > +			struct i2c_msg *msg, int nmsgs)
> > +{
> > +	struct efi_i2c_priv *i2c_priv = adapter_to_efi_i2c_priv(adapter);
> > +	int i, ret;
> > +
> > +	for (i = nmsgs; i > 0; i--, msg++) {
> > +		ret = efi_i2c_message_xfer(i2c_priv, msg);
> > +		if (ret)
> > +			return ret;
> > +	}
> 
> Each of the messages should start with a (repeated) start bit and only
> the last message should be followed by a stop bit. By separating the
> full message into segments and pushing each segment into EFI separately
> you would instead send a stop bit after each segment, see the comment
> above EFI_I2C_REQUEST_PACKET:
> 
> ///
> /// I2C device request
> ///
> /// The EFI_I2C_REQUEST_PACKET describes a single I2C transaction.  The
> /// transaction starts with a start bit followed by the first operation
> /// in the operation array.  Subsequent operations are separated with
> /// repeated start bits and the last operation is followed by a stop bit
> /// which concludes the transaction.  Each operation is described by one
> /// of the elements in the Operation array.
> ///
> 
> Instead you have to compile all nmsgs into a single EFI_I2C_REQUEST_PACKET
> and push it into EFI as a single request.

I see. You are right. I will fix.

Best regards

Tomas

> 
> Sascha
> 
> -- 
> Pengutronix e.K.                           |                             |
> Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
> 31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
> Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2024-03-26  6:09 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-03-20 12:55 [PATCH] i2c: add EFI i2c master driver Tomas Marek
2024-03-25 10:39 ` Sascha Hauer
2024-03-26  6:08   ` Tomas Marek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox