mail archive of the barebox mailing list
 help / color / mirror / Atom feed
From: Tomas Marek <tomas.marek@elrest.cz>
To: barebox@lists.infradead.org
Subject: [PATCH v2] i2c: add EFI i2c master driver
Date: Wed,  3 Apr 2024 09:49:09 +0200	[thread overview]
Message-ID: <20240403074909.32256-1-tomas.marek@elrest.cz> (raw)

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

Signed-off-by: Tomas Marek <tomas.marek@elrest.cz>
---
Changes in v2:
- Addressed comments from Sascha - mapped all nmsgs to a single EFI
  request packet instead of sending each message as one EFI request
  (sending just one stop bit for all nmsgs instead of sending the stop
  bit after each message).
- Fixed I2C set bus clock frequency (EFI parameter is a pointer to an
  unsigned int, not an unsigned int).
- Link to v1: https://lore.barebox.org/barebox/20240320125538.31679-1-tomas.marek@elrest.cz/
---
 drivers/i2c/busses/Kconfig   |   6 +
 drivers/i2c/busses/Makefile  |   1 +
 drivers/i2c/busses/i2c-efi.c | 292 +++++++++++++++++++++++++++++++++++
 efi/guid.c                   |   1 +
 include/efi.h                |   3 +
 5 files changed, 303 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..17bdbe995c
--- /dev/null
+++ b/drivers/i2c/busses/i2c-efi.c
@@ -0,0 +1,292 @@
+// 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
+
+/* The set_bus_frequency() EFI call doesn't work (doesn't alter SPI clock
+ * frequency) if it's parameter is defined on the stack (observed with
+ * American Megatrends EFI Revision 5.19) - let's define it globaly.
+ */
+static unsigned int bus_clock;
+
+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_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,
+	const struct efi_i2c_priv *i2c_priv,
+	const unsigned int slave_address)
+{
+	const struct device *dev = &i2c_priv->adapter.dev;
+	efi_status_t efiret;
+
+	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 u32 efi_i2c_max_len(const struct efi_i2c_priv *i2c_priv,
+			   const 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 unsigned int efi_i2c_msg_op_cnt(const struct efi_i2c_priv *i2c_priv,
+				       const struct i2c_msg *msg)
+{
+	unsigned int max_len;
+
+	max_len = efi_i2c_max_len(i2c_priv, msg);
+	return ((u64)msg->len + max_len - 1) / max_len;
+}
+
+static unsigned int efi_i2c_req_op_cnt(
+	const struct efi_i2c_priv *i2c_priv,
+	const struct i2c_msg *msg,
+	const int nmsgs)
+{
+	unsigned int op_cnt = 0;
+	int i;
+
+	for (i = nmsgs; i > 0; i--, msg++)
+		op_cnt += efi_i2c_msg_op_cnt(i2c_priv, msg);
+
+	return op_cnt;
+}
+
+static void i2c_msg_to_efi_op(
+	const struct efi_i2c_priv *i2c_priv,
+	const struct i2c_msg *msg,
+	struct efi_i2c_operation **op)
+{
+	unsigned int max_len = efi_i2c_max_len(i2c_priv, msg);
+	unsigned int remaining = msg->len;
+	u32 flags;
+
+	flags = (msg->flags & I2C_M_RD) ? EFI_I2C_FLAG_READ : 0;
+
+	do {
+		unsigned int len = (remaining < max_len) ? remaining : max_len;
+
+		(*op)->Flags = flags;
+		(*op)->LengthInBytes = len;
+		(*op)->Buffer = msg->buf + (msg->len - remaining);
+		(*op)++;
+
+		remaining -= len;
+	} while (remaining > 0);
+}
+
+static int i2c_msgs_to_efi_transaction(struct i2c_adapter *adapter,
+				       struct efi_i2c_operation *op,
+				       const struct i2c_msg *msg,
+				       const int nmsgs)
+{
+	struct efi_i2c_priv *i2c_priv = adapter_to_efi_i2c_priv(adapter);
+	const struct i2c_msg *msg_tmp;
+	int ret = 0;
+	int i;
+
+	msg_tmp = msg;
+	for (i = nmsgs; i > 0; i--, msg_tmp++) {
+		if (msg_tmp->flags & I2C_M_DATA_ONLY) {
+			ret = -ENOTSUPP;
+			break;
+		}
+
+		if (i > 0) {
+			if (msg_tmp->addr != msg->addr) {
+				dev_err(&adapter->dev, "Different I2C addresses in one request not supported!\n");
+				ret = -ENOTSUPP;
+				break;
+			}
+		}
+
+		i2c_msg_to_efi_op(i2c_priv, msg_tmp, &op);
+	}
+
+	return ret;
+}
+
+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);
+	struct efi_i2c_request_packet *request_packet;
+	unsigned int slave_address;
+	efi_status_t efiret;
+	unsigned int op_cnt;
+	int ret = nmsgs;
+	int len;
+
+	op_cnt = efi_i2c_req_op_cnt(i2c_priv, msg, nmsgs);
+
+	len = sizeof(*request_packet) + op_cnt*sizeof(struct efi_i2c_operation);
+	request_packet = malloc(len);
+	if (!request_packet)
+		return -ENOMEM;
+
+	request_packet->OperationCount = op_cnt;
+	ret = i2c_msgs_to_efi_transaction(adapter, request_packet->Operation,
+					  msg, nmsgs);
+	if (ret)
+		goto out_free;
+
+	slave_address = msg->addr;
+	if (msg->flags & I2C_M_TEN)
+		slave_address |= EFI_I2C_ADDRESSING_10_BIT;
+
+	efiret = efi_i2c_request(request_packet, i2c_priv, slave_address);
+	if (EFI_ERROR(efiret)) {
+		ret = -efi_errno(efiret);
+		goto out_free;
+	}
+
+	ret = nmsgs;
+
+out_free:
+	free(request_packet);
+
+	return ret;
+}
+
+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;
+	}
+
+	bus_clock = timings.bus_freq_hz;
+	efiret = efi_i2c->efi_protocol->set_bus_frequency(
+			efi_i2c->efi_protocol,
+			&bus_clock);
+	if (EFI_ERROR(efiret)) {
+		dev_err(&efidev->dev, "I2C clock frequency %u update failed - %s (%lx)\n",
+			timings.bus_freq_hz, efi_strerror(efiret), -efiret);
+		ret = -efi_errno(efiret);
+		goto out_free;
+	}
+	dev_info(&efidev->dev, "I2C clock frequency %u\n", bus_clock);
+
+	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




             reply	other threads:[~2024-04-03  7:50 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-03  7:49 Tomas Marek [this message]
2024-04-03 12:09 ` Sascha Hauer
2024-04-03 12:13 ` Sascha Hauer
2024-04-04  5:28   ` Tomas Marek
2024-04-04  6:35     ` Sascha Hauer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240403074909.32256-1-tomas.marek@elrest.cz \
    --to=tomas.marek@elrest.cz \
    --cc=barebox@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox