diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 3d17d40fa635..23a121c2e176 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -45,6 +45,7 @@ config GPIO_ACPI
 
 config GPIOLIB_IRQCHIP
 	select IRQ_DOMAIN
+	select IRQ_DOMAIN_HIERARCHY
 	bool
 
 config DEBUG_GPIO
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index e013d417a936..f976e95e54f5 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -1718,6 +1718,175 @@ void gpiochip_set_nested_irqchip(struct gpio_chip *gpiochip,
 }
 EXPORT_SYMBOL_GPL(gpiochip_set_nested_irqchip);
 
+/**
+ * gpiochip_set_hierarchical_irqchip() - connects a hierarchical irqchip
+ * to a gpiochip
+ * @gpiochip: the gpiochip to set the irqchip hierarchical handler to
+ * @irqchip: the irqchip to handle this level of the hierarchy, the interrupt
+ * will then percolate up to the parent
+ */
+void gpiochip_set_hierarchical_irqchip(struct gpio_chip *gpiochip,
+				       struct irq_chip *irqchip)
+{
+	if (!gpiochip->irq.domain) {
+		chip_err(gpiochip, "called %s before setting up irqchip\n",
+			 __func__);
+		return;
+	}
+	if (!gpiochip->irq.parent_domain) {
+		chip_err(gpiochip, "tried to create a hierarchy on non-hierarchical GPIO irqchip\n");
+		return;
+	}
+	/* DT will deal with mapping each IRQ as we go along */
+	if (is_of_node(gpiochip->irq.fwnode))
+		return;
+
+	/*
+	 * This is for legacy and boardfile "irqchip" fwnodes: allocate
+	 * irqs upfront instead of dynamically since we don't have the
+	 * dynamic type of allocation that hardware description languages
+	 * provide.
+	 */
+	if (is_fwnode_irqchip(gpiochip->irq.fwnode)) {
+		int i;
+		int ret;
+
+		for (i = 0; i < gpiochip->irq.parent_n_irq_maps; i++) {
+			const struct gpiochip_hierarchy_map *map =
+				&gpiochip->irq.parent_irq_map[i];
+			struct irq_fwspec fwspec;
+
+			fwspec.fwnode = gpiochip->irq.fwnode;
+			/* This is the hwirq for the GPIO line side of things */
+			fwspec.param[0] = map->hwirq;
+			/* Just pick something */
+			fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
+			fwspec.param_count = 2;
+			ret = __irq_domain_alloc_irqs(gpiochip->irq.domain,
+						      /* just pick something */
+						      -1,
+						      1,
+						      NUMA_NO_NODE,
+						      &fwspec,
+						      false,
+						      NULL);
+			if (ret < 0) {
+				chip_err(gpiochip,
+					 "can not allocate irq for GPIO line %d parent hwirq %d in hierarchy domain: %d\n",
+					 map->hwirq, map->parent_hwirq,
+					 ret);
+			}
+		}
+	}
+
+	chip_err(gpiochip, "%s unknown fwnode type proceed anyway\n", __func__);
+
+	return;
+}
+EXPORT_SYMBOL_GPL(gpiochip_set_hierarchical_irqchip);
+
+static int gpiochip_hierarchy_irq_domain_translate(struct irq_domain *d,
+						   struct irq_fwspec *fwspec,
+						   unsigned long *hwirq,
+						   unsigned int *type)
+{
+	/* We support standard DT translation */
+	if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) {
+		return irq_domain_translate_twocell(d, fwspec, hwirq, type);
+	}
+
+	/* This is for board files and others not using DT */
+	if (is_fwnode_irqchip(fwspec->fwnode)) {
+		int ret;
+
+		ret = irq_domain_translate_twocell(d, fwspec, hwirq, type);
+		if (ret)
+			return ret;
+		WARN_ON(*type == IRQ_TYPE_NONE);
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int gpiochip_hierarchy_irq_domain_alloc(struct irq_domain *d,
+					       unsigned int irq,
+					       unsigned int nr_irqs,
+					       void *data)
+{
+	struct gpio_chip *chip = d->host_data;
+	irq_hw_number_t hwirq;
+	unsigned int type = IRQ_TYPE_NONE;
+	struct irq_fwspec *fwspec = data;
+	int ret;
+	int i;
+
+	ret = gpiochip_hierarchy_irq_domain_translate(d, fwspec, &hwirq, &type);
+	if (ret)
+		return ret;
+
+	chip_info(chip, "allocate IRQ %d..%d, hwirq %lu..%lu\n",
+		  irq, irq + nr_irqs - 1,
+		  hwirq, hwirq + nr_irqs - 1);
+
+	for (i = 0; i < nr_irqs; i++) {
+		struct irq_fwspec parent_fwspec;
+		const struct gpiochip_hierarchy_map *map = NULL;
+		int j;
+
+		/* Find the map, maybe not all lines support IRQs */
+		for (j = 0; j < chip->irq.parent_n_irq_maps; j++) {
+			map = &chip->irq.parent_irq_map[j];
+			if (map->hwirq == hwirq)
+				break;
+		}
+		if (j == chip->irq.parent_n_irq_maps) {
+			chip_err(chip, "can't look up hwirq %lu\n", hwirq);
+			return -EINVAL;
+		}
+		chip_dbg(chip, "found parent hwirq %u\n", map->parent_hwirq);
+
+		/*
+		 * We set handle_bad_irq because the .set_type() should
+		 * always be invoked and set the right type of handler.
+		 */
+		irq_domain_set_info(d,
+				    irq + i,
+				    hwirq + i,
+				    chip->irq.chip,
+				    chip,
+				    handle_bad_irq,
+				    NULL, NULL);
+		irq_set_probe(irq + i);
+
+		/*
+		 * Create a IRQ fwspec to send up to the parent irqdomain:
+		 * specify the hwirq we address on the parent and tie it
+		 * all together up the chain.
+		 */
+		parent_fwspec.fwnode = d->parent->fwnode;
+		parent_fwspec.param_count = 2;
+		parent_fwspec.param[0] = map->parent_hwirq;
+		/* This parent only handles asserted level IRQs */
+		parent_fwspec.param[1] = map->parent_type;
+		chip_dbg(chip, "alloc_irqs_parent for %d parent hwirq %d\n",
+			 irq + i, map->parent_hwirq);
+		ret = irq_domain_alloc_irqs_parent(d, irq + i, 1,
+						   &parent_fwspec);
+		if (ret)
+			chip_err(chip,
+				 "failed to allocate parent hwirq %d for hwirq %lu\n",
+				 map->parent_hwirq, hwirq);
+	}
+
+	return 0;
+}
+
+static const struct irq_domain_ops gpiochip_hierarchy_domain_ops = {
+	.translate = gpiochip_hierarchy_irq_domain_translate,
+	.alloc = gpiochip_hierarchy_irq_domain_alloc,
+	.free = irq_domain_free_irqs_common,
+};
+
 /**
  * gpiochip_irq_map() - maps an IRQ into a GPIO irqchip
  * @d: the irqdomain used by this irqchip
@@ -1825,10 +1994,23 @@ EXPORT_SYMBOL_GPL(gpiochip_irq_domain_deactivate);
 
 static int gpiochip_to_irq(struct gpio_chip *chip, unsigned offset)
 {
+	struct irq_domain *domain = chip->irq.domain;
+
 	if (!gpiochip_irqchip_irq_valid(chip, offset))
 		return -ENXIO;
 
-	return irq_create_mapping(chip->irq.domain, offset);
+	if (irq_domain_is_hierarchy(domain)) {
+		struct irq_fwspec spec;
+
+		spec.fwnode = domain->fwnode;
+		spec.param_count = 2;
+		spec.param[0] = offset;
+		spec.param[1] = IRQ_TYPE_NONE;
+
+		return irq_create_fwspec_mapping(&spec);
+	}
+
+	return irq_create_mapping(domain, offset);
 }
 
 static int gpiochip_irq_reqres(struct irq_data *d)
@@ -1905,7 +2087,7 @@ static int gpiochip_add_irqchip(struct gpio_chip *gpiochip,
 				struct lock_class_key *request_key)
 {
 	struct irq_chip *irqchip = gpiochip->irq.chip;
-	const struct irq_domain_ops *ops;
+	const struct irq_domain_ops *ops = NULL;
 	struct device_node *np;
 	unsigned int type;
 	unsigned int i;
@@ -1941,14 +2123,36 @@ static int gpiochip_add_irqchip(struct gpio_chip *gpiochip,
 	gpiochip->irq.lock_key = lock_key;
 	gpiochip->irq.request_key = request_key;
 
+	/* Some drivers provide custom irqdomain ops */
 	if (gpiochip->irq.domain_ops)
 		ops = gpiochip->irq.domain_ops;
-	else
-		ops = &gpiochip_domain_ops;
 
-	gpiochip->irq.domain = irq_domain_add_simple(np, gpiochip->ngpio,
-						     gpiochip->irq.first,
-						     ops, gpiochip);
+	/* If a parent irqdomain is provided, let's build a hierarchy */
+	if (gpiochip->irq.parent_domain) {
+		if (!ops)
+			ops = &gpiochip_hierarchy_domain_ops;
+		if (!gpiochip->irq.parent_domain ||
+		    !gpiochip->irq.parent_irq_map ||
+		    !gpiochip->irq.parent_n_irq_maps ||
+		    !gpiochip->irq.fwnode) {
+			chip_err(gpiochip, "missing irqdomain vital data\n");
+			return -EINVAL;
+		}
+		gpiochip->irq.domain = irq_domain_create_hierarchy(
+			gpiochip->irq.parent_domain,
+			IRQ_DOMAIN_FLAG_HIERARCHY,
+			gpiochip->irq.parent_n_irq_maps,
+			gpiochip->irq.fwnode,
+			ops,
+			gpiochip);
+	} else {
+		if (!ops)
+			ops = &gpiochip_domain_ops;
+		gpiochip->irq.domain = irq_domain_add_simple(np,
+			gpiochip->ngpio,
+			gpiochip->irq.first,
+			ops, gpiochip);
+	}
 	if (!gpiochip->irq.domain)
 		return -EINVAL;
 
diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h
index a1d273c96016..65193878a0e1 100644
--- a/include/linux/gpio/driver.h
+++ b/include/linux/gpio/driver.h
@@ -22,6 +22,21 @@ enum gpiod_flags;
 #ifdef CONFIG_GPIOLIB
 
 #ifdef CONFIG_GPIOLIB_IRQCHIP
+
+/**
+ * struct gpiochip_hierarchy_map - hierarchical IRQ GPIO to parent IRQ map
+ * @hwirq: offset of the GPIO line (irq line) locally on the gpio_chip
+ * @parent_hwirq: hwirq on the parent IRQ controller that this local hardware
+ * irq is mapped to
+ * @parent_type: what type of IRQ the parent uses for this line, such
+ * as most typical IRQ_TYPE_LEVEL_HIGH.
+ */
+struct gpiochip_hierarchy_map {
+	int hwirq;
+	int parent_hwirq;
+	unsigned int parent_type;
+};
+
 /**
  * struct gpio_irq_chip - GPIO interrupt controller
  */
@@ -48,6 +63,42 @@ struct gpio_irq_chip {
 	 */
 	const struct irq_domain_ops *domain_ops;
 
+	/**
+	 * @fwnode:
+	 *
+	 * Firmware node corresponding to this gpiochip/irqchip, necessary
+	 * for hierarchical irqdomain support.
+	 */
+	struct fwnode_handle *fwnode;
+
+	/**
+	 * @parent_domain:
+	 *
+	 * If non-NULL, will be set as the parent of this GPIO interrupt
+	 * controller's IRQ domain to establish a hierarchical interrupt
+	 * domain. The presence of this will activate the hierarchical
+	 * interrupt support.
+	 */
+	struct irq_domain *parent_domain;
+
+	/**
+	 * @parent_irq_map:
+	 *
+	 * This should contain an array defining the maps between any hardware
+	 * GPIO line on the gpio_chip and the corresponding hardware IRQ on the
+	 * parent IRQ controller (irqchip) and the parent IRQ type for the
+	 * line when using hierarchical interrupts.
+	 */
+	const struct gpiochip_hierarchy_map *parent_irq_map;
+
+	/**
+	 * @parent_n_irq_maps:
+	 *
+	 * The number of parent irq map tuples in the array above, used for
+	 * hierarchical interrupt support.
+	 */
+	int parent_n_irq_maps;
+
 	/**
 	 * @handler:
 	 *
@@ -489,6 +540,9 @@ void gpiochip_set_nested_irqchip(struct gpio_chip *gpiochip,
 		struct irq_chip *irqchip,
 		unsigned int parent_irq);
 
+void gpiochip_set_hierarchical_irqchip(struct gpio_chip *gpiochip,
+				       struct irq_chip *irqchip);
+
 int gpiochip_irqchip_add_key(struct gpio_chip *gpiochip,
 			     struct irq_chip *irqchip,
 			     unsigned int first_irq,
