| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. |
| * Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved. |
| */ |
| |
| |
| #include <linux/clk.h> |
| #include <linux/clk-provider.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/firmware.h> |
| #include <linux/ktime.h> |
| #include <linux/of_address.h> |
| #include <linux/qcom_scm.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/slab.h> |
| #include <linux/soc/qcom/mdt_loader.h> |
| #include <linux/string.h> |
| #include <linux/version.h> |
| #include <soc/qcom/minidump.h> |
| |
| #include "adreno.h" |
| #include "kgsl_util.h" |
| |
| bool kgsl_regulator_disable_wait(struct regulator *reg, u32 timeout) |
| { |
| ktime_t tout = ktime_add_us(ktime_get(), timeout * 1000); |
| |
| if (IS_ERR_OR_NULL(reg)) |
| return true; |
| |
| regulator_disable(reg); |
| |
| for (;;) { |
| if (!regulator_is_enabled(reg)) |
| return true; |
| |
| if (ktime_compare(ktime_get(), tout) > 0) |
| return (!regulator_is_enabled(reg)); |
| |
| usleep_range((100 >> 2) + 1, 100); |
| } |
| } |
| |
| struct clk *kgsl_of_clk_by_name(struct clk_bulk_data *clks, int count, |
| const char *id) |
| { |
| int i; |
| |
| for (i = 0; clks && i < count; i++) |
| if (!strcmp(clks[i].id, id)) |
| return clks[i].clk; |
| |
| return NULL; |
| } |
| |
| int kgsl_regulator_set_voltage(struct device *dev, |
| struct regulator *reg, u32 voltage) |
| { |
| int ret; |
| |
| if (IS_ERR_OR_NULL(reg)) |
| return 0; |
| |
| ret = regulator_set_voltage(reg, voltage, INT_MAX); |
| if (ret) |
| dev_err(dev, "Regulator set voltage:%d failed:%d\n", voltage, ret); |
| |
| return ret; |
| } |
| |
| int kgsl_clk_set_rate(struct clk_bulk_data *clks, int num_clks, |
| const char *id, unsigned long rate) |
| { |
| struct clk *clk; |
| |
| clk = kgsl_of_clk_by_name(clks, num_clks, id); |
| if (!clk) |
| return -ENODEV; |
| |
| return clk_set_rate(clk, rate); |
| } |
| |
| #if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) |
| int kgsl_scm_gpu_init_regs(struct device *dev, u32 gpu_req) |
| { |
| int ret; |
| |
| if (!gpu_req) |
| return -EOPNOTSUPP; |
| |
| ret = qcom_scm_kgsl_init_regs(gpu_req); |
| if (ret) |
| dev_err(dev, "Scm call for requests:0x%x failed with ret:: %d\n", |
| gpu_req, ret); |
| |
| return ret; |
| } |
| #endif |
| |
| /* |
| * The PASID has stayed consistent across all targets thus far so we are |
| * cautiously optimistic that we can hard code it |
| */ |
| #define GPU_PASID 13 |
| |
| int kgsl_zap_shader_load(struct device *dev, const char *name) |
| { |
| struct device_node *np, *mem_np; |
| const struct firmware *fw; |
| void *mem_region = NULL; |
| phys_addr_t mem_phys; |
| struct resource res; |
| ssize_t mem_size; |
| int ret; |
| |
| np = of_get_child_by_name(dev->of_node, "zap-shader"); |
| if (!np) { |
| dev_err(dev, "zap-shader node not found. Please update the device tree\n"); |
| return -ENODEV; |
| } |
| |
| mem_np = of_parse_phandle(np, "memory-region", 0); |
| of_node_put(np); |
| if (!mem_np) { |
| dev_err(dev, "Couldn't parse the mem-region from the zap-shader node\n"); |
| return -EINVAL; |
| } |
| |
| ret = of_address_to_resource(mem_np, 0, &res); |
| of_node_put(mem_np); |
| if (ret) |
| return ret; |
| |
| ret = request_firmware(&fw, name, dev); |
| if (ret) { |
| dev_err(dev, "Couldn't load the firmware %s\n", name); |
| return ret; |
| } |
| |
| mem_size = qcom_mdt_get_size(fw); |
| if (mem_size < 0) { |
| ret = mem_size; |
| goto out; |
| } |
| |
| if (mem_size > resource_size(&res)) { |
| ret = -E2BIG; |
| goto out; |
| } |
| |
| mem_phys = res.start; |
| |
| mem_region = memremap(mem_phys, mem_size, MEMREMAP_WC); |
| if (!mem_region) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| ret = qcom_mdt_load(dev, fw, name, GPU_PASID, mem_region, |
| mem_phys, mem_size, NULL); |
| if (ret) { |
| dev_err(dev, "Error %d while loading the MDT\n", ret); |
| goto out; |
| } |
| |
| ret = qcom_scm_pas_auth_and_reset(GPU_PASID); |
| |
| out: |
| if (mem_region) |
| memunmap(mem_region); |
| |
| release_firmware(fw); |
| return ret; |
| } |
| |
| int kgsl_zap_shader_unload(struct device *dev) |
| { |
| int ret; |
| |
| ret = qcom_scm_pas_shutdown_retry(GPU_PASID); |
| if (ret) |
| dev_err(dev, "Error %d while PAS shutdown\n", ret); |
| |
| return ret; |
| } |
| |
| int kgsl_hwlock(struct cpu_gpu_lock *lock) |
| { |
| unsigned long timeout = jiffies + msecs_to_jiffies(1000); |
| |
| /* Indicate that the CPU wants the lock */ |
| lock->cpu_req = 1; |
| |
| /* post the request */ |
| wmb(); |
| |
| /* Wait for our turn */ |
| lock->turn = 0; |
| |
| /* Finish all memory transactions before moving on */ |
| mb(); |
| |
| /* |
| * Spin here while GPU ucode holds the lock, lock->gpu_req will |
| * be set to 0 after GPU ucode releases the lock. Maximum wait time |
| * is 1 second and this should be enough for GPU to release the lock. |
| */ |
| while (lock->gpu_req && lock->turn == 0) { |
| cpu_relax(); |
| /* Get the latest updates from GPU */ |
| rmb(); |
| |
| if (time_after(jiffies, timeout)) |
| break; |
| } |
| |
| if (lock->gpu_req && lock->turn == 0) |
| return -EBUSY; |
| |
| return 0; |
| } |
| |
| void kgsl_hwunlock(struct cpu_gpu_lock *lock) |
| { |
| /* Make sure all writes are done before releasing the lock */ |
| wmb(); |
| lock->cpu_req = 0; |
| } |
| |
| #if IS_ENABLED(CONFIG_QCOM_VA_MINIDUMP) |
| void kgsl_add_to_minidump(char *name, u64 virt_addr, u64 phy_addr, size_t size) |
| { |
| struct md_region md_entry = {0}; |
| int ret; |
| |
| if (!msm_minidump_enabled()) |
| return; |
| |
| scnprintf(md_entry.name, sizeof(md_entry.name), name); |
| md_entry.virt_addr = virt_addr; |
| md_entry.phys_addr = phy_addr; |
| md_entry.size = size; |
| ret = msm_minidump_add_region(&md_entry); |
| if (ret < 0 && ret != -EEXIST) |
| pr_err("kgsl: Failed to register %s with minidump:%d\n", name, ret); |
| |
| } |
| |
| void kgsl_remove_from_minidump(char *name, u64 virt_addr, u64 phy_addr, size_t size) |
| { |
| struct md_region md_entry = {0}; |
| int ret; |
| |
| if (!msm_minidump_enabled()) |
| return; |
| |
| scnprintf(md_entry.name, sizeof(md_entry.name), name); |
| md_entry.virt_addr = virt_addr; |
| md_entry.phys_addr = phy_addr; |
| md_entry.size = size; |
| ret = msm_minidump_remove_region(&md_entry); |
| if (ret < 0 && ret != -ENOENT) |
| pr_err("kgsl: Failed to remove %s from minidump\n", name); |
| } |
| |
| int kgsl_add_va_to_minidump(struct device *dev, const char *name, void *ptr, |
| size_t size) |
| { |
| struct va_md_entry entry = {0}; |
| int ret; |
| |
| scnprintf(entry.owner, sizeof(entry.owner), name); |
| entry.vaddr = (u64)(ptr); |
| entry.size = size; |
| ret = qcom_va_md_add_region(&entry); |
| if (ret < 0) |
| dev_err(dev, "Failed to register %s with va_minidump: %d\n", name, |
| ret); |
| |
| return ret; |
| } |
| |
| static int kgsl_add_driver_data_to_va_minidump(struct kgsl_device *device) |
| { |
| int ret; |
| char name[MAX_VA_MINIDUMP_STR_LEN]; |
| struct kgsl_pagetable *pt; |
| struct adreno_context *ctxt; |
| struct kgsl_process_private *p; |
| struct adreno_device *adreno_dev = ADRENO_DEVICE(device); |
| |
| ret = kgsl_add_va_to_minidump(device->dev, KGSL_DRIVER, |
| (void *)(&kgsl_driver), sizeof(struct kgsl_driver)); |
| if (ret) |
| return ret; |
| |
| /* hwsched path may not have scratch entry */ |
| if (device->scratch) { |
| ret = kgsl_add_va_to_minidump(device->dev, KGSL_SCRATCH_ENTRY, |
| device->scratch->hostptr, device->scratch->size); |
| if (ret) |
| return ret; |
| } |
| |
| ret = kgsl_add_va_to_minidump(device->dev, KGSL_MEMSTORE_ENTRY, |
| device->memstore->hostptr, device->memstore->size); |
| if (ret) |
| return ret; |
| |
| spin_lock(&adreno_dev->active_list_lock); |
| list_for_each_entry(ctxt, &adreno_dev->active_list, active_node) { |
| snprintf(name, sizeof(name), KGSL_ADRENO_CTX_ENTRY"_%d", ctxt->base.id); |
| ret = kgsl_add_va_to_minidump(device->dev, name, |
| (void *)(ctxt), sizeof(struct adreno_context)); |
| if (ret) |
| break; |
| } |
| spin_unlock(&adreno_dev->active_list_lock); |
| |
| read_lock(&kgsl_driver.proclist_lock); |
| list_for_each_entry(p, &kgsl_driver.process_list, list) { |
| snprintf(name, sizeof(name), KGSL_PROC_PRIV_ENTRY "_%d", pid_nr(p->pid)); |
| ret = kgsl_add_va_to_minidump(device->dev, name, |
| (void *)(p), sizeof(struct kgsl_process_private)); |
| if (ret) |
| break; |
| } |
| read_unlock(&kgsl_driver.proclist_lock); |
| |
| spin_lock(&kgsl_driver.ptlock); |
| list_for_each_entry(pt, &kgsl_driver.pagetable_list, list) { |
| snprintf(name, sizeof(name), KGSL_PGTABLE_ENTRY"_%d", pt->name); |
| ret = kgsl_add_va_to_minidump(device->dev, name, |
| (void *)(pt), sizeof(struct kgsl_pagetable)); |
| if (ret) |
| break; |
| } |
| spin_unlock(&kgsl_driver.ptlock); |
| |
| return ret; |
| } |
| |
| static int kgsl_va_minidump_callback(struct notifier_block *nb, |
| unsigned long action, void *unused) |
| { |
| struct adreno_device *adreno_dev = ADRENO_DEVICE(kgsl_driver.devp[0]); |
| const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); |
| |
| if (kgsl_add_driver_data_to_va_minidump(kgsl_driver.devp[0])) |
| return NOTIFY_BAD; |
| |
| if (gpudev->add_to_va_minidump(adreno_dev)) |
| return NOTIFY_BAD; |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block kgsl_va_minidump_nb = { |
| .priority = INT_MAX, |
| .notifier_call = kgsl_va_minidump_callback, |
| }; |
| |
| void kgsl_qcom_va_md_register(struct kgsl_device *device) |
| { |
| int ret; |
| |
| if (!qcom_va_md_enabled()) |
| return; |
| |
| ret = qcom_va_md_register("KGSL", &kgsl_va_minidump_nb); |
| if (ret) |
| dev_err(device->dev, "Failed to register notifier with va_minidump: %d\n", ret); |
| } |
| |
| void kgsl_qcom_va_md_unregister(struct kgsl_device *device) |
| { |
| int ret; |
| |
| if (!qcom_va_md_enabled()) |
| return; |
| |
| ret = qcom_va_md_unregister("KGSL", &kgsl_va_minidump_nb); |
| if (ret) |
| dev_err(device->dev, "Failed to unregister notifier with va_minidump: %d\n", ret); |
| } |
| #endif |