xref: /linux/drivers/char/ipmi/ipmi_si_ls2k.c (revision 524c4a5daf92982cf16d9e6c8cdf8721abe35a11)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Driver for Loongson-2K BMC IPMI interface
4  *
5  * Copyright (C) 2024-2025 Loongson Technology Corporation Limited.
6  *
7  * Authors:
8  *	Chong Qiao <qiaochong@loongson.cn>
9  *	Binbin Zhou <zhoubinbin@loongson.cn>
10  */
11 
12 #include <linux/bitfield.h>
13 #include <linux/ioport.h>
14 #include <linux/module.h>
15 #include <linux/types.h>
16 
17 #include "ipmi_si.h"
18 
19 #define LS2K_KCS_FIFO_IBFH	0x0
20 #define LS2K_KCS_FIFO_IBFT	0x1
21 #define LS2K_KCS_FIFO_OBFH	0x2
22 #define LS2K_KCS_FIFO_OBFT	0x3
23 
24 /* KCS registers */
25 #define LS2K_KCS_REG_STS	0x4
26 #define LS2K_KCS_REG_DATA_OUT	0x5
27 #define LS2K_KCS_REG_DATA_IN	0x6
28 #define LS2K_KCS_REG_CMD	0x8
29 
30 #define LS2K_KCS_CMD_DATA	0xa
31 #define LS2K_KCS_VERSION	0xb
32 #define LS2K_KCS_WR_REQ		0xc
33 #define LS2K_KCS_WR_ACK		0x10
34 
35 #define LS2K_KCS_STS_OBF	BIT(0)
36 #define LS2K_KCS_STS_IBF	BIT(1)
37 #define LS2K_KCS_STS_SMS_ATN	BIT(2)
38 #define LS2K_KCS_STS_CMD	BIT(3)
39 
40 #define LS2K_KCS_DATA_MASK	(LS2K_KCS_STS_OBF | LS2K_KCS_STS_IBF | LS2K_KCS_STS_CMD)
41 
42 static bool ls2k_registered;
43 
44 static unsigned char ls2k_mem_inb_v0(const struct si_sm_io *io, unsigned int offset)
45 {
46 	void __iomem *addr = io->addr;
47 	int reg_offset;
48 
49 	if (offset & BIT(0)) {
50 		reg_offset = LS2K_KCS_REG_STS;
51 	} else {
52 		writeb(readb(addr + LS2K_KCS_REG_STS) & ~LS2K_KCS_STS_OBF, addr + LS2K_KCS_REG_STS);
53 		reg_offset = LS2K_KCS_REG_DATA_OUT;
54 	}
55 
56 	return readb(addr + reg_offset);
57 }
58 
59 static unsigned char ls2k_mem_inb_v1(const struct si_sm_io *io, unsigned int offset)
60 {
61 	void __iomem *addr = io->addr;
62 	unsigned char inb = 0, cmd;
63 	bool obf, ibf;
64 
65 	obf = readb(addr + LS2K_KCS_FIFO_OBFH) ^ readb(addr + LS2K_KCS_FIFO_OBFT);
66 	ibf = readb(addr + LS2K_KCS_FIFO_IBFH) ^ readb(addr + LS2K_KCS_FIFO_IBFT);
67 	cmd = readb(addr + LS2K_KCS_CMD_DATA);
68 
69 	if (offset & BIT(0)) {
70 		inb = readb(addr + LS2K_KCS_REG_STS) & ~LS2K_KCS_DATA_MASK;
71 		inb |= FIELD_PREP(LS2K_KCS_STS_OBF, obf)
72 		    | FIELD_PREP(LS2K_KCS_STS_IBF, ibf)
73 		    | FIELD_PREP(LS2K_KCS_STS_CMD, cmd);
74 	} else {
75 		inb = readb(addr + LS2K_KCS_REG_DATA_OUT);
76 		writeb(readb(addr + LS2K_KCS_FIFO_OBFH), addr + LS2K_KCS_FIFO_OBFT);
77 	}
78 
79 	return inb;
80 }
81 
82 static void ls2k_mem_outb_v0(const struct si_sm_io *io, unsigned int offset,
83 			     unsigned char val)
84 {
85 	void __iomem *addr = io->addr;
86 	unsigned char sts = readb(addr + LS2K_KCS_REG_STS);
87 	int reg_offset;
88 
89 	if (sts & LS2K_KCS_STS_IBF)
90 		return;
91 
92 	if (offset & BIT(0)) {
93 		reg_offset = LS2K_KCS_REG_CMD;
94 		sts |= LS2K_KCS_STS_CMD;
95 	} else {
96 		reg_offset = LS2K_KCS_REG_DATA_IN;
97 		sts &= ~LS2K_KCS_STS_CMD;
98 	}
99 
100 	writew(val, addr + reg_offset);
101 	writeb(sts | LS2K_KCS_STS_IBF, addr + LS2K_KCS_REG_STS);
102 	writel(readl(addr + LS2K_KCS_WR_REQ) + 1, addr + LS2K_KCS_WR_REQ);
103 }
104 
105 static void ls2k_mem_outb_v1(const struct si_sm_io *io, unsigned int offset,
106 			     unsigned char val)
107 {
108 	void __iomem *addr = io->addr;
109 	unsigned char ibfh, ibft;
110 	int reg_offset;
111 
112 	ibfh = readb(addr + LS2K_KCS_FIFO_IBFH);
113 	ibft = readb(addr + LS2K_KCS_FIFO_IBFT);
114 
115 	if (ibfh ^ ibft)
116 		return;
117 
118 	reg_offset = (offset & BIT(0)) ? LS2K_KCS_REG_CMD : LS2K_KCS_REG_DATA_IN;
119 	writew(val, addr + reg_offset);
120 
121 	writeb(offset & BIT(0), addr + LS2K_KCS_CMD_DATA);
122 	writeb(!ibft, addr + LS2K_KCS_FIFO_IBFH);
123 	writel(readl(addr + LS2K_KCS_WR_REQ) + 1, addr + LS2K_KCS_WR_REQ);
124 }
125 
126 static void ls2k_mem_cleanup(struct si_sm_io *io)
127 {
128 	if (io->addr)
129 		iounmap(io->addr);
130 }
131 
132 static int ipmi_ls2k_mem_setup(struct si_sm_io *io)
133 {
134 	unsigned char version;
135 
136 	io->addr = ioremap(io->addr_data, io->regspacing);
137 	if (!io->addr)
138 		return -EIO;
139 
140 	version = readb(io->addr + LS2K_KCS_VERSION);
141 
142 	io->inputb = version ? ls2k_mem_inb_v1 : ls2k_mem_inb_v0;
143 	io->outputb = version ? ls2k_mem_outb_v1 : ls2k_mem_outb_v0;
144 	io->io_cleanup = ls2k_mem_cleanup;
145 
146 	return 0;
147 }
148 
149 static int ipmi_ls2k_probe(struct platform_device *pdev)
150 {
151 	struct si_sm_io io;
152 
153 	memset(&io, 0, sizeof(io));
154 
155 	io.si_info	= &ipmi_kcs_si_info;
156 	io.io_setup	= ipmi_ls2k_mem_setup;
157 	io.addr_data	= pdev->resource[0].start;
158 	io.regspacing	= resource_size(&pdev->resource[0]);
159 	io.dev		= &pdev->dev;
160 
161 	dev_dbg(&pdev->dev, "addr 0x%lx, spacing %d.\n", io.addr_data, io.regspacing);
162 
163 	return ipmi_si_add_smi(&io);
164 }
165 
166 static void ipmi_ls2k_remove(struct platform_device *pdev)
167 {
168 	ipmi_si_remove_by_dev(&pdev->dev);
169 }
170 
171 struct platform_driver ipmi_ls2k_platform_driver = {
172 	.driver = {
173 		.name = "ls2k-ipmi-si",
174 	},
175 	.probe	= ipmi_ls2k_probe,
176 	.remove	= ipmi_ls2k_remove,
177 };
178 
179 void ipmi_si_ls2k_init(void)
180 {
181 	platform_driver_register(&ipmi_ls2k_platform_driver);
182 	ls2k_registered = true;
183 }
184 
185 void ipmi_si_ls2k_shutdown(void)
186 {
187 	if (ls2k_registered)
188 		platform_driver_unregister(&ipmi_ls2k_platform_driver);
189 }
190