[PATCH v5 3/6] fsi: Add IBM I2C Responder virtual FSI master

From: Eddie James
Date: Tue Feb 21 2023 - 16:27:05 EST


The I2C Responder (I2CR) is an I2C device that translates I2C commands
to CFAM or SCOM operations, effectively implementing an FSI master and
bus.

Signed-off-by: Eddie James <eajames@xxxxxxxxxxxxx>
---
Changes since v4:
- Split the read/write functions into the FSI master register access functions
and generic functions that can do the SCOMs
- Rework the trace events to handle variable data size and fix the trace
structure alignment/padding
- Add a check on the status register during probe to avoid registering the
FSI master if the device isn't there

drivers/fsi/Kconfig | 9 +
drivers/fsi/Makefile | 1 +
drivers/fsi/fsi-master-i2cr.c | 228 +++++++++++++++++++++++++
drivers/fsi/fsi-master-i2cr.h | 24 +++
drivers/fsi/fsi-master.h | 1 +
include/trace/events/fsi_master_i2cr.h | 109 ++++++++++++
6 files changed, 372 insertions(+)
create mode 100644 drivers/fsi/fsi-master-i2cr.c
create mode 100644 drivers/fsi/fsi-master-i2cr.h
create mode 100644 include/trace/events/fsi_master_i2cr.h

diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
index e6668a869913..999be82720c5 100644
--- a/drivers/fsi/Kconfig
+++ b/drivers/fsi/Kconfig
@@ -62,6 +62,15 @@ config FSI_MASTER_ASPEED

Enable it for your BMC kernel in an OpenPower or IBM Power system.

