[PATCH v1 2/2] gpio: Add G7 Aspeed gpio controller driver

Billy Tsai billy_tsai at aspeedtech.com
Mon Aug 26 19:45:49 PDT 2024


Hi Andrew,

Thanks for your suggestion. As I understand it, you’re suggesting that this driver should share the
common parts with aspeed-gpio.c, correct?
However, I don’t think that’s necessary. You can treat it as a new GPIO controller because the
register layout is quite different from aspeed-gpio.c.
If I try to make it common, the driver could become too complex, potentially requiring a separate
gpio-aspeed-common.c and necessitating changes to the existing aspeed-gpio.c
Maybe the discussion of merging aspeed-gpio.c and this driver can be postponed until after this one
is accepted?

For Others, please see the reply inline.

Best regards,
Billy Tsai

> > In the 7th generation of the SoC from Aspeed, the control logic of the
> > GPIO controller has been updated to support per-pin control. Each pin now
> > has its own 32-bit register, allowing for individual control of the pin’s
> > value, direction, interrupt type, and other settings.
> >
> > Signed-off-by: Billy Tsai <billy_tsai at aspeedtech.com>
> > ---
> >  drivers/gpio/Kconfig          |   7 +
> >  drivers/gpio/Makefile         |   1 +
> >  drivers/gpio/gpio-aspeed-g7.c | 831 ++++++++++++++++++++++++++++++++++
> >  3 files changed, 839 insertions(+)
> >  create mode 100644 drivers/gpio/gpio-aspeed-g7.c
> >
> > diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> > index 58f43bcced7c..93f237429b92 100644
> > --- a/drivers/gpio/Kconfig
> > +++ b/drivers/gpio/Kconfig
> > @@ -172,6 +172,13 @@ config GPIO_ASPEED
> >       help
> >         Say Y here to support Aspeed AST2400 and AST2500 GPIO controllers.
> >
> > +config GPIO_ASPEED_G7
> > +     tristate "Aspeed G7 GPIO support"
> > +     depends on (ARCH_ASPEED || COMPILE_TEST) && OF_GPIO
> > +     select GPIOLIB_IRQCHIP
> > +     help
> > +       Say Y here to support Aspeed AST2700 GPIO controllers.
> > +
> >  config GPIO_ASPEED_SGPIO
> >       bool "Aspeed SGPIO support"
> >       depends on (ARCH_ASPEED || COMPILE_TEST) && OF_GPIO
> > diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> > index 64dd6d9d730d..e830291761ee 100644
> > --- a/drivers/gpio/Makefile
> > +++ b/drivers/gpio/Makefile
> > @@ -34,6 +34,7 @@ obj-$(CONFIG_GPIO_AMD_FCH)          += gpio-amd-fch.o
> >  obj-$(CONFIG_GPIO_AMDPT)             += gpio-amdpt.o
> >  obj-$(CONFIG_GPIO_ARIZONA)           += gpio-arizona.o
> >  obj-$(CONFIG_GPIO_ASPEED)            += gpio-aspeed.o
> > +obj-$(CONFIG_GPIO_ASPEED_G7)         += gpio-aspeed-g7.o
> >  obj-$(CONFIG_GPIO_ASPEED_SGPIO)              += gpio-aspeed-sgpio.o
> >  obj-$(CONFIG_GPIO_ATH79)             += gpio-ath79.o
> >  obj-$(CONFIG_GPIO_BCM_KONA)          += gpio-bcm-kona.o
> > diff --git a/drivers/gpio/gpio-aspeed-g7.c b/drivers/gpio/gpio-aspeed-g7.c
> > new file mode 100644
> > index 000000000000..dbca097de6ea
> > --- /dev/null
> > +++ b/drivers/gpio/gpio-aspeed-g7.c
> > @@ -0,0 +1,831 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Copyright 2024 Aspeed Technology Inc.
> > + *
> > + * Billy Tsai <billy_tsai at aspeedtech.com>
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/gpio/aspeed.h>

> I think you should either drop this include or rework the existing
> implementations so the coprocessor handshake works correctly.

The coprocessor handshake will be implemented later, so I will remove the related include for now.

