Message ID | 20250217-03-k1-gpio-v5-3-2863ec3e7b67@gentoo.org (mailing list archive) |
---|---|
State | Handled Elsewhere |
Headers | show |
Series | riscv: spacemit: add gpio support for K1 SoC | expand |
Context | Check | Description |
---|---|---|
bjorn/pre-ci_am | success | Success |
bjorn/build-rv32-defconfig | success | build-rv32-defconfig |
bjorn/build-rv64-clang-allmodconfig | success | build-rv64-clang-allmodconfig |
bjorn/build-rv64-gcc-allmodconfig | success | build-rv64-gcc-allmodconfig |
bjorn/build-rv64-nommu-k210-defconfig | success | build-rv64-nommu-k210-defconfig |
bjorn/build-rv64-nommu-k210-virt | success | build-rv64-nommu-k210-virt |
bjorn/checkpatch | warning | checkpatch |
bjorn/dtb-warn-rv64 | success | dtb-warn-rv64 |
bjorn/header-inline | success | header-inline |
bjorn/kdoc | success | kdoc |
bjorn/module-param | success | module-param |
bjorn/verify-fixes | success | verify-fixes |
bjorn/verify-signedoff | success | verify-signedoff |
On Mon, Feb 17, 2025 at 1:58 PM Yixun Lan <dlan@gentoo.org> wrote: > > Implement GPIO functionality which capable of setting pin as > input, output. Also, each pin can be used as interrupt which > support rising, failing, or both edge type trigger. > > Signed-off-by: Yixun Lan <dlan@gentoo.org> > --- > drivers/gpio/Kconfig | 8 + > drivers/gpio/Makefile | 1 + > drivers/gpio/gpio-spacemit-k1.c | 376 ++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 385 insertions(+) > > diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig > index add5ad29a673c09082a913cb2404073b2034af48..eaae729eec00a3d6d2b83769aed3e2b0ca9927e5 100644 > --- a/drivers/gpio/Kconfig > +++ b/drivers/gpio/Kconfig > @@ -655,6 +655,14 @@ 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 > + depends on OF_GPIO > + 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..f72511b5ab8f8f0b1d1c9e89d2f9ca07b623a866 > --- /dev/null > +++ b/drivers/gpio/gpio-spacemit-k1.c > @@ -0,0 +1,376 @@ > +// 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/io.h> > +#include <linux/init.h> > +#include <linux/irq.h> > +#include <linux/interrupt.h> > +#include <linux/gpio/driver.h> > +#include <linux/platform_device.h> > +#include <linux/pinctrl/pinctrl.h> > +#include <linux/property.h> > +#include <linux/seq_file.h> > +#include <linux/module.h> > + Please order includes alphabetically. > +#include "gpiolib.h" > + > +/* register offset */ > +/* GPIO port level register */ > +#define GPLR 0x00 > +/* GPIO port direction register - R/W */ > +#define GPDR 0x0c > +/* GPIO port set register - W */ > +#define GPSR 0x18 > +/* GPIO port clear register - W */ > +#define GPCR 0x24 > +/* GPIO port rising edge register R/W */ > +#define GRER 0x30 > +/* GPIO port falling edge register R/W */ > +#define GFER 0x3c > +/* GPIO edge detect status register - R/W1C */ > +#define GEDR 0x48 > +/* GPIO (set) direction register - W */ > +#define GSDR 0x54 > +/* GPIO (clear) direction register - W */ > +#define GCDR 0x60 > +/* GPIO (set) rising edge detect enable register - W */ > +#define GSRER 0x6c > +/* GPIO (clear) rising edge detect enable register - W */ > +#define GCRER 0x78 > +/* GPIO (set) falling edge detect enable register - W */ > +#define GSFER 0x84 > +/* GPIO (clear) falling edge detect enable register - W */ > +#define GCFER 0x90 > +/* GPIO interrupt mask register, 0 disable, 1 enable - R/W */ > +#define GAPMASK 0x9c > + > +#define NR_BANKS 4 > +#define NR_GPIOS_PER_BANK 32 > + Please use a common prefix for all symbols, even for defines. Use SPACEMIT_GCFER, etc. > +#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 index; > + u32 irq_mask; > + u32 irq_rising_edge; > + u32 irq_falling_edge; > +}; > + > +struct spacemit_gpio { > + struct device *dev; > + struct spacemit_gpio_bank sgb[NR_BANKS]; > +}; Please don't use tabs in struct definitions. > + > +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 + GEDR); > + if (!gedr) > + return IRQ_NONE; > + writel(gedr, gb->base + GEDR); > + > + gedr = gedr & gb->irq_mask; > + if (!gedr) > + return IRQ_NONE; > + > + pending = gedr; > + 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 + 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; > + > + if (bit & gb->irq_rising_edge) > + writel(bit, gb->base + GCRER); > + > + if (bit & gb->irq_falling_edge) > + writel(bit, gb->base + 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 + GSRER); > + > + if (bit & gb->irq_falling_edge) > + writel(bit, gb->base + GSFER); > +} > + > +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 + GSRER); > + } else { > + gb->irq_rising_edge &= ~bit; > + writel(bit, gb->base + GCRER); > + } > + > + if (type & IRQ_TYPE_EDGE_FALLING) { > + gb->irq_falling_edge |= bit; > + writel(bit, gb->base + GSFER); > + } else { > + gb->irq_falling_edge &= ~bit; > + writel(bit, gb->base + 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), gb->index); > +} > + > +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, > + GPIOCHIP_IRQ_RESOURCE_HELPERS, > +}; > + > +static int spacemit_gpio_xlate(struct gpio_chip *gc, > + const struct of_phandle_args *gpiospec, u32 *flags) > +{ > + struct spacemit_gpio_bank *gb = gpiochip_get_data(gc); > + struct spacemit_gpio *sg = gb->sg; > + Stray newline. > + int i; > + > + if (gc->of_gpio_n_cells != 3) > + return -EINVAL; > + > + if (gpiospec->args_count < gc->of_gpio_n_cells) > + return -EINVAL; > + > + i = gpiospec->args[0]; > + if (i >= NR_BANKS) > + return -EINVAL; > + > + if (gc != &sg->sgb[i].gc) > + return -EINVAL; > + > + if (gpiospec->args[1] >= gc->ngpio) > + return -EINVAL; > + > + if (flags) > + *flags = gpiospec->args[2]; > + > + return gpiospec->args[1]; > +} > + > +static int spacemit_add_pin_range(struct gpio_chip *gc) > +{ > + struct spacemit_gpio_bank *gb; > + struct of_phandle_args pinspec; > + struct pinctrl_dev *pctldev; > + struct device_node *np; > + int ret, trim; > + > + np = dev_of_node(&gc->gpiodev->dev); > + if (!np) > + return 0; > + > + gb = to_spacemit_gpio_bank(gc); > + > + ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, > + gb->index, &pinspec); > + if (ret) > + return ret; > + > + pctldev = of_pinctrl_get(pinspec.np); > + of_node_put(pinspec.np); > + if (!pctldev) > + return -EPROBE_DEFER; > + > + /* Ignore ranges outside of this GPIO chip */ > + if (pinspec.args[0] >= (gc->offset + gc->ngpio)) > + return -EINVAL; > + > + if (pinspec.args[0] + pinspec.args[2] <= gc->offset) > + return -EINVAL; > + > + if (!pinspec.args[2]) > + return -EINVAL; > + > + /* Trim the range to fit this GPIO chip */ > + if (gc->offset > pinspec.args[0]) { > + trim = gc->offset - pinspec.args[0]; > + pinspec.args[2] -= trim; > + pinspec.args[1] += trim; > + pinspec.args[0] = 0; > + } else { > + pinspec.args[0] -= gc->offset; > + } > + if ((pinspec.args[0] + pinspec.args[2]) > gc->ngpio) > + pinspec.args[2] = gc->ngpio - pinspec.args[0]; > + > + ret = gpiochip_add_pin_range(gc, > + pinctrl_dev_get_devname(pctldev), > + pinspec.args[0], > + pinspec.args[1], > + pinspec.args[2]); > + if (ret) > + return ret; > + > + return 0; > +} > + > +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->index = index; > + gb->base = regs + bank_base[index]; > + > + dat = gb->base + GPLR; > + set = gb->base + GPSR; > + clr = gb->base + GPCR; > + dirin = gb->base + GCDR; > + dirout = gb->base + 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 = NR_GPIOS_PER_BANK; > + gc->base = -1; > + > +#ifdef CONFIG_OF_GPIO > + gc->of_xlate = spacemit_gpio_xlate; > + gc->of_add_pin_range = spacemit_add_pin_range; > + gc->of_gpio_n_cells = 3; > +#endif > + > + girq = &gc->irq; > + girq->threaded = true; > + girq->handler = handle_simple_irq; > + > + gpio_irq_chip_set_chip(girq, &spacemit_gpio_chip); > + > + /* Clear Edge Detection Settings */ > + writel(0x0, gb->base + GRER); > + writel(0x0, gb->base + GFER); > + /* Clear and Disable Interrupt */ > + writel(0xffffffff, gb->base + GCFER); > + writel(0xffffffff, gb->base + GCRER); > + writel(0, gb->base + GAPMASK); > + > + 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 < 0) > + return dev_err_probe(dev, ret, "failed to add gpio chip\n"); > + > + /* Eable Interrupt */ Enable. > + writel(0xffffffff, gb->base + GAPMASK); > + > + return 0; > +} > + > +static int spacemit_gpio_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct spacemit_gpio *sg; > + struct resource *res; > + void __iomem *regs; > + int i, irq, ret; > + > + sg = devm_kzalloc(dev, sizeof(*sg), GFP_KERNEL); > + if (!sg) > + return -ENOMEM; > + > + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); > + if (IS_ERR(regs)) > + return PTR_ERR(regs); > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) > + return irq; > + > + sg->dev = dev; > + > + for (i = 0; i < 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"); > > -- > 2.48.1 > Bart
On 2025/2/20 21:34, Bartosz Golaszewski wrote: > On Mon, Feb 17, 2025 at 1:58 PM Yixun Lan <dlan@gentoo.org> wrote: [......] >> +#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 index; >> + u32 irq_mask; >> + u32 irq_rising_edge; >> + u32 irq_falling_edge; >> +}; >> + >> +struct spacemit_gpio { >> + struct device *dev; >> + struct spacemit_gpio_bank sgb[NR_BANKS]; >> +}; > Please don't use tabs in struct definitions. Why not?I see https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#struct-declarations-and-initializers [......] > _______________________________________________ > linux-riscv mailing list > linux-riscv@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-riscv
On Fri, Feb 21, 2025 at 12:36 AM Chen Wang <unicorn_wang@outlook.com> wrote: > > > On 2025/2/20 21:34, Bartosz Golaszewski wrote: > > On Mon, Feb 17, 2025 at 1:58 PM Yixun Lan <dlan@gentoo.org> wrote: > [......] > >> +#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 index; > >> + u32 irq_mask; > >> + u32 irq_rising_edge; > >> + u32 irq_falling_edge; > >> +}; > >> + > >> +struct spacemit_gpio { > >> + struct device *dev; > >> + struct spacemit_gpio_bank sgb[NR_BANKS]; > >> +}; > > Please don't use tabs in struct definitions. > > Why not?I see > https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#struct-declarations-and-initializers > This is for the tip tree, not treewide. It's my personal maintainer preference. We do use both under drivers/gpio/ but I prefer no-tabs in new code. Bart
Hi Bartosz Golaszewski: On 09:37 Fri 21 Feb , Bartosz Golaszewski wrote: > On Fri, Feb 21, 2025 at 12:36 AM Chen Wang <unicorn_wang@outlook.com> wrote: > > > > > > On 2025/2/20 21:34, Bartosz Golaszewski wrote: > > > On Mon, Feb 17, 2025 at 1:58 PM Yixun Lan <dlan@gentoo.org> wrote: > > [......] > > >> +#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 index; > > >> + u32 irq_mask; > > >> + u32 irq_rising_edge; > > >> + u32 irq_falling_edge; > > >> +}; > > >> + > > >> +struct spacemit_gpio { > > >> + struct device *dev; > > >> + struct spacemit_gpio_bank sgb[NR_BANKS]; > > >> +}; > > > Please don't use tabs in struct definitions. > > > > Why not?I see > > https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#struct-declarations-and-initializers > > > > This is for the tip tree, not treewide. > > It's my personal maintainer preference. We do use both under > drivers/gpio/ but I prefer no-tabs in new code. > thanks for this explanation.. my intention was trying to keep struct members aligned if tabs is a no-go, would using multi blank spaces to align be acceptable? something like: struct spacemit_gpio_bank { struct gpio_chip gc; struct spacemit_gpio *sg; void __iomem *base; ... } > Bart
On Fri, Feb 21, 2025 at 1:21 PM Yixun Lan <dlan@gentoo.org> wrote: > > Hi Bartosz Golaszewski: > > On 09:37 Fri 21 Feb , Bartosz Golaszewski wrote: > > On Fri, Feb 21, 2025 at 12:36 AM Chen Wang <unicorn_wang@outlook.com> wrote: > > > > > > > > > On 2025/2/20 21:34, Bartosz Golaszewski wrote: > > > > On Mon, Feb 17, 2025 at 1:58 PM Yixun Lan <dlan@gentoo.org> wrote: > > > [......] > > > >> +#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 index; > > > >> + u32 irq_mask; > > > >> + u32 irq_rising_edge; > > > >> + u32 irq_falling_edge; > > > >> +}; > > > >> + > > > >> +struct spacemit_gpio { > > > >> + struct device *dev; > > > >> + struct spacemit_gpio_bank sgb[NR_BANKS]; > > > >> +}; > > > > Please don't use tabs in struct definitions. > > > > > > Why not?I see > > > https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#struct-declarations-and-initializers > > > > > > > This is for the tip tree, not treewide. > > > > It's my personal maintainer preference. We do use both under > > drivers/gpio/ but I prefer no-tabs in new code. > > > > thanks for this explanation.. > > my intention was trying to keep struct members aligned > if tabs is a no-go, would using multi blank spaces to align be acceptable? > > something like: > > struct spacemit_gpio_bank { > struct gpio_chip gc; > struct spacemit_gpio *sg; > void __iomem *base; > ... > } > No, that's even worse. :/ Why are we even wasting time arguing, can you just use a single space? You'll get a driver merged and may just disappear from kernel development, Linus and I will have to maintain the code so we do get some degree of discretion when it comes to coding style. Bart
On 2/17/25 6:57 AM, Yixun Lan wrote: > Implement GPIO functionality which capable of setting pin as > input, output. Also, each pin can be used as interrupt which > support rising, failing, or both edge type trigger. > > Signed-off-by: Yixun Lan <dlan@gentoo.org> This looks nicer! I have some more comments, but they're pretty minor. > --- > drivers/gpio/Kconfig | 8 + > drivers/gpio/Makefile | 1 + > drivers/gpio/gpio-spacemit-k1.c | 376 ++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 385 insertions(+) > > diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig > index add5ad29a673c09082a913cb2404073b2034af48..eaae729eec00a3d6d2b83769aed3e2b0ca9927e5 100644 > --- a/drivers/gpio/Kconfig > +++ b/drivers/gpio/Kconfig > @@ -655,6 +655,14 @@ 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 > + depends on OF_GPIO > + 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..f72511b5ab8f8f0b1d1c9e89d2f9ca07b623a866 > --- /dev/null > +++ b/drivers/gpio/gpio-spacemit-k1.c > @@ -0,0 +1,376 @@ > +// 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/io.h> > +#include <linux/init.h> > +#include <linux/irq.h> > +#include <linux/interrupt.h> > +#include <linux/gpio/driver.h> > +#include <linux/platform_device.h> > +#include <linux/pinctrl/pinctrl.h> > +#include <linux/property.h> > +#include <linux/seq_file.h> > +#include <linux/module.h> > + > +#include "gpiolib.h" > + > +/* register offset */ The comments are great, but I think I'd like to see them be abbreviated further and added to the right of the definitions, if you can do that. I think you can drop "GPIO" and "register" in each one of them, and that might get you close to an 80 column limit. See what you can do. > +/* GPIO port level register */ I think the port level register is read-only, and you didn't include that annotation. > +#define GPLR 0x00 > +/* GPIO port direction register - R/W */ > +#define GPDR 0x0c > +/* GPIO port set register - W */ > +#define GPSR 0x18 > +/* GPIO port clear register - W */ > +#define GPCR 0x24 > +/* GPIO port rising edge register R/W */ > +#define GRER 0x30 > +/* GPIO port falling edge register R/W */ > +#define GFER 0x3c > +/* GPIO edge detect status register - R/W1C */ > +#define GEDR 0x48 > +/* GPIO (set) direction register - W */ Delete the extra space above. > +#define GSDR 0x54 > +/* GPIO (clear) direction register - W */ > +#define GCDR 0x60 > +/* GPIO (set) rising edge detect enable register - W */ > +#define GSRER 0x6c > +/* GPIO (clear) rising edge detect enable register - W */ > +#define GCRER 0x78 > +/* GPIO (set) falling edge detect enable register - W */ > +#define GSFER 0x84 > +/* GPIO (clear) falling edge detect enable register - W */ > +#define GCFER 0x90 > +/* GPIO interrupt mask register, 0 disable, 1 enable - R/W */ > +#define GAPMASK 0x9c > + > +#define NR_BANKS 4 > +#define 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 index; You almost never use the index field. It could easily be computed rather than stored: static u32 spacemit_gpio_bank_index(struct spacemit_gpio_bank *gb) { return (u32)(gb - gb->sg->sgb); } > + u32 irq_mask; > + u32 irq_rising_edge; > + u32 irq_falling_edge; > +}; > + > +struct spacemit_gpio { > + struct device *dev; > + struct spacemit_gpio_bank sgb[NR_BANKS]; > +}; > + > +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 + GEDR); > + if (!gedr) > + return IRQ_NONE; > + writel(gedr, gb->base + GEDR); > + > + gedr = gedr & gb->irq_mask; > + if (!gedr) > + return IRQ_NONE; > + > + pending = gedr; Instead, do: 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 + 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; This is a minor suggestion, and I'm not sure how much difference it makes. But here (and one or two more times below) you could avoid the writel() calls if you know the particular IRQ was already disabled. (Maybe that won't ever happen?) if (!(gb->irq_mask & bit)) return; gb->irq_mask &= !bit; ... This should work because in spacemit_gpio_add_bank() you reset all the IRQ state and disable all IRQs, so the cached copy of the irq_mask and the rising and falling edge masks should match reality. > + > + if (bit & gb->irq_rising_edge) > + writel(bit, gb->base + GCRER); > + > + if (bit & gb->irq_falling_edge) > + writel(bit, gb->base + 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)); > + Same thought here. if (gb->irq_mask & bit) return; > + gb->irq_mask |= bit; > + > + if (bit & gb->irq_rising_edge) > + writel(bit, gb->base + GSRER); > + > + if (bit & gb->irq_falling_edge) > + writel(bit, gb->base + GSFER); > +} > + > +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)); > + Same thought in this function, although it gets a little messier looking. > + if (type & IRQ_TYPE_EDGE_RISING) { > + gb->irq_rising_edge |= bit; > + writel(bit, gb->base + GSRER); > + } else { > + gb->irq_rising_edge &= ~bit; > + writel(bit, gb->base + GCRER); > + } Otherwise: if (type & IRQ_TYPE_EDGE_RISING) gb->irq_rising_edge |= bit; else gb->irq_rising_edge &= ~bit; writel(bit, gb->base + GSRER); and again below. > + > + if (type & IRQ_TYPE_EDGE_FALLING) { > + gb->irq_falling_edge |= bit; > + writel(bit, gb->base + GSFER); > + } else { > + gb->irq_falling_edge &= ~bit; > + writel(bit, gb->base + GCFER); > + } > + > + return 0; > +} > + You added this function in version 5 of the series. Please call attention to additions (or removals) like this in your cover page, and/or in notes at the top of this patch. > +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), gb->index); Does this look like "gpiochip2-15" or something? I wasn't able to find it in the debugfs file system. > +} > + > +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, Last time your flags value was IRQCHIP_SET_WAKE. Why the change? > + GPIOCHIP_IRQ_RESOURCE_HELPERS, > +}; > + Maybe you could add a comment indicating that gpiospec->args[] will contain: 0: bank index 1: GPIO offset within the bank 2: flags (And the GPIO chip instance number as Linus suggested.) > +static int spacemit_gpio_xlate(struct gpio_chip *gc, > + const struct of_phandle_args *gpiospec, u32 *flags) > +{ > + struct spacemit_gpio_bank *gb = gpiochip_get_data(gc); > + struct spacemit_gpio *sg = gb->sg; > + Get rid of the above blank line. > + int i; > + I'm not sure the context in which this runs. Can it be given arbitrary content from a DTB? Mainly I'm interested to know whether any of these checks can be eliminated. If it's called while parsing a DTB I can see why you'd need to verify all input values for validity. > + if (gc->of_gpio_n_cells != 3) > + return -EINVAL; > + > + if (gpiospec->args_count < gc->of_gpio_n_cells) > + return -EINVAL; > + > + i = gpiospec->args[0]; > + if (i >= NR_BANKS) > + return -EINVAL; > + > + if (gc != &sg->sgb[i].gc) > + return -EINVAL; > + > + if (gpiospec->args[1] >= gc->ngpio) > + return -EINVAL; > + > + if (flags) > + *flags = gpiospec->args[2]; > + > + return gpiospec->args[1]; > +} > + > +static int spacemit_add_pin_range(struct gpio_chip *gc) > +{ > + struct spacemit_gpio_bank *gb; > + struct of_phandle_args pinspec; > + struct pinctrl_dev *pctldev; > + struct device_node *np; > + int ret, trim; > + > + np = dev_of_node(&gc->gpiodev->dev); > + if (!np) > + return 0; > + > + gb = to_spacemit_gpio_bank(gc); > + > + ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, > + gb->index, &pinspec); > + if (ret) > + return ret; > + > + pctldev = of_pinctrl_get(pinspec.np); > + of_node_put(pinspec.np); > + if (!pctldev) > + return -EPROBE_DEFER; > + > + /* Ignore ranges outside of this GPIO chip */ > + if (pinspec.args[0] >= (gc->offset + gc->ngpio)) > + return -EINVAL; > + > + if (pinspec.args[0] + pinspec.args[2] <= gc->offset) > + return -EINVAL; > + I would do the following test earlier. > + if (!pinspec.args[2]) > + return -EINVAL; > + > + /* Trim the range to fit this GPIO chip */ > + if (gc->offset > pinspec.args[0]) { > + trim = gc->offset - pinspec.args[0]; > + pinspec.args[2] -= trim; > + pinspec.args[1] += trim; > + pinspec.args[0] = 0; > + } else { > + pinspec.args[0] -= gc->offset; > + } > + if ((pinspec.args[0] + pinspec.args[2]) > gc->ngpio) > + pinspec.args[2] = gc->ngpio - pinspec.args[0]; > + > + ret = gpiochip_add_pin_range(gc, > + pinctrl_dev_get_devname(pctldev), > + pinspec.args[0], > + pinspec.args[1], > + pinspec.args[2]); > + if (ret) > + return ret; > + > + return 0; Just do this: return gpiochip_add_pin_range(gc, pinctrl_dev_get_devname(pctldev), pinspec.args[0], pinspec.args[2]); > +} > + > +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->index = index; > + gb->base = regs + bank_base[index]; > + > + dat = gb->base + GPLR; > + set = gb->base + GPSR; > + clr = gb->base + GPCR; > + dirin = gb->base + GCDR; > + dirout = gb->base + 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 = NR_GPIOS_PER_BANK; > + gc->base = -1; > + > +#ifdef CONFIG_OF_GPIO Why are these lines conditionally defined? Is it intended to allow CONFIG_COMPILE_TEST to work? Your Kconfig states that this *depends on* OF_GPIO, so this is probably not needed. You don't define spacemit_gpio_xlate() earlier conditionally. Nor spacemit_add_pin_range(). > + gc->of_xlate = spacemit_gpio_xlate; > + gc->of_add_pin_range = spacemit_add_pin_range; > + gc->of_gpio_n_cells = 3; > +#endif > + > + girq = &gc->irq; > + girq->threaded = true; > + girq->handler = handle_simple_irq; > + > + gpio_irq_chip_set_chip(girq, &spacemit_gpio_chip); > + > + /* Clear Edge Detection Settings */ > + writel(0x0, gb->base + GRER); > + writel(0x0, gb->base + GFER); > + /* Clear and Disable Interrupt */ > + writel(0xffffffff, gb->base + GCFER); > + writel(0xffffffff, gb->base + GCRER); It seems that GAPMASK is an overall interrupt mask register. I assume that means that by writing 0 here, no interrupts of any kind will be generated for any of the 32 GPIO ports. If that's true, I would write this first, *then* disable the rising and falling edge detection interrupts, *then* clear any pending interrupts. Are there any interrupt types other than rising and falling edge? Does this just provide an atomic way to disable both types at once? If there are no other interrupt types maybe this could be used rather than disabling both types separately using GCFER etc. in spacemit_gpio_irq_*mask(). -Alex > + writel(0, gb->base + GAPMASK); > + > + 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 < 0) > + return dev_err_probe(dev, ret, "failed to add gpio chip\n"); > + > + /* Eable Interrupt */ > + writel(0xffffffff, gb->base + GAPMASK); > + > + return 0; > +} > + > +static int spacemit_gpio_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct spacemit_gpio *sg; > + struct resource *res; > + void __iomem *regs; > + int i, irq, ret; > + > + sg = devm_kzalloc(dev, sizeof(*sg), GFP_KERNEL); > + if (!sg) > + return -ENOMEM; > + > + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); > + if (IS_ERR(regs)) > + return PTR_ERR(regs); > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) > + return irq; > + > + sg->dev = dev; > + > + for (i = 0; i < 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"); >
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index add5ad29a673c09082a913cb2404073b2034af48..eaae729eec00a3d6d2b83769aed3e2b0ca9927e5 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -655,6 +655,14 @@ 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 + depends on OF_GPIO + 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..f72511b5ab8f8f0b1d1c9e89d2f9ca07b623a866 --- /dev/null +++ b/drivers/gpio/gpio-spacemit-k1.c @@ -0,0 +1,376 @@ +// 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/io.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio/driver.h> +#include <linux/platform_device.h> +#include <linux/pinctrl/pinctrl.h> +#include <linux/property.h> +#include <linux/seq_file.h> +#include <linux/module.h> + +#include "gpiolib.h" + +/* register offset */ +/* GPIO port level register */ +#define GPLR 0x00 +/* GPIO port direction register - R/W */ +#define GPDR 0x0c +/* GPIO port set register - W */ +#define GPSR 0x18 +/* GPIO port clear register - W */ +#define GPCR 0x24 +/* GPIO port rising edge register R/W */ +#define GRER 0x30 +/* GPIO port falling edge register R/W */ +#define GFER 0x3c +/* GPIO edge detect status register - R/W1C */ +#define GEDR 0x48 +/* GPIO (set) direction register - W */ +#define GSDR 0x54 +/* GPIO (clear) direction register - W */ +#define GCDR 0x60 +/* GPIO (set) rising edge detect enable register - W */ +#define GSRER 0x6c +/* GPIO (clear) rising edge detect enable register - W */ +#define GCRER 0x78 +/* GPIO (set) falling edge detect enable register - W */ +#define GSFER 0x84 +/* GPIO (clear) falling edge detect enable register - W */ +#define GCFER 0x90 +/* GPIO interrupt mask register, 0 disable, 1 enable - R/W */ +#define GAPMASK 0x9c + +#define NR_BANKS 4 +#define 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 index; + u32 irq_mask; + u32 irq_rising_edge; + u32 irq_falling_edge; +}; + +struct spacemit_gpio { + struct device *dev; + struct spacemit_gpio_bank sgb[NR_BANKS]; +}; + +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 + GEDR); + if (!gedr) + return IRQ_NONE; + writel(gedr, gb->base + GEDR); + + gedr = gedr & gb->irq_mask; + if (!gedr) + return IRQ_NONE; + + pending = gedr; + 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 + 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; + + if (bit & gb->irq_rising_edge) + writel(bit, gb->base + GCRER); + + if (bit & gb->irq_falling_edge) + writel(bit, gb->base + 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 + GSRER); + + if (bit & gb->irq_falling_edge) + writel(bit, gb->base + GSFER); +} + +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 + GSRER); + } else { + gb->irq_rising_edge &= ~bit; + writel(bit, gb->base + GCRER); + } + + if (type & IRQ_TYPE_EDGE_FALLING) { + gb->irq_falling_edge |= bit; + writel(bit, gb->base + GSFER); + } else { + gb->irq_falling_edge &= ~bit; + writel(bit, gb->base + 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), gb->index); +} + +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, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static int spacemit_gpio_xlate(struct gpio_chip *gc, + const struct of_phandle_args *gpiospec, u32 *flags) +{ + struct spacemit_gpio_bank *gb = gpiochip_get_data(gc); + struct spacemit_gpio *sg = gb->sg; + + int i; + + if (gc->of_gpio_n_cells != 3) + return -EINVAL; + + if (gpiospec->args_count < gc->of_gpio_n_cells) + return -EINVAL; + + i = gpiospec->args[0]; + if (i >= NR_BANKS) + return -EINVAL; + + if (gc != &sg->sgb[i].gc) + return -EINVAL; + + if (gpiospec->args[1] >= gc->ngpio) + return -EINVAL; + + if (flags) + *flags = gpiospec->args[2]; + + return gpiospec->args[1]; +} + +static int spacemit_add_pin_range(struct gpio_chip *gc) +{ + struct spacemit_gpio_bank *gb; + struct of_phandle_args pinspec; + struct pinctrl_dev *pctldev; + struct device_node *np; + int ret, trim; + + np = dev_of_node(&gc->gpiodev->dev); + if (!np) + return 0; + + gb = to_spacemit_gpio_bank(gc); + + ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, + gb->index, &pinspec); + if (ret) + return ret; + + pctldev = of_pinctrl_get(pinspec.np); + of_node_put(pinspec.np); + if (!pctldev) + return -EPROBE_DEFER; + + /* Ignore ranges outside of this GPIO chip */ + if (pinspec.args[0] >= (gc->offset + gc->ngpio)) + return -EINVAL; + + if (pinspec.args[0] + pinspec.args[2] <= gc->offset) + return -EINVAL; + + if (!pinspec.args[2]) + return -EINVAL; + + /* Trim the range to fit this GPIO chip */ + if (gc->offset > pinspec.args[0]) { + trim = gc->offset - pinspec.args[0]; + pinspec.args[2] -= trim; + pinspec.args[1] += trim; + pinspec.args[0] = 0; + } else { + pinspec.args[0] -= gc->offset; + } + if ((pinspec.args[0] + pinspec.args[2]) > gc->ngpio) + pinspec.args[2] = gc->ngpio - pinspec.args[0]; + + ret = gpiochip_add_pin_range(gc, + pinctrl_dev_get_devname(pctldev), + pinspec.args[0], + pinspec.args[1], + pinspec.args[2]); + if (ret) + return ret; + + return 0; +} + +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->index = index; + gb->base = regs + bank_base[index]; + + dat = gb->base + GPLR; + set = gb->base + GPSR; + clr = gb->base + GPCR; + dirin = gb->base + GCDR; + dirout = gb->base + 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 = NR_GPIOS_PER_BANK; + gc->base = -1; + +#ifdef CONFIG_OF_GPIO + gc->of_xlate = spacemit_gpio_xlate; + gc->of_add_pin_range = spacemit_add_pin_range; + gc->of_gpio_n_cells = 3; +#endif + + girq = &gc->irq; + girq->threaded = true; + girq->handler = handle_simple_irq; + + gpio_irq_chip_set_chip(girq, &spacemit_gpio_chip); + + /* Clear Edge Detection Settings */ + writel(0x0, gb->base + GRER); + writel(0x0, gb->base + GFER); + /* Clear and Disable Interrupt */ + writel(0xffffffff, gb->base + GCFER); + writel(0xffffffff, gb->base + GCRER); + writel(0, gb->base + GAPMASK); + + 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 < 0) + return dev_err_probe(dev, ret, "failed to add gpio chip\n"); + + /* Eable Interrupt */ + writel(0xffffffff, gb->base + GAPMASK); + + return 0; +} + +static int spacemit_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spacemit_gpio *sg; + struct resource *res; + void __iomem *regs; + int i, irq, ret; + + sg = devm_kzalloc(dev, sizeof(*sg), GFP_KERNEL); + if (!sg) + return -ENOMEM; + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + sg->dev = dev; + + for (i = 0; i < 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");
Implement GPIO functionality which capable of setting pin as input, output. Also, each pin can be used as interrupt which support rising, failing, or both edge type trigger. Signed-off-by: Yixun Lan <dlan@gentoo.org> --- drivers/gpio/Kconfig | 8 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-spacemit-k1.c | 376 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 385 insertions(+)