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 0000000000000000000000000000000000000000..386eec06cf08d9b7845fb06ec4154544fa0d8109 --- /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 551ab8a56329c67c2fb3fa9ccd15503efa178b5e..01e379f672fb89d2d4196e5c991aa529b338301e 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 00ab41f2fb286f8f5ea4ca24a9148f4e9810f532..44f351b3756070ac0f4cc0c3cb1f6a75775b2412 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 279a69f849fc61903fa2be076dc695bd8b920571..bdc47be1070da4aa871f88ee51d5330f716e59f7 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 4e25bd2625ea308541e0a571681f6baa471cf42b..abef1128fd5c54e671091d9bc216e50c17c7a238 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 16f9877bb517dcb5bbd1de5e9428e0e34baee63e..d64595e4ce29b3a95a11f257141dafb38b322bd9 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 0000000000000000000000000000000000000000..55cd0883a0d3c77de56780b687d29e733d252a5f --- /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 0000000000000000000000000000000000000000..972472805450cb11da2b6a0eefc2be34fd906eeb --- /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 0000000000000000000000000000000000000000..ef2fa649a0106022408f84bebfbeb7ef5082ae09 --- /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 0000000000000000000000000000000000000000..3c96f20081ebf6bf89d07ce3d623c3e986939d1c --- /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 0000000000000000000000000000000000000000..a8c8abf82692a6132ef27098ed32cbaf1609445b --- /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__ */