From patchwork Mon Oct 7 12:39:52 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrea della Porta X-Patchwork-Id: 13824652 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 054E2CFB43F for ; Mon, 7 Oct 2024 13:09:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:Content-Transfer-Encoding: MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:To:From:Reply-To: Cc:Content-Type:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=jLTVNTpWvNJNTEgav04tM9YHWKFoerW7GzoKep+fC2c=; b=zzJEV7QhVmZSerrSfAfUtNPq4n UPrM8j1aon5truVQHU1vH5Dzx+JHGRZKVOhXB6h/i3LYuEHB4Cb+rXabUUe1Y8M0muE2Ln+vouG8W jMfKD/HvmJTKy1dz/v+vkZm/ynaBwam8V/nu3g9dvgegS/i7DKZti+fPfavOq4mHbtyg5Rz85OKz+ NWf2xl/IxxoYzTtcLyQ9Y4nxYRDH871/LnzL2OXf66cIZNfrNK0bdcly/NdoA27wEklBhcvzrLJhU m7tR444Cjd6hTu639E+Kpj5vrfxe8Mb6sRDAO6RDzhZ7aiAJomtoYflWoK8LRgn4fslQKDPrRWLpi aLkJHJHA==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.98 #2 (Red Hat Linux)) id 1sxnUU-00000002S0A-1sBI; Mon, 07 Oct 2024 13:09:34 +0000 Received: from mail-lj1-x244.google.com ([2a00:1450:4864:20::244]) by bombadil.infradead.org with esmtps (Exim 4.98 #2 (Red Hat Linux)) id 1sxn1q-00000002McN-3TYg for linux-arm-kernel@lists.infradead.org; Mon, 07 Oct 2024 12:40:06 +0000 Received: by mail-lj1-x244.google.com with SMTP id 38308e7fff4ca-2fac49b17ebso32681081fa.0 for ; Mon, 07 Oct 2024 05:39:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=google; t=1728304796; x=1728909596; darn=lists.infradead.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to; bh=jLTVNTpWvNJNTEgav04tM9YHWKFoerW7GzoKep+fC2c=; b=dq+FqMia4wdaJLypwoskxCA8IVjoKdTdH4xpigRKtX1zr72W2a4MLntMlmNxD+BtQJ Fz0F4RGTF30t/yd1A/vfYBTO3JjEHivhTDjQCj8GXFXWinbbfrfwuNem5Y9ZXfR0K7nW sVIaKWae/Lwo3HiIdMoxOstzBaS3/litNu6lVZ/9Bd1pg4+eRxI7Rd632pvdDKHUtXzf ix5yFUxwa0FLJfvai+ge8HlpuCUpduuw02woqD8rblQqc8fuLHUsYCQBBiNEA4elWfWn 7CCnrfj3vJ8j7DdxQDDdwDsI9ohVuDB63xRbJGPL06gml3I1QwqQjhNIyWvU3MUhNd1A 0Wpg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1728304796; x=1728909596; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=jLTVNTpWvNJNTEgav04tM9YHWKFoerW7GzoKep+fC2c=; b=VY7Y5OkOB9+8KR13PQ+B95wSZjw0mVkCWdUbFG4kOv02owNaNHFaAiyDDik8bxwYL+ /d/G9dt7awTp4e8x9CX0v3W4LBtvprq43zMunTZePALjLlge/Vsh4PHUZJW1X4hjeyLu MPiKwJdO91htEOhiWlEoanvWgQfrbrBQxlNpqiEdCDzoAMQimDMH9q6X4Zu+Rn3Kbk08 fXNECh5RLL7csoCeMeV+POyJrb3B7EIHPSjtenSQH4yoDDlIgfCRI7iGW3C5iLLfoZ55 FcANJ4guUzRTSFf88qrXiHCMLNMKAJCXx1SnhMGwtxBbBJGQpodcdhvNCG89MjcEau+Z QAag== X-Forwarded-Encrypted: i=1; AJvYcCVZklBaldwzdrlJh7l2kZgNQbAubXVlnjqZvc+bx8wvf5MfWRqT8A677sITgJKTbX3vJlTj2hKEDVnpOsjqoxuq@lists.infradead.org X-Gm-Message-State: AOJu0Yxj38MNgFiYZIEJC4q6xcIhfD/ltrg1AyaT1Us9AWwal4dnrqOo 8htl7NdlW7baDh+IIzX9fJ9CNUKt7VkTSp45o8hwaZwNxZuhasSN00vqjrXe1uI= X-Google-Smtp-Source: AGHT+IE221xonLAH9xvAoowC3AixczbMz8OftHXPf4W3a988UdsERFQQeLrjc1SdmmsoaIWCGIRgyA== X-Received: by 2002:a2e:a586:0:b0:2f3:b71a:1e91 with SMTP id 38308e7fff4ca-2faf3c1ce4fmr58068821fa.17.1728304796301; Mon, 07 Oct 2024 05:39:56 -0700 (PDT) Received: from localhost (host-87-21-212-62.retail.telecomitalia.it. [87.21.212.62]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-5c8e05eb7b7sm3172901a12.68.2024.10.07.05.39.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 07 Oct 2024 05:39:55 -0700 (PDT) From: Andrea della Porta To: Andrea della Porta , Michael Turquette , Stephen Boyd , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Florian Fainelli , Broadcom internal kernel review list , Lorenzo Pieralisi , =?utf-8?q?Krzysztof_Wilczy=C5=84?= =?utf-8?q?ski?= , Manivannan Sadhasivam , Bjorn Helgaas , Linus Walleij , Catalin Marinas , Will Deacon , Bartosz Golaszewski , Derek Kiernan , Dragan Cvetic , Arnd Bergmann , Greg Kroah-Hartman , Saravana Kannan , linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-rpi-kernel@lists.infradead.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org, linux-gpio@vger.kernel.org, Masahiro Yamada , Stefan Wahren , Herve Codina , Luca Ceresoli , Thomas Petazzoni , Andrew Lunn Subject: [PATCH v2 09/14] pinctrl: rp1: Implement RaspberryPi RP1 gpio support Date: Mon, 7 Oct 2024 14:39:52 +0200 Message-ID: <199a4994312b120c73c95fa368954ad5a2a8aee6.1728300190.git.andrea.porta@suse.com> X-Mailer: git-send-email 2.44.0 In-Reply-To: References: MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20241007_053958_998951_BF614D02 X-CRM114-Status: GOOD ( 25.49 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org The RP1 is an MFD supporting a gpio controller and /pinmux/pinctrl. Add minimum support for the gpio only portion. The driver is in pinctrl folder since upcoming patches will add the pinmux/pinctrl support where the gpio part can be seen as an addition. Signed-off-by: Andrea della Porta --- Please note that checkpatch warning on this patch are unavoidable. MAINTAINERS | 1 + drivers/pinctrl/Kconfig | 11 + drivers/pinctrl/Makefile | 1 + drivers/pinctrl/pinctrl-rp1.c | 860 ++++++++++++++++++++++++++++++++++ 4 files changed, 873 insertions(+) create mode 100644 drivers/pinctrl/pinctrl-rp1.c diff --git a/MAINTAINERS b/MAINTAINERS index dc064cd4b6b5..06277969a522 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19388,6 +19388,7 @@ F: Documentation/devicetree/bindings/misc/pci1de4,1.yaml F: Documentation/devicetree/bindings/pci/pci-ep-bus.yaml F: Documentation/devicetree/bindings/pinctrl/raspberrypi,rp1-gpio.yaml F: drivers/clk/clk-rp1.c +F: drivers/pinctrl/pinctrl-rp1.c F: include/dt-bindings/clock/rp1.h F: include/dt-bindings/misc/rp1.h diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 354536de564b..f49b8b7f6afa 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -587,6 +587,17 @@ config PINCTRL_MLXBF3 each pin. This driver can also be built as a module called pinctrl-mlxbf3. +config PINCTRL_RP1 + bool "Pinctrl driver for RP1" + select PINMUX + select PINCONF + select GENERIC_PINCONF + select GPIOLIB + select GPIOLIB_IRQCHIP + help + Enable the gpio and pinctrl/mux driver for RaspberryPi RP1 + multi function device. + source "drivers/pinctrl/actions/Kconfig" source "drivers/pinctrl/aspeed/Kconfig" source "drivers/pinctrl/bcm/Kconfig" diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 97823f52b972..2d714047a1ce 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_PINCTRL_PIC32) += pinctrl-pic32.o obj-$(CONFIG_PINCTRL_PISTACHIO) += pinctrl-pistachio.o obj-$(CONFIG_PINCTRL_RK805) += pinctrl-rk805.o obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o +obj-$(CONFIG_PINCTRL_RP1) += pinctrl-rp1.o obj-$(CONFIG_PINCTRL_SCMI) += pinctrl-scmi.o obj-$(CONFIG_PINCTRL_SINGLE) += pinctrl-single.o obj-$(CONFIG_PINCTRL_ST) += pinctrl-st.o diff --git a/drivers/pinctrl/pinctrl-rp1.c b/drivers/pinctrl/pinctrl-rp1.c new file mode 100644 index 000000000000..617804871e03 --- /dev/null +++ b/drivers/pinctrl/pinctrl-rp1.c @@ -0,0 +1,860 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Raspberry Pi RP1 GPIO unit + * + * Copyright (C) 2023 Raspberry Pi Ltd. + * + * This driver is inspired by: + * pinctrl-bcm2835.c, please see original file for copyright information + */ + +#include +#include +#include +#include +#include + +#define MODULE_NAME "pinctrl-rp1" +#define RP1_NUM_GPIOS 54 +#define RP1_NUM_BANKS 3 + +#define RP1_INT_EDGE_FALLING BIT(0) +#define RP1_INT_EDGE_RISING BIT(1) +#define RP1_INT_LEVEL_LOW BIT(2) +#define RP1_INT_LEVEL_HIGH BIT(3) +#define RP1_INT_MASK GENMASK(3, 0) +#define RP1_INT_EDGE_BOTH (RP1_INT_EDGE_FALLING | \ + RP1_INT_EDGE_RISING) + +#define RP1_FSEL_COUNT 9 + +#define RP1_FSEL_ALT0 0x00 +#define RP1_FSEL_GPIO 0x05 +#define RP1_FSEL_NONE 0x09 +#define RP1_FSEL_NONE_HW 0x1f + +#define RP1_PAD_DRIVE_2MA 0x0 +#define RP1_PAD_DRIVE_4MA 0x1 +#define RP1_PAD_DRIVE_8MA 0x2 +#define RP1_PAD_DRIVE_12MA 0x3 + +enum { + RP1_PUD_OFF = 0, + RP1_PUD_DOWN = 1, + RP1_PUD_UP = 2, +}; + +enum { + RP1_DIR_OUTPUT = 0, + RP1_DIR_INPUT = 1, +}; + +enum { + RP1_OUTOVER_PERI = 0, + RP1_OUTOVER_INVPERI = 1, + RP1_OUTOVER_LOW = 2, + RP1_OUTOVER_HIGH = 3, +}; + +enum { + RP1_OEOVER_PERI = 0, + RP1_OEOVER_INVPERI = 1, + RP1_OEOVER_DISABLE = 2, + RP1_OEOVER_ENABLE = 3, +}; + +enum { + RP1_INOVER_PERI = 0, + RP1_INOVER_INVPERI = 1, + RP1_INOVER_LOW = 2, + RP1_INOVER_HIGH = 3, +}; + +enum { + RP1_GPIO_CTRL_IRQRESET_SET = 0, + RP1_GPIO_CTRL_INT_CLR = 1, + RP1_GPIO_CTRL_INT_SET = 2, + RP1_GPIO_CTRL_OEOVER = 3, + RP1_GPIO_CTRL_FUNCSEL = 4, + RP1_GPIO_CTRL_OUTOVER = 5, + RP1_GPIO_CTRL = 6, +}; + +enum { + RP1_INTE_SET = 0, + RP1_INTE_CLR = 1, +}; + +enum { + RP1_RIO_OUT_SET = 0, + RP1_RIO_OUT_CLR = 1, + RP1_RIO_OE = 2, + RP1_RIO_OE_SET = 3, + RP1_RIO_OE_CLR = 4, + RP1_RIO_IN = 5, +}; + +enum { + RP1_PAD_SLEWFAST = 0, + RP1_PAD_SCHMITT = 1, + RP1_PAD_PULL = 2, + RP1_PAD_DRIVE = 3, + RP1_PAD_IN_ENABLE = 4, + RP1_PAD_OUT_DISABLE = 5, +}; + +static const struct reg_field rp1_gpio_fields[] = { + [RP1_GPIO_CTRL_IRQRESET_SET] = REG_FIELD(0x2004, 28, 28), + [RP1_GPIO_CTRL_INT_CLR] = REG_FIELD(0x3004, 20, 23), + [RP1_GPIO_CTRL_INT_SET] = REG_FIELD(0x2004, 20, 23), + [RP1_GPIO_CTRL_OEOVER] = REG_FIELD(0x0004, 14, 15), + [RP1_GPIO_CTRL_FUNCSEL] = REG_FIELD(0x0004, 0, 4), + [RP1_GPIO_CTRL_OUTOVER] = REG_FIELD(0x0004, 12, 13), + [RP1_GPIO_CTRL] = REG_FIELD(0x0004, 0, 31), +}; + +static const struct reg_field rp1_inte_fields[] = { + [RP1_INTE_SET] = REG_FIELD(0x2000, 0, 0), + [RP1_INTE_CLR] = REG_FIELD(0x3000, 0, 0), +}; + +static const struct reg_field rp1_rio_fields[] = { + [RP1_RIO_OUT_SET] = REG_FIELD(0x2000, 0, 0), + [RP1_RIO_OUT_CLR] = REG_FIELD(0x3000, 0, 0), + [RP1_RIO_OE] = REG_FIELD(0x0004, 0, 0), + [RP1_RIO_OE_SET] = REG_FIELD(0x2004, 0, 0), + [RP1_RIO_OE_CLR] = REG_FIELD(0x3004, 0, 0), + [RP1_RIO_IN] = REG_FIELD(0x0008, 0, 0), +}; + +static const struct reg_field rp1_pad_fields[] = { + [RP1_PAD_SLEWFAST] = REG_FIELD(0, 0, 0), + [RP1_PAD_SCHMITT] = REG_FIELD(0, 1, 1), + [RP1_PAD_PULL] = REG_FIELD(0, 2, 3), + [RP1_PAD_DRIVE] = REG_FIELD(0, 4, 5), + [RP1_PAD_IN_ENABLE] = REG_FIELD(0, 6, 6), + [RP1_PAD_OUT_DISABLE] = REG_FIELD(0, 7, 7), +}; + +struct rp1_iobank_desc { + int min_gpio; + int num_gpios; + int gpio_offset; + int inte_offset; + int ints_offset; + int rio_offset; + int pads_offset; +}; + +struct rp1_pin_info { + u8 num; + u8 bank; + u8 offset; + u8 fsel; + u8 irq_type; + + struct regmap_field *gpio[ARRAY_SIZE(rp1_gpio_fields)]; + struct regmap_field *rio[ARRAY_SIZE(rp1_rio_fields)]; + struct regmap_field *inte[ARRAY_SIZE(rp1_inte_fields)]; + struct regmap_field *pad[ARRAY_SIZE(rp1_pad_fields)]; +}; + +struct rp1_pinctrl { + struct device *dev; + void __iomem *gpio_base; + void __iomem *rio_base; + void __iomem *pads_base; + int irq[RP1_NUM_BANKS]; + struct rp1_pin_info pins[RP1_NUM_GPIOS]; + + struct pinctrl_dev *pctl_dev; + struct gpio_chip gpio_chip; + struct pinctrl_gpio_range gpio_range; + + raw_spinlock_t irq_lock[RP1_NUM_BANKS]; + + struct configfs_subsystem subsys; +}; + +static const struct rp1_iobank_desc rp1_iobanks[RP1_NUM_BANKS] = { + /* gpio inte ints rio pads */ + { 0, 28, 0x0000, 0x011c, 0x0124, 0x0000, 0x0004 }, + { 28, 6, 0x4000, 0x411c, 0x4124, 0x4000, 0x4004 }, + { 34, 20, 0x8000, 0x811c, 0x8124, 0x8000, 0x8004 }, +}; + +static int rp1_pinconf_set(struct rp1_pin_info *pin, + unsigned int offset, unsigned long *configs, + unsigned int num_configs); + +static struct rp1_pin_info *rp1_get_pin(struct gpio_chip *chip, + unsigned int offset) +{ + struct rp1_pinctrl *pc = gpiochip_get_data(chip); + + if (pc && offset < RP1_NUM_GPIOS) + return &pc->pins[offset]; + return NULL; +} + +static void rp1_input_enable(struct rp1_pin_info *pin, int value) +{ + regmap_field_write(pin->pad[RP1_PAD_IN_ENABLE], !!value); +} + +static void rp1_output_enable(struct rp1_pin_info *pin, int value) +{ + regmap_field_write(pin->pad[RP1_PAD_OUT_DISABLE], !value); +} + +static u32 rp1_get_fsel(struct rp1_pin_info *pin) +{ + u32 oeover, fsel; + + regmap_field_read(pin->gpio[RP1_GPIO_CTRL_OEOVER], &oeover); + regmap_field_read(pin->gpio[RP1_GPIO_CTRL_FUNCSEL], &fsel); + + if (oeover != RP1_OEOVER_PERI || fsel >= RP1_FSEL_COUNT) + fsel = RP1_FSEL_NONE; + + return fsel; +} + +static void rp1_set_fsel(struct rp1_pin_info *pin, u32 fsel) +{ + if (fsel >= RP1_FSEL_COUNT) + fsel = RP1_FSEL_NONE_HW; + + rp1_input_enable(pin, 1); + rp1_output_enable(pin, 1); + + if (fsel == RP1_FSEL_NONE) { + regmap_field_write(pin->gpio[RP1_GPIO_CTRL_OEOVER], RP1_OEOVER_DISABLE); + } else { + regmap_field_write(pin->gpio[RP1_GPIO_CTRL_OUTOVER], RP1_OUTOVER_PERI); + regmap_field_write(pin->gpio[RP1_GPIO_CTRL_OEOVER], RP1_OEOVER_PERI); + } + + regmap_field_write(pin->gpio[RP1_GPIO_CTRL_FUNCSEL], fsel); +} + +static int rp1_get_dir(struct rp1_pin_info *pin) +{ + unsigned int val; + + regmap_field_read(pin->rio[RP1_RIO_OE], &val); + + return !val ? RP1_DIR_INPUT : RP1_DIR_OUTPUT; +} + +static void rp1_set_dir(struct rp1_pin_info *pin, bool is_input) +{ + int reg = is_input ? RP1_RIO_OE_CLR : RP1_RIO_OE_SET; + + regmap_field_write(pin->rio[reg], 1); +} + +static int rp1_get_value(struct rp1_pin_info *pin) +{ + unsigned int val; + + regmap_field_read(pin->rio[RP1_RIO_IN], &val); + + return !!val; +} + +static void rp1_set_value(struct rp1_pin_info *pin, int value) +{ + /* Assume the pin is already an output */ + int reg = value ? RP1_RIO_OUT_SET : RP1_RIO_OUT_CLR; + + regmap_field_write(pin->rio[reg], 1); +} + +static int rp1_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + int ret; + + if (!pin) + return -EINVAL; + + ret = rp1_get_value(pin); + + return ret; +} + +static void rp1_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + + if (pin) + rp1_set_value(pin, value); +} + +static int rp1_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + u32 fsel; + + if (!pin) + return -EINVAL; + + fsel = rp1_get_fsel(pin); + if (fsel != RP1_FSEL_GPIO) + return -EINVAL; + + return (rp1_get_dir(pin) == RP1_DIR_OUTPUT) ? + GPIO_LINE_DIRECTION_OUT : + GPIO_LINE_DIRECTION_IN; +} + +static int rp1_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + + if (!pin) + return -EINVAL; + rp1_set_dir(pin, RP1_DIR_INPUT); + rp1_set_fsel(pin, RP1_FSEL_GPIO); + + return 0; +} + +static int rp1_gpio_direction_output(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + + if (!pin) + return -EINVAL; + rp1_set_value(pin, value); + rp1_set_dir(pin, RP1_DIR_OUTPUT); + rp1_set_fsel(pin, RP1_FSEL_GPIO); + + return 0; +} + +static int rp1_gpio_set_config(struct gpio_chip *chip, unsigned int offset, + unsigned long config) +{ + struct rp1_pin_info *pin = rp1_get_pin(chip, offset); + unsigned long configs[] = { config }; + + return rp1_pinconf_set(pin, offset, configs, + ARRAY_SIZE(configs)); +} + +static const struct gpio_chip rp1_gpio_chip = { + .label = MODULE_NAME, + .owner = THIS_MODULE, + .request = gpiochip_generic_request, + .free = gpiochip_generic_free, + .direction_input = rp1_gpio_direction_input, + .direction_output = rp1_gpio_direction_output, + .get_direction = rp1_gpio_get_direction, + .get = rp1_gpio_get, + .set = rp1_gpio_set, + .base = -1, + .set_config = rp1_gpio_set_config, + .ngpio = RP1_NUM_GPIOS, + .can_sleep = false, +}; + +static void rp1_gpio_irq_handler(struct irq_desc *desc) +{ + struct gpio_chip *chip = irq_desc_get_handler_data(desc); + struct irq_chip *host_chip = irq_desc_get_chip(desc); + struct rp1_pinctrl *pc = gpiochip_get_data(chip); + const struct rp1_iobank_desc *bank; + int irq = irq_desc_get_irq(desc); + unsigned long ints; + int bit_pos; + + if (pc->irq[0] == irq) + bank = &rp1_iobanks[0]; + else if (pc->irq[1] == irq) + bank = &rp1_iobanks[1]; + else + bank = &rp1_iobanks[2]; + + chained_irq_enter(host_chip, desc); + + ints = readl(pc->gpio_base + bank->ints_offset); + for_each_set_bit(bit_pos, &ints, 32) { + struct rp1_pin_info *pin = rp1_get_pin(chip, bit_pos); + + regmap_field_write(pin->gpio[RP1_GPIO_CTRL_IRQRESET_SET], 1); + generic_handle_irq(irq_linear_revmap(pc->gpio_chip.irq.domain, + bank->gpio_offset + bit_pos)); + } + + chained_irq_exit(host_chip, desc); +} + +static void rp1_gpio_irq_config(struct rp1_pin_info *pin, bool enable) +{ + int reg = enable ? RP1_INTE_SET : RP1_INTE_CLR; + + regmap_field_write(pin->inte[reg], 1); + if (!enable) + /* Clear any latched events */ + regmap_field_write(pin->gpio[RP1_GPIO_CTRL_IRQRESET_SET], 1); +} + +static void rp1_gpio_irq_enable(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + unsigned int gpio = irqd_to_hwirq(data); + struct rp1_pin_info *pin = rp1_get_pin(chip, gpio); + + rp1_gpio_irq_config(pin, true); +} + +static void rp1_gpio_irq_disable(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + unsigned int gpio = irqd_to_hwirq(data); + struct rp1_pin_info *pin = rp1_get_pin(chip, gpio); + + rp1_gpio_irq_config(pin, false); +} + +static int rp1_irq_set_type(struct rp1_pin_info *pin, unsigned int type) +{ + u32 irq_flags; + + switch (type) { + case IRQ_TYPE_NONE: + irq_flags = 0; + break; + case IRQ_TYPE_EDGE_RISING: + irq_flags = RP1_INT_EDGE_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + irq_flags = RP1_INT_EDGE_FALLING; + break; + case IRQ_TYPE_EDGE_BOTH: + irq_flags = RP1_INT_EDGE_RISING | RP1_INT_EDGE_FALLING; + break; + case IRQ_TYPE_LEVEL_HIGH: + irq_flags = RP1_INT_LEVEL_HIGH; + break; + case IRQ_TYPE_LEVEL_LOW: + irq_flags = RP1_INT_LEVEL_LOW; + break; + + default: + return -EINVAL; + } + + /* Clear them all */ + regmap_field_write(pin->gpio[RP1_GPIO_CTRL_INT_CLR], RP1_INT_MASK); + + /* Set those that are needed */ + regmap_field_write(pin->gpio[RP1_GPIO_CTRL_INT_SET], irq_flags); + pin->irq_type = type; + + return 0; +} + +static int rp1_gpio_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + unsigned int gpio = irqd_to_hwirq(data); + struct rp1_pin_info *pin = rp1_get_pin(chip, gpio); + struct rp1_pinctrl *pc = gpiochip_get_data(chip); + int bank = pin->bank; + unsigned long flags; + int ret; + + raw_spin_lock_irqsave(&pc->irq_lock[bank], flags); + + ret = rp1_irq_set_type(pin, type); + if (!ret) { + if (type & IRQ_TYPE_EDGE_BOTH) + irq_set_handler_locked(data, handle_edge_irq); + else + irq_set_handler_locked(data, handle_level_irq); + } + + raw_spin_unlock_irqrestore(&pc->irq_lock[bank], flags); + + return ret; +} + +static void rp1_gpio_irq_ack(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + unsigned int gpio = irqd_to_hwirq(data); + struct rp1_pin_info *pin = rp1_get_pin(chip, gpio); + + /* Clear any latched events */ + regmap_field_write(pin->gpio[RP1_GPIO_CTRL_IRQRESET_SET], 1); +} + +static struct irq_chip rp1_gpio_irq_chip = { + .name = MODULE_NAME, + .irq_enable = rp1_gpio_irq_enable, + .irq_disable = rp1_gpio_irq_disable, + .irq_set_type = rp1_gpio_irq_set_type, + .irq_ack = rp1_gpio_irq_ack, + .irq_mask = rp1_gpio_irq_disable, + .irq_unmask = rp1_gpio_irq_enable, + .flags = IRQCHIP_IMMUTABLE, +}; + +static void rp1_pull_config_set(struct rp1_pin_info *pin, unsigned int arg) +{ + regmap_field_write(pin->pad[RP1_PAD_PULL], arg & 0x3); +} + +static int rp1_pinconf_set(struct rp1_pin_info *pin, unsigned int offset, + unsigned long *configs, unsigned int num_configs) +{ + u32 param, arg; + int i; + + if (!pin) + return -EINVAL; + + for (i = 0; i < num_configs; i++) { + param = pinconf_to_config_param(configs[i]); + arg = pinconf_to_config_argument(configs[i]); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + rp1_pull_config_set(pin, RP1_PUD_OFF); + break; + + case PIN_CONFIG_BIAS_PULL_DOWN: + rp1_pull_config_set(pin, RP1_PUD_DOWN); + break; + + case PIN_CONFIG_BIAS_PULL_UP: + rp1_pull_config_set(pin, RP1_PUD_UP); + break; + + case PIN_CONFIG_INPUT_ENABLE: + rp1_input_enable(pin, arg); + break; + + case PIN_CONFIG_OUTPUT_ENABLE: + rp1_output_enable(pin, arg); + break; + + case PIN_CONFIG_OUTPUT: + rp1_set_value(pin, arg); + rp1_set_dir(pin, RP1_DIR_OUTPUT); + rp1_set_fsel(pin, RP1_FSEL_GPIO); + break; + + case PIN_CONFIG_INPUT_SCHMITT_ENABLE: + regmap_field_write(pin->pad[RP1_PAD_SCHMITT], !!arg); + break; + + case PIN_CONFIG_SLEW_RATE: + regmap_field_write(pin->pad[RP1_PAD_SLEWFAST], !!arg); + break; + + case PIN_CONFIG_DRIVE_STRENGTH: + switch (arg) { + case 2: + arg = RP1_PAD_DRIVE_2MA; + break; + case 4: + arg = RP1_PAD_DRIVE_4MA; + break; + case 8: + arg = RP1_PAD_DRIVE_8MA; + break; + case 12: + arg = RP1_PAD_DRIVE_12MA; + break; + default: + return -ENOTSUPP; + } + regmap_field_write(pin->pad[RP1_PAD_DRIVE], arg); + break; + + default: + return -ENOTSUPP; + + } /* switch param type */ + } /* for each config */ + + return 0; +} + +static const struct of_device_id rp1_pinctrl_match[] = { + { .compatible = "raspberrypi,rp1-gpio" }, + {}, +}; + +static ssize_t rp1_gpio_set_names_write(struct config_item *item, + const void *buf, size_t count) +{ + struct rp1_pinctrl *rp1 = container_of(to_configfs_subsystem(to_config_group(item)), + struct rp1_pinctrl, subsys); + struct gpio_chip *chip = &rp1->gpio_chip; + struct device *dev = rp1->dev; + int ret, ovcs_id; + + ret = of_overlay_fdt_apply(buf, count, &ovcs_id, NULL); + + if (ret) { + dev_err(dev, "Cannot apply rp1_gpio overlay\n"); + return ret; + } + + ret = gpiochip_set_names(chip); + + if (ret) { + dev_err(dev, "Cannot set gpio line names\n"); + return ret; + } + + return count; +} + +CONFIGFS_BIN_ATTR_WO(rp1_, gpio_set_names, NULL, 2048); + +static struct configfs_bin_attribute *rp1_cfg_attrs[] = { + &rp1_attr_gpio_set_names, + NULL, +}; + +static const struct config_item_type rp1_cfg_type = { + .ct_bin_attrs = rp1_cfg_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct rp1_pinctrl rp1_pinctrl_data = { + .subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "rp1-cfg", + .ci_type = &rp1_cfg_type, + }, + }, + }, +}; + +static const struct regmap_config rp1_pinctrl_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .fast_io = true, + .name = "rp1-pinctrl", +}; + +static int rp1_gen_regfield(struct device *dev, + const struct reg_field *array, + size_t array_size, + int reg_off, + int pin_off, + bool additive_offset, + struct regmap *regmap, + struct regmap_field *out[]) +{ + struct reg_field regfield; + int k; + + for (k = 0; k < array_size; k++) { + regfield = array[k]; + regfield.reg = (additive_offset ? regfield.reg : 0) + reg_off; + if (pin_off >= 0) { + regfield.lsb = pin_off; + regfield.msb = regfield.lsb; + } + out[k] = devm_regmap_field_alloc(dev, regmap, regfield); + + if (IS_ERR(out[k])) + return PTR_ERR(out[k]); + } + + return 0; +} + +static int rp1_pinctrl_probe(struct platform_device *pdev) +{ + struct regmap *gpio_regmap, *rio_regmap, *pads_regmap; + struct rp1_pinctrl *pc = &rp1_pinctrl_data; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct gpio_irq_chip *girq; + int err, i; + + pc->dev = dev; + pc->gpio_chip = rp1_gpio_chip; + pc->gpio_chip.parent = dev; + + pc->gpio_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pc->gpio_base)) { + dev_err(dev, "could not get GPIO IO memory\n"); + return PTR_ERR(pc->gpio_base); + } + + pc->rio_base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(pc->rio_base)) { + dev_err(dev, "could not get RIO IO memory\n"); + return PTR_ERR(pc->rio_base); + } + + pc->pads_base = devm_platform_ioremap_resource(pdev, 2); + if (IS_ERR(pc->pads_base)) { + dev_err(dev, "could not get PADS IO memory\n"); + return PTR_ERR(pc->pads_base); + } + + gpio_regmap = devm_regmap_init_mmio(dev, pc->gpio_base, + &rp1_pinctrl_regmap_cfg); + if (IS_ERR(gpio_regmap)) { + dev_err(dev, "could not init GPIO regmap\n"); + return PTR_ERR(gpio_regmap); + } + + rio_regmap = devm_regmap_init_mmio(dev, pc->rio_base, + &rp1_pinctrl_regmap_cfg); + if (IS_ERR(rio_regmap)) { + dev_err(dev, "could not init RIO regmap\n"); + return PTR_ERR(rio_regmap); + } + + pads_regmap = devm_regmap_init_mmio(dev, pc->pads_base, + &rp1_pinctrl_regmap_cfg); + if (IS_ERR(pads_regmap)) { + dev_err(dev, "could not init PADS regmap\n"); + return PTR_ERR(pads_regmap); + } + + for (i = 0; i < RP1_NUM_BANKS; i++) { + const struct rp1_iobank_desc *bank = &rp1_iobanks[i]; + int j; + + for (j = 0; j < bank->num_gpios; j++) { + struct rp1_pin_info *pin = + &pc->pins[bank->min_gpio + j]; + int reg_off; + + pin->num = bank->min_gpio + j; + pin->bank = i; + pin->offset = j; + + reg_off = bank->gpio_offset + pin->offset + * sizeof(u32) * 2; + err = rp1_gen_regfield(dev, + rp1_gpio_fields, + ARRAY_SIZE(rp1_gpio_fields), + reg_off, + -1, + true, + gpio_regmap, + pin->gpio); + + if (err) { + dev_err(dev, "Unable to allocate regmap for gpio\n"); + return err; + } + + reg_off = bank->inte_offset; + err = rp1_gen_regfield(dev, + rp1_inte_fields, + ARRAY_SIZE(rp1_inte_fields), + reg_off, + pin->offset, + true, + gpio_regmap, + pin->inte); + + if (err) { + dev_err(dev, "Unable to allocate regmap for inte\n"); + return err; + } + + reg_off = bank->rio_offset; + err = rp1_gen_regfield(dev, + rp1_rio_fields, + ARRAY_SIZE(rp1_rio_fields), + reg_off, + pin->offset, + true, + rio_regmap, + pin->rio); + + if (err) { + dev_err(dev, "Unable to allocate regmap for rio\n"); + return err; + } + + reg_off = bank->pads_offset + pin->offset * sizeof(u32); + err = rp1_gen_regfield(dev, + rp1_pad_fields, + ARRAY_SIZE(rp1_pad_fields), + reg_off, + -1, + false, + pads_regmap, + pin->pad); + + if (err) { + dev_err(dev, "Unable to allocate regmap for pad\n"); + return err; + } + } + + raw_spin_lock_init(&pc->irq_lock[i]); + } + + girq = &pc->gpio_chip.irq; + girq->chip = &rp1_gpio_irq_chip; + girq->parent_handler = rp1_gpio_irq_handler; + girq->num_parents = RP1_NUM_BANKS; + girq->parents = pc->irq; + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_level_irq; + + /* + * Use the same handler for all groups: this is necessary + * since we use one gpiochip to cover all lines - the + * irq handler then needs to figure out which group and + * bank that was firing the IRQ and look up the per-group + * and bank data. + */ + for (i = 0; i < RP1_NUM_BANKS; i++) { + pc->irq[i] = irq_of_parse_and_map(np, i); + if (!pc->irq[i]) { + girq->num_parents = i; + break; + } + } + + platform_set_drvdata(pdev, pc); + + err = devm_gpiochip_add_data(dev, &pc->gpio_chip, pc); + if (err) { + dev_err(dev, "could not add GPIO chip\n"); + return err; + } + + config_group_init(&pc->subsys.su_group); + mutex_init(&pc->subsys.su_mutex); + err = configfs_register_subsystem(&pc->subsys); + if (err) { + dev_err(dev, "Error %d while registering subsystem %s\n", + err, pc->subsys.su_group.cg_item.ci_namebuf); + return err; + } + + return 0; +} + +static struct platform_driver rp1_pinctrl_driver = { + .probe = rp1_pinctrl_probe, + .driver = { + .name = MODULE_NAME, + .of_match_table = rp1_pinctrl_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(rp1_pinctrl_driver);