From patchwork Thu Apr 8 20:52:34 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sander Vanheule X-Patchwork-Id: 12192497 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E993BC433B4 for ; Thu, 8 Apr 2021 21:01:11 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id AB2D7611AD for ; Thu, 8 Apr 2021 21:01:11 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232345AbhDHVBW (ORCPT ); Thu, 8 Apr 2021 17:01:22 -0400 Received: from polaris.svanheule.net ([84.16.241.116]:60018 "EHLO polaris.svanheule.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232305AbhDHVBT (ORCPT ); Thu, 8 Apr 2021 17:01:19 -0400 Received: from terra.local.svanheule.net (unknown [IPv6:2a02:a03f:eaff:9f01:6fea:16c6:2e86:c69]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: sander@svanheule.net) by polaris.svanheule.net (Postfix) with ESMTPSA id 45EFD1ECD3A; Thu, 8 Apr 2021 22:52:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=svanheule.net; s=mail1707; t=1617915165; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=1HNkVs+fE0nRqWoxJ2FcJAWlbxwCmExLGCCRESK3LDM=; b=BQOidEbcOT/4k0hOP/F1nFpEt48nXyqtyQ4ej2uJ+cG+/VNcb3EdZ6j11OXT4LaC1YfjAN EbE38ByMxwkiNfqctu5NTiakv6EEki6OCdSDHNlpNqOccZvP1sCh7ZxLAF8/ZU42JzWxEw QsWsyRmw3BoyUilV0TMRAfmHA8sgoRAC+4e2jScAza6fDMAtET2ykgdOVFgzeepZq7r9wY /en+FRDQhL0opFMzgHlCQqOom7rclq79NV2Z/LZULoXPe1lpsTD/rw4wkqVMZzMVMni0Mw QymMJ4gVbcMRu9WcMu1Lz+T4LWycPeSUz+k1DP9ocVAKexoV9J410wgRcX/vNA== From: Sander Vanheule To: netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, Mark Brown , Greg Kroah-Hartman , "Rafael J . Wysocki" Cc: bert@biot.com, Sander Vanheule Subject: [RFC PATCH 1/2] regmap: add miim bus support Date: Thu, 8 Apr 2021 22:52:34 +0200 Message-Id: <489e8a2d22dc8a5aaa3600289669c3bf0a15ba19.1617914861.git.sander@svanheule.net> X-Mailer: git-send-email 2.30.2 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-State: RFC Basic support for MIIM bus access. Support only includes clause-22 register access, with 5-bit addresses, and 16-bit wide registers. Signed-off-by: Sander Vanheule --- drivers/base/regmap/Kconfig | 6 +++- drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-miim.c | 58 +++++++++++++++++++++++++++++++ include/linux/regmap.h | 36 +++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 drivers/base/regmap/regmap-miim.c diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index 50b1e2d06a25..5bcd9789284e 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -4,7 +4,7 @@ # subsystems should select the appropriate symbols. config REGMAP - default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SOUNDWIRE || REGMAP_SOUNDWIRE_MBQ || REGMAP_SCCB || REGMAP_I3C || REGMAP_SPI_AVMM) + default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SOUNDWIRE || REGMAP_SOUNDWIRE_MBQ || REGMAP_SCCB || REGMAP_I3C || REGMAP_SPI_AVMM || REGMAP_MIIM) select IRQ_DOMAIN if REGMAP_IRQ bool @@ -36,6 +36,10 @@ config REGMAP_W1 tristate depends on W1 +config REGMAP_MIIM + tristate + depends on MDIO_DEVICE + config REGMAP_MMIO tristate diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 33f63adb5b3d..d80920bd42ce 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_REGMAP_SOUNDWIRE_MBQ) += regmap-sdw-mbq.o obj-$(CONFIG_REGMAP_SCCB) += regmap-sccb.o obj-$(CONFIG_REGMAP_I3C) += regmap-i3c.o obj-$(CONFIG_REGMAP_SPI_AVMM) += regmap-spi-avmm.o +obj-$(CONFIG_REGMAP_MIIM) += regmap-miim.o diff --git a/drivers/base/regmap/regmap-miim.c b/drivers/base/regmap/regmap-miim.c new file mode 100644 index 000000000000..a560d2910417 --- /dev/null +++ b/drivers/base/regmap/regmap-miim.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +static int regmap_miim_read(void *context, unsigned int reg, unsigned int *val) +{ + struct mdio_device *mdio_dev = context; + int ret; + + ret = mdiobus_read(mdio_dev->bus, mdio_dev->addr, reg); + *val = ret & 0xffff; + + return ret < 0 ? ret : 0; +} + +static int regmap_miim_write(void *context, unsigned int reg, unsigned int val) +{ + struct mdio_device *mdio_dev = context; + + return mdiobus_write(mdio_dev->bus, mdio_dev->addr, reg, val); +} + +static const struct regmap_bus regmap_miim_bus = { + .reg_write = regmap_miim_write, + .reg_read = regmap_miim_read, +}; + +struct regmap *__regmap_init_miim(struct mdio_device *mdio_dev, + const struct regmap_config *config, struct lock_class_key *lock_key, + const char *lock_name) +{ + if (config->reg_bits != 5 || config->val_bits != 16) + return ERR_PTR(-ENOTSUPP); + + return __regmap_init(&mdio_dev->dev, ®map_miim_bus, mdio_dev, config, + lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__regmap_init_miim); + +struct regmap *__devm_regmap_init_miim(struct mdio_device *mdio_dev, + const struct regmap_config *config, struct lock_class_key *lock_key, + const char *lock_name) +{ + if (config->reg_bits != 5 || config->val_bits != 16) + return ERR_PTR(-ENOTSUPP); + + return __devm_regmap_init(&mdio_dev->dev, ®map_miim_bus, mdio_dev, + config, lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__devm_regmap_init_miim); + +MODULE_AUTHOR("Sander Vanheule "); +MODULE_DESCRIPTION("Regmap MIIM Module"); +MODULE_LICENSE("GPL v2"); + diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 2cc4ecd36298..f25630f793c3 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -27,6 +27,7 @@ struct device_node; struct i2c_client; struct i3c_device; struct irq_domain; +struct mdio_device; struct slim_device; struct spi_device; struct spmi_device; @@ -538,6 +539,10 @@ struct regmap *__regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__regmap_init_miim(struct mdio_device *mdio_dev, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name); struct regmap *__regmap_init_sccb(struct i2c_client *i2c, const struct regmap_config *config, struct lock_class_key *lock_key, @@ -594,6 +599,10 @@ struct regmap *__devm_regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__devm_regmap_init_miim(struct mdio_device *mdio_dev, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name); struct regmap *__devm_regmap_init_sccb(struct i2c_client *i2c, const struct regmap_config *config, struct lock_class_key *lock_key, @@ -697,6 +706,19 @@ int regmap_attach_dev(struct device *dev, struct regmap *map, __regmap_lockdep_wrapper(__regmap_init_i2c, #config, \ i2c, config) +/** + * regmap_init_miim() - Initialise register map + * + * @mdio_dev: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +#define regmap_init_miim(mdio_dev, config) \ + __regmap_lockdep_wrapper(__regmap_init_miim, #config, \ + mdio_dev, config) + /** * regmap_init_sccb() - Initialise register map * @@ -888,6 +910,20 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg); __regmap_lockdep_wrapper(__devm_regmap_init_i2c, #config, \ i2c, config) +/** + * devm_regmap_init_miim() - Initialise managed register map + * + * @mdio_dev: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct regmap. The regmap will be automatically freed by the + * device management code. + */ +#define devm_regmap_init_miim(mdio_dev, config) \ + __regmap_lockdep_wrapper(__devm_regmap_init_miim, #config, \ + mdio_dev, config) + /** * devm_regmap_init_sccb() - Initialise managed register map * From patchwork Thu Apr 8 20:52:35 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sander Vanheule X-Patchwork-Id: 12192501 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.9 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS, UNWANTED_LANGUAGE_BODY,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 126E1C433ED for ; Thu, 8 Apr 2021 21:01:16 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E043761179 for ; Thu, 8 Apr 2021 21:01:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232482AbhDHVBZ (ORCPT ); Thu, 8 Apr 2021 17:01:25 -0400 Received: from polaris.svanheule.net ([84.16.241.116]:60006 "EHLO polaris.svanheule.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232091AbhDHVBT (ORCPT ); Thu, 8 Apr 2021 17:01:19 -0400 X-Greylist: delayed 500 seconds by postgrey-1.27 at vger.kernel.org; Thu, 08 Apr 2021 17:01:18 EDT Received: from terra.local.svanheule.net (unknown [IPv6:2a02:a03f:eaff:9f01:6fea:16c6:2e86:c69]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: sander@svanheule.net) by polaris.svanheule.net (Postfix) with ESMTPSA id BC20C1ECD3B; Thu, 8 Apr 2021 22:52:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=svanheule.net; s=mail1707; t=1617915166; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=SS9GaPRDuLhxXWnxoBHuqXWDVS6xyMYDV/rd7M8lMyM=; b=N8xKJaf/nq2mBBZDefGV9nCV7IGC5LCG0wAvz2wWTQp8UM72wzWxkj4s9En51xwM2Uv0xX xl09qVDDZKIk5KgqZY7FSck3SUUVNbfXH/xs+wHrv5OyiGsFaK1UZ42kCubwmlNrTVbc/w r2CNP+urqc+hAMLyLDGLmC1+LDOWlOEqVw4QLpai0LEsFWX39S0yE6aV+IKHVuZAlxqULk FscRlYGj7mshxHbbpTn4RfEk1gNxDT33oXla5CG1Qas33mEirnEj0gBNRHcK/0EfJFYZHd A1JYlMRBO0OYY8mnE1x5Ax3L9tH509VdKgNcKQMA+p+2oPGwJEM2LL971CQuGw== From: Sander Vanheule To: netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, Mark Brown , Greg Kroah-Hartman , "Rafael J . Wysocki" Cc: bert@biot.com, Sander Vanheule Subject: [RFC PATCH 2/2] gpio: Add Realtek RTL8231 support Date: Thu, 8 Apr 2021 22:52:35 +0200 Message-Id: X-Mailer: git-send-email 2.30.2 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org X-Patchwork-State: RFC The RTL8231 GPIO and LED expander chip can be controlled via a MIIM or I2C bus. A regmap interface is used to allow for portable code. This patch only provides GPIO support, since this is the only known use, as found on commercial devices. Signed-off-by: Sander Vanheule --- drivers/gpio/Kconfig | 9 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-rtl8231.c | 404 ++++++++++++++++++++++++++++++++++++ 3 files changed, 414 insertions(+) create mode 100644 drivers/gpio/gpio-rtl8231.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 20cc0012a5ef..982c87855510 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -521,6 +521,15 @@ config GPIO_REG A 32-bit single register GPIO fixed in/out implementation. This can be used to represent any register as a set of GPIO signals. +config GPIO_RTL8231 + tristate "Realtek RTL8231 GPIO expander" + select REGMAP_I2C + select REGMAP_MIIM + select OF_MDIO + help + RTL8231 GPIO and LED expander support. + When built as a module, the module will be called rtl8231_expander. + config GPIO_SAMA5D2_PIOBU tristate "SAMA5D2 PIOBU GPIO support" depends on MFD_SYSCON diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 7ba71922817e..7f22f0e5430e 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -126,6 +126,7 @@ obj-$(CONFIG_GPIO_RDA) += gpio-rda.o obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o obj-$(CONFIG_GPIO_REALTEK_OTTO) += gpio-realtek-otto.o obj-$(CONFIG_GPIO_REG) += gpio-reg.o +obj-$(CONFIG_GPIO_RTL8231) += gpio-rtl8231.o obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o diff --git a/drivers/gpio/gpio-rtl8231.c b/drivers/gpio/gpio-rtl8231.c new file mode 100644 index 000000000000..e0dfee68c859 --- /dev/null +++ b/drivers/gpio/gpio-rtl8231.c @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* RTL8231 registers for LED control */ +#define RTL8231_FUNC0 0x00 +#define RTL8231_FUNC1 0x01 +#define RTL8231_PIN_MODE0 0x02 +#define RTL8231_PIN_MODE1 0x03 +#define RTL8231_PIN_HI_CFG 0x04 +#define RTL8231_GPIO_DIR0 0x05 +#define RTL8231_GPIO_DIR1 0x06 +#define RTL8231_GPIO_INVERT0 0x07 +#define RTL8231_GPIO_INVERT1 0x08 +#define RTL8231_GPIO_DATA0 0x1c +#define RTL8231_GPIO_DATA1 0x1d +#define RTL8231_GPIO_DATA2 0x1e + +#define RTL8231_READY_CODE_VALUE 0x37 +#define RTL8231_GPIO_DIR_IN 1 +#define RTL8231_GPIO_DIR_OUT 0 + +#define RTL8231_MAX_GPIOS 37 + +enum rtl8231_regfield { + RTL8231_FIELD_LED_START, + RTL8231_FIELD_READY_CODE, + RTL8231_FIELD_SOFT_RESET, + RTL8231_FIELD_PIN_MODE0, + RTL8231_FIELD_PIN_MODE1, + RTL8231_FIELD_PIN_MODE2, + RTL8231_FIELD_GPIO_DIR0, + RTL8231_FIELD_GPIO_DIR1, + RTL8231_FIELD_GPIO_DIR2, + RTL8231_FIELD_GPIO_DATA0, + RTL8231_FIELD_GPIO_DATA1, + RTL8231_FIELD_GPIO_DATA2, + RTL8231_FIELD_MAX +}; + +static const struct reg_field rtl8231_fields[RTL8231_FIELD_MAX] = { + [RTL8231_FIELD_LED_START] = REG_FIELD(RTL8231_FUNC0, 1, 1), + [RTL8231_FIELD_READY_CODE] = REG_FIELD(RTL8231_FUNC1, 4, 9), + [RTL8231_FIELD_SOFT_RESET] = REG_FIELD(RTL8231_PIN_HI_CFG, 15, 15), + [RTL8231_FIELD_PIN_MODE0] = REG_FIELD(RTL8231_PIN_MODE0, 0, 15), + [RTL8231_FIELD_PIN_MODE1] = REG_FIELD(RTL8231_PIN_MODE1, 0, 15), + [RTL8231_FIELD_PIN_MODE2] = REG_FIELD(RTL8231_PIN_HI_CFG, 0, 4), + [RTL8231_FIELD_GPIO_DIR0] = REG_FIELD(RTL8231_GPIO_DIR0, 0, 15), + [RTL8231_FIELD_GPIO_DIR1] = REG_FIELD(RTL8231_GPIO_DIR1, 0, 15), + [RTL8231_FIELD_GPIO_DIR2] = REG_FIELD(RTL8231_PIN_HI_CFG, 5, 9), + [RTL8231_FIELD_GPIO_DATA0] = REG_FIELD(RTL8231_GPIO_DATA0, 0, 15), + [RTL8231_FIELD_GPIO_DATA1] = REG_FIELD(RTL8231_GPIO_DATA1, 0, 15), + [RTL8231_FIELD_GPIO_DATA2] = REG_FIELD(RTL8231_GPIO_DATA2, 0, 4), +}; + +/** + * struct rtl8231_gpio_ctrl - Control data for an RTL8231 chip + * + * @gc: Associated gpio_chip instance + * @dev + * @fields + */ +struct rtl8231_gpio_ctrl { + struct gpio_chip gc; + struct device *dev; + struct regmap_field *fields[RTL8231_FIELD_MAX]; +}; + +static int rtl8231_pin_read(struct rtl8231_gpio_ctrl *ctrl, int base, int offset) +{ + int field = base + offset / 16; + int bit = offset % 16; + unsigned int v; + int err; + + err = regmap_field_read(ctrl->fields[field], &v); + + if (err) + return err; + + return !!(v & BIT(bit)); +} + +static int rtl8231_pin_write(struct rtl8231_gpio_ctrl *ctrl, int base, int offset, int val) +{ + int field = base + offset / 16; + int bit = offset % 16; + + return regmap_field_update_bits(ctrl->fields[field], BIT(bit), val << bit); +} + +static int rtl8231_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + struct rtl8231_gpio_ctrl *ctrl = gpiochip_get_data(gc); + + return rtl8231_pin_write(ctrl, RTL8231_FIELD_GPIO_DIR0, offset, RTL8231_GPIO_DIR_IN); +} + +static int rtl8231_direction_output(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct rtl8231_gpio_ctrl *ctrl = gpiochip_get_data(gc); + int err; + + err = rtl8231_pin_write(ctrl, RTL8231_FIELD_GPIO_DIR0, offset, RTL8231_GPIO_DIR_OUT); + if (err) + return err; + + return rtl8231_pin_write(ctrl, RTL8231_FIELD_GPIO_DATA0, offset, value); +} + +static int rtl8231_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct rtl8231_gpio_ctrl *ctrl = gpiochip_get_data(gc); + + return rtl8231_pin_read(ctrl, RTL8231_FIELD_GPIO_DIR0, offset); +} + +static int rtl8231_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct rtl8231_gpio_ctrl *ctrl = gpiochip_get_data(gc); + + return rtl8231_pin_read(ctrl, RTL8231_FIELD_GPIO_DATA0, offset); +} + +static void rtl8231_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct rtl8231_gpio_ctrl *ctrl = gpiochip_get_data(gc); + + rtl8231_pin_write(ctrl, RTL8231_FIELD_GPIO_DATA0, offset, value); +} + +static int rtl8231_gpio_get_multiple(struct gpio_chip *gc, + unsigned long *mask, unsigned long *bits) +{ + struct rtl8231_gpio_ctrl *ctrl = gpiochip_get_data(gc); + int read, field; + int offset, shift; + int sub_mask; + int value, err; + + err = 0; + read = 0; + field = 0; + + while (read < gc->ngpio) { + shift = read % (8 * sizeof(*bits)); + offset = read / (8 * sizeof(*bits)); + sub_mask = (mask[offset] >> shift) & 0xffff; + if (sub_mask) { + err = regmap_field_read(ctrl->fields[RTL8231_FIELD_GPIO_DATA0 + field], + &value); + if (err) + return err; + value = (sub_mask & value) << shift; + sub_mask <<= shift; + bits[offset] = (bits[offset] & ~sub_mask) | value; + } + + field += 1; + read += 16; + } + + return err; +} + +static void rtl8231_gpio_set_multiple(struct gpio_chip *gc, + unsigned long *mask, unsigned long *bits) +{ + struct rtl8231_gpio_ctrl *ctrl = gpiochip_get_data(gc); + int read, field; + int offset, shift; + int sub_mask; + int value; + + read = 0; + field = 0; + + while (read < gc->ngpio) { + shift = read % (8 * sizeof(*bits)); + offset = read / (8 * sizeof(*bits)); + sub_mask = (mask[offset] >> shift) & 0xffff; + if (sub_mask) { + value = bits[offset] >> shift; + regmap_field_update_bits(ctrl->fields[RTL8231_FIELD_GPIO_DATA0 + field], + sub_mask, value); + } + + field += 1; + read += 16; + } +} + +static int rtl8231_init(struct rtl8231_gpio_ctrl *ctrl) +{ + unsigned int v; + int err; + + err = regmap_field_read(ctrl->fields[RTL8231_FIELD_READY_CODE], &v); + if (err) { + dev_err(ctrl->dev, "failed to read READY_CODE\n"); + return -ENODEV; + } else if (v != RTL8231_READY_CODE_VALUE) { + dev_err(ctrl->dev, "RTL8231 not present or ready 0x%x != 0x%x\n", + v, RTL8231_READY_CODE_VALUE); + return -ENODEV; + } + + dev_info(ctrl->dev, "RTL8231 found\n"); + + /* If the device was already configured, just leave it alone */ + err = regmap_field_read(ctrl->fields[RTL8231_FIELD_LED_START], &v); + if (err) + return err; + else if (v) + return 0; + + regmap_field_write(ctrl->fields[RTL8231_FIELD_SOFT_RESET], 1); + regmap_field_write(ctrl->fields[RTL8231_FIELD_LED_START], 1); + + /* Select GPIO functionality for all pins and set to input */ + regmap_field_write(ctrl->fields[RTL8231_FIELD_PIN_MODE0], 0xffff); + regmap_field_write(ctrl->fields[RTL8231_FIELD_GPIO_DIR0], 0xffff); + regmap_field_write(ctrl->fields[RTL8231_FIELD_PIN_MODE1], 0xffff); + regmap_field_write(ctrl->fields[RTL8231_FIELD_GPIO_DIR1], 0xffff); + regmap_field_write(ctrl->fields[RTL8231_FIELD_PIN_MODE2], 0x1f); + regmap_field_write(ctrl->fields[RTL8231_FIELD_GPIO_DIR2], 0x1f); + + return 0; +} + +#define OF_COMPATIBLE_RTL8231_MDIO "realtek,rtl8231-mdio" +#define OF_COMPATIBLE_RTL8231_I2C "realtek,rtl8231-i2c" + +static const struct of_device_id rtl8231_gpio_of_match[] = { + { .compatible = OF_COMPATIBLE_RTL8231_MDIO }, + { .compatible = OF_COMPATIBLE_RTL8231_I2C }, + {}, +}; + +MODULE_DEVICE_TABLE(of, rtl8231_gpio_of_match); + +static struct regmap *rtl8231_gpio_regmap_mdio(struct device *dev, struct regmap_config *cfg) +{ + struct device_node *np = dev->of_node; + struct device_node *expander_np = NULL; + struct mdio_device *mdiodev; + + expander_np = of_parse_phandle(np, "dev-handle", 0); + if (!expander_np) { + dev_err(dev, "missing dev-handle node\n"); + return ERR_PTR(-EINVAL); + } + + mdiodev = of_mdio_find_device(expander_np); + of_node_put(expander_np); + + if (!mdiodev) { + dev_err(dev, "failed to find MDIO device\n"); + return ERR_PTR(-EPROBE_DEFER); + } + + cfg->reg_bits = 5; + return devm_regmap_init_miim(mdiodev, cfg); +} + +static struct regmap *rtl8231_gpio_regmap_i2c(struct device *dev, struct regmap_config *cfg) +{ + struct device_node *np = dev->of_node; + struct i2c_client *i2cdev; + struct regmap *map; + u32 reg_width; + + // TODO untested + i2cdev = of_find_i2c_device_by_node(np); + if (IS_ERR(i2cdev)) { + dev_err(dev, "failed to find I2C device\n"); + return ERR_PTR(-ENODEV); + } + + /* Complete 7-bit I2C address is [1 0 1 0 A2 A1 A0] */ + if ((i2cdev->addr & ~(0x7)) != 0x50) { + dev_err(dev, "invalid address\n"); + map = ERR_PTR(-EINVAL); + goto regmap_i2c_out; + } + + if (of_property_read_u32(np, "realtek,regnum-width", ®_width) + || reg_width != 1 || reg_width != 2) { + dev_err(dev, "invalid realtek,regnum-width\n"); + map = ERR_PTR(-EINVAL); + goto regmap_i2c_out; + } + + cfg->reg_bits = 8*reg_width; + map = devm_regmap_init_i2c(i2cdev, cfg); + +regmap_i2c_out: + put_device(&i2cdev->dev); + return map; +} + +static int rtl8231_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regmap *map; + struct regmap_config regmap_cfg = {}; + struct rtl8231_gpio_ctrl *ctrl; + int field, err; + u32 ngpios; + + if (!np) { + dev_err(dev, "no DT node found\n"); + return -EINVAL; + } + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + ngpios = RTL8231_MAX_GPIOS; + of_property_read_u32(np, "ngpios", &ngpios); + if (ngpios > RTL8231_MAX_GPIOS) { + dev_err(dev, "ngpios can be at most %d\n", RTL8231_MAX_GPIOS); + return -EINVAL; + } + + regmap_cfg.val_bits = 16; + regmap_cfg.max_register = 30; + regmap_cfg.cache_type = REGCACHE_NONE; + regmap_cfg.num_ranges = 0; + regmap_cfg.use_single_read = true; + regmap_cfg.use_single_write = true; + regmap_cfg.reg_format_endian = REGMAP_ENDIAN_BIG; + regmap_cfg.val_format_endian = REGMAP_ENDIAN_BIG; + + if (of_device_is_compatible(np, OF_COMPATIBLE_RTL8231_MDIO)) { + map = rtl8231_gpio_regmap_mdio(dev, ®map_cfg); + } else if (of_device_is_compatible(np, OF_COMPATIBLE_RTL8231_I2C)) { + map = rtl8231_gpio_regmap_i2c(dev, ®map_cfg); + } else { + dev_err(dev, "invalid bus type\n"); + return -ENOTSUPP; + } + + if (IS_ERR(map)) { + dev_err(dev, "failed to init regmap\n"); + return PTR_ERR(map); + } + + for (field = 0; field < RTL8231_FIELD_MAX; field++) { + ctrl->fields[field] = devm_regmap_field_alloc(dev, map, rtl8231_fields[field]); + if (IS_ERR(ctrl->fields[field])) { + dev_err(dev, "unable to allocate regmap field\n"); + return PTR_ERR(ctrl->fields[field]); + } + } + + ctrl->dev = dev; + err = rtl8231_init(ctrl); + if (err < 0) + return err; + + ctrl->gc.base = -1; + ctrl->gc.ngpio = ngpios; + ctrl->gc.label = "rtl8231-gpio"; + ctrl->gc.parent = dev; + ctrl->gc.owner = THIS_MODULE; + ctrl->gc.can_sleep = true; + + ctrl->gc.set = rtl8231_gpio_set; + ctrl->gc.set_multiple = rtl8231_gpio_set_multiple; + ctrl->gc.get = rtl8231_gpio_get; + ctrl->gc.get_multiple = rtl8231_gpio_get_multiple; + ctrl->gc.direction_input = rtl8231_direction_input; + ctrl->gc.direction_output = rtl8231_direction_output; + ctrl->gc.get_direction = rtl8231_get_direction; + + return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl); +} + +static struct platform_driver rtl8231_gpio_driver = { + .driver = { + .name = "rtl8231-expander", + .of_match_table = rtl8231_gpio_of_match, + }, + .probe = rtl8231_gpio_probe, +}; +module_platform_driver(rtl8231_gpio_driver); + +MODULE_AUTHOR("Sander Vanheule "); +MODULE_DESCRIPTION("Realtek RTL8231 GPIO and LED expander support"); +MODULE_LICENSE("GPL v2");