Commit d46651d4 authored by Binbin Zhou's avatar Binbin Zhou Committed by Corey Minyard
Browse files

ipmi: Add Loongson-2K BMC support



This patch adds Loongson-2K BMC IPMI support.

According to the existing design, we use software simulation to
implement the KCS interface registers: Stauts/Command/Data_Out/Data_In.

Also since both host side and BMC side read and write kcs status, fifo flag
is used to ensure data consistency.

The single KCS message block is as follows:

+-------------------------------------------------------------------------+
|FIFO flags| KCS register data | CMD data | KCS version | WR REQ | WR ACK |
+-------------------------------------------------------------------------+

Co-developed-by: default avatarChong Qiao <qiaochong@loongson.cn>
Signed-off-by: default avatarChong Qiao <qiaochong@loongson.cn>
Reviewed-by: default avatarHuacai Chen <chenhuacai@loongson.cn>
Acked-by: default avatarCorey Minyard <corey@minyard.net>
Signed-off-by: default avatarBinbin Zhou <zhoubinbin@loongson.cn>
Message-ID: <8f9ffb6f0405345af8f04193ce1510aacd075e72.1756987761.git.zhoubinbin@loongson.cn>
Signed-off-by: default avatarCorey Minyard <corey@minyard.net>
parent bc3a9d21
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -84,6 +84,13 @@ config IPMI_IPMB
	  bus, and it also supports direct messaging on the bus using
	  IPMB direct messages.  This module requires I2C support.

config IPMI_LS2K
	bool 'Loongson-2K IPMI interface'
	depends on LOONGARCH
	select MFD_LS2K_BMC_CORE
	help
	  Provides a driver for Loongson-2K IPMI interfaces.

config IPMI_POWERNV
	depends on PPC_POWERNV
	tristate 'POWERNV (OPAL firmware) IPMI interface'
+1 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ ipmi_si-y := ipmi_si_intf.o ipmi_kcs_sm.o ipmi_smic_sm.o ipmi_bt_sm.o \
	ipmi_si_mem_io.o
ipmi_si-$(CONFIG_HAS_IOPORT) += ipmi_si_port_io.o
ipmi_si-$(CONFIG_PCI) += ipmi_si_pci.o
ipmi_si-$(CONFIG_IPMI_LS2K) += ipmi_si_ls2k.o
ipmi_si-$(CONFIG_PARISC) += ipmi_si_parisc.o

obj-$(CONFIG_IPMI_HANDLER) += ipmi_msghandler.o
+7 −0
Original line number Diff line number Diff line
@@ -101,6 +101,13 @@ void ipmi_si_pci_shutdown(void);
static inline void ipmi_si_pci_init(void) { }
static inline void ipmi_si_pci_shutdown(void) { }
#endif
#ifdef CONFIG_IPMI_LS2K
void ipmi_si_ls2k_init(void);
void ipmi_si_ls2k_shutdown(void);
#else
static inline void ipmi_si_ls2k_init(void) { }
static inline void ipmi_si_ls2k_shutdown(void) { }
#endif
#ifdef CONFIG_PARISC
void ipmi_si_parisc_init(void);
void ipmi_si_parisc_shutdown(void);
+4 −0
Original line number Diff line number Diff line
@@ -2138,6 +2138,8 @@ static int __init init_ipmi_si(void)

	ipmi_si_pci_init();

	ipmi_si_ls2k_init();

	ipmi_si_parisc_init();

	mutex_lock(&smi_infos_lock);
@@ -2349,6 +2351,8 @@ static void cleanup_ipmi_si(void)

	ipmi_si_pci_shutdown();

	ipmi_si_ls2k_shutdown();

	ipmi_si_parisc_shutdown();

	ipmi_si_platform_shutdown();
+189 −0
Original line number Diff line number Diff line
// 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);
}