From ef5aecd4636bc042ccc4e3cafdec5514cee2ded2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20H=C3=B6ppner?= <jonas.hoeppner@garz-fricke.com>
Date: Wed, 29 Mar 2023 11:49:20 +0200
Subject: [PATCH] drivers:staging:trizeps8-mcu: Add mfd and gpio driver for the
 trizeps8mcu

The trizeps modules may be equipped with an MCU that exposes different
features like gpio and touch.
This driver is based on kuk_tr8mcu_touch.c but splits up into an mfd
part and implements the gpio part. Further features are not yet
available.

BCS 746-00127
---
 .../bindings/mfd/seco,trizeps8-mcu.txt        | 102 +++++++
 .../boot/dts/seconorth/trizeps8plus.dtsi      |  31 ++
 drivers/staging/Kconfig                       |   2 +
 drivers/staging/Makefile                      |   1 +
 drivers/staging/gpio/Kconfig                  |  11 +-
 drivers/staging/gpio/Makefile                 |   1 +
 drivers/staging/gpio/gpio-trizeps8mcu.c       | 224 +++++++++++++++
 drivers/staging/mfd/Kconfig                   |   7 +
 drivers/staging/mfd/Makefile                  |   6 +
 drivers/staging/mfd/seco-trizeps8mcu.c        | 268 ++++++++++++++++++
 include/linux/mfd/seco-trizeps8mcu.h          |  57 ++++
 11 files changed, 708 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mfd/seco,trizeps8-mcu.txt
 create mode 100644 drivers/staging/gpio/gpio-trizeps8mcu.c
 create mode 100644 drivers/staging/mfd/Kconfig
 create mode 100644 drivers/staging/mfd/Makefile
 create mode 100644 drivers/staging/mfd/seco-trizeps8mcu.c
 create mode 100644 include/linux/mfd/seco-trizeps8mcu.h

