blob: 3f6c7cd658cb359e352880c8bd32cb3c01c71990 [file] [log] [blame]
From 80f5982795238bbde8e57a07abb3908a3683ef6a Mon Sep 17 00:00:00 2001
From: David Wang <davidwang@quantatw.com>
Date: Tue, 7 Nov 2023 21:46:39 +0800
Subject: [PATCH 20/23] drivers: soc: sync npcm soc
---
drivers/soc/Kconfig | 1 +
drivers/soc/Makefile | 1 +
drivers/soc/nuvoton/Kconfig | 29 ++
drivers/soc/nuvoton/Makefile | 4 +
drivers/soc/nuvoton/npcm-cerberus.c | 305 +++++++++++++++
drivers/soc/nuvoton/npcm-espi-vwgpio.c | 504 +++++++++++++++++++++++++
6 files changed, 844 insertions(+)
create mode 100644 drivers/soc/nuvoton/Kconfig
create mode 100644 drivers/soc/nuvoton/Makefile
create mode 100644 drivers/soc/nuvoton/npcm-cerberus.c
create mode 100644 drivers/soc/nuvoton/npcm-espi-vwgpio.c
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index e8a30c4c5aec..3e5b48dba3cf 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -12,6 +12,7 @@ source "drivers/soc/imx/Kconfig"
source "drivers/soc/ixp4xx/Kconfig"
source "drivers/soc/litex/Kconfig"
source "drivers/soc/mediatek/Kconfig"
+source "drivers/soc/nuvoton/Kconfig"
source "drivers/soc/qcom/Kconfig"
source "drivers/soc/renesas/Kconfig"
source "drivers/soc/rockchip/Kconfig"
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index a05e9fbcd3e0..8e2c8444132f 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -17,6 +17,7 @@ obj-y += ixp4xx/
obj-$(CONFIG_SOC_XWAY) += lantiq/
obj-$(CONFIG_LITEX_SOC_CONTROLLER) += litex/
obj-y += mediatek/
+obj-y += nuvoton/
obj-y += amlogic/
obj-y += qcom/
obj-y += renesas/
diff --git a/drivers/soc/nuvoton/Kconfig b/drivers/soc/nuvoton/Kconfig
new file mode 100644
index 000000000000..20f144d9aaa6
--- /dev/null
+++ b/drivers/soc/nuvoton/Kconfig
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+if ARCH_NPCM || COMPILE_TEST
+
+menu "NUVOTON SoC drivers"
+
+config NPCM_MBOX_CERBERUS
+ tristate "NPCM CERBERUS Mailbox"
+ depends on ARCH_NPCM || COMPILE_TEST
+ depends on MAILBOX
+ help
+ An implementation of the NPCM mailbox controller.
+ It is used to send message between the Core processor and other
+ processors on the BMC such as TIP and CP.
+ Select Y here if you want to use NPCM mailbox controller.
+
+config NPCM_ESPI_VWGPIO
+ tristate "NPCM eSPI Virtual Wire GPIO"
+ depends on ARCH_NPCM || COMPILE_TEST
+ help
+ An implementation of the NPCM eSPI virutal wire gpio driver.
+ It is used to control the virtual wire master-to-slave and
+ slave-to-master GPIO. It also registers an gpio controller
+ to handle the master-to-slave gpio event.
+ Select Y here if you want to use eSPI virtual wire gpio.
+
+endmenu
+
+endif
diff --git a/drivers/soc/nuvoton/Makefile b/drivers/soc/nuvoton/Makefile
new file mode 100644
index 000000000000..483460e6f399
--- /dev/null
+++ b/drivers/soc/nuvoton/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_NPCM_MBOX_CERBERUS) += npcm-cerberus.o
+obj-$(CONFIG_NPCM_ESPI_VWGPIO) += npcm-espi-vwgpio.o
+
diff --git a/drivers/soc/nuvoton/npcm-cerberus.c b/drivers/soc/nuvoton/npcm-cerberus.c
new file mode 100644
index 000000000000..11dab9700a95
--- /dev/null
+++ b/drivers/soc/nuvoton/npcm-cerberus.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2022, Microsoft Corporation
+
+#define pr_fmt(fmt) "cerberus-on-TIP: " fmt
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mailbox_client.h>
+#include <linux/miscdevice.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <linux/kfifo.h>
+#include <linux/slab.h>
+
+#define MSG_QUEUE_SIZE 32 // Max number of msgs in the queue
+#define MAX_MSG_SIZE 2048 // Should be equal to rd shmem size
+
+/**
+ * struct chan_shmem - mbx channel's shmem info
+ *
+ * @off: IO memory offset address of shmem
+ * @size: Size of the shmem region
+ */
+struct mbx_shmem {
+ u8 __iomem *off;
+ size_t size;
+};
+
+/**
+ * struct msg - message node in the queue
+ *
+ * @len: length of the message
+ * @buf: message buffer
+ */
+struct cerberus_msg {
+ u8 buf[MAX_MSG_SIZE];
+};
+
+/**
+ * struct cerberus_drvinfo - cerberus driver info
+ *
+ * @pdev: Reference to platform device
+ * @cl: Cerberus mbox client info
+ * @chan: Mailbox channel info
+ * @rd_win: Mailbox shmem read window
+ * @wr_win: Mailbox shmem write window
+ * @mutex: Lock for serializing the writes
+ * @lock: Spinlock for IO operations
+ * @mq: Queue to hold the messages read from Cerberus TIP
+ * @miscdev: Miscellaneous device structure
+ */
+struct cerberus_drvinfo {
+ struct platform_device *pdev;
+ struct mbox_client cl;
+ struct mbox_chan *chan;
+ struct mbx_shmem rd_win;
+ struct mbx_shmem wr_win;
+ struct mutex mutex;
+ spinlock_t lock;
+ DECLARE_KFIFO_PTR(mq, struct cerberus_msg);
+ struct miscdevice miscdev;
+};
+
+static struct cerberus_drvinfo *to_cerberus_drvinfo(struct file *fp)
+{
+ return container_of(fp->private_data, struct cerberus_drvinfo, miscdev);
+}
+
+ssize_t cerberus_read(struct file *fp, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ ssize_t ret;
+ struct cerberus_drvinfo *cerberus = to_cerberus_drvinfo(fp);
+ unsigned long flags;
+ struct cerberus_msg *rmsg;
+
+ /* offset always should be 0 */
+ *ppos = 0;
+
+ if ((fp->f_flags & O_NONBLOCK) && kfifo_is_empty(&cerberus->mq)) {
+ return -EAGAIN;
+ }
+
+ rmsg = kmalloc(sizeof(struct cerberus_msg), GFP_KERNEL);
+ if (!rmsg)
+ return 0;
+
+ spin_lock_irqsave(&cerberus->lock, flags);
+ ret = kfifo_len(&cerberus->mq);
+ if (ret > 0)
+ kfifo_out(&cerberus->mq, rmsg, 1);
+
+ spin_unlock_irqrestore(&cerberus->lock, flags);
+
+ if (ret <= 0) {
+ ret = 0;
+ goto fail_free_resources;
+ }
+
+ ret = simple_read_from_buffer(buf, count, ppos, rmsg->buf,
+ cerberus->rd_win.size);
+fail_free_resources:
+ kfree(rmsg);
+ return ret;
+}
+
+/**
+ * Callback handler for data received from Cerberus.
+ * This function copies the message from read shmem to the next
+ * message slot in the message queue, and wakes up one thread waiting
+ * on the message
+ *
+ * @cl: mailbox client
+ * @msg: message from cerberus (not used)
+ */
+static void msg_from_cerberus(struct mbox_client *cl, void *msg)
+{
+ unsigned long flags;
+ struct cerberus_drvinfo *cerberus = dev_get_drvdata(cl->dev);
+
+ spin_lock_irqsave(&cerberus->lock, flags);
+ if (kfifo_is_full(&cerberus->mq)) {
+ pr_err("Msg queue is full. Oldest message will be lost\n");
+ kfifo_skip(&cerberus->mq);
+ }
+ kfifo_in(&cerberus->mq, (struct cerberus_msg *)cerberus->rd_win.off, 1);
+ spin_unlock_irqrestore(&cerberus->lock, flags);
+}
+
+ssize_t cerberus_write(struct file *fp, const char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ ssize_t ret;
+ struct cerberus_drvinfo *cerberus = to_cerberus_drvinfo(fp);
+ struct cerberus_msg *wmsg;
+
+ /* Write size must be with in window limits */
+ if (count > cerberus->wr_win.size)
+ return -EINVAL;
+
+ /* offset always should be 0 */
+ *ppos = 0;
+
+ wmsg = kmalloc(sizeof(struct cerberus_msg), GFP_KERNEL);
+ if (!wmsg)
+ return 0;
+
+ memset(wmsg, 0, sizeof(struct cerberus_msg));
+
+ if (copy_from_user(wmsg->buf, buf, count)) {
+ ret = -EFAULT;
+ goto fail_free_resources;
+ }
+
+ mutex_lock(&cerberus->mutex);
+
+ memcpy_toio(cerberus->wr_win.off, wmsg->buf, count);
+
+ /** Ring the doorbell (Blocking call) and wait for the data
+ * to be received by TIP.
+ */
+ mbox_send_message(cerberus->chan, cerberus->wr_win.off);
+
+ ret = count;
+
+fail_unlock:
+ mutex_unlock(&cerberus->mutex);
+fail_free_resources:
+ kfree(wmsg);
+ return ret;
+}
+
+static const struct file_operations cerberus_fops = {
+ .owner = THIS_MODULE,
+ .read = cerberus_read,
+ .write = cerberus_write,
+};
+
+static int cerberus_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct cerberus_drvinfo *cerberus;
+ struct resource res;
+ struct device_node *shmem;
+ resource_size_t shmem_size;
+ u8 __iomem *off;
+
+ cerberus = devm_kzalloc(dev, sizeof(*cerberus), GFP_KERNEL);
+ if (!cerberus) {
+ dev_err(dev, "memory not allocated for cerberus drvinfo\n");
+ return -ENOMEM;
+ }
+
+ /* extract shmem information from device node */
+ shmem = of_parse_phandle(np, "shmem", 0);
+ ret = of_address_to_resource(shmem, 0, &res);
+ of_node_put(shmem);
+ if (ret) {
+ dev_err(dev, "Failed to get shared mem resource\n");
+ return ret;
+ }
+
+ shmem_size = resource_size(&res);
+ off = devm_ioremap_resource(dev, &res);
+ if (IS_ERR(off)) {
+ dev_err(dev, "device mem io remap failed\n");
+ return PTR_ERR(off);
+ }
+
+ /* top half for write and bottom half for read */
+ cerberus->wr_win.off = off;
+ cerberus->wr_win.size = shmem_size >> 1;
+ cerberus->rd_win.off = off + cerberus->wr_win.size;
+ cerberus->rd_win.size = shmem_size - cerberus->wr_win.size;
+
+ if (unlikely(cerberus->rd_win.size != MAX_MSG_SIZE)) {
+ dev_err(dev, "Message size is not same as read shmem size\n");
+ return -EINVAL;
+ }
+
+ mutex_init(&cerberus->mutex);
+ spin_lock_init(&cerberus->lock);
+ ret = kfifo_alloc(&cerberus->mq, MSG_QUEUE_SIZE, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ /* set up and request mailbox channel */
+ cerberus->cl.dev = dev;
+ cerberus->cl.rx_callback = msg_from_cerberus; /* read non-blocking */
+ cerberus->cl.tx_done = NULL;
+ cerberus->cl.tx_block = true; /* write blocking */
+ cerberus->cl.tx_tout = 500; /* msec */
+ cerberus->chan = mbox_request_channel_byname(&cerberus->cl, "cerberus");
+ if (IS_ERR(cerberus->chan)) {
+ dev_err(dev, "mbox channel request failed\n");
+ ret = PTR_ERR(cerberus->chan);
+ goto fail_free_resources;
+ }
+
+ /* Register the driver as misc device */
+ cerberus->miscdev.minor = MISC_DYNAMIC_MINOR;
+ cerberus->miscdev.name = "cerberus";
+ cerberus->miscdev.fops = &cerberus_fops;
+ cerberus->miscdev.parent = dev;
+ ret = misc_register(&cerberus->miscdev);
+ if (ret) {
+ dev_err(dev, "Unable to register misc device\n");
+ goto fail_free_mbox_chan;
+ }
+
+ platform_set_drvdata(pdev, cerberus);
+
+ dev_info(dev, "Cerberus mailbox client driver initialized\n");
+ return 0;
+
+fail_free_mbox_chan:
+ dev_info(dev, "freeing mbox chan");
+ mbox_free_channel(cerberus->chan);
+fail_free_resources:
+ dev_info(dev, "freeing kfifo");
+ kfifo_free(&cerberus->mq);
+ return ret;
+}
+
+static int cerberus_remove(struct platform_device *pdev)
+{
+ struct cerberus_drvinfo *cerberus = platform_get_drvdata(pdev);
+
+ mbox_free_channel(cerberus->chan);
+ kfifo_free(&cerberus->mq);
+ misc_deregister(&cerberus->miscdev);
+
+ return 0;
+}
+
+static const struct of_device_id cerberus_ids[] = {
+ {
+ .compatible = "nuvoton,cerberus",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cerberus_ids);
+
+static struct platform_driver cerberus_mbox_client_driver = {
+ .probe = cerberus_probe,
+ .remove = cerberus_remove,
+ .driver = {
+ .name = "cerberus",
+ .of_match_table = cerberus_ids,
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(cerberus_mbox_client_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Parvathi Bhogaraju <pbhogaraju@microsoft.com>");
+MODULE_DESCRIPTION("Mailbox client driver for cerberus on TIP");
+
diff --git a/drivers/soc/nuvoton/npcm-espi-vwgpio.c b/drivers/soc/nuvoton/npcm-espi-vwgpio.c
new file mode 100644
index 000000000000..a69032356bdd
--- /dev/null
+++ b/drivers/soc/nuvoton/npcm-espi-vwgpio.c
@@ -0,0 +1,504 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Nuvoton Technology corporation.
+ */
+
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+#define ESPI_ESPICFG 0x004
+#define ESPICFG_VWCHANEN BIT(1)
+#define ESPICFG_VWCHN_SUPP BIT(25)
+#define ESPI_ESPISTS 0x008
+#define ESPISTS_VWUPD BIT(8)
+#define ESPI_ESPIIE 0x00C
+#define ESPIIE_VWUPDIE BIT(8)
+#define ESPI_VWGPSM(n) (0x180 + (4*(n)))
+#define VWGPSM_INDEX_EN BIT(15)
+#define ESPI_ESPIERR 0x03C
+#define ESPIERR_VWERR BIT(11)
+#define ESPI_VWGPMS(n) (0x1C0 + (4*(n)))
+#define VWGPMS_INDEX_EN BIT(15)
+#define VWGPMS_MODIFIED BIT(16)
+#define VWGPMS_IE BIT(18)
+#define ESPI_VWCTL 0x2FC
+#define VWCTL_INTWIN(x) FIELD_PREP(GENMASK(1, 0), (x))
+#define VWCTL_GPVWMAP(x) FIELD_PREP(GENMASK(3, 2), (x))
+#define VWCTL_IRQD BIT(4)
+#define VWCTL_NOVALID BIT(5)
+
+#define VWGPSM_INDEX_COUNT 16
+#define VWGPMS_INDEX_COUNT 16
+/* SM GPIO: 0~63, MS GPIO: 64~127 */
+#define VM_SMGPIO_START 0
+#define VW_SMGPIO_NUM 64
+#define VM_MSGPIO_START 64
+#define VW_MSGPIO_NUM 64
+
+/* vwgpio_event type */
+#define VW_GPIO_EVENT_EDGE_RISING 0
+#define VW_GPIO_EVENT_EDGE_FALLING 1
+#define VW_GPIO_EVENT_EDGE_BOTH 2
+#define VW_GPIO_EVENT_LEVEL_HIGH 3
+#define VW_GPIO_EVENT_LEVEL_LOW 4
+struct vwgpio_event {
+ u8 enable : 1;
+ u8 state: 1;
+ u8 type: 3;
+ u8 flags: 3;
+};
+
+struct npcm_vwgpio {
+ struct gpio_chip chip;
+ struct device *dev;
+ struct regmap *map;
+ struct vwgpio_event events[VW_MSGPIO_NUM];
+ int irq;
+ u64 mswire_default;
+};
+
+static int vwgpio_get_value(struct gpio_chip *gc, unsigned int offset)
+{
+ struct npcm_vwgpio *vwgpio = gpiochip_get_data(gc);
+ u32 wire = offset % 4;
+ u32 index;
+ u32 val;
+
+ dev_dbg(gc->parent, "%s: offset=%u\n", __func__, offset);
+ /* Accept MS GPIO only */
+ if (offset < VM_MSGPIO_START || offset >= (VM_MSGPIO_START + VW_MSGPIO_NUM))
+ return -EINVAL;
+
+ index = (offset - VM_MSGPIO_START) / 4;
+ regmap_read(vwgpio->map, ESPI_VWGPMS(index), &val);
+ /* Check wire valid bit */
+ if (!(val & BIT(wire + 4)))
+ return !!(vwgpio->mswire_default & BIT(offset - VM_MSGPIO_START));
+
+ return !!(val & BIT(wire));
+}
+
+static void vwgpio_set_value(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ struct npcm_vwgpio *vwgpio = gpiochip_get_data(gc);
+ u32 index = offset / 4;
+ u32 wire = offset % 4;
+ u32 reg;
+
+ dev_dbg(gc->parent, "%s: offset=%u, val=%d\n", __func__,
+ offset, val);
+ /* Accept SM GPIO only */
+ if (offset >= VW_SMGPIO_NUM)
+ return;
+
+ regmap_read(vwgpio->map, ESPI_VWGPSM(index), &reg);
+ /* Set index enable & wire valid */
+ reg |= BIT(wire + 4) | VWGPSM_INDEX_EN;
+ if (val)
+ reg |= BIT(wire);
+ else
+ reg &= ~BIT(wire);
+ regmap_write(vwgpio->map, ESPI_VWGPSM(index), reg);
+}
+
+static int vwgpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ return (offset >= VW_SMGPIO_NUM) ? GPIO_LINE_DIRECTION_IN
+ : GPIO_LINE_DIRECTION_OUT;
+}
+
+static int vwgpio_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+ return (offset >= VW_SMGPIO_NUM) ? 0 : -EINVAL;
+}
+
+static int vwgpio_direction_output(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ if (offset < VW_SMGPIO_NUM) {
+ gc->set(gc, offset, val);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static void npcm_vwgpio_gpms_config(struct npcm_vwgpio *vwgpio, int index,
+ bool enable, bool int_en)
+{
+ u32 val;
+
+ regmap_read(vwgpio->map, ESPI_VWGPMS(index), &val);
+ if (enable)
+ val |= VWGPMS_INDEX_EN;
+ else
+ val &= ~VWGPMS_INDEX_EN;
+
+ if (int_en)
+ val |= VWGPMS_IE;
+ else
+ val &= ~VWGPMS_IE;
+
+ regmap_write(vwgpio->map, ESPI_VWGPMS(index), val);
+}
+
+static void npcm_vwgpio_check_event(struct npcm_vwgpio *vwgpio,
+ unsigned int event_idx)
+{
+ struct gpio_chip *gc = &vwgpio->chip;
+ struct vwgpio_event *event;
+ bool raise_irq = false;
+ unsigned int girq;
+ u32 index = event_idx / 4;
+ u32 wire = event_idx % 4;
+ u8 new_state;
+ u32 val;
+
+ if (event_idx >= VW_MSGPIO_NUM)
+ return;
+
+ event = &vwgpio->events[event_idx];
+
+ regmap_read(vwgpio->map, ESPI_VWGPMS(index), &val);
+ /* Clear MODIFIED bit */
+ regmap_write(vwgpio->map, ESPI_VWGPMS(index), val | VWGPMS_MODIFIED);
+
+ /* Check event enable */
+ if (!event->enable)
+ return;
+
+ /* Check wire valid bit */
+ if (!(val & BIT(wire + 4)))
+ return;
+
+ new_state = !!(val & BIT(wire));
+ switch (event->type) {
+ case VW_GPIO_EVENT_EDGE_RISING:
+ if (event->state == 0 && new_state == 1) {
+ event->state = new_state;
+ raise_irq = true;
+ }
+ break;
+ case VW_GPIO_EVENT_EDGE_FALLING:
+ if (event->state == 1 && new_state == 0) {
+ event->state = new_state;
+ raise_irq = true;
+ }
+ break;
+ case VW_GPIO_EVENT_EDGE_BOTH:
+ if ((event->state == 1 && new_state == 0) ||
+ (event->state == 0 && new_state == 1)) {
+ event->state = new_state;
+ raise_irq = true;
+ }
+ break;
+ case VW_GPIO_EVENT_LEVEL_HIGH:
+ if (new_state == 1) {
+ event->state = new_state;
+ raise_irq = true;
+ }
+ break;
+ case VW_GPIO_EVENT_LEVEL_LOW:
+ if (new_state == 0) {
+ event->state = new_state;
+ raise_irq = true;
+ }
+ break;
+ }
+
+ if (raise_irq) {
+ girq = irq_find_mapping(gc->irq.domain,
+ VM_MSGPIO_START + event_idx);
+ generic_handle_irq(girq);
+ }
+}
+
+static irqreturn_t npcm_vwgpio_irq_handler(int irq, void *dev_id)
+{
+ struct npcm_vwgpio *vwgpio = dev_id;
+ u32 status;
+ int i;
+
+ regmap_read(vwgpio->map, ESPI_ESPISTS, &status);
+ if (!(status & ESPISTS_VWUPD))
+ return IRQ_HANDLED;
+
+ /* Clear ESPISTS_VWUPD status */
+ regmap_write(vwgpio->map, ESPI_ESPISTS, ESPISTS_VWUPD);
+ /* Check all events */
+ for (i = 0; i < VW_MSGPIO_NUM; i++)
+ npcm_vwgpio_check_event(vwgpio, i);
+
+ return IRQ_HANDLED;
+}
+
+static int npcm_vwgpio_set_irq_type(struct irq_data *d, unsigned int type)
+{
+ struct npcm_vwgpio *vwgpio = gpiochip_get_data(irq_data_get_irq_chip_data(d));
+ unsigned int gpio = irqd_to_hwirq(d);
+ unsigned int event_idx;
+
+ pr_debug("%s: gpio %u, type %d\n", __func__, gpio, type);
+ if (gpio < VM_MSGPIO_START || gpio >= (VM_MSGPIO_START + VW_MSGPIO_NUM))
+ return -EINVAL;
+ event_idx = gpio - VM_MSGPIO_START;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ vwgpio->events[event_idx].type = VW_GPIO_EVENT_EDGE_RISING;
+ irq_set_handler_locked(d, handle_edge_irq);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ vwgpio->events[event_idx].type = VW_GPIO_EVENT_EDGE_FALLING;
+ irq_set_handler_locked(d, handle_edge_irq);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ vwgpio->events[event_idx].type = VW_GPIO_EVENT_EDGE_BOTH;
+ irq_set_handler_locked(d, handle_edge_irq);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ vwgpio->events[event_idx].type = VW_GPIO_EVENT_LEVEL_LOW;
+ irq_set_handler_locked(d, handle_level_irq);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ vwgpio->events[event_idx].type = VW_GPIO_EVENT_LEVEL_HIGH;
+ irq_set_handler_locked(d, handle_level_irq);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void npcm_vwgpio_irq_ack(struct irq_data *d)
+{
+}
+
+static void npcm_vwgpio_irq_mask(struct irq_data *d)
+{
+ struct npcm_vwgpio *vwgpio = gpiochip_get_data(irq_data_get_irq_chip_data(d));
+ unsigned int gpio = irqd_to_hwirq(d);
+ bool int_enable = false;
+ int index;
+ int wire;
+
+ pr_debug("%s: gpio %u\n", __func__, gpio);
+ /* Accept MS GPIO only */
+ if (gpio < VM_MSGPIO_START || gpio >= (VM_MSGPIO_START + VW_MSGPIO_NUM))
+ return;
+
+ vwgpio->events[gpio - VM_MSGPIO_START].enable = 0;
+ index = (gpio - VM_MSGPIO_START) / 4;
+ /* Check all wires in the same VMGPIOMS index */
+ for (wire = 0; wire < 4; wire++) {
+ if (vwgpio->events[index * 4 + wire].enable) {
+ int_enable = true;
+ break;
+ }
+ }
+ if (!int_enable)
+ npcm_vwgpio_gpms_config(vwgpio, index, true, false);
+}
+
+static void npcm_vwgpio_irq_unmask(struct irq_data *d)
+{
+ struct npcm_vwgpio *vwgpio = gpiochip_get_data(irq_data_get_irq_chip_data(d));
+ unsigned int gpio = irqd_to_hwirq(d);
+ int index;
+
+ pr_debug("%s: gpio %u\n", __func__, gpio);
+ /* Accept MS GPIO only */
+ if (gpio < VM_MSGPIO_START || gpio >= (VM_MSGPIO_START + VW_MSGPIO_NUM))
+ return;
+
+ /* Get current state */
+ vwgpio->events[gpio - VM_MSGPIO_START].state =
+ vwgpio_get_value(&vwgpio->chip, gpio);
+
+ /* Set event enable */
+ vwgpio->events[gpio - VM_MSGPIO_START].enable = 1;
+ index = (gpio - VM_MSGPIO_START) / 4;
+ npcm_vwgpio_gpms_config(vwgpio, index, true, true);
+}
+
+static struct irq_chip npcm_vwgpio_irqchip = {
+ .name = "NPCM-VW-GPIO-IRQ",
+ .irq_ack = npcm_vwgpio_irq_ack,
+ .irq_unmask = npcm_vwgpio_irq_unmask,
+ .irq_mask = npcm_vwgpio_irq_mask,
+ .irq_set_type = npcm_vwgpio_set_irq_type,
+};
+
+static void npcm_vwgpio_init(struct npcm_vwgpio *vwgpio)
+{
+ int i;
+
+ /* Get gpio initial state */
+ memset(&vwgpio->events, 0, sizeof(vwgpio->events));
+ for (i = 0; i < VW_MSGPIO_NUM; i++)
+ vwgpio->events[i].state =
+ vwgpio_get_value(&vwgpio->chip, VM_MSGPIO_START + i);
+
+ /* enable VWUPD interrupt */
+ regmap_set_bits(vwgpio->map, ESPI_ESPIIE, ESPIIE_VWUPDIE);
+}
+
+static void npcm_vwgpio_config(struct npcm_vwgpio *vwgpio, u8 intwin,
+ u8 gpvwmap, u32 idxenmap, u64 smwiremap)
+{
+ u32 val = VWCTL_INTWIN(intwin) | VWCTL_GPVWMAP(gpvwmap);
+ u32 reg;
+ int i, j;
+
+ regmap_write(vwgpio->map, ESPI_VWCTL, val);
+
+ for (i = 0; i < VWGPSM_INDEX_COUNT; i++) {
+ regmap_read(vwgpio->map, ESPI_VWGPSM(i), &reg);
+ if ((idxenmap & BIT(i))) {
+ reg |= VWGPSM_INDEX_EN;
+ for (j = 0; j < 4; j++) {
+ reg |= BIT(j + 4);
+ if (!(smwiremap & BIT((i * 4) + j)))
+ reg &= ~BIT(j);
+ else
+ reg |= BIT(j);
+ }
+ }
+ else {
+ reg &= ~VWGPSM_INDEX_EN;
+ for (j = 0; j < 4; j++) {
+ reg &= ~BIT(j + 4);
+ }
+ }
+ regmap_write(vwgpio->map, ESPI_VWGPSM(i), reg);
+ }
+ for (i = 0; i < VWGPMS_INDEX_COUNT; i++) {
+ if (idxenmap & BIT(i + VWGPSM_INDEX_COUNT))
+ regmap_set_bits(vwgpio->map, ESPI_VWGPMS(i),
+ VWGPMS_INDEX_EN);
+ else
+ regmap_clear_bits(vwgpio->map, ESPI_VWGPMS(i),
+ VWGPMS_INDEX_EN);
+ }
+}
+
+static int npcm_vwgpio_probe(struct platform_device *pdev)
+{
+ struct npcm_vwgpio *vwgpio;
+ struct device *dev = &pdev->dev;
+ struct gpio_irq_chip *irq;
+ u32 gpvwmap, intwin, idxenmap;
+ u64 mswiremap, smwiremap;
+ int rc;
+
+ vwgpio = devm_kzalloc(dev, sizeof(*vwgpio), GFP_KERNEL);
+ if (!vwgpio)
+ return -ENOMEM;
+
+ vwgpio->map = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(vwgpio->map)) {
+ dev_err(dev, "Couldn't get regmap\n");
+ return -ENODEV;
+ }
+
+ vwgpio->irq = platform_get_irq(pdev, 0);
+ if (vwgpio->irq < 0)
+ return vwgpio->irq;
+
+ vwgpio->dev = dev;
+
+ if (of_property_read_u32(pdev->dev.of_node, "nuvoton,gpio-control-map", &gpvwmap))
+ gpvwmap = 0;
+ if (of_property_read_u32(pdev->dev.of_node, "nuvoton,control-interrupt-map", &intwin))
+ intwin = 0;
+ if (of_property_read_u32(pdev->dev.of_node, "nuvoton,index-en-map", &idxenmap))
+ idxenmap = 0;
+ if (of_property_read_u64(pdev->dev.of_node, "nuvoton,vwgpms-wire-map", &mswiremap))
+ vwgpio->mswire_default = 0;
+ else
+ vwgpio->mswire_default = mswiremap;
+ if (of_property_read_u64(pdev->dev.of_node, "nuvoton,vwgpsm-wire-map", &smwiremap))
+ smwiremap = 0xFFFFFFFFFFFFFFFFULL;
+
+ npcm_vwgpio_config(vwgpio, intwin, gpvwmap, idxenmap, smwiremap);
+
+ if (of_find_property(pdev->dev.of_node, "gpio-controller", NULL)) {
+ vwgpio->chip.of_node = pdev->dev.of_node;
+ vwgpio->chip.label = "ESPI_VW_GPIO";
+ vwgpio->chip.base = -1;
+ vwgpio->chip.parent = dev;
+ vwgpio->chip.owner = THIS_MODULE;
+ vwgpio->chip.ngpio = 128;
+ vwgpio->chip.can_sleep = 0;
+ vwgpio->chip.get = vwgpio_get_value;
+ vwgpio->chip.set = vwgpio_set_value;
+ vwgpio->chip.direction_output = vwgpio_direction_output;
+ vwgpio->chip.direction_input = vwgpio_direction_input;
+ vwgpio->chip.get_direction = vwgpio_get_direction;
+
+ irq = &vwgpio->chip.irq;
+ irq->chip = &npcm_vwgpio_irqchip;
+ irq->handler = handle_bad_irq;
+ irq->default_type = IRQ_TYPE_NONE;
+ irq->parent_handler = NULL;
+ irq->parents = NULL;
+ irq->num_parents = 0;
+
+ rc = devm_gpiochip_add_data(dev, &vwgpio->chip, vwgpio);
+ if (rc) {
+ pr_info("Error adding ESPI vw gpiochip\n");
+ return rc;
+ }
+
+ /* Clear ESPISTS_VWUPD status */
+ regmap_write(vwgpio->map, ESPI_ESPISTS, ESPISTS_VWUPD);
+ /* Clear ESPIERR_VWERR status */
+ regmap_write(vwgpio->map, ESPI_ESPIERR, ESPIERR_VWERR);
+ /* Disable VWUPD interrupt */
+ regmap_clear_bits(vwgpio->map, ESPI_ESPIIE, ESPIIE_VWUPDIE);
+
+ rc = devm_request_irq(dev, vwgpio->irq, npcm_vwgpio_irq_handler,
+ IRQF_SHARED, "espi-vw-gpio", vwgpio);
+ if (rc) {
+ dev_err(dev, "failed to request IRQ\n");
+ return rc;
+ }
+ npcm_vwgpio_init(vwgpio);
+ }
+
+ pr_info("%s OK\n", __func__);
+
+ return 0;
+}
+
+static int npcm_vwgpio_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static const struct of_device_id npcm_vwgpio_of_matches[] = {
+ { .compatible = "nuvoton,npcm845-espi-vwgpio", },
+ { },
+};
+
+static struct platform_driver npcm_vwgpio_driver = {
+ .driver = {
+ .name = "npcm-espi-vwgpio",
+ .of_match_table = npcm_vwgpio_of_matches,
+ },
+ .probe = npcm_vwgpio_probe,
+ .remove = npcm_vwgpio_remove,
+};
+
+module_platform_driver(npcm_vwgpio_driver);
+
+MODULE_AUTHOR("Tomer Maimon <Tomer.Maimon@nuvoton.com>");
+MODULE_AUTHOR("Ban Feng <kcfeng0@nuvoton.com>");
+MODULE_AUTHOR("Stanley Chu <yschu@nuvoton.com>");
+MODULE_DESCRIPTION("Nuvoton NPCM eSPI Virtual Wire GPIO Driver");
+MODULE_LICENSE("GPL v2");
--
2.25.1