blob: 597e479b9f1593ce84e0c2c2fc5b472c5a2eae6c [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Structures and helpers for managing GXP MicroController Unit.
*
* Copyright (C) 2022 Google LLC
*/
#include <linux/delay.h>
#include <linux/sizes.h>
#include <gcip/gcip-mem-pool.h>
#include "gxp-config.h"
#include "gxp-internal.h"
#include "gxp-lpm.h"
#include "gxp-mcu-firmware.h"
#include "gxp-mcu.h"
#include "gxp-uci.h"
/* Setting bit 15 and 16 of GPOUT_LO_WRT register to 0 will hold MCU reset. */
#define GPOUT_LO_MCU_RESET (3u << 15)
#define GPOUT_LO_MCU_PSTATE (1u << 2)
#define GPOUT_LO_MCU_PREG (1u << 3)
#define GPIN_LO_MCU_PACCEPT (1u << 2)
#define GPIN_LO_MCU_PDENY (1u << 3)
/* Allocates the MCU <-> cores shared buffer region. */
static int gxp_alloc_shared_buffer(struct gxp_dev *gxp, struct gxp_mcu *mcu)
{
const size_t size = GXP_SHARED_BUFFER_SIZE;
phys_addr_t paddr;
struct gxp_mapped_resource *res = &mcu->gxp->shared_buf;
size_t offset;
void *vaddr;
paddr = gcip_mem_pool_alloc(&mcu->remap_data_pool, size);
if (!paddr)
return -ENOMEM;
res->paddr = paddr;
res->size = size;
res->daddr = GXP_IOVA_SHARED_BUFFER;
/* clear shared buffer to make sure it's clean */
offset = gcip_mem_pool_offset(&mcu->remap_data_pool, paddr);
vaddr = offset + (mcu->fw.image_buf.vaddr + GXP_IREMAP_DATA_OFFSET);
memset(vaddr, 0, size);
res->vaddr = vaddr;
return 0;
}
static void gxp_free_shared_buffer(struct gxp_mcu *mcu)
{
struct gxp_mapped_resource *res = &mcu->gxp->shared_buf;
gcip_mem_pool_free(&mcu->remap_data_pool, res->paddr, res->size);
}
/*
* Initializes memory pools, must be called after @mcu->fw has been initialized
* to have a valid image_buf.
*/
static int gxp_mcu_mem_pools_init(struct gxp_dev *gxp, struct gxp_mcu *mcu)
{
phys_addr_t iremap_paddr = mcu->fw.image_buf.paddr;
int ret;
ret = gcip_mem_pool_init(&mcu->remap_data_pool, gxp->dev,
iremap_paddr + GXP_IREMAP_DATA_OFFSET,
GXP_IREMAP_DATA_SIZE, SZ_4K);
if (ret)
return ret;
ret = gcip_mem_pool_init(&mcu->remap_secure_pool, gxp->dev,
iremap_paddr + GXP_IREMAP_SECURE_OFFSET,
GXP_IREMAP_SECURE_SIZE, SZ_4K);
if (ret) {
gcip_mem_pool_exit(&mcu->remap_data_pool);
return ret;
}
return 0;
}
static void gxp_mcu_mem_pools_exit(struct gxp_mcu *mcu)
{
gcip_mem_pool_exit(&mcu->remap_secure_pool);
gcip_mem_pool_exit(&mcu->remap_data_pool);
}
int gxp_mcu_mem_alloc_data(struct gxp_mcu *mcu, struct gxp_mapped_resource *mem, size_t size)
{
size_t offset;
phys_addr_t paddr;
paddr = gcip_mem_pool_alloc(&mcu->remap_data_pool, size);
if (!paddr)
return -ENOMEM;
offset = gcip_mem_pool_offset(&mcu->remap_data_pool, paddr);
mem->size = size;
mem->paddr = paddr;
mem->vaddr = offset + (mcu->fw.image_buf.vaddr + GXP_IREMAP_DATA_OFFSET);
mem->daddr = offset + (mcu->fw.image_buf.daddr + GXP_IREMAP_DATA_OFFSET);
return 0;
}
void gxp_mcu_mem_free_data(struct gxp_mcu *mcu, struct gxp_mapped_resource *mem)
{
gcip_mem_pool_free(&mcu->remap_data_pool, mem->paddr, mem->size);
mem->size = 0;
mem->paddr = 0;
mem->vaddr = NULL;
mem->daddr = 0;
}
int gxp_mcu_init(struct gxp_dev *gxp, struct gxp_mcu *mcu)
{
int ret;
mcu->gxp = gxp;
ret = gxp_mcu_firmware_init(gxp, &mcu->fw);
if (ret)
return ret;
ret = gxp_mcu_mem_pools_init(gxp, mcu);
if (ret)
goto err_fw_exit;
ret = gxp_alloc_shared_buffer(gxp, mcu);
if (ret)
goto err_pools_exit;
/*
* MCU telemetry must be initialized before UCI and KCI to match the
* .log_buffer address in the firmware linker.ld.
*/
ret = gxp_mcu_telemetry_init(mcu);
if (ret)
goto err_free_shared_buffer;
ret = gxp_uci_init(mcu);
if (ret)
goto err_telemetry_exit;
ret = gxp_kci_init(mcu);
if (ret)
goto err_uci_exit;
return 0;
err_uci_exit:
gxp_uci_exit(&mcu->uci);
err_telemetry_exit:
gxp_mcu_telemetry_exit(mcu);
err_free_shared_buffer:
gxp_free_shared_buffer(mcu);
err_pools_exit:
gxp_mcu_mem_pools_exit(mcu);
err_fw_exit:
gxp_mcu_firmware_exit(&mcu->fw);
return ret;
}
void gxp_mcu_exit(struct gxp_mcu *mcu)
{
gxp_kci_exit(&mcu->kci);
gxp_uci_exit(&mcu->uci);
gxp_mcu_telemetry_exit(mcu);
gxp_free_shared_buffer(mcu);
gxp_mcu_mem_pools_exit(mcu);
gxp_mcu_firmware_exit(&mcu->fw);
}
int gxp_mcu_reset(struct gxp_dev *gxp, bool release_reset)
{
u32 gpout_lo_rd, gpin_lo_rd, orig;
int i, ret = 0;
/* 1. Read gpout_lo_rd register. */
orig = gpout_lo_rd =
lpm_read_32_psm(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID), PSM_REG_GPOUT_LO_RD_OFFSET);
/* 2. Toggle bit 15 and 16 of this register to '0'. */
gpout_lo_rd &= ~GPOUT_LO_MCU_RESET;
/* 3. Set psm in debug mode with debug_cfg.en=1 and debug_cfg.gpout_override=1. */
lpm_write_32_psm(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID), PSM_REG_DEBUG_CFG_OFFSET, 0b11);
/* 4. Write the modified value from step2 to gpout_lo_wrt register. */
lpm_write_32_psm(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID), PSM_REG_GPOUT_LO_WRT_OFFSET,
gpout_lo_rd);
/*
* 5. Wait for MCU being reset.
*
* Basically, to verify the MCU reset, we should poll bit 0 of MCU_RESET_STATUS register
* (CORERESET_N) to become 0.
*
* However, as we cannot access the register for the security reason, there is no way to
* poll it. Based on the experiment, resetting MCU was already done when the step 4 above
* is finished which took under 5 us. Therefore, waiting 1~2 ms as a margin should be
* enough.
*/
usleep_range(1000, 2000);
if (!release_reset)
return 0;
/*
* 6. Modify gpout_lo_wrt register locally to set bit [3:2]={1,0} to let MCU transit to
* RUN state.
*/
gpout_lo_rd = (gpout_lo_rd | GPOUT_LO_MCU_PREG) & ~GPOUT_LO_MCU_PSTATE;
lpm_write_32_psm(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID), PSM_REG_GPOUT_LO_WRT_OFFSET,
gpout_lo_rd);
/* 7. Toggle bit 15 and 16 of gpout_lo_wrt register to '1' to release reset. */
gpout_lo_rd |= GPOUT_LO_MCU_RESET;
lpm_write_32_psm(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID), PSM_REG_GPOUT_LO_WRT_OFFSET,
gpout_lo_rd);
/* 8. Poll gpin_lo_rd for one of bit 2 (paccept) and 3 (pdeny) becoming non-zero. */
for (i = 10000; i > 0; i--) {
gpin_lo_rd = lpm_read_32_psm(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID),
PSM_REG_GPIN_LO_RD_OFFSET);
if (gpin_lo_rd & (GPIN_LO_MCU_PACCEPT | GPIN_LO_MCU_PDENY))
break;
udelay(GXP_TIME_DELAY_FACTOR);
}
if (!i) {
dev_warn(gxp->dev, "MCU is not responding to the power control");
ret = -ETIMEDOUT;
} else if (gpin_lo_rd & GPIN_LO_MCU_PDENY) {
dev_warn(gxp->dev, "MCU denied the power control for reset");
ret = -EAGAIN;
}
/* 9. Write gpout_lo_wrt the same as gpout_lo_rd of step 1. */
lpm_write_32_psm(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID), PSM_REG_GPOUT_LO_WRT_OFFSET, orig);
/*
* 10. Move PSM back to func mode with gpout override disabled debug_cfg.en=0 and
* debug_cfg.gpout=0.
*/
lpm_write_32_psm(gxp, CORE_TO_PSM(GXP_MCU_CORE_ID), PSM_REG_DEBUG_CFG_OFFSET, 0);
return ret;
}