[PATCH v2 2/3] gpio: spacemit: add support for K1 SoC

Yixun Lan posted 3 patches 12 months ago
There is a newer version of this series
[PATCH v2 2/3] gpio: spacemit: add support for K1 SoC
Posted by Yixun Lan 12 months ago
Implement GPIO functionality which capable of setting pin as
input, output. Also, each pin can be used as interrupt which
support rising/failing edge type trigger, or both.

Signed-off-by: Yixun Lan <dlan@gentoo.org>
---
 drivers/gpio/Kconfig            |   7 +
 drivers/gpio/Makefile           |   1 +
 drivers/gpio/gpio-spacemit-k1.c | 454 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 462 insertions(+)

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 93ee3aa092f81cd9573fcf91aef82ede1c5d7130..b525a98a6c176a4e71d2b0f1bd217e7d89141e81 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -655,6 +655,13 @@ config GPIO_SNPS_CREG
 	  where only several fields in register belong to GPIO lines and
 	  each GPIO line owns a field with different length and on/off value.
 
+config GPIO_SPACEMIT_K1
+	bool "SPACEMIT K1 GPIO support"
+	depends on ARCH_SPACEMIT || COMPILE_TEST
+	select GPIOLIB_IRQCHIP
+	help
+	  Say yes here to support the SpacemiT's K1 GPIO device.
+
 config GPIO_SPEAR_SPICS
 	bool "ST SPEAr13xx SPI Chip Select as GPIO support"
 	depends on PLAT_SPEAR
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index af3ba4d81b583842893ea69e677fbe2abf31bc7b..6709ce511a0cf10310a94521c85a2d382dcfa696 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -156,6 +156,7 @@ obj-$(CONFIG_GPIO_SIOX)			+= gpio-siox.o
 obj-$(CONFIG_GPIO_SL28CPLD)		+= gpio-sl28cpld.o
 obj-$(CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER) += gpio-sloppy-logic-analyzer.o
 obj-$(CONFIG_GPIO_SODAVILLE)		+= gpio-sodaville.o
+obj-$(CONFIG_GPIO_SPACEMIT_K1)		+= gpio-spacemit-k1.o
 obj-$(CONFIG_GPIO_SPEAR_SPICS)		+= gpio-spear-spics.o
 obj-$(CONFIG_GPIO_SPRD)			+= gpio-sprd.o
 obj-$(CONFIG_GPIO_STMPE)		+= gpio-stmpe.o
