// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2026 Intel Corporation. */ #include #include #include #include #include #include #include #include #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(sizeof(*amc), GFP_KERNEL); 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); } }