[PATCH v3 2/2] gpio: axiado: add AX3005 SGPIO controller support

Petar Stepanovic pstepanovic at axiado.com
Thu Sep 3 00:11:33 PDT 2026


Add support for the Axiado AX3005 SGPIO controller.

Each SGPIO position provides one input GPIO and one output GPIO with
fixed directions. The driver registers the controller as a gpio_chip,
supports interrupts on input GPIOs, and uses regmap for register access.

Signed-off-by: Petar Stepanovic <pstepanovic at axiado.com>
---
 MAINTAINERS                      |   1 +
 drivers/gpio/Kconfig             |  21 ++
 drivers/gpio/Makefile            |   1 +
 drivers/gpio/gpio-axiado-sgpio.c | 737 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 760 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 5bec6ea50c70..aeedf503d422 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4413,6 +4413,7 @@ M:	Prasad Bolisetty <pbolisetty at axiado.com>
 L:	linux-gpio at vger.kernel.org
 S:	Supported
 F:	Documentation/devicetree/bindings/gpio/axiado,ax3005-sgpio.yaml
+F:	drivers/gpio/gpio-axiado-sgpio.c
 
 AXIS ARTPEC ARM64 SoC SUPPORT
 M:	Jesper Nilsson <jesper.nilsson at axis.com>
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index a48586bb8edb..364472b3788f 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -204,6 +204,27 @@ config GPIO_ATH79
 	  Select this option to enable GPIO driver for
 	  Atheros AR71XX/AR724X/AR913X SoC devices.
 
+config GPIO_AXIADO_SGPIO
+	tristate "Axiado AX3005 SGPIO support"
+	depends on ARCH_AXIADO || COMPILE_TEST
+	select GPIOLIB_IRQCHIP
+	select GPIO_REGMAP
+	select REGMAP_MMIO
+	help
+	  Enable support for the Axiado AX3005 Serial GPIO (SGPIO)
+	  controller.
+
+	  The SGPIO controller provides a serialized interface for
+	  controlling multiple GPIO signals over a limited number of
+	  physical lines. Each signal is exposed as one input and one
+	  output line, and input lines can raise interrupts.
+
+	  This driver integrates with the Linux GPIO subsystem and
+	  exposes the controller as a standard GPIO provider.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called gpio-axiado-sgpio.
+
 config GPIO_RASPBERRYPI_EXP
 	tristate "Raspberry Pi 3 GPIO Expander"
 	default RASPBERRYPI_FIRMWARE
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index dc9e6d643b5b..b2820e4cf98a 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_GPIO_ARIZONA)		+= gpio-arizona.o
 obj-$(CONFIG_GPIO_ASPEED)		+= gpio-aspeed.o
 obj-$(CONFIG_GPIO_ASPEED_SGPIO)		+= gpio-aspeed-sgpio.o
 obj-$(CONFIG_GPIO_ATH79)		+= gpio-ath79.o
+obj-$(CONFIG_GPIO_AXIADO_SGPIO)		+= gpio-axiado-sgpio.o
 obj-$(CONFIG_GPIO_BCM_KONA)		+= gpio-bcm-kona.o
 obj-$(CONFIG_GPIO_BCM_XGS_IPROC)	+= gpio-xgs-iproc.o
 obj-$(CONFIG_GPIO_BD71815)		+= gpio-bd71815.o
