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), ®, + 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