blob: 71fc4ae69de7b2f9782ae18b394d73c27cedc506 [file] [log] [blame] [edit]
// 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");