* [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