> > +#include <linux/gpio/driver.h>
> > +#include <linux/hashtable.h>
> > +#include <linux/init.h>
> > +#include <linux/io.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/pinctrl/consumer.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/seq_file.h>
> > +#include <linux/spinlock.h>
> > +#include <linux/string.h>
> > +
> > +#include <asm/div64.h>
> > +
> > +#define GPIO_G7_IRQ_STS_BASE 0x100
> > +#define GPIO_G7_IRQ_STS_OFFSET(x) (GPIO_G7_IRQ_STS_BASE + (x) * 0x4)
> > +#define GPIO_G7_CTRL_REG_BASE 0x180
> > +#define GPIO_G7_CTRL_REG_OFFSET(x) (GPIO_G7_CTRL_REG_BASE + (x) * 0x4)
> > +#define GPIO_G7_OUT_DATA BIT(0)
> > +#define GPIO_G7_DIR BIT(1)
> > +#define GPIO_G7_IRQ_EN BIT(2)
> > +#define GPIO_G7_IRQ_TYPE0 BIT(3)
> > +#define GPIO_G7_IRQ_TYPE1 BIT(4)
> > +#define GPIO_G7_IRQ_TYPE2 BIT(5)
> > +#define GPIO_G7_RST_TOLERANCE BIT(6)
> > +#define GPIO_G7_DEBOUNCE_SEL GENMASK(8, 7)
> > +#define GPIO_G7_INPUT_MASK BIT(9)
> > +#define GPIO_G7_IRQ_STS BIT(12)
> > +#define GPIO_G7_IN_DATA BIT(13)

> Can you please add `CTRL` into these field macro names so it's clear
> they relate to the control register?

Okay.

> > +/*
> > + * The configuration of the following registers should be determined
> > + * outside of the GPIO driver.

> Where though?

The usage of the following registers hasn’t been implemented yet, so I will remove them for now.

> > + */
> > +#define GPIO_G7_PRIVILEGE_W_REG_BASE 0x810
> > +#define GPIO_G7_PRIVILEGE_W_REG_OFFSET(x) (GPIO_G7_PRIVILEGE_W_REG_BASE + ((x) >> 2) * 0x4)
> > +#define GPIO_G7_PRIVILEGE_R_REG_BASE 0x910
> > +#define GPIO_G7_PRIVILEGE_R_REG_OFFSET(x) (GPIO_G7_PRIVILEGE_R_REG_BASE + ((x) >> 2) * 0x4)
> > +#define GPIO_G7_IRQ_TARGET_REG_BASE 0xA10
> > +#define GPIO_G7_IRQ_TARGET_REG_OFFSET(x) (GPIO_G7_IRQ_TARGET_REG_BASE + ((x) >> 2) * 0x4)
> > +#define GPIO_G7_IRQ_TO_INTC2_18 BIT(0)
> > +#define GPIO_G7_IRQ_TO_INTC2_19 BIT(1)
> > +#define GPIO_G7_IRQ_TO_INTC2_20 BIT(2)
> > +#define GPIO_G7_IRQ_TO_SIO BIT(3)
> > +#define GPIO_G7_IRQ_TARGET_RESET_TOLERANCE BIT(6)
> > +#define GPIO_G7_IRQ_TARGET_W_PROTECT BIT(7)
> > +
> > +static inline u32 field_get(u32 _mask, u32 _val)
> > +{
> > +     return (((_val) & (_mask)) >> (ffs(_mask) - 1));
> > +}
> > +
> > +static inline u32 field_prep(u32 _mask, u32 _val)
> > +{
> > +     return (((_val) << (ffs(_mask) - 1)) & (_mask));
> > +}

> So linux/bitfield.h provides a lot of APIs along these lines, perhaps
> use them instead?

I will use FIELD_GET and FIELD_PREP to replace them.

