git: 83276e1f952d - main - linuxkpi: Add `i2c_adapter_quirks` support

From: Jean-Sébastien Pédron <dumbbell_at_FreeBSD.org>
Date: Mon, 13 Feb 2023 21:09:48 UTC
The branch main has been updated by dumbbell (ports committer):

URL: https://cgit.FreeBSD.org/src/commit/?id=83276e1f952d720c23092ea17e95c03fef8476b7

commit 83276e1f952d720c23092ea17e95c03fef8476b7
Author:     Jean-Sébastien Pédron <dumbbell@FreeBSD.org>
AuthorDate: 2023-02-08 17:57:32 +0000
Commit:     Jean-Sébastien Pédron <dumbbell@FreeBSD.org>
CommitDate: 2023-02-13 21:09:31 +0000

    linuxkpi: Add `i2c_adapter_quirks` support
    
    While here, also declare `I2C_CLASS_HWMON`.
    
    Reviewed by:    manu
    Approved by:    manu
    Differential Revision:  https://reviews.freebsd.org/D38535
---
 sys/compat/linuxkpi/common/include/linux/i2c.h |  25 ++++++
 sys/compat/linuxkpi/common/src/linux_i2c.c     | 113 +++++++++++++++++++++++++
 2 files changed, 138 insertions(+)

diff --git a/sys/compat/linuxkpi/common/include/linux/i2c.h b/sys/compat/linuxkpi/common/include/linux/i2c.h
index 365ab893fdfd..f24d282586f6 100644
--- a/sys/compat/linuxkpi/common/include/linux/i2c.h
+++ b/sys/compat/linuxkpi/common/include/linux/i2c.h
@@ -46,6 +46,7 @@
 #define	I2C_FUNC_SMBUS_BLOCK_PROC_CALL	0
 #define	I2C_FUNC_10BIT_ADDR		0
 
+#define	I2C_CLASS_HWMON	0x1
 #define	I2C_CLASS_DDC	0x8
 #define	I2C_CLASS_SPD	0x80
 
@@ -58,6 +59,7 @@ struct i2c_adapter {
 
 	const struct i2c_lock_operations *lock_ops;
 	const struct i2c_algorithm *algo;
+	const struct i2c_adapter_quirks *quirks;
 	void *algo_data;
 
 	int retries;
@@ -82,6 +84,29 @@ struct i2c_lock_operations {
 	void (*unlock_bus)(struct i2c_adapter *, unsigned int);
 };
 
+struct i2c_adapter_quirks {
+	uint64_t flags;
+	int max_num_msgs;
+	uint16_t max_write_len;
+	uint16_t max_read_len;
+	uint16_t max_comb_1st_msg_len;
+	uint16_t max_comb_2nd_msg_len;
+};
+
+#define	I2C_AQ_COMB			BIT(0)
+#define	I2C_AQ_COMB_WRITE_FIRST		BIT(1)
+#define	I2C_AQ_COMB_READ_SECOND		BIT(2)
+#define	I2C_AQ_COMB_SAME_ADDR		BIT(3)
+#define	I2C_AQ_COMB_WRITE_THEN_READ \
+    (I2C_AQ_COMB | I2C_AQ_COMB_WRITE_FIRST | \
+    I2C_AQ_COMB_READ_SECOND | I2C_AQ_COMB_SAME_ADDR)
+#define	I2C_AQ_NO_CLK_STRETCH		BIT(4)
+#define	I2C_AQ_NO_ZERO_LEN_READ		BIT(5)
+#define	I2C_AQ_NO_ZERO_LEN_WRITE	BIT(6)
+#define	I2C_AQ_NO_ZERO_LEN \
+    (I2C_AQ_NO_ZERO_LEN_READ | I2C_AQ_NO_ZERO_LEN_WRITE)
+#define	I2C_AQ_NO_REP_START		BIT(7)
+
 int lkpi_i2c_add_adapter(struct i2c_adapter *adapter);
 int lkpi_i2c_del_adapter(struct i2c_adapter *adapter);
 
diff --git a/sys/compat/linuxkpi/common/src/linux_i2c.c b/sys/compat/linuxkpi/common/src/linux_i2c.c
index 72ad37919d7f..253482b52550 100644
--- a/sys/compat/linuxkpi/common/src/linux_i2c.c
+++ b/sys/compat/linuxkpi/common/src/linux_i2c.c
@@ -167,6 +167,116 @@ lkpi_i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
 	return (0);
 }
 
