From e78061cdc72ef067dc5e41266d949e6c394f0ed3 Mon Sep 17 00:00:00 2001
From: Alec Jackson <alec.jackson@polaris.com>
Date: Tue, 25 Feb 2025 16:32:35 -0800
Subject: [PATCH 1/2] Add New Pericom I2C GPIO Expander Driver

Adds a driver for the pericom i2c gpio hardware. Specifically this is for the 16 pin model and was developed for the pi4ioe5v6416. This supports the basic gpio subsystem functions, but there are some further operations that could be supported as part of the set config interface.
---
 drivers/gpio/gpio-pericom.c | 200 ++++++++++++++++++++++++++++++++++++
 1 file changed, 200 insertions(+)
 create mode 100644 drivers/gpio/gpio-pericom.c

diff --git a/drivers/gpio/gpio-pericom.c b/drivers/gpio/gpio-pericom.c
new file mode 100644
index 000000000000..9b4246e86307
--- /dev/null
+++ b/drivers/gpio/gpio-pericom.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the Diodes Pericom GPIO expander
+ * Originally Written for the PI4IOE5V6416
+ *
+ * Copyright (C) 2025 Polaris Ind
+ *
+ * Author Alec Jackson <alec@jatas.org>
+ */
+
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/types.h>
+
+struct diode_pc {
+	struct gpio_chip gc;
+	struct i2c_client *client;
+	u8 state[2];
+	u8 outputstate[2];
+};
+
+
+/*
+ *  HW Notes:
+ *  Get 0x00/0x01 Read input value
+ *	Set 0x02/0x03 output pin state
+ *	Set 0x46/0x47 pin drive up/down
+ *	Set 0x06/0x07 input/output state
+ *
+ *  The hardware uses 8 bit registers, this means when addressing the 8-15 gpio pins
+ *  the address needs to be changed and the offset adjusted
+*/
+
+// Define Control Registers
+#define INPUT_READ_BASE_REG		0x00
+#define OUTPUT_STATE_BASE_REG 	0x02
+#define INPUT_OUTPUT_BASE_REG 	0x06
+#define OUTPUT_DRIVE_BASE_REG 	0x46
+
+static int diode_pc_get_value(struct gpio_chip *gc, unsigned offset)
+{
+	struct diode_pc *pchip;
+	s32 data;
+
+	pchip = gpiochip_get_data(gc);
+
+	if (offset > 15)
+		return  -ERANGE;
+
+	data = i2c_smbus_read_byte_data(pchip->client, INPUT_READ_BASE_REG + (offset >> 3));
+
+	if (data < 0)
+		return data;
+
+	return data & BIT(offset % 8);
+}
+
+static void diode_pc_set_value(struct gpio_chip *gc, unsigned offset, int val)
+{
+	struct diode_pc *pchip;
+
+	pchip = gpiochip_get_data(gc);
+
+	if (offset > 15)
+		return;
+
+	// If val is positive the gpio is being turned on. This affects how the bit flip needs to be handled
+	if (val) {
+		pchip->outputstate[(offset >> 3)] = pchip->outputstate[(offset >> 3)] | BIT(offset % 8);
+		i2c_smbus_write_byte_data(pchip->client, OUTPUT_STATE_BASE_REG + (offset >> 3), pchip->outputstate[(offset >> 3)]);
+	} else {
+		pchip->outputstate[(offset >> 3)] = pchip->outputstate[(offset >> 3)] & ~BIT(offset % 8);
+		i2c_smbus_write_byte_data(pchip->client, OUTPUT_STATE_BASE_REG + (offset >> 3), pchip->outputstate[(offset >> 3)]);
+	}
+}
+
+static int diode_pc_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+	struct diode_pc *pchip;
+	s32 data;
+
+	pchip = gpiochip_get_data(gc);
+
+	if (offset > 15)
+		return -ERANGE;
+
+	data = i2c_smbus_read_byte_data(pchip->client, INPUT_OUTPUT_BASE_REG + (offset >> 3));
+
+	if (data < 0)
+		return data;
+
+	return data & BIT(offset % 8);
+}
+
+static int diode_pc_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+	struct diode_pc *pchip;
+	s32 retData;
+
+	pchip = gpiochip_get_data(gc);
+
+	if (offset > 15)
+		return -ERANGE;
+
+	pchip->state[(offset >> 3)] = pchip->state[(offset >> 3)] & ~BIT(offset % 8);
+
+	retData = i2c_smbus_write_byte_data(pchip->client, INPUT_OUTPUT_BASE_REG + (offset >> 3), ~pchip->state[(offset >> 3)]);
+	retData = i2c_smbus_write_byte_data(pchip->client, OUTPUT_DRIVE_BASE_REG + (offset >> 3), pchip->state[(offset >> 3)]);
+
+	return retData;
+}
+
+static int diode_pc_direction_output(struct gpio_chip *gc, unsigned int offset, int val)
+{
+	struct diode_pc *pchip;
+	s32 retData;
+
+	pchip = gpiochip_get_data(gc);
+
+	if (offset > 15)
+		return -ERANGE;
+
+	// If val is positive the gpio is being turned on. This affects how the bit flip needs to be handled
+	if (val) {
+		pchip->outputstate[(offset >> 3)] = pchip->outputstate[(offset >> 3)] | BIT(offset % 8);
+		i2c_smbus_write_byte_data(pchip->client, OUTPUT_STATE_BASE_REG + (offset >> 3), pchip->outputstate[(offset >> 3)]);
+	} else {
+		pchip->outputstate[(offset >> 3)] = pchip->outputstate[(offset >> 3)] & ~BIT(offset % 8);
+		i2c_smbus_write_byte_data(pchip->client, OUTPUT_STATE_BASE_REG + (offset >> 3), pchip->outputstate[(offset >> 3)]);
+	}
+
+	pchip->state[(offset >> 3)] = pchip->state[(offset >> 3)] | BIT(offset % 8);
+
+	retData = i2c_smbus_write_byte_data(pchip->client, INPUT_OUTPUT_BASE_REG + (offset >> 3), ~pchip->state[(offset >> 3)]);
+	retData = i2c_smbus_write_byte_data(pchip->client, OUTPUT_DRIVE_BASE_REG + (offset >> 3), pchip->state[(offset >> 3)]);
+
+	return retData;
+}
+
+static int diode_pc_probe(struct i2c_client *client)
+{
+
+	struct diode_pc *pchip;
+	int returncode;
+
+	dev_dbg(&client->dev, "probe start\n");
+
+	pchip = devm_kzalloc(&client->dev, sizeof(*pchip), GFP_KERNEL);
+	if (pchip == NULL)
+		return -ENOMEM;
+
+	pchip->gc.parent = &client->dev;
+	pchip->gc.label = dev_name(&client->dev);
+	pchip->gc.owner = THIS_MODULE;
+	pchip->gc.get_direction = diode_pc_get_direction;
+	pchip->gc.direction_input = diode_pc_direction_input;
+	pchip->gc.direction_output = diode_pc_direction_output;
+	pchip->gc.get = diode_pc_get_value;
+	pchip->gc.set = diode_pc_set_value;
+	pchip->client = client;
+
+	// Initial driver is for a 16 pin controller
+	// this could be changed to detect or DT defined for other pericom controllers
+	pchip->gc.ngpio = 16;
+
+	// Anything but negative (dynamic allocation) is deprecated
+	pchip->gc.base = -1;
+
+	i2c_set_clientdata(client, pchip);
+	returncode = devm_gpiochip_add_data(&client->dev, &pchip->gc, pchip);
+
+	dev_info(&client->dev, "registered pericom GPIO expander\n");
+
+	return returncode;
+}
+ 
+static const struct i2c_device_id diode_pc_id[] = {
+	{ "diode-pc", },
+	{},
+};
+
+static const struct of_device_id diode_pc_dt_ids[] = {
+	{ .compatible = "diode,pi4io5v6416" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, diode_pc_dt_ids);
+
+static struct i2c_driver diode_pc_driver = {
+	.driver = {
+		.name = "diode_pc",
+		.of_match_table = diode_pc_dt_ids,
+	},
+	.probe = diode_pc_probe,
+	.id_table = diode_pc_id,
+};
+module_i2c_driver(diode_pc_driver);
+
+MODULE_AUTHOR("Alec Jackson <alec@jatas.org");
+MODULE_DESCRIPTION("GPIO expander driver for the Diodes Pericom 16 bit with i2c interface");
+MODULE_LICENSE("GPL v2");
-- 
2.25.1