+config FSI_MASTER_I2CR
+ tristate "IBM I2C Responder virtual FSI master"
+ depends on I2C
+ help
+ This option enables a virtual FSI master in order to access a CFAM
+ behind an IBM I2C Responder (I2CR) chip. The I2CR is an I2C device
+ that translates I2C commands to CFAM or SCOM operations, effectively
+ implementing an FSI master and bus.
+
config FSI_SCOM
tristate "SCOM FSI client device driver"
help
diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
index da218a1ad8e1..34dbaa1c452e 100644
--- a/drivers/fsi/Makefile
+++ b/drivers/fsi/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_FSI) += fsi-core.o
obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o
obj-$(CONFIG_FSI_MASTER_ASPEED) += fsi-master-aspeed.o
obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
+obj-$(CONFIG_FSI_MASTER_I2CR) += fsi-master-i2cr.o
obj-$(CONFIG_FSI_MASTER_AST_CF) += fsi-master-ast-cf.o
obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o
diff --git a/drivers/fsi/fsi-master-i2cr.c b/drivers/fsi/fsi-master-i2cr.c
new file mode 100644
index 000000000000..d4ea41cde1ae
--- /dev/null
+++ b/drivers/fsi/fsi-master-i2cr.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) IBM Corporation 2023 */
+
+#include <linux/device.h>
+#include <linux/fsi.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+
+#include "fsi-master-i2cr.h"
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/fsi_master_i2cr.h>
+
+#define I2CR_ADDRESS_CFAM(a) ((a) >> 2)
+#define I2CR_STATUS 0x30001
+#define I2CR_STATUS_ERR BIT_ULL(61)
+#define I2CR_ERROR 0x30002
+
+static bool i2cr_check_parity(u32 v, bool parity)
+{
+ u32 i;
+
+ for (i = 0; i < 32; ++i) {
+ if (v & (1 << i))
+ parity = !parity;
+ }
+
+ return parity;
+}
+
+static __be32 i2cr_get_command(u32 address, bool parity)
+{
+ address <<= 1;
+
+ if (i2cr_check_parity(address, parity))
+ address |= 1;
+
+ return cpu_to_be32(address);
+}
+
+static int i2cr_transfer(struct i2c_client *client, u32 address, __be64 *data)
+{
+ struct i2c_msg msgs[2];
+ __be32 command;
+ int ret;
+
+ command = i2cr_get_command(address, true);
+ msgs[0].addr = client->addr;
+ msgs[0].flags = 0;
+ msgs[0].len = sizeof(command);
+ msgs[0].buf = (__u8 *)&command;
+ msgs[1].addr = client->addr;
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].len = sizeof(*data);
+ msgs[1].buf = (__u8 *)data;
+
+ ret = i2c_transfer(client->adapter, msgs, 2);
+ if (ret == 2)
+ return 0;
+
+ trace_i2cr_i2c_error(client, command, ret);
+
+ if (ret < 0)
+ return ret;
+
+ return -EIO;
+}
+
+static int i2cr_check_status(struct i2c_client *client)
+{
+ __be64 status_be = 0;
+ u64 status;
+ int ret;
+
+ ret = i2cr_transfer(client, I2CR_STATUS, &status_be);
+ if (ret)
+ return ret;
+
+ status = be64_to_cpu(status_be);
+ if (status & I2CR_STATUS_ERR) {
+ __be64 error_be = 0;
+ u64 error;
+
+ i2cr_transfer(client, I2CR_ERROR, &error_be);
+ error = be64_to_cpu(error_be);
+ trace_i2cr_status_error(client, status, error);
+
+ dev_err(&client->dev, "status:%016llx error:%016llx\n", status, error);
+ return -EREMOTEIO;
+ }
+
+ trace_i2cr_status(client, status);
+ return 0;
+}
+
+int fsi_master_i2cr_read(struct fsi_master_i2cr *i2cr, u32 addr, void *data, size_t size)
+{
+ __be64 buf;
+ int ret;
+
+ mutex_lock(&i2cr->lock);
+
+ ret = i2cr_transfer(i2cr->client, addr, &buf);
+ if (ret)
+ goto unlock;
+
+ ret = i2cr_check_status(i2cr->client);
+ if (ret)
+ goto unlock;
+
+ memcpy(data, &buf, size);
+ trace_i2cr_read(i2cr->client, addr, data, size);
+
+unlock:
+ mutex_unlock(&i2cr->lock);
+ return ret;
+}
+
+int fsi_master_i2cr_write(struct fsi_master_i2cr *i2cr, u32 addr, const void *data, size_t size)
+{
+ u32 buf[3] = { 0 };
+ __be32 command;
+ int ret;
+
+ memcpy(&buf[1], data, size);
+ command = i2cr_get_command(addr, i2cr_check_parity(buf[1], true));
+ memcpy(&buf[0], &command, sizeof(u32));
+
+ mutex_lock(&i2cr->lock);
+
+ ret = i2c_master_send(i2cr->client, (const char *)buf, sizeof(buf));
+ if (ret == sizeof(buf)) {
+ ret = i2cr_check_status(i2cr->client);
+ if (!ret)
+ trace_i2cr_write(i2cr->client, addr, data, size);
+ } else {
+ trace_i2cr_i2c_error(i2cr->client, command, ret);
+
+ if (ret >= 0)
+ ret = -EIO;
+ }
+
+ mutex_unlock(&i2cr->lock);
+ return ret;
+}
+
+static int i2cr_read(struct fsi_master *master, int link, uint8_t id, uint32_t addr, void *val,
+ size_t size)
+{
+ struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master);
+
+ if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4))
+ return -EINVAL;
+
+ return fsi_master_i2cr_read(i2cr, I2CR_ADDRESS_CFAM(addr), val, size);
+}
+
+static int i2cr_write(struct fsi_master *master, int link, uint8_t id, uint32_t addr,
+ const void *val, size_t size)
+{
+ struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master);
+
+ if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4))
+ return -EINVAL;
+
+ return fsi_master_i2cr_write(i2cr, I2CR_ADDRESS_CFAM(addr), val, size);
+}
+
+static int i2cr_probe(struct i2c_client *client)
+{
+ struct fsi_master_i2cr *i2cr;
+ int ret;
+
+ if (i2cr_check_status(client))
+ return -ENODEV;
+
+ i2cr = devm_kzalloc(&client->dev, sizeof(*i2cr), GFP_KERNEL);
+ if (!i2cr)
+ return -ENOMEM;
+
+ i2cr->master.dev.parent = &client->dev;
+ i2cr->master.dev.of_node = of_node_get(dev_of_node(&client->dev));
+
+ i2cr->master.n_links = 1;
+ i2cr->master.flags = FSI_MASTER_FLAG_I2CR;
+ i2cr->master.read = i2cr_read;
+ i2cr->master.write = i2cr_write;
+
+ mutex_init(&i2cr->lock);
+ i2cr->client = client;
+
+ ret = fsi_master_register(&i2cr->master);
+ if (ret)
+ return ret;
+
+ i2c_set_clientdata(client, i2cr);
+ return 0;
+}
+
+static void i2cr_remove(struct i2c_client *client)
+{
+ struct fsi_master_i2cr *i2cr = i2c_get_clientdata(client);
+
+ fsi_master_unregister(&i2cr->master);
+}
+
+static const struct of_device_id i2cr_ids[] = {
+ { .compatible = "ibm,i2cr-fsi-master" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, i2cr_ids);
+
+static struct i2c_driver i2cr_driver = {
+ .probe_new = i2cr_probe,
+ .remove = i2cr_remove,
+ .driver = {
+ .name = "fsi-master-i2cr",
+ .of_match_table = i2cr_ids,
+ },
+};
+
+module_i2c_driver(i2cr_driver)
+
+MODULE_AUTHOR("Eddie James <eajames@xxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("IBM I2C Responder virtual FSI master driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/fsi/fsi-master-i2cr.h b/drivers/fsi/fsi-master-i2cr.h
new file mode 100644
index 000000000000..0e164b3c6d3c
--- /dev/null
+++ b/drivers/fsi/fsi-master-i2cr.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) IBM Corporation 2023 */
+
+#ifndef DRIVERS_FSI_MASTER_I2CR_H
+#define DRIVERS_FSI_MASTER_I2CR_H
+
+#include <linux/mutex.h>
+
+#include "fsi-master.h"
+
+struct i2c_client;
+
+struct fsi_master_i2cr {
+ struct fsi_master master;
+ struct mutex lock; /* protect HW access */
+ struct i2c_client *client;
+};
+
+#define to_fsi_master_i2cr(m) container_of(m, struct fsi_master_i2cr, master)
+
+int fsi_master_i2cr_read(struct fsi_master_i2cr *i2cr, u32 addr, void *data, size_t size);
+int fsi_master_i2cr_write(struct fsi_master_i2cr *i2cr, u32 addr, const void *data, size_t size);
+
+#endif /* DRIVERS_FSI_MASTER_I2CR_H */
diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h
index 967622c1cabf..a1fa315849d2 100644
--- a/drivers/fsi/fsi-master.h
+++ b/drivers/fsi/fsi-master.h
@@ -111,6 +111,7 @@

/* fsi-master definition and flags */
#define FSI_MASTER_FLAG_SWCLOCK 0x1
+#define FSI_MASTER_FLAG_I2CR 0x2

/*
* Structures and function prototypes
diff --git a/include/trace/events/fsi_master_i2cr.h b/include/trace/events/fsi_master_i2cr.h
new file mode 100644
index 000000000000..bbd7875ee5a6
--- /dev/null
+++ b/include/trace/events/fsi_master_i2cr.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM fsi_master_i2cr
+
+#if !defined(_TRACE_FSI_MASTER_I2CR_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_FSI_MASTER_I2CR_H
+
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(i2cr_i2c_error,
+ TP_PROTO(const struct i2c_client *client, __be32 command, int rc),
+ TP_ARGS(client, command, rc),
+ TP_STRUCT__entry(
+ __field(int, bus)
+ __field(int, rc)
+ __array(unsigned char, command, sizeof(__be32))
+ __field(unsigned short, addr)
+ ),
+ TP_fast_assign(
+ __entry->bus = client->adapter->nr;
+ __entry->addr = client->addr;
+ memcpy(__entry->command, &command, sizeof(__be32));
+ __entry->rc = rc;
+ ),
+ TP_printk("%d-%02x command:{ %*ph } rc:%d", __entry->bus, __entry->addr,
+ (int)sizeof(__be32), __entry->command, __entry->rc)
+);
+
+TRACE_EVENT(i2cr_read,
+ TP_PROTO(const struct i2c_client *client, uint32_t address, const void *data, size_t size),
+ TP_ARGS(client, address, data, size),
+ TP_STRUCT__entry(
+ __field(size_t, size)
+ __field(int, bus)
+ __field(uint32_t, address)
+ __array(unsigned char, data, 8)
+ __field(unsigned short, addr)
+ ),
+ TP_fast_assign(
+ __entry->bus = client->adapter->nr;
+ __entry->addr = client->addr;
+ __entry->address = address;
+ __entry->size = size > 8 ? 8 : size;
+ memcpy(__entry->data, data, __entry->size);
+ ),
+ TP_printk("%d-%02x address:%08x size:%zu { %*ph }", __entry->bus, __entry->addr,
+ __entry->address, __entry->size, (int)__entry->size, __entry->data)
+);
+
+TRACE_EVENT(i2cr_status,
+ TP_PROTO(const struct i2c_client *client, uint64_t status),
+ TP_ARGS(client, status),
+ TP_STRUCT__entry(
+ __field(uint64_t, status)
+ __field(int, bus)
+ __field(unsigned short, addr)
+ ),
+ TP_fast_assign(
+ __entry->bus = client->adapter->nr;
+ __entry->addr = client->addr;
+ __entry->status = status;
+ ),
+ TP_printk("%d-%02x %016llx", __entry->bus, __entry->addr, __entry->status)
+);
+
+TRACE_EVENT(i2cr_status_error,
+ TP_PROTO(const struct i2c_client *client, uint64_t status, uint64_t error),
+ TP_ARGS(client, status, error),
+ TP_STRUCT__entry(
+ __field(uint64_t, error)
+ __field(uint64_t, status)
+ __field(int, bus)
+ __field(unsigned short, addr)
+ ),
+ TP_fast_assign(
+ __entry->bus = client->adapter->nr;
+ __entry->addr = client->addr;
+ __entry->error = error;
+ __entry->status = status;
+ ),
+ TP_printk("%d-%02x status:%016llx error:%016llx", __entry->bus, __entry->addr,
+ __entry->status, __entry->error)
+);
+
+TRACE_EVENT(i2cr_write,
+ TP_PROTO(const struct i2c_client *client, uint32_t address, const void *data, size_t size),
+ TP_ARGS(client, address, data, size),
+ TP_STRUCT__entry(
+ __field(size_t, size)
+ __field(int, bus)
+ __field(uint32_t, address)
+ __array(unsigned char, data, 8)
+ __field(unsigned short, addr)
+ ),
+ TP_fast_assign(
+ __entry->bus = client->adapter->nr;
+ __entry->addr = client->addr;
+ __entry->address = address;
+ __entry->size = size > 8 ? 8 : size;
+ memcpy(__entry->data, data, __entry->size);
+ ),
+ TP_printk("%d-%02x address:%08x size:%zu { %*ph }", __entry->bus, __entry->addr,
+ __entry->address, __entry->size, (int)__entry->size, __entry->data)
+);
+
+#endif
+
+#include <trace/define_trace.h>
--
2.31.1