[v4,2/5] gpio: gxp: Add HPE GXP GPIO PL

Message ID 20230621213115.113266-3-nick.hawkins@hpe.com
State New
Headers
Series ARM: Add GPIO support |

Commit Message

Hawkins, Nick June 21, 2023, 9:31 p.m. UTC
  From: Nick Hawkins <nick.hawkins@hpe.com>

The GXP SoC supports GPIO on multiple interfaces. The interfaces are
CPLD and Host. The gpio-gxp-pl driver covers the CPLD which takes
physical I/O from the board and shares it with GXP via a proprietary
interface that maps the I/O onto a specific register area of the GXP.
This driver supports interrupts from the CPLD.

Signed-off-by: Nick Hawkins <nick.hawkins@hpe.com>

---
v4:
 *New commit: Broke GPIO into two different commits one for each driver
 *Explain psu_presence global
 *Moved gpio_chip to first in gxp_gpio_drvdata
 *Moved regmap_config out of function and made it static const
 *Removed unnecessary variables
 *Removed redundant conditional
 *Modified regmap_read switch statements to calculate offset and mask
  then read at end.
 *Removed use of -EOPNOTSUPP
 *Removed redundant casting
 *Switched generic_handle_irq -> generic_handle_domain_irq
 *Used GENMASK where applicable
 *Used bitmap_xor and for_each_bit_set
 *Made GPIO chip const and marked as a template (in the name)
 *Made irq_chip const and immutable
 *Removed casting in one case
 *Corrected check on devm_gpiochip_add_data
 *Remove dev_err_probe on platform_get_irq
 *Changed return 0 to devm_request_irq
 *Added debug FS call to enable reading of the server_id
v3:
 *Did not exist
v:2
 *Did not exist
v1:
 *Did not exist
---
 drivers/gpio/Kconfig       |   9 +
 drivers/gpio/Makefile      |   1 +
 drivers/gpio/gpio-gxp-pl.c | 582 +++++++++++++++++++++++++++++++++++++
 3 files changed, 592 insertions(+)
 create mode 100644 drivers/gpio/gpio-gxp-pl.c
  

Comments

Linus Walleij June 22, 2023, 7:02 a.m. UTC | #1
Hi Nick,

thanks for your patch!

This is looking pretty good, I have some minor questions.

On Wed, Jun 21, 2023 at 11:35 PM <nick.hawkins@hpe.com> wrote:

> From: Nick Hawkins <nick.hawkins@hpe.com>
>
> The GXP SoC supports GPIO on multiple interfaces. The interfaces are
> CPLD and Host. The gpio-gxp-pl driver covers the CPLD which takes
> physical I/O from the board and shares it with GXP via a proprietary
> interface that maps the I/O onto a specific register area of the GXP.
> This driver supports interrupts from the CPLD.
>
> Signed-off-by: Nick Hawkins <nick.hawkins@hpe.com>

