Commit d0055324 authored by Yixun Lan's avatar Yixun Lan Committed by Bartosz Golaszewski
Browse files

gpio: spacemit: add support for K1 SoC



Implement GPIO functionality which capable of setting pin as
input, output. Also, each pin can be used as interrupt which
support rising, falling, or both edge type trigger.

Reviewed-by: default avatarAlex Elder <elder@riscstar.com>
Reviewed-by: default avatarLinus Walleij <linus.walleij@linaro.org>
Signed-off-by: default avatarYixun Lan <dlan@gentoo.org>
Link: https://lore.kernel.org/r/20250412-03-k1-gpio-v8-2-1c6862d272ec@gentoo.org


Signed-off-by: default avatarBartosz Golaszewski <brgl@bgdev.pl>
parent 26bb7614
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -667,6 +667,15 @@ 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
	tristate "SPACEMIT K1 GPIO support"
	depends on ARCH_SPACEMIT || COMPILE_TEST
	depends on OF_GPIO
	select GPIO_GENERIC
	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
+1 −0
Original line number Diff line number Diff line
@@ -160,6 +160,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
+293 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
 * Copyright (C) 2023-2025 SpacemiT (Hangzhou) Technology Co. Ltd
 * Copyright (C) 2025 Yixun Lan <dlan@gentoo.org>
 */

#include <linux/clk.h>
#include <linux/gpio/driver.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>

/* register offset */
#define SPACEMIT_GPLR		0x00 /* port level - R */
#define SPACEMIT_GPDR		0x0c /* port direction - R/W */
#define SPACEMIT_GPSR		0x18 /* port set - W */
#define SPACEMIT_GPCR		0x24 /* port clear - W */
#define SPACEMIT_GRER		0x30 /* port rising edge R/W */
#define SPACEMIT_GFER		0x3c /* port falling edge R/W */
#define SPACEMIT_GEDR		0x48 /* edge detect status - R/W1C */
#define SPACEMIT_GSDR		0x54 /* (set) direction - W */
#define SPACEMIT_GCDR		0x60 /* (clear) direction - W */
#define SPACEMIT_GSRER		0x6c /* (set) rising edge detect enable - W */
#define SPACEMIT_GCRER		0x78 /* (clear) rising edge detect enable - W */
#define SPACEMIT_GSFER		0x84 /* (set) falling edge detect enable - W */
#define SPACEMIT_GCFER		0x90 /* (clear) falling edge detect enable - W */
#define SPACEMIT_GAPMASK	0x9c /* interrupt mask , 0 disable, 1 enable - R/W */

#define SPACEMIT_NR_BANKS		4
#define SPACEMIT_NR_GPIOS_PER_BANK	32

#define to_spacemit_gpio_bank(x) container_of((x), struct spacemit_gpio_bank, gc)

struct spacemit_gpio;

struct spacemit_gpio_bank {
	struct gpio_chip gc;
	struct spacemit_gpio *sg;
	void __iomem *base;
	u32 irq_mask;
	u32 irq_rising_edge;
	u32 irq_falling_edge;
};

struct spacemit_gpio {
	struct device *dev;
	struct spacemit_gpio_bank sgb[SPACEMIT_NR_BANKS];
};

static u32 spacemit_gpio_bank_index(struct spacemit_gpio_bank *gb)
{
	return (u32)(gb - gb->sg->sgb);
}

static irqreturn_t spacemit_gpio_irq_handler(int irq, void *dev_id)
{
	struct spacemit_gpio_bank *gb = dev_id;
	unsigned long pending;
	u32 n, gedr;

	gedr = readl(gb->base + SPACEMIT_GEDR);
	if (!gedr)
		return IRQ_NONE;
	writel(gedr, gb->base + SPACEMIT_GEDR);

	pending = gedr & gb->irq_mask;
	if (!pending)
		return IRQ_NONE;

	for_each_set_bit(n, &pending, BITS_PER_LONG)
		handle_nested_irq(irq_find_mapping(gb->gc.irq.domain, n));

	return IRQ_HANDLED;
}

static void spacemit_gpio_irq_ack(struct irq_data *d)
{
	struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);

	writel(BIT(irqd_to_hwirq(d)), gb->base + SPACEMIT_GEDR);
}

static void spacemit_gpio_irq_mask(struct irq_data *d)
{
	struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
	u32 bit = BIT(irqd_to_hwirq(d));

	gb->irq_mask &= ~bit;
	writel(gb->irq_mask, gb->base + SPACEMIT_GAPMASK);

	if (bit & gb->irq_rising_edge)
		writel(bit, gb->base + SPACEMIT_GCRER);

	if (bit & gb->irq_falling_edge)
		writel(bit, gb->base + SPACEMIT_GCFER);
}

static void spacemit_gpio_irq_unmask(struct irq_data *d)
{
	struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
	u32 bit = BIT(irqd_to_hwirq(d));

	gb->irq_mask |= bit;

	if (bit & gb->irq_rising_edge)
		writel(bit, gb->base + SPACEMIT_GSRER);

	if (bit & gb->irq_falling_edge)
		writel(bit, gb->base + SPACEMIT_GSFER);

	writel(gb->irq_mask, gb->base + SPACEMIT_GAPMASK);
}

static int spacemit_gpio_irq_set_type(struct irq_data *d, unsigned int type)
{
	struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
	u32 bit = BIT(irqd_to_hwirq(d));

	if (type & IRQ_TYPE_EDGE_RISING) {
		gb->irq_rising_edge |= bit;
		writel(bit, gb->base + SPACEMIT_GSRER);
	} else {
		gb->irq_rising_edge &= ~bit;
		writel(bit, gb->base + SPACEMIT_GCRER);
	}

	if (type & IRQ_TYPE_EDGE_FALLING) {
		gb->irq_falling_edge |= bit;
		writel(bit, gb->base + SPACEMIT_GSFER);
	} else {
		gb->irq_falling_edge &= ~bit;
		writel(bit, gb->base + SPACEMIT_GCFER);
	}

	return 0;
}

