--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#include <linux/devcoredump.h>
+#include "hif.h"
+#include "coredump.h"
+#include "debug.h"
+
+enum
+ath11k_fw_crash_dump_type ath11k_coredump_get_dump_type(int type)
+{
+       enum ath11k_fw_crash_dump_type dump_type;
+
+       switch (type) {
+       case HOST_DDR_REGION_TYPE:
+               dump_type = FW_CRASH_DUMP_REMOTE_MEM_DATA;
+               break;
+       case M3_DUMP_REGION_TYPE:
+               dump_type = FW_CRASH_DUMP_M3_DUMP;
+               break;
+       case PAGEABLE_MEM_REGION_TYPE:
+               dump_type = FW_CRASH_DUMP_PAGEABLE_DATA;
+               break;
+       case BDF_MEM_REGION_TYPE:
+       case CALDB_MEM_REGION_TYPE:
+               dump_type = FW_CRASH_DUMP_NONE;
+               break;
+       default:
+               dump_type = FW_CRASH_DUMP_TYPE_MAX;
+               break;
+       }
+
+       return dump_type;
+}
+EXPORT_SYMBOL(ath11k_coredump_get_dump_type);
+
+void ath11k_coredump_upload(struct work_struct *work)
+{
+       struct ath11k_base *ab = container_of(work, struct ath11k_base, dump_work);
+
+       ath11k_info(ab, "Uploading coredump\n");
+       /* dev_coredumpv() takes ownership of the buffer */
+       dev_coredumpv(ab->dev, ab->dump_data, ab->ath11k_coredump_len, GFP_KERNEL);
+       ab->dump_data = NULL;
+}
+
+void ath11k_coredump_collect(struct ath11k_base *ab)
+{
+       ath11k_hif_coredump_download(ab);
+}
 
--- /dev/null
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#ifndef _ATH11K_COREDUMP_H_
+#define _ATH11K_COREDUMP_H_
+
+#define ATH11K_FW_CRASH_DUMP_V2      2
+
+enum ath11k_fw_crash_dump_type {
+       FW_CRASH_DUMP_PAGING_DATA,
+       FW_CRASH_DUMP_RDDM_DATA,
+       FW_CRASH_DUMP_REMOTE_MEM_DATA,
+       FW_CRASH_DUMP_PAGEABLE_DATA,
+       FW_CRASH_DUMP_M3_DUMP,
+       FW_CRASH_DUMP_NONE,
+
+       /* keep last */
+       FW_CRASH_DUMP_TYPE_MAX,
+};
+
+#define COREDUMP_TLV_HDR_SIZE 8
+
+struct ath11k_tlv_dump_data {
+       /* see ath11k_fw_crash_dump_type above */
+       __le32 type;
+
+       /* in bytes */
+       __le32 tlv_len;
+
+       /* pad to 32-bit boundaries as needed */
+       u8 tlv_data[];
+} __packed;
+
+struct ath11k_dump_file_data {
+       /* "ATH11K-FW-DUMP" */
+       char df_magic[16];
+       /* total dump len in bytes */
+       __le32 len;
+       /* file dump version */
+       __le32 version;
+       /* pci device id */
+       __le32 chip_id;
+       /* qrtr instance id */
+       __le32 qrtr_id;
+       /* pci domain id */
+       __le32 bus_id;
+       guid_t guid;
+       /* time-of-day stamp */
+       __le64 tv_sec;
+       /* time-of-day stamp, nano-seconds */
+       __le64 tv_nsec;
+       /* room for growth w/out changing binary format */
+       u8 unused[128];
+       u8 data[];
+} __packed;
+
+#ifdef CONFIG_DEV_COREDUMP
+enum ath11k_fw_crash_dump_type ath11k_coredump_get_dump_type(int type);
+void ath11k_coredump_upload(struct work_struct *work);
+void ath11k_coredump_collect(struct ath11k_base *ab);
+#else
+static inline enum
+ath11k_fw_crash_dump_type ath11k_coredump_get_dump_type(int type)
+{
+       return FW_CRASH_DUMP_TYPE_MAX;
+}
+
+static inline void ath11k_coredump_upload(struct work_struct *work)
+{
+}
+
+static inline void ath11k_coredump_collect(struct ath11k_base *ab)
+{
+}
+#endif
+
+#endif
 
 #include <linux/msi.h>
 #include <linux/pci.h>
 #include <linux/of.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
 
 #include "pci.h"
 #include "core.h"
                                                   PCI_EXP_LNKCTL_ASPMC);
 }
 
