mirror of
https://github.com/torvalds/linux.git
synced 2026-03-07 23:04:33 +01:00
No need for it to be global. Reported-by: kernel test robot <lkp@intel.com> Closes: https://lore.kernel.org/oe-kbuild-all/202601170753.3zDBerGP-lkp@intel.com/ Signed-off-by: Corey Minyard <corey@minyard.net>
189 lines
4.5 KiB
C
189 lines
4.5 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Driver for Loongson-2K BMC IPMI interface
|
|
*
|
|
* Copyright (C) 2024-2025 Loongson Technology Corporation Limited.
|
|
*
|
|
* Authors:
|
|
* Chong Qiao <qiaochong@loongson.cn>
|
|
* Binbin Zhou <zhoubinbin@loongson.cn>
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "ipmi_si.h"
|
|
|
|
#define LS2K_KCS_FIFO_IBFH 0x0
|
|
#define LS2K_KCS_FIFO_IBFT 0x1
|
|
#define LS2K_KCS_FIFO_OBFH 0x2
|
|
#define LS2K_KCS_FIFO_OBFT 0x3
|
|
|
|
/* KCS registers */
|
|
#define LS2K_KCS_REG_STS 0x4
|
|
#define LS2K_KCS_REG_DATA_OUT 0x5
|
|
#define LS2K_KCS_REG_DATA_IN 0x6
|
|
#define LS2K_KCS_REG_CMD 0x8
|
|
|
|
#define LS2K_KCS_CMD_DATA 0xa
|
|
#define LS2K_KCS_VERSION 0xb
|
|
#define LS2K_KCS_WR_REQ 0xc
|
|
#define LS2K_KCS_WR_ACK 0x10
|
|
|
|
#define LS2K_KCS_STS_OBF BIT(0)
|
|
#define LS2K_KCS_STS_IBF BIT(1)
|
|
#define LS2K_KCS_STS_SMS_ATN BIT(2)
|
|
#define LS2K_KCS_STS_CMD BIT(3)
|
|
|
|
#define LS2K_KCS_DATA_MASK (LS2K_KCS_STS_OBF | LS2K_KCS_STS_IBF | LS2K_KCS_STS_CMD)
|
|
|
|
static bool ls2k_registered;
|
|
|
|
static unsigned char ls2k_mem_inb_v0(const struct si_sm_io *io, unsigned int offset)
|
|
{
|
|
void __iomem *addr = io->addr;
|
|
int reg_offset;
|
|
|
|
if (offset & BIT(0)) {
|
|
reg_offset = LS2K_KCS_REG_STS;
|
|
} else {
|
|
writeb(readb(addr + LS2K_KCS_REG_STS) & ~LS2K_KCS_STS_OBF, addr + LS2K_KCS_REG_STS);
|
|
reg_offset = LS2K_KCS_REG_DATA_OUT;
|
|
}
|
|
|
|
return readb(addr + reg_offset);
|
|
}
|
|
|
|
static unsigned char ls2k_mem_inb_v1(const struct si_sm_io *io, unsigned int offset)
|
|
{
|
|
void __iomem *addr = io->addr;
|
|
unsigned char inb = 0, cmd;
|
|
bool obf, ibf;
|
|
|
|
obf = readb(addr + LS2K_KCS_FIFO_OBFH) ^ readb(addr + LS2K_KCS_FIFO_OBFT);
|
|
ibf = readb(addr + LS2K_KCS_FIFO_IBFH) ^ readb(addr + LS2K_KCS_FIFO_IBFT);
|
|
cmd = readb(addr + LS2K_KCS_CMD_DATA);
|
|
|
|
if (offset & BIT(0)) {
|
|
inb = readb(addr + LS2K_KCS_REG_STS) & ~LS2K_KCS_DATA_MASK;
|
|
inb |= FIELD_PREP(LS2K_KCS_STS_OBF, obf)
|
|
| FIELD_PREP(LS2K_KCS_STS_IBF, ibf)
|
|
| FIELD_PREP(LS2K_KCS_STS_CMD, cmd);
|
|
} else {
|
|
inb = readb(addr + LS2K_KCS_REG_DATA_OUT);
|
|
writeb(readb(addr + LS2K_KCS_FIFO_OBFH), addr + LS2K_KCS_FIFO_OBFT);
|
|
}
|
|
|
|
return inb;
|
|
}
|
|
|
|
static void ls2k_mem_outb_v0(const struct si_sm_io *io, unsigned int offset,
|
|
unsigned char val)
|
|
{
|
|
void __iomem *addr = io->addr;
|
|
unsigned char sts = readb(addr + LS2K_KCS_REG_STS);
|
|
int reg_offset;
|
|
|
|
if (sts & LS2K_KCS_STS_IBF)
|
|
return;
|
|
|
|
if (offset & BIT(0)) {
|
|
reg_offset = LS2K_KCS_REG_CMD;
|
|
sts |= LS2K_KCS_STS_CMD;
|
|
} else {
|
|
reg_offset = LS2K_KCS_REG_DATA_IN;
|
|
sts &= ~LS2K_KCS_STS_CMD;
|
|
}
|
|
|
|
writew(val, addr + reg_offset);
|
|
writeb(sts | LS2K_KCS_STS_IBF, addr + LS2K_KCS_REG_STS);
|
|
writel(readl(addr + LS2K_KCS_WR_REQ) + 1, addr + LS2K_KCS_WR_REQ);
|
|
}
|
|
|
|
static void ls2k_mem_outb_v1(const struct si_sm_io *io, unsigned int offset,
|
|
unsigned char val)
|
|
{
|
|
void __iomem *addr = io->addr;
|
|
unsigned char ibfh, ibft;
|
|
int reg_offset;
|
|
|
|
ibfh = readb(addr + LS2K_KCS_FIFO_IBFH);
|
|
ibft = readb(addr + LS2K_KCS_FIFO_IBFT);
|
|
|
|
if (ibfh ^ ibft)
|
|
return;
|
|
|
|
reg_offset = (offset & BIT(0)) ? LS2K_KCS_REG_CMD : LS2K_KCS_REG_DATA_IN;
|
|
writew(val, addr + reg_offset);
|
|
|
|
writeb(offset & BIT(0), addr + LS2K_KCS_CMD_DATA);
|
|
writeb(!ibft, addr + LS2K_KCS_FIFO_IBFH);
|
|
writel(readl(addr + LS2K_KCS_WR_REQ) + 1, addr + LS2K_KCS_WR_REQ);
|
|
}
|
|
|
|
static void ls2k_mem_cleanup(struct si_sm_io *io)
|
|
{
|
|
if (io->addr)
|
|
iounmap(io->addr);
|
|
}
|
|
|
|
static int ipmi_ls2k_mem_setup(struct si_sm_io *io)
|
|
{
|
|
unsigned char version;
|
|
|
|
io->addr = ioremap(io->addr_data, io->regspacing);
|
|
if (!io->addr)
|
|
return -EIO;
|
|
|
|
version = readb(io->addr + LS2K_KCS_VERSION);
|
|
|
|
io->inputb = version ? ls2k_mem_inb_v1 : ls2k_mem_inb_v0;
|
|
io->outputb = version ? ls2k_mem_outb_v1 : ls2k_mem_outb_v0;
|
|
io->io_cleanup = ls2k_mem_cleanup;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ipmi_ls2k_probe(struct platform_device *pdev)
|
|
{
|
|
struct si_sm_io io;
|
|
|
|
memset(&io, 0, sizeof(io));
|
|
|
|
io.si_info = &ipmi_kcs_si_info;
|
|
io.io_setup = ipmi_ls2k_mem_setup;
|
|
io.addr_data = pdev->resource[0].start;
|
|
io.regspacing = resource_size(&pdev->resource[0]);
|
|
io.dev = &pdev->dev;
|
|
|
|
dev_dbg(&pdev->dev, "addr 0x%lx, spacing %d.\n", io.addr_data, io.regspacing);
|
|
|
|
return ipmi_si_add_smi(&io);
|
|
}
|
|
|
|
static void ipmi_ls2k_remove(struct platform_device *pdev)
|
|
{
|
|
ipmi_si_remove_by_dev(&pdev->dev);
|
|
}
|
|
|
|
static struct platform_driver ipmi_ls2k_platform_driver = {
|
|
.driver = {
|
|
.name = "ls2k-ipmi-si",
|
|
},
|
|
.probe = ipmi_ls2k_probe,
|
|
.remove = ipmi_ls2k_remove,
|
|
};
|
|
|
|
void ipmi_si_ls2k_init(void)
|
|
{
|
|
platform_driver_register(&ipmi_ls2k_platform_driver);
|
|
ls2k_registered = true;
|
|
}
|
|
|
|
void ipmi_si_ls2k_shutdown(void)
|
|
{
|
|
if (ls2k_registered)
|
|
platform_driver_unregister(&ipmi_ls2k_platform_driver);
|
|
}
|