blob: edd50bf8261eca0aea8df79d3324461272e0252a [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2026 Intel Corporation.
*/
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/pci_ids.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/workqueue.h>
#include "regs/xe_i2c_regs.h"
#include "xe_amc.h"
#include "xe_device.h"
#include "xe_i2c.h"
#include "xe_mmio.h"
/**
* DOC: Add-In Management Controller (AMC)
*
* Handler for the SMBus Alerts from the AMC. All the alerts from AMC will cause
* the device to be declared wedged.
*/
#define AMC_COMMAND 0x0f
#define AMC_GPU_I2C_ADDR 0x8f
#define AMC_VERSION_V1 0x01
#define AMC_DESTINATION_ID 12
#define AMC_SOURCE_ID 8
#define AMC_FLAGS 0xc8
#define AMC_MSG_TYPE 0x7e
#define AMC_GET_ALERT_REASON 0x01
enum xe_amc_alert {
AMC_ALERT_UNKNOWN,
AMC_ALERT_FW_DOWNLOAD,
AMC_ALERT_THERMAL_TRIP,
AMC_ALERT_OOB_REQUEST,
AMC_ALERT_OOB_RESET,
AMC_ALERT_CATERR,
};
static const char * const amc_alert[] = {
[AMC_ALERT_FW_DOWNLOAD] = "Firmware Download",
[AMC_ALERT_THERMAL_TRIP] = "Thermal Trip",
[AMC_ALERT_OOB_REQUEST] = "OOB Request",
[AMC_ALERT_OOB_RESET] = "OOB Reset",
[AMC_ALERT_CATERR] = "Catastrophic",
};
struct xe_amc {
struct xe_i2c *i2c;
struct work_struct work;
};
struct amc_header {
u8 command;
u8 len;
u8 address;
u8 version;
u8 destination;
u8 source;
u8 flags;
} __packed;
struct amc_message {
u8 type;
u16 vendor;
u8 command;
} __packed;
struct amc_request {
struct amc_header header;
struct amc_message message;
u32 reserved;
} __packed;
struct amc_response {
struct amc_header header;
struct amc_message message;
u8 error;
u8 value;
} __packed;
static const struct amc_request amc_get_alert_reason = {
.header = {
.command = AMC_COMMAND,
.len = sizeof(struct amc_request) - 2,
.address = AMC_GPU_I2C_ADDR,
.version = AMC_VERSION_V1,
.destination = AMC_DESTINATION_ID,
.source = AMC_SOURCE_ID,
.flags = AMC_FLAGS,
},
.message = {
.type = AMC_MSG_TYPE,
.vendor = htons(PCI_VENDOR_ID_INTEL),
.command = AMC_GET_ALERT_REASON,
},
};
static void xe_amc_work(struct work_struct *work)
{
const struct amc_request *request = &amc_get_alert_reason;
struct xe_amc *amc = from_work(amc, work, work);
u8 alert_reason = AMC_ALERT_UNKNOWN;
struct amc_response response;
struct i2c_client *client;
int ret;
client = amc->i2c->client[XE_I2C_CLIENT_AMC];
if (IS_ERR_OR_NULL(client))
goto out_reassert_interrupt;
ret = i2c_master_send(client, (u8 *)request, sizeof(*request));
if (ret < 0) {
dev_err(&client->dev, "failed to send request (%d)\n", ret);
goto out_reassert_interrupt;
}
/* AMC needs 20ms to generate the response. */
fsleep(20 * USEC_PER_MSEC);
ret = i2c_master_recv(client, (u8 *)&response, sizeof(response));
if (ret < 0) {
dev_err(&client->dev, "failed to read response (%d)\n", ret);
goto out_reassert_interrupt;
}
if (!response.header.len) {
dev_err(&client->dev, "empty response from AMC\n");
goto out_reassert_interrupt;
}
if (memcmp(&response.message, &request->message, sizeof(struct amc_message))) {
dev_err(&client->dev, "response does not match the request\n");
goto out_reassert_interrupt;
}
if (response.error) {
dev_err(&client->dev, "AMC error 0x%02x\n", response.error);
goto out_reassert_interrupt;
}
alert_reason = response.value;
dev_dbg(&client->dev, "Alert reason: %d\n", alert_reason);
out_reassert_interrupt:
xe_mmio_rmw32(amc->i2c->mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0);
switch (alert_reason) {
case AMC_ALERT_FW_DOWNLOAD:
case AMC_ALERT_THERMAL_TRIP:
case AMC_ALERT_OOB_REQUEST:
case AMC_ALERT_OOB_RESET:
case AMC_ALERT_CATERR:
dev_warn(amc->i2c->drm_dev, "AMC Alert: %s\n", amc_alert[alert_reason]);
xe_device_declare_wedged(i2c_client_to_xe_device(client));
break;
default:
dev_warn(amc->i2c->drm_dev, "unknown AMC alert: %d\n", alert_reason);
break;
}
}
void xe_amc_handle_alert(struct xe_i2c *i2c)
{
queue_work(system_long_wq, &i2c->amc->work);
}
int xe_amc_init(struct xe_i2c *i2c)
{
struct xe_amc *amc;
amc = kzalloc_obj(*amc);
if (!amc)
return -ENOMEM;
INIT_WORK(&amc->work, xe_amc_work);
i2c->amc = amc;
amc->i2c = i2c;
return 0;
}
void xe_amc_exit(struct xe_i2c *i2c)
{
if (i2c->amc) {
cancel_work_sync(&i2c->amc->work);
kfree(i2c->amc);
}
}