> > +
> > +static inline void ast_write_bits(void __iomem *addr, u32 mask, u32 val)
> > +{
> > +     iowrite32((ioread32(addr) & ~(mask)) | field_prep(mask, val), addr);
> > +}
> > +
> > +static inline void ast_clr_bits(void __iomem *addr, u32 mask)
> > +{
> > +     iowrite32((ioread32(addr) & ~(mask)), addr);
> > +}
> > +
> > +struct aspeed_bank_props {
> > +     unsigned int bank;
> > +     u32 input;
> > +     u32 output;
> > +};
> > +
> > +struct aspeed_gpio_g7_config {
> > +     unsigned int nr_gpios;
> > +     const struct aspeed_bank_props *props;
> > +};
> > +
> > +/*
> > + * @offset_timer: Maps an offset to an @timer_users index, or zero if disabled
> > + * @timer_users: Tracks the number of users for each timer
> > + *
> > + * The @timer_users has four elements but the first element is unused. This is
> > + * to simplify accounting and indexing, as a zero value in @offset_timer
> > + * represents disabled debouncing for the GPIO. Any other value for an element
> > + * of @offset_timer is used as an index into @timer_users. This behaviour of
> > + * the zero value aligns with the behaviour of zero built from the timer
> > + * configuration registers (i.e. debouncing is disabled).
> > + */
> > +struct aspeed_gpio_g7 {
> > +     struct gpio_chip chip;
> > +     struct device *dev;
> > +     raw_spinlock_t lock;
> > +     void __iomem *base;
> > +     int irq;
> > +     const struct aspeed_gpio_g7_config *config;
> > +
> > +     u8 *offset_timer;
> > +     unsigned int timer_users[4];
> > +     struct clk *clk;
> > +
> > +     u32 *dcache;
> > +};
> > +
> > +/*
> > + * Note: The "value" register returns the input value sampled on the
> > + *       line even when the GPIO is configured as an output. Since
> > + *       that input goes through synchronizers, writing, then reading
> > + *       back may not return the written value right away.
> > + *
> > + *       The "rdata" register returns the content of the write latch
> > + *       and thus can be used to read back what was last written
> > + *       reliably.
> > + */
> > +
> > +static const int debounce_timers[4] = { 0x00, 0x04, 0x00, 0x08 };

> This is all largely copy/pasted from gpio-aspeed.c. Can we split it out
> and share the definitions?

Do you mean moving them into the common header file? 
The structure is fine, but I’m unsure about the debounce_timers. 
It’s a static array, so I don’t think it needs to be shared with gpio-aspeed.c.

> > +
> > +#define GPIO_BANK(x) ((x) >> 5)
> > +#define GPIO_OFFSET(x) ((x) & 0x1f)
> > +#define GPIO_BIT(x) BIT(GPIO_OFFSET(x))
> > +
> > +static inline bool is_bank_props_sentinel(const struct aspeed_bank_props *props)
> > +{
> > +     return !(props->input || props->output);
> > +}
> > +
> > +static inline const struct aspeed_bank_props *find_bank_props(struct aspeed_gpio_g7 *gpio,
> > +                                                           unsigned int offset)
> > +{
> > +     const struct aspeed_bank_props *props = gpio->config->props;
> > +
> > +     while (!is_bank_props_sentinel(props)) {
> > +             if (props->bank == GPIO_BANK(offset))
> > +                     return props;
> > +             props++;
> > +     }
> > +
> > +     return NULL;
> > +}
> > +
> > +static inline bool have_gpio(struct aspeed_gpio_g7 *gpio, unsigned int offset)
> > +{
> > +     const struct aspeed_bank_props *props = find_bank_props(gpio, offset);
> > +
> > +     if (offset > gpio->chip.ngpio)
> > +             return false;
> > +
> > +     return (!props || ((props->input | props->output) & GPIO_BIT(offset)));
> > +}
> > +
> > +static inline bool have_input(struct aspeed_gpio_g7 *gpio, unsigned int offset)
> > +{
> > +     const struct aspeed_bank_props *props = find_bank_props(gpio, offset);
> > +
> > +     return !props || (props->input & GPIO_BIT(offset));
> > +}
> > +
> > +#define have_irq(g, o) have_input((g), (o))
> > +#define have_debounce(g, o) have_input((g), (o))
> > +
> > +static inline bool have_output(struct aspeed_gpio_g7 *gpio, unsigned int offset)
> > +{
> > +     const struct aspeed_bank_props *props = find_bank_props(gpio, offset);
> > +
> > +     return !props || (props->output & GPIO_BIT(offset));
> > +}
> > +

> This is all common as well.

Moving them into the common header file?

