[PATCH v1 2/2] gpio: Add G7 Aspeed gpio controller driver
Billy Tsai
billy_tsai at aspeedtech.com
Wed Aug 21 17:07:40 AEST 2024
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>
+#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)
+/*
+ * The configuration of the following registers should be determined
+ * outside of the GPIO driver.
+ */
+#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));
+}
+
+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 };
+
+#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));
+}
+
+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);
+ 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;
+}
+
+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;
+}
+
+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);
+
+ 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, ®, 32)
+ generic_handle_domain_irq(gc->irq.domain, i * 32 + p);
+ }
+
+ chained_irq_exit(ic, desc);
+}
+
+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++;
+ }
+}
+
+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);
+}
+
+static void aspeed_gpio_g7_free(struct gpio_chip *chip, unsigned int offset)
+{
+ pinctrl_gpio_free(chip->base + offset);
+}
+
+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;
+}
+
+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);
+}
+
+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;
+}
+
+static void aspeed_gpio_g7_irq_print_chip(struct irq_data *d, struct seq_file *p)
+{
+ struct aspeed_gpio_g7 *gpio;
+ int rc, offset;
+
+ rc = irqd_to_aspeed_gpio_g7_data(d, &gpio, &offset);
+ if (rc)
+ return;
+
+ seq_printf(p, dev_name(gpio->dev));
+}
+
+static const struct irq_chip aspeed_gpio_g7_irq_chip = {
+ .irq_ack = aspeed_gpio_g7_irq_ack,
+ .irq_mask = aspeed_gpio_g7_irq_mask,
+ .irq_unmask = aspeed_gpio_g7_irq_unmask,
+ .irq_set_type = aspeed_gpio_g7_set_type,
+ .irq_print_chip = aspeed_gpio_g7_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static const struct aspeed_bank_props ast2700_bank_props[] = {
+ /* input output */
+ { 1, 0x0fffffff, 0x0fffffff }, /* E/F/G/H, 4-GPIO hole */
+ { 6, 0x00ffffff, 0x00ffffff }, /* Y/Z/AA */
+ {},
+};
+
+static const struct aspeed_gpio_g7_config ast2700_config =
+ /*
+ * ast2700 has two controllers one with 212 GPIOs and one with 16 GPIOs.
+ * 216 for simplicity, actual number is 212 (4-GPIO hole in GPIOH)
+ * We expect ngpio being set in the device tree and this is a fallback
+ * option.
+ */
+ {
+ .nr_gpios = 216,
+ .props = ast2700_bank_props,
+ };
+
+static const struct of_device_id aspeed_gpio_g7_of_table[] = {
+ {
+ .compatible = "aspeed,ast2700-gpio",
+ .data = &ast2700_config,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, aspeed_gpio_g7_of_table);
+
+static int __init aspeed_gpio_g7_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *gpio_id;
+ struct aspeed_gpio_g7 *gpio;
+ int rc, banks, err;
+ u32 ngpio;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gpio->base))
+ return PTR_ERR(gpio->base);
+
+ gpio->dev = &pdev->dev;
+
+ raw_spin_lock_init(&gpio->lock);
+
+ gpio_id = of_match_node(aspeed_gpio_g7_of_table, pdev->dev.of_node);
+ if (!gpio_id)
+ return -EINVAL;
+
+ gpio->clk = of_clk_get(pdev->dev.of_node, 0);
+ if (IS_ERR(gpio->clk)) {
+ dev_warn(&pdev->dev, "Failed to get clock from devicetree, debouncing disabled\n");
+ gpio->clk = NULL;
+ }
+
+ gpio->config = gpio_id->data;
+
+ gpio->chip.parent = &pdev->dev;
+ err = of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpio);
+ gpio->chip.ngpio = (u16)ngpio;
+ if (err)
+ gpio->chip.ngpio = gpio->config->nr_gpios;
+ gpio->chip.direction_input = aspeed_gpio_g7_dir_in;
+ gpio->chip.direction_output = aspeed_gpio_g7_dir_out;
+ gpio->chip.get_direction = aspeed_gpio_g7_get_direction;
+ gpio->chip.request = aspeed_gpio_g7_request;
+ gpio->chip.free = aspeed_gpio_g7_free;
+ gpio->chip.get = aspeed_gpio_g7_get;
+ gpio->chip.set = aspeed_gpio_g7_set;
+ gpio->chip.set_config = aspeed_gpio_g7_set_config;
+ gpio->chip.label = dev_name(&pdev->dev);
+ gpio->chip.base = -1;
+
+ /* Allocate a cache of the output registers */
+ banks = DIV_ROUND_UP(gpio->chip.ngpio, 32);
+ gpio->dcache = devm_kcalloc(&pdev->dev, banks, sizeof(u32), GFP_KERNEL);
+ if (!gpio->dcache)
+ return -ENOMEM;
+
+ /* Optionally set up an irqchip if there is an IRQ */
+ rc = platform_get_irq(pdev, 0);
+ if (rc > 0) {
+ struct gpio_irq_chip *girq;
+
+ gpio->irq = rc;
+ girq = &gpio->chip.irq;
+ gpio_irq_chip_set_chip(girq, &aspeed_gpio_g7_irq_chip);
+ girq->chip->name = dev_name(&pdev->dev);
+
+ girq->parent_handler = aspeed_gpio_g7_irq_handler;
+ girq->num_parents = 1;
+ girq->parents = devm_kcalloc(&pdev->dev, 1, sizeof(*girq->parents), GFP_KERNEL);
+ if (!girq->parents)
+ return -ENOMEM;
+ girq->parents[0] = gpio->irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+ girq->init_valid_mask = aspeed_init_irq_valid_mask;
+ }
+
+ gpio->offset_timer = devm_kzalloc(&pdev->dev, gpio->chip.ngpio, GFP_KERNEL);
+ if (!gpio->offset_timer)
+ return -ENOMEM;
+
+ rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static struct platform_driver aspeed_gpio_g7_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = aspeed_gpio_g7_of_table,
+ },
+};
+
+module_platform_driver_probe(aspeed_gpio_g7_driver, aspeed_gpio_g7_probe);
+
+MODULE_DESCRIPTION("Aspeed G7 GPIO Driver");
+MODULE_LICENSE("GPL");
--
2.25.1
More information about the Linux-aspeed
mailing list