From: Tomas Marek <tomas.marek@elrest.cz>
To: barebox@lists.infradead.org
Subject: [PATCH] i2c: add EFI i2c master driver
Date: Wed, 20 Mar 2024 13:55:38 +0100 [thread overview]
Message-ID: <20240320125538.31679-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>
---
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
next reply other threads:[~2024-03-20 12:56 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-03-20 12:55 Tomas Marek [this message]
2024-03-25 10:39 ` [PATCH] i2c: add EFI i2c master driver Sascha Hauer
2024-03-26 6:08 ` Tomas Marek
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=20240320125538.31679-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.