| 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), ®); |
| + /* 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), ®); |
| + 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 |
| |