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
ls2k_mem_inb_v0(const struct si_sm_io * io,unsigned int offset)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
ls2k_mem_inb_v1(const struct si_sm_io * io,unsigned int offset)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
ls2k_mem_outb_v0(const struct si_sm_io * io,unsigned int offset,unsigned char val)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
ls2k_mem_outb_v1(const struct si_sm_io * io,unsigned int offset,unsigned char val)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
ls2k_mem_cleanup(struct si_sm_io * io)126 static void ls2k_mem_cleanup(struct si_sm_io *io)
127 {
128 if (io->addr)
129 iounmap(io->addr);
130 }
131
ipmi_ls2k_mem_setup(struct si_sm_io * io)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
ipmi_ls2k_probe(struct platform_device * pdev)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
ipmi_ls2k_remove(struct platform_device * pdev)166 static void ipmi_ls2k_remove(struct platform_device *pdev)
167 {
168 ipmi_si_remove_by_dev(&pdev->dev);
169 }
170
171 static 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
ipmi_si_ls2k_init(void)179 void ipmi_si_ls2k_init(void)
180 {
181 platform_driver_register(&ipmi_ls2k_platform_driver);
182 ls2k_registered = true;
183 }
184
ipmi_si_ls2k_shutdown(void)185 void ipmi_si_ls2k_shutdown(void)
186 {
187 if (ls2k_registered)
188 platform_driver_unregister(&ipmi_ls2k_platform_driver);
189 }
190