[v2,2/5] drivers: fsi: Add I2C Responder driver
Commit Message
Add a regmap driver for the I2C Responder (I2CR) which provides
access to an FSI CFAM through an I2C endpoint device. Since no
userspace access is needed, simply provide a regmap bus for the
CFAM address space through the I2CR.
Signed-off-by: Eddie James <eajames@linux.ibm.com>
---
drivers/fsi/Kconfig | 6 +++
drivers/fsi/Makefile | 1 +
drivers/fsi/i2cr.c | 116 +++++++++++++++++++++++++++++++++++++++++++
drivers/fsi/i2cr.h | 19 +++++++
4 files changed, 142 insertions(+)
create mode 100644 drivers/fsi/i2cr.c
create mode 100644 drivers/fsi/i2cr.h
@@ -67,6 +67,12 @@ config FSI_SCOM
help
This option enables an FSI based SCOM device driver.
+config FSI_I2CR
+ tristate "I2C Responder device driver"
+ help
+ This option enables the I2C client driver for the IBM POWER I2C
+ responder which gives access to CFAM address space.
+
config FSI_SBEFIFO
tristate "SBEFIFO FSI client device driver"
depends on OF_ADDRESS
@@ -6,5 +6,6 @@ obj-$(CONFIG_FSI_MASTER_ASPEED) += fsi-master-aspeed.o
obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
obj-$(CONFIG_FSI_MASTER_AST_CF) += fsi-master-ast-cf.o
obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
+obj-$(CONFIG_FSI_I2CR) += i2cr.o
obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o
obj-$(CONFIG_FSI_OCC) += fsi-occ.o
new file mode 100644
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#define I2CR_STATUS 0x30001
+#define I2CR_STATUS_ERR BIT(29)
+
+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, u32 *data)
+{
+ struct i2c_msg msgs[2];
+ __be32 response[2];
+ __be32 command;
+ int ret;
+
+ command = i2cr_get_command(address, false);
+ 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(response);
+ msgs[1].buf = (__u8 *)response;
+
+ ret = i2c_transfer(client->adapter, msgs, 2);
+ if (ret == 2) {
+ *data = be32_to_cpu(response[0]);
+ return 0;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ return -EIO;
+}
+
+static int i2cr_check_status(struct i2c_client *client)
+{
+ u32 status;
+ int ret;
+
+ ret = i2cr_transfer(client, I2CR_STATUS, &status);
+ if (ret)
+ return ret;
+
+ if (status & I2CR_STATUS_ERR)
+ return -EREMOTEIO;
+
+ return 0;
+}
+
+static int i2cr_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct i2c_client *client = context;
+ int ret;
+ u32 v;
+
+ ret = i2cr_transfer(client, (u32)reg, &v);
+ if (ret)
+ return ret;
+
+ *val = v;
+ return i2cr_check_status(client);
+}
+
+static int i2cr_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct i2c_client *client = context;
+ __be32 data[3];
+ int ret;
+
+ data[0] = i2cr_get_command((u32)reg, i2cr_check_parity((u32)val, false));
+ data[1] = cpu_to_be32((u32)val);
+
+ ret = i2c_master_send(client, (const char *)data, sizeof(data));
+ if (ret == sizeof(data))
+ return i2cr_check_status(client);
+
+ if (ret < 0)
+ return ret;
+
+ return -EIO;
+}
+
+const struct regmap_bus regmap_bus_i2cr = {
+ .reg_write = i2cr_reg_write,
+ .reg_read = i2cr_reg_read,
+};
+EXPORT_SYMBOL_GPL(regmap_bus_i2cr);
+
+MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
+MODULE_DESCRIPTION("IBM I2C Responder driver");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) IBM Corporation 2022 */
+
+#ifndef DRIVERS_I2CR_H
+#define DRIVERS_I2CR_H
+
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+extern const struct regmap_bus regmap_bus_i2cr;
+
+#define init_i2cr(client, config) \
+ __regmap_lockdep_wrapper(__regmap_init, #config, &(client)->dev, ®map_bus_i2cr, \
+ client, config)
+#define devm_init_i2cr(client, config) \
+ __regmap_lockdep_wrapper(__devm_regmap_init, #config, &(client)->dev, ®map_bus_i2cr, \
+ client, config)
+
+#endif /* DRIVERS_I2CR_H */