Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/char/ipmi/ipmi_si_ls2k.c
29269 views
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 <[email protected]>
9
* Binbin Zhou <[email protected]>
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
191