--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NPCM Serial GPIO Driver
+ *
+ * Copyright (C) 2021 Nuvoton Technologies
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/gpio/driver.h>
+#include <linux/hashtable.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/units.h>
+
+#define MAX_NR_HW_SGPIO                64
+
+#define  NPCM_IOXCFG1          0x2A
+#define  NPCM_IOXCFG1_SFT_CLK  GENMASK(3, 0)
+#define  NPCM_IOXCFG1_SCLK_POL BIT(4)
+#define  NPCM_IOXCFG1_LDSH_POL BIT(5)
+
+#define  NPCM_IOXCTS                   0x28
+#define  NPCM_IOXCTS_IOXIF_EN          BIT(7)
+#define  NPCM_IOXCTS_RD_MODE           GENMASK(2, 1)
+#define  NPCM_IOXCTS_RD_MODE_PERIODIC  BIT(2)
+
+#define  NPCM_IOXCFG2          0x2B
+#define  NPCM_IOXCFG2_PORT     GENMASK(3, 0)
+
+#define  NPCM_IXOEVCFG_MASK    GENMASK(1, 0)
+#define  NPCM_IXOEVCFG_FALLING BIT(1)
+#define  NPCM_IXOEVCFG_RISING  BIT(0)
+#define  NPCM_IXOEVCFG_BOTH    (NPCM_IXOEVCFG_FALLING | NPCM_IXOEVCFG_RISING)
+
+#define NPCM_CLK_MHZ   (8 * HZ_PER_MHZ)
+#define NPCM_750_OPT   6
+#define NPCM_845_OPT   5
+
+#define GPIO_BANK(x)    ((x) / 8)
+#define GPIO_BIT(x)     ((x) % 8)
+
+/*
+ * Select the frequency of shift clock.
+ * The shift clock is a division of the APB clock.
+ */
+struct npcm_clk_cfg {
+       unsigned int    *sft_clk;
+       unsigned int    *clk_sel;
+       unsigned int    cfg_opt;
+};
+
+struct npcm_sgpio {
+       struct gpio_chip chip;
+       struct clk *pclk;
+       struct irq_chip intc;
+       raw_spinlock_t lock;
+
+       void __iomem *base;
+       int irq;
+       u8 nin_sgpio;
+       u8 nout_sgpio;
+       u8 in_port;
+       u8 out_port;
+       u8 int_type[MAX_NR_HW_SGPIO];
+};
+
+struct npcm_sgpio_bank {
+       u8 rdata_reg;
+       u8 wdata_reg;
+       u8 event_config;
+       u8 event_status;
+};
+
+enum npcm_sgpio_reg {
+       READ_DATA,
+       WRITE_DATA,
+       EVENT_CFG,
+       EVENT_STS,
+};
+
+static const struct npcm_sgpio_bank npcm_sgpio_banks[] = {
+       {
+               .wdata_reg = 0x00,
+               .rdata_reg = 0x08,
+               .event_config = 0x10,
+               .event_status = 0x20,
+       },
+       {
+               .wdata_reg = 0x01,
+               .rdata_reg = 0x09,
+               .event_config = 0x12,
+               .event_status = 0x21,
+       },
+       {
+               .wdata_reg = 0x02,
+               .rdata_reg = 0x0a,
+               .event_config = 0x14,
+               .event_status = 0x22,
+       },
+       {
+               .wdata_reg = 0x03,
+               .rdata_reg = 0x0b,
+               .event_config = 0x16,
+               .event_status = 0x23,
+       },
+       {
+               .wdata_reg = 0x04,
+               .rdata_reg = 0x0c,
+               .event_config = 0x18,
+               .event_status = 0x24,
+       },
+       {
+               .wdata_reg = 0x05,
+               .rdata_reg = 0x0d,
+               .event_config = 0x1a,
+               .event_status = 0x25,
+       },
+       {
+               .wdata_reg = 0x06,
+               .rdata_reg = 0x0e,
+               .event_config = 0x1c,
+               .event_status = 0x26,
+       },
+       {
+               .wdata_reg = 0x07,
+               .rdata_reg = 0x0f,
+               .event_config = 0x1e,
+               .event_status = 0x27,
+       },
+};
+
+static void __iomem *bank_reg(struct npcm_sgpio *gpio,
+                             const struct npcm_sgpio_bank *bank,
+                             const enum npcm_sgpio_reg reg)
+{
+       switch (reg) {
+       case READ_DATA:
+               return gpio->base + bank->rdata_reg;
+       case WRITE_DATA:
+               return gpio->base + bank->wdata_reg;
+       case EVENT_CFG:
+               return gpio->base + bank->event_config;
+       case EVENT_STS:
+               return gpio->base + bank->event_status;
+       default:
+               /* actually if code runs to here, it's an error case */
+               dev_WARN(gpio->chip.parent, "Getting here is an error condition");
+               return NULL;
+       }
+}
+
+static const struct npcm_sgpio_bank *offset_to_bank(unsigned int offset)
+{
+       unsigned int bank = GPIO_BANK(offset);
+
+       return &npcm_sgpio_banks[bank];
+}
+
+static void npcm_sgpio_irqd_to_data(struct irq_data *d,
+                                   struct npcm_sgpio **gpio,
+                                   const struct npcm_sgpio_bank **bank,
+                                   u8 *bit, unsigned int *offset)
+{
+       struct npcm_sgpio *internal;
+
+       *offset = irqd_to_hwirq(d);
+       internal = irq_data_get_irq_chip_data(d);
+
+       *gpio = internal;
+       *offset -= internal->nout_sgpio;
+       *bank = offset_to_bank(*offset);
+       *bit = GPIO_BIT(*offset);
+}
+
+static int npcm_sgpio_init_port(struct npcm_sgpio *gpio)
+{
+       u8 in_port, out_port, set_port, reg;
+
+       in_port = GPIO_BANK(gpio->nin_sgpio);
+       if (GPIO_BIT(gpio->nin_sgpio) > 0)
+               in_port += 1;
+
+       out_port = GPIO_BANK(gpio->nout_sgpio);
+       if (GPIO_BIT(gpio->nout_sgpio) > 0)
+               out_port += 1;
+
+       gpio->in_port = in_port;
+       gpio->out_port = out_port;
+       set_port = (out_port & NPCM_IOXCFG2_PORT) << 4 |
+                  (in_port & NPCM_IOXCFG2_PORT);
+       iowrite8(set_port, gpio->base + NPCM_IOXCFG2);
+
+       reg = ioread8(gpio->base + NPCM_IOXCFG2);
+
+       return reg == set_port ? 0 : -EINVAL;
+
+}
+
+static int npcm_sgpio_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+       struct npcm_sgpio *gpio = gpiochip_get_data(gc);
+
+       return offset < gpio->nout_sgpio ? -EINVAL : 0;
+
+}
+
+static int npcm_sgpio_dir_out(struct gpio_chip *gc, unsigned int offset, int val)
+{
+       gc->set(gc, offset, val);
+
+       return 0;
+}
+
+static int npcm_sgpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+       struct npcm_sgpio *gpio = gpiochip_get_data(gc);
+
+       if (offset < gpio->nout_sgpio)
+               return GPIO_LINE_DIRECTION_OUT;
+
+       return GPIO_LINE_DIRECTION_IN;
+}
+
+static void npcm_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+       struct npcm_sgpio *gpio = gpiochip_get_data(gc);
+       const struct  npcm_sgpio_bank *bank = offset_to_bank(offset);
+       void __iomem *addr;
+       u8 reg = 0;
+
+       addr = bank_reg(gpio, bank, WRITE_DATA);
+       reg = ioread8(addr);
+
+       if (val)
+               reg |= BIT(GPIO_BIT(offset));
+       else
+               reg &= ~BIT(GPIO_BIT(offset));
+
+       iowrite8(reg, addr);
+}
+
+static int npcm_sgpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+       struct npcm_sgpio *gpio = gpiochip_get_data(gc);
+       const struct  npcm_sgpio_bank *bank;
+       void __iomem *addr;
+       u8 reg;
+
+       if (offset < gpio->nout_sgpio) {
+               bank = offset_to_bank(offset);
+               addr = bank_reg(gpio, bank, WRITE_DATA);
+       } else {
+               offset -= gpio->nout_sgpio;
+               bank = offset_to_bank(offset);
+               addr = bank_reg(gpio, bank, READ_DATA);
+       }
+
+       reg = ioread8(addr);
+
+       return !!(reg & BIT(GPIO_BIT(offset)));
+}
+
+static void npcm_sgpio_setup_enable(struct npcm_sgpio *gpio, bool enable)
+{
+       u8 reg;
+
+       reg = ioread8(gpio->base + NPCM_IOXCTS);
+       reg = (reg & ~NPCM_IOXCTS_RD_MODE) | NPCM_IOXCTS_RD_MODE_PERIODIC;
+
+       if (enable)
+               reg |= NPCM_IOXCTS_IOXIF_EN;
+       else
+               reg &= ~NPCM_IOXCTS_IOXIF_EN;
+
+       iowrite8(reg, gpio->base + NPCM_IOXCTS);
+}
+
+static int npcm_sgpio_setup_clk(struct npcm_sgpio *gpio,
+                               const struct npcm_clk_cfg *clk_cfg)
+{
+       unsigned long apb_freq;
+       u32 val;
+       u8 tmp;
+       int i;
+
+       apb_freq = clk_get_rate(gpio->pclk);
+       tmp = ioread8(gpio->base + NPCM_IOXCFG1) & ~NPCM_IOXCFG1_SFT_CLK;
+
+       for (i = clk_cfg->cfg_opt-1; i > 0; i--) {
+               val = apb_freq / clk_cfg->sft_clk[i];
+               if (NPCM_CLK_MHZ > val) {
+                       iowrite8(clk_cfg->clk_sel[i] | tmp,
+                                gpio->base + NPCM_IOXCFG1);
+                       return 0;
+               }
+       }
+
+       return -EINVAL;
+}
+
+static void npcm_sgpio_irq_init_valid_mask(struct gpio_chip *gc,
+                                          unsigned long *valid_mask,
+                                          unsigned int ngpios)
+{
+       struct npcm_sgpio *gpio = gpiochip_get_data(gc);
+
+       /* input GPIOs in the high range */
+       bitmap_set(valid_mask, gpio->nout_sgpio, gpio->nin_sgpio);
+       bitmap_clear(valid_mask, 0, gpio->nout_sgpio);
+}
+
+static void npcm_sgpio_irq_set_mask(struct irq_data *d, bool set)
+{
+       const struct npcm_sgpio_bank *bank;
+       struct npcm_sgpio *gpio;
+       unsigned long flags;
+       void __iomem *addr;
+       unsigned int offset;
+       u16 reg, type;
+       u8 bit;
+
+       npcm_sgpio_irqd_to_data(d, &gpio, &bank, &bit, &offset);
+       addr = bank_reg(gpio, bank, EVENT_CFG);
+
+       reg = ioread16(addr);
+       if (set) {
+               reg &= ~(NPCM_IXOEVCFG_MASK << (bit * 2));
+       } else {
+               type = gpio->int_type[offset];
+               reg |= (type << (bit * 2));
+       }
+
+       raw_spin_lock_irqsave(&gpio->lock, flags);
+
+       npcm_sgpio_setup_enable(gpio, false);
+
+       iowrite16(reg, addr);
+
+       npcm_sgpio_setup_enable(gpio, true);
+
+       addr = bank_reg(gpio, bank, EVENT_STS);
+       reg = ioread8(addr);
+       reg |= BIT(bit);
+       iowrite8(reg, addr);
+
+       raw_spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static void npcm_sgpio_irq_ack(struct irq_data *d)
+{
+       const struct npcm_sgpio_bank *bank;
+       struct npcm_sgpio *gpio;
+       unsigned long flags;
+       void __iomem *status_addr;
+       unsigned int offset;
+       u8 bit;
+
+       npcm_sgpio_irqd_to_data(d, &gpio, &bank, &bit, &offset);
+       status_addr = bank_reg(gpio, bank, EVENT_STS);
+       raw_spin_lock_irqsave(&gpio->lock, flags);
+       iowrite8(BIT(bit), status_addr);
+       raw_spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static void npcm_sgpio_irq_mask(struct irq_data *d)
+{
+       npcm_sgpio_irq_set_mask(d, true);
+}
+
+static void npcm_sgpio_irq_unmask(struct irq_data *d)
+{
+       npcm_sgpio_irq_set_mask(d, false);
+}
+
+static int npcm_sgpio_set_type(struct irq_data *d, unsigned int type)
+{
+       const struct npcm_sgpio_bank *bank;
+       irq_flow_handler_t handler;
+       struct npcm_sgpio *gpio;
+       unsigned long flags;
+       void __iomem *addr;
+       unsigned int offset;
+       u16 reg, val;
+       u8 bit;
+
+       npcm_sgpio_irqd_to_data(d, &gpio, &bank, &bit, &offset);
+
+       switch (type & IRQ_TYPE_SENSE_MASK) {
+       case IRQ_TYPE_EDGE_BOTH:
+               val = NPCM_IXOEVCFG_BOTH;
+               break;
+       case IRQ_TYPE_EDGE_RISING:
+       case IRQ_TYPE_LEVEL_HIGH:
+               val = NPCM_IXOEVCFG_RISING;
+               break;
+       case IRQ_TYPE_EDGE_FALLING:
+       case IRQ_TYPE_LEVEL_LOW:
+               val = NPCM_IXOEVCFG_FALLING;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (type & IRQ_TYPE_LEVEL_MASK)
+               handler = handle_level_irq;
+       else
+               handler = handle_edge_irq;
+
+       gpio->int_type[offset] = val;
+
+       raw_spin_lock_irqsave(&gpio->lock, flags);
+       npcm_sgpio_setup_enable(gpio, false);
+       addr = bank_reg(gpio, bank, EVENT_CFG);
+       reg = ioread16(addr);
+
+       reg |= (val << (bit * 2));
+
+       iowrite16(reg, addr);
+       npcm_sgpio_setup_enable(gpio, true);
+       raw_spin_unlock_irqrestore(&gpio->lock, flags);
+
+       irq_set_handler_locked(d, handler);
+
+       return 0;
+}
+
+static void npcm_sgpio_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);
+       struct npcm_sgpio *gpio = gpiochip_get_data(gc);
+       unsigned int i, j, girq;
+       unsigned long reg;
+
+       chained_irq_enter(ic, desc);
+
+       for (i = 0; i < ARRAY_SIZE(npcm_sgpio_banks); i++) {
+               const struct npcm_sgpio_bank *bank = &npcm_sgpio_banks[i];
+
+               reg = ioread8(bank_reg(gpio, bank, EVENT_STS));
+               for_each_set_bit(j, ®, 8) {
+                       girq = irq_find_mapping(gc->irq.domain,
+                                               i * 8 + gpio->nout_sgpio + j);
+                       generic_handle_domain_irq(gc->irq.domain, girq);
+               }
+       }
+
+       chained_irq_exit(ic, desc);
+}
+
+static const struct irq_chip sgpio_irq_chip = {
+       .name = "sgpio-irq",
+       .irq_ack = npcm_sgpio_irq_ack,
+       .irq_mask = npcm_sgpio_irq_mask,
+       .irq_unmask = npcm_sgpio_irq_unmask,
+       .irq_set_type = npcm_sgpio_set_type,
+       .flags  = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND,
+       GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int npcm_sgpio_setup_irqs(struct npcm_sgpio *gpio,
+                                struct platform_device *pdev)
+{
+       int rc, i;
+       struct gpio_irq_chip *irq;
+
+       rc = platform_get_irq(pdev, 0);
+       if (rc < 0)
+               return rc;
+
+       gpio->irq = rc;
+
+       npcm_sgpio_setup_enable(gpio, false);
+
+       /* Disable IRQ and clear Interrupt status registers for all SGPIO Pins. */
+       for (i = 0; i < ARRAY_SIZE(npcm_sgpio_banks); i++) {
+               const struct npcm_sgpio_bank *bank = &npcm_sgpio_banks[i];
+
+               iowrite16(0, bank_reg(gpio, bank, EVENT_CFG));
+               iowrite8(0xff, bank_reg(gpio, bank, EVENT_STS));
+       }
+
+       irq = &gpio->chip.irq;
+       gpio_irq_chip_set_chip(irq, &sgpio_irq_chip);
+       irq->init_valid_mask = npcm_sgpio_irq_init_valid_mask;
+       irq->handler = handle_bad_irq;
+       irq->default_type = IRQ_TYPE_NONE;
+       irq->parent_handler = npcm_sgpio_irq_handler;
+       irq->parent_handler_data = gpio;
+       irq->parents = &gpio->irq;
+       irq->num_parents = 1;
+
+       return 0;
+}
+
+static int npcm_sgpio_probe(struct platform_device *pdev)
+{
+       struct npcm_sgpio *gpio;
+       const struct npcm_clk_cfg *clk_cfg;
+       int rc;
+       u32 nin_gpios, nout_gpios;
+
+       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);
+
+       clk_cfg = device_get_match_data(&pdev->dev);
+       if (!clk_cfg)
+               return -EINVAL;
+
+       rc = device_property_read_u32(&pdev->dev, "nuvoton,input-ngpios",
+                                     &nin_gpios);
+       if (rc < 0)
+               return dev_err_probe(&pdev->dev, rc, "Could not read ngpios property\n");
+
+       rc = device_property_read_u32(&pdev->dev, "nuvoton,output-ngpios",
+                                     &nout_gpios);
+       if (rc < 0)
+               return dev_err_probe(&pdev->dev, rc, "Could not read ngpios property\n");
+
+       gpio->nin_sgpio = nin_gpios;
+       gpio->nout_sgpio = nout_gpios;
+       if (gpio->nin_sgpio > MAX_NR_HW_SGPIO ||
+           gpio->nout_sgpio > MAX_NR_HW_SGPIO)
+               return dev_err_probe(&pdev->dev, -EINVAL, "Number of GPIOs exceeds the maximum of %d: input: %d output: %d\n", MAX_NR_HW_SGPIO, nin_gpios, nout_gpios);
+
+       gpio->pclk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(gpio->pclk))
+               return dev_err_probe(&pdev->dev, PTR_ERR(gpio->pclk), "Could not get pclk\n");
+
+       rc = npcm_sgpio_setup_clk(gpio, clk_cfg);
+       if (rc < 0)
+               return dev_err_probe(&pdev->dev, rc, "Failed to setup clock\n");
+
+       raw_spin_lock_init(&gpio->lock);
+       gpio->chip.parent = &pdev->dev;
+       gpio->chip.ngpio = gpio->nin_sgpio + gpio->nout_sgpio;
+       gpio->chip.direction_input = npcm_sgpio_dir_in;
+       gpio->chip.direction_output = npcm_sgpio_dir_out;
+       gpio->chip.get_direction = npcm_sgpio_get_direction;
+       gpio->chip.get = npcm_sgpio_get;
+       gpio->chip.set = npcm_sgpio_set;
+       gpio->chip.label = dev_name(&pdev->dev);
+       gpio->chip.base = -1;
+
+       rc = npcm_sgpio_init_port(gpio);
+       if (rc < 0)
+               return rc;
+
+       rc = npcm_sgpio_setup_irqs(gpio, pdev);
+       if (rc < 0)
+               return rc;
+
+       rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
+       if (rc)
+               return dev_err_probe(&pdev->dev, rc, "GPIO registering failed\n");
+
+       npcm_sgpio_setup_enable(gpio, true);
+
+       return 0;
+}
+
+static unsigned int npcm750_SFT_CLK[NPCM_750_OPT] = {
+       1024, 32, 8, 4, 3, 2,
+};
+
+static unsigned int npcm750_CLK_SEL[NPCM_750_OPT] = {
+       0x00, 0x05, 0x07, 0x0C, 0x0D, 0x0E,
+};
+
+static unsigned int npcm845_SFT_CLK[NPCM_845_OPT] = {
+       1024, 32, 16, 8, 4,
+};
+
+static unsigned int npcm845_CLK_SEL[NPCM_845_OPT] = {
+       0x00, 0x05, 0x06, 0x07, 0x0C,
+};
+
+static struct npcm_clk_cfg npcm750_sgpio_pdata = {
+       .sft_clk = npcm750_SFT_CLK,
+       .clk_sel = npcm750_CLK_SEL,
+       .cfg_opt = NPCM_750_OPT,
+};
+
+static const struct npcm_clk_cfg npcm845_sgpio_pdata = {
+       .sft_clk = npcm845_SFT_CLK,
+       .clk_sel = npcm845_CLK_SEL,
+       .cfg_opt = NPCM_845_OPT,
+};
+
+static const struct of_device_id npcm_sgpio_of_table[] = {
+       { .compatible = "nuvoton,npcm750-sgpio", .data = &npcm750_sgpio_pdata, },
+       { .compatible = "nuvoton,npcm845-sgpio", .data = &npcm845_sgpio_pdata, },
+       {}
+};
+MODULE_DEVICE_TABLE(of, npcm_sgpio_of_table);
+
+static struct platform_driver npcm_sgpio_driver = {
+       .driver = {
+               .name = KBUILD_MODNAME,
+               .of_match_table = npcm_sgpio_of_table,
+       },
+       .probe  = npcm_sgpio_probe,
+};
+module_platform_driver(npcm_sgpio_driver);
+
+MODULE_AUTHOR("Jim Liu <jjliu0@nuvoton.com>");
+MODULE_AUTHOR("Joseph Liu <kwliu@nuvoton.com>");
+MODULE_DESCRIPTION("Nuvoton NPCM Serial GPIO Driver");
+MODULE_LICENSE("GPL v2");