/* *Thiscangetcalledfromdevfreqwhilethehardwareisidle.Don't *bringupthepowerifitisn'talreadyactive.Allwe'redoinghere *isupdatingthefrequencysothatwhenwecomebackonlinewe'reat *therightrate.
*/ if (suspended) return;
if (!gmu->legacy) {
a6xx_hfi_set_freq(gmu, perf_index, bw_index); /* With Bandwidth voting, we now vote for all resources, so skip OPP set */ if (!bw_index)
dev_pm_opp_set_opp(&gpu->pdev->dev, opp); return;
}
staticbool a6xx_gmu_check_idle_level(struct a6xx_gmu *gmu)
{
u32 val; int local = gmu->idle_level;
/* SPTP and IFPC both report as IFPC */ if (gmu->idle_level == GMU_IDLE_STATE_SPTP)
local = GMU_IDLE_STATE_IFPC;
val = gmu_read(gmu, REG_A6XX_GPU_GMU_CX_GMU_RPMH_POWER_STATE);
if (val == local) { if (gmu->idle_level != GMU_IDLE_STATE_IFPC ||
!a6xx_gmu_gx_is_on(gmu)) returntrue;
}
returnfalse;
}
/* Wait for the GMU to get to its most idle state */ int a6xx_gmu_wait_for_idle(struct a6xx_gmu *gmu)
{ return spin_until(a6xx_gmu_check_idle_level(gmu));
}
/* Set the log wptr index *note:downstreamsavesthevalueinpoweroffandrestoresithere
*/ if (adreno_is_a7xx(adreno_gpu))
gmu_write(gmu, REG_A7XX_GMU_GENERAL_9, 0); else
gmu_write(gmu, REG_A6XX_GPU_GMU_CX_GMU_PWR_COL_CP_RESP, 0);
if (ret)
DRM_DEV_ERROR(gmu->dev, "GMU firmware initialization timed out\n");
set_bit(GMU_STATUS_FW_START, &gmu->status);
return ret;
}
staticint a6xx_gmu_hfi_start(struct a6xx_gmu *gmu)
{
u32 val; int ret;
gmu_write(gmu, REG_A6XX_GMU_HFI_CTRL_INIT, 1);
ret = gmu_poll_timeout(gmu, REG_A6XX_GMU_HFI_CTRL_STATUS, val,
val & 1, 100, 10000); if (ret)
DRM_DEV_ERROR(gmu->dev, "Unable to start the HFI queues\n");
/* Trigger a OOB (out of band) request to the GMU */ int a6xx_gmu_set_oob(struct a6xx_gmu *gmu, enum a6xx_gmu_oob_state state)
{ int ret;
u32 val; int request, ack;
WARN_ON_ONCE(!mutex_is_locked(&gmu->lock));
if (state >= ARRAY_SIZE(a6xx_gmu_oob_bits)) return -EINVAL;
/* Wait for the acknowledge interrupt */
ret = gmu_poll_timeout(gmu, REG_A6XX_GMU_GMU2HOST_INTR_INFO, val,
val & (1 << ack), 100, 10000);
if (ret)
DRM_DEV_ERROR(gmu->dev, "Timeout waiting for GMU OOB set %s: 0x%x\n",
a6xx_gmu_oob_bits[state].name,
gmu_read(gmu, REG_A6XX_GMU_GMU2HOST_INTR_INFO));
/* Let the GMU know that we are about to go into slumber */ staticint a6xx_gmu_notify_slumber(struct a6xx_gmu *gmu)
{ int ret;
/* Disable the power counter so the GMU isn't busy */
gmu_write(gmu, REG_A6XX_GMU_CX_GMU_POWER_COUNTER_ENABLE, 0);
/* Disable SPTP_PC if the CPU is responsible for it */ if (gmu->idle_level < GMU_IDLE_STATE_SPTP)
a6xx_sptprac_disable(gmu);
if (!gmu->legacy) {
ret = a6xx_hfi_send_prep_slumber(gmu); goto out;
}
/* Tell the GMU to get ready to slumber */
gmu_write(gmu, REG_A6XX_GMU_BOOT_SLUMBER_OPTION, 1);
ret = a6xx_gmu_set_oob(gmu, GMU_OOB_BOOT_SLUMBER);
a6xx_gmu_clear_oob(gmu, GMU_OOB_BOOT_SLUMBER);
if (!ret) { /* Check to see if the GMU really did slumber */ if (gmu_read(gmu, REG_A6XX_GPU_GMU_CX_GMU_RPMH_POWER_STATE)
!= 0x0f) {
DRM_DEV_ERROR(gmu->dev, "The GMU did not go into slumber\n");
ret = -ETIMEDOUT;
}
}
out:
a6xx_gemnoc_workaround(gmu);
/* Put fence into allow mode */
gmu_write(gmu, REG_A6XX_GMU_AO_AHB_FENCE_CTRL, 0); return ret;
}
staticint a6xx_rpmh_start(struct a6xx_gmu *gmu)
{ int ret;
u32 val;
if (!test_and_clear_bit(GMU_STATUS_PDC_SLEEP, &gmu->status)) return0;
ret = gmu_poll_timeout(gmu, REG_A6XX_GMU_RSCC_CONTROL_ACK, val,
val & (1 << 1), 100, 10000); if (ret) {
DRM_DEV_ERROR(gmu->dev, "Unable to power on the GPU RSC\n"); return ret;
}
ret = gmu_poll_timeout_rscc(gmu, REG_A6XX_RSCC_SEQ_BUSY_DRV0, val,
!val, 100, 10000);
if (ret) {
DRM_DEV_ERROR(gmu->dev, "GPU RSC sequence stuck while waking up the GPU\n"); return ret;
}
gmu_write(gmu, REG_A6XX_GMU_RSCC_CONTROL_REQ, 0);
return0;
}
staticvoid a6xx_rpmh_stop(struct a6xx_gmu *gmu)
{ int ret;
u32 val;
if (test_and_clear_bit(GMU_STATUS_FW_START, &gmu->status)) return;
gmu_write(gmu, REG_A6XX_GMU_RSCC_CONTROL_REQ, 1);
ret = gmu_poll_timeout_rscc(gmu, REG_A6XX_GPU_RSCC_RSC_STATUS0_DRV0,
val, val & (1 << 16), 100, 10000); if (ret)
DRM_DEV_ERROR(gmu->dev, "Unable to power off the GPU RSC\n");
/* The second spin of A7xx GPUs messed with some register offsets.. */ if (adreno_is_a740_family(adreno_gpu))
seqmem0_drv0_reg = REG_A7XX_RSCC_SEQ_MEM_0_DRV0_A740;
/* Set up the idle state for the GMU */ staticvoid a6xx_gmu_power_config(struct a6xx_gmu *gmu)
{ struct a6xx_gpu *a6xx_gpu = container_of(gmu, struct a6xx_gpu, gmu); struct adreno_gpu *adreno_gpu = &a6xx_gpu->base;
if (adreno_is_a650_family(adreno_gpu) || adreno_is_a7xx(adreno_gpu))
dtcm_base = 0x10004000;
if (gmu->legacy) { /* Sanity check the size of the firmware that was loaded */ if (fw_image->size > 0x8000) {
DRM_DEV_ERROR(gmu->dev, "GMU firmware is bigger than the available region\n"); return -EINVAL;
}
/* Vote veto for FAL10 */ if (adreno_is_a650_family(adreno_gpu) || adreno_is_a7xx(adreno_gpu)) {
gmu_write(gmu, REG_A6XX_GPU_GMU_CX_GMU_CX_FALNEXT_INTF, 1);
gmu_write(gmu, REG_A6XX_GPU_GMU_CX_GMU_CX_FAL_INTF, 1);
}
/* Turn on TCM (Tightly Coupled Memory) retention */ if (adreno_is_a7xx(adreno_gpu))
a6xx_llc_write(a6xx_gpu, REG_A7XX_CX_MISC_TCM_RET_CNTL, 1); else
gmu_write(gmu, REG_A6XX_GMU_GENERAL_7, 1);
ret = a6xx_rpmh_start(gmu); if (ret) return ret;
if (state == GMU_COLD_BOOT) { if (WARN(!adreno_gpu->fw[ADRENO_FW_GMU], "GMU firmware is not loaded\n")) return -ENOENT;
ret = a6xx_gmu_fw_load(gmu); if (ret) return ret;
}
/* Clear init result to make sure we are getting a fresh value */
gmu_write(gmu, REG_A6XX_GMU_CM3_FW_INIT_RESULT, 0);
gmu_write(gmu, REG_A6XX_GMU_CM3_BOOT_CONFIG, 0x02);
/* Write the iova of the HFI table */
gmu_write(gmu, REG_A6XX_GMU_HFI_QTBL_ADDR, gmu->hfi.iova);
gmu_write(gmu, REG_A6XX_GMU_HFI_QTBL_INFO, 1);
/* Set up the lowest idle level on the GMU */
a6xx_gmu_power_config(gmu);
ret = a6xx_gmu_start(gmu); if (ret) return ret;
if (gmu->legacy) {
ret = a6xx_gmu_gfx_rail_on(gmu); if (ret) return ret;
}
/* Enable SPTP_PC if the CPU is responsible for it */ if (gmu->idle_level < GMU_IDLE_STATE_SPTP) {
ret = a6xx_sptprac_enable(gmu); if (ret) return ret;
}
ret = a6xx_gmu_hfi_start(gmu); if (ret) return ret;
/* Stop the interrupts */
a6xx_gmu_irq_disable(gmu);
/* Force off SPTP in case the GMU is managing it */
a6xx_sptprac_disable(gmu);
a6xx_gemnoc_workaround(gmu);
/* Make sure there are no outstanding RPMh votes */
a6xx_gmu_rpmh_off(gmu);
/* Clear the WRITEDROPPED fields and put fence into allow mode */
gmu_write(gmu, REG_A6XX_GMU_AHB_FENCE_STATUS_CLR, 0x7);
gmu_write(gmu, REG_A6XX_GMU_AO_AHB_FENCE_CTRL, 0);
/* Make sure the above writes go through */
wmb();
/* Halt the gmu cm3 core */
gmu_write(gmu, REG_A6XX_GMU_CM3_SYSRESET, 1);
if (WARN(!gmu->initialized, "The GMU is not set up yet\n")) return -EINVAL;
gmu->hung = false;
/* Turn on the resources */
pm_runtime_get_sync(gmu->dev);
/* *"enable"theGXpowerdomainwhichwon'tactuallydoanythingbutit *willmakesurethattherefcountingiscorrectincaseweneedto *bringdowntheGXafteraGMUfailure
*/ if (!IS_ERR_OR_NULL(gmu->gxpd))
pm_runtime_get_sync(gmu->gxpd);
/* Use a known rate to bring up the GMU */
clk_set_rate(gmu->core_clk, 200000000);
clk_set_rate(gmu->hub_clk, adreno_is_a740_family(adreno_gpu) ? 200000000 : 150000000);
ret = clk_bulk_prepare_enable(gmu->nr_clocks, gmu->clocks); if (ret) {
pm_runtime_put(gmu->gxpd);
pm_runtime_put(gmu->dev); return ret;
}
/* Set the bus quota to a reasonable value for boot */
a6xx_gmu_set_initial_bw(gpu, gmu);
/* Check to see if we are doing a cold or warm boot */ if (adreno_is_a7xx(adreno_gpu)) {
status = a6xx_llc_read(a6xx_gpu, REG_A7XX_CX_MISC_TCM_RET_CNTL) == 1 ?
GMU_WARM_BOOT : GMU_COLD_BOOT;
} elseif (gmu->legacy) {
status = gmu_read(gmu, REG_A6XX_GMU_GENERAL_7) == 1 ?
GMU_WARM_BOOT : GMU_COLD_BOOT;
} else { /* *WarmbootpathdoesnotworkonnewerA6xxGPUs *Presumablythisisbecauseicache/dcacheregionsmustberestored
*/
status = GMU_COLD_BOOT;
}
ret = a6xx_gmu_fw_start(gmu, status); if (ret) goto out;
ret = a6xx_hfi_start(gmu, status); if (ret) goto out;
/* Set the GPU to the current freq */
a6xx_gmu_set_initial_freq(gpu, gmu);
out: /* On failure, shut down the GMU to leave it in a good state */ if (ret) {
disable_irq(gmu->gmu_irq);
a6xx_rpmh_stop(gmu);
pm_runtime_put(gmu->gxpd);
pm_runtime_put(gmu->dev);
}
if (ret)
DRM_DEV_ERROR(gmu->dev, "Unable to slumber GMU: status = 0%x/0%x\n",
gmu_read(gmu,
REG_A6XX_GPU_GMU_AO_GPU_CX_BUSY_STATUS),
gmu_read(gmu,
REG_A6XX_GPU_GMU_AO_GPU_CX_BUSY_STATUS2));
/* Turn off HFI */
a6xx_hfi_stop(gmu);
/* Stop the interrupts and mask the hardware */
a6xx_gmu_irq_disable(gmu);
/* Tell RPMh to power off the GPU */
a6xx_rpmh_stop(gmu);
/* Multiply the bandwidth by the width of the connection */
peak = (u64)bw * le16_to_cpu(bcm_data[bcm_index]->width);
do_div(peak, bcm->buswidth);
/* Input bandwidth value is in KBps, scale the value to BCM unit */
peak *= 1000;
do_div(peak, le32_to_cpu(bcm_data[bcm_index]->unit));
vote = clamp(peak, 1, BCM_TCS_CMD_VOTE_MASK);
/* GMUs on A7xx votes on both x & y */ if (adreno_is_a7xx(adreno_gpu))
data[bcm_index] = BCM_TCS_CMD(commit, true, vote, vote); else
data[bcm_index] = BCM_TCS_CMD(commit, true, 0, vote);
}
}
return0;
}
/* Return the 'arc-level' for the given frequency */ staticunsignedint a6xx_gmu_get_arc_level(struct device *dev, unsignedlong freq)
{ struct dev_pm_opp *opp; unsignedint val;
if (!freq) return0;
opp = dev_pm_opp_find_freq_exact(dev, freq, true); if (IS_ERR(opp)) return0;
val = dev_pm_opp_get_level(opp);
dev_pm_opp_put(opp);
return val;
}
staticint a6xx_gmu_rpmh_arc_votes_init(struct device *dev, u32 *votes, unsignedlong *freqs, int freqs_count, constchar *id)
{ int i, j; const u16 *pri, *sec;
size_t pri_count, sec_count;
pri = cmd_db_read_aux_data(id, &pri_count); if (IS_ERR(pri)) return PTR_ERR(pri); /* *Thedatacomesbackasanarrayofunsignedshortssoadjustthe *countaccordingly
*/
pri_count >>= 1; if (!pri_count) return -EINVAL;
/* Construct a vote for each frequency */ for (i = 0; i < freqs_count; i++) {
u8 pindex = 0, sindex = 0; unsignedint level = a6xx_gmu_get_arc_level(dev, freqs[i]);
/* Get the primary index that matches the arc level */ for (j = 0; j < pri_count; j++) { if (pri[j] >= level) {
pindex = j; break;
}
}
if (j == pri_count) {
DRM_DEV_ERROR(dev, "Level %u not found in the RPMh list\n",
level);
DRM_DEV_ERROR(dev, "Available levels:\n"); for (j = 0; j < pri_count; j++)
DRM_DEV_ERROR(dev, " %u\n", pri[j]);
/* *TheGMUhandlesitsownfrequencyswitchingsobuildalistof *availablefrequenciestosendduringinitialization
*/
ret = devm_pm_opp_of_add_table(gmu->dev); if (ret) {
DRM_DEV_ERROR(gmu->dev, "Unable to set the OPP table for the GMU\n"); return ret;
}
/* Skip freq = 0 and parse acd-level for rest of the OPPs */ for (i = 1; i < gmu->nr_gpu_freqs; i++) { struct dev_pm_opp *opp; struct device_node *np; unsignedlong freq;
u32 val;
/* It is a problem if qmp node is unavailable when ACD is required */ if (cmd->enable_by_level && IS_ERR_OR_NULL(gmu->qmp)) {
DRM_DEV_ERROR(gmu->dev, "Unable to send ACD state to AOSS\n"); return -EINVAL;
}
/* Otherwise, nothing to do if qmp is unavailable */ if (IS_ERR_OR_NULL(gmu->qmp)) return0;
/* *NotifyAOSSabouttheACDstate.AOSSissupposedtoassumethatACDisdisabledon *systemreset.Soitisharmlessifwecouldn'tnotify'OFF'state
*/
ret = qmp_send(gmu->qmp, "{class: gpu, res: acd, val: %d}", !!cmd->enable_by_level); if (ret && cmd->enable_by_level) {
DRM_DEV_ERROR(gmu->dev, "Failed to send ACD state to AOSS\n"); return ret;
}
return0;
}
staticint a6xx_gmu_clocks_probe(struct a6xx_gmu *gmu)
{ int ret = devm_clk_bulk_get_all(gmu->dev, &gmu->clocks);
if (!res) {
DRM_DEV_ERROR(&pdev->dev, "Unable to find the %s registers\n", name); return ERR_PTR(-EINVAL);
}
ret = ioremap(res->start, resource_size(res)); if (!ret) {
DRM_DEV_ERROR(&pdev->dev, "Unable to map the %s registers\n", name); return ERR_PTR(-EINVAL);
}
/* Get a link to the GX power domain to reset the GPU */
gmu->gxpd = dev_pm_domain_attach_by_name(gmu->dev, "gx"); if (IS_ERR(gmu->gxpd)) {
ret = PTR_ERR(gmu->gxpd); goto err_mmio;
}
ret = of_dma_configure(gmu->dev, node, true); if (ret) return ret;
/* Fow now, don't do anything fancy until we get our feet under us */
gmu->idle_level = GMU_IDLE_STATE_ACTIVE;
pm_runtime_enable(gmu->dev);
/* Get the list of clocks */
ret = a6xx_gmu_clocks_probe(gmu); if (ret) goto err_put_device;
ret = a6xx_gmu_memory_probe(adreno_gpu->base.dev, gmu); if (ret) goto err_put_device;
/* A660 now requires handling "prealloc requests" in GMU firmware *Fornowjusthardcodeallocationsbasedontheknownfirmware. *note:thereisnoindicationthatthesecorrespondto"dummy"or *"debug"regions,butthis"guess"allowsreusingtheseBOswhich *areotherwiseunusedbya660.
*/
gmu->dummy.size = SZ_4K; if (adreno_is_a660_family(adreno_gpu) ||
adreno_is_a7xx(adreno_gpu)) {
ret = a6xx_gmu_memory_alloc(gmu, &gmu->debug, SZ_4K * 7, 0x60400000, "debug"); if (ret) goto err_memory;
gmu->dummy.size = SZ_8K;
}
/* Allocate memory for the GMU dummy page */
ret = a6xx_gmu_memory_alloc(gmu, &gmu->dummy, gmu->dummy.size, 0x60000000, "dummy"); if (ret) goto err_memory;
/* Note that a650 family also includes a660 family: */ if (adreno_is_a650_family(adreno_gpu) ||
adreno_is_a7xx(adreno_gpu)) {
ret = a6xx_gmu_memory_alloc(gmu, &gmu->icache,
SZ_16M - SZ_16K, 0x04000, "icache"); if (ret) goto err_memory; /* *NOTE:whenportinglegacy("pre-650-family")GPUsyoumaybetemptedtoaddacondition *toallocateicache/dcachehere,asperdownstreamcodeflow,butitmaynotactuallybe *necessary.Ifyouomitthisstepandyoudon'tgetrandompagefaults,youarelikely *goodtogowithoutthis!
*/
} elseif (adreno_is_a640_family(adreno_gpu)) {
ret = a6xx_gmu_memory_alloc(gmu, &gmu->icache,
SZ_256K - SZ_16K, 0x04000, "icache"); if (ret) goto err_memory;
ret = a6xx_gmu_memory_alloc(gmu, &gmu->dcache,
SZ_256K - SZ_16K, 0x44000, "dcache"); if (ret) goto err_memory;
} elseif (adreno_is_a630_family(adreno_gpu)) { /* HFI v1, has sptprac */
gmu->legacy = true;
/* Allocate memory for the GMU debug region */
ret = a6xx_gmu_memory_alloc(gmu, &gmu->debug, SZ_16K, 0, "debug"); if (ret) goto err_memory;
}
/* Allocate memory for the GMU log region */
ret = a6xx_gmu_memory_alloc(gmu, &gmu->log, SZ_16K, 0, "log"); if (ret) goto err_memory;
/* Allocate memory for for the HFI queues */
ret = a6xx_gmu_memory_alloc(gmu, &gmu->hfi, SZ_16K, 0, "hfi"); if (ret) goto err_memory;
/* Map the GMU registers */
gmu->mmio = a6xx_gmu_get_mmio(pdev, "gmu"); if (IS_ERR(gmu->mmio)) {
ret = PTR_ERR(gmu->mmio); goto err_memory;
}
if (adreno_is_a650_family(adreno_gpu) ||
adreno_is_a7xx(adreno_gpu)) {
gmu->rscc = a6xx_gmu_get_mmio(pdev, "rscc"); if (IS_ERR(gmu->rscc)) {
ret = -ENODEV; goto err_mmio;
}
} else {
gmu->rscc = gmu->mmio + 0x23000;
}
/* Get the HFI and GMU interrupts */
gmu->hfi_irq = a6xx_gmu_get_irq(gmu, pdev, "hfi", a6xx_hfi_irq);
gmu->gmu_irq = a6xx_gmu_get_irq(gmu, pdev, "gmu", a6xx_gmu_irq);
if (gmu->hfi_irq < 0 || gmu->gmu_irq < 0) {
ret = -ENODEV; goto err_mmio;
}
gmu->cxpd = dev_pm_domain_attach_by_name(gmu->dev, "cx"); if (IS_ERR(gmu->cxpd)) {
ret = PTR_ERR(gmu->cxpd); goto err_mmio;
}
link = device_link_add(gmu->dev, gmu->cxpd, DL_FLAG_PM_RUNTIME); if (!link) {
ret = -ENODEV; goto detach_cxpd;
}
/* Other errors are handled during GPU ACD probe */
gmu->qmp = qmp_get(gmu->dev); if (PTR_ERR_OR_ZERO(gmu->qmp) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER; goto detach_gxpd;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.