static void spacemit_gpio_irq_print_chip(struct irq_data *data, struct seq_file *p)
{
	struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(data);

	seq_printf(p, "%s-%d", dev_name(gb->gc.parent), spacemit_gpio_bank_index(gb));
}

static struct irq_chip spacemit_gpio_chip = {
	.name		= "k1-gpio-irqchip",
	.irq_ack	= spacemit_gpio_irq_ack,
	.irq_mask	= spacemit_gpio_irq_mask,
	.irq_unmask	= spacemit_gpio_irq_unmask,
	.irq_set_type	= spacemit_gpio_irq_set_type,
	.irq_print_chip	= spacemit_gpio_irq_print_chip,
	.flags		= IRQCHIP_IMMUTABLE | IRQCHIP_SKIP_SET_WAKE,
	GPIOCHIP_IRQ_RESOURCE_HELPERS,
};

static bool spacemit_of_node_instance_match(struct gpio_chip *gc, unsigned int i)
{
	struct spacemit_gpio_bank *gb = gpiochip_get_data(gc);
	struct spacemit_gpio *sg = gb->sg;

	if (i >= SPACEMIT_NR_BANKS)
		return false;

	return (gc == &sg->sgb[i].gc);
}

static int spacemit_gpio_add_bank(struct spacemit_gpio *sg,
				  void __iomem *regs,
				  int index, int irq)
{
	struct spacemit_gpio_bank *gb = &sg->sgb[index];
	struct gpio_chip *gc = &gb->gc;
	struct device *dev = sg->dev;
	struct gpio_irq_chip *girq;
	void __iomem *dat, *set, *clr, *dirin, *dirout;
	int ret, bank_base[] = { 0x0, 0x4, 0x8, 0x100 };

	gb->base = regs + bank_base[index];

	dat	= gb->base + SPACEMIT_GPLR;
	set	= gb->base + SPACEMIT_GPSR;
	clr	= gb->base + SPACEMIT_GPCR;
	dirin	= gb->base + SPACEMIT_GCDR;
	dirout	= gb->base + SPACEMIT_GSDR;

	/* This registers 32 GPIO lines per bank */
	ret = bgpio_init(gc, dev, 4, dat, set, clr, dirout, dirin,
			 BGPIOF_UNREADABLE_REG_SET | BGPIOF_UNREADABLE_REG_DIR);
	if (ret)
		return dev_err_probe(dev, ret, "failed to init gpio chip\n");

	gb->sg = sg;

	gc->label		= dev_name(dev);
	gc->request		= gpiochip_generic_request;
	gc->free		= gpiochip_generic_free;
	gc->ngpio		= SPACEMIT_NR_GPIOS_PER_BANK;
	gc->base		= -1;
	gc->of_gpio_n_cells	= 3;
	gc->of_node_instance_match = spacemit_of_node_instance_match;

	girq			= &gc->irq;
	girq->threaded		= true;
	girq->handler		= handle_simple_irq;

	gpio_irq_chip_set_chip(girq, &spacemit_gpio_chip);

	/* Disable Interrupt */
	writel(0, gb->base + SPACEMIT_GAPMASK);
	/* Disable Edge Detection Settings */
	writel(0x0, gb->base + SPACEMIT_GRER);
	writel(0x0, gb->base + SPACEMIT_GFER);
	/* Clear Interrupt */
	writel(0xffffffff, gb->base + SPACEMIT_GCRER);
	writel(0xffffffff, gb->base + SPACEMIT_GCFER);

	ret = devm_request_threaded_irq(dev, irq, NULL,
					spacemit_gpio_irq_handler,
					IRQF_ONESHOT | IRQF_SHARED,
					gb->gc.label, gb);
	if (ret < 0)
		return dev_err_probe(dev, ret, "failed to register IRQ\n");

	ret = devm_gpiochip_add_data(dev, gc, gb);
	if (ret)
		return ret;

	/* Distuingish IRQ domain, for selecting threecells mode */
	irq_domain_update_bus_token(girq->domain, DOMAIN_BUS_WIRED);

	return 0;
}

static int spacemit_gpio_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct spacemit_gpio *sg;
	struct clk *core_clk, *bus_clk;
	void __iomem *regs;
	int i, irq, ret;

	sg = devm_kzalloc(dev, sizeof(*sg), GFP_KERNEL);
	if (!sg)
		return -ENOMEM;

	regs = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(regs))
		return PTR_ERR(regs);

	irq = platform_get_irq(pdev, 0);
	if (irq < 0)
		return irq;

	sg->dev	= dev;

	core_clk = devm_clk_get_enabled(dev, "core");
	if (IS_ERR(core_clk))
		return dev_err_probe(dev, PTR_ERR(core_clk), "failed to get clock\n");

	bus_clk = devm_clk_get_enabled(dev, "bus");
	if (IS_ERR(bus_clk))
		return dev_err_probe(dev, PTR_ERR(bus_clk), "failed to get bus clock\n");

	for (i = 0; i < SPACEMIT_NR_BANKS; i++) {
		ret = spacemit_gpio_add_bank(sg, regs, i, irq);
		if (ret)
			return ret;
	}

	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_AUTHOR("Yixun Lan <dlan@gentoo.org>");
MODULE_DESCRIPTION("GPIO driver for SpacemiT K1 SoC");
MODULE_LICENSE("GPL");