diff --git a/drivers/gpio/gpio-spacemit-k1.c b/drivers/gpio/gpio-spacemit-k1.c
new file mode 100644
index 0000000000000000000000000000000000000000..493ddc99f38c141187b05db6c292bc28ad5331b4
--- /dev/null
+++ b/drivers/gpio/gpio-spacemit-k1.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Copyright (C) 2023-2024 SpacemiT (Hangzhou) Technology Co. Ltd
+ * Copyright (c) 2024 Yixun Lan <dlan@gentoo.org>
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/irqdomain.h>
+
+/* register offset */
+#define GPLR		0x00
+#define GPDR		0x0c
+#define GPSR		0x18
+#define GPCR		0x24
+#define GRER		0x30
+#define GFER		0x3c
+#define GEDR		0x48
+#define GSDR		0x54
+#define GCDR		0x60
+#define GSRER		0x6c
+#define GCRER		0x78
+#define GSFER		0x84
+#define GCFER		0x90
+#define GAPMASK		0x9c
+#define GCPMASK		0xa8
+
+#define K1_BANK_GPIO_NUMBER	(32)
+#define BANK_GPIO_MASK		(K1_BANK_GPIO_NUMBER - 1)
+
+#define spacemit_gpio_to_bank_idx(gpio)		((gpio) / K1_BANK_GPIO_NUMBER)
+#define spacemit_gpio_to_bank_offset(gpio)	((gpio) & BANK_GPIO_MASK)
+#define spacemit_bank_to_gpio(idx, offset)	\
+	(((idx) * K1_BANK_GPIO_NUMBER) | ((offset) & BANK_GPIO_MASK))
+
+static u32 k1_gpio_bank_offset[] = { 0x0, 0x4, 0x8, 0x100 };
+
+struct spacemit_gpio_bank {
+	void __iomem			*reg_bank;
+	u32				irq_mask;
+	u32				irq_rising_edge;
+	u32				irq_falling_edge;
+};
+
+struct spacemit_gpio_chip {
+	struct gpio_chip		chip;
+	struct irq_domain		*domain;
+	struct spacemit_gpio_bank	*banks;
+	void __iomem			*reg_base;
+	unsigned int			ngpio;
+	unsigned int			nbank;
+	int				irq;
+};
+
+static struct spacemit_gpio_chip *to_spacemit_gpio_chip(struct gpio_chip *chip)
+{
+	return container_of(chip, struct spacemit_gpio_chip, chip);
+}
+
+static inline void spacemit_clear_edge_detection(struct spacemit_gpio_bank *bank,
+						 u32 bit)
+{
+	writel(bit, bank->reg_bank + GCRER);
+	writel(bit, bank->reg_bank + GCFER);
+}
+
+static inline void spacemit_set_edge_detection(struct spacemit_gpio_bank *bank,
+					       u32 bit)
+{
+	writel(bit & bank->irq_rising_edge, bank->reg_bank + GSRER);
+	writel(bit & bank->irq_falling_edge, bank->reg_bank + GSFER);
+}
+
+static void spacemit_reset_edge_detection(struct spacemit_gpio_chip *schip)
+{
+	struct spacemit_gpio_bank *bank;
+	unsigned int i;
+
+	for (i = 0; i < schip->nbank; i++) {
+		bank = &schip->banks[i];
+
+		writel(0xffffffff, bank->reg_bank + GCFER);
+		writel(0xffffffff, bank->reg_bank + GCRER);
+		writel(0xffffffff, bank->reg_bank + GAPMASK);
+	}
+}
+
+static int spacemit_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
+{
+	struct spacemit_gpio_chip *schip = to_spacemit_gpio_chip(chip);
+
+	return irq_create_mapping(schip->domain, offset);
+}
+
+static struct spacemit_gpio_bank *
+spacemit_gpio_get_bank(struct spacemit_gpio_chip *schip,
+		       unsigned int offset)
+{
+	return &schip->banks[spacemit_gpio_to_bank_idx(offset)];
+}
+
+static int spacemit_gpio_direction_input(struct gpio_chip *chip,
+					 unsigned int offset)
+{
+	struct spacemit_gpio_chip *schip;
+	struct spacemit_gpio_bank *bank;
+	u32 bit;
+
+	schip =	to_spacemit_gpio_chip(chip);
+	bank = spacemit_gpio_get_bank(schip, offset);
+	bit = BIT(spacemit_gpio_to_bank_offset(offset));
+
+	writel(bit, bank->reg_bank + GCDR);
+
+	return 0;
+}
+
+static int spacemit_gpio_direction_output(struct gpio_chip *chip,
+					  unsigned int offset, int value)
+{
+	struct spacemit_gpio_chip *schip;
+	struct spacemit_gpio_bank *bank;
+	u32 bit;
+
+	schip =	to_spacemit_gpio_chip(chip);
+	bank = spacemit_gpio_get_bank(schip, offset);
+	bit = BIT(spacemit_gpio_to_bank_offset(offset));
+
+	writel(bit, bank->reg_bank + (value ? GPSR : GPCR));
+	writel(bit, bank->reg_bank + GSDR);
+
+	return 0;
+}
+
+static int spacemit_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct spacemit_gpio_chip *schip;
+	struct spacemit_gpio_bank *bank;
+	u32 bit, gplr;
+
+	schip =	to_spacemit_gpio_chip(chip);
+	bank = spacemit_gpio_get_bank(schip, offset);
+	bit = BIT(spacemit_gpio_to_bank_offset(offset));
+
+	gplr = readl(bank->reg_bank + GPLR);
+
+	return !!(gplr & bit);
+}
+
+static void spacemit_gpio_set(struct gpio_chip *chip,
+			      unsigned int offset, int value)
+{
+	struct spacemit_gpio_chip *schip;
+	struct spacemit_gpio_bank *bank;
+	u32 bit, gpdr;
+
+	schip =	to_spacemit_gpio_chip(chip);
+	bank = spacemit_gpio_get_bank(schip, offset);
+	bit = BIT(spacemit_gpio_to_bank_offset(offset));
+	gpdr = readl(bank->reg_bank + GPDR);
+
+	/* Is it configured as output? */
+	if (gpdr & bit)
+		writel(bit, bank->reg_bank + (value ? GPSR : GPCR));
+}
+
+#ifdef CONFIG_OF_GPIO
+static int spacemit_gpio_of_xlate(struct gpio_chip *chip,
+				  const struct of_phandle_args *gpiospec,
+				  u32 *flags)
+{
+	struct spacemit_gpio_chip *schip;
+
+	schip =	to_spacemit_gpio_chip(chip);
+	/* GPIO index start from 0. */
+	if (gpiospec->args[0] >= schip->ngpio)
+		return -EINVAL;
+
+	if (flags)
+		*flags = gpiospec->args[1];
+
+	return gpiospec->args[0];
+}
+#endif
+
+static int spacemit_gpio_irq_type(struct irq_data *d, unsigned int type)
+{
+	struct spacemit_gpio_chip *schip;
+	struct spacemit_gpio_bank *bank;
+	int gpio = irqd_to_hwirq(d);
+	u32 bit;
+
+	schip = irq_data_get_irq_chip_data(d);
+	bank = spacemit_gpio_get_bank(schip, gpio);
+	bit = BIT(spacemit_gpio_to_bank_offset(gpio));
+
+	if (type & IRQ_TYPE_EDGE_RISING) {
+		bank->irq_rising_edge |= bit;
+		writel(bit, bank->reg_bank + GSRER);
+	} else {
+		bank->irq_rising_edge &= ~bit;
+		writel(bit, bank->reg_bank + GCRER);
+	}
+
+	if (type & IRQ_TYPE_EDGE_FALLING) {
+		bank->irq_falling_edge |= bit;
+		writel(bit, bank->reg_bank + GSFER);
+	} else {
+		bank->irq_falling_edge &= ~bit;
+		writel(bit, bank->reg_bank + GCFER);
+	}
+
+	return 0;
+}
+
+static irqreturn_t spacemit_gpio_irq_handler(int irq, void *data)
+{
+	struct spacemit_gpio_chip *schip = data;
+	struct spacemit_gpio_bank *bank;
+	unsigned int irqs_handled = 0;
+	unsigned long pending = 0;
+	u32 gedr, girq;
+	int i, n;
+
+	for (i = 0; i < schip->nbank; i++) {
+		bank = &schip->banks[i];
+
+		gedr = readl(bank->reg_bank + GEDR);
+		if (!gedr)
+			continue;
+
+		writel(gedr, bank->reg_bank + GEDR);
+		gedr = gedr & bank->irq_mask;
+
+		if (!gedr)
+			continue;
+
+		pending = gedr;
+		for_each_set_bit(n, &pending, BITS_PER_LONG) {
+			girq = irq_find_mapping(schip->domain,
+						spacemit_bank_to_gpio(i, n));
+			handle_nested_irq(girq);
+		}
+
+		irqs_handled++;
+	}
+
+	return irqs_handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static void spacemit_ack_muxed_gpio(struct irq_data *d)
+{
+	struct spacemit_gpio_chip *schip;
+	struct spacemit_gpio_bank *bank;
+	int gpio = irqd_to_hwirq(d);
+	u32 bit;
+
+	schip = irq_data_get_irq_chip_data(d);
+	bank = spacemit_gpio_get_bank(schip, gpio);
+	bit = BIT(spacemit_gpio_to_bank_offset(gpio));
+
+	writel(bit, bank->reg_bank + GEDR);
+}
+
+static void spacemit_mask_muxed_gpio(struct irq_data *d)
+{
+	struct spacemit_gpio_chip *schip;
+	struct spacemit_gpio_bank *bank;
+	int gpio = irqd_to_hwirq(d);
+	u32 bit;
+
+	schip = irq_data_get_irq_chip_data(d);
+	bank = spacemit_gpio_get_bank(schip, gpio);
+	bit = BIT(spacemit_gpio_to_bank_offset(gpio));
+
+	bank->irq_mask &= ~bit;
+
+	spacemit_clear_edge_detection(bank, bit);
+}
+
+static void spacemit_unmask_muxed_gpio(struct irq_data *d)
+{
+	struct spacemit_gpio_chip *schip;
+	struct spacemit_gpio_bank *bank;
+	int gpio = irqd_to_hwirq(d);
+	u32 bit;
+
+	schip = irq_data_get_irq_chip_data(d);
+	bank = spacemit_gpio_get_bank(schip, gpio);
+	bit = BIT(spacemit_gpio_to_bank_offset(gpio));
+
+	bank->irq_mask |= bit;
+
+	spacemit_set_edge_detection(bank, bit);
+}
+
+static struct irq_chip spacemit_muxed_gpio_chip = {
+	.name		= "k1-gpio-irqchip",
+	.irq_ack	= spacemit_ack_muxed_gpio,
+	.irq_mask	= spacemit_mask_muxed_gpio,
+	.irq_unmask	= spacemit_unmask_muxed_gpio,
+	.irq_set_type	= spacemit_gpio_irq_type,
+	.flags		= IRQCHIP_SKIP_SET_WAKE,
+	GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int spacemit_irq_domain_map(struct irq_domain *d, unsigned int irq,
+				   irq_hw_number_t hw)
+{
+	irq_set_chip_data(irq, d->host_data);
+	irq_set_chip_and_handler(irq, &spacemit_muxed_gpio_chip,
+				 handle_edge_irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops spacemit_gpio_irq_domain_ops = {
+	.map	= spacemit_irq_domain_map,
+	.xlate	= irq_domain_xlate_twocell,
+};
+
+static int spacemit_gpio_probe_dt(struct platform_device *pdev,
+				  struct spacemit_gpio_chip *schip)
+{
+	void __iomem *reg;
+	int i, nbank;
+
+	nbank = ARRAY_SIZE(k1_gpio_bank_offset);
+
+	schip->banks = devm_kzalloc(&pdev->dev,
+				    sizeof(*schip->banks) * nbank,
+				    GFP_KERNEL);
+	if (!schip->banks)
+		return -ENOMEM;
+
+	for (i = 0; i < nbank; i++) {
+		reg = schip->reg_base + k1_gpio_bank_offset[i];
+		schip->banks[i].reg_bank = reg;
+	}
+
+	schip->nbank = nbank;
+	schip->ngpio = nbank * K1_BANK_GPIO_NUMBER;
+
+	return 0;
+}
+
+static void spacemit_gpio_remove_irq_domain(void *data)
+{
+	struct irq_domain *domain = data;
+
+	irq_domain_remove(domain);
+}
+
+static int spacemit_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np;
+	struct spacemit_gpio_chip *schip;
+	struct irq_domain *domain;
+	struct resource *res;
+	void __iomem *base;
+	int ret;
+
+	np = pdev->dev.of_node;
+	if (!np)
+		return -EINVAL;
+
+	schip = devm_kzalloc(dev, sizeof(*schip), GFP_KERNEL);
+	if (!schip)
+		return -ENOMEM;
+
+	schip->irq = platform_get_irq(pdev, 0);
+	if (schip->irq < 0)
+		return schip->irq;
+
+	base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	schip->reg_base = base;
+
+	ret = spacemit_gpio_probe_dt(pdev, schip);
+	if (ret)
+		return dev_err_probe(dev, ret, "Fail to initialize gpio unit\n");
+
+	spacemit_reset_edge_detection(schip);
+
+	domain = irq_domain_add_linear(np, schip->ngpio,
+				       &spacemit_gpio_irq_domain_ops,
+				       schip);
+	if (domain == NULL)
+		return -EINVAL;
+
+	ret = devm_add_action_or_reset(dev, spacemit_gpio_remove_irq_domain,
+				       domain);
+	if (ret)
+		return ret;
+
+	schip->domain			= domain;
+	schip->chip.label		= "k1-gpio";
+	schip->chip.parent		= dev;
+	schip->chip.request		= gpiochip_generic_request;
+	schip->chip.free		= gpiochip_generic_free;
+	schip->chip.direction_input	= spacemit_gpio_direction_input;
+	schip->chip.direction_output	= spacemit_gpio_direction_output;
+	schip->chip.get			= spacemit_gpio_get;
+	schip->chip.set			= spacemit_gpio_set;
+	schip->chip.to_irq		= spacemit_gpio_to_irq;
+#ifdef CONFIG_OF_GPIO
+	schip->chip.of_xlate		= spacemit_gpio_of_xlate;
+	schip->chip.of_gpio_n_cells	= 2;
+#endif
+	schip->chip.ngpio		= schip->ngpio;
+	schip->chip.base		= -1;
+
+	ret = devm_request_threaded_irq(dev, schip->irq, NULL,
+					spacemit_gpio_irq_handler,
+					IRQF_ONESHOT,
+					schip->chip.label, schip);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "failed to request high IRQ\n");
+
+	ret = devm_gpiochip_add_data(dev, &schip->chip, schip);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "failed to add gpiochip\n");
+
+	return 0;
+}
+
+static const struct of_device_id spacemit_gpio_dt_ids[] = {
+	{ .compatible = "spacemit,k1-gpio" },
+	{ /* sentinel */ }
+};
+
+static struct platform_driver spacemit_gpio_driver = {
+	.probe		= spacemit_gpio_probe,
+	.driver		= {
+		.name	= "k1-gpio",
+		.of_match_table = spacemit_gpio_dt_ids,
+	},
+};
+module_platform_driver(spacemit_gpio_driver);
+
+MODULE_DESCRIPTION("GPIO driver for SpacemiT K1 SoC");
+MODULE_LICENSE("GPL");

-- 
2.47.0