diff --git a/Documentation/devicetree/bindings/mfd/seco,trizeps8-mcu.txt b/Documentation/devicetree/bindings/mfd/seco,trizeps8-mcu.txt
new file mode 100644
index 0000000000000..386eec06cf08d
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/seco,trizeps8-mcu.txt
@@ -0,0 +1,102 @@
+* ROHM BD70528 Power Management Integrated Circuit bindings
+
+BD70528MWV is an ultra-low quiescent current general purpose, single-chip,
+power management IC for battery-powered portable devices. The IC
+integrates 3 ultra-low current consumption buck converters, 3 LDOs and 2
+LED Drivers. Also included are 4 GPIOs, a real-time clock (RTC), a 32kHz
+clock gate, high-accuracy VREF for use with an external ADC, flexible
+dual-input power path, 10 bit SAR ADC for battery temperature monitor and
+1S battery charger with scalable charge currents.
+
+Required properties:
+ - compatible		: Should be "rohm,bd70528"
+ - reg			: I2C slave address.
+ - interrupts		: The interrupt line the device is connected to.
+ - interrupt-controller	: To indicate BD70528 acts as an interrupt controller.
+ - #interrupt-cells	: Should be 2. Usage is compliant to the 2 cells
+			  variant of ../interrupt-controller/interrupts.txt
+ - gpio-controller	: To indicate BD70528 acts as a GPIO controller.
+ - #gpio-cells		: Should be 2. The first cell is the pin number and
+			  the second cell is used to specify flags. See
+			  ../gpio/gpio.txt for more information.
+ - #clock-cells		: Should be 0.
+ - regulators:		: List of child nodes that specify the regulators.
+			  Please see ../regulator/rohm,bd70528-regulator.txt
+
+Optional properties:
+ - clock-output-names	: Should contain name for output clock.
+
+Example:
+/* External oscillator */
+osc: oscillator {
+	compatible = "fixed-clock";
+	#clock-cells = <1>;
+	clock-frequency  = <32768>;
+	clock-output-names = "osc";
+};
+
+pmic: pmic@4b {
+	compatible = "rohm,bd70528";
+	reg = <0x4b>;
+	interrupt-parent = <&gpio1>;
+	interrupts = <29 IRQ_TYPE_LEVEL_LOW>;
+	clocks = <&osc 0>;
+	#clock-cells = <0>;
+	clock-output-names = "bd70528-32k-out";
+	#gpio-cells = <2>;
+	gpio-controller;
+	interrupt-controller;
+	#interrupt-cells = <2>;
+
+	regulators {
+		buck1: BUCK1 {
+			regulator-name = "buck1";
+			regulator-min-microvolt = <1200000>;
+			regulator-max-microvolt = <3400000>;
+			regulator-boot-on;
+			regulator-ramp-delay = <125>;
+		};
+		buck2: BUCK2 {
+			regulator-name = "buck2";
+			regulator-min-microvolt = <1200000>;
+			regulator-max-microvolt = <3300000>;
+			regulator-boot-on;
+			regulator-ramp-delay = <125>;
+		};
+		buck3: BUCK3 {
+			regulator-name = "buck3";
+			regulator-min-microvolt = <800000>;
+			regulator-max-microvolt = <1800000>;
+			regulator-boot-on;
+			regulator-ramp-delay = <250>;
+		};
+		ldo1: LDO1 {
+			regulator-name = "ldo1";
+			regulator-min-microvolt = <1650000>;
+			regulator-max-microvolt = <3300000>;
+			regulator-boot-on;
+		};
+		ldo2: LDO2 {
+			regulator-name = "ldo2";
+			regulator-min-microvolt = <1650000>;
+			regulator-max-microvolt = <3300000>;
+			regulator-boot-on;
+		};
+
+		ldo3: LDO3 {
+			regulator-name = "ldo3";
+			regulator-min-microvolt = <1650000>;
+			regulator-max-microvolt = <3300000>;
+		};
+		led_ldo1: LED_LDO1 {
+			regulator-name = "led_ldo1";
+			regulator-min-microvolt = <200000>;
+			regulator-max-microvolt = <300000>;
+		};
+		led_ldo2: LED_LDO2 {
+			regulator-name = "led_ldo2";
+			regulator-min-microvolt = <200000>;
+			regulator-max-microvolt = <300000>;
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/seconorth/trizeps8plus.dtsi b/arch/arm64/boot/dts/seconorth/trizeps8plus.dtsi
index 551ab8a56329c..01e379f672fb8 100644
--- a/arch/arm64/boot/dts/seconorth/trizeps8plus.dtsi
+++ b/arch/arm64/boot/dts/seconorth/trizeps8plus.dtsi
@@ -758,6 +758,33 @@ mcutouch: kuk_tr8mcu_touch@10 { /* Kinetis MCU and touch-controller */
 		linux,wakeup;
 	};
 #endif
+	tr8mcu: tr8mcu@10 { /* Kinetis MCU and touch-controller */
+		compatible = "seco,tr8mcu";
+		reg = <0x10>;
+
+		pinctrl-0 = <&pinctrl_tr8mcu>;
+		interrupt-parent = <&gpio1>;
+		interrupts = <10 GPIO_ACTIVE_LOW>;
+
+		status = "okay";
+
+		gpio_mcu: tr8mcu-gpios {
+			compatible = "seco,tr8mcu-gpio";
+			status = "okay";
+			gpio-controller;
+			#gpio-cells = <2>;
+
+			// Map the 'SODIMM' offsets according to
+			// https://secogroup.atlassian.net/wiki/x/KQE6Vg
+			// to the gpiochipX offset 0-16
+			gpio-mapping = < 2 4 6 8 14 16 18 20 87 75 99 251>;
+
+			gpio-line-names =
+				"SPIN2", "SPIN4", "SPIN6", "SPIN8",
+				"SPIN14", "SPIN16", "SPIN18", "SPIN20",
+				"SPIN87", "SPIN75", "SPIN99", "nPCIE_RESET";
+		};
+	};
 };
 
 &flexcan1 {
@@ -1038,6 +1065,10 @@ pinctrl_restouch: restouchgrp {
 		fsl,pins = < MCU_INT_GPIO       PAD_GPIO >; /* MCU Interrupt */
 	};
 
+	pinctrl_tr8mcu: tr8mcugrp {
+		fsl,pins = < MCU_INT_GPIO       PAD_GPIO >; /* MCU Interrupt */
+	};
+
 #if 0
 	pinctrl_i2c3_sn65dsi84_en: sn65dsi84_iogrp {
 		fsl,pins = < LVDS_EN_GPIO  PAD_GPIO  >; /* Myon internal LVDS Enable and external Pin0-72 */
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 00ab41f2fb286..44f351b375607 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -112,4 +112,6 @@ source "drivers/staging/gpio/Kconfig"
 
 source "drivers/staging/gpu/drm/Kconfig"
 
+source "drivers/staging/mfd/Kconfig"
+
 endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 279a69f849fc6..bdc47be1070da 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -46,3 +46,4 @@ obj-$(CONFIG_WFX)		+= wfx/
 obj-$(CONFIG_FSL_PPFE)		+= fsl_ppfe/
 obj-y				+= gpio/
 obj-y				+= gpu/drm/
+obj-y				+= mfd/
diff --git a/drivers/staging/gpio/Kconfig b/drivers/staging/gpio/Kconfig
index 4e25bd2625ea3..abef1128fd5c5 100644
--- a/drivers/staging/gpio/Kconfig
+++ b/drivers/staging/gpio/Kconfig
@@ -2,13 +2,20 @@
 #
 # GPIO infrastructure and drivers
 #
-menu "I2C GPIO expanders"
-	depends on I2C
+menu "GPIO expanders"
 
 config GPIO_PI4IOE5V6408
 	tristate "Pericom PI4IOE5V6408 I2C GPIO Expander"
 	select REGMAP_I2C
+	depends on I2C
 	help
 	  Say yes here to enable the Pericom PI4IOE5V6408 GPIO Expander
 
+config GPIO_TRIZEPS8_MCU
+	tristate "Trizeps8 MCU GPIO Expander"
+	depends on MFD_SECO_TRIZEPS8_MCU
+	help
+	  Say yes here to enable the Trizeps8 MCU based GPIO Expander
+
+
 endmenu
diff --git a/drivers/staging/gpio/Makefile b/drivers/staging/gpio/Makefile
index 16f9877bb517d..d64595e4ce29b 100644
--- a/drivers/staging/gpio/Makefile
+++ b/drivers/staging/gpio/Makefile
@@ -4,3 +4,4 @@
 #
 
 obj-$(CONFIG_GPIO_PI4IOE5V6408)	+= gpio-pi4ioe5v6408.o
+obj-$(CONFIG_GPIO_TRIZEPS8_MCU)	+= gpio-trizeps8mcu.o
diff --git a/drivers/staging/gpio/gpio-trizeps8mcu.c b/drivers/staging/gpio/gpio-trizeps8mcu.c
new file mode 100644
index 0000000000000..55cd0883a0d3c
--- /dev/null
+++ b/drivers/staging/gpio/gpio-trizeps8mcu.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2018 ROHM Semiconductors
+// gpio-tr8mcu.c ROHM TR8MCUMWV gpio driver
+
+#include <linux/gpio/driver.h>
+#include <linux/mfd/seco-trizeps8mcu.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+struct tr8mcu_gpio {
+	struct tr8mcu		*parent;
+	struct gpio_chip	chip;
+	const int			*gpio_to_pin;
+
+	//struct irq_chip		irqchip;
+	//struct mutex		lock;		/* protect 'out' */
+	//unsigned		out;		/* software latch */
+	//unsigned		status;		/* current status */
+	//unsigned int		irq_parent;
+	//unsigned		irq_enabled;	/* enabled irqs */
+};
+
+#define NGPIOS 11
+static const int default_gpio_to_pin[NGPIOS] = {
+/*  0  1  2  3   4   5   6   7   8   9  10 */
+	2, 4, 6, 8, 14, 16, 18, 20, 87, 97, 99
+};
+
+static int tr8mcu_get_pin(struct gpio_chip *chip, unsigned int offset)
+{
+	struct tr8mcu_gpio *tr8mcu_gpio = gpiochip_get_data(chip);
+	struct device * dev = tr8mcu_gpio->chip.parent;
+	int pin;
+
+	if( offset > tr8mcu_gpio->chip.ngpio) goto err;
+
+	// Map gpio 0-X to the MCU's pins
+	pin = tr8mcu_gpio->gpio_to_pin[offset];
+	if( pin == -1 ) goto err;
+
+	dev_info(dev, "Mapped offset %d to pin %d\n", offset, pin);
+	return pin;
+err:
+	dev_err(dev, "Offset %d is not mapped\n", offset);
+	return -ENOENT;
+
+}
+
+static int tr8mcu_set_direction(struct gpio_chip *chip, unsigned int offset,
+								int direction, int value)
+{
+	struct tr8mcu_gpio *tr8mcu_gpio = gpiochip_get_data(chip);
+	struct tr8mcu *tr8mcu = tr8mcu_gpio->parent;
+	struct device * dev = tr8mcu_gpio->chip.parent;
+	int pin, ret;
+	u32 data;
+
+	dev_info(dev, "%s %d: dir %d offset %d value %d\n",
+			 __func__, __LINE__, direction, offset, value);
+
+	pin = tr8mcu_get_pin( chip, offset);
+	if( pin < 0) return pin;
+
+	data = pin;
+
+	//  1; /* Alt1=Input  */
+	// 21; /* Alt1-Output High */
+	// 20; /* Alt1-Output Low  */
+	if( direction == GPIO_LINE_DIRECTION_IN)
+		data |= ( 1) << 8;
+	else // GPIO_LINE_DIRECTION_OUT
+		data |= ( 20 + ( !! value )) << 8;
+
+	ret = tr8mcu_write(tr8mcu, TR8MCU_REG_PIN_CONFIG, 2, data);
+	if(ret)
+		dev_err(dev, "Failed to set direction %s for pin %d level %d\n",
+				  direction == GPIO_LINE_DIRECTION_IN ? "input" : "output",
+				  offset, value);
+
+	return ret;
+}
+
+static int tr8mcu_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+	return tr8mcu_set_direction(chip, offset, GPIO_LINE_DIRECTION_IN, 0);
+}
+
+static int tr8mcu_direction_output(struct gpio_chip *chip, unsigned int offset,
+				    int value)
+{
+	return tr8mcu_set_direction(chip, offset, GPIO_LINE_DIRECTION_OUT, value);
+}
+
+
+static void tr8mcu_gpio_set(struct gpio_chip *chip, unsigned int offset,
+			     int value)
+{
+	struct tr8mcu_gpio *tr8mcu_gpio = gpiochip_get_data(chip);
+	struct device * dev = tr8mcu_gpio->chip.parent;
+	int ret;
+
+	dev_info(dev, "%s %d: offset %d value %d\n",
+			 __func__, __LINE__, offset, value);
+	// From kuk_tr8mci_touch driver
+	// There seems to be a bug in the firmware of the so direction output is
+	// used instead
+	ret = tr8mcu_direction_output(chip, offset, value);
+
+}
+
+static int tr8mcu_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct tr8mcu_gpio *tr8mcu_gpio = gpiochip_get_data(chip);
+	struct tr8mcu *tr8mcu = tr8mcu_gpio->parent;
+	struct device * dev = tr8mcu_gpio->chip.parent;
+	int pin, ret;
+	u32 data;
+
+	dev_info(dev, "%s %d: offset %d \n",
+			 __func__, __LINE__, offset);
+
+	pin = tr8mcu_get_pin( chip, offset);
+	if( pin < 0) return pin;
+
+	ret = tr8mcu_read_with_param(tr8mcu, TR8MCU_REG_PIN_GET, pin, 1, &data);
+
+	dev_info(dev, "%s %d: offset %d pin %d, level %d, ret %d\n",
+			 __func__, __LINE__, offset, pin, data, ret);
+
+	if (ret) {
+		dev_err(dev, "Unable to fetch MCU-pin: %d err: %d\n", pin, ret);
+		return 0;
+	}
+	return data;
+}
+
+static int tr8mcu_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct tr8mcu_gpio *tr8mcu_gpio;
+	struct tr8mcu *tr8mcu;
+	const struct device_node * np = pdev->dev.of_node;
+	int ret, count;
+	int *gpio_map;
+
+	dev_info(dev, "%s %d\n", __func__, __LINE__);
+
+	tr8mcu = dev_get_drvdata(pdev->dev.parent);
+	if (!tr8mcu) {
+		dev_err(dev, "No MFD driver data\n");
+		return -EINVAL;
+	}
+
+	tr8mcu_gpio = devm_kzalloc(dev, sizeof(*tr8mcu_gpio),
+			      GFP_KERNEL);
+	if (!tr8mcu_gpio)
+		return -ENOMEM;
+
+	tr8mcu_gpio->parent = tr8mcu;
+	tr8mcu_gpio->chip.parent = dev;
+	tr8mcu_gpio->chip.label = "tr8mcu-gpio";
+	tr8mcu_gpio->chip.owner = THIS_MODULE;
+	tr8mcu_gpio->chip.can_sleep = true;
+	tr8mcu_gpio->chip.direction_input = tr8mcu_direction_input;
+	tr8mcu_gpio->chip.direction_output = tr8mcu_direction_output;
+	tr8mcu_gpio->chip.get = tr8mcu_gpio_get;
+	tr8mcu_gpio->chip.set = tr8mcu_gpio_set;
+	tr8mcu_gpio->chip.base = -1;
+
+	count = of_property_count_u32_elems(np, "gpio-mapping");
+	if(count > 0)
+	{
+		dev_info(dev, "%d gpios configured\n", count);
+		gpio_map = devm_kzalloc(dev, count * sizeof(int), GFP_KERNEL);
+		if(!gpio_map){
+			dev_err(dev, "Failed to allocate memory.");
+			return -ENOMEM;
+		}
+
+		if(of_property_read_u32_array(np, "gpio-mapping", gpio_map, count))
+		{
+			dev_err(dev, "Failed read devicetree property.");
+			return -ENOENT;
+		}
+
+		tr8mcu_gpio->gpio_to_pin = gpio_map;
+		tr8mcu_gpio->chip.ngpio = count;
+
+	}
+	else{
+		dev_info(dev, "Mapping for gpios not configured, using default\n");
+		tr8mcu_gpio->chip.ngpio = NGPIOS;
+		tr8mcu_gpio->gpio_to_pin = default_gpio_to_pin;
+	}
+
+	ret = devm_gpiochip_add_data(dev, &tr8mcu_gpio->chip,
+				     tr8mcu_gpio);
+	if (ret)
+		dev_err(dev, "gpio_init: Failed to add tr8mcu-gpios\n");
+
+	return ret;
+}
+
+static const struct of_device_id tr8mcu_gpio_of_match[] = {
+	{ .compatible = "seco,tr8mcu-gpio", },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, tr8mcu_gpio_of_match);
+
+static struct platform_driver tr8mcu_gpio = {
+	.driver = {
+		.name = "tr8mcu-gpios",
+		.of_match_table = tr8mcu_gpio_of_match,
+	},
+	.probe = tr8mcu_gpio_probe,
+};
+
+module_platform_driver(tr8mcu_gpio);
+
+MODULE_AUTHOR("Jonas Höppner <jonas.hoeppner@seco.com>");
+MODULE_DESCRIPTION("Trizeps8 MCU driver gpio");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:tr8mcu-gpio");
diff --git a/drivers/staging/mfd/Kconfig b/drivers/staging/mfd/Kconfig
new file mode 100644
index 0000000000000..972472805450c
--- /dev/null
+++ b/drivers/staging/mfd/Kconfig
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+menuconfig MFD_SECO_TRIZEPS8_MCU
+	bool "SECO Trizeps8 MCU Multifunction device driver"
+	help
+	  Say yes here to enable the Trizeps8 MCU Multifunction device driver
+
diff --git a/drivers/staging/mfd/Makefile b/drivers/staging/mfd/Makefile
new file mode 100644
index 0000000000000..ef2fa649a0106
--- /dev/null
+++ b/drivers/staging/mfd/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for GPIO infrastructure and drivers
+#
+
+obj-$(CONFIG_MFD_SECO_TRIZEPS8_MCU)	+= seco-trizeps8mcu.o
diff --git a/drivers/staging/mfd/seco-trizeps8mcu.c b/drivers/staging/mfd/seco-trizeps8mcu.c
new file mode 100644
index 0000000000000..3c96f20081ebf
--- /dev/null
+++ b/drivers/staging/mfd/seco-trizeps8mcu.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// Copyright (C) 2023 SECO Nothern Europe
+//
+//
+// Multifunction device driver for Trizeps8 MCU
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/seco-trizeps8mcu.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/types.h>
+
+struct tr8mcu {
+	struct i2c_client * client;
+	struct mfd_cell * mfd_cells;
+};
+
+/*== Read Write =======================================================*/
+
+static int tr8mcu_readwrite(struct i2c_client *client,
+				   u16 wr_len, u8 *wr_buf,
+				   u16 rd_len, u8 *rd_buf)
+{
+	//struct device dev = client->dev;
+	struct i2c_msg wrmsg[2];
+	int i = 0;
+	int ret;
+	// TODO do we need a mutex here 
+
+	if (wr_len) {
+		wrmsg[i].addr  = client->addr;
+		wrmsg[i].flags = 0;
+		wrmsg[i].len = wr_len;
+		wrmsg[i].buf = wr_buf;
+		i++;
+	}
+	if (rd_len) {
+		wrmsg[i].addr  = client->addr;
+		wrmsg[i].flags = I2C_M_RD;
+		wrmsg[i].len = rd_len;
+		wrmsg[i].buf = rd_buf;
+		i++;
+	}
+
+	dev_info(&client->dev, "%s %d: write buf: 0x%02X, 0x%02X, 0x%02X\n",
+			 __func__, __LINE__, wr_buf[0], wr_buf[1], wr_buf[2]);
+
+	ret = i2c_transfer(client->adapter, wrmsg, i);
+	if (ret < 0)
+		return ret;
+	if (ret != i)
+		return -EIO;
+	if(rd_buf)
+			dev_info(&client->dev, "%s %d: read buf: 0x%02X, 0x%02X, 0x%02X\n",
+			 __func__, __LINE__, rd_buf[0], rd_buf[1], rd_buf[2]);
+
+	return 0;
+}
+
+int tr8mcu_read_with_param(struct tr8mcu * tr8mcu, u8 reg, u8 param, int len,
+						   u32 * data)
+{
+	struct i2c_client *client = tr8mcu->client;
+	u32 readdata = 0, mask;
+	u8 writedata[2];
+	int error;
+
+	dev_info(&client->dev, "%s %d: 0x%02X, len %d\n",
+			 __func__, __LINE__, reg, len);
+
+	if( len == 0 || len > 4 )
+	{
+		dev_err_ratelimited(&client->dev,
+				"tr8mcu_write: reading %d bytes is not supported\n", len);
+		return -EPERM;
+	}
+
+	writedata[0] = reg;
+	writedata[1] = param;
+
+	error = tr8mcu_readwrite( client, 2, writedata, len, (u8*)&readdata);
+	if (error) {
+		dev_err_ratelimited(&client->dev,
+				"tr8mcu_read: Unable to fetch %d bytes, error: %d\n",
+				len, error);
+		return error;
+	}
+	dev_info(&client->dev, "%s %d: 0x%02X, len %d: 0x%02X\n",
+			 __func__, __LINE__, reg, len, readdata);
+
+	// Mask out unread bits
+	mask = 0xFFFFFFFF >> ( (4 - len) * 8 );
+	readdata &= mask;
+
+	dev_info(&client->dev, "%s %d: 0x%02X, len %d: 0x%02X, 0x%08X\n",
+			 __func__, __LINE__, reg, len, readdata, mask);
+	*data = readdata;
+	return 0;
+}
+
+int tr8mcu_read(struct tr8mcu * tr8mcu, u8 reg, int len, u32 * data)
+{
+	struct i2c_client *client = tr8mcu->client;
+	u32 readdata, mask;
+	int error;
+
+	dev_info(&client->dev, "%s %d: 0x%02X, len %d\n",
+			 __func__, __LINE__, reg, len);
+
+	if( len == 0 || len > 4 )
+	{
+		dev_err_ratelimited(&client->dev,
+				"tr8mcu_write: reading %d bytes is not supported\n", len);
+		return -EPERM;
+	}
+
+	error = tr8mcu_readwrite( client,
+					sizeof(reg), &reg,
+					len, (u8*)&readdata);
+	if (error) {
+		dev_err_ratelimited(&client->dev,
+				"tr8mcu_read: Unable to fetch %d bytes, error: %d\n",
+				len, error);
+		return error;
+	}
+	dev_info(&client->dev, "%s %d: 0x%02X, len %d: 0x%02X\n",
+			 __func__, __LINE__, reg, len, readdata);
+
+	// Mask out unread bits
+	mask = 0xFFFFFFFF >> ( (4 - len) * 8 );
+	readdata &= mask;
+
+	dev_info(&client->dev, "%s %d: 0x%02X, len %d: 0x%02X, 0x%08X\n",
+			 __func__, __LINE__, reg, len, readdata, mask);
+	*data = readdata;
+	return 0;
+}
+
+int tr8mcu_write(struct tr8mcu * tr8mcu, u8 reg, int len, u32 data)
+{
+	struct i2c_client *client = tr8mcu->client;
+	u8 buf[5];
+	u32 mask;
+	int i, error;
+
+	dev_info(&client->dev, "%s %d: Reg: 0x%02X, len %d, data 0x%02x\n",
+			 __func__, __LINE__, reg, len, data);
+	if( len == 0 || len > 4 )
+	{
+		dev_err_ratelimited(&client->dev,
+				"tr8mcu_write: writing %d bytes is not supported\n", len);
+		return EPERM;
+	}
+
+	buf[0] = reg;
+
+	// Mask out unread bits
+	mask = 0xFFFFFFFF >> ( (4 - len) * 8 );
+	data &= mask;
+
+	for( i = 0; i < 4; i++)
+		buf[1+i] = ( data >> ( 8 * i)) & 0xFF;
+
+	error = tr8mcu_readwrite( client, len + 1, buf, 0, NULL);
+	if (error) {
+		dev_err_ratelimited(&client->dev,
+				"tr8mcu_write: Unable to write %d bytes, error: %d\n",
+				len + 1, error);
+		return error;
+	}
+	return 0;
+}
+
+/*== Read Write =======================================================*/
+
+static int tr8mcu_i2c_probe(struct i2c_client *client,
+			     const struct i2c_device_id *id)
+{
+	struct tr8mcu *tr8mcu;
+	//struct regmap_irq_chip_data *irq_data;
+	int ret = 0;
+	u32 data;
+	int child_count = 0;
+	struct device_node *np, *child;
+
+	if (!client->irq) {
+		dev_err(&client->dev, "No IRQ configured\n");
+		return -EINVAL;
+	}
+
+	tr8mcu = devm_kzalloc(&client->dev, sizeof(struct tr8mcu), GFP_KERNEL);
+	if (!tr8mcu)
+		return -ENOMEM;
+
+	tr8mcu->client = client;
+	dev_set_drvdata(&client->dev, tr8mcu);
+
+	// Try to read out the ID to check if we find an MCU
+	if(tr8mcu_read(tr8mcu, TR8MCU_REG_ID, 1, &data) || data != 0x61)
+	{
+		dev_err(&client->dev, "Trizeps8 MCU: probe No MCU found!\n");
+		return -ENODEV;
+	}
+
+	np = client->dev.of_node;
+	child_count = of_get_available_child_count(np);
+	tr8mcu->mfd_cells = devm_kzalloc(&client->dev,
+				child_count * sizeof(struct mfd_cell), GFP_KERNEL);
+	dev_info(&client->dev, "%d child nodes found.\n", child_count);
+
+	for_each_available_child_of_node(np, child)
+	{
+		struct property *prop;
+		const char *cp = child->name, *compatible;
+		dev_info(&client->dev, "%s %d: Found child: %s\n",
+			 __func__, __LINE__, child->name);
+
+		/* TODO use compatible here instead of the name, currently breaks
+		 * everything */
+		prop = of_find_property(child, "compatible", NULL);
+		if(prop){
+			compatible = of_prop_next_string(prop, NULL);
+			dev_info(&client->dev, "%s %d: child: %s\n",
+			 __func__, __LINE__, compatible);
+			if(compatible) cp = compatible;
+			tr8mcu->mfd_cells->of_compatible = devm_kstrdup(&client->dev,
+						compatible, GFP_KERNEL);
+		}
+
+		tr8mcu->mfd_cells->name = devm_kstrdup(&client->dev, cp, GFP_KERNEL);
+	}
+
+	if(child_count)
+	{
+		ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO,
+				   tr8mcu->mfd_cells, child_count, NULL, 0, NULL);
+		if (ret)
+			dev_err(&client->dev, "Failed to create subdevices\n");
+	}
+
+
+	return ret;
+}
+
+static const struct of_device_id tr8mcu_of_match[] = {
+	{ .compatible = "seco,tr8mcu", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, tr8mcu_of_match);
+
+static struct i2c_driver tr8mcu_drv = {
+	.driver = {
+		.name = "seco-tr8mcu",
+		.of_match_table = tr8mcu_of_match,
+	},
+	.probe = &tr8mcu_i2c_probe,
+};
+
+module_i2c_driver(tr8mcu_drv);
+
+MODULE_AUTHOR("Jonas Höppner <jonas.hoeppner@seco.com>");
+MODULE_DESCRIPTION("Trizeps8 MCU driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/seco-trizeps8mcu.h b/include/linux/mfd/seco-trizeps8mcu.h
new file mode 100644
index 0000000000000..a8c8abf82692a
--- /dev/null
+++ b/include/linux/mfd/seco-trizeps8mcu.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2018 ROHM Semiconductors */
+
+#ifndef __LINUX_MFD_TR8MCU_H__
+#define __LINUX_MFD_TR8MCU_H__
+
+#include <linux/types.h>
+
+#define TR8MCU_REG_ID				0x01
+#define TR8MCU_REG_CONTROL			0x02
+#define TR8MCU_REG_CONFIG1			0x03
+#define TR8MCU_REG_PIN_CONFIG		0x04
+#define TR8MCU_REG_PIN_SET			0x05
+#define TR8MCU_REG_PIN_GET			0x06
+#define TR8MCU_REG_ADC_CONFIG		0x10
+#define TR8MCU_REG_ADC				0x11
+#define TR8MCU_REG_LSB				0x12
+#define TR8MCU_REG_MSB				0x13
+
+#define TR8MCU_REG_TOUCH_CONFIG		 0x20
+#define TR8MCU_REG_TOUCH_WAITTIME	 0x21
+#define TR8MCU_REG_TOUCH_XSCALE_LSB	 0x22
+#define TR8MCU_REG_TOUCH_XSCALE_MSB	 0x23
+#define TR8MCU_REG_TOUCH_YSCALE_LSB	 0x24
+#define TR8MCU_REG_TOUCH_YSCALE_MSB	 0x25
+#define TR8MCU_REG_TOUCH_XMIN_LSB	 0x26
+#define TR8MCU_REG_TOUCH_XMIN_MSB	 0x27
+#define TR8MCU_REG_TOUCH_YMIN_LSB	 0x28
+#define TR8MCU_REG_TOUCH_YMIN_MSB	 0x29
+#define TR8MCU_REG_TOUCH_XMAX_Z_LSB	 0x2A
+#define TR8MCU_REG_TOUCH_XMAX_Z_MSB	 0x2B
+#define TR8MCU_REG_TOUCH_YMAX_LSB	 0x2C
+#define TR8MCU_REG_TOUCH_YMAX_MSB	 0x2D
+#define TR8MCU_REG_TOUCH_AVERAGE	 0x2E
+
+#define TR8MCU_REG_CONFIG1_RESET2USBBOOT	0x01
+#define TR8MCU_REG_CONFIG1_SDVSEL			0x02
+
+
+struct tr8mcu;
+
+/* Read up to 4 bytes from the MCU
+   Returns 0 or a negative error code
+*/
+int tr8mcu_read(struct tr8mcu * tr8mcu, u8 reg, int len, u32 * data);
+
+/* Read up to 4 bytes from the MCU, specified by reg and param.
+   Returns 0 or a negative error code
+*/
+int tr8mcu_read_with_param(struct tr8mcu * tr8mcu, u8 reg, u8 param, int len,
+						   u32 * data);
+/* Write up to 4 bytes to the MCU
+   Returns 0 or a negative error code
+*/
+int tr8mcu_write(struct tr8mcu * tr8mcu, u8 reg, int len, u32 data);
+
+#endif /* __LINUX_MFD_TR8TR8MCU_H__ */
-- 
GitLab