(...)
> +enum pl_gpio_pn {
> +       IOP_LED1 = 0,
> +       IOP_LED2 = 1,
> +       IOP_LED3 = 2,
> +       IOP_LED4 = 3,
(...)

The confusing bit here is that GPIO means
*generic purpose input/output*
and these use cases are hardcoded into the driver and
do not look very generic purpose at all.

But I understand that it is convenient. I would add some
comment saying that if there is a new version with a
different layout of the pins, we need to make this kind
of stuff go away and just use the numbers.

> +static const struct gpio_chip template_chip = {
> +       .label                  = "gxp_gpio_plreg",
> +       .owner                  = THIS_MODULE,
> +       .get                    = gxp_gpio_pl_get,
> +       .set                    = gxp_gpio_pl_set,
> +       .get_direction = gxp_gpio_pl_get_direction,
> +       .direction_input = gxp_gpio_pl_direction_input,
> +       .direction_output = gxp_gpio_pl_direction_output,
> +       .base = -1,
> +};

Neat! Since you so explicitly have assigned a meaning to each
GPIO line, you can go ahead and assign the .names property as
well. Check in the kernel tree for other drivers doing this.

> +       drvdata->chip = template_chip;
> +       drvdata->chip.ngpio = 80;

If you're always assigning 80 to this you can just put that in the
template as well.

Other than that I think it looks good!

Yours,
Linus Walleij
  

Patch

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index fa0c9fdbb50c..b0a24ef18392 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1244,6 +1244,15 @@  config GPIO_GXP
 	  support for the multiple GPIO interfaces available to be
 	  available to the Host.
 
+config GPIO_GXP_PL
+	tristate "GXP GPIO PL support"
+	depends on ARCH_HPE_GXP || COMPILE_TEST
+	select GPIOLIB_IRQCHIP
+	help
+	  Say Y here to support GXP GPIO PL controller. It provides
+	  support for the GPIO PL interface available to be
+	  available to the Host.
+
 config GPIO_JANZ_TTL
 	tristate "Janz VMOD-TTL Digital IO Module"
 	depends on MFD_JANZ_CMODIO
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index a7ce0ab097aa..1f285c630e15 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -64,6 +64,7 @@  obj-$(CONFIG_GPIO_GE_FPGA)		+= gpio-ge.o
 obj-$(CONFIG_GPIO_GPIO_MM)		+= gpio-gpio-mm.o
 obj-$(CONFIG_GPIO_GRGPIO)		+= gpio-grgpio.o
 obj-$(CONFIG_GPIO_GXP)                  += gpio-gxp.o
+obj-$(CONFIG_GPIO_GXP_PL)               += gpio-gxp-pl.o
 obj-$(CONFIG_GPIO_GW_PLD)		+= gpio-gw-pld.o
 obj-$(CONFIG_GPIO_HISI)                 += gpio-hisi.o
 obj-$(CONFIG_GPIO_HLWD)			+= gpio-hlwd.o
diff --git a/drivers/gpio/gpio-gxp-pl.c b/drivers/gpio/gpio-gxp-pl.c
new file mode 100644
index 000000000000..8506e2a96da4
--- /dev/null
+++ b/drivers/gpio/gpio-gxp-pl.c
@@ -0,0 +1,582 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
+
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/* Specific offsets in CPLD registers for interrupts */
+#define PLREG_INT_GRP_STAT_MASK	0x08
+#define PLREG_INT_HI_PRI_EN	0x0C
+#define PLREG_INT_GRP5_BASE	0x31
+#define PLREG_INT_GRP6_BASE	0x35
+#define PLREG_INT_GRP5_FLAG	0x30
+#define PLREG_INT_GRP5_STATE	0x32
+#define PLREG_INT_GRP6_FLAG	0x34
+
+/* Specific bits to enable Group 4 and Group 5 interrupts */
+#define PLREG_GRP4_GRP5_MASK	GENMASK(5, 4)
+
+/* Specific offsets in CPLD registers */
+#define PLREG_SERVER_ID		0x01 /* 2 Bytes */
+#define PLREG_IOP_LED		0x04
+#define PLREG_IDENT_LED		0x05
+#define PLREG_HEALTH_LED	0x0D
+#define PLREG_PSU_INST		0x19
+#define PLREG_PSU_AC		0x1B
+#define PLREG_PSU_DC		0x1C
+#define PLREG_FAN_INST		0x27
+#define PLREG_FAN_FAIL		0x29
+#define PLREG_SIDEBAND		0x40
+#define GXP_GPIO_DIR_OUT        0x00
+#define GXP_GPIO_DIR_IN         0x01
+
+enum pl_gpio_pn {
+	IOP_LED1 = 0,
+	IOP_LED2 = 1,
+	IOP_LED3 = 2,
+	IOP_LED4 = 3,
+	IOP_LED5 = 4,
+	IOP_LED6 = 5,
+	IOP_LED7 = 6,
+	IOP_LED8 = 7,
+	FAN1_INST = 8,
+	FAN2_INST = 9,
+	FAN3_INST = 10,
+	FAN4_INST = 11,
+	FAN5_INST = 12,
+	FAN6_INST = 13,
+	FAN7_INST = 14,
+	FAN8_INST = 15,
+	FAN1_FAIL = 16,
+	FAN2_FAIL = 17,
+	FAN3_FAIL = 18,
+	FAN4_FAIL = 19,
+	FAN5_FAIL = 20,
+	FAN6_FAIL = 21,
+	FAN7_FAIL = 22,
+	FAN8_FAIL = 23,
+	LED_IDENTIFY = 24,
+	LED_HEALTH_RED = 25,
+	LED_HEALTH_AMBER = 26,
+	PWR_BTN_INT = 27,
+	UID_PRESS_INT = 28,
+	SLP_INT = 29,
+	ACM_FORCE_OFF = 30,
+	ACM_REMOVED = 31,
+	ACM_REQ_N = 32,
+	PSU1_INST = 33,
+	PSU2_INST = 34,
+	PSU3_INST = 35,
+	PSU4_INST = 36,
+	PSU5_INST = 37,
+	PSU6_INST = 38,
+	PSU7_INST = 39,
+	PSU8_INST = 40,
+	PSU1_AC = 41,
+	PSU2_AC = 42,
+	PSU3_AC = 43,
+	PSU4_AC = 44,
+	PSU5_AC = 45,
+	PSU6_AC = 46,
+	PSU7_AC = 47,
+	PSU8_AC = 48,
+	PSU1_DC = 49,
+	PSU2_DC = 50,
+	PSU3_DC = 51,
+	PSU4_DC = 52,
+	PSU5_DC = 53,
+	PSU6_DC = 54,
+	PSU7_DC = 55,
+	PSU8_DC = 56,
+	RESET = 57,
+	NMI_OUT = 58,
+	VPBTN = 59,
+	PGOOD = 60,
+	PERST = 61,
+	POST_COMPLETE = 62,
+	SIDEBAND_SEL_L = 63,
+	SIDEBAND_SEL_H = 64
+};
+
+/*
+ * When an interrupt fires for a PSU config change
+ * there is a need to know the previous PSU configuration
+ * so that the appropriate gpio line is interrupted for
+ * the correct PSU. In order to keep this variable up to
+ * date it is global so that it can be set at init and
+ * each time the interrupt fires.
+ */
+u8 psu_presence;
+
+struct gxp_gpio_drvdata {
+	struct gpio_chip chip;
+	struct regmap *base;
+	struct regmap *interrupt;
+	int irq;
+};
+
+static const struct regmap_config gxp_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0x80,
+	.name = "gxp-gpio-pl",
+};
+
+static const struct regmap_config gxp_int_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0x7f,
+	.name = "gxp-gpio-pl-int",
+};
+
+static struct regmap *gxp_gpio_init_regmap(struct platform_device *pdev,
+					   char *reg_name, bool is_interrupt)
+{
+	void __iomem *base;
+
+	base = devm_platform_ioremap_resource_byname(pdev, reg_name);
+	if (IS_ERR(base))
+		return ERR_CAST(base);
+
+	if (is_interrupt)
+		return devm_regmap_init_mmio(&pdev->dev, base, &gxp_int_regmap_config);
+	else
+		return devm_regmap_init_mmio(&pdev->dev, base, &gxp_regmap_config);
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+#include <linux/debugfs.h>
+
+static int gxp_gpio_serverid_show(struct seq_file *s, void *unused)
+{
+	struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(s->private);
+	unsigned int server_id_l;
+	unsigned int server_id_h;
+
+	regmap_read(drvdata->base, PLREG_SERVER_ID, &server_id_l);
+	regmap_read(drvdata->base, PLREG_SERVER_ID + 0x01, &server_id_h);
+
+	seq_printf(s, "%02x %02x", server_id_h, server_id_l);
+
+	return 0;
+}
+
+static void gxp_gpio_debuginit(struct platform_device *pdev)
+{
+	debugfs_create_devm_seqfile(&pdev->dev, "gxp_gpio_serverid", NULL,
+				    gxp_gpio_serverid_show);
+}
+
+#else
+
+static inline void gxp_gpio_debuginit(struct platform_device *pdev)
+{
+}
+
+#endif
+
+static int gxp_gpio_pl_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+	unsigned int val;
+	unsigned int reg_offset;
+	u8 reg_mask;
+	bool is_active_low = false;
+
+	switch (offset) {
+	case IOP_LED1 ... IOP_LED8:
+		reg_offset = PLREG_IOP_LED;
+		reg_mask = BIT(offset);
+		break;
+	case FAN1_INST ...FAN8_INST:
+		regmap_read(drvdata->base, PLREG_FAN_INST, &val);
+		reg_mask = BIT(offset - FAN1_INST);
+		break;
+	case FAN1_FAIL ... FAN8_FAIL:
+		regmap_read(drvdata->base, PLREG_FAN_FAIL, &val);
+		reg_mask = BIT(offset - FAN1_FAIL);
+		break;
+	case PWR_BTN_INT ... SLP_INT:
+		/* Note this is active low */
+		reg_offset = PLREG_INT_GRP5_STATE;
+		reg_mask = BIT(offset - PWR_BTN_INT);
+		is_active_low = true;
+		break;
+	case  PSU1_INST ... PSU8_INST:
+		reg_offset = PLREG_PSU_INST;
+		reg_mask = BIT(offset - PSU1_INST);
+		break;
+	case PSU1_AC ... PSU8_AC:
+		reg_offset = PLREG_PSU_AC;
+		reg_mask = BIT(offset - PSU1_AC);
+		break;
+	case PSU1_DC ... PSU8_DC:
+		reg_offset = PLREG_PSU_DC;
+		reg_mask = BIT(offset - PSU1_DC);
+		break;
+	case LED_IDENTIFY:
+		reg_offset = PLREG_IDENT_LED;
+		reg_mask = BIT(1);
+		break;
+	case LED_HEALTH_RED:
+		reg_offset = PLREG_HEALTH_LED;
+		reg_mask = GENMASK(5, 4); /* Bit 5 set, bit 4 clear */
+		break;
+	case LED_HEALTH_AMBER:
+		reg_offset = PLREG_HEALTH_LED;
+		reg_mask = GENMASK(5, 4); /* Bit 5, bit 4 set */
+		break;
+	case SIDEBAND_SEL_L:
+		reg_offset = PLREG_SIDEBAND;
+		reg_mask = BIT(0);
+		break;
+	case SIDEBAND_SEL_H:
+		reg_offset = PLREG_SIDEBAND;
+		reg_mask = BIT(1);
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	regmap_read(drvdata->base, reg_offset, &val);
+
+	/* Special case: Check two bits for Health LED */
+	if (offset == LED_HEALTH_RED)
+		/* Bit 5 set, bit 4 not set */
+		return ((val & reg_mask) == BIT(5) ? 1 : 0);
+	else if (offset == LED_HEALTH_AMBER)
+		/* Bit 5 and 4 set */
+		return ((val & reg_mask) == reg_mask ? 1 : 0);
+
+	val = val & reg_mask;
+
+	if (is_active_low)
+		return (val ? 0 : 1);
+	else
+		return (val ? 1 : 0);
+}
+
+static void gxp_gpio_pl_set(struct gpio_chip *chip,
+			    unsigned int offset, int value)
+{
+	struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+
+	switch (offset) {
+	case IOP_LED1 ... IOP_LED8:
+		regmap_update_bits(drvdata->base,
+				   PLREG_IOP_LED,
+				   BIT(offset),
+				   value == 0 ? 0 : BIT(offset));
+		break;
+	case LED_IDENTIFY:
+		regmap_update_bits(drvdata->base,
+				   PLREG_IDENT_LED,
+				   GENMASK(7, 6),
+				   value == 0 ? BIT(7) : GENMASK(7, 6));
+		break;
+	case LED_HEALTH_RED:
+		regmap_update_bits(drvdata->base,
+				   PLREG_HEALTH_LED,
+				   GENMASK(7, 6),
+				   value == 0 ? 0 : BIT(7));
+		break;
+	case LED_HEALTH_AMBER:
+		regmap_update_bits(drvdata->base,
+				   PLREG_HEALTH_LED,
+				   GENMASK(7, 6),
+				   value == 0 ? 0 : BIT(6));
+		break;
+	case SIDEBAND_SEL_L ... SIDEBAND_SEL_H:
+		regmap_update_bits(drvdata->base,
+				   PLREG_SIDEBAND,
+				   BIT(offset - SIDEBAND_SEL_L),
+				   value == 0 ? 0 : BIT(offset - SIDEBAND_SEL_L));
+		break;
+	default:
+		break;
+	}
+}
+
+static int gxp_gpio_pl_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+	switch (offset) {
+	case IOP_LED1 ... IOP_LED8:
+	case LED_IDENTIFY ... LED_HEALTH_AMBER:
+	case ACM_FORCE_OFF:
+	case ACM_REQ_N:
+	case SIDEBAND_SEL_L ... SIDEBAND_SEL_H:
+		return GXP_GPIO_DIR_OUT;
+	default:
+		return -ENOTSUPP;
+	}
+}
+
+static int gxp_gpio_pl_direction_input(struct gpio_chip *chip,
+				       unsigned int offset)
+{
+	switch (offset) {
+	case FAN1_INST ... FAN8_FAIL:
+		return GXP_GPIO_DIR_OUT;
+	case PWR_BTN_INT ... SLP_INT:
+		return GXP_GPIO_DIR_OUT;
+	default:
+		return -ENOTSUPP;
+	}
+}
+
+static int gxp_gpio_pl_direction_output(struct gpio_chip *chip,
+					unsigned int offset, int value)
+{
+	switch (offset) {
+	case IOP_LED1 ... IOP_LED8:
+	case LED_IDENTIFY ... LED_HEALTH_AMBER:
+	case ACM_FORCE_OFF:
+	case ACM_REQ_N:
+	case SIDEBAND_SEL_L ... SIDEBAND_SEL_H:
+		gxp_gpio_pl_set(chip, offset, value);
+		return GXP_GPIO_DIR_OUT;
+	default:
+		return -ENOTSUPP;
+	}
+}
+
+static void gxp_gpio_pl_irq_ack(struct irq_data *d)
+{
+	struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+	struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+	unsigned int val;
+
+	/* Read latched interrupt for group 5 */
+	regmap_read(drvdata->interrupt, PLREG_INT_GRP5_FLAG, &val);
+	/* Clear latched interrupt */
+	regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_FLAG,
+			   0xFF, 0xFF);
+
+	/* Read latched interrupt for group 6 */
+	regmap_read(drvdata->interrupt, PLREG_INT_GRP6_FLAG, &val);
+	/* Clear latched interrupt */
+	regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_FLAG,
+			   0xFF, 0xFF);
+}
+
+static void gxp_gpio_pl_irq_set_mask(struct irq_data *d, bool set)
+{
+	struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+	struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+
+	regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_BASE,
+			   BIT(0) | BIT(2), set ? 0 : BIT(0) | BIT(2));
+
+	regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_BASE,
+			   BIT(2), set ? 0 : BIT(2));
+}
+
+static void gxp_gpio_pl_irq_mask(struct irq_data *d)
+{
+	gxp_gpio_pl_irq_set_mask(d, false);
+}
+
+static void gxp_gpio_pl_irq_unmask(struct irq_data *d)
+{
+	gxp_gpio_pl_irq_set_mask(d, true);
+}
+
+static int gxp_gpio_irq_init_hw(struct gpio_chip *chip)
+{
+	struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+
+	regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_BASE,
+			   BIT(0) | BIT(2), 0);
+
+	regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_BASE,
+			   BIT(2), 0);
+
+	return 0;
+}
+
+static int gxp_gpio_pl_set_type(struct irq_data *d, unsigned int type)
+{
+	if (type & IRQ_TYPE_LEVEL_MASK)
+		irq_set_handler_locked(d, handle_level_irq);
+	else
+		irq_set_handler_locked(d, handle_edge_irq);
+
+	return 0;
+}
+
+static irqreturn_t gxp_gpio_pl_irq_handle(int irq, void *_drvdata)
+{
+	struct gxp_gpio_drvdata *drvdata = _drvdata;
+	unsigned int val, i;
+	unsigned long temp;
+
+	/* Check group 5 interrupts */
+
+	regmap_read(drvdata->base, PLREG_INT_GRP5_FLAG, &val);
+
+	temp = (unsigned long)val;
+	for_each_set_bit(i, &temp, 3) {
+		generic_handle_domain_irq(drvdata->chip.irq.domain,
+					  i + PWR_BTN_INT);
+	}
+
+		/* Clear latched interrupt */
+	regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_FLAG,
+			   GENMASK(7, 0), GENMASK(7, 0));
+	regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_BASE,
+			   BIT(0) | BIT(2), 0);
+
+	/* Check group 6 interrupts */
+
+	regmap_read(drvdata->base, PLREG_INT_GRP6_FLAG, &val);
+
+	if (val & BIT(2)) {
+		u8 old_psu = psu_presence;
+
+		regmap_read(drvdata->base, PLREG_PSU_INST, &val);
+		psu_presence = val;
+
+		if (old_psu != psu_presence) {
+			/* Identify all bits which differs */
+			unsigned long current_val = psu_presence;
+			unsigned long old_val = old_psu;
+			unsigned long changed_bits;
+
+			bitmap_xor(&changed_bits, &current_val, &old_val, 8);
+
+			for_each_set_bit(i, &changed_bits, 8) {
+				/* PSU state has changed */
+				generic_handle_domain_irq(drvdata->chip.irq.domain,
+							  i + PSU1_INST);
+			}
+		}
+	}
+
+	/* Clear latched interrupt */
+	regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_FLAG,
+			   GENMASK(7, 0), GENMASK(7, 0));
+	regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_BASE,
+			   BIT(2), 0);
+
+	return IRQ_HANDLED;
+}
+
+static const struct gpio_chip template_chip = {
+	.label			= "gxp_gpio_plreg",
+	.owner			= THIS_MODULE,
+	.get			= gxp_gpio_pl_get,
+	.set			= gxp_gpio_pl_set,
+	.get_direction = gxp_gpio_pl_get_direction,
+	.direction_input = gxp_gpio_pl_direction_input,
+	.direction_output = gxp_gpio_pl_direction_output,
+	.base = -1,
+};
+
+static const struct irq_chip gxp_plreg_irqchip = {
+	.name		= "gxp_plreg",
+	.irq_ack	= gxp_gpio_pl_irq_ack,
+	.irq_mask	= gxp_gpio_pl_irq_mask,
+	.irq_unmask	= gxp_gpio_pl_irq_unmask,
+	.irq_set_type	= gxp_gpio_pl_set_type,
+	.flags = IRQCHIP_IMMUTABLE,
+};
+
+static const struct of_device_id gxp_gpio_of_match[] = {
+	{ .compatible = "hpe,gxp-gpio-pl" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, gxp_gpio_of_match);
+
+static int gxp_gpio_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct gxp_gpio_drvdata *drvdata;
+	struct gpio_irq_chip *girq;
+	unsigned int val;
+
+	/* Initialize global vars */
+	psu_presence = 0;
+
+	drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, drvdata);
+
+	drvdata->base = gxp_gpio_init_regmap(pdev, "base", false);
+	if (IS_ERR(drvdata->base))
+		return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->base),
+				     "failed to map base\n");
+
+	drvdata->interrupt = gxp_gpio_init_regmap(pdev, "interrupt", true);
+	if (IS_ERR(drvdata->interrupt))
+		return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->interrupt),
+				     "failed to map interrupt base\n");
+
+	/* Necessary to read the server id */
+	gxp_gpio_debuginit(pdev);
+
+	/* Initialize psu_presence variable */
+	regmap_read(drvdata->base, PLREG_PSU_INST, &val);
+	psu_presence = val;
+
+	drvdata->chip = template_chip;
+	drvdata->chip.ngpio = 80;
+	drvdata->chip.parent = &pdev->dev;
+
+	girq = &drvdata->chip.irq;
+	gpio_irq_chip_set_chip(girq, &gxp_plreg_irqchip);
+	girq->parent_handler = NULL;
+	girq->num_parents = 0;
+	girq->parents = NULL;
+	girq->default_type = IRQ_TYPE_NONE;
+	girq->handler = handle_bad_irq;
+
+	girq->init_hw = gxp_gpio_irq_init_hw;
+
+	ret = devm_gpiochip_add_data(&pdev->dev, &drvdata->chip, drvdata);
+	if (ret < 0)
+		return dev_err_probe(&pdev->dev, ret, "Could not register gpiochip for plreg\n");
+
+	regmap_update_bits(drvdata->interrupt,
+			   PLREG_INT_HI_PRI_EN,
+			   PLREG_GRP4_GRP5_MASK,
+			   PLREG_GRP4_GRP5_MASK);
+	regmap_update_bits(drvdata->interrupt,
+			   PLREG_INT_GRP_STAT_MASK,
+			   PLREG_GRP4_GRP5_MASK,
+			   0x00);
+
+	regmap_read(drvdata->interrupt, PLREG_INT_HI_PRI_EN, &val);
+	regmap_read(drvdata->interrupt, PLREG_INT_GRP_STAT_MASK, &val);
+
+	ret = platform_get_irq(pdev, 0);
+	if (ret < 0)
+		return ret;
+
+	drvdata->irq = ret;
+
+	return devm_request_irq(&pdev->dev, drvdata->irq, gxp_gpio_pl_irq_handle,
+				IRQF_SHARED, "gxp-pl", drvdata);
+}
+
+static struct platform_driver gxp_gpio_driver = {
+	.driver = {
+		.name = "gxp-gpio-pl",
+		.of_match_table = gxp_gpio_of_match,
+	},
+	.probe = gxp_gpio_probe,
+};
+module_platform_driver(gxp_gpio_driver);
+
+MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>");
+MODULE_DESCRIPTION("GPIO PL interface for GXP");
+MODULE_LICENSE("GPL");