diff --git a/drivers/gpio/gpio-axiado-sgpio.c b/drivers/gpio/gpio-axiado-sgpio.c
new file mode 100644
index 000000000000..a7c970217c59
--- /dev/null
+++ b/drivers/gpio/gpio-axiado-sgpio.c
@@ -0,0 +1,737 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022-2026 Axiado Corporation
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/irqdomain.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+struct axiado_sgpio_reg_offsets {
+	u32 mux_0;
+	u32 preset_0;
+	u32 count_0;
+	u32 pos_0;
+
+	u32 mux_1;
+	u32 ld;
+	u32 ld_ss;
+
+	u32 preset_1;
+	u32 count_1;
+	u32 pos_1;
+
+	u32 mux_2;
+	u32 dout;
+	u32 dout_ss;
+
+	u32 preset_2;
+	u32 count_2;
+	u32 pos_2;
+
+	u32 mux_3;
+	u32 preset_3;
+	u32 count_3;
+	u32 pos_3;
+
+	u32 mux_4;
+	u32 oe;
+	u32 oe_ss;
+
+	u32 preset_4;
+	u32 count_4;
+	u32 pos_4;
+
+	u32 mask;
+	u32 ctrl_en;
+	u32 ctrl_en_pos;
+
+	u32 din_ss;
+	u32 status;
+};
+
+static const struct axiado_sgpio_reg_offsets axiado_sgpio_offsets = {
+	.mux_0 = 0x000,
+	.preset_0 = 0x1dc,
+	.count_0 = 0x1f0,
+	.pos_0 = 0x204,
+
+	.mux_1 = 0x004,
+	.ld = 0x014,
+	.ld_ss = 0x0d8,
+
+	.preset_1 = 0x1e0,
+	.count_1 = 0x1f4,
+	.pos_1 = 0x208,
+
+	.mux_2 = 0x008,
+	.dout = 0x054,
+	.dout_ss = 0x158,
+
+	.preset_2 = 0x1e4,
+	.count_2 = 0x1f8,
+	.pos_2 = 0x20c,
+
+	.mux_3 = 0x00c,
+	.preset_3 = 0x1e8,
+	.count_3 = 0x1fc,
+	.pos_3 = 0x210,
+
+	.mux_4 = 0x010,
+	.oe = 0x0d4,
+	.oe_ss = 0x1d8,
+
+	.preset_4 = 0x1ec,
+	.count_4 = 0x200,
+	.pos_4 = 0x214,
+
+	.mask = 0x224,
+	.ctrl_en = 0x218,
+	.ctrl_en_pos = 0x21c,
+
+	.din_ss = 0x198,
+	.status = 0x228,
+};
+
+/* Number of SGPIO positions held by one bank register. */
+#define SGPIO_BANK_SIZE			32
+#define SGPIO_MAX_SIGNALS		512
+#define SGPIO_NUM_BANKS			16
+
+/*
+ * Slice mux encodings taken from the AX3005 SGPIO programming sequence.
+ */
+#define SGPIO_MUX_CLK			0x306
+#define SGPIO_MUX_DATA			0x30c
+#define SGPIO_MUX_DIN			0x14c
+#define SGPIO_MUX_OE			0x10c
+
+/* A POS register holds the first and the last position driven by the slice. */
+#define SGPIO_POS_FIRST			GENMASK(31, 16)
+#define SGPIO_POS_LAST			GENMASK(15, 0)
+#define SGPIO_POS(pos)			(FIELD_PREP(SGPIO_POS_FIRST, (pos)) | \
+					 FIELD_PREP(SGPIO_POS_LAST, (pos)))
+
+/* The clock and output enable slices always cover a full bank. */
+#define SGPIO_POS_FULL_BANK		SGPIO_POS(SGPIO_BANK_SIZE - 1)
+
+/* The enable and interrupt registers hold one bit per slice. */
+#define SGPIO_SLICE_MASK		GENMASK(15, 0)
+
+/*
+ * Interrupt sources enabled by the programming sequence. Bit 13 is left
+ * masked, the remaining slice events are all reported.
+ */
+#define SGPIO_IRQ_ENABLE		0xdfff
+
+/* The upper half of the status register reports one bit per data bank. */
+#define SGPIO_STATUS_EXCHANGE		GENMASK(31, 16)
+
+struct axiado_sgpio {
+	u32 preset_value;
+	u32 count_value;
+	u32 pos_reg;
+	/* Last sampled input word of each bank, for edge detection. */
+	u32 *din_shadow;
+	unsigned long *irq_unmasked;
+	unsigned long *irq_rising;
+	unsigned long *irq_falling;
+	u32 nsignals;
+	u32 din_bank_shift;
+	unsigned long *dir_out;
+	struct gpio_regmap *gpio;
+	struct irq_domain *domain;
+	struct regmap *regmap;
+	const struct axiado_sgpio_reg_offsets *regs;
+};
+
+/*
+ * Tell lockdep that these interrupts are in a different category than the
+ * parent they are handled from, so that the nested handling is not reported
+ * as recursion.
+ */
+static struct lock_class_key axiado_sgpio_irq_lock_class;
+static struct lock_class_key axiado_sgpio_irq_request_class;
+
+/*
+ * Each SGPIO signal is exposed as two GPIO lines: an even line for the serial
+ * input and the following odd line for the serial output. Signal n therefore
+ * lives in bit (n % 32) of bank (n / 32) of either the input or the output
+ * register.
+ */
+static int axiado_sgpio_reg_mask_xlate(struct gpio_regmap *gpio,
+				       enum gpio_regmap_operation op,
+				       unsigned int base, unsigned int offset,
+				       unsigned int *reg, unsigned int *mask)
+{
+	struct axiado_sgpio *sgpio = gpio_regmap_get_drvdata(gpio);
+	unsigned int signal = offset / 2;
+	unsigned int bank = signal / SGPIO_BANK_SIZE;
+
+	/*
+	 * An output line lives in the output register for both reads and
+	 * writes, so the register follows from the line rather than from the
+	 * operation. The AX3005 input banks are right aligned, hence the
+	 * shift.
+	 */
+	if (offset % 2)
+		base = sgpio->regs->dout_ss;
+	else
+		bank += sgpio->din_bank_shift;
+
+	*reg = base + bank * sizeof(u32);
+	*mask = BIT(signal % SGPIO_BANK_SIZE);
+
+	return 0;
+}
+
+static irqreturn_t axiado_sgpio_irq_handler(int irq, void *arg)
+{
+	struct axiado_sgpio *sgpio = arg;
+	unsigned int nbanks = DIV_ROUND_UP(sgpio->nsignals, SGPIO_BANK_SIZE);
+	u32 status, new_value, changed_value;
+	unsigned int bit, reg_ptr, i;
+	int ret;
+
+	/* Read-on-clear (ACK) parent cause */
+	ret = regmap_read(sgpio->regmap, sgpio->regs->status, &status);
+	if (ret)
+		return IRQ_NONE;
+
+	/* Nothing pending: the interrupt came from somebody else. */
+	if (!status)
+		return IRQ_NONE;
+
+	status = FIELD_GET(SGPIO_STATUS_EXCHANGE, status);
+
+	reg_ptr = sgpio->din_bank_shift;
+
+	for (i = 0; i < nbanks; i++, reg_ptr++) {
+		if (status & BIT(reg_ptr)) {
+			ret = regmap_read(sgpio->regmap,
+					  sgpio->regs->din_ss + reg_ptr * sizeof(u32),
+					  &new_value);
+			if (ret)
+				continue;
+
+			/*
+			 * Track every signal so that the shadow always holds
+			 * the last sampled level, then report only the ones
+			 * whose interrupt is unmasked. Transitions of a masked
+			 * signal are dropped rather than delivered later, the
+			 * hardware has no per signal latch to replay them
+			 * from. Only this handler touches the shadow and it is
+			 * not reentrant.
+			 */
+			changed_value = sgpio->din_shadow[i] ^ new_value;
+			sgpio->din_shadow[i] = new_value;
+			changed_value &= bitmap_read(sgpio->irq_unmasked,
+						     i * SGPIO_BANK_SIZE,
+						     SGPIO_BANK_SIZE);
+
+			while (changed_value) {
+				unsigned int child_irq;
+				irq_hw_number_t hwirq;
+				unsigned int position;
+				bool rising;
+
+				bit = __ffs(changed_value);
+				changed_value &= ~BIT(bit);
+
+				position = i * SGPIO_BANK_SIZE + bit;
+				hwirq = position * 2;
+
+				rising = !!(new_value & BIT(bit));
+
+				if (!test_bit(position, rising ? sgpio->irq_rising
+							       : sgpio->irq_falling))
+					continue;
+
+				child_irq = irq_find_mapping(sgpio->domain, hwirq);
+				if (child_irq)
+					handle_nested_irq(child_irq);
+			}
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int axiado_sgpio_hw_init(struct axiado_sgpio *sgpio)
+{
+	const struct axiado_sgpio_reg_offsets *regs = sgpio->regs;
+	unsigned int nbanks = DIV_ROUND_UP(sgpio->nsignals, SGPIO_BANK_SIZE);
+	unsigned int last_bank = (sgpio->nsignals - 1) / SGPIO_BANK_SIZE;
+	unsigned int last_bit = (sgpio->nsignals - 1) % SGPIO_BANK_SIZE;
+	unsigned int i;
+	int ret;
+
+	/* Slice A0 is the shift clock, slice B1 the data load. */
+	const struct reg_sequence pre_load[] = {
+		{ regs->mask, 0 },
+		{ regs->mux_0, SGPIO_MUX_CLK },
+		{ regs->preset_0, sgpio->preset_value },
+		{ regs->count_0, sgpio->count_value },
+		{ regs->pos_0, SGPIO_POS_FULL_BANK },
+		{ regs->mux_1, SGPIO_MUX_DATA },
+	};
+	/* Slice C2 is the data output. */
+	const struct reg_sequence pre_dout[] = {
+		{ regs->preset_1, sgpio->preset_value },
+		{ regs->count_1, sgpio->count_value },
+		{ regs->pos_1, sgpio->pos_reg },
+		{ regs->mux_2, SGPIO_MUX_DATA },
+	};
+	/* Slice D3 is the data input, slice E4 the output enable. */
+	const struct reg_sequence post_dout[] = {
+		{ regs->preset_2, sgpio->preset_value },
+		{ regs->count_2, sgpio->count_value },
+		{ regs->pos_2, sgpio->pos_reg },
+		{ regs->mux_3, SGPIO_MUX_DIN },
+		{ regs->preset_3, sgpio->preset_value },
+		{ regs->count_3, sgpio->count_value },
+		{ regs->pos_3, sgpio->pos_reg },
+		{ regs->mux_4, SGPIO_MUX_OE },
+		{ regs->oe, GENMASK(31, 0) },
+		{ regs->oe_ss, GENMASK(31, 0) },
+		{ regs->preset_4, sgpio->preset_value },
+		{ regs->count_4, sgpio->count_value },
+		{ regs->pos_4, SGPIO_POS_FULL_BANK },
+		{ regs->ctrl_en, SGPIO_SLICE_MASK },
+		{ regs->ctrl_en_pos, SGPIO_SLICE_MASK },
+	};
+
+	ret = regmap_multi_reg_write(sgpio->regmap, pre_load,
+				     ARRAY_SIZE(pre_load));
+	if (ret)
+		return ret;
+
+	/*
+	 * Latch the signals that are present. Only the last bank may be
+	 * partially populated; a last bit of zero still describes one signal.
+	 */
+	for (i = 0; i < nbanks; i++) {
+		u32 val = i < last_bank ? GENMASK(SGPIO_BANK_SIZE - 1, 0) :
+					  GENMASK(last_bit, 0);
+
+		ret = regmap_write(sgpio->regmap, regs->ld + i * 4, val);
+		if (ret)
+			return ret;
+
+		ret = regmap_write(sgpio->regmap, regs->ld_ss + i * 4, val);
+		if (ret)
+			return ret;
+	}
+
+	ret = regmap_multi_reg_write(sgpio->regmap, pre_dout,
+				     ARRAY_SIZE(pre_dout));
+	if (ret)
+		return ret;
+
+	/* All outputs start out driving zero. */
+	for (i = 0; i < nbanks; i++) {
+		ret = regmap_write(sgpio->regmap, regs->dout + i * 4, 0);
+		if (ret)
+			return ret;
+
+		ret = regmap_write(sgpio->regmap, regs->dout_ss + i * 4, 0);
+		if (ret)
+			return ret;
+	}
+
+	return regmap_multi_reg_write(sgpio->regmap, post_dout,
+				      ARRAY_SIZE(post_dout));
+}
+
+static int axiado_sgpio_irq_enable(struct axiado_sgpio *sgpio)
+{
+	return regmap_write(sgpio->regmap, sgpio->regs->mask,
+			    SGPIO_IRQ_ENABLE);
+}
+
+static void axiado_sgpio_mask_irqs(void *data)
+{
+	struct axiado_sgpio *sgpio = data;
+
+	regmap_write(sgpio->regmap, sgpio->regs->mask, 0);
+}
+
+static void axiado_sgpio_disable(void *data)
+{
+	struct axiado_sgpio *sgpio = data;
+
+	/* Mask the interrupts and stop the shift engine. */
+	regmap_write(sgpio->regmap, sgpio->regs->mask, 0);
+	regmap_write(sgpio->regmap, sgpio->regs->ctrl_en, 0);
+}
+
+static int axiado_sgpio_set_irq_type(struct irq_data *d, unsigned int type)
+{
+	struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+	unsigned int position;
+
+	/* Only even GPIO offsets represent SGPIO inputs. */
+	if (hwirq & 1)
+		return -EINVAL;
+
+	position = hwirq / 2;
+	if (position >= sgpio->nsignals)
+		return -EINVAL;
+
+	type &= IRQ_TYPE_SENSE_MASK;
+
+	switch (type) {
+	case IRQ_TYPE_EDGE_BOTH:
+	case IRQ_TYPE_EDGE_RISING:
+	case IRQ_TYPE_EDGE_FALLING:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	assign_bit(position, sgpio->irq_rising, type & IRQ_TYPE_EDGE_RISING);
+	assign_bit(position, sgpio->irq_falling, type & IRQ_TYPE_EDGE_FALLING);
+
+	return 0;
+}
+
+static void axiado_sgpio_mask_irq(struct irq_data *d)
+{
+	struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+	clear_bit(hwirq / 2, sgpio->irq_unmasked);
+	gpio_regmap_disable_irq(sgpio->gpio, hwirq);
+}
+
+static void axiado_sgpio_unmask_irq(struct irq_data *d)
+{
+	struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+	gpio_regmap_enable_irq(sgpio->gpio, hwirq);
+	set_bit(hwirq / 2, sgpio->irq_unmasked);
+}
+
+static int axiado_sgpio_irq_request_resources(struct irq_data *d)
+{
+	struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
+
+	return gpio_regmap_reqres_irq(sgpio->gpio, d->hwirq);
+}
+
+static void axiado_sgpio_irq_release_resources(struct irq_data *d)
+{
+	struct axiado_sgpio *sgpio = irq_data_get_irq_chip_data(d);
+
+	gpio_regmap_relres_irq(sgpio->gpio, d->hwirq);
+}
+
+static const struct irq_chip axiado_sgpio_irqchip = {
+	.name = "axiado-sgpio",
+	.irq_mask = axiado_sgpio_mask_irq,
+	.irq_unmask = axiado_sgpio_unmask_irq,
+	.irq_set_type = axiado_sgpio_set_irq_type,
+	.flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND,
+	.irq_request_resources = axiado_sgpio_irq_request_resources,
+	.irq_release_resources = axiado_sgpio_irq_release_resources,
+};
+
+static int axiado_sgpio_irq_map(struct irq_domain *domain, unsigned int virq,
+				irq_hw_number_t hwirq)
+{
+	struct axiado_sgpio *sgpio = domain->host_data;
+
+	/* Only even offsets represent input GPIOs. */
+	if (hwirq % 2)
+		return -EINVAL;
+
+	irq_set_chip_data(virq, sgpio);
+	irq_set_lockdep_class(virq, &axiado_sgpio_irq_lock_class,
+			      &axiado_sgpio_irq_request_class);
+	irq_set_chip_and_handler(virq, &axiado_sgpio_irqchip, handle_simple_irq);
+	irq_set_nested_thread(virq, true);
+	irq_set_noprobe(virq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops axiado_sgpio_domain_ops = {
+	.map = axiado_sgpio_irq_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+static int axiado_sgpio_init_input_cache(struct axiado_sgpio *sgpio)
+{
+	unsigned int num_banks = DIV_ROUND_UP(sgpio->nsignals,
+					      SGPIO_BANK_SIZE);
+	unsigned int reg_ptr = sgpio->din_bank_shift;
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < num_banks; i++, reg_ptr++) {
+		ret = regmap_read(sgpio->regmap,
+				  sgpio->regs->din_ss + reg_ptr * sizeof(u32),
+				  &sgpio->din_shadow[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static bool axiado_sgpio_dout_reg(const struct axiado_sgpio *sgpio,
+				  unsigned int reg)
+{
+	unsigned int span = SGPIO_NUM_BANKS * sizeof(u32);
+
+	return reg >= sgpio->regs->dout_ss &&
+	       reg < sgpio->regs->dout_ss + span;
+}
+
+static bool axiado_sgpio_readable_reg(struct device *dev, unsigned int reg)
+{
+	const struct axiado_sgpio *sgpio = dev_get_drvdata(dev);
+
+	/* The status register is the last one of the block. */
+	if (reg > sgpio->regs->status)
+		return false;
+
+	/*
+	 * The output shadow feeds the shift register and cannot be read back,
+	 * the driven value is kept in the register cache instead.
+	 */
+	return !axiado_sgpio_dout_reg(sgpio, reg);
+}
+
+static bool axiado_sgpio_volatile_reg(struct device *dev, unsigned int reg)
+{
+	const struct axiado_sgpio *sgpio = dev_get_drvdata(dev);
+
+	/*
+	 * Everything but the output shadow is live hardware state: shifted in
+	 * data, running counters and the interrupt status. Caching any of it
+	 * would hand out stale values, so the cache holds the output banks
+	 * alone.
+	 */
+	return !axiado_sgpio_dout_reg(sgpio, reg);
+}
+
+static bool axiado_sgpio_precious_reg(struct device *dev, unsigned int reg)
+{
+	const struct axiado_sgpio *sgpio = dev_get_drvdata(dev);
+
+	/* Reading the status register acknowledges the interrupts. */
+	return reg == sgpio->regs->status;
+}
+
+static const struct regmap_config axiado_sgpio_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.readable_reg = axiado_sgpio_readable_reg,
+	.volatile_reg = axiado_sgpio_volatile_reg,
+	.precious_reg = axiado_sgpio_precious_reg,
+	.cache_type = REGCACHE_MAPLE,
+};
+
+static int axiado_sgpio_probe(struct platform_device *pdev)
+{
+	struct gpio_regmap_config config = { };
+	struct irq_domain_info d_info = { };
+	struct axiado_sgpio *sgpio;
+	unsigned int ngpio, i;
+	unsigned long apb_freq;
+	struct clk *apb_clk;
+	void __iomem *base;
+	u32 sgpio_freq;
+	int irq, rc;
+
+	sgpio = devm_kzalloc(&pdev->dev, sizeof(*sgpio), GFP_KERNEL);
+	if (!sgpio)
+		return -ENOMEM;
+
+	/* The regmap callbacks below need both of these. */
+	platform_set_drvdata(pdev, sgpio);
+
+	sgpio->regs = &axiado_sgpio_offsets;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	sgpio->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+					      &axiado_sgpio_regmap_config);
+	if (IS_ERR(sgpio->regmap))
+		return dev_err_probe(&pdev->dev, PTR_ERR(sgpio->regmap),
+				     "Failed to init regmap\n");
+
+	rc = device_property_read_u32(&pdev->dev, "ngpios", &ngpio);
+	if (rc)
+		return dev_err_probe(&pdev->dev, rc,
+				     "Failed to read ngpios property\n");
+
+	/*
+	 * Each SGPIO signal is exposed as one input and one output line, so
+	 * the number of lines is twice the number of signals.
+	 */
+	if (!ngpio || ngpio % 2 || ngpio > SGPIO_MAX_SIGNALS * 2)
+		return dev_err_probe(&pdev->dev, -EINVAL,
+				     "Invalid ngpios value: %u (even, max %u)\n",
+				     ngpio, SGPIO_MAX_SIGNALS * 2);
+
+	sgpio->nsignals = ngpio / 2;
+
+	sgpio->din_shadow = devm_kcalloc(&pdev->dev,
+					 DIV_ROUND_UP(sgpio->nsignals,
+						      SGPIO_BANK_SIZE),
+					 sizeof(*sgpio->din_shadow), GFP_KERNEL);
+	sgpio->irq_unmasked = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals,
+						 GFP_KERNEL);
+	sgpio->irq_rising = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals,
+					       GFP_KERNEL);
+	sgpio->irq_falling = devm_bitmap_zalloc(&pdev->dev, sgpio->nsignals,
+						GFP_KERNEL);
+	if (!sgpio->din_shadow || !sgpio->irq_unmasked ||
+	    !sgpio->irq_rising || !sgpio->irq_falling)
+		return -ENOMEM;
+
+	apb_clk = devm_clk_get_enabled(&pdev->dev, NULL);
+	if (IS_ERR(apb_clk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(apb_clk),
+				     "Failed to get and enable APB clock\n");
+
+	rc = device_property_read_u32(&pdev->dev, "bus-frequency",
+				      &sgpio_freq);
+	if (rc)
+		return dev_err_probe(&pdev->dev, rc,
+				     "Failed to read bus-frequency\n");
+
+	apb_freq = clk_get_rate(apb_clk);
+
+	if (!apb_freq || !sgpio_freq || sgpio_freq > apb_freq)
+		return dev_err_probe(&pdev->dev, -EINVAL,
+				     "Invalid SGPIO bus frequency\n");
+
+	sgpio->preset_value = (apb_freq / sgpio_freq) - 1;
+	sgpio->count_value = sgpio->preset_value;
+
+	sgpio->pos_reg = SGPIO_POS(sgpio->nsignals - 1);
+
+	sgpio->din_bank_shift =	SGPIO_NUM_BANKS -
+				DIV_ROUND_UP(sgpio->nsignals, SGPIO_BANK_SIZE);
+
+	/*
+	 * From here on the hardware may be running, so the shutdown action has
+	 * to be in place before the first register is programmed. It only ever
+	 * writes zeroes, which is harmless if initialisation never got that
+	 * far, and it runs before the APB clock is disabled because that was
+	 * requested earlier.
+	 */
+	rc = devm_add_action_or_reset(&pdev->dev, axiado_sgpio_disable, sgpio);
+	if (rc)
+		return rc;
+
+	rc = axiado_sgpio_hw_init(sgpio);
+	if (rc)
+		return dev_err_probe(&pdev->dev, rc,
+				     "Failed to initialize hardware\n");
+
+	rc = axiado_sgpio_init_input_cache(sgpio);
+	if (rc)
+		return dev_err_probe(&pdev->dev, rc,
+				     "Failed to initialize input cache\n");
+
+	sgpio->dir_out = devm_bitmap_zalloc(&pdev->dev, ngpio, GFP_KERNEL);
+	if (!sgpio->dir_out)
+		return -ENOMEM;
+
+	/* Even lines are the serial inputs, odd lines the serial outputs. */
+	for (i = 1; i < ngpio; i += 2)
+		__set_bit(i, sgpio->dir_out);
+
+	d_info.fwnode = dev_fwnode(&pdev->dev);
+	d_info.size = ngpio;
+	d_info.hwirq_max = ngpio;
+	d_info.ops = &axiado_sgpio_domain_ops;
+	d_info.host_data = sgpio;
+
+	sgpio->domain = devm_irq_domain_instantiate(&pdev->dev, &d_info);
+	if (IS_ERR(sgpio->domain))
+		return PTR_ERR(sgpio->domain);
+
+	config.parent = &pdev->dev;
+	config.regmap = sgpio->regmap;
+	config.ngpio = ngpio;
+	config.reg_dat_base = GPIO_REGMAP_ADDR(sgpio->regs->din_ss);
+	config.reg_set_base = GPIO_REGMAP_ADDR(sgpio->regs->dout_ss);
+	config.reg_mask_xlate = axiado_sgpio_reg_mask_xlate;
+	config.fixed_direction_output = sgpio->dir_out;
+	config.irq_domain = sgpio->domain;
+	config.drvdata = sgpio;
+
+	sgpio->gpio = devm_gpio_regmap_register(&pdev->dev, &config);
+	if (IS_ERR(sgpio->gpio))
+		return dev_err_probe(&pdev->dev, PTR_ERR(sgpio->gpio),
+				     "Could not register gpiochip\n");
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	rc = devm_request_threaded_irq(&pdev->dev, irq, NULL, axiado_sgpio_irq_handler,
+				       IRQF_ONESHOT, dev_name(&pdev->dev), sgpio);
+	if (rc)
+		return dev_err_probe(&pdev->dev, rc, "Failed to request IRQ\n");
+
+	rc = axiado_sgpio_irq_enable(sgpio);
+	if (rc)
+		return dev_err_probe(&pdev->dev, rc,
+				     "Failed to enable interrupts\n");
+
+	/*
+	 * Registered last so that it runs first on teardown: the interrupts
+	 * must be masked while the parent interrupt is still requested. The
+	 * shift engine is stopped by axiado_sgpio_disable(), registered before
+	 * the hardware was programmed so that it also covers failures during
+	 * probe.
+	 */
+	return devm_add_action_or_reset(&pdev->dev, axiado_sgpio_mask_irqs,
+					sgpio);
+}
+
+static const struct of_device_id axiado_sgpio_of_match[] = {
+	{ .compatible = "axiado,ax3005-sgpio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, axiado_sgpio_of_match);
+
+static struct platform_driver axiado_sgpio_driver = {
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.of_match_table = axiado_sgpio_of_match,
+	},
+	.probe = axiado_sgpio_probe,
+};
+module_platform_driver(axiado_sgpio_driver);
+
+MODULE_DESCRIPTION("Axiado AX3005 Serial GPIO Driver");
+MODULE_AUTHOR("Axiado Corporation");
+MODULE_LICENSE("GPL");

-- 
2.34.1




More information about the linux-arm-kernel mailing list