diff options
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/Kconfig | 22 | ||||
-rw-r--r-- | drivers/misc/Makefile | 3 | ||||
-rw-r--r-- | drivers/misc/mctp-lpc.c | 443 | ||||
-rw-r--r-- | drivers/misc/npcm7xx-lpc-bpc.c | 394 | ||||
-rw-r--r-- | drivers/misc/npcm7xx-pci-mbox.c | 288 |
5 files changed, 1150 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c55b63750757..c90a9cacc19f 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -454,6 +454,13 @@ config XILINX_SDFEC If unsure, say N. +config MCTP_LPC + tristate "MCTP LPC binding implementation for ASPEED BMCs" + depends on REGMAP + help + Implements the MCTP LPC binding via KCS LPC IO cycles for control and + LPC FWH cycles for data + config MISC_RTSX tristate default MISC_RTSX_PCI || MISC_RTSX_USB @@ -466,6 +473,21 @@ config PVPANIC a paravirtualized device provided by QEMU; it lets a virtual machine (guest) communicate panic events to the host. +config NPCM7XX_LPC_BPC + tristate "NPCM7xx LPC BIOS Post Code support" + depends on (ARCH_NPCM7XX || COMPILE_TEST) + help + Provides a NPCM7xx driver to control the LPC BIOS Post Code + interface which allows the BMC to monitoring and save + the data written by the host to an arbitrary LPC I/O port. + +config NPCM7XX_PCI_MBOX + tristate "NPCM7xx PCI Mailbox Controller" + depends on (ARCH_NPCM7XX || COMPILE_TEST) && REGMAP && MFD_SYSCON + help + Expose the NPCM750/730/715/705 PCI MBOX registers found on + Nuvoton SOCs to userspace. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c1860d35dc7e..9ef78d42be9d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -57,3 +57,6 @@ obj-y += cardreader/ obj-$(CONFIG_PVPANIC) += pvpanic.o obj-$(CONFIG_HABANA_AI) += habanalabs/ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o +obj-$(CONFIG_NPCM7XX_LPC_BPC) += npcm7xx-lpc-bpc.o +obj-$(CONFIG_NPCM7XX_PCI_MBOX) += npcm7xx-pci-mbox.o +obj-$(CONFIG_MCTP_LPC) += mctp-lpc.o diff --git a/drivers/misc/mctp-lpc.c b/drivers/misc/mctp-lpc.c new file mode 100644 index 000000000000..71fc4ae69de7 --- /dev/null +++ b/drivers/misc/mctp-lpc.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2019, IBM Corp. + */ + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/regmap.h> +#include <linux/sched/signal.h> +#include <linux/uaccess.h> +#include <linux/wait.h> + +#define LPC_HICRB 0x080 +#define LPC_HICRB_IBFIF4 BIT(1) +#define LPC_HICRB_LPC4E BIT(0) +#define LPC_HICRC 0x084 +#define LPC_KCS4_IRQSEL_MASK GENMASK(7, 4) +#define LPC_KCS4_IRQSEL_SHIFT 4 +#define LPC_KCS4_IRQTYPE_MASK GENMASK(3, 2) +#define LPC_KCS4_IRQTYPE_SHIFT 2 +#define LPC_KCS4_IRQTYPE_LOW 0b00 +#define LPC_KCS4_IRQTYPE_HIGH 0b01 +#define LPC_KCS4_IRQTYPE_RSVD 0b10 +#define LPC_KCS4_IRQTYPE_RISING 0b11 +#define LPC_KCS4_OBF4_AUTO_CLR BIT(1) +#define LPC_KCS4_IRQ_HOST BIT(0) +#define LPC_LADR4 0x090 +#define LPC_IDR4 0x094 +#define LPC_ODR4 0x098 +#define LPC_STR4 0x09C +#define STR4_IBF (1 << 1) +#define STR4_OBF (1 << 0) + +#define HOST_ODR 0xca2 +#define HOST_STR 0xca3 +#define HOST_SERIRQ_ID 11 +#define HOST_SERIRQ_TYPE LPC_KCS4_IRQTYPE_LOW + +#define RX_BUF_SIZE 1024 + +struct mctp_lpc { + struct miscdevice miscdev; + struct regmap *map; + + wait_queue_head_t rx; + bool pending; + u8 idr; +}; + +static irqreturn_t mctp_lpc_irq(int irq, void *data) +{ + struct mctp_lpc *priv = data; + unsigned long flags; + unsigned int hicrb; + struct device *dev; + unsigned int str; + irqreturn_t ret; + + dev = priv->miscdev.this_device; + + spin_lock_irqsave(&priv->rx.lock, flags); + + regmap_read(priv->map, LPC_STR4, &str); + regmap_read(priv->map, LPC_HICRB, &hicrb); + + if ((str & STR4_IBF) && (hicrb & LPC_HICRB_IBFIF4)) { + unsigned int val; + + if (priv->pending) + dev_err(dev, "Storm brewing!"); + + /* Mask the IRQ / Enter polling mode */ + dev_dbg(dev, "Received IRQ %d, disabling to provide back-pressure\n", + irq); + regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIF4, 0); + + /* + * Extract the IDR4 value to ack the IRQ. Reading IDR clears + * IBF and allows the host to write another value, however as + * we have disabled IRQs the back-pressure is still applied + * until userspace starts servicing the interface. + */ + regmap_read(priv->map, LPC_IDR4, &val); + priv->idr = val & 0xff; + priv->pending = true; + + dev_dbg(dev, "Set pending, waking waiters\n"); + wake_up_locked(&priv->rx); + ret = IRQ_HANDLED; + } else { + dev_dbg(dev, "LPC IRQ triggered, but not for us (str=0x%x, hicrb=0x%x)\n", + str, hicrb); + ret = IRQ_NONE; + } + + spin_unlock_irqrestore(&priv->rx.lock, flags); + + return ret; +} + +static inline struct mctp_lpc *to_mctp_lpc(struct file *filp) +{ + return container_of(filp->private_data, struct mctp_lpc, miscdev); +} + +static ssize_t mctp_lpc_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct mctp_lpc *priv; + struct device *dev; + size_t remaining; + ssize_t rc; + + priv = to_mctp_lpc(filp); + dev = priv->miscdev.this_device; + + if (!count) + return 0; + + if (count > 2 || *ppos > 1) + return -EINVAL; + + remaining = count; + + spin_lock_irq(&priv->rx.lock); + if (*ppos == 0) { + unsigned int val; + u8 str; + + /* YOLO blocking, non-block not supported */ + dev_dbg(dev, "Waiting for IBF\n"); + regmap_read(priv->map, LPC_STR4, &val); + str = val & 0xff; + rc = wait_event_interruptible_locked(priv->rx, (priv->pending || str & STR4_IBF)); + if (rc < 0) + goto out; + + if (signal_pending(current)) { + dev_dbg(dev, "Interrupted waiting for IBF\n"); + rc = -EINTR; + goto out; + } + + /* + * Re-enable IRQs prior to possible read of IDR (which clears + * IBF) to ensure we receive interrupts for subsequent writes + * to IDR. Writes to IDR by the host should not occur while IBF + * is set. + */ + dev_dbg(dev, "Woken by IBF, enabling IRQ\n"); + regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIF4, + LPC_HICRB_IBFIF4); + + /* Read data out of IDR into internal storage if necessary */ + if (!priv->pending) { + WARN(!(str & STR4_IBF), "Unknown reason for wakeup!"); + + /* Extract the IDR4 value to ack the IRQ */ + regmap_read(priv->map, LPC_IDR4, &val); + priv->idr = val & 0xff; + } + + /* Copy data from internal storage to userspace */ + if (copy_to_user(buf, &priv->idr, sizeof(priv->idr))) { + rc = -EFAULT; + goto out; + } + + /* We're done consuming the internally stored value */ + priv->pending = false; + + remaining--; + buf++; + } + + if (remaining) { + /* Either: + * + * 1. (count == 1 && *ppos == 1) + * 2. (count == 2 && *ppos == 0) + */ + unsigned int val; + u8 str; + + regmap_read(priv->map, LPC_STR4, &val); + str = val & 0xff; + if (*ppos == 0 || priv->pending) + /* + * If we got this far with `*ppos == 0` then we've read + * data out of IDR, so set IBF when reporting back to + * userspace so userspace knows the IDR value is valid. + */ + str |= STR4_IBF; + + dev_dbg(dev, "Read status 0x%x\n", str); + if (copy_to_user(buf, &str, sizeof(str))) { + rc = -EFAULT; + goto out; + } + + remaining--; + } + + WARN_ON(remaining); + + rc = count; + +out: + spin_unlock_irq(&priv->rx.lock); + + return rc; +} + +static ssize_t mctp_lpc_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + uint8_t _data[2], *data = &_data[0]; + struct mctp_lpc *priv; + struct device *dev; + size_t remaining; + unsigned int str; + + priv = to_mctp_lpc(filp); + dev = priv->miscdev.this_device; + + if (!count) + return count; + + if (count > 2) + return -EINVAL; + + if (*ppos >= 2) + return -EINVAL; + + if (*ppos + count > 2) + return -EINVAL; + + if (copy_from_user(data, buf, count)) + return -EFAULT; + + remaining = count; + + if (*ppos == 0) { + /* Wait until OBF is clear - we don't get an IRQ */ + dev_dbg(dev, "Waiting for OBF to clear\n"); + for (;;) { + if (signal_pending(current)) + return -EINTR; + + regmap_read(priv->map, LPC_STR4, &str); + if (!(str & STR4_OBF)) + break; + + msleep(1); + } + + dev_dbg(dev, "Writing 0x%x to ODR\n", *data); + regmap_write(priv->map, LPC_ODR4, *data); + remaining--; + data++; + } + + if (remaining) { + if (!(*data & STR4_OBF)) + dev_err(dev, "Clearing OBF with status write: 0x%x\n", + *data); + dev_dbg(dev, "Writing status 0x%x\n", *data); + regmap_write(priv->map, LPC_STR4, *data); + remaining--; + } + + WARN_ON(remaining); + + regmap_read(priv->map, LPC_STR4, &str); + dev_dbg(dev, "Triggering SerIRQ (current str=0x%x)\n", str); + + /* + * Trigger Host IRQ on ODR write. Do this after any STR write in case + * we need to write ODR to indicate an STR update (which we do). + */ + if (*ppos == 0) + regmap_update_bits(priv->map, LPC_HICRC, LPC_KCS4_IRQ_HOST, + LPC_KCS4_IRQ_HOST); + + return count; +} + +static __poll_t mctp_lpc_poll(struct file *filp, poll_table *wait) +{ + struct mctp_lpc *priv; + struct device *dev; + unsigned int val; + bool ibf; + + priv = to_mctp_lpc(filp); + dev = priv->miscdev.this_device; + + regmap_read(priv->map, LPC_STR4, &val); + + spin_lock_irq(&priv->rx.lock); + + ibf = priv->pending || val & STR4_IBF; + + if (!ibf) { + dev_dbg(dev, "Polling on IBF\n"); + + spin_unlock_irq(&priv->rx.lock); + + poll_wait(filp, &priv->rx, wait); + if (signal_pending(current)) { + dev_dbg(dev, "Polling IBF was interrupted\n"); + goto out; + } + + spin_lock_irq(&priv->rx.lock); + + regmap_read(priv->map, LPC_STR4, &val); + + ibf = priv->pending || val & STR4_IBF; + } + + spin_unlock_irq(&priv->rx.lock); + +out: + dev_dbg(dev, "Polled IBF state: %s\n", ibf ? "set" : "clear"); + + return ibf ? EPOLLIN : 0; +} + +static const struct file_operations mctp_lpc_fops = { + .owner = THIS_MODULE, + .llseek = no_seek_end_llseek, + .read = mctp_lpc_read, + .write = mctp_lpc_write, + .poll = mctp_lpc_poll, +}; + +static int mctp_lpc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + unsigned int mask, val; + struct mctp_lpc *priv; + int irq; + int rc; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->map = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(priv->map)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + /* + * Set the LPC address. Simultaneously, test our MMIO regmap works. All + * subsequent accesses are assumed to work + */ + rc = regmap_write(priv->map, LPC_LADR4, ((HOST_STR) << 16) | HOST_ODR); + if (rc < 0) + return rc; + + /* Set up the SerIRQ */ + mask = LPC_KCS4_IRQSEL_MASK + | LPC_KCS4_IRQTYPE_MASK + | LPC_KCS4_OBF4_AUTO_CLR; + val = (HOST_SERIRQ_ID << LPC_KCS4_IRQSEL_SHIFT) + | (HOST_SERIRQ_TYPE << LPC_KCS4_IRQTYPE_SHIFT); + val &= ~LPC_KCS4_OBF4_AUTO_CLR; /* Unnecessary, just documentation */ + regmap_update_bits(priv->map, LPC_HICRC, mask, val); + + /* Trigger waiters from IRQ */ + init_waitqueue_head(&priv->rx); + + dev_set_drvdata(dev, priv); + + /* Set up the miscdevice */ + priv->miscdev.minor = MISC_DYNAMIC_MINOR; + priv->miscdev.name = "mctp0"; + priv->miscdev.fops = &mctp_lpc_fops; + + /* Configure the IRQ handler */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + rc = devm_request_irq(dev, irq, mctp_lpc_irq, IRQF_SHARED, + dev_name(dev), priv); + if (rc < 0) + return rc; + + /* Register the device */ + rc = misc_register(&priv->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + return rc; + } + + /* Enable the channel */ + regmap_update_bits(priv->map, LPC_HICRB, + LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E, + LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E); + + return 0; +} + +static int mctp_lpc_remove(struct platform_device *pdev) +{ + struct mctp_lpc *ctx = dev_get_drvdata(&pdev->dev); + + misc_deregister(&ctx->miscdev); + + return 0; +} + +static const struct of_device_id mctp_lpc_match[] = { + { .compatible = "openbmc,mctp-lpc" }, + { } +}; +MODULE_DEVICE_TABLE(of, mctp_lpc_match); + +static struct platform_driver mctp_lpc = { + .driver = { + .name = "mctp-lpc", + .of_match_table = mctp_lpc_match, + }, + .probe = mctp_lpc_probe, + .remove = mctp_lpc_remove, +}; +module_platform_driver(mctp_lpc); + +MODULE_LICENSE("GPL v2+"); +MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>"); +MODULE_DESCRIPTION("OpenBMC MCTP LPC binding on ASPEED KCS"); diff --git a/drivers/misc/npcm7xx-lpc-bpc.c b/drivers/misc/npcm7xx-lpc-bpc.c new file mode 100644 index 000000000000..e014e07cd4a4 --- /dev/null +++ b/drivers/misc/npcm7xx-lpc-bpc.c @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2014-2018 Nuvoton Technology corporation. + +#include <linux/fs.h> +#include <linux/bitops.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/miscdevice.h> +#include <linux/poll.h> + +#define DEVICE_NAME "npcm7xx-lpc-bpc" + +#define NUM_BPC_CHANNELS 2 +#define DW_PAD_SIZE 3 + +/* BIOS POST Code FIFO Registers */ +#define NPCM7XX_BPCFA2L_REG 0x2 //BIOS POST Code FIFO Address 2 LSB +#define NPCM7XX_BPCFA2M_REG 0x4 //BIOS POST Code FIFO Address 2 MSB +#define NPCM7XX_BPCFEN_REG 0x6 //BIOS POST Code FIFO Enable +#define NPCM7XX_BPCFSTAT_REG 0x8 //BIOS POST Code FIFO Status +#define NPCM7XX_BPCFDATA_REG 0xA //BIOS POST Code FIFO Data +#define NPCM7XX_BPCFMSTAT_REG 0xC //BIOS POST Code FIFO Miscellaneous Status +#define NPCM7XX_BPCFA1L_REG 0x10 //BIOS POST Code FIFO Address 1 LSB +#define NPCM7XX_BPCFA1M_REG 0x12 //BIOS POST Code FIFO Address 1 MSB + +/*BIOS regiser data*/ +#define FIFO_IOADDR1_ENABLE 0x80 +#define FIFO_IOADDR2_ENABLE 0x40 + +/* BPC interface package and structure definition */ +#define BPC_KFIFO_SIZE 0x400 + +/*BPC regiser data*/ +#define FIFO_DATA_VALID 0x80 +#define FIFO_OVERFLOW 0x20 +#define FIFO_READY_INT_ENABLE 0x8 +#define FIFO_DWCAPTURE 0x4 +#define FIFO_ADDR_DECODE 0x1 + +/*Host Reset*/ +#define HOST_RESET_INT_ENABLE 0x10 +#define HOST_RESET_CHANGED 0x40 + +struct npcm7xx_bpc_channel { + struct npcm7xx_bpc *data; + struct kfifo fifo; + wait_queue_head_t wq; + bool host_reset; + struct miscdevice miscdev; +}; + +struct npcm7xx_bpc { + void __iomem *base; + int irq; + bool en_dwcap; + struct npcm7xx_bpc_channel ch[NUM_BPC_CHANNELS]; +}; + +static struct npcm7xx_bpc_channel *npcm7xx_file_to_ch(struct file *file) +{ + return container_of(file->private_data, struct npcm7xx_bpc_channel, + miscdev); +} + +static ssize_t npcm7xx_bpc_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct npcm7xx_bpc_channel *chan = npcm7xx_file_to_ch(file); + struct npcm7xx_bpc *lpc_bpc = chan->data; + unsigned int copied; + int ret = 0; + int cond_size = 1; + + if (lpc_bpc->en_dwcap) + cond_size = 3; + + if (kfifo_len(&chan->fifo) < cond_size) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible + (chan->wq, kfifo_len(&chan->fifo) > cond_size); + if (ret == -ERESTARTSYS) + return -EINTR; + } + + ret = kfifo_to_user(&chan->fifo, buffer, count, &copied); + + return ret ? ret : copied; +} + +static __poll_t npcm7xx_bpc_poll(struct file *file, + struct poll_table_struct *pt) +{ + struct npcm7xx_bpc_channel *chan = npcm7xx_file_to_ch(file); + __poll_t mask = 0; + + poll_wait(file, &chan->wq, pt); + if (!kfifo_is_empty(&chan->fifo)) + mask |= POLLIN; + + if (chan->host_reset) { + mask |= POLLHUP; + chan->host_reset = false; + } + + return mask; +} + +static const struct file_operations npcm7xx_bpc_fops = { + .owner = THIS_MODULE, + .read = npcm7xx_bpc_read, + .poll = npcm7xx_bpc_poll, + .llseek = noop_llseek, +}; + +static irqreturn_t npcm7xx_bpc_irq(int irq, void *arg) +{ + struct npcm7xx_bpc *lpc_bpc = arg; + u8 fifo_st; + u8 host_st; + u8 addr_index = 0; + u8 Data; + u8 padzero[3] = {0}; + u8 last_addr_bit = 0; + bool isr_flag = false; + + fifo_st = ioread8(lpc_bpc->base + NPCM7XX_BPCFSTAT_REG); + while (FIFO_DATA_VALID & fifo_st) { + /* If dwcapture enabled only channel 0 (FIFO 0) used */ + if (!lpc_bpc->en_dwcap) + addr_index = fifo_st & FIFO_ADDR_DECODE; + else + last_addr_bit = fifo_st & FIFO_ADDR_DECODE; + + /*Read data from FIFO to clear interrupt*/ + Data = ioread8(lpc_bpc->base + NPCM7XX_BPCFDATA_REG); + if (kfifo_is_full(&lpc_bpc->ch[addr_index].fifo)) + kfifo_skip(&lpc_bpc->ch[addr_index].fifo); + kfifo_put(&lpc_bpc->ch[addr_index].fifo, Data); + if (fifo_st & FIFO_OVERFLOW) + pr_info("BIOS Post Codes FIFO Overflow!!!\n"); + + fifo_st = ioread8(lpc_bpc->base + NPCM7XX_BPCFSTAT_REG); + if (lpc_bpc->en_dwcap && last_addr_bit) { + if ((fifo_st & FIFO_ADDR_DECODE) || + ((FIFO_DATA_VALID & fifo_st) == 0)) { + while (kfifo_avail(&lpc_bpc->ch[addr_index].fifo) < DW_PAD_SIZE) + kfifo_skip(&lpc_bpc->ch[addr_index].fifo); + kfifo_in(&lpc_bpc->ch[addr_index].fifo, + padzero, DW_PAD_SIZE); + } + } + isr_flag = true; + } + + host_st = ioread8(lpc_bpc->base + NPCM7XX_BPCFMSTAT_REG); + if (host_st & HOST_RESET_CHANGED) { + iowrite8(HOST_RESET_CHANGED, + lpc_bpc->base + NPCM7XX_BPCFMSTAT_REG); + lpc_bpc->ch[addr_index].host_reset = true; + isr_flag = true; + } + + if (isr_flag) { + wake_up_interruptible(&lpc_bpc->ch[addr_index].wq); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int npcm7xx_bpc_config_irq(struct npcm7xx_bpc *lpc_bpc, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int rc; + + lpc_bpc->irq = platform_get_irq(pdev, 0); + if (lpc_bpc->irq < 0) { + dev_err(dev, "get IRQ failed\n"); + return lpc_bpc->irq; + } + + rc = devm_request_irq(dev, lpc_bpc->irq, + npcm7xx_bpc_irq, IRQF_SHARED, + DEVICE_NAME, lpc_bpc); + if (rc < 0) { + dev_warn(dev, "Unable to request IRQ %d\n", lpc_bpc->irq); + return rc; + } + + return 0; +} + +static int npcm7xx_enable_bpc(struct npcm7xx_bpc *lpc_bpc, struct device *dev, + int channel, u16 lpc_port) +{ + int rc; + u8 addr_en, reg_en; + + init_waitqueue_head(&lpc_bpc->ch[channel].wq); + + rc = kfifo_alloc(&lpc_bpc->ch[channel].fifo, + BPC_KFIFO_SIZE, GFP_KERNEL); + if (rc) + return rc; + + lpc_bpc->ch[channel].miscdev.minor = MISC_DYNAMIC_MINOR; + lpc_bpc->ch[channel].miscdev.name = + devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); + lpc_bpc->ch[channel].miscdev.fops = &npcm7xx_bpc_fops; + lpc_bpc->ch[channel].miscdev.parent = dev; + rc = misc_register(&lpc_bpc->ch[channel].miscdev); + if (rc) + return rc; + + lpc_bpc->ch[channel].data = lpc_bpc; + lpc_bpc->ch[channel].host_reset = false; + + /* Enable LPC snoop channel at requested port */ + switch (channel) { + case 0: + addr_en = FIFO_IOADDR1_ENABLE; + iowrite8((u8)lpc_port & 0xFF, + lpc_bpc->base + NPCM7XX_BPCFA1L_REG); + iowrite8((u8)(lpc_port >> 8), + lpc_bpc->base + NPCM7XX_BPCFA1M_REG); + break; + case 1: + addr_en = FIFO_IOADDR2_ENABLE; + iowrite8((u8)lpc_port & 0xFF, + lpc_bpc->base + NPCM7XX_BPCFA2L_REG); + iowrite8((u8)(lpc_port >> 8), + lpc_bpc->base + NPCM7XX_BPCFA2M_REG); + break; + default: + return -EINVAL; + } + + if (lpc_bpc->en_dwcap) + addr_en = FIFO_DWCAPTURE; + + /* + * Enable FIFO Ready Interrupt, FIFO Capture of I/O addr, + * and Host Reset + */ + reg_en = ioread8(lpc_bpc->base + NPCM7XX_BPCFEN_REG); + iowrite8(reg_en | addr_en | FIFO_READY_INT_ENABLE | + HOST_RESET_INT_ENABLE, lpc_bpc->base + NPCM7XX_BPCFEN_REG); + + return 0; +} + +static void npcm7xx_disable_bpc(struct npcm7xx_bpc *lpc_bpc, int channel) +{ + u8 reg_en; + + switch (channel) { + case 0: + reg_en = ioread8(lpc_bpc->base + NPCM7XX_BPCFEN_REG); + if (lpc_bpc->en_dwcap) + iowrite8(reg_en & ~FIFO_DWCAPTURE, + lpc_bpc->base + NPCM7XX_BPCFEN_REG); + else + iowrite8(reg_en & ~FIFO_IOADDR1_ENABLE, + lpc_bpc->base + NPCM7XX_BPCFEN_REG); + break; + case 1: + reg_en = ioread8(lpc_bpc->base + NPCM7XX_BPCFEN_REG); + iowrite8(reg_en & ~FIFO_IOADDR2_ENABLE, + lpc_bpc->base + NPCM7XX_BPCFEN_REG); + break; + default: + return; + } + + if (!(reg_en & (FIFO_IOADDR1_ENABLE | FIFO_IOADDR2_ENABLE))) + iowrite8(reg_en & + ~(FIFO_READY_INT_ENABLE | HOST_RESET_INT_ENABLE), + lpc_bpc->base + NPCM7XX_BPCFEN_REG); + + kfifo_free(&lpc_bpc->ch[channel].fifo); + misc_deregister(&lpc_bpc->ch[channel].miscdev); +} + +static int npcm7xx_bpc_probe(struct platform_device *pdev) +{ + struct npcm7xx_bpc *lpc_bpc; + struct resource *res; + struct device *dev; + u32 port; + int rc; + + dev = &pdev->dev; + + lpc_bpc = devm_kzalloc(dev, sizeof(*lpc_bpc), GFP_KERNEL); + if (!lpc_bpc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "BIOS post code reg resource not found\n"); + return -ENODEV; + } + + dev_dbg(dev, "BIOS post code base resource is %pR\n", res); + lpc_bpc->base = devm_ioremap_resource(dev, res); + if (IS_ERR(lpc_bpc->base)) + return PTR_ERR(lpc_bpc->base); + + dev_set_drvdata(&pdev->dev, lpc_bpc); + + rc = of_property_read_u32_index(dev->of_node, "monitor-ports", 0, + &port); + if (rc) { + dev_err(dev, "no monitor ports configured\n"); + return -ENODEV; + } + + lpc_bpc->en_dwcap = + of_property_read_bool(dev->of_node, "bpc-en-dwcapture"); + + rc = npcm7xx_bpc_config_irq(lpc_bpc, pdev); + if (rc) + return rc; + + rc = npcm7xx_enable_bpc(lpc_bpc, dev, 0, port); + if (rc) { + dev_err(dev, "Enable BIOS post code I/O port 0 failed\n"); + return rc; + } + + /* + * Configuration of second BPC channel port is optional + * Double-Word Capture ignoring address 2 + */ + if (!lpc_bpc->en_dwcap) { + if (of_property_read_u32_index(dev->of_node, "monitor-ports", + 1, &port) == 0) { + rc = npcm7xx_enable_bpc(lpc_bpc, dev, 1, port); + if (rc) { + dev_err(dev, "Enable BIOS post code I/O port 1 failed, disable I/O port 0\n"); + npcm7xx_disable_bpc(lpc_bpc, 0); + return rc; + } + } + } + + pr_info("npcm7xx BIOS post code probe\n"); + + return rc; +} + +static int npcm7xx_bpc_remove(struct platform_device *pdev) +{ + struct npcm7xx_bpc *lpc_bpc = dev_get_drvdata(&pdev->dev); + u8 reg_en; + + reg_en = ioread8(lpc_bpc->base + NPCM7XX_BPCFEN_REG); + + if (reg_en & FIFO_IOADDR1_ENABLE) + npcm7xx_disable_bpc(lpc_bpc, 0); + if (reg_en & FIFO_IOADDR2_ENABLE) + npcm7xx_disable_bpc(lpc_bpc, 1); + + return 0; +} + +static const struct of_device_id npcm7xx_bpc_match[] = { + { .compatible = "nuvoton,npcm750-lpc-bpc" }, + { }, +}; + +static struct platform_driver npcm7xx_bpc_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = npcm7xx_bpc_match, + }, + .probe = npcm7xx_bpc_probe, + .remove = npcm7xx_bpc_remove, +}; + +module_platform_driver(npcm7xx_bpc_driver); + +MODULE_DEVICE_TABLE(of, npcm7xx_bpc_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>"); +MODULE_DESCRIPTION("Linux driver to control NPCM7XX LPC BIOS post code monitoring"); diff --git a/drivers/misc/npcm7xx-pci-mbox.c b/drivers/misc/npcm7xx-pci-mbox.c new file mode 100644 index 000000000000..1a80661a4296 --- /dev/null +++ b/drivers/misc/npcm7xx-pci-mbox.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2014-2018 Nuvoton Technology corporation. + +#include <linux/interrupt.h> +#include <linux/mfd/syscon.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define DEVICE_NAME "npcm7xx-pci-mbox" + +#define NPCM7XX_MBOX_BMBXSTAT 0x0 +#define NPCM7XX_MBOX_BMBXCTL 0x4 +#define NPCM7XX_MBOX_BMBXCMD 0x8 + +#define NPCM7XX_MBOX_CIF_0 BIT(0) +#define NPCM7XX_MBOX_CIE_0 BIT(0) +#define NPCM7XX_MBOX_HIF_0 BIT(0) + +#define NPCM7XX_MBOX_ALL_CIF GENMASK(7, 0) +#define NPCM7XX_MBOX_ALL_CIE GENMASK(7, 0) +#define NPCM7XX_MBOX_ALL_HIF GENMASK(7, 0) + +struct npcm7xx_mbox { + struct miscdevice miscdev; + struct regmap *regmap; + void __iomem *memory; + wait_queue_head_t queue; + spinlock_t lock; /* mbox access mutex */ + bool cif0; + u32 max_buf_size; +}; + +static atomic_t npcm7xx_mbox_open_count = ATOMIC_INIT(0); + +static struct npcm7xx_mbox *file_mbox(struct file *file) +{ + return container_of(file->private_data, struct npcm7xx_mbox, miscdev); +} + +static int npcm7xx_mbox_open(struct inode *inode, struct file *file) +{ + struct npcm7xx_mbox *mbox = file_mbox(file); + + if (atomic_inc_return(&npcm7xx_mbox_open_count) == 1) { + /* enable mailbox interrupt */ + regmap_update_bits(mbox->regmap, NPCM7XX_MBOX_BMBXCTL, + NPCM7XX_MBOX_ALL_CIE, NPCM7XX_MBOX_CIE_0); + return 0; + } + + atomic_dec(&npcm7xx_mbox_open_count); + return -EBUSY; +} + +static ssize_t npcm7xx_mbox_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct npcm7xx_mbox *mbox = file_mbox(file); + unsigned long flags; + + if (!access_ok(buf, count)) + return -EFAULT; + + if ((*ppos + count) > mbox->max_buf_size) + return -EINVAL; + + if (file->f_flags & O_NONBLOCK) { + if (!mbox->cif0) + return -EAGAIN; + } else if (wait_event_interruptible(mbox->queue, mbox->cif0)) { + return -ERESTARTSYS; + } + + spin_lock_irqsave(&mbox->lock, flags); + + if (copy_to_user((void __user *)buf, + (const void *)(mbox->memory + *ppos), count)) { + spin_unlock_irqrestore(&mbox->lock, flags); + return -EFAULT; + } + + mbox->cif0 = false; + spin_unlock_irqrestore(&mbox->lock, flags); + return count; +} + +static ssize_t npcm7xx_mbox_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct npcm7xx_mbox *mbox = file_mbox(file); + unsigned long flags; + + if (!access_ok(buf, count)) + return -EFAULT; + + if ((*ppos + count) > mbox->max_buf_size) + return -EINVAL; + + spin_lock_irqsave(&mbox->lock, flags); + + if (copy_from_user((void *)(mbox->memory + *ppos), + (void __user *)buf, count)) { + spin_unlock_irqrestore(&mbox->lock, flags); + return -EFAULT; + } + + regmap_update_bits(mbox->regmap, NPCM7XX_MBOX_BMBXCMD, + NPCM7XX_MBOX_ALL_HIF, NPCM7XX_MBOX_HIF_0); + + spin_unlock_irqrestore(&mbox->lock, flags); + return count; +} + +static unsigned int npcm7xx_mbox_poll(struct file *file, poll_table *wait) +{ + struct npcm7xx_mbox *mbox = file_mbox(file); + unsigned int mask = 0; + + poll_wait(file, &mbox->queue, wait); + if (mbox->cif0) + mask |= POLLIN; + + return mask; +} + +static int npcm7xx_mbox_release(struct inode *inode, struct file *file) +{ + atomic_dec(&npcm7xx_mbox_open_count); + return 0; +} + +static const struct file_operations npcm7xx_mbox_fops = { + .owner = THIS_MODULE, + .llseek = no_seek_end_llseek, + .read = npcm7xx_mbox_read, + .write = npcm7xx_mbox_write, + .open = npcm7xx_mbox_open, + .release = npcm7xx_mbox_release, + .poll = npcm7xx_mbox_poll, +}; + +static irqreturn_t npcm7xx_mbox_irq(int irq, void *arg) +{ + struct npcm7xx_mbox *mbox = arg; + u32 val; + + regmap_read(mbox->regmap, NPCM7XX_MBOX_BMBXSTAT, &val); + if ((val & NPCM7XX_MBOX_CIF_0) != NPCM7XX_MBOX_CIF_0) + return IRQ_NONE; + + /* + * Leave the status bit set so that we know the data is for us, + * clear it once it has been read. + */ + mbox->cif0 = true; + + /* Mask it off, we'll clear it when we the data gets read */ + regmap_write_bits(mbox->regmap, NPCM7XX_MBOX_BMBXSTAT, + NPCM7XX_MBOX_ALL_CIF, NPCM7XX_MBOX_CIF_0); + + wake_up(&mbox->queue); + + return IRQ_HANDLED; +} + +static int npcm7xx_mbox_config_irq(struct npcm7xx_mbox *mbox, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int rc, irq; + u32 val; + + /* Disable all register based interrupts */ + regmap_update_bits(mbox->regmap, NPCM7XX_MBOX_BMBXCTL, + NPCM7XX_MBOX_ALL_CIE, 0); +/* + * These registers are write one to clear. Clear them. + * Per spec, cleared bits should not be re-cleared. + * Need to read and clear needed bits only, instead of blindly clearing all. + */ + regmap_read(mbox->regmap, NPCM7XX_MBOX_BMBXSTAT, &val); + val &= NPCM7XX_MBOX_ALL_CIF; + + /* If any bit is set, write back to clear */ + if (val) + regmap_write_bits(mbox->regmap, NPCM7XX_MBOX_BMBXSTAT, + NPCM7XX_MBOX_ALL_CIF, val); + + irq = irq_of_parse_and_map(dev->of_node, 0); + if (!irq) + return -ENODEV; + + rc = devm_request_irq(dev, irq, npcm7xx_mbox_irq, 0, DEVICE_NAME, mbox); + if (rc < 0) { + dev_err(dev, "Unable to request IRQ %d\n", irq); + return rc; + } + + return 0; +} + +static int npcm7xx_mbox_probe(struct platform_device *pdev) +{ + struct npcm7xx_mbox *mbox; + struct device *dev; + struct resource *res; + int rc; + + dev = &pdev->dev; + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, mbox); + + mbox->regmap = syscon_node_to_regmap(dev->of_node); + if (IS_ERR(mbox->regmap)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + mbox->memory = devm_ioremap_resource(dev, res); + if (IS_ERR(mbox->memory)) + return PTR_ERR(mbox->memory); + mbox->max_buf_size = resource_size(res); + + spin_lock_init(&mbox->lock); + init_waitqueue_head(&mbox->queue); + + mbox->miscdev.minor = MISC_DYNAMIC_MINOR; + mbox->miscdev.name = DEVICE_NAME; + mbox->miscdev.fops = &npcm7xx_mbox_fops; + mbox->miscdev.parent = dev; + mbox->cif0 = false; + rc = misc_register(&mbox->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + return rc; + } + + rc = npcm7xx_mbox_config_irq(mbox, pdev); + if (rc) { + dev_err(dev, "Failed to configure IRQ\n"); + misc_deregister(&mbox->miscdev); + return rc; + } + + pr_info("NPCM7xx PCI Mailbox probed\n"); + + return 0; +} + +static int npcm7xx_mbox_remove(struct platform_device *pdev) +{ + struct npcm7xx_mbox *mbox = dev_get_drvdata(&pdev->dev); + + misc_deregister(&mbox->miscdev); + + return 0; +} + +static const struct of_device_id npcm7xx_mbox_match[] = { + { .compatible = "nuvoton,npcm750-pci-mbox" }, + { }, +}; + +static struct platform_driver npcm7xx_mbox_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = npcm7xx_mbox_match, + }, + .probe = npcm7xx_mbox_probe, + .remove = npcm7xx_mbox_remove, +}; + +module_platform_driver(npcm7xx_mbox_driver); + +MODULE_DEVICE_TABLE(of, npcm7xx_mbox_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>"); +MODULE_DESCRIPTION("NPCM7XX mailbox device driver"); |