+static int i2c_check_for_quirks(struct i2c_adapter *adapter,
+    struct iic_msg *msgs, uint32_t nmsgs)
+{
+	const struct i2c_adapter_quirks *quirks;
+	device_t dev;
+	int i, max_nmsgs;
+	bool check_len;
+
+	dev = adapter->dev.parent->bsddev;
+	quirks = adapter->quirks;
+	if (quirks == NULL)
+		return (0);
+
+	check_len = true;
+	max_nmsgs = quirks->max_num_msgs;
+
+	if (quirks->flags & I2C_AQ_COMB) {
+		max_nmsgs = 2;
+
+		if (nmsgs == 2) {
+			if (quirks->flags & I2C_AQ_COMB_WRITE_FIRST &&
+			    msgs[0].flags & IIC_M_RD) {
+				device_printf(dev,
+				    "Error: "
+				    "first combined message must be write\n");
+				return (EOPNOTSUPP);
+			}
+			if (quirks->flags & I2C_AQ_COMB_READ_SECOND &&
+			    !(msgs[1].flags & IIC_M_RD)) {
+				device_printf(dev,
+				    "Error: "
+				    "second combined message must be read\n");
+				return (EOPNOTSUPP);
+			}
+
+			if (quirks->flags & I2C_AQ_COMB_SAME_ADDR &&
+			    msgs[0].slave != msgs[1].slave) {
+				device_printf(dev,
+				    "Error: "
+				    "combined message must be use the same "
+				    "address\n");
+				return (EOPNOTSUPP);
+			}
+
+			if (quirks->max_comb_1st_msg_len &&
+			    msgs[0].len > quirks->max_comb_1st_msg_len) {
+				device_printf(dev,
+				    "Error: "
+				    "message too long: %hu > %hu max\n",
+				    msgs[0].len,
+				    quirks->max_comb_1st_msg_len);
+				return (EOPNOTSUPP);
+			}
+			if (quirks->max_comb_2nd_msg_len &&
+			    msgs[1].len > quirks->max_comb_2nd_msg_len) {
+				device_printf(dev,
+				    "Error: "
+				    "message too long: %hu > %hu max\n",
+				    msgs[1].len,
+				    quirks->max_comb_2nd_msg_len);
+				return (EOPNOTSUPP);
+			}
+
+			check_len = false;
+		}
+	}
+
+	if (max_nmsgs && nmsgs > max_nmsgs) {
+		device_printf(dev,
+		    "Error: too many messages: %d > %d max\n",
+		    nmsgs, max_nmsgs);
+		return (EOPNOTSUPP);
+	}
+
+	for (i = 0; i < nmsgs; i++) {
+		if (msgs[i].flags & IIC_M_RD) {
+			if (check_len && quirks->max_read_len &&
+			    msgs[i].len > quirks->max_read_len) {
+				device_printf(dev,
+				    "Error: "
+				    "message %d too long: %hu > %hu max\n",
+				    i, msgs[i].len, quirks->max_read_len);
+				return (EOPNOTSUPP);
+			}
+			if (quirks->flags & I2C_AQ_NO_ZERO_LEN_READ &&
+			    msgs[i].len == 0) {
+				device_printf(dev,
+				    "Error: message %d of length 0\n", i);
+				return (EOPNOTSUPP);
+			}
+		} else {
+			if (check_len && quirks->max_write_len &&
+			    msgs[i].len > quirks->max_write_len) {
+				device_printf(dev,
+				    "Message %d too long: %hu > %hu max\n",
+				    i, msgs[i].len, quirks->max_write_len);
+				return (EOPNOTSUPP);
+			}
+			if (quirks->flags & I2C_AQ_NO_ZERO_LEN_WRITE &&
+			    msgs[i].len == 0) {
+				device_printf(dev,
+				    "Error: message %d of length 0\n", i);
+				return (EOPNOTSUPP);
+			}
+		}
+	}
+
+	return (0);
+}
+
 static int
 lkpi_i2c_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
 {
@@ -177,6 +287,9 @@ lkpi_i2c_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
 	sc = device_get_softc(dev);
 	if (sc->adapter == NULL)
 		return (ENXIO);
+	ret = i2c_check_for_quirks(sc->adapter, msgs, nmsgs);
+	if (ret != 0)
+		return (ret);
 	linux_set_current(curthread);
 
 	linux_msgs = malloc(sizeof(struct i2c_msg) * nmsgs,