+#ifdef CONFIG_DEV_COREDUMP
+static int ath11k_pci_coredump_calculate_size(struct ath11k_base *ab, u32 *dump_seg_sz)
+{
+       struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
+       struct mhi_controller *mhi_ctrl = ab_pci->mhi_ctrl;
+       struct image_info *rddm_img, *fw_img;
+       struct ath11k_tlv_dump_data *dump_tlv;
+       enum ath11k_fw_crash_dump_type mem_type;
+       u32 len = 0, rddm_tlv_sz = 0, paging_tlv_sz = 0;
+       struct ath11k_dump_file_data *file_data;
+       int i;
+
+       rddm_img = mhi_ctrl->rddm_image;
+       if (!rddm_img) {
+               ath11k_err(ab, "No RDDM dump found\n");
+               return 0;
+       }
+
+       fw_img = mhi_ctrl->fbc_image;
+
+       for (i = 0; i < fw_img->entries ; i++) {
+               if (!fw_img->mhi_buf[i].buf)
+                       continue;
+
+               paging_tlv_sz += fw_img->mhi_buf[i].len;
+       }
+       dump_seg_sz[FW_CRASH_DUMP_PAGING_DATA] = paging_tlv_sz;
+
+       for (i = 0; i < rddm_img->entries; i++) {
+               if (!rddm_img->mhi_buf[i].buf)
+                       continue;
+
+               rddm_tlv_sz += rddm_img->mhi_buf[i].len;
+       }
+       dump_seg_sz[FW_CRASH_DUMP_RDDM_DATA] = rddm_tlv_sz;
+
+       for (i = 0; i < ab->qmi.mem_seg_count; i++) {
+               mem_type = ath11k_coredump_get_dump_type(ab->qmi.target_mem[i].type);
+
+               if (mem_type == FW_CRASH_DUMP_NONE)
+                       continue;
+
+               if (mem_type == FW_CRASH_DUMP_TYPE_MAX) {
+                       ath11k_dbg(ab, ATH11K_DBG_PCI,
+                                  "target mem region type %d not supported",
+                                  ab->qmi.target_mem[i].type);
+                       continue;
+               }
+
+               if (!ab->qmi.target_mem[i].anyaddr)
+                       continue;
+
+               dump_seg_sz[mem_type] += ab->qmi.target_mem[i].size;
+       }
+
+       for (i = 0; i < FW_CRASH_DUMP_TYPE_MAX; i++) {
+               if (!dump_seg_sz[i])
+                       continue;
+
+               len += sizeof(*dump_tlv) + dump_seg_sz[i];
+       }
+
+       if (len)
+               len += sizeof(*file_data);
+
+       return len;
+}
+
+static void ath11k_pci_coredump_download(struct ath11k_base *ab)
+{
+       struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
+       struct mhi_controller *mhi_ctrl = ab_pci->mhi_ctrl;
+       struct image_info *rddm_img, *fw_img;
+       struct timespec64 timestamp;
+       int i, len, mem_idx;
+       enum ath11k_fw_crash_dump_type mem_type;
+       struct ath11k_dump_file_data *file_data;
+       struct ath11k_tlv_dump_data *dump_tlv;
+       size_t hdr_len = sizeof(*file_data);
+       void *buf;
+       u32 dump_seg_sz[FW_CRASH_DUMP_TYPE_MAX] = { 0 };
+
+       ath11k_mhi_coredump(mhi_ctrl, false);
+
+       len = ath11k_pci_coredump_calculate_size(ab, dump_seg_sz);
+       if (!len) {
+               ath11k_warn(ab, "No crash dump data found for devcoredump");
+               return;
+       }
+
+       rddm_img = mhi_ctrl->rddm_image;
+       fw_img = mhi_ctrl->fbc_image;
+
+       /* dev_coredumpv() requires vmalloc data */
+       buf = vzalloc(len);
+       if (!buf)
+               return;
+
+       ab->dump_data = buf;
+       ab->ath11k_coredump_len = len;
+       file_data = ab->dump_data;
+       strscpy(file_data->df_magic, "ATH11K-FW-DUMP", sizeof(file_data->df_magic));
+       file_data->len = cpu_to_le32(len);
+       file_data->version = cpu_to_le32(ATH11K_FW_CRASH_DUMP_V2);
+       file_data->chip_id = cpu_to_le32(ab_pci->dev_id);
+       file_data->qrtr_id = cpu_to_le32(ab_pci->ab->qmi.service_ins_id);
+       file_data->bus_id = cpu_to_le32(pci_domain_nr(ab_pci->pdev->bus));
+       guid_gen(&file_data->guid);
+       ktime_get_real_ts64(×tamp);
+       file_data->tv_sec = cpu_to_le64(timestamp.tv_sec);
+       file_data->tv_nsec = cpu_to_le64(timestamp.tv_nsec);
+       buf += hdr_len;
+       dump_tlv = buf;
+       dump_tlv->type = cpu_to_le32(FW_CRASH_DUMP_PAGING_DATA);
+       dump_tlv->tlv_len = cpu_to_le32(dump_seg_sz[FW_CRASH_DUMP_PAGING_DATA]);
+       buf += COREDUMP_TLV_HDR_SIZE;
+
+       /* append all segments together as they are all part of a single contiguous
+        * block of memory
+        */
+       for (i = 0; i < fw_img->entries ; i++) {
+               if (!fw_img->mhi_buf[i].buf)
+                       continue;
+
+               memcpy_fromio(buf, (void const __iomem *)fw_img->mhi_buf[i].buf,
+                             fw_img->mhi_buf[i].len);
+               buf += fw_img->mhi_buf[i].len;
+       }
+
+       dump_tlv = buf;
+       dump_tlv->type = cpu_to_le32(FW_CRASH_DUMP_RDDM_DATA);
+       dump_tlv->tlv_len = cpu_to_le32(dump_seg_sz[FW_CRASH_DUMP_RDDM_DATA]);
+       buf += COREDUMP_TLV_HDR_SIZE;
+
+       /* append all segments together as they are all part of a single contiguous
+        * block of memory
+        */
+       for (i = 0; i < rddm_img->entries; i++) {
+               if (!rddm_img->mhi_buf[i].buf)
+                       continue;
+
+               memcpy_fromio(buf, (void const __iomem *)rddm_img->mhi_buf[i].buf,
+                             rddm_img->mhi_buf[i].len);
+               buf += rddm_img->mhi_buf[i].len;
+       }
+
+       mem_idx = FW_CRASH_DUMP_REMOTE_MEM_DATA;
+       for (; mem_idx < FW_CRASH_DUMP_TYPE_MAX; mem_idx++) {
+               if (mem_idx == FW_CRASH_DUMP_NONE)
+                       continue;
+
+               for (i = 0; i < ab->qmi.mem_seg_count; i++) {
+                       mem_type = ath11k_coredump_get_dump_type
+                                               (ab->qmi.target_mem[i].type);
+
+                       if (mem_type != mem_idx)
+                               continue;
+
+                       if (!ab->qmi.target_mem[i].anyaddr) {
+                               ath11k_dbg(ab, ATH11K_DBG_PCI,
+                                          "Skipping mem region type %d",
+                                          ab->qmi.target_mem[i].type);
+                               continue;
+                       }
+
+                       dump_tlv = buf;
+                       dump_tlv->type = cpu_to_le32(mem_idx);
+                       dump_tlv->tlv_len = cpu_to_le32(dump_seg_sz[mem_idx]);
+                       buf += COREDUMP_TLV_HDR_SIZE;
+
+                       memcpy_fromio(buf, ab->qmi.target_mem[i].iaddr,
+                                     ab->qmi.target_mem[i].size);
+
+                       buf += ab->qmi.target_mem[i].size;
+               }
+       }
+
+       queue_work(ab->workqueue, &ab->dump_work);
+}
+#endif
+
 static int ath11k_pci_power_up(struct ath11k_base *ab)
 {
        struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
        .ce_irq_enable = ath11k_pci_hif_ce_irq_enable,
        .ce_irq_disable = ath11k_pci_hif_ce_irq_disable,
        .get_ce_msi_idx = ath11k_pcic_get_ce_msi_idx,
+#ifdef CONFIG_DEV_COREDUMP
+       .coredump_download = ath11k_pci_coredump_download,
+#endif
 };
 
 static void ath11k_pci_read_hw_version(struct ath11k_base *ab, u32 *major, u32 *minor)
 
        set_bit(ATH11K_FLAG_UNREGISTERING, &ab->dev_flags);
 
+       cancel_work_sync(&ab->reset_work);
+       cancel_work_sync(&ab->dump_work);
        ath11k_core_deinit(ab);
 
 qmi_fail: