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