diff options
Diffstat (limited to 'drivers/misc/mctp-lpc.c')
-rw-r--r-- | drivers/misc/mctp-lpc.c | 443 |
1 files changed, 0 insertions, 443 deletions
diff --git a/drivers/misc/mctp-lpc.c b/drivers/misc/mctp-lpc.c deleted file mode 100644 index 0228f6bbf1a7..000000000000 --- a/drivers/misc/mctp-lpc.c +++ /dev/null @@ -1,443 +0,0 @@ -// 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 0x100 -#define LPC_HICRB_IBFIF4 BIT(1) -#define LPC_HICRB_LPC4E BIT(0) -#define LPC_HICRC 0x104 -#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 0x110 -#define LPC_IDR4 0x114 -#define LPC_ODR4 0x118 -#define LPC_STR4 0x11C -#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"); |