git-subtree-dir: qcom/opensource/graphics-kernel git-subtree-mainline:992813d9c1
git-subtree-split:b4fdc4c042
Change-Id: repo: https://git.codelinaro.org/clo/la/platform/vendor/qcom/opensource/graphics-kernel tag: GRAPHICS.LA.14.0.r1-07700-lanai.0
3438 lines
88 KiB
C
3438 lines
88 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2022-2024, Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <dt-bindings/regulator/qcom,rpmh-regulator-levels.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/component.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-map-ops.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/interconnect.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/qcom-iommu-util.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/mailbox/qmp.h>
|
|
#include <soc/qcom/cmd-db.h>
|
|
|
|
#include "adreno.h"
|
|
#include "adreno_gen7.h"
|
|
#include "adreno_trace.h"
|
|
#include "kgsl_bus.h"
|
|
#include "kgsl_device.h"
|
|
#include "kgsl_trace.h"
|
|
#include "kgsl_util.h"
|
|
|
|
static struct gmu_vma_entry gen7_gmu_vma[] = {
|
|
[GMU_ITCM] = {
|
|
.start = 0x00000000,
|
|
.size = SZ_16K,
|
|
},
|
|
[GMU_CACHE] = {
|
|
.start = SZ_16K,
|
|
.size = (SZ_16M - SZ_16K),
|
|
.next_va = SZ_16K,
|
|
},
|
|
[GMU_DTCM] = {
|
|
.start = SZ_256M + SZ_16K,
|
|
.size = SZ_16K,
|
|
},
|
|
[GMU_DCACHE] = {
|
|
.start = 0x0,
|
|
.size = 0x0,
|
|
},
|
|
[GMU_NONCACHED_KERNEL] = {
|
|
.start = 0x60000000,
|
|
.size = SZ_512M,
|
|
.next_va = 0x60000000,
|
|
},
|
|
[GMU_NONCACHED_KERNEL_EXTENDED] = {
|
|
.start = 0xc0000000,
|
|
.size = SZ_512M,
|
|
.next_va = 0xc0000000,
|
|
},
|
|
};
|
|
|
|
static ssize_t log_stream_enable_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct gen7_gmu_device *gmu = container_of(kobj, struct gen7_gmu_device, log_kobj);
|
|
bool val;
|
|
int ret;
|
|
|
|
ret = kstrtobool(buf, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
gmu->log_stream_enable = val;
|
|
adreno_mark_for_coldboot(gen7_gmu_to_adreno(gmu));
|
|
return count;
|
|
}
|
|
|
|
static ssize_t log_stream_enable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct gen7_gmu_device *gmu = container_of(kobj, struct gen7_gmu_device, log_kobj);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%d\n", gmu->log_stream_enable);
|
|
}
|
|
|
|
static ssize_t log_group_mask_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct gen7_gmu_device *gmu = container_of(kobj, struct gen7_gmu_device, log_kobj);
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = kstrtou32(buf, 0, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
gmu->log_group_mask = val;
|
|
adreno_mark_for_coldboot(gen7_gmu_to_adreno(gmu));
|
|
return count;
|
|
}
|
|
|
|
static ssize_t log_group_mask_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct gen7_gmu_device *gmu = container_of(kobj, struct gen7_gmu_device, log_kobj);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%x\n", gmu->log_group_mask);
|
|
}
|
|
|
|
static struct kobj_attribute log_stream_enable_attr =
|
|
__ATTR(log_stream_enable, 0644, log_stream_enable_show, log_stream_enable_store);
|
|
|
|
static struct kobj_attribute log_group_mask_attr =
|
|
__ATTR(log_group_mask, 0644, log_group_mask_show, log_group_mask_store);
|
|
|
|
static struct attribute *log_attrs[] = {
|
|
&log_stream_enable_attr.attr,
|
|
&log_group_mask_attr.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(log);
|
|
|
|
static struct kobj_type log_kobj_type = {
|
|
.sysfs_ops = &kobj_sysfs_ops,
|
|
.default_groups = log_groups,
|
|
};
|
|
|
|
static ssize_t stats_enable_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct gen7_gmu_device *gmu = container_of(kobj, struct gen7_gmu_device, stats_kobj);
|
|
bool val;
|
|
int ret;
|
|
|
|
ret = kstrtobool(buf, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
gmu->stats_enable = val;
|
|
adreno_mark_for_coldboot(gen7_gmu_to_adreno(gmu));
|
|
return count;
|
|
}
|
|
|
|
static ssize_t stats_enable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct gen7_gmu_device *gmu = container_of(kobj, struct gen7_gmu_device, stats_kobj);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%d\n", gmu->stats_enable);
|
|
}
|
|
|
|
static ssize_t stats_mask_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct gen7_gmu_device *gmu = container_of(kobj, struct gen7_gmu_device, stats_kobj);
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = kstrtou32(buf, 0, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
gmu->stats_mask = val;
|
|
adreno_mark_for_coldboot(gen7_gmu_to_adreno(gmu));
|
|
return count;
|
|
}
|
|
|
|
static ssize_t stats_mask_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct gen7_gmu_device *gmu = container_of(kobj, struct gen7_gmu_device, stats_kobj);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%x\n", gmu->stats_mask);
|
|
}
|
|
|
|
static ssize_t stats_interval_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct gen7_gmu_device *gmu = container_of(kobj, struct gen7_gmu_device, stats_kobj);
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = kstrtou32(buf, 0, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
gmu->stats_interval = val;
|
|
adreno_mark_for_coldboot(gen7_gmu_to_adreno(gmu));
|
|
return count;
|
|
}
|
|
|
|
static ssize_t stats_interval_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct gen7_gmu_device *gmu = container_of(kobj, struct gen7_gmu_device, stats_kobj);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%x\n", gmu->stats_interval);
|
|
}
|
|
|
|
static struct kobj_attribute stats_enable_attr =
|
|
__ATTR(stats_enable, 0644, stats_enable_show, stats_enable_store);
|
|
|
|
static struct kobj_attribute stats_mask_attr =
|
|
__ATTR(stats_mask, 0644, stats_mask_show, stats_mask_store);
|
|
|
|
static struct kobj_attribute stats_interval_attr =
|
|
__ATTR(stats_interval, 0644, stats_interval_show, stats_interval_store);
|
|
|
|
static struct attribute *stats_attrs[] = {
|
|
&stats_enable_attr.attr,
|
|
&stats_mask_attr.attr,
|
|
&stats_interval_attr.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(stats);
|
|
|
|
static struct kobj_type stats_kobj_type = {
|
|
.sysfs_ops = &kobj_sysfs_ops,
|
|
.default_groups = stats_groups,
|
|
};
|
|
|
|
static int gen7_timed_poll_check_rscc(struct gen7_gmu_device *gmu,
|
|
unsigned int offset, unsigned int expected_ret,
|
|
unsigned int timeout, unsigned int mask)
|
|
{
|
|
u32 value;
|
|
|
|
return readl_poll_timeout(gmu->rscc_virt + (offset << 2), value,
|
|
(value & mask) == expected_ret, 100, timeout * 1000);
|
|
}
|
|
|
|
struct gen7_gmu_device *to_gen7_gmu(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_device *gen7_dev = container_of(adreno_dev,
|
|
struct gen7_device, adreno_dev);
|
|
|
|
return &gen7_dev->gmu;
|
|
}
|
|
|
|
struct adreno_device *gen7_gmu_to_adreno(struct gen7_gmu_device *gmu)
|
|
{
|
|
struct gen7_device *gen7_dev =
|
|
container_of(gmu, struct gen7_device, gmu);
|
|
|
|
return &gen7_dev->adreno_dev;
|
|
}
|
|
|
|
#define RSC_CMD_OFFSET 2
|
|
|
|
static void _regwrite(void __iomem *regbase,
|
|
unsigned int offsetwords, unsigned int value)
|
|
{
|
|
void __iomem *reg;
|
|
|
|
reg = regbase + (offsetwords << 2);
|
|
__raw_writel(value, reg);
|
|
}
|
|
|
|
void gen7_load_rsc_ucode(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
void __iomem *rscc = gmu->rscc_virt;
|
|
unsigned int seq_offset = GEN7_RSCC_SEQ_MEM_0_DRV0;
|
|
|
|
/* Disable SDE clock gating */
|
|
_regwrite(rscc, GEN7_GPU_RSCC_RSC_STATUS0_DRV0, BIT(24));
|
|
|
|
/* Setup RSC PDC handshake for sleep and wakeup */
|
|
_regwrite(rscc, GEN7_RSCC_PDC_SLAVE_ID_DRV0, 1);
|
|
_regwrite(rscc, GEN7_RSCC_HIDDEN_TCS_CMD0_DATA, 0);
|
|
_regwrite(rscc, GEN7_RSCC_HIDDEN_TCS_CMD0_ADDR, 0);
|
|
_regwrite(rscc, GEN7_RSCC_HIDDEN_TCS_CMD0_DATA + RSC_CMD_OFFSET, 0);
|
|
_regwrite(rscc, GEN7_RSCC_HIDDEN_TCS_CMD0_ADDR + RSC_CMD_OFFSET, 0);
|
|
_regwrite(rscc, GEN7_RSCC_HIDDEN_TCS_CMD0_DATA + RSC_CMD_OFFSET * 2,
|
|
adreno_is_gen7_2_x_family(adreno_dev) ? 0x80000021 : 0x80000000);
|
|
_regwrite(rscc, GEN7_RSCC_HIDDEN_TCS_CMD0_ADDR + RSC_CMD_OFFSET * 2, 0);
|
|
_regwrite(rscc, GEN7_RSCC_OVERRIDE_START_ADDR, 0);
|
|
_regwrite(rscc, GEN7_RSCC_PDC_SEQ_START_ADDR, 0x4520);
|
|
_regwrite(rscc, GEN7_RSCC_PDC_MATCH_VALUE_LO, 0x4510);
|
|
_regwrite(rscc, GEN7_RSCC_PDC_MATCH_VALUE_HI, 0x4514);
|
|
|
|
if (adreno_is_gen7_2_x_family(adreno_dev))
|
|
seq_offset = GEN7_2_0_RSCC_SEQ_MEM_0_DRV0;
|
|
|
|
/* Load RSC sequencer uCode for sleep and wakeup */
|
|
_regwrite(rscc, seq_offset, 0xeaaae5a0);
|
|
_regwrite(rscc, seq_offset + 1, 0xe1a1ebab);
|
|
_regwrite(rscc, seq_offset + 2, 0xa2e0a581);
|
|
_regwrite(rscc, seq_offset + 3, 0xecac82e2);
|
|
_regwrite(rscc, seq_offset + 4, 0x0020edad);
|
|
}
|
|
|
|
int gen7_load_pdc_ucode(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct resource *res_cfg;
|
|
void __iomem *cfg = NULL;
|
|
|
|
res_cfg = platform_get_resource_byname(gmu->pdev, IORESOURCE_MEM,
|
|
"gmu_pdc");
|
|
if (res_cfg)
|
|
cfg = ioremap(res_cfg->start, resource_size(res_cfg));
|
|
|
|
if (!cfg) {
|
|
dev_err(&gmu->pdev->dev, "Failed to map PDC CFG\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Setup GPU PDC */
|
|
_regwrite(cfg, GEN7_PDC_GPU_SEQ_START_ADDR, 0);
|
|
_regwrite(cfg, GEN7_PDC_GPU_ENABLE_PDC, 0x80000001);
|
|
|
|
iounmap(cfg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Configure and enable GMU low power mode */
|
|
static void gen7_gmu_power_config(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
|
|
/* Disable GMU WB/RB buffer and caches at boot */
|
|
gmu_core_regwrite(device, GEN7_GMU_SYS_BUS_CONFIG, 0x1);
|
|
gmu_core_regwrite(device, GEN7_GMU_ICACHE_CONFIG, 0x1);
|
|
gmu_core_regwrite(device, GEN7_GMU_DCACHE_CONFIG, 0x1);
|
|
}
|
|
|
|
static void gmu_ao_sync_event(struct adreno_device *adreno_dev)
|
|
{
|
|
const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
|
|
unsigned long flags;
|
|
u64 ticks;
|
|
|
|
/*
|
|
* Get the GMU always on ticks and log it in a trace message. This
|
|
* will be used to map GMU ticks to ftrace time. Do this in atomic
|
|
* context to ensure nothing happens between reading the always
|
|
* on ticks and doing the trace.
|
|
*/
|
|
|
|
local_irq_save(flags);
|
|
|
|
ticks = gpudev->read_alwayson(adreno_dev);
|
|
|
|
trace_gmu_ao_sync(ticks);
|
|
|
|
local_irq_restore(flags);
|
|
}
|
|
|
|
int gen7_gmu_device_start(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
|
|
gmu_core_reset_trace_header(&gmu->trace);
|
|
|
|
gmu_ao_sync_event(adreno_dev);
|
|
|
|
/* Bring GMU out of reset */
|
|
gmu_core_regwrite(device, GEN7_GMU_CM3_SYSRESET, 0);
|
|
|
|
/* Make sure the write is posted before moving ahead */
|
|
wmb();
|
|
|
|
if (gmu_core_timed_poll_check(device, GEN7_GMU_CM3_FW_INIT_RESULT,
|
|
BIT(8), 100, GENMASK(8, 0))) {
|
|
dev_err(&gmu->pdev->dev, "GMU failed to come out of reset\n");
|
|
gmu_core_fault_snapshot(device);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* gen7_gmu_hfi_start() - Write registers and start HFI.
|
|
* @device: Pointer to KGSL device
|
|
*/
|
|
int gen7_gmu_hfi_start(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_HFI_CTRL_INIT, 1);
|
|
|
|
if (gmu_core_timed_poll_check(device, GEN7_GMU_HFI_CTRL_STATUS,
|
|
BIT(0), 100, BIT(0))) {
|
|
dev_err(&gmu->pdev->dev, "GMU HFI init failed\n");
|
|
gmu_core_fault_snapshot(device);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gen7_rscc_wakeup_sequence(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct device *dev = &gmu->pdev->dev;
|
|
|
|
/* Skip wakeup sequence if we didn't do the sleep sequence */
|
|
if (!test_bit(GMU_PRIV_RSCC_SLEEP_DONE, &gmu->flags))
|
|
return 0;
|
|
|
|
/* RSC wake sequence */
|
|
gmu_core_regwrite(device, GEN7_GMU_RSCC_CONTROL_REQ, BIT(1));
|
|
|
|
/* Write request before polling */
|
|
wmb();
|
|
|
|
if (gmu_core_timed_poll_check(device, GEN7_GMU_RSCC_CONTROL_ACK,
|
|
BIT(1), 100, BIT(1))) {
|
|
dev_err(dev, "Failed to do GPU RSC power on\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
if (gen7_timed_poll_check_rscc(gmu, GEN7_RSCC_SEQ_BUSY_DRV0,
|
|
0x0, 100, UINT_MAX)) {
|
|
dev_err(dev, "GPU RSC sequence stuck in waking up GPU\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_RSCC_CONTROL_REQ, 0);
|
|
|
|
clear_bit(GMU_PRIV_RSCC_SLEEP_DONE, &gmu->flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gen7_rscc_sleep_sequence(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret;
|
|
|
|
if (!test_bit(GMU_PRIV_FIRST_BOOT_DONE, &gmu->flags))
|
|
return 0;
|
|
|
|
if (test_bit(GMU_PRIV_RSCC_SLEEP_DONE, &gmu->flags))
|
|
return 0;
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_CM3_SYSRESET, 1);
|
|
/* Make sure M3 is in reset before going on */
|
|
wmb();
|
|
|
|
gmu_core_regread(device, GEN7_GMU_GENERAL_9, &gmu->log_wptr_retention);
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_RSCC_CONTROL_REQ, BIT(0));
|
|
/* Make sure the request completes before continuing */
|
|
wmb();
|
|
|
|
ret = gen7_timed_poll_check_rscc(gmu, GEN7_GPU_RSCC_RSC_STATUS0_DRV0,
|
|
BIT(16), 100, BIT(16));
|
|
if (ret) {
|
|
dev_err(&gmu->pdev->dev, "GPU RSC power off fail\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_RSCC_CONTROL_REQ, 0);
|
|
|
|
set_bit(GMU_PRIV_RSCC_SLEEP_DONE, &gmu->flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct kgsl_memdesc *find_gmu_memdesc(struct gen7_gmu_device *gmu,
|
|
u32 addr, u32 size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < gmu->global_entries; i++) {
|
|
struct kgsl_memdesc *md = &gmu->gmu_globals[i];
|
|
|
|
if ((addr >= md->gmuaddr) &&
|
|
(((addr + size) <= (md->gmuaddr + md->size))))
|
|
return md;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int find_vma_block(struct gen7_gmu_device *gmu, u32 addr, u32 size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < GMU_MEM_TYPE_MAX; i++) {
|
|
struct gmu_vma_entry *vma = &gmu->vma[i];
|
|
|
|
if ((addr >= vma->start) &&
|
|
((addr + size) <= (vma->start + vma->size)))
|
|
return i;
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
static void load_tcm(struct adreno_device *adreno_dev, const u8 *src,
|
|
u32 tcm_start, u32 base, const struct gmu_block_header *blk)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
u32 tcm_offset = tcm_start + ((blk->addr - base)/sizeof(u32));
|
|
|
|
kgsl_regmap_bulk_write(&device->regmap, tcm_offset, src,
|
|
blk->size >> 2);
|
|
}
|
|
|
|
int gen7_gmu_load_fw(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
const u8 *fw = (const u8 *)gmu->fw_image->data;
|
|
|
|
while (fw < gmu->fw_image->data + gmu->fw_image->size) {
|
|
const struct gmu_block_header *blk =
|
|
(const struct gmu_block_header *)fw;
|
|
int id;
|
|
|
|
fw += sizeof(*blk);
|
|
|
|
/* Don't deal with zero size blocks */
|
|
if (blk->size == 0)
|
|
continue;
|
|
|
|
id = find_vma_block(gmu, blk->addr, blk->size);
|
|
|
|
if (id < 0) {
|
|
dev_err(&gmu->pdev->dev,
|
|
"Unknown block in GMU FW addr:0x%x size:0x%x\n",
|
|
blk->addr, blk->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (id == GMU_ITCM) {
|
|
load_tcm(adreno_dev, fw,
|
|
GEN7_GMU_CM3_ITCM_START,
|
|
gmu->vma[GMU_ITCM].start, blk);
|
|
} else if (id == GMU_DTCM) {
|
|
load_tcm(adreno_dev, fw,
|
|
GEN7_GMU_CM3_DTCM_START,
|
|
gmu->vma[GMU_DTCM].start, blk);
|
|
} else {
|
|
/* The firmware block for memory needs to be copied on first boot only */
|
|
if (!test_bit(GMU_PRIV_FIRST_BOOT_DONE, &gmu->flags)) {
|
|
struct kgsl_memdesc *md =
|
|
find_gmu_memdesc(gmu, blk->addr, blk->size);
|
|
|
|
if (!md) {
|
|
dev_err(&gmu->pdev->dev,
|
|
"No backing memory for GMU FW block addr:0x%x size:0x%x\n",
|
|
blk->addr, blk->size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(md->hostptr + (blk->addr - md->gmuaddr), fw,
|
|
blk->size);
|
|
}
|
|
}
|
|
|
|
fw += blk->size;
|
|
}
|
|
|
|
/* Proceed only after the FW is written */
|
|
wmb();
|
|
return 0;
|
|
}
|
|
|
|
static const char *oob_to_str(enum oob_request req)
|
|
{
|
|
switch (req) {
|
|
case oob_gpu:
|
|
return "oob_gpu";
|
|
case oob_perfcntr:
|
|
return "oob_perfcntr";
|
|
case oob_boot_slumber:
|
|
return "oob_boot_slumber";
|
|
case oob_dcvs:
|
|
return "oob_dcvs";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static void trigger_reset_recovery(struct adreno_device *adreno_dev,
|
|
enum oob_request req)
|
|
{
|
|
/*
|
|
* Trigger recovery for perfcounter oob only since only
|
|
* perfcounter oob can happen alongside an actively rendering gpu.
|
|
*/
|
|
if (req != oob_perfcntr)
|
|
return;
|
|
|
|
if (adreno_dev->dispatch_ops && adreno_dev->dispatch_ops->fault)
|
|
adreno_dev->dispatch_ops->fault(adreno_dev,
|
|
ADRENO_GMU_FAULT_SKIP_SNAPSHOT);
|
|
}
|
|
|
|
int gen7_gmu_oob_set(struct kgsl_device *device,
|
|
enum oob_request req)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret = 0;
|
|
int set, check;
|
|
|
|
if (req == oob_perfcntr && gmu->num_oob_perfcntr++)
|
|
return 0;
|
|
|
|
if (req >= oob_boot_slumber) {
|
|
dev_err(&gmu->pdev->dev,
|
|
"Unsupported OOB request %s\n",
|
|
oob_to_str(req));
|
|
return -EINVAL;
|
|
}
|
|
|
|
set = BIT(30 - req * 2);
|
|
check = BIT(31 - req);
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_HOST2GMU_INTR_SET, set);
|
|
|
|
if (gmu_core_timed_poll_check(device, GEN7_GMU_GMU2HOST_INTR_INFO, check,
|
|
100, check)) {
|
|
if (req == oob_perfcntr)
|
|
gmu->num_oob_perfcntr--;
|
|
gmu_core_fault_snapshot(device);
|
|
ret = -ETIMEDOUT;
|
|
WARN(1, "OOB request %s timed out\n", oob_to_str(req));
|
|
trigger_reset_recovery(adreno_dev, req);
|
|
}
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_GMU2HOST_INTR_CLR, check);
|
|
|
|
trace_kgsl_gmu_oob_set(set);
|
|
return ret;
|
|
}
|
|
|
|
void gen7_gmu_oob_clear(struct kgsl_device *device,
|
|
enum oob_request req)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int clear = BIT(31 - req * 2);
|
|
|
|
if (req == oob_perfcntr && --gmu->num_oob_perfcntr)
|
|
return;
|
|
|
|
if (req >= oob_boot_slumber) {
|
|
dev_err(&gmu->pdev->dev, "Unsupported OOB clear %s\n",
|
|
oob_to_str(req));
|
|
return;
|
|
}
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_HOST2GMU_INTR_SET, clear);
|
|
trace_kgsl_gmu_oob_clear(clear);
|
|
}
|
|
|
|
void gen7_gmu_irq_enable(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct gen7_hfi *hfi = &gmu->hfi;
|
|
|
|
/* Clear pending IRQs and Unmask needed IRQs */
|
|
gmu_core_regwrite(device, GEN7_GMU_GMU2HOST_INTR_CLR, UINT_MAX);
|
|
gmu_core_regwrite(device, GEN7_GMU_AO_HOST_INTERRUPT_CLR, UINT_MAX);
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_GMU2HOST_INTR_MASK,
|
|
(unsigned int)~HFI_IRQ_MASK);
|
|
gmu_core_regwrite(device, GEN7_GMU_AO_HOST_INTERRUPT_MASK,
|
|
(unsigned int)~GMU_AO_INT_MASK);
|
|
|
|
/* Enable all IRQs on host */
|
|
enable_irq(hfi->irq);
|
|
enable_irq(gmu->irq);
|
|
}
|
|
|
|
void gen7_gmu_irq_disable(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct gen7_hfi *hfi = &gmu->hfi;
|
|
|
|
/* Disable all IRQs on host */
|
|
disable_irq(gmu->irq);
|
|
disable_irq(hfi->irq);
|
|
|
|
/* Mask all IRQs and clear pending IRQs */
|
|
gmu_core_regwrite(device, GEN7_GMU_GMU2HOST_INTR_MASK, UINT_MAX);
|
|
gmu_core_regwrite(device, GEN7_GMU_AO_HOST_INTERRUPT_MASK, UINT_MAX);
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_GMU2HOST_INTR_CLR, UINT_MAX);
|
|
gmu_core_regwrite(device, GEN7_GMU_AO_HOST_INTERRUPT_CLR, UINT_MAX);
|
|
}
|
|
|
|
static int gen7_gmu_hfi_start_msg(struct adreno_device *adreno_dev)
|
|
{
|
|
struct hfi_start_cmd req;
|
|
int ret;
|
|
|
|
ret = CMD_MSG_HDR(req, H2F_MSG_START);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return gen7_hfi_send_generic_req(adreno_dev, &req, sizeof(req));
|
|
}
|
|
|
|
static u32 gen7_rscc_tcsm_drv0_status_reglist[] = {
|
|
GEN7_RSCC_TCS0_DRV0_STATUS,
|
|
GEN7_RSCC_TCS1_DRV0_STATUS,
|
|
GEN7_RSCC_TCS2_DRV0_STATUS,
|
|
GEN7_RSCC_TCS3_DRV0_STATUS,
|
|
GEN7_RSCC_TCS4_DRV0_STATUS,
|
|
GEN7_RSCC_TCS5_DRV0_STATUS,
|
|
GEN7_RSCC_TCS6_DRV0_STATUS,
|
|
GEN7_RSCC_TCS7_DRV0_STATUS,
|
|
GEN7_RSCC_TCS8_DRV0_STATUS,
|
|
GEN7_RSCC_TCS9_DRV0_STATUS,
|
|
};
|
|
|
|
static u32 gen7_2_0_rscc_tcsm_drv0_status_reglist[] = {
|
|
GEN7_2_0_RSCC_TCS0_DRV0_STATUS,
|
|
GEN7_2_0_RSCC_TCS1_DRV0_STATUS,
|
|
GEN7_2_0_RSCC_TCS2_DRV0_STATUS,
|
|
GEN7_2_0_RSCC_TCS3_DRV0_STATUS,
|
|
GEN7_2_0_RSCC_TCS4_DRV0_STATUS,
|
|
GEN7_2_0_RSCC_TCS5_DRV0_STATUS,
|
|
GEN7_2_0_RSCC_TCS6_DRV0_STATUS,
|
|
GEN7_2_0_RSCC_TCS7_DRV0_STATUS,
|
|
GEN7_2_0_RSCC_TCS8_DRV0_STATUS,
|
|
GEN7_2_0_RSCC_TCS9_DRV0_STATUS,
|
|
};
|
|
|
|
static int gen7_complete_rpmh_votes(struct gen7_gmu_device *gmu,
|
|
u32 timeout)
|
|
{
|
|
struct adreno_device *adreno_dev = gen7_gmu_to_adreno(gmu);
|
|
int i, ret = 0;
|
|
|
|
if (adreno_is_gen7_2_x_family(adreno_dev)) {
|
|
for (i = 0; i < ARRAY_SIZE(gen7_2_0_rscc_tcsm_drv0_status_reglist); i++)
|
|
ret |= gen7_timed_poll_check_rscc(gmu,
|
|
gen7_2_0_rscc_tcsm_drv0_status_reglist[i], BIT(0), timeout,
|
|
BIT(0));
|
|
} else {
|
|
for (i = 0; i < ARRAY_SIZE(gen7_rscc_tcsm_drv0_status_reglist); i++)
|
|
ret |= gen7_timed_poll_check_rscc(gmu,
|
|
gen7_rscc_tcsm_drv0_status_reglist[i], BIT(0), timeout,
|
|
BIT(0));
|
|
}
|
|
|
|
if (ret)
|
|
dev_err(&gmu->pdev->dev, "RPMH votes timedout: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define GX_GDSC_POWER_OFF BIT(0)
|
|
#define GX_CLK_OFF BIT(1)
|
|
#define is_on(val) (!(val & (GX_GDSC_POWER_OFF | GX_CLK_OFF)))
|
|
|
|
bool gen7_gmu_gx_is_on(struct adreno_device *adreno_dev)
|
|
{
|
|
unsigned int val;
|
|
|
|
gmu_core_regread(KGSL_DEVICE(adreno_dev),
|
|
GEN7_GMU_GFX_PWR_CLK_STATUS, &val);
|
|
return is_on(val);
|
|
}
|
|
|
|
static const char *idle_level_name(int level)
|
|
{
|
|
if (level == GPU_HW_ACTIVE)
|
|
return "GPU_HW_ACTIVE";
|
|
else if (level == GPU_HW_IFPC)
|
|
return "GPU_HW_IFPC";
|
|
|
|
return "(Unknown)";
|
|
}
|
|
|
|
int gen7_gmu_wait_for_lowest_idle(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
|
|
u32 reg, reg1, reg2, reg3, reg4;
|
|
unsigned long t;
|
|
u64 ts1, ts2;
|
|
|
|
ts1 = gpudev->read_alwayson(adreno_dev);
|
|
|
|
t = jiffies + msecs_to_jiffies(100);
|
|
do {
|
|
gmu_core_regread(device,
|
|
GEN7_GPU_GMU_CX_GMU_RPMH_POWER_STATE, ®);
|
|
gmu_core_regread(device, GEN7_GMU_GFX_PWR_CLK_STATUS, ®1);
|
|
|
|
/*
|
|
* Check that we are at lowest level. If lowest level is IFPC
|
|
* double check that GFX clock is off.
|
|
*/
|
|
if (gmu->idle_level == reg)
|
|
if (!(gmu->idle_level == GPU_HW_IFPC && is_on(reg1)))
|
|
return 0;
|
|
|
|
/* Wait 100us to reduce unnecessary AHB bus traffic */
|
|
usleep_range(10, 100);
|
|
} while (!time_after(jiffies, t));
|
|
|
|
/* Check one last time */
|
|
gmu_core_regread(device, GEN7_GPU_GMU_CX_GMU_RPMH_POWER_STATE, ®);
|
|
gmu_core_regread(device, GEN7_GMU_GFX_PWR_CLK_STATUS, ®1);
|
|
|
|
/*
|
|
* Check that we are at lowest level. If lowest level is IFPC
|
|
* double check that GFX clock is off.
|
|
*/
|
|
if (gmu->idle_level == reg)
|
|
if (!(gmu->idle_level == GPU_HW_IFPC && is_on(reg1)))
|
|
return 0;
|
|
|
|
ts2 = gpudev->read_alwayson(adreno_dev);
|
|
|
|
/* Collect abort data to help with debugging */
|
|
gmu_core_regread(device, GEN7_GPU_GMU_AO_GPU_CX_BUSY_STATUS, ®2);
|
|
gmu_core_regread(device, GEN7_GMU_RBBM_INT_UNMASKED_STATUS, ®3);
|
|
gmu_core_regread(device, GEN7_GMU_GMU_PWR_COL_KEEPALIVE, ®4);
|
|
|
|
dev_err(&gmu->pdev->dev,
|
|
"----------------------[ GMU error ]----------------------\n");
|
|
dev_err(&gmu->pdev->dev, "Timeout waiting for lowest idle level %s\n",
|
|
idle_level_name(gmu->idle_level));
|
|
dev_err(&gmu->pdev->dev, "Start: %llx (absolute ticks)\n", ts1);
|
|
dev_err(&gmu->pdev->dev, "Poll: %llx (ticks relative to start)\n", ts2-ts1);
|
|
dev_err(&gmu->pdev->dev, "RPMH_POWER_STATE=%x GFX_PWR_CLK_STATUS=%x\n", reg, reg1);
|
|
dev_err(&gmu->pdev->dev, "CX_BUSY_STATUS=%x\n", reg2);
|
|
dev_err(&gmu->pdev->dev, "RBBM_INT_UNMASKED_STATUS=%x PWR_COL_KEEPALIVE=%x\n", reg3, reg4);
|
|
|
|
/* Access GX registers only when GX is ON */
|
|
if (is_on(reg1)) {
|
|
kgsl_regread(device, GEN7_CP_STATUS_1, ®2);
|
|
kgsl_regread(device, GEN7_CP_CP2GMU_STATUS, ®3);
|
|
kgsl_regread(device, GEN7_CP_CONTEXT_SWITCH_CNTL, ®4);
|
|
|
|
dev_err(&gmu->pdev->dev, "GEN7_CP_STATUS_1=%x\n", reg2);
|
|
dev_err(&gmu->pdev->dev, "CP2GMU_STATUS=%x CONTEXT_SWITCH_CNTL=%x\n", reg3, reg4);
|
|
}
|
|
|
|
WARN_ON(1);
|
|
gmu_core_fault_snapshot(device);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/* Bitmask for GPU idle status check */
|
|
#define CXGXCPUBUSYIGNAHB BIT(30)
|
|
int gen7_gmu_wait_for_idle(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
|
|
u32 status2;
|
|
u64 ts1;
|
|
|
|
ts1 = gpudev->read_alwayson(adreno_dev);
|
|
if (gmu_core_timed_poll_check(device, GEN7_GPU_GMU_AO_GPU_CX_BUSY_STATUS,
|
|
0, 100, CXGXCPUBUSYIGNAHB)) {
|
|
gmu_core_regread(device,
|
|
GEN7_GPU_GMU_AO_GPU_CX_BUSY_STATUS2, &status2);
|
|
dev_err(&gmu->pdev->dev,
|
|
"GMU not idling: status2=0x%x %llx %llx\n",
|
|
status2, ts1,
|
|
gpudev->read_alwayson(adreno_dev));
|
|
gmu_core_fault_snapshot(device);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gen7_gmu_version_info(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
const struct adreno_gen7_core *gen7_core = to_gen7_core(adreno_dev);
|
|
|
|
/* GMU version info is at a fixed offset in the DTCM */
|
|
gmu_core_regread(device, GEN7_GMU_CM3_DTCM_START + 0xff8,
|
|
&gmu->ver.core);
|
|
gmu_core_regread(device, GEN7_GMU_CM3_DTCM_START + 0xff9,
|
|
&gmu->ver.core_dev);
|
|
gmu_core_regread(device, GEN7_GMU_CM3_DTCM_START + 0xffa,
|
|
&gmu->ver.pwr);
|
|
gmu_core_regread(device, GEN7_GMU_CM3_DTCM_START + 0xffb,
|
|
&gmu->ver.pwr_dev);
|
|
gmu_core_regread(device, GEN7_GMU_CM3_DTCM_START + 0xffc,
|
|
&gmu->ver.hfi);
|
|
|
|
/* Check if gmu fw version on device is compatible with kgsl driver */
|
|
if (gmu->ver.core < gen7_core->gmu_fw_version) {
|
|
dev_err_once(&gmu->pdev->dev,
|
|
"GMU FW version 0x%x error (expected 0x%x)\n",
|
|
gmu->ver.core, gen7_core->gmu_fw_version);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int gen7_gmu_itcm_shadow(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
u32 i, *dest;
|
|
|
|
if (gmu->itcm_shadow)
|
|
return 0;
|
|
|
|
gmu->itcm_shadow = vzalloc(gmu->vma[GMU_ITCM].size);
|
|
if (!gmu->itcm_shadow)
|
|
return -ENOMEM;
|
|
|
|
dest = (u32 *)gmu->itcm_shadow;
|
|
|
|
for (i = 0; i < (gmu->vma[GMU_ITCM].size >> 2); i++)
|
|
gmu_core_regread(KGSL_DEVICE(adreno_dev),
|
|
GEN7_GMU_CM3_ITCM_START + i, dest++);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void gen7_gmu_register_config(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
u32 val;
|
|
|
|
/* Clear any previously set cm3 fault */
|
|
atomic_set(&gmu->cm3_fault, 0);
|
|
|
|
/* Vote veto for FAL10 */
|
|
gmu_core_regwrite(device, GEN7_GPU_GMU_CX_GMU_CX_FALNEXT_INTF, 0x1);
|
|
gmu_core_regwrite(device, GEN7_GPU_GMU_CX_GMU_CX_FAL_INTF, 0x1);
|
|
|
|
/* Clear init result to make sure we are getting fresh value */
|
|
gmu_core_regwrite(device, GEN7_GMU_CM3_FW_INIT_RESULT, 0);
|
|
gmu_core_regwrite(device, GEN7_GMU_CM3_BOOT_CONFIG, 0x2);
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_HFI_QTBL_ADDR,
|
|
gmu->hfi.hfi_mem->gmuaddr);
|
|
gmu_core_regwrite(device, GEN7_GMU_HFI_QTBL_INFO, 1);
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_AHB_FENCE_RANGE_0, BIT(31) |
|
|
FIELD_PREP(GENMASK(30, 18), 0x32) |
|
|
FIELD_PREP(GENMASK(17, 0), 0x8a0));
|
|
|
|
/*
|
|
* Make sure that CM3 state is at reset value. Snapshot is changing
|
|
* NMI bit and if we boot up GMU with NMI bit set GMU will boot
|
|
* straight in to NMI handler without executing __main code
|
|
*/
|
|
gmu_core_regwrite(device, GEN7_GMU_CM3_CFG, 0x4052);
|
|
|
|
/**
|
|
* We may have asserted gbif halt as part of reset sequence which may
|
|
* not get cleared if the gdsc was not reset. So clear it before
|
|
* attempting GMU boot.
|
|
*/
|
|
kgsl_regwrite(device, GEN7_GBIF_HALT, 0x0);
|
|
|
|
/* Set vrb address before starting GMU */
|
|
if (!IS_ERR_OR_NULL(gmu->vrb))
|
|
gmu_core_regwrite(device, GEN7_GMU_GENERAL_11, gmu->vrb->gmuaddr);
|
|
|
|
/* Set the log wptr index */
|
|
gmu_core_regwrite(device, GEN7_GMU_GENERAL_9,
|
|
gmu->log_wptr_retention);
|
|
|
|
/* Pass chipid to GMU FW, must happen before starting GMU */
|
|
gmu_core_regwrite(device, GEN7_GMU_GENERAL_10,
|
|
ADRENO_GMU_REV(ADRENO_GPUREV(adreno_dev)));
|
|
|
|
/* Log size is encoded in (number of 4K units - 1) */
|
|
val = (gmu->gmu_log->gmuaddr & GENMASK(31, 12)) |
|
|
((GMU_LOG_SIZE/SZ_4K - 1) & GENMASK(7, 0));
|
|
gmu_core_regwrite(device, GEN7_GMU_GENERAL_8, val);
|
|
|
|
/* Configure power control and bring the GMU out of reset */
|
|
gen7_gmu_power_config(adreno_dev);
|
|
|
|
/*
|
|
* Enable BCL throttling -
|
|
* XOCLK1: countable: 0x13 (25% throttle)
|
|
* XOCLK2: countable: 0x17 (58% throttle)
|
|
* XOCLK3: countable: 0x19 (75% throttle)
|
|
* POWER_CONTROL_SELECT_0 controls counters 0 - 3, each selector
|
|
* is 8 bits wide.
|
|
*/
|
|
if (adreno_dev->bcl_enabled)
|
|
gmu_core_regrmw(device, GEN7_GMU_CX_GMU_POWER_COUNTER_SELECT_0,
|
|
0xffffff00, FIELD_PREP(GENMASK(31, 24), 0x19) |
|
|
FIELD_PREP(GENMASK(23, 16), 0x17) |
|
|
FIELD_PREP(GENMASK(15, 8), 0x13));
|
|
}
|
|
|
|
static struct gmu_vma_node *find_va(struct gmu_vma_entry *vma, u32 addr, u32 size)
|
|
{
|
|
struct rb_node *node = vma->vma_root.rb_node;
|
|
|
|
while (node != NULL) {
|
|
struct gmu_vma_node *data = rb_entry(node, struct gmu_vma_node, node);
|
|
|
|
if (addr + size <= data->va)
|
|
node = node->rb_left;
|
|
else if (addr >= data->va + data->size)
|
|
node = node->rb_right;
|
|
else
|
|
return data;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Return true if VMA supports dynamic allocations */
|
|
static bool vma_is_dynamic(int vma_id)
|
|
{
|
|
/* Dynamic allocations are done in the GMU_NONCACHED_KERNEL space */
|
|
return vma_id == GMU_NONCACHED_KERNEL;
|
|
}
|
|
|
|
static int insert_va(struct gmu_vma_entry *vma, u32 addr, u32 size)
|
|
{
|
|
struct rb_node **node, *parent = NULL;
|
|
struct gmu_vma_node *new = kzalloc(sizeof(*new), GFP_NOWAIT);
|
|
|
|
if (new == NULL)
|
|
return -ENOMEM;
|
|
|
|
new->va = addr;
|
|
new->size = size;
|
|
|
|
node = &vma->vma_root.rb_node;
|
|
while (*node != NULL) {
|
|
struct gmu_vma_node *this;
|
|
|
|
parent = *node;
|
|
this = rb_entry(parent, struct gmu_vma_node, node);
|
|
|
|
if (addr + size <= this->va)
|
|
node = &parent->rb_left;
|
|
else if (addr >= this->va + this->size)
|
|
node = &parent->rb_right;
|
|
else {
|
|
kfree(new);
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
|
|
/* Add new node and rebalance tree */
|
|
rb_link_node(&new->node, parent, node);
|
|
rb_insert_color(&new->node, &vma->vma_root);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 find_unmapped_va(struct gmu_vma_entry *vma, u32 size, u32 va_align)
|
|
{
|
|
struct rb_node *node = rb_first(&vma->vma_root);
|
|
u32 cur = vma->start;
|
|
bool found = false;
|
|
|
|
cur = ALIGN(cur, va_align);
|
|
|
|
while (node) {
|
|
struct gmu_vma_node *data = rb_entry(node, struct gmu_vma_node, node);
|
|
|
|
if (cur + size <= data->va) {
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
cur = ALIGN(data->va + data->size, va_align);
|
|
node = rb_next(node);
|
|
}
|
|
|
|
/* Do we have space after the last node? */
|
|
if (!found && (cur + size <= vma->start + vma->size))
|
|
found = true;
|
|
return found ? cur : 0;
|
|
}
|
|
|
|
static int _map_gmu_dynamic(struct gen7_gmu_device *gmu,
|
|
struct kgsl_memdesc *md,
|
|
u32 addr, u32 vma_id, int attrs, u32 align)
|
|
{
|
|
int ret;
|
|
struct gmu_vma_entry *vma = &gmu->vma[vma_id];
|
|
struct gmu_vma_node *vma_node = NULL;
|
|
u32 size = ALIGN(md->size, hfi_get_gmu_sz_alignment(align));
|
|
|
|
spin_lock(&vma->lock);
|
|
if (!addr) {
|
|
/*
|
|
* We will end up with a hole (GMU VA range not backed by physical mapping) if
|
|
* the aligned size is greater than the size of the physical mapping
|
|
*/
|
|
addr = find_unmapped_va(vma, size, hfi_get_gmu_va_alignment(align));
|
|
if (addr == 0) {
|
|
spin_unlock(&vma->lock);
|
|
dev_err(&gmu->pdev->dev,
|
|
"Insufficient VA space size: %x\n", size);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
ret = insert_va(vma, addr, size);
|
|
spin_unlock(&vma->lock);
|
|
if (ret < 0) {
|
|
dev_err(&gmu->pdev->dev,
|
|
"Could not insert va: %x size %x\n", addr, size);
|
|
return ret;
|
|
}
|
|
|
|
ret = gmu_core_map_memdesc(gmu->domain, md, addr, attrs);
|
|
if (!ret) {
|
|
md->gmuaddr = addr;
|
|
return 0;
|
|
}
|
|
|
|
/* Failed to map to GMU */
|
|
dev_err(&gmu->pdev->dev,
|
|
"Unable to map GMU kernel block: addr:0x%08x size:0x%llx :%d\n",
|
|
addr, md->size, ret);
|
|
|
|
spin_lock(&vma->lock);
|
|
vma_node = find_va(vma, md->gmuaddr, size);
|
|
if (vma_node)
|
|
rb_erase(&vma_node->node, &vma->vma_root);
|
|
spin_unlock(&vma->lock);
|
|
kfree(vma_node);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int _map_gmu_static(struct gen7_gmu_device *gmu,
|
|
struct kgsl_memdesc *md,
|
|
u32 addr, u32 vma_id, int attrs, u32 align)
|
|
{
|
|
int ret;
|
|
struct gmu_vma_entry *vma = &gmu->vma[vma_id];
|
|
u32 size = ALIGN(md->size, hfi_get_gmu_sz_alignment(align));
|
|
|
|
if (!addr)
|
|
addr = ALIGN(vma->next_va, hfi_get_gmu_va_alignment(align));
|
|
|
|
ret = gmu_core_map_memdesc(gmu->domain, md, addr, attrs);
|
|
if (ret) {
|
|
dev_err(&gmu->pdev->dev,
|
|
"Unable to map GMU kernel block: addr:0x%08x size:0x%llx :%d\n",
|
|
addr, md->size, ret);
|
|
return ret;
|
|
}
|
|
md->gmuaddr = addr;
|
|
/*
|
|
* We will end up with a hole (GMU VA range not backed by physical mapping) if the aligned
|
|
* size is greater than the size of the physical mapping
|
|
*/
|
|
vma->next_va = md->gmuaddr + size;
|
|
return 0;
|
|
}
|
|
|
|
static int _map_gmu(struct gen7_gmu_device *gmu,
|
|
struct kgsl_memdesc *md,
|
|
u32 addr, u32 vma_id, int attrs, u32 align)
|
|
{
|
|
return vma_is_dynamic(vma_id) ?
|
|
_map_gmu_dynamic(gmu, md, addr, vma_id, attrs, align) :
|
|
_map_gmu_static(gmu, md, addr, vma_id, attrs, align);
|
|
}
|
|
|
|
int gen7_gmu_import_buffer(struct gen7_gmu_device *gmu, u32 vma_id,
|
|
struct kgsl_memdesc *md, u32 attrs, u32 align)
|
|
{
|
|
return _map_gmu(gmu, md, 0, vma_id, attrs, align);
|
|
}
|
|
|
|
struct kgsl_memdesc *gen7_reserve_gmu_kernel_block(struct gen7_gmu_device *gmu,
|
|
u32 addr, u32 size, u32 vma_id, u32 align)
|
|
{
|
|
int ret;
|
|
struct kgsl_memdesc *md;
|
|
struct kgsl_device *device = KGSL_DEVICE(gen7_gmu_to_adreno(gmu));
|
|
int attrs = IOMMU_READ | IOMMU_WRITE | IOMMU_PRIV;
|
|
|
|
if (gmu->global_entries == ARRAY_SIZE(gmu->gmu_globals))
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
md = &gmu->gmu_globals[gmu->global_entries];
|
|
|
|
ret = kgsl_allocate_kernel(device, md, size, 0, KGSL_MEMDESC_SYSMEM);
|
|
if (ret) {
|
|
memset(md, 0x0, sizeof(*md));
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
ret = _map_gmu(gmu, md, addr, vma_id, attrs, align);
|
|
if (ret) {
|
|
kgsl_sharedmem_free(md);
|
|
memset(md, 0x0, sizeof(*md));
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
gmu->global_entries++;
|
|
|
|
return md;
|
|
}
|
|
|
|
struct kgsl_memdesc *gen7_reserve_gmu_kernel_block_fixed(struct gen7_gmu_device *gmu,
|
|
u32 addr, u32 size, u32 vma_id, const char *resource, int attrs, u32 align)
|
|
{
|
|
int ret;
|
|
struct kgsl_memdesc *md;
|
|
struct kgsl_device *device = KGSL_DEVICE(gen7_gmu_to_adreno(gmu));
|
|
|
|
if (gmu->global_entries == ARRAY_SIZE(gmu->gmu_globals))
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
md = &gmu->gmu_globals[gmu->global_entries];
|
|
|
|
ret = kgsl_memdesc_init_fixed(device, gmu->pdev, resource, md);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
ret = _map_gmu(gmu, md, addr, vma_id, attrs, align);
|
|
|
|
sg_free_table(md->sgt);
|
|
kfree(md->sgt);
|
|
md->sgt = NULL;
|
|
|
|
if (!ret)
|
|
gmu->global_entries++;
|
|
else {
|
|
dev_err(&gmu->pdev->dev,
|
|
"Unable to map GMU kernel block: addr:0x%08x size:0x%llx :%d\n",
|
|
addr, md->size, ret);
|
|
memset(md, 0x0, sizeof(*md));
|
|
md = ERR_PTR(ret);
|
|
}
|
|
return md;
|
|
}
|
|
|
|
int gen7_alloc_gmu_kernel_block(struct gen7_gmu_device *gmu,
|
|
struct kgsl_memdesc *md, u32 size, u32 vma_id, int attrs)
|
|
{
|
|
int ret;
|
|
struct kgsl_device *device = KGSL_DEVICE(gen7_gmu_to_adreno(gmu));
|
|
|
|
ret = kgsl_allocate_kernel(device, md, size, 0, KGSL_MEMDESC_SYSMEM);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = _map_gmu(gmu, md, 0, vma_id, attrs, 0);
|
|
if (ret)
|
|
kgsl_sharedmem_free(md);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void gen7_free_gmu_block(struct gen7_gmu_device *gmu, struct kgsl_memdesc *md)
|
|
{
|
|
int vma_id = find_vma_block(gmu, md->gmuaddr, md->size);
|
|
struct gmu_vma_entry *vma;
|
|
struct gmu_vma_node *vma_node;
|
|
|
|
if ((vma_id < 0) || !vma_is_dynamic(vma_id))
|
|
return;
|
|
|
|
vma = &gmu->vma[vma_id];
|
|
|
|
/*
|
|
* Do not remove the vma node if we failed to unmap the entire buffer. This is because the
|
|
* iommu driver considers remapping an already mapped iova as fatal.
|
|
*/
|
|
if (md->size != iommu_unmap(gmu->domain, md->gmuaddr, md->size))
|
|
goto free;
|
|
|
|
spin_lock(&vma->lock);
|
|
vma_node = find_va(vma, md->gmuaddr, md->size);
|
|
if (vma_node)
|
|
rb_erase(&vma_node->node, &vma->vma_root);
|
|
spin_unlock(&vma->lock);
|
|
kfree(vma_node);
|
|
free:
|
|
kgsl_sharedmem_free(md);
|
|
}
|
|
|
|
static int gen7_gmu_process_prealloc(struct gen7_gmu_device *gmu,
|
|
struct gmu_block_header *blk)
|
|
{
|
|
struct kgsl_memdesc *md;
|
|
|
|
int id = find_vma_block(gmu, blk->addr, blk->value);
|
|
|
|
if (id < 0) {
|
|
dev_err(&gmu->pdev->dev,
|
|
"Invalid prealloc block addr: 0x%x value:%d\n",
|
|
blk->addr, blk->value);
|
|
return id;
|
|
}
|
|
|
|
/* Nothing to do for TCM blocks or user uncached */
|
|
if (id == GMU_ITCM || id == GMU_DTCM || id == GMU_NONCACHED_USER)
|
|
return 0;
|
|
|
|
/* Check if the block is already allocated */
|
|
md = find_gmu_memdesc(gmu, blk->addr, blk->value);
|
|
if (md != NULL)
|
|
return 0;
|
|
|
|
md = gen7_reserve_gmu_kernel_block(gmu, blk->addr, blk->value, id, 0);
|
|
|
|
return PTR_ERR_OR_ZERO(md);
|
|
}
|
|
|
|
int gen7_gmu_parse_fw(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
const struct adreno_gen7_core *gen7_core = to_gen7_core(adreno_dev);
|
|
struct gmu_block_header *blk;
|
|
int ret, offset = 0;
|
|
const char *gmufw_name = gen7_core->gmufw_name;
|
|
|
|
/*
|
|
* If GMU fw already saved and verified, do nothing new.
|
|
* Skip only request_firmware and allow preallocation to
|
|
* ensure in scenario where GMU request firmware succeeded
|
|
* but preallocation fails, we don't return early without
|
|
* successful preallocations on next open call.
|
|
*/
|
|
if (!gmu->fw_image) {
|
|
|
|
if (gen7_core->gmufw_name == NULL)
|
|
return -EINVAL;
|
|
|
|
ret = request_firmware(&gmu->fw_image, gmufw_name,
|
|
&gmu->pdev->dev);
|
|
if (ret) {
|
|
if (gen7_core->gmufw_bak_name) {
|
|
gmufw_name = gen7_core->gmufw_bak_name;
|
|
ret = request_firmware(&gmu->fw_image, gmufw_name,
|
|
&gmu->pdev->dev);
|
|
}
|
|
|
|
if (ret) {
|
|
dev_err(&gmu->pdev->dev,
|
|
"request_firmware (%s) failed: %d\n",
|
|
gmufw_name, ret);
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Zero payload fw blocks contain metadata and are
|
|
* guaranteed to precede fw load data. Parse the
|
|
* metadata blocks.
|
|
*/
|
|
while (offset < gmu->fw_image->size) {
|
|
blk = (struct gmu_block_header *)&gmu->fw_image->data[offset];
|
|
|
|
if (offset + sizeof(*blk) > gmu->fw_image->size) {
|
|
dev_err(&gmu->pdev->dev, "Invalid FW Block\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Done with zero length blocks so return */
|
|
if (blk->size)
|
|
break;
|
|
|
|
offset += sizeof(*blk);
|
|
|
|
if (blk->type == GMU_BLK_TYPE_PREALLOC_REQ ||
|
|
blk->type == GMU_BLK_TYPE_PREALLOC_PERSIST_REQ) {
|
|
ret = gen7_gmu_process_prealloc(gmu, blk);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gen7_gmu_memory_init(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
|
|
/* GMU master log */
|
|
if (IS_ERR_OR_NULL(gmu->gmu_log))
|
|
gmu->gmu_log = gen7_reserve_gmu_kernel_block(gmu, 0,
|
|
GMU_LOG_SIZE, GMU_NONCACHED_KERNEL, 0);
|
|
|
|
return PTR_ERR_OR_ZERO(gmu->gmu_log);
|
|
}
|
|
|
|
static int gen7_gmu_init(struct adreno_device *adreno_dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = gen7_gmu_parse_fw(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_gmu_memory_init(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return gen7_hfi_init(adreno_dev);
|
|
}
|
|
|
|
static void _do_gbif_halt(struct kgsl_device *device, u32 reg, u32 ack_reg,
|
|
u32 mask, const char *client)
|
|
{
|
|
u32 ack;
|
|
unsigned long t;
|
|
|
|
kgsl_regwrite(device, reg, mask);
|
|
|
|
t = jiffies + msecs_to_jiffies(100);
|
|
do {
|
|
kgsl_regread(device, ack_reg, &ack);
|
|
if ((ack & mask) == mask)
|
|
return;
|
|
|
|
/*
|
|
* If we are attempting recovery in case of stall-on-fault
|
|
* then the halt sequence will not complete as long as SMMU
|
|
* is stalled.
|
|
*/
|
|
kgsl_mmu_pagefault_resume(&device->mmu, false);
|
|
|
|
usleep_range(10, 100);
|
|
} while (!time_after(jiffies, t));
|
|
|
|
/* Check one last time */
|
|
kgsl_mmu_pagefault_resume(&device->mmu, false);
|
|
|
|
kgsl_regread(device, ack_reg, &ack);
|
|
if ((ack & mask) == mask)
|
|
return;
|
|
|
|
dev_err(device->dev, "%s GBIF halt timed out\n", client);
|
|
}
|
|
|
|
static void gen7_gmu_pwrctrl_suspend(struct adreno_device *adreno_dev)
|
|
{
|
|
int ret = 0;
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
|
|
|
|
/* Disconnect GPU from BUS is not needed if CX GDSC goes off later */
|
|
|
|
/*
|
|
* GEMNOC can enter power collapse state during GPU power down sequence.
|
|
* This could abort CX GDSC collapse. Assert Qactive to avoid this.
|
|
*/
|
|
gmu_core_regwrite(device, GEN7_GPU_GMU_CX_GMU_CX_FALNEXT_INTF, 0x1);
|
|
|
|
/* Check no outstanding RPMh voting */
|
|
gen7_complete_rpmh_votes(gmu, 1);
|
|
|
|
/* Clear the WRITEDROPPED fields and set fence to allow mode */
|
|
gmu_core_regwrite(device, GEN7_GMU_AHB_FENCE_STATUS_CLR, 0x7);
|
|
gmu_core_regwrite(device, GEN7_GMU_AO_AHB_FENCE_CTRL, 0);
|
|
|
|
/* Make sure above writes are committed before we proceed to recovery */
|
|
wmb();
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_CM3_SYSRESET, 1);
|
|
|
|
/* Halt GX traffic */
|
|
if (gen7_gmu_gx_is_on(adreno_dev))
|
|
_do_gbif_halt(device, GEN7_RBBM_GBIF_HALT,
|
|
GEN7_RBBM_GBIF_HALT_ACK,
|
|
GEN7_GBIF_GX_HALT_MASK,
|
|
"GX");
|
|
|
|
/* Halt CX traffic */
|
|
_do_gbif_halt(device, GEN7_GBIF_HALT, GEN7_GBIF_HALT_ACK,
|
|
GEN7_GBIF_ARB_HALT_MASK, "CX");
|
|
|
|
if (gen7_gmu_gx_is_on(adreno_dev))
|
|
kgsl_regwrite(device, GEN7_RBBM_SW_RESET_CMD, 0x1);
|
|
|
|
/* Make sure above writes are posted before turning off power resources */
|
|
wmb();
|
|
|
|
/* Allow the software reset to complete */
|
|
udelay(100);
|
|
|
|
/*
|
|
* This is based on the assumption that GMU is the only one controlling
|
|
* the GX HS. This code path is the only client voting for GX through
|
|
* the regulator interface.
|
|
*/
|
|
if (pwr->gx_gdsc) {
|
|
if (gen7_gmu_gx_is_on(adreno_dev)) {
|
|
/* Switch gx gdsc control from GMU to CPU
|
|
* force non-zero reference count in clk driver
|
|
* so next disable call will turn
|
|
* off the GDSC
|
|
*/
|
|
ret = regulator_enable(pwr->gx_gdsc);
|
|
if (ret)
|
|
dev_err(&gmu->pdev->dev,
|
|
"suspend fail: gx enable %d\n", ret);
|
|
|
|
ret = regulator_disable(pwr->gx_gdsc);
|
|
if (ret)
|
|
dev_err(&gmu->pdev->dev,
|
|
"suspend fail: gx disable %d\n", ret);
|
|
|
|
if (gen7_gmu_gx_is_on(adreno_dev))
|
|
dev_err(&gmu->pdev->dev,
|
|
"gx is stuck on\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* gen7_gmu_notify_slumber() - initiate request to GMU to prepare to slumber
|
|
* @device: Pointer to KGSL device
|
|
*/
|
|
static int gen7_gmu_notify_slumber(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int bus_level = pwr->pwrlevels[pwr->default_pwrlevel].bus_freq;
|
|
int perf_idx = gmu->dcvs_table.gpu_level_num -
|
|
pwr->default_pwrlevel - 1;
|
|
struct hfi_prep_slumber_cmd req = {
|
|
.freq = perf_idx,
|
|
.bw = bus_level,
|
|
};
|
|
int ret;
|
|
|
|
req.bw |= gen7_bus_ab_quantize(adreno_dev, 0);
|
|
|
|
/* Disable the power counter so that the GMU is not busy */
|
|
gmu_core_regwrite(device, GEN7_GMU_CX_GMU_POWER_COUNTER_ENABLE, 0);
|
|
|
|
ret = CMD_MSG_HDR(req, H2F_MSG_PREPARE_SLUMBER);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_hfi_send_generic_req(adreno_dev, &req, sizeof(req));
|
|
|
|
/* Make sure the fence is in ALLOW mode */
|
|
gmu_core_regwrite(device, GEN7_GMU_AO_AHB_FENCE_CTRL, 0);
|
|
|
|
/*
|
|
* GEMNOC can enter power collapse state during GPU power down sequence.
|
|
* This could abort CX GDSC collapse. Assert Qactive to avoid this.
|
|
*/
|
|
gmu_core_regwrite(device, GEN7_GPU_GMU_CX_GMU_CX_FALNEXT_INTF, 0x1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void gen7_gmu_suspend(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
|
|
gen7_gmu_pwrctrl_suspend(adreno_dev);
|
|
|
|
clk_bulk_disable_unprepare(gmu->num_clks, gmu->clks);
|
|
|
|
kgsl_pwrctrl_disable_cx_gdsc(device);
|
|
|
|
gen7_rdpm_cx_freq_update(gmu, 0);
|
|
|
|
dev_err(&gmu->pdev->dev, "Suspended GMU\n");
|
|
|
|
kgsl_pwrctrl_set_state(device, KGSL_STATE_NONE);
|
|
}
|
|
|
|
static int gen7_gmu_dcvs_set(struct adreno_device *adreno_dev,
|
|
int gpu_pwrlevel, int bus_level, u32 ab)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct gen7_dcvs_table *table = &gmu->dcvs_table;
|
|
struct hfi_gx_bw_perf_vote_cmd req = {
|
|
.ack_type = DCVS_ACK_BLOCK,
|
|
.freq = INVALID_DCVS_IDX,
|
|
.bw = INVALID_DCVS_IDX,
|
|
};
|
|
int ret = 0;
|
|
|
|
if (!test_bit(GMU_PRIV_HFI_STARTED, &gmu->flags))
|
|
return 0;
|
|
|
|
/* Do not set to XO and lower GPU clock vote from GMU */
|
|
if ((gpu_pwrlevel != INVALID_DCVS_IDX) &&
|
|
(gpu_pwrlevel >= table->gpu_level_num - 1))
|
|
return -EINVAL;
|
|
|
|
if (gpu_pwrlevel < table->gpu_level_num - 1)
|
|
req.freq = table->gpu_level_num - gpu_pwrlevel - 1;
|
|
|
|
if (bus_level < pwr->ddr_table_count && bus_level > 0)
|
|
req.bw = bus_level;
|
|
|
|
req.bw |= gen7_bus_ab_quantize(adreno_dev, ab);
|
|
|
|
/* GMU will vote for slumber levels through the sleep sequence */
|
|
if ((req.freq == INVALID_DCVS_IDX) && (req.bw == INVALID_BW_VOTE))
|
|
return 0;
|
|
|
|
ret = CMD_MSG_HDR(req, H2F_MSG_GX_BW_PERF_VOTE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_hfi_send_generic_req(adreno_dev, &req, sizeof(req));
|
|
if (ret) {
|
|
dev_err_ratelimited(&gmu->pdev->dev,
|
|
"Failed to set GPU perf idx %u, bw idx %u\n",
|
|
req.freq, req.bw);
|
|
|
|
/*
|
|
* If this was a dcvs request along side an active gpu, request
|
|
* dispatcher based reset and recovery.
|
|
*/
|
|
if (test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags))
|
|
adreno_dispatcher_fault(adreno_dev, ADRENO_GMU_FAULT |
|
|
ADRENO_GMU_FAULT_SKIP_SNAPSHOT);
|
|
}
|
|
|
|
if (req.freq != INVALID_DCVS_IDX)
|
|
gen7_rdpm_mx_freq_update(gmu,
|
|
gmu->dcvs_table.gx_votes[req.freq].freq);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gen7_gmu_clock_set(struct adreno_device *adreno_dev, u32 pwrlevel)
|
|
{
|
|
return gen7_gmu_dcvs_set(adreno_dev, pwrlevel, INVALID_DCVS_IDX, INVALID_AB_VALUE);
|
|
}
|
|
|
|
static int gen7_gmu_ifpc_store(struct kgsl_device *device,
|
|
unsigned int val)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
unsigned int requested_idle_level;
|
|
|
|
if (!ADRENO_FEATURE(adreno_dev, ADRENO_IFPC))
|
|
return -EINVAL;
|
|
|
|
if (val)
|
|
requested_idle_level = GPU_HW_IFPC;
|
|
else
|
|
requested_idle_level = GPU_HW_ACTIVE;
|
|
|
|
if (gmu->idle_level == requested_idle_level)
|
|
return 0;
|
|
|
|
/* Power down the GPU before changing the idle level */
|
|
return adreno_power_cycle_u32(adreno_dev, &gmu->idle_level,
|
|
requested_idle_level);
|
|
}
|
|
|
|
static unsigned int gen7_gmu_ifpc_isenabled(struct kgsl_device *device)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(ADRENO_DEVICE(device));
|
|
|
|
return gmu->idle_level == GPU_HW_IFPC;
|
|
}
|
|
|
|
/* Send an NMI to the GMU */
|
|
void gen7_gmu_send_nmi(struct kgsl_device *device, bool force)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
u32 result;
|
|
|
|
/*
|
|
* Do not send NMI if the SMMU is stalled because GMU will not be able
|
|
* to save cm3 state to DDR.
|
|
*/
|
|
if (gen7_gmu_gx_is_on(adreno_dev) && adreno_smmu_is_stalled(adreno_dev)) {
|
|
dev_err(&gmu->pdev->dev,
|
|
"Skipping NMI because SMMU is stalled\n");
|
|
return;
|
|
}
|
|
|
|
if (force)
|
|
goto nmi;
|
|
|
|
/*
|
|
* We should not send NMI if there was a CM3 fault reported because we
|
|
* don't want to overwrite the critical CM3 state captured by gmu before
|
|
* it sent the CM3 fault interrupt. Also don't send NMI if GMU reset is
|
|
* already active. We could have hit a GMU assert and NMI might have
|
|
* already been triggered.
|
|
*/
|
|
|
|
/* make sure we're reading the latest cm3_fault */
|
|
smp_rmb();
|
|
|
|
if (atomic_read(&gmu->cm3_fault))
|
|
return;
|
|
|
|
gmu_core_regread(device, GEN7_GMU_CM3_FW_INIT_RESULT, &result);
|
|
|
|
if (result & 0xE00)
|
|
return;
|
|
|
|
nmi:
|
|
/* Mask so there's no interrupt caused by NMI */
|
|
gmu_core_regwrite(device, GEN7_GMU_GMU2HOST_INTR_MASK, UINT_MAX);
|
|
|
|
/* Make sure the interrupt is masked before causing it */
|
|
wmb();
|
|
|
|
/* This will cause the GMU to save it's internal state to ddr */
|
|
gmu_core_regrmw(device, GEN7_GMU_CM3_CFG, BIT(9), BIT(9));
|
|
|
|
/* Make sure the NMI is invoked before we proceed*/
|
|
wmb();
|
|
|
|
/* Wait for the NMI to be handled */
|
|
udelay(200);
|
|
}
|
|
|
|
static void gen7_gmu_cooperative_reset(struct kgsl_device *device)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
unsigned int result;
|
|
|
|
gmu_core_regwrite(device, GEN7_GMU_CX_GMU_WDOG_CTRL, 0);
|
|
gmu_core_regwrite(device, GEN7_GMU_HOST2GMU_INTR_SET, BIT(17));
|
|
|
|
/*
|
|
* After triggering graceful death wait for snapshot ready
|
|
* indication from GMU.
|
|
*/
|
|
if (!gmu_core_timed_poll_check(device, GEN7_GMU_CM3_FW_INIT_RESULT,
|
|
0x800, 2, 0x800))
|
|
return;
|
|
|
|
gmu_core_regread(device, GEN7_GMU_CM3_FW_INIT_RESULT, &result);
|
|
dev_err(&gmu->pdev->dev,
|
|
"GMU cooperative reset timed out 0x%x\n", result);
|
|
/*
|
|
* If we dont get a snapshot ready from GMU, trigger NMI
|
|
* and if we still timeout then we just continue with reset.
|
|
*/
|
|
gen7_gmu_send_nmi(device, true);
|
|
|
|
gmu_core_regread(device, GEN7_GMU_CM3_FW_INIT_RESULT, &result);
|
|
if ((result & 0x800) != 0x800)
|
|
dev_err(&gmu->pdev->dev,
|
|
"GMU cooperative reset NMI timed out 0x%x\n", result);
|
|
}
|
|
|
|
static int gen7_gmu_wait_for_active_transition(struct kgsl_device *device)
|
|
{
|
|
unsigned int reg;
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(ADRENO_DEVICE(device));
|
|
|
|
if (gmu_core_timed_poll_check(device, GEN7_GPU_GMU_CX_GMU_RPMH_POWER_STATE,
|
|
GPU_HW_ACTIVE, 100, GENMASK(3, 0))) {
|
|
gmu_core_regread(device, GEN7_GPU_GMU_CX_GMU_RPMH_POWER_STATE, ®);
|
|
dev_err(&gmu->pdev->dev,
|
|
"GMU failed to move to ACTIVE state, Current state: 0x%x\n",
|
|
reg);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool gen7_gmu_scales_bandwidth(struct kgsl_device *device)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void gen7_gmu_handle_watchdog(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
u32 mask;
|
|
|
|
/* Temporarily mask the watchdog interrupt to prevent a storm */
|
|
gmu_core_regread(device, GEN7_GMU_AO_HOST_INTERRUPT_MASK, &mask);
|
|
gmu_core_regwrite(device, GEN7_GMU_AO_HOST_INTERRUPT_MASK,
|
|
(mask | GMU_INT_WDOG_BITE));
|
|
|
|
gen7_gmu_send_nmi(device, false);
|
|
|
|
dev_err_ratelimited(&gmu->pdev->dev,
|
|
"GMU watchdog expired interrupt received\n");
|
|
}
|
|
|
|
static irqreturn_t gen7_gmu_irq_handler(int irq, void *data)
|
|
{
|
|
struct kgsl_device *device = data;
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
const struct gen7_gpudev *gen7_gpudev =
|
|
to_gen7_gpudev(ADRENO_GPU_DEVICE(adreno_dev));
|
|
unsigned int status = 0;
|
|
|
|
gmu_core_regread(device, GEN7_GMU_AO_HOST_INTERRUPT_STATUS, &status);
|
|
gmu_core_regwrite(device, GEN7_GMU_AO_HOST_INTERRUPT_CLR, status);
|
|
|
|
if (status & GMU_INT_HOST_AHB_BUS_ERR)
|
|
dev_err_ratelimited(&gmu->pdev->dev,
|
|
"AHB bus error interrupt received\n");
|
|
|
|
if (status & GMU_INT_WDOG_BITE)
|
|
gen7_gpudev->handle_watchdog(adreno_dev);
|
|
|
|
if (status & GMU_INT_FENCE_ERR) {
|
|
unsigned int fence_status;
|
|
|
|
gmu_core_regread(device, GEN7_GMU_AHB_FENCE_STATUS,
|
|
&fence_status);
|
|
dev_err_ratelimited(&gmu->pdev->dev,
|
|
"FENCE error interrupt received %x\n", fence_status);
|
|
}
|
|
|
|
if (status & ~GMU_AO_INT_MASK)
|
|
dev_err_ratelimited(&gmu->pdev->dev,
|
|
"Unhandled GMU interrupts 0x%lx\n",
|
|
status & ~GMU_AO_INT_MASK);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
void gen7_gmu_aop_send_acd_state(struct gen7_gmu_device *gmu, bool flag)
|
|
{
|
|
struct qmp_pkt msg;
|
|
char msg_buf[36];
|
|
u32 size;
|
|
int ret;
|
|
|
|
if (IS_ERR_OR_NULL(gmu->mailbox.channel))
|
|
return;
|
|
|
|
size = scnprintf(msg_buf, sizeof(msg_buf),
|
|
"{class: gpu, res: acd, val: %d}", flag);
|
|
|
|
/* mailbox controller expects 4-byte aligned buffer */
|
|
msg.size = ALIGN((size + 1), SZ_4);
|
|
msg.data = msg_buf;
|
|
|
|
ret = mbox_send_message(gmu->mailbox.channel, &msg);
|
|
|
|
if (ret < 0)
|
|
dev_err(&gmu->pdev->dev,
|
|
"AOP mbox send message failed: %d\n", ret);
|
|
}
|
|
|
|
int gen7_gmu_enable_clks(struct adreno_device *adreno_dev, u32 level)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
int ret;
|
|
|
|
gen7_rdpm_cx_freq_update(gmu, gmu->freqs[level] / 1000);
|
|
|
|
ret = kgsl_clk_set_rate(gmu->clks, gmu->num_clks, "gmu_clk",
|
|
gmu->freqs[level]);
|
|
if (ret) {
|
|
dev_err(&gmu->pdev->dev, "GMU clock:%d set failed:%d\n",
|
|
gmu->freqs[level], ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = kgsl_clk_set_rate(gmu->clks, gmu->num_clks, "hub_clk",
|
|
adreno_dev->gmu_hub_clk_freq);
|
|
if (ret && ret != -ENODEV) {
|
|
dev_err(&gmu->pdev->dev, "Unable to set the HUB clock\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_bulk_prepare_enable(gmu->num_clks, gmu->clks);
|
|
if (ret) {
|
|
dev_err(&gmu->pdev->dev, "Cannot enable GMU clocks\n");
|
|
return ret;
|
|
}
|
|
|
|
device->state = KGSL_STATE_AWARE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gen7_gmu_first_boot(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int level, ret;
|
|
|
|
kgsl_pwrctrl_request_state(device, KGSL_STATE_AWARE);
|
|
|
|
gen7_gmu_aop_send_acd_state(gmu, adreno_dev->acd_enabled);
|
|
|
|
ret = kgsl_pwrctrl_enable_cx_gdsc(device);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_gmu_enable_clks(adreno_dev, 0);
|
|
if (ret)
|
|
goto gdsc_off;
|
|
|
|
/*
|
|
* Enable AHB timeout detection to catch any register access taking longer
|
|
* time before NOC timeout gets detected. Enable this logic before any
|
|
* register access which happens to be just after enabling clocks.
|
|
*/
|
|
gen7_enable_ahb_timeout_detection(adreno_dev);
|
|
|
|
/* Initialize the CX timer */
|
|
gen7_cx_timer_init(adreno_dev);
|
|
|
|
ret = gen7_gmu_load_fw(adreno_dev);
|
|
if (ret)
|
|
goto clks_gdsc_off;
|
|
|
|
ret = gen7_gmu_version_info(adreno_dev);
|
|
if (ret)
|
|
goto clks_gdsc_off;
|
|
|
|
ret = gen7_gmu_itcm_shadow(adreno_dev);
|
|
if (ret)
|
|
goto clks_gdsc_off;
|
|
|
|
ret = gen7_scm_gpu_init_cx_regs(adreno_dev);
|
|
if (ret)
|
|
goto clks_gdsc_off;
|
|
|
|
gen7_gmu_register_config(adreno_dev);
|
|
|
|
gen7_gmu_irq_enable(adreno_dev);
|
|
|
|
/* Vote for minimal DDR BW for GMU to init */
|
|
level = pwr->pwrlevels[pwr->default_pwrlevel].bus_min;
|
|
icc_set_bw(pwr->icc_path, 0, kBps_to_icc(pwr->ddr_table[level]));
|
|
|
|
/* Clear any GPU faults that might have been left over */
|
|
adreno_clear_gpu_fault(adreno_dev);
|
|
|
|
ret = gen7_gmu_device_start(adreno_dev);
|
|
if (ret)
|
|
goto err;
|
|
|
|
if (!test_bit(GMU_PRIV_PDC_RSC_LOADED, &gmu->flags)) {
|
|
ret = gen7_load_pdc_ucode(adreno_dev);
|
|
if (ret)
|
|
goto err;
|
|
|
|
gen7_load_rsc_ucode(adreno_dev);
|
|
set_bit(GMU_PRIV_PDC_RSC_LOADED, &gmu->flags);
|
|
}
|
|
|
|
ret = gen7_gmu_hfi_start(adreno_dev);
|
|
if (ret)
|
|
goto err;
|
|
|
|
gen7_get_gpu_feature_info(adreno_dev);
|
|
|
|
ret = gen7_hfi_start(adreno_dev);
|
|
if (ret)
|
|
goto err;
|
|
|
|
if (gen7_hfi_send_get_value(adreno_dev, HFI_VALUE_GMU_AB_VOTE, 0) == 1 &&
|
|
!WARN_ONCE(!adreno_dev->gpucore->num_ddr_channels,
|
|
"Number of DDR channel is not specified in gpu core")) {
|
|
adreno_dev->gmu_ab = true;
|
|
set_bit(ADRENO_DEVICE_GMU_AB, &adreno_dev->priv);
|
|
}
|
|
|
|
icc_set_bw(pwr->icc_path, 0, 0);
|
|
|
|
device->gmu_fault = false;
|
|
|
|
kgsl_pwrctrl_set_state(device, KGSL_STATE_AWARE);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
gen7_gmu_irq_disable(adreno_dev);
|
|
|
|
if (device->gmu_fault) {
|
|
gen7_gmu_suspend(adreno_dev);
|
|
return ret;
|
|
}
|
|
|
|
clks_gdsc_off:
|
|
clk_bulk_disable_unprepare(gmu->num_clks, gmu->clks);
|
|
|
|
gdsc_off:
|
|
kgsl_pwrctrl_disable_cx_gdsc(device);
|
|
|
|
gen7_rdpm_cx_freq_update(gmu, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gen7_gmu_boot(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret = 0;
|
|
|
|
kgsl_pwrctrl_request_state(device, KGSL_STATE_AWARE);
|
|
|
|
ret = kgsl_pwrctrl_enable_cx_gdsc(device);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_gmu_enable_clks(adreno_dev, 0);
|
|
if (ret)
|
|
goto gdsc_off;
|
|
|
|
/*
|
|
* Enable AHB timeout detection to catch any register access taking longer
|
|
* time before NOC timeout gets detected. Enable this logic before any
|
|
* register access which happens to be just after enabling clocks.
|
|
*/
|
|
gen7_enable_ahb_timeout_detection(adreno_dev);
|
|
|
|
/* Initialize the CX timer */
|
|
gen7_cx_timer_init(adreno_dev);
|
|
|
|
ret = gen7_rscc_wakeup_sequence(adreno_dev);
|
|
if (ret)
|
|
goto clks_gdsc_off;
|
|
|
|
ret = gen7_gmu_load_fw(adreno_dev);
|
|
if (ret)
|
|
goto clks_gdsc_off;
|
|
|
|
gen7_gmu_register_config(adreno_dev);
|
|
|
|
gen7_gmu_irq_enable(adreno_dev);
|
|
|
|
/* Clear any GPU faults that might have been left over */
|
|
adreno_clear_gpu_fault(adreno_dev);
|
|
|
|
ret = gen7_gmu_device_start(adreno_dev);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = gen7_gmu_hfi_start(adreno_dev);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = gen7_hfi_start(adreno_dev);
|
|
if (ret)
|
|
goto err;
|
|
|
|
device->gmu_fault = false;
|
|
|
|
kgsl_pwrctrl_set_state(device, KGSL_STATE_AWARE);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
gen7_gmu_irq_disable(adreno_dev);
|
|
|
|
if (device->gmu_fault) {
|
|
gen7_gmu_suspend(adreno_dev);
|
|
return ret;
|
|
}
|
|
|
|
clks_gdsc_off:
|
|
clk_bulk_disable_unprepare(gmu->num_clks, gmu->clks);
|
|
|
|
gdsc_off:
|
|
kgsl_pwrctrl_disable_cx_gdsc(device);
|
|
|
|
gen7_rdpm_cx_freq_update(gmu, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void set_acd(struct adreno_device *adreno_dev, void *priv)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
|
|
adreno_dev->acd_enabled = *((bool *)priv);
|
|
gen7_gmu_aop_send_acd_state(gmu, adreno_dev->acd_enabled);
|
|
}
|
|
|
|
static int gen7_gmu_acd_set(struct kgsl_device *device, bool val)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
|
|
if (IS_ERR_OR_NULL(gmu->mailbox.channel))
|
|
return -EINVAL;
|
|
|
|
/* Don't do any unneeded work if ACD is already in the correct state */
|
|
if (adreno_dev->acd_enabled == val)
|
|
return 0;
|
|
|
|
/* Power cycle the GPU for changes to take effect */
|
|
return adreno_power_cycle(adreno_dev, set_acd, &val);
|
|
}
|
|
|
|
#define BCL_RESP_TYPE_MASK BIT(0)
|
|
#define BCL_SID0_MASK GENMASK(7, 1)
|
|
#define BCL_SID1_MASK GENMASK(14, 8)
|
|
#define BCL_SID2_MASK GENMASK(21, 15)
|
|
|
|
static int gen7_bcl_sid_set(struct kgsl_device *device, u32 sid_id, u64 sid_val)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
u32 bcl_data, val = (u32) sid_val;
|
|
|
|
if (!ADRENO_FEATURE(adreno_dev, ADRENO_BCL) ||
|
|
!FIELD_GET(BCL_RESP_TYPE_MASK, adreno_dev->bcl_data))
|
|
return -EINVAL;
|
|
|
|
switch (sid_id) {
|
|
case 0:
|
|
adreno_dev->bcl_data &= ~BCL_SID0_MASK;
|
|
bcl_data = adreno_dev->bcl_data | FIELD_PREP(BCL_SID0_MASK, val);
|
|
break;
|
|
case 1:
|
|
adreno_dev->bcl_data &= ~BCL_SID1_MASK;
|
|
bcl_data = adreno_dev->bcl_data | FIELD_PREP(BCL_SID1_MASK, val);
|
|
break;
|
|
case 2:
|
|
adreno_dev->bcl_data &= ~BCL_SID2_MASK;
|
|
bcl_data = adreno_dev->bcl_data | FIELD_PREP(BCL_SID2_MASK, val);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return adreno_power_cycle_u32(adreno_dev, &adreno_dev->bcl_data, bcl_data);
|
|
}
|
|
|
|
static u64 gen7_bcl_sid_get(struct kgsl_device *device, u32 sid_id)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
|
|
if (!ADRENO_FEATURE(adreno_dev, ADRENO_BCL) ||
|
|
!FIELD_GET(BCL_RESP_TYPE_MASK, adreno_dev->bcl_data))
|
|
return 0;
|
|
|
|
switch (sid_id) {
|
|
case 0:
|
|
return ((u64) FIELD_GET(BCL_SID0_MASK, adreno_dev->bcl_data));
|
|
case 1:
|
|
return ((u64) FIELD_GET(BCL_SID1_MASK, adreno_dev->bcl_data));
|
|
case 2:
|
|
return ((u64) FIELD_GET(BCL_SID2_MASK, adreno_dev->bcl_data));
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void gen7_send_tlb_hint(struct kgsl_device *device, bool val)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
|
|
if (!gmu->domain)
|
|
return;
|
|
|
|
#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE)
|
|
qcom_skip_tlb_management(&gmu->pdev->dev, val);
|
|
#endif
|
|
if (!val)
|
|
iommu_flush_iotlb_all(gmu->domain);
|
|
}
|
|
|
|
static const struct gmu_dev_ops gen7_gmudev = {
|
|
.oob_set = gen7_gmu_oob_set,
|
|
.oob_clear = gen7_gmu_oob_clear,
|
|
.ifpc_store = gen7_gmu_ifpc_store,
|
|
.ifpc_isenabled = gen7_gmu_ifpc_isenabled,
|
|
.cooperative_reset = gen7_gmu_cooperative_reset,
|
|
.wait_for_active_transition = gen7_gmu_wait_for_active_transition,
|
|
.scales_bandwidth = gen7_gmu_scales_bandwidth,
|
|
.acd_set = gen7_gmu_acd_set,
|
|
.bcl_sid_set = gen7_bcl_sid_set,
|
|
.bcl_sid_get = gen7_bcl_sid_get,
|
|
.send_nmi = gen7_gmu_send_nmi,
|
|
.send_tlb_hint = gen7_send_tlb_hint,
|
|
};
|
|
|
|
static int gen7_gmu_bus_set(struct adreno_device *adreno_dev, int buslevel,
|
|
u32 ab)
|
|
{
|
|
const struct adreno_gen7_core *gen7_core = to_gen7_core(adreno_dev);
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
|
|
int ret = 0;
|
|
|
|
/* Skip icc path for targets that supports ACV vote from GMU */
|
|
if (!gen7_core->acv_perfmode_vote)
|
|
kgsl_icc_set_tag(pwr, buslevel);
|
|
|
|
if (buslevel == pwr->cur_buslevel)
|
|
buslevel = INVALID_DCVS_IDX;
|
|
|
|
if ((ab == pwr->cur_ab) || (ab == 0))
|
|
ab = INVALID_AB_VALUE;
|
|
|
|
if ((ab == INVALID_AB_VALUE) && (buslevel == INVALID_DCVS_IDX))
|
|
return 0;
|
|
|
|
ret = gen7_gmu_dcvs_set(adreno_dev, INVALID_DCVS_IDX,
|
|
buslevel, ab);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (buslevel != INVALID_DCVS_IDX)
|
|
pwr->cur_buslevel = buslevel;
|
|
|
|
if (ab != INVALID_AB_VALUE) {
|
|
if (!adreno_dev->gmu_ab)
|
|
icc_set_bw(pwr->icc_path, MBps_to_icc(ab), 0);
|
|
pwr->cur_ab = ab;
|
|
}
|
|
|
|
trace_kgsl_buslevel(device, pwr->active_pwrlevel, pwr->cur_buslevel, pwr->cur_ab);
|
|
return ret;
|
|
}
|
|
|
|
u32 gen7_bus_ab_quantize(struct adreno_device *adreno_dev, u32 ab)
|
|
{
|
|
u16 vote = 0;
|
|
u32 max_bw, max_ab;
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
|
|
|
|
if (!adreno_dev->gmu_ab || (ab == INVALID_AB_VALUE))
|
|
return (FIELD_PREP(GENMASK(31, 16), INVALID_AB_VALUE));
|
|
|
|
/*
|
|
* max ddr bandwidth (kbps) = (Max bw in kbps per channel * number of channel)
|
|
* max ab (Mbps) = max ddr bandwidth (kbps) / 1000
|
|
*/
|
|
max_bw = pwr->ddr_table[pwr->ddr_table_count - 1] * adreno_dev->gpucore->num_ddr_channels;
|
|
max_ab = max_bw / 1000;
|
|
|
|
/*
|
|
* If requested AB is higher than theoretical max bandwidth, set AB vote as max
|
|
* allowable quantized AB value.
|
|
*
|
|
* Power FW supports a 16 bit AB BW level. We can quantize the entire vote-able BW
|
|
* range to a 16 bit space and the quantized value can be used to vote for AB though
|
|
* GMU. Quantization can be performed as below.
|
|
*
|
|
* quantized_vote = (ab vote (kbps) * 2^16) / max ddr bandwidth (kbps)
|
|
*/
|
|
if (ab >= max_ab)
|
|
vote = MAX_AB_VALUE;
|
|
else
|
|
vote = (u16)(((u64)ab * 1000 * (1 << 16)) / max_bw);
|
|
|
|
/*
|
|
* Vote will be calculated as 0 for smaller AB values.
|
|
* Set a minimum non-zero vote in such cases.
|
|
*/
|
|
if (ab && !vote)
|
|
vote = 0x1;
|
|
|
|
/*
|
|
* Set ab enable mask and valid AB vote. req.bw is 32 bit value 0xABABENIB
|
|
* and with this return we want to set the upper 16 bits and EN field specifies
|
|
* if the AB vote is valid or not.
|
|
*/
|
|
return (FIELD_PREP(GENMASK(31, 16), vote) | FIELD_PREP(GENMASK(15, 8), 1));
|
|
}
|
|
|
|
static void gen7_free_gmu_globals(struct gen7_gmu_device *gmu)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < gmu->global_entries && i < ARRAY_SIZE(gmu->gmu_globals); i++) {
|
|
struct kgsl_memdesc *md = &gmu->gmu_globals[i];
|
|
|
|
if (!md->gmuaddr)
|
|
continue;
|
|
|
|
iommu_unmap(gmu->domain, md->gmuaddr, md->size);
|
|
|
|
if (md->priv & KGSL_MEMDESC_SYSMEM)
|
|
kgsl_sharedmem_free(md);
|
|
|
|
memset(md, 0, sizeof(*md));
|
|
}
|
|
|
|
if (gmu->domain) {
|
|
iommu_detach_device(gmu->domain, &gmu->pdev->dev);
|
|
iommu_domain_free(gmu->domain);
|
|
gmu->domain = NULL;
|
|
}
|
|
|
|
gmu->global_entries = 0;
|
|
}
|
|
|
|
static int gen7_gmu_aop_mailbox_init(struct adreno_device *adreno_dev,
|
|
struct gen7_gmu_device *gmu)
|
|
{
|
|
struct kgsl_mailbox *mailbox = &gmu->mailbox;
|
|
|
|
mailbox->client.dev = &gmu->pdev->dev;
|
|
mailbox->client.tx_block = true;
|
|
mailbox->client.tx_tout = 1000;
|
|
mailbox->client.knows_txdone = false;
|
|
|
|
mailbox->channel = mbox_request_channel(&mailbox->client, 0);
|
|
if (IS_ERR(mailbox->channel))
|
|
return PTR_ERR(mailbox->channel);
|
|
|
|
adreno_dev->acd_enabled = true;
|
|
return 0;
|
|
}
|
|
|
|
static void gen7_gmu_acd_probe(struct kgsl_device *device,
|
|
struct gen7_gmu_device *gmu, struct device_node *node)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct kgsl_pwrctrl *pwr = &device->pwrctrl;
|
|
struct kgsl_pwrlevel *pwrlevel =
|
|
&pwr->pwrlevels[pwr->num_pwrlevels - 1];
|
|
struct hfi_acd_table_cmd *cmd = &gmu->hfi.acd_table;
|
|
int ret, i, cmd_idx = 0;
|
|
|
|
if (!ADRENO_FEATURE(adreno_dev, ADRENO_ACD))
|
|
return;
|
|
|
|
cmd->hdr = CREATE_MSG_HDR(H2F_MSG_ACD_TBL, HFI_MSG_CMD);
|
|
|
|
cmd->version = 1;
|
|
cmd->stride = 1;
|
|
cmd->enable_by_level = 0;
|
|
|
|
/*
|
|
* Iterate through each gpu power level and generate a mask for GMU
|
|
* firmware for ACD enabled levels and store the corresponding control
|
|
* register configurations to the acd_table structure.
|
|
*/
|
|
for (i = 0; i < pwr->num_pwrlevels; i++) {
|
|
if (pwrlevel->acd_level) {
|
|
cmd->enable_by_level |= (1 << (i + 1));
|
|
cmd->data[cmd_idx++] = pwrlevel->acd_level;
|
|
}
|
|
pwrlevel--;
|
|
}
|
|
|
|
if (!cmd->enable_by_level)
|
|
return;
|
|
|
|
cmd->num_levels = cmd_idx;
|
|
|
|
ret = gen7_gmu_aop_mailbox_init(adreno_dev, gmu);
|
|
if (ret)
|
|
dev_err(&gmu->pdev->dev,
|
|
"AOP mailbox init failed: %d\n", ret);
|
|
}
|
|
|
|
static int gen7_gmu_reg_probe(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret;
|
|
|
|
ret = kgsl_regmap_add_region(&device->regmap, gmu->pdev, "gmu", NULL, NULL);
|
|
|
|
if (ret)
|
|
dev_err(&gmu->pdev->dev, "Unable to map the GMU registers\n");
|
|
/*
|
|
* gmu_ao_blk_dec1 and gmu_ao_blk_dec2 are contiguous and contained within the gmu region
|
|
* mapped above. gmu_ao_blk_dec0 is not within the gmu region and is mapped separately.
|
|
*/
|
|
kgsl_regmap_add_region(&device->regmap, gmu->pdev, "gmu_ao_blk_dec0", NULL, NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gen7_gmu_clk_probe(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret, i;
|
|
int tbl_size;
|
|
int num_freqs;
|
|
int offset;
|
|
|
|
ret = devm_clk_bulk_get_all(&gmu->pdev->dev, &gmu->clks);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/*
|
|
* Voting for apb_pclk will enable power and clocks required for
|
|
* QDSS path to function. However, if QCOM_KGSL_QDSS_STM is not enabled,
|
|
* QDSS is essentially unusable. Hence, if QDSS cannot be used,
|
|
* don't vote for this clock.
|
|
*/
|
|
if (!IS_ENABLED(CONFIG_QCOM_KGSL_QDSS_STM)) {
|
|
for (i = 0; i < ret; i++) {
|
|
if (!strcmp(gmu->clks[i].id, "apb_pclk")) {
|
|
gmu->clks[i].clk = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gmu->num_clks = ret;
|
|
|
|
/* Read the optional list of GMU frequencies */
|
|
if (of_get_property(gmu->pdev->dev.of_node,
|
|
"qcom,gmu-freq-table", &tbl_size) == NULL)
|
|
goto default_gmu_freq;
|
|
|
|
num_freqs = (tbl_size / sizeof(u32)) / 2;
|
|
if (num_freqs != ARRAY_SIZE(gmu->freqs))
|
|
goto default_gmu_freq;
|
|
|
|
for (i = 0; i < num_freqs; i++) {
|
|
offset = i * 2;
|
|
ret = of_property_read_u32_index(gmu->pdev->dev.of_node,
|
|
"qcom,gmu-freq-table", offset, &gmu->freqs[i]);
|
|
if (ret)
|
|
goto default_gmu_freq;
|
|
ret = of_property_read_u32_index(gmu->pdev->dev.of_node,
|
|
"qcom,gmu-freq-table", offset + 1, &gmu->vlvls[i]);
|
|
if (ret)
|
|
goto default_gmu_freq;
|
|
}
|
|
return 0;
|
|
|
|
default_gmu_freq:
|
|
/* The GMU frequency table is missing or invalid. Go with a default */
|
|
gmu->freqs[0] = GMU_FREQ_MIN;
|
|
gmu->vlvls[0] = RPMH_REGULATOR_LEVEL_LOW_SVS;
|
|
gmu->freqs[1] = GMU_FREQ_MAX;
|
|
gmu->vlvls[1] = RPMH_REGULATOR_LEVEL_SVS;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void gen7_gmu_rdpm_probe(struct gen7_gmu_device *gmu,
|
|
struct kgsl_device *device)
|
|
{
|
|
struct resource *res;
|
|
|
|
res = platform_get_resource_byname(device->pdev, IORESOURCE_MEM, "rdpm_cx");
|
|
if (res)
|
|
gmu->rdpm_cx_virt = devm_ioremap(&device->pdev->dev,
|
|
res->start, resource_size(res));
|
|
|
|
res = platform_get_resource_byname(device->pdev, IORESOURCE_MEM, "rdpm_mx");
|
|
if (res)
|
|
gmu->rdpm_mx_virt = devm_ioremap(&device->pdev->dev,
|
|
res->start, resource_size(res));
|
|
}
|
|
|
|
void gen7_gmu_remove(struct kgsl_device *device)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
|
|
if (!IS_ERR_OR_NULL(gmu->mailbox.channel))
|
|
mbox_free_channel(gmu->mailbox.channel);
|
|
|
|
adreno_dev->acd_enabled = false;
|
|
|
|
if (gmu->fw_image)
|
|
release_firmware(gmu->fw_image);
|
|
|
|
gen7_free_gmu_globals(gmu);
|
|
|
|
vfree(gmu->itcm_shadow);
|
|
if (gmu->log_kobj.state_initialized)
|
|
kobject_put(&gmu->log_kobj);
|
|
if (gmu->stats_kobj.state_initialized)
|
|
kobject_put(&gmu->stats_kobj);
|
|
}
|
|
|
|
static int gen7_gmu_iommu_fault_handler(struct iommu_domain *domain,
|
|
struct device *dev, unsigned long addr, int flags, void *token)
|
|
{
|
|
char *fault_type = "unknown";
|
|
|
|
if (flags & IOMMU_FAULT_TRANSLATION)
|
|
fault_type = "translation";
|
|
else if (flags & IOMMU_FAULT_PERMISSION)
|
|
fault_type = "permission";
|
|
else if (flags & IOMMU_FAULT_EXTERNAL)
|
|
fault_type = "external";
|
|
else if (flags & IOMMU_FAULT_TRANSACTION_STALLED)
|
|
fault_type = "transaction stalled";
|
|
|
|
dev_err(dev, "GMU fault addr = %lX, context=kernel (%s %s fault)\n",
|
|
addr,
|
|
(flags & IOMMU_FAULT_WRITE) ? "write" : "read",
|
|
fault_type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gen7_gmu_iommu_init(struct gen7_gmu_device *gmu)
|
|
{
|
|
int ret;
|
|
|
|
gmu->domain = iommu_domain_alloc(&platform_bus_type);
|
|
if (gmu->domain == NULL) {
|
|
dev_err(&gmu->pdev->dev, "Unable to allocate GMU IOMMU domain\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/*
|
|
* Disable stall on fault for the GMU context bank.
|
|
* This sets SCTLR.CFCFG = 0.
|
|
* Also note that, the smmu driver sets SCTLR.HUPCF = 0 by default.
|
|
*/
|
|
qcom_iommu_set_fault_model(gmu->domain, QCOM_IOMMU_FAULT_MODEL_NO_STALL);
|
|
|
|
ret = iommu_attach_device(gmu->domain, &gmu->pdev->dev);
|
|
if (!ret) {
|
|
iommu_set_fault_handler(gmu->domain,
|
|
gen7_gmu_iommu_fault_handler, gmu);
|
|
return 0;
|
|
}
|
|
|
|
dev_err(&gmu->pdev->dev,
|
|
"Unable to attach GMU IOMMU domain: %d\n", ret);
|
|
iommu_domain_free(gmu->domain);
|
|
gmu->domain = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Default IFPC timer (300usec) value */
|
|
#define GEN7_GMU_LONG_IFPC_HYST FIELD_PREP(GENMASK(15, 0), 0x1680)
|
|
|
|
/* Minimum IFPC timer (200usec) allowed to override default value */
|
|
#define GEN7_GMU_LONG_IFPC_HYST_FLOOR FIELD_PREP(GENMASK(15, 0), 0x0F00)
|
|
|
|
int gen7_gmu_probe(struct kgsl_device *device,
|
|
struct platform_device *pdev)
|
|
{
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct device *dev = &pdev->dev;
|
|
struct resource *res;
|
|
int ret, i;
|
|
|
|
gmu->pdev = pdev;
|
|
|
|
dma_set_coherent_mask(&gmu->pdev->dev, DMA_BIT_MASK(64));
|
|
gmu->pdev->dev.dma_mask = &gmu->pdev->dev.coherent_dma_mask;
|
|
set_dma_ops(&gmu->pdev->dev, NULL);
|
|
|
|
res = platform_get_resource_byname(device->pdev, IORESOURCE_MEM,
|
|
"rscc");
|
|
if (res) {
|
|
gmu->rscc_virt = devm_ioremap(&device->pdev->dev, res->start,
|
|
resource_size(res));
|
|
if (!gmu->rscc_virt) {
|
|
dev_err(&gmu->pdev->dev, "rscc ioremap failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
/* Setup any rdpm register ranges */
|
|
gen7_gmu_rdpm_probe(gmu, device);
|
|
|
|
/* Set up GMU regulators */
|
|
ret = kgsl_pwrctrl_probe_regulators(device, pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_gmu_clk_probe(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Set up GMU IOMMU and shared memory with GMU */
|
|
ret = gen7_gmu_iommu_init(gmu);
|
|
if (ret)
|
|
goto error;
|
|
|
|
gmu->vma = gen7_gmu_vma;
|
|
for (i = 0; i < ARRAY_SIZE(gen7_gmu_vma); i++) {
|
|
struct gmu_vma_entry *vma = &gen7_gmu_vma[i];
|
|
|
|
vma->vma_root = RB_ROOT;
|
|
spin_lock_init(&vma->lock);
|
|
}
|
|
|
|
/* Map and reserve GMU CSRs registers */
|
|
ret = gen7_gmu_reg_probe(adreno_dev);
|
|
if (ret)
|
|
goto error;
|
|
|
|
/* Populates RPMh configurations */
|
|
ret = gen7_build_rpmh_tables(adreno_dev);
|
|
if (ret)
|
|
goto error;
|
|
|
|
/* Set up GMU idle state */
|
|
if (ADRENO_FEATURE(adreno_dev, ADRENO_IFPC)) {
|
|
gmu->idle_level = GPU_HW_IFPC;
|
|
adreno_dev->ifpc_hyst = GEN7_GMU_LONG_IFPC_HYST;
|
|
adreno_dev->ifpc_hyst_floor = GEN7_GMU_LONG_IFPC_HYST_FLOOR;
|
|
} else {
|
|
gmu->idle_level = GPU_HW_ACTIVE;
|
|
}
|
|
|
|
gen7_gmu_acd_probe(device, gmu, pdev->dev.of_node);
|
|
|
|
set_bit(GMU_ENABLED, &device->gmu_core.flags);
|
|
|
|
device->gmu_core.dev_ops = &gen7_gmudev;
|
|
|
|
/* Set default GMU attributes */
|
|
gmu->log_stream_enable = false;
|
|
gmu->log_group_mask = 0x3;
|
|
|
|
/* Initialize to zero to detect trace packet loss */
|
|
gmu->trace.seq_num = 0;
|
|
|
|
/* Disabled by default */
|
|
gmu->stats_enable = false;
|
|
/* Set default to CM3 busy cycles countable */
|
|
gmu->stats_mask = BIT(GEN7_GMU_CM3_BUSY_CYCLES);
|
|
/* Interval is in 50 us units. Set default sampling frequency to 4x50 us */
|
|
gmu->stats_interval = HFI_FEATURE_GMU_STATS_INTERVAL;
|
|
|
|
/* GMU sysfs nodes setup */
|
|
(void) kobject_init_and_add(&gmu->log_kobj, &log_kobj_type, &dev->kobj, "log");
|
|
(void) kobject_init_and_add(&gmu->stats_kobj, &stats_kobj_type, &dev->kobj, "stats");
|
|
|
|
of_property_read_u32(gmu->pdev->dev.of_node, "qcom,gmu-perf-ddr-bw",
|
|
&gmu->perf_ddr_bw);
|
|
|
|
spin_lock_init(&gmu->hfi.cmdq_lock);
|
|
|
|
gmu->irq = kgsl_request_irq(gmu->pdev, "gmu",
|
|
gen7_gmu_irq_handler, device);
|
|
|
|
if (gmu->irq >= 0)
|
|
return 0;
|
|
|
|
ret = gmu->irq;
|
|
|
|
error:
|
|
gen7_gmu_remove(device);
|
|
return ret;
|
|
}
|
|
|
|
static void gen7_gmu_active_count_put(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
|
|
if (WARN_ON(!mutex_is_locked(&device->mutex)))
|
|
return;
|
|
|
|
if (WARN(atomic_read(&device->active_cnt) == 0,
|
|
"Unbalanced get/put calls to KGSL active count\n"))
|
|
return;
|
|
|
|
if (atomic_dec_and_test(&device->active_cnt)) {
|
|
kgsl_pwrscale_update_stats(device);
|
|
kgsl_pwrscale_update(device);
|
|
kgsl_start_idle_timer(device);
|
|
}
|
|
|
|
trace_kgsl_active_count(device,
|
|
(unsigned long) __builtin_return_address(0));
|
|
|
|
wake_up(&device->active_cnt_wq);
|
|
}
|
|
|
|
int gen7_halt_gbif(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
int ret;
|
|
|
|
/* Halt new client requests */
|
|
kgsl_regwrite(device, GEN7_GBIF_HALT, GEN7_GBIF_CLIENT_HALT_MASK);
|
|
ret = adreno_wait_for_halt_ack(device,
|
|
GEN7_GBIF_HALT_ACK, GEN7_GBIF_CLIENT_HALT_MASK);
|
|
|
|
/* Halt all AXI requests */
|
|
kgsl_regwrite(device, GEN7_GBIF_HALT, GEN7_GBIF_ARB_HALT_MASK);
|
|
ret = adreno_wait_for_halt_ack(device,
|
|
GEN7_GBIF_HALT_ACK, GEN7_GBIF_ARB_HALT_MASK);
|
|
|
|
/* De-assert the halts */
|
|
kgsl_regwrite(device, GEN7_GBIF_HALT, 0x0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gen7_gmu_power_off(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
int ret = 0;
|
|
|
|
if (device->gmu_fault)
|
|
goto error;
|
|
|
|
/* Wait for the lowest idle level we requested */
|
|
ret = gen7_gmu_wait_for_lowest_idle(adreno_dev);
|
|
if (ret)
|
|
goto error;
|
|
|
|
ret = gen7_complete_rpmh_votes(gmu, 2);
|
|
if (ret)
|
|
goto error;
|
|
|
|
ret = gen7_gmu_notify_slumber(adreno_dev);
|
|
if (ret)
|
|
goto error;
|
|
|
|
ret = gen7_gmu_wait_for_idle(adreno_dev);
|
|
if (ret)
|
|
goto error;
|
|
|
|
ret = gen7_rscc_sleep_sequence(adreno_dev);
|
|
if (ret)
|
|
goto error;
|
|
|
|
gen7_rdpm_mx_freq_update(gmu, 0);
|
|
|
|
/* Now that we are done with GMU and GPU, Clear the GBIF */
|
|
ret = gen7_halt_gbif(adreno_dev);
|
|
if (ret)
|
|
goto error;
|
|
|
|
gen7_gmu_irq_disable(adreno_dev);
|
|
|
|
gen7_hfi_stop(adreno_dev);
|
|
|
|
clk_bulk_disable_unprepare(gmu->num_clks, gmu->clks);
|
|
|
|
kgsl_pwrctrl_disable_cx_gdsc(device);
|
|
|
|
gen7_rdpm_cx_freq_update(gmu, 0);
|
|
|
|
kgsl_pwrctrl_set_state(device, KGSL_STATE_NONE);
|
|
|
|
return 0;
|
|
|
|
error:
|
|
gen7_gmu_irq_disable(adreno_dev);
|
|
gen7_hfi_stop(adreno_dev);
|
|
gen7_gmu_suspend(adreno_dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void gen7_enable_gpu_irq(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
|
|
kgsl_pwrctrl_irq(device, true);
|
|
|
|
adreno_irqctrl(adreno_dev, 1);
|
|
}
|
|
|
|
void gen7_disable_gpu_irq(struct adreno_device *adreno_dev)
|
|
{
|
|
kgsl_pwrctrl_irq(KGSL_DEVICE(adreno_dev), false);
|
|
|
|
if (gen7_gmu_gx_is_on(adreno_dev))
|
|
adreno_irqctrl(adreno_dev, 0);
|
|
}
|
|
|
|
static int gen7_gpu_boot(struct adreno_device *adreno_dev)
|
|
{
|
|
const struct adreno_gen7_core *gen7_core = to_gen7_core(adreno_dev);
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
int ret;
|
|
|
|
adreno_set_active_ctxs_null(adreno_dev);
|
|
|
|
ret = kgsl_mmu_start(device);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = gen7_gmu_oob_set(device, oob_gpu);
|
|
if (ret)
|
|
goto oob_clear;
|
|
|
|
ret = gen7_gmu_hfi_start_msg(adreno_dev);
|
|
if (ret)
|
|
goto oob_clear;
|
|
|
|
/* Clear the busy_data stats - we're starting over from scratch */
|
|
memset(&adreno_dev->busy_data, 0, sizeof(adreno_dev->busy_data));
|
|
|
|
gen7_start(adreno_dev);
|
|
|
|
if (gen7_core->qos_value && adreno_is_preemption_enabled(adreno_dev))
|
|
kgsl_regwrite(device, GEN7_RBBM_GBIF_CLIENT_QOS_CNTL,
|
|
gen7_core->qos_value[adreno_dev->cur_rb->id]);
|
|
|
|
/* Re-initialize the coresight registers if applicable */
|
|
adreno_coresight_start(adreno_dev);
|
|
|
|
adreno_perfcounter_start(adreno_dev);
|
|
|
|
/* Clear FSR here in case it is set from a previous pagefault */
|
|
kgsl_mmu_clear_fsr(&device->mmu);
|
|
|
|
gen7_enable_gpu_irq(adreno_dev);
|
|
|
|
ret = gen7_rb_start(adreno_dev);
|
|
if (ret) {
|
|
gen7_disable_gpu_irq(adreno_dev);
|
|
goto oob_clear;
|
|
}
|
|
|
|
/*
|
|
* At this point it is safe to assume that we recovered. Setting
|
|
* this field allows us to take a new snapshot for the next failure
|
|
* if we are prioritizing the first unrecoverable snapshot.
|
|
*/
|
|
if (device->snapshot)
|
|
device->snapshot->recovered = true;
|
|
|
|
/* Start the dispatcher */
|
|
adreno_dispatcher_start(device);
|
|
|
|
device->reset_counter++;
|
|
|
|
gen7_gmu_oob_clear(device, oob_gpu);
|
|
|
|
return 0;
|
|
|
|
oob_clear:
|
|
gen7_gmu_oob_clear(device, oob_gpu);
|
|
|
|
err:
|
|
gen7_gmu_power_off(adreno_dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void gmu_idle_timer(struct timer_list *t)
|
|
{
|
|
struct kgsl_device *device = container_of(t, struct kgsl_device,
|
|
idle_timer);
|
|
|
|
kgsl_schedule_work(&device->idle_check_ws);
|
|
}
|
|
|
|
static int gen7_boot(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
int ret;
|
|
|
|
if (WARN_ON(test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags)))
|
|
return 0;
|
|
|
|
kgsl_pwrctrl_request_state(device, KGSL_STATE_ACTIVE);
|
|
|
|
ret = gen7_gmu_boot(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_gpu_boot(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
kgsl_start_idle_timer(device);
|
|
kgsl_pwrscale_wake(device);
|
|
|
|
set_bit(GMU_PRIV_GPU_STARTED, &gmu->flags);
|
|
|
|
device->pwrctrl.last_stat_updated = ktime_get();
|
|
|
|
kgsl_pwrctrl_set_state(device, KGSL_STATE_ACTIVE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gen7_first_boot(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret;
|
|
|
|
if (test_bit(GMU_PRIV_FIRST_BOOT_DONE, &gmu->flags)) {
|
|
if (!test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags))
|
|
return gen7_boot(adreno_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
ret = gen7_ringbuffer_init(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_microcode_read(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_init(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_gmu_init(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
kgsl_pwrctrl_request_state(device, KGSL_STATE_ACTIVE);
|
|
|
|
ret = gen7_gmu_first_boot(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = gen7_gpu_boot(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
adreno_get_bus_counters(adreno_dev);
|
|
|
|
adreno_dev->cooperative_reset = ADRENO_FEATURE(adreno_dev,
|
|
ADRENO_COOP_RESET);
|
|
|
|
adreno_create_profile_buffer(adreno_dev);
|
|
|
|
set_bit(GMU_PRIV_FIRST_BOOT_DONE, &gmu->flags);
|
|
set_bit(GMU_PRIV_GPU_STARTED, &gmu->flags);
|
|
|
|
/*
|
|
* BCL needs respective Central Broadcast register to
|
|
* be programed from TZ. For kernel version prior to 6.1, this
|
|
* programing happens only when zap shader firmware load is successful.
|
|
* Zap firmware load can fail in boot up path hence enable BCL only
|
|
* after we successfully complete first boot to ensure that Central
|
|
* Broadcast register was programed before enabling BCL.
|
|
*/
|
|
if (ADRENO_FEATURE(adreno_dev, ADRENO_BCL))
|
|
adreno_dev->bcl_enabled = true;
|
|
|
|
/*
|
|
* There is a possible deadlock scenario during kgsl firmware reading
|
|
* (request_firmware) and devfreq update calls. During first boot, kgsl
|
|
* device mutex is held and then request_firmware is called for reading
|
|
* firmware. request_firmware internally takes dev_pm_qos_mtx lock.
|
|
* Whereas in case of devfreq update calls triggered by thermal/bcl or
|
|
* devfreq sysfs, it first takes the same dev_pm_qos_mtx lock and then
|
|
* tries to take kgsl device mutex as part of get_dev_status/target
|
|
* calls. This results in deadlock when both thread are unable to acquire
|
|
* the mutex held by other thread. Enable devfreq updates now as we are
|
|
* done reading all firmware files.
|
|
*/
|
|
device->pwrscale.devfreq_enabled = true;
|
|
|
|
device->pwrctrl.last_stat_updated = ktime_get();
|
|
|
|
kgsl_pwrctrl_set_state(device, KGSL_STATE_ACTIVE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool gen7_irq_pending(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
u32 status;
|
|
|
|
kgsl_regread(device, GEN7_RBBM_INT_0_STATUS, &status);
|
|
|
|
/* Return busy if a interrupt is pending */
|
|
return ((status & adreno_dev->irq_mask) ||
|
|
atomic_read(&adreno_dev->pending_irq_refcnt));
|
|
}
|
|
|
|
static int gen7_power_off(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret;
|
|
|
|
WARN_ON(!test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags));
|
|
|
|
adreno_suspend_context(device);
|
|
|
|
/*
|
|
* adreno_suspend_context() unlocks the device mutex, which
|
|
* could allow a concurrent thread to attempt SLUMBER sequence.
|
|
* Hence, check the flags again before proceeding with SLUMBER.
|
|
*/
|
|
if (!test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags))
|
|
return 0;
|
|
|
|
kgsl_pwrctrl_request_state(device, KGSL_STATE_SLUMBER);
|
|
|
|
ret = gen7_gmu_oob_set(device, oob_gpu);
|
|
if (ret)
|
|
goto no_gx_power;
|
|
|
|
if (gen7_irq_pending(adreno_dev)) {
|
|
gen7_gmu_oob_clear(device, oob_gpu);
|
|
return -EBUSY;
|
|
}
|
|
|
|
kgsl_pwrscale_update_stats(device);
|
|
|
|
/* Save active coresight registers if applicable */
|
|
adreno_coresight_stop(adreno_dev);
|
|
|
|
adreno_irqctrl(adreno_dev, 0);
|
|
|
|
no_gx_power:
|
|
gen7_gmu_oob_clear(device, oob_gpu);
|
|
|
|
kgsl_pwrctrl_irq(device, false);
|
|
|
|
gen7_gmu_power_off(adreno_dev);
|
|
|
|
adreno_set_active_ctxs_null(adreno_dev);
|
|
|
|
adreno_dispatcher_stop(adreno_dev);
|
|
|
|
adreno_ringbuffer_stop(adreno_dev);
|
|
|
|
adreno_llcc_slice_deactivate(adreno_dev);
|
|
|
|
clear_bit(GMU_PRIV_GPU_STARTED, &gmu->flags);
|
|
|
|
del_timer_sync(&device->idle_timer);
|
|
|
|
kgsl_pwrscale_sleep(device);
|
|
|
|
kgsl_pwrctrl_clear_l3_vote(device);
|
|
|
|
/*
|
|
* Reset the context records so that CP can start
|
|
* at the correct read pointer for BV thread after
|
|
* coming out of slumber.
|
|
*/
|
|
gen7_reset_preempt_records(adreno_dev);
|
|
|
|
kgsl_pwrctrl_set_state(device, KGSL_STATE_SLUMBER);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void gmu_idle_check(struct work_struct *work)
|
|
{
|
|
struct kgsl_device *device = container_of(work,
|
|
struct kgsl_device, idle_check_ws);
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret;
|
|
|
|
mutex_lock(&device->mutex);
|
|
|
|
if (test_bit(GMU_DISABLE_SLUMBER, &device->gmu_core.flags))
|
|
goto done;
|
|
|
|
if (atomic_read(&device->active_cnt) || time_is_after_jiffies(device->idle_jiffies)) {
|
|
kgsl_pwrscale_update(device);
|
|
kgsl_start_idle_timer(device);
|
|
goto done;
|
|
}
|
|
|
|
if (!test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags))
|
|
goto done;
|
|
|
|
spin_lock(&device->submit_lock);
|
|
|
|
if (device->submit_now) {
|
|
spin_unlock(&device->submit_lock);
|
|
kgsl_pwrscale_update(device);
|
|
kgsl_start_idle_timer(device);
|
|
goto done;
|
|
}
|
|
|
|
device->skip_inline_submit = true;
|
|
spin_unlock(&device->submit_lock);
|
|
|
|
ret = gen7_power_off(adreno_dev);
|
|
if (ret == -EBUSY) {
|
|
kgsl_pwrscale_update(device);
|
|
kgsl_start_idle_timer(device);
|
|
}
|
|
|
|
done:
|
|
mutex_unlock(&device->mutex);
|
|
}
|
|
|
|
static int gen7_gmu_first_open(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
int ret;
|
|
|
|
/*
|
|
* Do the one time settings that need to happen when we
|
|
* attempt to boot the gpu the very first time
|
|
*/
|
|
ret = gen7_first_boot(adreno_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* A client that does a first_open but never closes the device
|
|
* may prevent us from going back to SLUMBER. So trigger the idle
|
|
* check by incrementing the active count and immediately releasing it.
|
|
*/
|
|
atomic_inc(&device->active_cnt);
|
|
gen7_gmu_active_count_put(adreno_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gen7_gmu_last_close(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
|
|
if (test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags))
|
|
return gen7_power_off(adreno_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gen7_gmu_active_count_get(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret = 0;
|
|
|
|
if (WARN_ON(!mutex_is_locked(&device->mutex)))
|
|
return -EINVAL;
|
|
|
|
if (test_bit(GMU_PRIV_PM_SUSPEND, &gmu->flags))
|
|
return -EINVAL;
|
|
|
|
if ((atomic_read(&device->active_cnt) == 0) &&
|
|
!test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags))
|
|
ret = gen7_boot(adreno_dev);
|
|
|
|
if (ret == 0)
|
|
atomic_inc(&device->active_cnt);
|
|
|
|
trace_kgsl_active_count(device,
|
|
(unsigned long) __builtin_return_address(0));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gen7_gmu_pm_suspend(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret;
|
|
|
|
if (test_bit(GMU_PRIV_PM_SUSPEND, &gmu->flags))
|
|
return 0;
|
|
|
|
kgsl_pwrctrl_request_state(device, KGSL_STATE_SUSPEND);
|
|
|
|
/* Halt any new submissions */
|
|
reinit_completion(&device->halt_gate);
|
|
|
|
/* wait for active count so device can be put in slumber */
|
|
ret = kgsl_active_count_wait(device, 0, HZ);
|
|
if (ret) {
|
|
dev_err(device->dev,
|
|
"Timed out waiting for the active count\n");
|
|
goto err;
|
|
}
|
|
|
|
ret = adreno_idle(device);
|
|
if (ret)
|
|
goto err;
|
|
|
|
if (test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags))
|
|
gen7_power_off(adreno_dev);
|
|
|
|
set_bit(GMU_PRIV_PM_SUSPEND, &gmu->flags);
|
|
|
|
adreno_get_gpu_halt(adreno_dev);
|
|
|
|
kgsl_pwrctrl_set_state(device, KGSL_STATE_SUSPEND);
|
|
|
|
return 0;
|
|
err:
|
|
adreno_dispatcher_start(device);
|
|
return ret;
|
|
}
|
|
|
|
static void gen7_gmu_pm_resume(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
|
|
if (WARN(!test_bit(GMU_PRIV_PM_SUSPEND, &gmu->flags),
|
|
"resume invoked without a suspend\n"))
|
|
return;
|
|
|
|
adreno_put_gpu_halt(adreno_dev);
|
|
|
|
adreno_dispatcher_start(device);
|
|
|
|
clear_bit(GMU_PRIV_PM_SUSPEND, &gmu->flags);
|
|
}
|
|
|
|
static void gen7_gmu_touch_wakeup(struct adreno_device *adreno_dev)
|
|
{
|
|
struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
int ret;
|
|
|
|
/*
|
|
* Do not wake up a suspended device or until the first boot sequence
|
|
* has been completed.
|
|
*/
|
|
if (test_bit(GMU_PRIV_PM_SUSPEND, &gmu->flags) ||
|
|
!test_bit(GMU_PRIV_FIRST_BOOT_DONE, &gmu->flags))
|
|
return;
|
|
|
|
if (test_bit(GMU_PRIV_GPU_STARTED, &gmu->flags))
|
|
goto done;
|
|
|
|
kgsl_pwrctrl_request_state(device, KGSL_STATE_ACTIVE);
|
|
|
|
ret = gen7_gmu_boot(adreno_dev);
|
|
if (ret)
|
|
return;
|
|
|
|
ret = gen7_gpu_boot(adreno_dev);
|
|
if (ret)
|
|
return;
|
|
|
|
kgsl_pwrscale_wake(device);
|
|
|
|
set_bit(GMU_PRIV_GPU_STARTED, &gmu->flags);
|
|
|
|
device->pwrctrl.last_stat_updated = ktime_get();
|
|
|
|
kgsl_pwrctrl_set_state(device, KGSL_STATE_ACTIVE);
|
|
|
|
done:
|
|
/*
|
|
* When waking up from a touch event we want to stay active long enough
|
|
* for the user to send a draw command. The default idle timer timeout
|
|
* is shorter than we want so go ahead and push the idle timer out
|
|
* further for this special case
|
|
*/
|
|
mod_timer(&device->idle_timer, jiffies +
|
|
msecs_to_jiffies(adreno_wake_timeout));
|
|
}
|
|
|
|
const struct adreno_power_ops gen7_gmu_power_ops = {
|
|
.first_open = gen7_gmu_first_open,
|
|
.last_close = gen7_gmu_last_close,
|
|
.active_count_get = gen7_gmu_active_count_get,
|
|
.active_count_put = gen7_gmu_active_count_put,
|
|
.pm_suspend = gen7_gmu_pm_suspend,
|
|
.pm_resume = gen7_gmu_pm_resume,
|
|
.touch_wakeup = gen7_gmu_touch_wakeup,
|
|
.gpu_clock_set = gen7_gmu_clock_set,
|
|
.gpu_bus_set = gen7_gmu_bus_set,
|
|
};
|
|
|
|
int gen7_gmu_device_probe(struct platform_device *pdev,
|
|
u32 chipid, const struct adreno_gpu_core *gpucore)
|
|
{
|
|
struct adreno_device *adreno_dev;
|
|
struct kgsl_device *device;
|
|
struct gen7_device *gen7_dev;
|
|
int ret;
|
|
|
|
gen7_dev = devm_kzalloc(&pdev->dev, sizeof(*gen7_dev),
|
|
GFP_KERNEL);
|
|
if (!gen7_dev)
|
|
return -ENOMEM;
|
|
|
|
adreno_dev = &gen7_dev->adreno_dev;
|
|
|
|
adreno_dev->irq_mask = GEN7_INT_MASK;
|
|
|
|
ret = gen7_probe_common(pdev, adreno_dev, chipid, gpucore);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = adreno_dispatcher_init(adreno_dev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "adreno dispatcher init failed ret %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
device = KGSL_DEVICE(adreno_dev);
|
|
|
|
INIT_WORK(&device->idle_check_ws, gmu_idle_check);
|
|
|
|
timer_setup(&device->idle_timer, gmu_idle_timer, 0);
|
|
|
|
if (ADRENO_FEATURE(adreno_dev, ADRENO_DMS)) {
|
|
set_bit(ADRENO_DEVICE_DMS, &adreno_dev->priv);
|
|
adreno_dev->dms_enabled = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gen7_gmu_reset(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
|
|
gen7_disable_gpu_irq(adreno_dev);
|
|
|
|
gen7_gmu_irq_disable(adreno_dev);
|
|
|
|
gen7_hfi_stop(adreno_dev);
|
|
|
|
/* Hard reset the gmu and gpu */
|
|
gen7_gmu_suspend(adreno_dev);
|
|
|
|
gen7_reset_preempt_records(adreno_dev);
|
|
|
|
adreno_llcc_slice_deactivate(adreno_dev);
|
|
|
|
clear_bit(GMU_PRIV_GPU_STARTED, &gmu->flags);
|
|
|
|
/* Attempt to reboot the gmu and gpu */
|
|
return gen7_boot(adreno_dev);
|
|
}
|
|
|
|
int gen7_gmu_hfi_probe(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_gmu_device *gmu = to_gen7_gmu(adreno_dev);
|
|
struct gen7_hfi *hfi = &gmu->hfi;
|
|
|
|
hfi->irq = kgsl_request_irq(gmu->pdev, "hfi",
|
|
gen7_hfi_irq_handler, KGSL_DEVICE(adreno_dev));
|
|
|
|
return hfi->irq < 0 ? hfi->irq : 0;
|
|
}
|
|
|
|
int gen7_gmu_add_to_minidump(struct adreno_device *adreno_dev)
|
|
{
|
|
struct gen7_device *gen7_dev = container_of(adreno_dev,
|
|
struct gen7_device, adreno_dev);
|
|
int ret;
|
|
|
|
ret = kgsl_add_va_to_minidump(adreno_dev->dev.dev, KGSL_GEN7_DEVICE,
|
|
(void *)(gen7_dev), sizeof(struct gen7_device));
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = kgsl_add_va_to_minidump(adreno_dev->dev.dev, KGSL_GMU_LOG_ENTRY,
|
|
gen7_dev->gmu.gmu_log->hostptr, gen7_dev->gmu.gmu_log->size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = kgsl_add_va_to_minidump(adreno_dev->dev.dev, KGSL_HFIMEM_ENTRY,
|
|
gen7_dev->gmu.hfi.hfi_mem->hostptr, gen7_dev->gmu.hfi.hfi_mem->size);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gen7_gmu_bind(struct device *dev, struct device *master, void *data)
|
|
{
|
|
struct kgsl_device *device = dev_get_drvdata(master);
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
|
|
const struct gen7_gpudev *gen7_gpudev = to_gen7_gpudev(gpudev);
|
|
int ret;
|
|
|
|
ret = gen7_gmu_probe(device, to_platform_device(dev));
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (gen7_gpudev->hfi_probe) {
|
|
ret = gen7_gpudev->hfi_probe(adreno_dev);
|
|
|
|
if (ret) {
|
|
gen7_gmu_remove(device);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void gen7_gmu_unbind(struct device *dev, struct device *master,
|
|
void *data)
|
|
{
|
|
struct kgsl_device *device = dev_get_drvdata(master);
|
|
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
|
|
const struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev);
|
|
const struct gen7_gpudev *gen7_gpudev = to_gen7_gpudev(gpudev);
|
|
|
|
if (gen7_gpudev->hfi_remove)
|
|
gen7_gpudev->hfi_remove(adreno_dev);
|
|
|
|
gen7_gmu_remove(device);
|
|
}
|
|
|
|
static const struct component_ops gen7_gmu_component_ops = {
|
|
.bind = gen7_gmu_bind,
|
|
.unbind = gen7_gmu_unbind,
|
|
};
|
|
|
|
static int gen7_gmu_probe_dev(struct platform_device *pdev)
|
|
{
|
|
return component_add(&pdev->dev, &gen7_gmu_component_ops);
|
|
}
|
|
|
|
static int gen7_gmu_remove_dev(struct platform_device *pdev)
|
|
{
|
|
component_del(&pdev->dev, &gen7_gmu_component_ops);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id gen7_gmu_match_table[] = {
|
|
{ .compatible = "qcom,gen7-gmu" },
|
|
{ },
|
|
};
|
|
|
|
struct platform_driver gen7_gmu_driver = {
|
|
.probe = gen7_gmu_probe_dev,
|
|
.remove = gen7_gmu_remove_dev,
|
|
.driver = {
|
|
.name = "adreno-gen7-gmu",
|
|
.of_match_table = gen7_gmu_match_table,
|
|
},
|
|
};
|