> > +static int aspeed_gpio_g7_get(struct gpio_chip *gc, unsigned int offset)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(gc);
> > +     void __iomem *addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset);
> > +
> > +     return !!(field_get(GPIO_G7_IN_DATA, ioread32(addr)));
> > +}
> > +
> > +static void __aspeed_gpio_g7_set(struct gpio_chip *gc, unsigned int offset, int val)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(gc);
> > +     void __iomem *addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset);

> The rest of the implementation of this function is broadly the same as
> in gpio-aspeed.c. The main difference is accounting for the address to
> access and the bit to whack. If we define some callbacks that replace
> GPIO_BANK()/to_bank() and GPIO_BIT() that can account for the
> differences in register layout, perhaps this could be common?

> The trade-off is some complexity vs copy-paste, but there does seem to
> be an awful lot of the latter with only minor changes so far.

Do you mean I need to create a gpio-aspeed-common.c, define the necessary common APIs,
and have gpio-aspeed.c and this driver hook into those APIs? 

> > +     u32 reg;
> > +
> > +     reg = gpio->dcache[GPIO_BANK(offset)];
> > +
> > +     if (val)
> > +             reg |= GPIO_BIT(offset);
> > +     else
> > +             reg &= ~GPIO_BIT(offset);
> > +     gpio->dcache[GPIO_BANK(offset)] = reg;
> > +
> > +     ast_write_bits(addr, GPIO_G7_OUT_DATA, val);
> > +}
> > +
> > +static void aspeed_gpio_g7_set(struct gpio_chip *gc, unsigned int offset, int val)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(gc);
> > +     unsigned long flags;
> > +
> > +     raw_spin_lock_irqsave(&gpio->lock, flags);
> > +
> > +     __aspeed_gpio_g7_set(gc, offset, val);
> > +
> > +     raw_spin_unlock_irqrestore(&gpio->lock, flags);
> > +}
> > +
> > +static int aspeed_gpio_g7_dir_in(struct gpio_chip *gc, unsigned int offset)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(gc);
> > +     void __iomem *addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset);
> > +     unsigned long flags;
> > +
> > +     if (!have_input(gpio, offset))
> > +             return -EOPNOTSUPP;
> > +
> > +     raw_spin_lock_irqsave(&gpio->lock, flags);
> > +
> > +     ast_clr_bits(addr, GPIO_G7_DIR);
> > +
> > +     raw_spin_unlock_irqrestore(&gpio->lock, flags);
> > +
> > +     return 0;
> > +}
> > +
> > +static int aspeed_gpio_g7_dir_out(struct gpio_chip *gc, unsigned int offset, int val)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(gc);
> > +     void __iomem *addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset);
> > +     unsigned long flags;
> > +
> > +     if (!have_output(gpio, offset))
> > +             return -EOPNOTSUPP;
> > +
> > +     raw_spin_lock_irqsave(&gpio->lock, flags);
> > +
> > +     __aspeed_gpio_g7_set(gc, offset, val);
> > +     ast_write_bits(addr, GPIO_G7_DIR, 1);
> > +
> > +     raw_spin_unlock_irqrestore(&gpio->lock, flags);
> > +
> > +     return 0;
> > +}
> > +
> > +static int aspeed_gpio_g7_get_direction(struct gpio_chip *gc, unsigned int offset)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(gc);
> > +     void __iomem *addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset);
> > +     unsigned long flags;
> > +     u32 val;
> > +
> > +     if (!have_input(gpio, offset))
> > +             return GPIO_LINE_DIRECTION_OUT;
> > +
> > +     if (!have_output(gpio, offset))
> > +             return GPIO_LINE_DIRECTION_IN;
> > +
> > +     raw_spin_lock_irqsave(&gpio->lock, flags);
> > +
> > +     val = ioread32(addr) & GPIO_G7_DIR;
> > +
> > +     raw_spin_unlock_irqrestore(&gpio->lock, flags);
> > +
> > +     return val ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
> > +}

> On top of handling the differences in the register layout as I
> mentioned above, the main difference in these get/set implementations
> is dropping the calls through the coprocessor handshake APIs. If you
> stub out the implementation of the coprocessor APIs I think it's likely
> these can be common. To do that you would need to make them use
> callbacks into the SoC-specific driver. To stub out the implementation
> you could leave the callback pointer as NULL for now.

Same as above?

