xe_wait_user_fence.o \
xe_wopcm.o
-xe-$(CONFIG_I2C) += xe_i2c.o
+xe-$(CONFIG_I2C) += xe_i2c.o \
+ xe_amc.o
+
xe-$(CONFIG_DRM_XE_GPUSVM) += xe_svm.o
xe-$(CONFIG_DRM_GPUSVM) += xe_userptr.o
#define I2C_CONFIG_CMD XE_REG(I2C_CONFIG_SPACE_OFFSET + PCI_COMMAND)
#define I2C_CONFIG_PMCSR XE_REG(I2C_CONFIG_SPACE_OFFSET + 0x84)
+#define I2C_REG(reg) XE_REG((reg) + I2C_MEM_SPACE_OFFSET)
+
#endif /* _XE_I2C_REGS_H_ */
--- /dev/null
+// 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(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);
+ }
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _XE_AMC_H_
+#define _XE_AMC_H_
+
+#include <linux/i2c.h>
+
+#include "xe_device.h"
+
+struct xe_i2c;
+
+static inline struct xe_device *i2c_adapter_to_xe_device(struct i2c_adapter *adapter)
+{
+ return kdev_to_xe_device(adapter->dev.parent->parent);
+}
+
+static inline struct xe_device *i2c_client_to_xe_device(struct i2c_client *client)
+{
+ return i2c_adapter_to_xe_device(client->adapter);
+}
+
+int xe_amc_init(struct xe_i2c *i2c);
+void xe_amc_exit(struct xe_i2c *i2c);
+void xe_amc_handle_alert(struct xe_i2c *i2c);
+
+#endif /* _XE_AMC_H_ */
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/ioport.h>
-#include <linux/irq.h>
-#include <linux/irqdomain.h>
#include <linux/notifier.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/workqueue.h>
+#include <linux/designware_i2c.h>
+
#include "regs/xe_i2c_regs.h"
#include "regs/xe_irq_regs.h"
+#include "xe_amc.h"
#include "xe_device.h"
#include "xe_i2c.h"
#include "xe_mmio.h"
val[1] = xe_mmio_read32(mmio, REG_SG_REMAP_ADDR_POSTFIX);
}
+static void xe_i2c_handle_smbus_alert(struct xe_i2c *i2c)
+{
+ u32 stat;
+
+ stat = xe_mmio_read32(i2c->mmio, I2C_REG(DW_IC_SMBUS_INTR_STAT));
+ if (!stat)
+ return;
+
+ xe_mmio_write32(i2c->mmio, I2C_REG(DW_IC_CLR_SMBUS_INTR), stat);
+
+ if (stat & DW_IC_SMBUS_INTR_ALERT && i2c->amc)
+ xe_amc_handle_alert(i2c);
+ else
+ xe_mmio_rmw32(i2c->mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0);
+}
+
static void xe_i2c_client_work(struct work_struct *work)
{
struct xe_i2c *i2c = container_of(work, struct xe_i2c, work);
struct i2c_board_info info = {
.type = "amc",
.flags = I2C_CLIENT_HOST_NOTIFY,
- .addr = i2c->ep.addr[1],
+ .addr = i2c->ep.addr[XE_I2C_CLIENT_AMC],
};
- i2c->client[0] = i2c_new_client_device(i2c->adapter, &info);
+ i2c->client[XE_I2C_CLIENT_AMC] = i2c_new_client_device(i2c->adapter, &info);
}
static int xe_i2c_notifier(struct notifier_block *nb, unsigned long action, void *data)
goto err_fwnode_remove;
}
- if (i2c->adapter_irq) {
- struct resource res;
-
- res = DEFINE_RES_IRQ_NAMED(i2c->adapter_irq, "xe_i2c");
-
- ret = platform_device_add_resources(pdev, &res, 1);
- if (ret)
- goto err_pdev_put;
- }
-
pdev->dev.parent = i2c->drm_dev;
pdev->dev.fwnode = fwnode;
i2c->adapter_node = fwnode;
static bool xe_i2c_irq_present(struct xe_device *xe)
{
- return xe->i2c && xe->i2c->adapter_irq;
+ return xe->i2c && xe->i2c->ep.capabilities & XE_I2C_EP_CAP_IRQ &&
+ !xe_survivability_mode_is_boot_enabled(xe);
}
/**
*/
void xe_i2c_irq_handler(struct xe_device *xe, u32 master_ctl)
{
- struct xe_mmio *mmio = xe_root_tile_mmio(xe);
-
if (!(master_ctl & I2C_IRQ) || !xe_i2c_irq_present(xe))
return;
- /* Forward interrupt to I2C adapter */
- generic_handle_irq_safe(xe->i2c->adapter_irq);
-
- /* Deassert after I2C adapter clears the interrupt */
- xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, 0, PCI_COMMAND_INTX_DISABLE);
- /* Reassert to allow subsequent interrupt generation */
- xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0);
+ xe_i2c_handle_smbus_alert(xe->i2c);
}
void xe_i2c_irq_reset(struct xe_device *xe)
xe_mmio_rmw32(mmio, I2C_CONFIG_CMD, PCI_COMMAND_INTX_DISABLE, 0);
}
-static int xe_i2c_irq_map(struct irq_domain *h, unsigned int virq,
- irq_hw_number_t hw_irq_num)
-{
- irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
- return 0;
-}
-
-static const struct irq_domain_ops xe_i2c_irq_ops = {
- .map = xe_i2c_irq_map,
-};
-
-static int xe_i2c_create_irq(struct xe_device *xe)
-{
- struct xe_i2c *i2c = xe->i2c;
- struct irq_domain *domain;
-
- if (!(i2c->ep.capabilities & XE_I2C_EP_CAP_IRQ) ||
- xe_survivability_mode_is_boot_enabled(xe))
- return 0;
-
- domain = irq_domain_create_linear(dev_fwnode(i2c->drm_dev), 1, &xe_i2c_irq_ops, NULL);
- if (!domain)
- return -ENOMEM;
-
- i2c->adapter_irq = irq_create_mapping(domain, 0);
- i2c->irqdomain = domain;
-
- return 0;
-}
-
-static void xe_i2c_remove_irq(struct xe_i2c *i2c)
-{
- if (!i2c->irqdomain)
- return;
-
- irq_dispose_mapping(i2c->adapter_irq);
- irq_domain_remove(i2c->irqdomain);
-}
-
static int xe_i2c_read(void *context, unsigned int reg, unsigned int *val)
{
struct xe_i2c *i2c = context;
{
struct xe_i2c *i2c = context;
- xe_mmio_write32(i2c->mmio, XE_REG(reg + I2C_MEM_SPACE_OFFSET), val);
+ switch (reg) {
+ case DW_IC_SMBUS_INTR_MASK:
+ /* Make sure the Alert is never masked. */
+ val |= DW_IC_SMBUS_INTR_ALERT;
+ break;
+ default:
+ break;
+ }
+ xe_mmio_write32(i2c->mmio, I2C_REG(reg), val);
return 0;
}
struct xe_i2c *i2c = data;
unsigned int i;
- for (i = 0; i < XE_I2C_MAX_CLIENTS; i++)
+ xe_amc_exit(i2c);
+
+ for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) {
i2c_unregister_device(i2c->client[i]);
+ i2c->client[i] = NULL;
+ }
bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier);
xe_i2c_unregister_adapter(i2c);
- xe_i2c_remove_irq(i2c);
}
/**
if (ret)
return ret;
- ret = xe_i2c_create_irq(xe);
- if (ret)
- goto err_unregister_notifier;
-
ret = xe_i2c_register_adapter(i2c);
- if (ret)
- goto err_remove_irq;
+ if (ret) {
+ bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier);
+ return ret;
+ }
+
+ ret = xe_amc_init(i2c);
+ if (ret) {
+ xe_i2c_remove(i2c);
+ return ret;
+ }
xe_i2c_irq_postinstall(xe);
return devm_add_action_or_reset(drm_dev, xe_i2c_remove, i2c);
-
-err_remove_irq:
- xe_i2c_remove_irq(i2c);
-
-err_unregister_notifier:
- bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier);
-
- return ret;
}
struct fwnode_handle;
struct i2c_adapter;
struct i2c_client;
-struct irq_domain;
struct platform_device;
+struct xe_amc;
struct xe_device;
struct xe_mmio;
-#define XE_I2C_MAX_CLIENTS 3
-
#define XE_I2C_EP_COOKIE_DEVICE 0xde
/* Endpoint Capabilities */
#define XE_I2C_EP_CAP_IRQ BIT(0)
+enum XE_I2C_CLIENT {
+ XE_I2C_CLIENT_AMC = 1,
+ XE_I2C_MAX_CLIENTS = 3,
+};
+
struct xe_i2c_endpoint {
u8 cookie;
u8 capabilities;
struct notifier_block bus_notifier;
struct work_struct work;
- struct irq_domain *irqdomain;
- int adapter_irq;
-
struct xe_i2c_endpoint ep;
struct device *drm_dev;
struct xe_mmio *mmio;
+ struct xe_amc *amc;
};
#if IS_ENABLED(CONFIG_I2C)