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