> > +
> > +static inline int irqd_to_aspeed_gpio_g7_data(struct irq_data *d, struct aspeed_gpio_g7 **gpio,
> > +                                           int *offset)
> > +{
> > +     struct aspeed_gpio_g7 *internal;
> > +
> > +     *offset = irqd_to_hwirq(d);
> > +
> > +     internal = irq_data_get_irq_chip_data(d);
> > +
> > +     /* This might be a bit of a questionable place to check */
> > +     if (!have_irq(internal, *offset))
> > +             return -EOPNOTSUPP;
> > +
> > +     *gpio = internal;
> > +
> > +     return 0;
> > +}

> You do have different data-types here (struct aspeed_gpio_g7), but
> possibly with appropriate struct definitions and use of container_of()
> by the caller, this could be common too?

> > +
> > +static void aspeed_gpio_g7_irq_ack(struct irq_data *d)
> > +{
> > +     struct aspeed_gpio_g7 *gpio;
> > +     unsigned long flags;
> > +     void __iomem *addr;
> > +     int rc, offset;
> > +
> > +     rc = irqd_to_aspeed_gpio_g7_data(d, &gpio, &offset);
> > +     if (rc)
> > +             return;
> > +
> > +     addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset);
> > +
> > +     raw_spin_lock_irqsave(&gpio->lock, flags);
> > +
> > +     ast_write_bits(addr, GPIO_G7_IRQ_STS, 1);
> > +
> > +     raw_spin_unlock_irqrestore(&gpio->lock, flags);
> > +}
> > +
> > +static void aspeed_gpio_g7_irq_set_mask(struct irq_data *d, bool set)
> > +{
> > +     struct aspeed_gpio_g7 *gpio;
> > +     unsigned long flags;
> > +     void __iomem *addr;
> > +     int rc, offset;
> > +
> > +     rc = irqd_to_aspeed_gpio_g7_data(d, &gpio, &offset);
> > +     if (rc)
> > +             return;
> > +
> > +     addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset);
> > +
> > +     /* Unmasking the IRQ */
> > +     if (set)
> > +             gpiochip_enable_irq(&gpio->chip, irqd_to_hwirq(d));
> > +
> > +     raw_spin_lock_irqsave(&gpio->lock, flags);
> > +
> > +     if (set)
> > +             ast_write_bits(addr, GPIO_G7_IRQ_EN, 1);
> > +     else
> > +             ast_clr_bits(addr, GPIO_G7_IRQ_EN);
> > +
> > +     raw_spin_unlock_irqrestore(&gpio->lock, flags);
> > +
> > +     /* Masking the IRQ */
> > +     if (!set)
> > +             gpiochip_disable_irq(&gpio->chip, irqd_to_hwirq(d));
> > +}
> > +
> > +static void aspeed_gpio_g7_irq_mask(struct irq_data *d)
> > +{
> > +     aspeed_gpio_g7_irq_set_mask(d, false);
> > +}
> > +
> > +static void aspeed_gpio_g7_irq_unmask(struct irq_data *d)
> > +{
> > +     aspeed_gpio_g7_irq_set_mask(d, true);
> > +}
> > +
> > +static int aspeed_gpio_g7_set_type(struct irq_data *d, unsigned int type)
> > +{
> > +     u32 type0 = 0;
> > +     u32 type1 = 0;
> > +     u32 type2 = 0;
> > +     irq_flow_handler_t handler;
> > +     struct aspeed_gpio_g7 *gpio;
> > +     unsigned long flags;
> > +     void __iomem *addr;
> > +     int rc, offset;
> > +
> > +     rc = irqd_to_aspeed_gpio_g7_data(d, &gpio, &offset);
> > +     if (rc)
> > +             return -EINVAL;
> > +     addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset);
> > +
> > +     switch (type & IRQ_TYPE_SENSE_MASK) {
> > +     case IRQ_TYPE_EDGE_BOTH:
> > +             type2 = 1;
> > +             fallthrough;
> > +     case IRQ_TYPE_EDGE_RISING:
> > +             type0 = 1;
> > +             fallthrough;
> > +     case IRQ_TYPE_EDGE_FALLING:
> > +             handler = handle_edge_irq;
> > +             break;
> > +     case IRQ_TYPE_LEVEL_HIGH:
> > +             type0 |= 1;
> > +             fallthrough;
> > +     case IRQ_TYPE_LEVEL_LOW:
> > +             type1 |= 1;
> > +             handler = handle_level_irq;
> > +             break;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +
> > +     raw_spin_lock_irqsave(&gpio->lock, flags);
> > +
> > +     ast_write_bits(addr, GPIO_G7_IRQ_TYPE2, type2);
> > +     ast_write_bits(addr, GPIO_G7_IRQ_TYPE1, type1);
> > +     ast_write_bits(addr, GPIO_G7_IRQ_TYPE0, type0);

> Can we write them as a field in the one call? They're all in the same
> register, which was not true in the previous controller register
> layout.

Okay.

> > +
> > +     raw_spin_unlock_irqrestore(&gpio->lock, flags);
> > +
> > +     irq_set_handler_locked(d, handler);
> > +
> > +     return 0;
> > +}
> > +
> > +static void aspeed_gpio_g7_irq_handler(struct irq_desc *desc)
> > +{
> > +     struct gpio_chip *gc = irq_desc_get_handler_data(desc);
> > +     struct irq_chip *ic = irq_desc_get_chip(desc);
> > +     unsigned int i, p, banks;
> > +     unsigned long reg;
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(gc);
> > +     void __iomem *addr;
> > +
> > +     chained_irq_enter(ic, desc);
> > +
> > +     banks = DIV_ROUND_UP(gpio->chip.ngpio, 32);
> > +     for (i = 0; i < banks; i++) {
> > +             addr = gpio->base + GPIO_G7_IRQ_STS_OFFSET(i);
> > +
> > +             reg = ioread32(addr);
> > +
> > +             for_each_set_bit(p, &reg, 32)
> > +                     generic_handle_domain_irq(gc->irq.domain, i * 32 + p);
> > +     }
> > +
> > +     chained_irq_exit(ic, desc);
> > +}

