1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
// 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);
}
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);
}
|