| // SPDX-License-Identifier: MIT | |
| /* | |
| * Copyright(c) 2019-2025, Intel Corporation. All rights reserved. | |
| */ | |
| #include <linux/intel_dg_nvm_aux.h> | |
| #include <linux/pci.h> | |
| #include "xe_device.h" | |
| #include "xe_mmio.h" | |
| #include "xe_nvm.h" | |
| #include "xe_pcode_api.h" | |
| #include "regs/xe_gsc_regs.h" | |
| #include "xe_sriov.h" | |
| #define GEN12_GUNIT_NVM_BASE 0x00102040 | |
| #define GEN12_DEBUG_NVM_BASE 0x00101018 | |
| #define GEN12_CNTL_PROTECTED_NVM_REG 0x0010100C | |
| #define GEN12_GUNIT_NVM_SIZE 0x80 | |
| #define GEN12_DEBUG_NVM_SIZE 0x4 | |
| #define NVM_NON_POSTED_ERASE_CHICKEN_BIT BIT(13) | |
| #define HECI_FW_STATUS_2_NVM_ACCESS_MODE BIT(3) | |
| static const struct intel_dg_nvm_region regions[INTEL_DG_NVM_REGIONS] = { | |
| [0] = { .name = "DESCRIPTOR", }, | |
| [2] = { .name = "GSC", }, | |
| [9] = { .name = "PADDING", }, | |
| [11] = { .name = "OptionROM", }, | |
| [12] = { .name = "DAM", }, | |
| }; | |
| static void xe_nvm_release_dev(struct device *dev) | |
| { | |
| struct auxiliary_device *aux = container_of(dev, struct auxiliary_device, dev); | |
| struct intel_dg_nvm_dev *nvm = container_of(aux, struct intel_dg_nvm_dev, aux_dev); | |
| kfree(nvm); | |
| } | |
| static bool xe_nvm_non_posted_erase(struct xe_device *xe) | |
| { | |
| struct xe_mmio *mmio = xe_root_tile_mmio(xe); | |
| switch (xe->info.platform) { | |
| case XE_CRESCENTISLAND: | |
| case XE_BATTLEMAGE: | |
| return !(xe_mmio_read32(mmio, XE_REG(GEN12_CNTL_PROTECTED_NVM_REG)) & | |
| NVM_NON_POSTED_ERASE_CHICKEN_BIT); | |
| default: | |
| return false; | |
| } | |
| } | |
| static bool xe_nvm_writable_override(struct xe_device *xe) | |
| { | |
| struct xe_mmio *mmio = xe_root_tile_mmio(xe); | |
| bool writable_override; | |
| struct xe_reg reg; | |
| u32 test_bit, test_val; | |
| switch (xe->info.platform) { | |
| case XE_CRESCENTISLAND: | |
| reg = PCODE_SCRATCH(0); | |
| test_bit = FDO_MODE; | |
| test_val = FDO_MODE; | |
| break; | |
| case XE_BATTLEMAGE: | |
| reg = HECI_FWSTS2(DG2_GSC_HECI2_BASE); | |
| test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; | |
| test_val = 0; | |
| break; | |
| case XE_PVC: | |
| reg = HECI_FWSTS2(PVC_GSC_HECI2_BASE); | |
| test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; | |
| test_val = 0; | |
| break; | |
| case XE_DG2: | |
| reg = HECI_FWSTS2(DG2_GSC_HECI2_BASE); | |
| test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; | |
| test_val = 0; | |
| break; | |
| case XE_DG1: | |
| reg = HECI_FWSTS2(DG1_GSC_HECI2_BASE); | |
| test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; | |
| test_val = 0; | |
| break; | |
| default: | |
| drm_err(&xe->drm, "Unknown platform\n"); | |
| return true; | |
| } | |
| writable_override = (xe_mmio_read32(mmio, reg) & test_bit) == test_val; | |
| if (writable_override) | |
| drm_info(&xe->drm, "NVM access overridden by jumper\n"); | |
| return writable_override; | |
| } | |
| static void xe_nvm_fini(void *arg) | |
| { | |
| struct xe_device *xe = arg; | |
| struct intel_dg_nvm_dev *nvm = xe->nvm; | |
| if (!xe->info.has_gsc_nvm) | |
| return; | |
| /* No access to internal NVM from VFs */ | |
| if (IS_SRIOV_VF(xe)) | |
| return; | |
| /* Nvm pointer should not be NULL here */ | |
| if (WARN_ON(!nvm)) | |
| return; | |
| auxiliary_device_delete(&nvm->aux_dev); | |
| auxiliary_device_uninit(&nvm->aux_dev); | |
| xe->nvm = NULL; | |
| } | |
| int xe_nvm_init(struct xe_device *xe) | |
| { | |
| struct pci_dev *pdev = to_pci_dev(xe->drm.dev); | |
| struct auxiliary_device *aux_dev; | |
| struct intel_dg_nvm_dev *nvm; | |
| int ret; | |
| if (!xe->info.has_gsc_nvm) | |
| return 0; | |
| /* No access to internal NVM from VFs */ | |
| if (IS_SRIOV_VF(xe)) | |
| return 0; | |
| /* Nvm pointer should be NULL here */ | |
| if (WARN_ON(xe->nvm)) | |
| return -EFAULT; | |
| nvm = kzalloc_obj(*nvm); | |
| if (!nvm) | |
| return -ENOMEM; | |
| nvm->writable_override = xe_nvm_writable_override(xe); | |
| nvm->non_posted_erase = xe_nvm_non_posted_erase(xe); | |
| nvm->bar.parent = &pdev->resource[0]; | |
| nvm->bar.start = GEN12_GUNIT_NVM_BASE + pdev->resource[0].start; | |
| nvm->bar.end = nvm->bar.start + GEN12_GUNIT_NVM_SIZE - 1; | |
| nvm->bar.flags = IORESOURCE_MEM; | |
| nvm->bar.desc = IORES_DESC_NONE; | |
| nvm->regions = regions; | |
| nvm->bar2.parent = &pdev->resource[0]; | |
| nvm->bar2.start = GEN12_DEBUG_NVM_BASE + pdev->resource[0].start; | |
| nvm->bar2.end = nvm->bar2.start + GEN12_DEBUG_NVM_SIZE - 1; | |
| nvm->bar2.flags = IORESOURCE_MEM; | |
| nvm->bar2.desc = IORES_DESC_NONE; | |
| aux_dev = &nvm->aux_dev; | |
| aux_dev->name = "nvm"; | |
| aux_dev->id = (pci_domain_nr(pdev->bus) << 16) | pci_dev_id(pdev); | |
| aux_dev->dev.parent = &pdev->dev; | |
| aux_dev->dev.release = xe_nvm_release_dev; | |
| ret = auxiliary_device_init(aux_dev); | |
| if (ret) { | |
| drm_err(&xe->drm, "xe-nvm aux init failed %d\n", ret); | |
| kfree(nvm); | |
| return ret; | |
| } | |
| ret = auxiliary_device_add(aux_dev); | |
| if (ret) { | |
| drm_err(&xe->drm, "xe-nvm aux add failed %d\n", ret); | |
| auxiliary_device_uninit(aux_dev); | |
| return ret; | |
| } | |
| xe->nvm = nvm; | |
| return devm_add_action_or_reset(xe->drm.dev, xe_nvm_fini, xe); | |
| } |