> The only thing that's different for the IRQ status handling is the
> spread of the registers in the layout. In terms of the bits in each
> bank's IRQ status register, the layout is the same. By implementing the
> means to locate the status register as a callback this code could be
> common between the drivers.

> > +
> > +static void aspeed_init_irq_valid_mask(struct gpio_chip *gc, unsigned long *valid_mask,
> > +                                    unsigned int ngpios)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(gc);
> > +     const struct aspeed_bank_props *props = gpio->config->props;
> > +
> > +     while (!is_bank_props_sentinel(props)) {
> > +             unsigned int offset;
> > +             const unsigned long input = props->input;
> > +
> > +             /* Pretty crummy approach, but similar to GPIO core */
> > +             for_each_clear_bit(offset, &input, 32) {
> > +                     unsigned int i = props->bank * 32 + offset;
> > +
> > +                     if (i >= gpio->chip.ngpio)
> > +                             break;
> > +
> > +                     clear_bit(i, valid_mask);
> > +             }
> > +
> > +             props++;
> > +     }
> > +}

> This is the same except for the change to use `struct aspeed_gpio_g7`?
> Can we make this common?

> > +
> > +static int aspeed_gpio_g7_reset_tolerance(struct gpio_chip *chip, unsigned int offset, bool enable)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(chip);
> > +     unsigned long flags;
> > +     void __iomem *addr;
> > +
> > +     addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset);
> > +
> > +     raw_spin_lock_irqsave(&gpio->lock, flags);
> > +
> > +     if (enable)
> > +             ast_write_bits(addr, GPIO_G7_RST_TOLERANCE, 1);
> > +     else
> > +             ast_clr_bits(addr, GPIO_G7_RST_TOLERANCE);
> > +
> > +     raw_spin_unlock_irqrestore(&gpio->lock, flags);
> > +
> > +     return 0;
> > +}
> > +
> > +static int aspeed_gpio_g7_request(struct gpio_chip *chip, unsigned int offset)
> > +{
> > +     if (!have_gpio(gpiochip_get_data(chip), offset))
> > +             return -ENODEV;
> > +
> > +     return pinctrl_gpio_request(chip->base + offset);

> pinctrl_gpio_request() takes the chip and the offset value separately.
> It looks like you've developed this patch against an older kernel tree?

I will fix it.

> > +}
> > +
> > +static void aspeed_gpio_g7_free(struct gpio_chip *chip, unsigned int offset)
> > +{
> > +     pinctrl_gpio_free(chip->base + offset);

> Same as above for pinctrl_gpio_free().

I will fix it.

> > +}
> > +
> > +static int usecs_to_cycles(struct aspeed_gpio_g7 *gpio, unsigned long usecs, u32 *cycles)
> > +{
> > +     u64 rate;
> > +     u64 n;
> > +     u32 r;
> > +
> > +     rate = clk_get_rate(gpio->clk);
> > +     if (!rate)
> > +             return -EOPNOTSUPP;
> > +
> > +     n = rate * usecs;
> > +     r = do_div(n, 1000000);
> > +
> > +     if (n >= U32_MAX)
> > +             return -ERANGE;
> > +
> > +     /* At least as long as the requested time */
> > +     *cycles = n + (!!r);
> > +
> > +     return 0;
> > +}
> > +
> > +/* Call under gpio->lock */
> > +static int register_allocated_timer(struct aspeed_gpio_g7 *gpio, unsigned int offset,
> > +                                 unsigned int timer)
> > +{
> > +     if (WARN(gpio->offset_timer[offset] != 0, "Offset %d already allocated timer %d\n", offset,
> > +              gpio->offset_timer[offset]))
> > +             return -EINVAL;
> > +
> > +     if (WARN(gpio->timer_users[timer] == UINT_MAX, "Timer user count would overflow\n"))
> > +             return -EPERM;
> > +
> > +     gpio->offset_timer[offset] = timer;
> > +     gpio->timer_users[timer]++;
> > +
> > +     return 0;
> > +}
> > +
> > +/* Call under gpio->lock */
> > +static int unregister_allocated_timer(struct aspeed_gpio_g7 *gpio, unsigned int offset)
> > +{
> > +     if (WARN(gpio->offset_timer[offset] == 0, "No timer allocated to offset %d\n", offset))
> > +             return -EINVAL;
> > +
> > +     if (WARN(gpio->timer_users[gpio->offset_timer[offset]] == 0,
> > +              "No users recorded for timer %d\n", gpio->offset_timer[offset]))
> > +             return -EINVAL;
> > +
> > +     gpio->timer_users[gpio->offset_timer[offset]]--;
> > +     gpio->offset_timer[offset] = 0;
> > +
> > +     return 0;
> > +}
> > +
> > +/* Call under gpio->lock */
> > +static inline bool timer_allocation_registered(struct aspeed_gpio_g7 *gpio, unsigned int offset)
> > +{
> > +     return gpio->offset_timer[offset] > 0;
> > +}

