1f43fa4b8SHeikki Krogerus // SPDX-License-Identifier: GPL-2.0
2f43fa4b8SHeikki Krogerus /*
3f43fa4b8SHeikki Krogerus * Copyright (C) 2026 Intel Corporation.
4f43fa4b8SHeikki Krogerus */
5f43fa4b8SHeikki Krogerus
6f43fa4b8SHeikki Krogerus #include <linux/delay.h>
7f43fa4b8SHeikki Krogerus #include <linux/dev_printk.h>
8f43fa4b8SHeikki Krogerus #include <linux/err.h>
9f43fa4b8SHeikki Krogerus #include <linux/i2c.h>
10f43fa4b8SHeikki Krogerus #include <linux/pci_ids.h>
11f43fa4b8SHeikki Krogerus #include <linux/slab.h>
12f43fa4b8SHeikki Krogerus #include <linux/string.h>
13f43fa4b8SHeikki Krogerus #include <linux/workqueue.h>
14f43fa4b8SHeikki Krogerus
15f43fa4b8SHeikki Krogerus #include "regs/xe_i2c_regs.h"
16f43fa4b8SHeikki Krogerus
17f43fa4b8SHeikki Krogerus #include "xe_amc.h"
18f43fa4b8SHeikki Krogerus #include "xe_device.h"
19f43fa4b8SHeikki Krogerus #include "xe_i2c.h"
20f43fa4b8SHeikki Krogerus #include "xe_mmio.h"
21f43fa4b8SHeikki Krogerus
22f43fa4b8SHeikki Krogerus /**
23f43fa4b8SHeikki Krogerus * DOC: Add-In Management Controller (AMC)
24f43fa4b8SHeikki Krogerus *
25f43fa4b8SHeikki Krogerus * Handler for the SMBus Alerts from the AMC. All the alerts from AMC will cause
26f43fa4b8SHeikki Krogerus * the device to be declared wedged.
27f43fa4b8SHeikki Krogerus */
28f43fa4b8SHeikki Krogerus
29f43fa4b8SHeikki Krogerus #define AMC_COMMAND 0x0f
30f43fa4b8SHeikki Krogerus #define AMC_GPU_I2C_ADDR 0x8f
31f43fa4b8SHeikki Krogerus #define AMC_VERSION_V1 0x01
32f43fa4b8SHeikki Krogerus #define AMC_DESTINATION_ID 12
33f43fa4b8SHeikki Krogerus #define AMC_SOURCE_ID 8
34f43fa4b8SHeikki Krogerus #define AMC_FLAGS 0xc8
35f43fa4b8SHeikki Krogerus
36f43fa4b8SHeikki Krogerus #define AMC_MSG_TYPE 0x7e
37f43fa4b8SHeikki Krogerus #define AMC_GET_ALERT_REASON 0x01
38f43fa4b8SHeikki Krogerus
39f43fa4b8SHeikki Krogerus enum xe_amc_alert {
40f43fa4b8SHeikki Krogerus AMC_ALERT_UNKNOWN,
41f43fa4b8SHeikki Krogerus AMC_ALERT_FW_DOWNLOAD,
42f43fa4b8SHeikki Krogerus AMC_ALERT_THERMAL_TRIP,
43f43fa4b8SHeikki Krogerus AMC_ALERT_OOB_REQUEST,
44f43fa4b8SHeikki Krogerus AMC_ALERT_OOB_RESET,
45f43fa4b8SHeikki Krogerus AMC_ALERT_CATERR,
46f43fa4b8SHeikki Krogerus };
47f43fa4b8SHeikki Krogerus
48f43fa4b8SHeikki Krogerus static const char * const amc_alert[] = {
49f43fa4b8SHeikki Krogerus [AMC_ALERT_FW_DOWNLOAD] = "Firmware Download",
50f43fa4b8SHeikki Krogerus [AMC_ALERT_THERMAL_TRIP] = "Thermal Trip",
51f43fa4b8SHeikki Krogerus [AMC_ALERT_OOB_REQUEST] = "OOB Request",
52f43fa4b8SHeikki Krogerus [AMC_ALERT_OOB_RESET] = "OOB Reset",
53f43fa4b8SHeikki Krogerus [AMC_ALERT_CATERR] = "Catastrophic",
54f43fa4b8SHeikki Krogerus };
55f43fa4b8SHeikki Krogerus
56f43fa4b8SHeikki Krogerus struct xe_amc {
57f43fa4b8SHeikki Krogerus struct xe_i2c *i2c;
58f43fa4b8SHeikki Krogerus struct work_struct work;
59f43fa4b8SHeikki Krogerus };
60f43fa4b8SHeikki Krogerus
61f43fa4b8SHeikki Krogerus struct amc_header {
62f43fa4b8SHeikki Krogerus u8 command;
63f43fa4b8SHeikki Krogerus u8 len;
64f43fa4b8SHeikki Krogerus u8 address;
65f43fa4b8SHeikki Krogerus u8 version;
66f43fa4b8SHeikki Krogerus u8 destination;
67f43fa4b8SHeikki Krogerus u8 source;
68f43fa4b8SHeikki Krogerus u8 flags;
69f43fa4b8SHeikki Krogerus } __packed;
70f43fa4b8SHeikki Krogerus
71f43fa4b8SHeikki Krogerus struct amc_message {
72f43fa4b8SHeikki Krogerus u8 type;
73f43fa4b8SHeikki Krogerus u16 vendor;
74f43fa4b8SHeikki Krogerus u8 command;
75f43fa4b8SHeikki Krogerus } __packed;
76f43fa4b8SHeikki Krogerus
77f43fa4b8SHeikki Krogerus struct amc_request {
78f43fa4b8SHeikki Krogerus struct amc_header header;
79f43fa4b8SHeikki Krogerus struct amc_message message;
80f43fa4b8SHeikki Krogerus u32 reserved;
81f43fa4b8SHeikki Krogerus } __packed;
82f43fa4b8SHeikki Krogerus
83f43fa4b8SHeikki Krogerus struct amc_response {
84f43fa4b8SHeikki Krogerus struct amc_header header;
85f43fa4b8SHeikki Krogerus struct amc_message message;
86f43fa4b8SHeikki Krogerus u8 error;
87f43fa4b8SHeikki Krogerus u8 value;
88f43fa4b8SHeikki Krogerus } __packed;
89f43fa4b8SHeikki Krogerus
90f43fa4b8SHeikki Krogerus static const struct amc_request amc_get_alert_reason = {
91f43fa4b8SHeikki Krogerus .header = {
92f43fa4b8SHeikki Krogerus .command = AMC_COMMAND,
93f43fa4b8SHeikki Krogerus .len = sizeof(struct amc_request) - 2,
94f43fa4b8SHeikki Krogerus .address = AMC_GPU_I2C_ADDR,
95f43fa4b8SHeikki Krogerus .version = AMC_VERSION_V1,
96f43fa4b8SHeikki Krogerus .destination = AMC_DESTINATION_ID,
97f43fa4b8SHeikki Krogerus .source = AMC_SOURCE_ID,
98f43fa4b8SHeikki Krogerus .flags = AMC_FLAGS,
99f43fa4b8SHeikki Krogerus },
100f43fa4b8SHeikki Krogerus .message = {
101f43fa4b8SHeikki Krogerus .type = AMC_MSG_TYPE,
102f43fa4b8SHeikki Krogerus .vendor = htons(PCI_VENDOR_ID_INTEL),
103f43fa4b8SHeikki Krogerus .command = AMC_GET_ALERT_REASON,
104f43fa4b8SHeikki Krogerus },
105f43fa4b8SHeikki Krogerus };
106f43fa4b8SHeikki Krogerus
xe_amc_work(struct work_struct * work)107f43fa4b8SHeikki Krogerus static void xe_amc_work(struct work_struct *work)
108f43fa4b8SHeikki Krogerus {
109f43fa4b8SHeikki Krogerus const struct amc_request *request = &amc_get_alert_reason;
110f43fa4b8SHeikki Krogerus struct xe_amc *amc = from_work(amc, work, work);
111f43fa4b8SHeikki Krogerus u8 alert_reason = AMC_ALERT_UNKNOWN;
112f43fa4b8SHeikki Krogerus struct amc_response response;
113f43fa4b8SHeikki Krogerus struct i2c_client *client;
114f43fa4b8SHeikki Krogerus int ret;
115f43fa4b8SHeikki Krogerus
116f43fa4b8SHeikki Krogerus client = amc->i2c->client[XE_I2C_CLIENT_AMC];
117f43fa4b8SHeikki Krogerus if (IS_ERR_OR_NULL(client))
118f43fa4b8SHeikki Krogerus goto out_reassert_interrupt;
119f43fa4b8SHeikki Krogerus
120f43fa4b8SHeikki Krogerus ret = i2c_master_send(client, (u8 *)request, sizeof(*request));
121f43fa4b8SHeikki Krogerus if (ret < 0) {
122f43fa4b8SHeikki Krogerus dev_err(&client->dev, "failed to send request (%d)\n", ret);
123f43fa4b8SHeikki Krogerus goto out_reassert_interrupt;
124f43fa4b8SHeikki Krogerus }
125f43fa4b8SHeikki Krogerus
126f43fa4b8SHeikki Krogerus /* AMC needs 20ms to generate the response. */
127f43fa4b8SHeikki Krogerus fsleep(20 * USEC_PER_MSEC);
128f43fa4b8SHeikki Krogerus
129f43fa4b8SHeikki Krogerus ret = i2c_master_recv(client, (u8 *)&response, sizeof(response));
130f43fa4b8SHeikki Krogerus if (ret < 0) {
131f43fa4b8SHeikki Krogerus dev_err(&client->dev, "failed to read response (%d)\n", ret);
132f43fa4b8SHeikki Krogerus goto out_reassert_interrupt;
133f43fa4b8SHeikki Krogerus }
134f43fa4b8SHeikki Krogerus
135f43fa4b8SHeikki Krogerus if (!response.header.len) {
136f43fa4b8SHeikki Krogerus dev_err(&client->dev, "empty response from AMC\n");
137f43fa4b8SHeikki Krogerus goto out_reassert_interrupt;
138f43fa4b8SHeikki Krogerus }
139f43fa4b8SHeikki Krogerus
140f43fa4b8SHeikki Krogerus if (memcmp(&response.message, &request->message, sizeof(struct amc_message))) {
141f43fa4b8SHeikki Krogerus dev_err(&client->dev, "response does not match the request\n");
142f43fa4b8SHeikki Krogerus goto out_reassert_interrupt;
143f43fa4b8SHeikki Krogerus }
144f43fa4b8SHeikki Krogerus
145f43fa4b8SHeikki Krogerus if (response.error) {
146f43fa4b8SHeikki Krogerus dev_err(&client->dev, "AMC error 0x%02x\n", response.error);
147f43fa4b8SHeikki Krogerus goto out_reassert_interrupt;
148f43fa4b8SHeikki Krogerus }
149f43fa4b8SHeikki Krogerus
150f43fa4b8SHeikki Krogerus alert_reason = response.value;
151f43fa4b8SHeikki Krogerus dev_dbg(&client->dev, "Alert reason: %d\n", alert_reason);
152f43fa4b8SHeikki Krogerus
153f43fa4b8SHeikki Krogerus out_reassert_interrupt:
154f43fa4b8SHeikki Krogerus xe_mmio_rmw32(amc->i2c->mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0);
155f43fa4b8SHeikki Krogerus
156f43fa4b8SHeikki Krogerus switch (alert_reason) {
157f43fa4b8SHeikki Krogerus case AMC_ALERT_FW_DOWNLOAD:
158f43fa4b8SHeikki Krogerus case AMC_ALERT_THERMAL_TRIP:
159f43fa4b8SHeikki Krogerus case AMC_ALERT_OOB_REQUEST:
160f43fa4b8SHeikki Krogerus case AMC_ALERT_OOB_RESET:
161f43fa4b8SHeikki Krogerus case AMC_ALERT_CATERR:
162f43fa4b8SHeikki Krogerus dev_warn(amc->i2c->drm_dev, "AMC Alert: %s\n", amc_alert[alert_reason]);
163f43fa4b8SHeikki Krogerus xe_device_declare_wedged(i2c_client_to_xe_device(client));
164f43fa4b8SHeikki Krogerus break;
165f43fa4b8SHeikki Krogerus default:
166f43fa4b8SHeikki Krogerus dev_warn(amc->i2c->drm_dev, "unknown AMC alert: %d\n", alert_reason);
167f43fa4b8SHeikki Krogerus break;
168f43fa4b8SHeikki Krogerus }
169f43fa4b8SHeikki Krogerus }
170f43fa4b8SHeikki Krogerus
xe_amc_handle_alert(struct xe_i2c * i2c)171f43fa4b8SHeikki Krogerus void xe_amc_handle_alert(struct xe_i2c *i2c)
172f43fa4b8SHeikki Krogerus {
173f43fa4b8SHeikki Krogerus queue_work(system_long_wq, &i2c->amc->work);
174f43fa4b8SHeikki Krogerus }
175f43fa4b8SHeikki Krogerus
xe_amc_init(struct xe_i2c * i2c)176f43fa4b8SHeikki Krogerus int xe_amc_init(struct xe_i2c *i2c)
177f43fa4b8SHeikki Krogerus {
178f43fa4b8SHeikki Krogerus struct xe_amc *amc;
179f43fa4b8SHeikki Krogerus
180*3a2c4d55SKees Cook amc = kzalloc_obj(*amc);
181f43fa4b8SHeikki Krogerus if (!amc)
182f43fa4b8SHeikki Krogerus return -ENOMEM;
183f43fa4b8SHeikki Krogerus
184f43fa4b8SHeikki Krogerus INIT_WORK(&amc->work, xe_amc_work);
185f43fa4b8SHeikki Krogerus i2c->amc = amc;
186f43fa4b8SHeikki Krogerus amc->i2c = i2c;
187f43fa4b8SHeikki Krogerus
188f43fa4b8SHeikki Krogerus return 0;
189f43fa4b8SHeikki Krogerus }
190f43fa4b8SHeikki Krogerus
xe_amc_exit(struct xe_i2c * i2c)191f43fa4b8SHeikki Krogerus void xe_amc_exit(struct xe_i2c *i2c)
192f43fa4b8SHeikki Krogerus {
193f43fa4b8SHeikki Krogerus if (i2c->amc) {
194f43fa4b8SHeikki Krogerus cancel_work_sync(&i2c->amc->work);
195f43fa4b8SHeikki Krogerus kfree(i2c->amc);
196f43fa4b8SHeikki Krogerus }
197f43fa4b8SHeikki Krogerus }
198