diff mbox series

[2/5] regmap: Add IBM I2CR support

Message ID 20221014220540.55570-3-eajames@linux.ibm.com
State New
Headers show
Series fsi: Add regmap and refactor sbefifo | expand

Commit Message

Eddie James Oct. 14, 2022, 10:05 p.m. UTC
Add regmap support for the IBM I2CR. The I2CR (I2C Responder) is an
I2C end-point device that provides access to an IBM POWER CFAM, which
has traditionally been accessed over FSI.

Signed-off-by: Eddie James <eajames@linux.ibm.com>
---
 drivers/base/regmap/Kconfig           |   4 +
 drivers/base/regmap/Makefile          |   1 +
 drivers/base/regmap/regmap-ibm-i2cr.c | 159 ++++++++++++++++++++++++++
 include/linux/regmap.h                |  35 ++++++
 4 files changed, 199 insertions(+)
 create mode 100644 drivers/base/regmap/regmap-ibm-i2cr.c
diff mbox series

Patch

diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig
index cd4bb642b9de..71af079a1b67 100644
--- a/drivers/base/regmap/Kconfig
+++ b/drivers/base/regmap/Kconfig
@@ -69,3 +69,7 @@  config REGMAP_SPI_AVMM
 config REGMAP_FSI
 	tristate
 	depends on FSI
+
+config REGMAP_IBM_I2CR
+	tristate
+	depends on I2C
diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile
index 6990de7ca9a9..871069903ad5 100644
--- a/drivers/base/regmap/Makefile
+++ b/drivers/base/regmap/Makefile
@@ -21,3 +21,4 @@  obj-$(CONFIG_REGMAP_I3C) += regmap-i3c.o
 obj-$(CONFIG_REGMAP_SPI_AVMM) += regmap-spi-avmm.o
 obj-$(CONFIG_REGMAP_MDIO) += regmap-mdio.o
 obj-$(CONFIG_REGMAP_FSI) += regmap-fsi.o
+obj-$(CONFIG_REGMAP_IBM_I2CR) += regmap-ibm-i2cr.o
diff --git a/drivers/base/regmap/regmap-ibm-i2cr.c b/drivers/base/regmap/regmap-ibm-i2cr.c
new file mode 100644
index 000000000000..799ad9e43a45
--- /dev/null
+++ b/drivers/base/regmap/regmap-ibm-i2cr.c
@@ -0,0 +1,159 @@ 
+// SPDX-License-Identifier: GPL-2.0
+//
+// Register map access API - IBM I2CR for POWER CFAM access
+//
+// Copyright 2022 IBM Corp
+//
+// Author: Eddie James <eajames@linux.ibm.com>
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "internal.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 regmap_ibm_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 regmap_ibm_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;
+}
+
+static const struct regmap_bus regmap_ibm_i2cr = {
+	.reg_write = regmap_ibm_i2cr_reg_write,
+	.reg_read = regmap_ibm_i2cr_reg_read,
+};
+
+static const struct regmap_bus *regmap_get_ibm_i2cr_bus(struct i2c_client *client,
+							const struct regmap_config *config)
+{
+	const struct regmap_bus *bus = NULL;
+
+	if (config->reg_bits == 32 && config->val_bits == 32)
+		bus = &regmap_ibm_i2cr;
+
+	return bus ?: ERR_PTR(-EOPNOTSUPP);
+}
+
+struct regmap *__regmap_init_ibm_i2cr(struct i2c_client *client,
+				      const struct regmap_config *config,
+				      struct lock_class_key *lock_key, const char *lock_name)
+{
+	const struct regmap_bus *bus = regmap_get_ibm_i2cr_bus(client, config);
+
+	if (IS_ERR(bus))
+		return ERR_CAST(bus);
+
+	return __regmap_init(&client->dev, bus, client, config, lock_key, lock_name);
+}
+EXPORT_SYMBOL_GPL(__regmap_init_ibm_i2cr);
+
+struct regmap *__devm_regmap_init_ibm_i2cr(struct i2c_client *client,
+					   const struct regmap_config *config,
+					   struct lock_class_key *lock_key, const char *lock_name)
+{
+	const struct regmap_bus *bus = regmap_get_ibm_i2cr_bus(client, config);
+
+	if (IS_ERR(bus))
+		return ERR_CAST(bus);
+
+	return __devm_regmap_init(&client->dev, bus, client, config, lock_key, lock_name);
+}
+EXPORT_SYMBOL_GPL(__devm_regmap_init_ibm_i2cr);
+
+MODULE_LICENSE("GPL");
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index e477112fb1c7..1821ebfa640c 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -633,6 +633,10 @@  struct regmap *__regmap_init_fsi(struct fsi_device *fsi_dev,
 				 const struct regmap_config *config,
 				 struct lock_class_key *lock_key,
 				 const char *lock_name);
+struct regmap *__regmap_init_ibm_i2cr(struct i2c_client *client,
+				      const struct regmap_config *config,
+				      struct lock_class_key *lock_key,
+				      const char *lock_name);
 
 struct regmap *__devm_regmap_init(struct device *dev,
 				  const struct regmap_bus *bus,
@@ -702,6 +706,10 @@  struct regmap *__devm_regmap_init_fsi(struct fsi_device *fsi_dev,
 				      const struct regmap_config *config,
 				      struct lock_class_key *lock_key,
 				      const char *lock_name);
+struct regmap *__devm_regmap_init_ibm_i2cr(struct i2c_client *client,
+					   const struct regmap_config *config,
+					   struct lock_class_key *lock_key,
+					   const char *lock_name);
 
 /*
  * Wrapper for regmap_init macros to include a unique lockdep key and name
@@ -942,6 +950,19 @@  bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg);
 	__regmap_lockdep_wrapper(__regmap_init_fsi, #config, fsi_dev,	\
 				 config)
 
+/**
+ * regmap_init_ibm_i2cr() - Initialise register map
+ *
+ * @client: Device that will be interacted with
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer to
+ * a struct regmap.
+ */
+#define regmap_init_ibm_i2cr(client, config)				\
+	__regmap_lockdep_wrapper(__regmap_init_ibm_i2cr, #config,	\
+				 client, config)
+
 /**
  * devm_regmap_init() - Initialise managed register map
  *
@@ -1185,6 +1206,20 @@  bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg);
 	__regmap_lockdep_wrapper(__devm_regmap_init_fsi, #config,	\
 				 fsi_dev, config)
 
+/**
+ * devm_regmap_init_ibm_i2cr() - Initialise managed register map
+ *
+ * @client: Device that will be interacted with
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a struct regmap.  The regmap will be automatically freed by the
+ * device management code.
+ */
+#define devm_regmap_init_ibm_i2cr(client, config)			\
+	__regmap_lockdep_wrapper(__devm_regmap_init_ibm_i2cr, #config,	\
+				 client, config)
+
 int regmap_mmio_attach_clk(struct regmap *map, struct clk *clk);
 void regmap_mmio_detach_clk(struct regmap *map);
 void regmap_exit(struct regmap *map);