> The above functions have all been copy/pasted, can we make them common?

> > +
> > +static void configure_timer(struct aspeed_gpio_g7 *gpio, unsigned int offset, unsigned int timer)
> > +{
> > +     void __iomem *addr = gpio->base + GPIO_G7_CTRL_REG_OFFSET(offset);
> > +
> > +     /* Note: Debounce timer isn't under control of the command
> > +      * source registers, so no need to sync with the coprocessor
> > +      */
> > +     ast_write_bits(addr, GPIO_G7_DEBOUNCE_SEL, timer);
> > +}
> > +
> > +static int enable_debounce(struct gpio_chip *chip, unsigned int offset, unsigned long usecs)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(chip);
> > +     u32 requested_cycles;
> > +     unsigned long flags;
> > +     int rc;
> > +     int i;
> > +
> > +     if (!gpio->clk)
> > +             return -EINVAL;
> > +
> > +     rc = usecs_to_cycles(gpio, usecs, &requested_cycles);
> > +     if (rc < 0) {
> > +             dev_warn(chip->parent, "Failed to convert %luus to cycles at %luHz: %d\n", usecs,
> > +                      clk_get_rate(gpio->clk), rc);
> > +             return rc;
> > +     }
> > +
> > +     raw_spin_lock_irqsave(&gpio->lock, flags);
> > +
> > +     if (timer_allocation_registered(gpio, offset)) {
> > +             rc = unregister_allocated_timer(gpio, offset);
> > +             if (rc < 0)
> > +                     goto out;
> > +     }
> > +
> > +     /* Try to find a timer already configured for the debounce period */
> > +     for (i = 1; i < ARRAY_SIZE(debounce_timers); i++) {
> > +             u32 cycles;
> > +
> > +             cycles = ioread32(gpio->base + debounce_timers[i]);
> > +             if (requested_cycles == cycles)
> > +                     break;
> > +     }
> > +
> > +     if (i == ARRAY_SIZE(debounce_timers)) {
> > +             int j;
> > +
> > +             /*
> > +              * As there are no timers configured for the requested debounce
> > +              * period, find an unused timer instead
> > +              */
> > +             for (j = 1; j < ARRAY_SIZE(gpio->timer_users); j++) {
> > +                     if (gpio->timer_users[j] == 0)
> > +                             break;
> > +             }
> > +
> > +             if (j == ARRAY_SIZE(gpio->timer_users)) {
> > +                     dev_warn(chip->parent,
> > +                              "Debounce timers exhausted, cannot debounce for period %luus\n",
> > +                              usecs);
> > +
> > +                     rc = -EPERM;
> > +
> > +                     /*
> > +                      * We already adjusted the accounting to remove @offset
> > +                      * as a user of its previous timer, so also configure
> > +                      * the hardware so @offset has timers disabled for
> > +                      * consistency.
> > +                      */
> > +                     configure_timer(gpio, offset, 0);
> > +                     goto out;
> > +             }
> > +
> > +             i = j;
> > +
> > +             iowrite32(requested_cycles, gpio->base + debounce_timers[i]);
> > +     }
> > +
> > +     if (WARN(i == 0, "Cannot register index of disabled timer\n")) {
> > +             rc = -EINVAL;
> > +             goto out;
> > +     }
> > +
> > +     register_allocated_timer(gpio, offset, i);
> > +     configure_timer(gpio, offset, i);
> > +
> > +out:
> > +     raw_spin_unlock_irqrestore(&gpio->lock, flags);
> > +
> > +     return rc;
> > +}
> > +
> > +static int disable_debounce(struct gpio_chip *chip, unsigned int offset)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(chip);
> > +     unsigned long flags;
> > +     int rc;
> > +
> > +     raw_spin_lock_irqsave(&gpio->lock, flags);
> > +
> > +     rc = unregister_allocated_timer(gpio, offset);
> > +     if (!rc)
> > +             configure_timer(gpio, offset, 0);
> > +
> > +     raw_spin_unlock_irqrestore(&gpio->lock, flags);
> > +
> > +     return rc;
> > +}
> > +
> > +static int set_debounce(struct gpio_chip *chip, unsigned int offset, unsigned long usecs)
> > +{
> > +     struct aspeed_gpio_g7 *gpio = gpiochip_get_data(chip);
> > +
> > +     if (!have_debounce(gpio, offset))
> > +             return -EOPNOTSUPP;
> > +
> > +     if (usecs)
> > +             return enable_debounce(chip, offset, usecs);
> > +
> > +     return disable_debounce(chip, offset);
> > +}

