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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
|
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Alert Standard Format Platform Driver
*
* Copyright (c) 2024, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
* Sanket Goswami <Sanket.Goswami@amd.com>
*/
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/devm-helpers.h>
#include <linux/errno.h>
#include <linux/gfp_types.h>
#include <linux/i2c.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/sprintf.h>
#include "i2c-piix4.h"
/* ASF register bits */
#define ASF_SLV_LISTN 0
#define ASF_SLV_INTR 1
#define ASF_SLV_RST 4
#define ASF_PEC_SP 5
#define ASF_DATA_EN 7
#define ASF_MSTR_EN 16
#define ASF_CLK_EN 17
/* ASF address offsets */
#define ASFINDEX (0x07 + piix4_smba)
#define ASFLISADDR (0x09 + piix4_smba)
#define ASFSTA (0x0A + piix4_smba)
#define ASFSLVSTA (0x0D + piix4_smba)
#define ASFDATARWPTR (0x11 + piix4_smba)
#define ASFSETDATARDPTR (0x12 + piix4_smba)
#define ASFDATABNKSEL (0x13 + piix4_smba)
#define ASFSLVEN (0x15 + piix4_smba)
#define ASF_BLOCK_MAX_BYTES 72
#define ASF_ERROR_STATUS GENMASK(3, 1)
struct amd_asf_dev {
struct i2c_adapter adap;
void __iomem *eoi_base;
struct i2c_client *target;
struct delayed_work work_buf;
struct sb800_mmio_cfg mmio_cfg;
struct resource *port_addr;
};
static void amd_asf_process_target(struct work_struct *work)
{
struct amd_asf_dev *dev = container_of(work, struct amd_asf_dev, work_buf.work);
unsigned short piix4_smba = dev->port_addr->start;
u8 data[ASF_BLOCK_MAX_BYTES];
u8 bank, reg, cmd;
u8 len = 0, idx, val;
/* Read target status register */
reg = inb_p(ASFSLVSTA);
/* Check if no error bits are set in target status register */
if (reg & ASF_ERROR_STATUS) {
/* Set bank as full */
cmd = 0;
reg |= GENMASK(3, 2);
outb_p(reg, ASFDATABNKSEL);
} else {
/* Read data bank */
reg = inb_p(ASFDATABNKSEL);
bank = (reg & BIT(3)) ? 1 : 0;
/* Set read data bank */
if (bank) {
reg |= BIT(4);
reg &= ~BIT(3);
} else {
reg &= ~BIT(4);
reg &= ~BIT(2);
}
/* Read command register */
outb_p(reg, ASFDATABNKSEL);
cmd = inb_p(ASFINDEX);
len = inb_p(ASFDATARWPTR);
for (idx = 0; idx < len; idx++)
data[idx] = inb_p(ASFINDEX);
/* Clear data bank status */
if (bank) {
reg |= BIT(3);
outb_p(reg, ASFDATABNKSEL);
} else {
reg |= BIT(2);
outb_p(reg, ASFDATABNKSEL);
}
}
outb_p(0, ASFSETDATARDPTR);
if (cmd & BIT(0))
return;
/*
* Although i2c_slave_event() returns an appropriate error code, we
* don't check it here because we're operating in the workqueue context.
*/
i2c_slave_event(dev->target, I2C_SLAVE_WRITE_REQUESTED, &val);
for (idx = 0; idx < len; idx++) {
val = data[idx];
i2c_slave_event(dev->target, I2C_SLAVE_WRITE_RECEIVED, &val);
}
i2c_slave_event(dev->target, I2C_SLAVE_STOP, &val);
}
static void amd_asf_update_ioport_target(unsigned short piix4_smba, u8 bit,
unsigned long offset, bool set)
{
unsigned long reg;
reg = inb_p(offset);
__assign_bit(bit, ®, set);
outb_p(reg, offset);
}
static void amd_asf_update_mmio_target(struct amd_asf_dev *dev, u8 bit, bool set)
{
unsigned long reg;
reg = ioread32(dev->mmio_cfg.addr);
__assign_bit(bit, ®, set);
iowrite32(reg, dev->mmio_cfg.addr);
}
static void amd_asf_setup_target(struct amd_asf_dev *dev)
{
unsigned short piix4_smba = dev->port_addr->start;
/* Reset both host and target before setting up */
outb_p(0, SMBHSTSTS);
outb_p(0, ASFSLVSTA);
outb_p(0, ASFSTA);
/* Update target address */
amd_asf_update_ioport_target(piix4_smba, ASF_SLV_LISTN, ASFLISADDR, true);
/* Enable target and set the clock */
amd_asf_update_mmio_target(dev, ASF_MSTR_EN, false);
amd_asf_update_mmio_target(dev, ASF_CLK_EN, true);
/* Enable target interrupt */
amd_asf_update_ioport_target(piix4_smba, ASF_SLV_INTR, ASFSLVEN, true);
amd_asf_update_ioport_target(piix4_smba, ASF_SLV_RST, ASFSLVEN, false);
/* Enable PEC and PEC append */
amd_asf_update_ioport_target(piix4_smba, ASF_DATA_EN, SMBHSTCNT, true);
amd_asf_update_ioport_target(piix4_smba, ASF_PEC_SP, SMBHSTCNT, true);
}
static int amd_asf_access(struct i2c_adapter *adap, u16 addr, u8 command, u8 *data)
{
struct amd_asf_dev *dev = i2c_get_adapdata(adap);
unsigned short piix4_smba = dev->port_addr->start;
u8 i, len;
outb_p((addr << 1), SMBHSTADD);
outb_p(command, SMBHSTCMD);
len = data[0];
if (len == 0 || len > ASF_BLOCK_MAX_BYTES)
return -EINVAL;
outb_p(len, SMBHSTDAT0);
/* Reset SMBBLKDAT */
inb_p(SMBHSTCNT);
for (i = 1; i <= len; i++)
outb_p(data[i], SMBBLKDAT);
outb_p(PIIX4_BLOCK_DATA, SMBHSTCNT);
/* Enable PEC and PEC append */
amd_asf_update_ioport_target(piix4_smba, ASF_DATA_EN, SMBHSTCNT, true);
amd_asf_update_ioport_target(piix4_smba, ASF_PEC_SP, SMBHSTCNT, true);
return piix4_transaction(adap, piix4_smba);
}
static int amd_asf_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
struct amd_asf_dev *dev = i2c_get_adapdata(adap);
unsigned short piix4_smba = dev->port_addr->start;
u8 asf_data[ASF_BLOCK_MAX_BYTES];
struct i2c_msg *dev_msgs = msgs;
u8 prev_port;
int ret;
if (msgs->flags & I2C_M_RD) {
dev_err(&adap->dev, "ASF: Read not supported\n");
return -EOPNOTSUPP;
}
/* Exclude the receive header and PEC */
if (msgs->len > ASF_BLOCK_MAX_BYTES - 3) {
dev_warn(&adap->dev, "ASF: max message length exceeded\n");
return -EOPNOTSUPP;
}
asf_data[0] = dev_msgs->len;
memcpy(asf_data + 1, dev_msgs[0].buf, dev_msgs->len);
ret = piix4_sb800_region_request(&adap->dev, &dev->mmio_cfg);
if (ret)
return ret;
amd_asf_update_ioport_target(piix4_smba, ASF_SLV_RST, ASFSLVEN, true);
amd_asf_update_ioport_target(piix4_smba, ASF_SLV_LISTN, ASFLISADDR, false);
/* Clear ASF target status */
outb_p(0, ASFSLVSTA);
/* Enable ASF SMBus controller function */
amd_asf_update_mmio_target(dev, ASF_MSTR_EN, true);
prev_port = piix4_sb800_port_sel(0, &dev->mmio_cfg);
ret = amd_asf_access(adap, msgs->addr, msgs[0].buf[0], asf_data);
piix4_sb800_port_sel(prev_port, &dev->mmio_cfg);
amd_asf_setup_target(dev);
piix4_sb800_region_release(&adap->dev, &dev->mmio_cfg);
return ret;
}
static int amd_asf_reg_target(struct i2c_client *target)
{
struct amd_asf_dev *dev = i2c_get_adapdata(target->adapter);
unsigned short piix4_smba = dev->port_addr->start;
int ret;
u8 reg;
if (dev->target)
return -EBUSY;
ret = piix4_sb800_region_request(&target->dev, &dev->mmio_cfg);
if (ret)
return ret;
reg = (target->addr << 1) | I2C_M_RD;
outb_p(reg, ASFLISADDR);
amd_asf_setup_target(dev);
dev->target = target;
amd_asf_update_ioport_target(piix4_smba, ASF_DATA_EN, ASFDATABNKSEL, false);
piix4_sb800_region_release(&target->dev, &dev->mmio_cfg);
return 0;
}
static int amd_asf_unreg_target(struct i2c_client *target)
{
struct amd_asf_dev *dev = i2c_get_adapdata(target->adapter);
unsigned short piix4_smba = dev->port_addr->start;
amd_asf_update_ioport_target(piix4_smba, ASF_SLV_INTR, ASFSLVEN, false);
amd_asf_update_ioport_target(piix4_smba, ASF_SLV_RST, ASFSLVEN, true);
dev->target = NULL;
return 0;
}
static u32 amd_asf_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_PEC | I2C_FUNC_SLAVE;
}
static const struct i2c_algorithm amd_asf_smbus_algorithm = {
.master_xfer = amd_asf_xfer,
.reg_slave = amd_asf_reg_target,
.unreg_slave = amd_asf_unreg_target,
.functionality = amd_asf_func,
};
static irqreturn_t amd_asf_irq_handler(int irq, void *ptr)
{
struct amd_asf_dev *dev = ptr;
unsigned short piix4_smba = dev->port_addr->start;
u8 target_int = inb_p(ASFSTA);
if (target_int & BIT(6)) {
/* Target Interrupt */
outb_p(target_int | BIT(6), ASFSTA);
schedule_delayed_work(&dev->work_buf, HZ);
} else {
/* Controller Interrupt */
amd_asf_update_ioport_target(piix4_smba, ASF_SLV_INTR, SMBHSTSTS, true);
}
return IRQ_HANDLED;
}
static int amd_asf_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct amd_asf_dev *asf_dev;
struct resource *eoi_addr;
int ret, irq;
asf_dev = devm_kzalloc(dev, sizeof(*asf_dev), GFP_KERNEL);
if (!asf_dev)
return dev_err_probe(dev, -ENOMEM, "Failed to allocate memory\n");
asf_dev->mmio_cfg.use_mmio = true;
asf_dev->port_addr = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (!asf_dev->port_addr)
return dev_err_probe(dev, -EINVAL, "missing IO resources\n");
/*
* The resource obtained via ACPI might not belong to the ASF device address space. Instead,
* it could be within other IP blocks of the ASIC, which are crucial for generating
* subsequent interrupts. Therefore, we avoid using devm_platform_ioremap_resource() and
* use platform_get_resource() and devm_ioremap() separately to prevent any address space
* conflicts.
*/
eoi_addr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!eoi_addr)
return dev_err_probe(dev, -EINVAL, "missing MEM resources\n");
asf_dev->eoi_base = devm_ioremap(dev, eoi_addr->start, resource_size(eoi_addr));
if (!asf_dev->eoi_base)
return dev_err_probe(dev, -EBUSY, "failed mapping IO region\n");
ret = devm_delayed_work_autocancel(dev, &asf_dev->work_buf, amd_asf_process_target);
if (ret)
return dev_err_probe(dev, ret, "failed to create work queue\n");
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return dev_err_probe(dev, irq, "missing IRQ resources\n");
ret = devm_request_irq(dev, irq, amd_asf_irq_handler, IRQF_SHARED, "amd_asf", asf_dev);
if (ret)
return dev_err_probe(dev, ret, "Unable to request irq: %d for use\n", irq);
asf_dev->adap.owner = THIS_MODULE;
asf_dev->adap.algo = &amd_asf_smbus_algorithm;
asf_dev->adap.dev.parent = dev;
i2c_set_adapdata(&asf_dev->adap, asf_dev);
snprintf(asf_dev->adap.name, sizeof(asf_dev->adap.name), "AMD ASF adapter");
return devm_i2c_add_adapter(dev, &asf_dev->adap);
}
static const struct acpi_device_id amd_asf_acpi_ids[] = {
{ "AMDI001A" },
{ }
};
MODULE_DEVICE_TABLE(acpi, amd_asf_acpi_ids);
static struct platform_driver amd_asf_driver = {
.driver = {
.name = "i2c-amd-asf",
.acpi_match_table = amd_asf_acpi_ids,
},
.probe = amd_asf_probe,
};
module_platform_driver(amd_asf_driver);
MODULE_IMPORT_NS("PIIX4_SMBUS");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("AMD Alert Standard Format Driver");
|