summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/imagination/pvr_fw.c
diff options
context:
space:
mode:
authorSarah Walker <sarah.walker@imgtec.com>2023-11-22 19:34:31 +0300
committerMaxime Ripard <mripard@kernel.org>2023-11-23 11:01:46 +0300
commitf99f5f3ea7efd54ba0529c4f2d7c72712918a522 (patch)
treeb11d3aa755d4b652c02b3386ed21c2e6eddefa80 /drivers/gpu/drm/imagination/pvr_fw.c
parenta26f067feac1f6142c3ccbaeaee8f84078bca9d4 (diff)
downloadlinux-f99f5f3ea7efd54ba0529c4f2d7c72712918a522.tar.xz
drm/imagination: Add GPU ID parsing and firmware loading
Read the GPU ID register at probe time and select the correct features/quirks/enhancements. Use the GPU ID to form the firmware file name and load the firmware. The features/quirks/enhancements arrays are currently hardcoded in the driver for the supported GPUs. We are looking at moving this information to the firmware image. Changes since v8: - Corrected license identifiers Changes since v7: - Fix kerneldoc for pvr_device_info_set_enhancements() Changes since v5: - Add BRN 71242 to device info Changes since v4: - Retrieve device information from firmware header - Pull forward firmware header parsing from FW infrastructure patch - Use devm_add_action_or_reset to release firmware Changes since v3: - Use drm_dev_{enter,exit} Co-developed-by: Frank Binns <frank.binns@imgtec.com> Signed-off-by: Frank Binns <frank.binns@imgtec.com> Co-developed-by: Matt Coster <matt.coster@imgtec.com> Signed-off-by: Matt Coster <matt.coster@imgtec.com> Co-developed-by: Donald Robson <donald.robson@imgtec.com> Signed-off-by: Donald Robson <donald.robson@imgtec.com> Signed-off-by: Sarah Walker <sarah.walker@imgtec.com> Link: https://lore.kernel.org/r/1ff76f7a5b45c742279c78910f8491b8a5e7f6e6.1700668843.git.donald.robson@imgtec.com Signed-off-by: Maxime Ripard <mripard@kernel.org>
Diffstat (limited to 'drivers/gpu/drm/imagination/pvr_fw.c')
-rw-r--r--drivers/gpu/drm/imagination/pvr_fw.c145
1 files changed, 145 insertions, 0 deletions
diff --git a/drivers/gpu/drm/imagination/pvr_fw.c b/drivers/gpu/drm/imagination/pvr_fw.c
new file mode 100644
index 000000000000..a9377c735c9c
--- /dev/null
+++ b/drivers/gpu/drm/imagination/pvr_fw.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright (c) 2023 Imagination Technologies Ltd. */
+
+#include "pvr_device.h"
+#include "pvr_device_info.h"
+#include "pvr_fw.h"
+
+#include <drm/drm_drv.h>
+#include <linux/firmware.h>
+#include <linux/sizes.h>
+
+#define FW_MAX_SUPPORTED_MAJOR_VERSION 1
+
+/**
+ * pvr_fw_validate() - Parse firmware header and check compatibility
+ * @pvr_dev: Device pointer.
+ *
+ * Returns:
+ * * 0 on success, or
+ * * -EINVAL if firmware is incompatible.
+ */
+static int
+pvr_fw_validate(struct pvr_device *pvr_dev)
+{
+ struct drm_device *drm_dev = from_pvr_device(pvr_dev);
+ const struct firmware *firmware = pvr_dev->fw_dev.firmware;
+ const struct pvr_fw_layout_entry *layout_entries;
+ const struct pvr_fw_info_header *header;
+ const u8 *fw = firmware->data;
+ u32 fw_offset = firmware->size - SZ_4K;
+ u32 layout_table_size;
+ u32 entry;
+
+ if (firmware->size < SZ_4K || (firmware->size % FW_BLOCK_SIZE))
+ return -EINVAL;
+
+ header = (const struct pvr_fw_info_header *)&fw[fw_offset];
+
+ if (header->info_version != PVR_FW_INFO_VERSION) {
+ drm_err(drm_dev, "Unsupported fw info version %u\n",
+ header->info_version);
+ return -EINVAL;
+ }
+
+ if (header->header_len != sizeof(struct pvr_fw_info_header) ||
+ header->layout_entry_size != sizeof(struct pvr_fw_layout_entry) ||
+ header->layout_entry_num > PVR_FW_INFO_MAX_NUM_ENTRIES) {
+ drm_err(drm_dev, "FW info format mismatch\n");
+ return -EINVAL;
+ }
+
+ if (!(header->flags & PVR_FW_FLAGS_OPEN_SOURCE) ||
+ header->fw_version_major > FW_MAX_SUPPORTED_MAJOR_VERSION ||
+ header->fw_version_major == 0) {
+ drm_err(drm_dev, "Unsupported FW version %u.%u (build: %u%s)\n",
+ header->fw_version_major, header->fw_version_minor,
+ header->fw_version_build,
+ (header->flags & PVR_FW_FLAGS_OPEN_SOURCE) ? " OS" : "");
+ return -EINVAL;
+ }
+
+ if (pvr_gpu_id_to_packed_bvnc(&pvr_dev->gpu_id) != header->bvnc) {
+ struct pvr_gpu_id fw_gpu_id;
+
+ packed_bvnc_to_pvr_gpu_id(header->bvnc, &fw_gpu_id);
+ drm_err(drm_dev, "FW built for incorrect GPU ID %i.%i.%i.%i (expected %i.%i.%i.%i)\n",
+ fw_gpu_id.b, fw_gpu_id.v, fw_gpu_id.n, fw_gpu_id.c,
+ pvr_dev->gpu_id.b, pvr_dev->gpu_id.v, pvr_dev->gpu_id.n, pvr_dev->gpu_id.c);
+ return -EINVAL;
+ }
+
+ fw_offset += header->header_len;
+ layout_table_size =
+ header->layout_entry_size * header->layout_entry_num;
+ if ((fw_offset + layout_table_size) > firmware->size)
+ return -EINVAL;
+
+ layout_entries = (const struct pvr_fw_layout_entry *)&fw[fw_offset];
+ for (entry = 0; entry < header->layout_entry_num; entry++) {
+ u32 start_addr = layout_entries[entry].base_addr;
+ u32 end_addr = start_addr + layout_entries[entry].alloc_size;
+
+ if (start_addr >= end_addr)
+ return -EINVAL;
+ }
+
+ fw_offset = (firmware->size - SZ_4K) - header->device_info_size;
+
+ drm_info(drm_dev, "FW version v%u.%u (build %u OS)\n", header->fw_version_major,
+ header->fw_version_minor, header->fw_version_build);
+
+ pvr_dev->fw_version.major = header->fw_version_major;
+ pvr_dev->fw_version.minor = header->fw_version_minor;
+
+ pvr_dev->fw_dev.header = header;
+ pvr_dev->fw_dev.layout_entries = layout_entries;
+
+ return 0;
+}
+
+static int
+pvr_fw_get_device_info(struct pvr_device *pvr_dev)
+{
+ const struct firmware *firmware = pvr_dev->fw_dev.firmware;
+ struct pvr_fw_device_info_header *header;
+ const u8 *fw = firmware->data;
+ const u64 *dev_info;
+ u32 fw_offset;
+
+ fw_offset = (firmware->size - SZ_4K) - pvr_dev->fw_dev.header->device_info_size;
+
+ header = (struct pvr_fw_device_info_header *)&fw[fw_offset];
+ dev_info = (u64 *)(header + 1);
+
+ pvr_device_info_set_quirks(pvr_dev, dev_info, header->brn_mask_size);
+ dev_info += header->brn_mask_size;
+
+ pvr_device_info_set_enhancements(pvr_dev, dev_info, header->ern_mask_size);
+ dev_info += header->ern_mask_size;
+
+ return pvr_device_info_set_features(pvr_dev, dev_info, header->feature_mask_size,
+ header->feature_param_size);
+}
+
+/**
+ * pvr_fw_validate_init_device_info() - Validate firmware and initialise device information
+ * @pvr_dev: Target PowerVR device.
+ *
+ * This function must be called before querying device information.
+ *
+ * Returns:
+ * * 0 on success, or
+ * * -%EINVAL if firmware validation fails.
+ */
+int
+pvr_fw_validate_init_device_info(struct pvr_device *pvr_dev)
+{
+ int err;
+
+ err = pvr_fw_validate(pvr_dev);
+ if (err)
+ return err;
+
+ return pvr_fw_get_device_info(pvr_dev);
+}