> These are all copy/pasted, save for changing to `struct
> aspeed_gpio_g7`. Can we make them common?

> > +
> > +static int aspeed_gpio_g7_set_config(struct gpio_chip *chip, unsigned int offset,
> > +                                  unsigned long config)
> > +{
> > +     unsigned long param = pinconf_to_config_param(config);
> > +     u32 arg = pinconf_to_config_argument(config);
> > +
> > +     if (param == PIN_CONFIG_INPUT_DEBOUNCE)
> > +             return set_debounce(chip, offset, arg);
> > +     else if (param == PIN_CONFIG_BIAS_DISABLE || param == PIN_CONFIG_BIAS_PULL_DOWN ||
> > +              param == PIN_CONFIG_DRIVE_STRENGTH)
> > +             return pinctrl_gpio_set_config(offset, config);
> > +     else if (param == PIN_CONFIG_DRIVE_OPEN_DRAIN || param == PIN_CONFIG_DRIVE_OPEN_SOURCE)
> > +             /* Return -EOPNOTSUPP to trigger emulation, as per datasheet */
> > +             return -EOPNOTSUPP;
> > +     else if (param == PIN_CONFIG_PERSIST_STATE)
> > +             return aspeed_gpio_g7_reset_tolerance(chip, offset, arg);
> > +
> > +     return -EOPNOTSUPP;
> > +}

> This is copy/paste, save for the call to
> aspeed_gpio_g7_reset_tolerance(). Can we make it common?


More information about the linux-arm-kernel mailing list