linux/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c

1045 lines
28 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for MediaTek MFlexGraphics Devices
*
* Copyright (C) 2025, Collabora Ltd.
*/
#include <linux/completion.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/container_of.h>
#include <linux/iopoll.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#include <linux/overflow.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_opp.h>
#include <linux/regulator/consumer.h>
#include <linux/units.h>
#define GPR_LP_STATE 0x0028
#define EB_ON_SUSPEND 0x0
#define EB_ON_RESUME 0x1
#define GPR_IPI_MAGIC 0x34
#define RPC_PWR_CON 0x0504
#define PWR_ACK_M GENMASK(31, 30)
#define RPC_DUMMY_REG_2 0x0658
#define RPC_GHPM_CFG0_CON 0x0800
#define GHPM_ENABLE_M BIT(0)
#define GHPM_ON_SEQ_M BIT(2)
#define RPC_GHPM_RO0_CON 0x09A4
#define GHPM_STATE_M GENMASK(7, 0)
#define GHPM_PWR_STATE_M BIT(16)
#define GF_REG_MAGIC 0x0000
#define GF_REG_GPU_OPP_IDX 0x0004
#define GF_REG_STK_OPP_IDX 0x0008
#define GF_REG_GPU_OPP_NUM 0x000c
#define GF_REG_STK_OPP_NUM 0x0010
#define GF_REG_GPU_OPP_SNUM 0x0014
#define GF_REG_STK_OPP_SNUM 0x0018
#define GF_REG_POWER_COUNT 0x001c
#define GF_REG_BUCK_COUNT 0x0020
#define GF_REG_MTCMOS_COUNT 0x0024
#define GF_REG_CG_COUNT 0x0028 /* CG = Clock Gate? */
#define GF_REG_ACTIVE_COUNT 0x002C
#define GF_REG_TEMP_RAW 0x0030
#define GF_REG_TEMP_NORM_GPU 0x0034
#define GF_REG_TEMP_HIGH_GPU 0x0038
#define GF_REG_TEMP_NORM_STK 0x003C
#define GF_REG_TEMP_HIGH_STK 0x0040
#define GF_REG_FREQ_CUR_GPU 0x0044
#define GF_REG_FREQ_CUR_STK 0x0048
#define GF_REG_FREQ_OUT_GPU 0x004C /* Guess: actual achieved freq */
#define GF_REG_FREQ_OUT_STK 0x0050 /* Guess: actual achieved freq */
#define GF_REG_FREQ_METER_GPU 0x0054 /* Seems unused, always 0 */
#define GF_REG_FREQ_METER_STK 0x0058 /* Seems unused, always 0 */
#define GF_REG_VOLT_CUR_GPU 0x005C /* in tens of microvolts */
#define GF_REG_VOLT_CUR_STK 0x0060 /* in tens of microvolts */
#define GF_REG_VOLT_CUR_GPU_SRAM 0x0064
#define GF_REG_VOLT_CUR_STK_SRAM 0x0068
#define GF_REG_VOLT_CUR_GPU_REG 0x006C /* Seems unused, always 0 */
#define GF_REG_VOLT_CUR_STK_REG 0x0070 /* Seems unused, always 0 */
#define GF_REG_VOLT_CUR_GPU_REG_SRAM 0x0074
#define GF_REG_VOLT_CUR_STK_REG_SRAM 0x0078
#define GF_REG_PWR_CUR_GPU 0x007C /* in milliwatts */
#define GF_REG_PWR_CUR_STK 0x0080 /* in milliwatts */
#define GF_REG_PWR_MAX_GPU 0x0084 /* in milliwatts */
#define GF_REG_PWR_MAX_STK 0x0088 /* in milliwatts */
#define GF_REG_PWR_MIN_GPU 0x008C /* in milliwatts */
#define GF_REG_PWR_MIN_STK 0x0090 /* in milliwatts */
#define GF_REG_LEAKAGE_RT_GPU 0x0094 /* Unknown */
#define GF_REG_LEAKAGE_RT_STK 0x0098 /* Unknown */
#define GF_REG_LEAKAGE_RT_SRAM 0x009C /* Unknown */
#define GF_REG_LEAKAGE_HT_GPU 0x00A0 /* Unknown */
#define GF_REG_LEAKAGE_HT_STK 0x00A4 /* Unknown */
#define GF_REG_LEAKAGE_HT_SRAM 0x00A8 /* Unknown */
#define GF_REG_VOLT_DAC_LOW_GPU 0x00AC /* Seems unused, always 0 */
#define GF_REG_VOLT_DAC_LOW_STK 0x00B0 /* Seems unused, always 0 */
#define GF_REG_OPP_CUR_CEIL 0x00B4
#define GF_REG_OPP_CUR_FLOOR 0x00B8
#define GF_REG_OPP_CUR_LIMITER_CEIL 0x00BC
#define GF_REG_OPP_CUR_LIMITER_FLOOR 0x00C0
#define GF_REG_OPP_PRIORITY_CEIL 0x00C4
#define GF_REG_OPP_PRIORITY_FLOOR 0x00C8
#define GF_REG_PWR_CTL 0x00CC
#define GF_REG_ACTIVE_SLEEP_CTL 0x00D0
#define GF_REG_DVFS_STATE 0x00D4
#define GF_REG_SHADER_PRESENT 0x00D8
#define GF_REG_ASENSOR_ENABLE 0x00DC
#define GF_REG_AGING_LOAD 0x00E0
#define GF_REG_AGING_MARGIN 0x00E4
#define GF_REG_AVS_ENABLE 0x00E8
#define GF_REG_AVS_MARGIN 0x00EC
#define GF_REG_CHIP_TYPE 0x00F0
#define GF_REG_SB_VERSION 0x00F4
#define GF_REG_PTP_VERSION 0x00F8
#define GF_REG_DBG_VERSION 0x00FC
#define GF_REG_KDBG_VERSION 0x0100
#define GF_REG_GPM1_MODE 0x0104
#define GF_REG_GPM3_MODE 0x0108
#define GF_REG_DFD_MODE 0x010C
#define GF_REG_DUAL_BUCK 0x0110
#define GF_REG_SEGMENT_ID 0x0114
#define GF_REG_POWER_TIME_H 0x0118
#define GF_REG_POWER_TIME_L 0x011C
#define GF_REG_PWR_STATUS 0x0120
#define GF_REG_STRESS_TEST 0x0124
#define GF_REG_TEST_MODE 0x0128
#define GF_REG_IPS_MODE 0x012C
#define GF_REG_TEMP_COMP_MODE 0x0130
#define GF_REG_HT_TEMP_COMP_MODE 0x0134
#define GF_REG_PWR_TRACKER_MODE 0x0138
#define GF_REG_OPP_TABLE_GPU 0x0314
#define GF_REG_OPP_TABLE_STK 0x09A4
#define GF_REG_OPP_TABLE_GPU_S 0x1034
#define GF_REG_OPP_TABLE_STK_S 0x16c4
#define GF_REG_LIMIT_TABLE 0x1d54
#define GF_REG_GPM3_TABLE 0x223C
#define MFG_MT8196_E2_ID 0x101
#define GPUEB_SLEEP_MAGIC 0x55667788UL
#define GPUEB_MEM_MAGIC 0xBABADADAUL
#define GPUEB_TIMEOUT_US 10000UL
#define GPUEB_POLL_US 50
#define MAX_OPP_NUM 70
#define GPUEB_MBOX_MAX_RX_SIZE 32 /* in bytes */
/*
* This enum is part of the ABI of the GPUEB firmware. Don't change the
* numbering, as you would wreak havoc.
*/
enum mtk_mfg_ipi_cmd {
CMD_INIT_SHARED_MEM = 0,
CMD_GET_FREQ_BY_IDX = 1,
CMD_GET_POWER_BY_IDX = 2,
CMD_GET_OPPIDX_BY_FREQ = 3,
CMD_GET_LEAKAGE_POWER = 4,
CMD_SET_LIMIT = 5,
CMD_POWER_CONTROL = 6,
CMD_ACTIVE_SLEEP_CONTROL = 7,
CMD_COMMIT = 8,
CMD_DUAL_COMMIT = 9,
CMD_PDCA_CONFIG = 10,
CMD_UPDATE_DEBUG_OPP_INFO = 11,
CMD_SWITCH_LIMIT = 12,
CMD_FIX_TARGET_OPPIDX = 13,
CMD_FIX_DUAL_TARGET_OPPIDX = 14,
CMD_FIX_CUSTOM_FREQ_VOLT = 15,
CMD_FIX_DUAL_CUSTOM_FREQ_VOLT = 16,
CMD_SET_MFGSYS_CONFIG = 17,
CMD_MSSV_COMMIT = 18,
CMD_NUM = 19,
};
/*
* This struct is part of the ABI of the GPUEB firmware. Changing it, or
* reordering fields in it, will break things, so don't do it. Thank you.
*/
struct __packed mtk_mfg_ipi_msg {
__le32 magic;
__le32 cmd;
__le32 target;
/*
* Downstream relies on the compiler to implicitly add the following
* padding, as it declares the struct as non-packed.
*/
__le32 reserved;
union {
s32 __bitwise oppidx;
s32 __bitwise return_value;
__le32 freq;
__le32 volt;
__le32 power;
__le32 power_state;
__le32 mode;
__le32 value;
struct {
__le64 base;
__le32 size;
} shared_mem;
struct {
__le32 freq;
__le32 volt;
} custom;
struct {
__le32 limiter;
s32 __bitwise ceiling_info;
s32 __bitwise floor_info;
} set_limit;
struct {
__le32 target;
__le32 val;
} mfg_cfg;
struct {
__le32 target;
__le32 val;
} mssv;
struct {
s32 __bitwise gpu_oppidx;
s32 __bitwise stack_oppidx;
} dual_commit;
struct {
__le32 fgpu;
__le32 vgpu;
__le32 fstack;
__le32 vstack;
} dual_custom;
} u;
};
struct __packed mtk_mfg_ipi_sleep_msg {
__le32 event;
__le32 state;
__le32 magic;
};
/**
* struct mtk_mfg_opp_entry - OPP table entry from firmware
* @freq_khz: The operating point's frequency in kilohertz
* @voltage_core: The operating point's core voltage in tens of microvolts
* @voltage_sram: The operating point's SRAM voltage in tens of microvolts
* @posdiv: exponent of base 2 for PLL frequency divisor used for this OPP
* @voltage_margin: Number of tens of microvolts the voltage can be undershot
* @power_mw: estimate of power usage at this operating point, in milliwatts
*
* This struct is part of the ABI with the EB firmware. Do not change it.
*/
struct __packed mtk_mfg_opp_entry {
__le32 freq_khz;
__le32 voltage_core;
__le32 voltage_sram;
__le32 posdiv;
__le32 voltage_margin;
__le32 power_mw;
};
struct mtk_mfg_mbox {
struct mbox_client cl;
struct completion rx_done;
struct mtk_mfg *mfg;
struct mbox_chan *ch;
void *rx_data;
};
struct mtk_mfg {
struct generic_pm_domain pd;
struct platform_device *pdev;
struct clk *clk_eb;
struct clk_bulk_data *gpu_clks;
struct clk_hw clk_core_hw;
struct clk_hw clk_stack_hw;
struct regulator_bulk_data *gpu_regs;
void __iomem *rpc;
void __iomem *gpr;
void __iomem *shared_mem;
phys_addr_t shared_mem_phys;
unsigned int shared_mem_size;
u16 ghpm_en_reg;
u32 ipi_magic;
unsigned short num_gpu_opps;
unsigned short num_stack_opps;
struct dev_pm_opp_data *gpu_opps;
struct dev_pm_opp_data *stack_opps;
struct mtk_mfg_mbox *gf_mbox;
struct mtk_mfg_mbox *slp_mbox;
const struct mtk_mfg_variant *variant;
};
struct mtk_mfg_variant {
const char *const *clk_names;
unsigned int num_clks;
const char *const *regulator_names;
unsigned int num_regulators;
/** @turbo_below: opp indices below this value are considered turbo */
unsigned int turbo_below;
int (*init)(struct mtk_mfg *mfg);
};
static inline struct mtk_mfg *mtk_mfg_from_genpd(struct generic_pm_domain *pd)
{
return container_of(pd, struct mtk_mfg, pd);
}
static inline void mtk_mfg_update_reg_bits(void __iomem *addr, u32 mask, u32 val)
{
writel((readl(addr) & ~mask) | (val & mask), addr);
}
static inline bool mtk_mfg_is_powered_on(struct mtk_mfg *mfg)
{
return (readl(mfg->rpc + RPC_PWR_CON) & PWR_ACK_M) == PWR_ACK_M;
}
static unsigned long mtk_mfg_recalc_rate_gpu(struct clk_hw *hw,
unsigned long parent_rate)
{
struct mtk_mfg *mfg = container_of(hw, struct mtk_mfg, clk_core_hw);
return readl(mfg->shared_mem + GF_REG_FREQ_OUT_GPU) * HZ_PER_KHZ;
}
static int mtk_mfg_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
/*
* The determine_rate callback needs to be implemented to avoid returning
* the current clock frequency, rather than something even remotely
* close to the frequency that was asked for.
*
* Instead of writing considerable amounts of possibly slow code just to
* somehow figure out which of the three PLLs to round for, or even to
* do a search through one of two OPP tables in order to find the closest
* OPP of a frequency, just return the rate as-is. This avoids devfreq
* "rounding" a request for the lowest frequency to the possibly very
* high current frequency, breaking the powersave governor in the process.
*/
return 0;
}
static unsigned long mtk_mfg_recalc_rate_stack(struct clk_hw *hw,
unsigned long parent_rate)
{
struct mtk_mfg *mfg = container_of(hw, struct mtk_mfg, clk_stack_hw);
return readl(mfg->shared_mem + GF_REG_FREQ_OUT_STK) * HZ_PER_KHZ;
}
static const struct clk_ops mtk_mfg_clk_gpu_ops = {
.recalc_rate = mtk_mfg_recalc_rate_gpu,
.determine_rate = mtk_mfg_determine_rate,
};
static const struct clk_ops mtk_mfg_clk_stack_ops = {
.recalc_rate = mtk_mfg_recalc_rate_stack,
.determine_rate = mtk_mfg_determine_rate,
};
static const struct clk_init_data mtk_mfg_clk_gpu_init = {
.name = "gpu-core",
.ops = &mtk_mfg_clk_gpu_ops,
.flags = CLK_GET_RATE_NOCACHE,
};
static const struct clk_init_data mtk_mfg_clk_stack_init = {
.name = "gpu-stack",
.ops = &mtk_mfg_clk_stack_ops,
.flags = CLK_GET_RATE_NOCACHE,
};
static int mtk_mfg_eb_on(struct mtk_mfg *mfg)
{
struct device *dev = &mfg->pdev->dev;
u32 val;
int ret;
/*
* If MFG is already on from e.g. the bootloader, skip doing the
* power-on sequence, as it wouldn't work without powering it off first.
*/
if (mtk_mfg_is_powered_on(mfg))
return 0;
ret = readl_poll_timeout(mfg->rpc + RPC_GHPM_RO0_CON, val,
!(val & (GHPM_PWR_STATE_M | GHPM_STATE_M)),
GPUEB_POLL_US, GPUEB_TIMEOUT_US);
if (ret) {
dev_err(dev, "timed out waiting for EB to power on\n");
return ret;
}
mtk_mfg_update_reg_bits(mfg->rpc + mfg->ghpm_en_reg, GHPM_ENABLE_M,
GHPM_ENABLE_M);
mtk_mfg_update_reg_bits(mfg->rpc + RPC_GHPM_CFG0_CON, GHPM_ON_SEQ_M, 0);
mtk_mfg_update_reg_bits(mfg->rpc + RPC_GHPM_CFG0_CON, GHPM_ON_SEQ_M,
GHPM_ON_SEQ_M);
mtk_mfg_update_reg_bits(mfg->rpc + mfg->ghpm_en_reg, GHPM_ENABLE_M, 0);
ret = readl_poll_timeout(mfg->rpc + RPC_PWR_CON, val,
(val & PWR_ACK_M) == PWR_ACK_M,
GPUEB_POLL_US, GPUEB_TIMEOUT_US);
if (ret) {
dev_err(dev, "timed out waiting for EB power ack, val = 0x%X\n",
val);
return ret;
}
ret = readl_poll_timeout(mfg->gpr + GPR_LP_STATE, val,
(val == EB_ON_RESUME),
GPUEB_POLL_US, GPUEB_TIMEOUT_US);
if (ret) {
dev_err(dev, "timed out waiting for EB to resume, status = 0x%X\n", val);
return ret;
}
return 0;
}
static int mtk_mfg_eb_off(struct mtk_mfg *mfg)
{
struct device *dev = &mfg->pdev->dev;
struct mtk_mfg_ipi_sleep_msg msg = {
.event = 0,
.state = 0,
.magic = GPUEB_SLEEP_MAGIC
};
u32 val;
int ret;
ret = mbox_send_message(mfg->slp_mbox->ch, &msg);
if (ret < 0) {
dev_err(dev, "Cannot send sleep command: %pe\n", ERR_PTR(ret));
return ret;
}
ret = readl_poll_timeout(mfg->rpc + RPC_PWR_CON, val,
!(val & PWR_ACK_M), GPUEB_POLL_US,
GPUEB_TIMEOUT_US);
if (ret) {
dev_err(dev, "Timed out waiting for EB to power off, val=0x%08X\n", val);
return ret;
}
return 0;
}
/**
* mtk_mfg_send_ipi - synchronously send an IPI message on the gpufreq channel
* @mfg: pointer to this driver instance's private &struct mtk_mfg
* @msg: pointer to a message to send; will have magic filled and response assigned
*
* Send an IPI message on the gpufreq channel, and wait for a response. Once a
* response is received, assign a pointer to the response buffer (valid until
* next response is received) to @msg.
*
* Returns 0 on success, negative errno on failure.
*/
static int mtk_mfg_send_ipi(struct mtk_mfg *mfg, struct mtk_mfg_ipi_msg *msg)
{
struct device *dev = &mfg->pdev->dev;
unsigned long wait;
int ret;
msg->magic = mfg->ipi_magic;
ret = mbox_send_message(mfg->gf_mbox->ch, msg);
if (ret < 0) {
dev_err(dev, "Cannot send GPUFreq IPI command: %pe\n", ERR_PTR(ret));
return ret;
}
wait = wait_for_completion_timeout(&mfg->gf_mbox->rx_done, msecs_to_jiffies(500));
if (!wait)
return -ETIMEDOUT;
msg = mfg->gf_mbox->rx_data;
if (msg->u.return_value < 0) {
dev_err(dev, "IPI return: %d\n", msg->u.return_value);
return -EPROTO;
}
return 0;
}
static int mtk_mfg_init_shared_mem(struct mtk_mfg *mfg)
{
struct device *dev = &mfg->pdev->dev;
struct mtk_mfg_ipi_msg msg = {};
int ret;
dev_dbg(dev, "clearing GPUEB shared memory, 0x%X bytes\n", mfg->shared_mem_size);
memset_io(mfg->shared_mem, 0, mfg->shared_mem_size);
msg.cmd = CMD_INIT_SHARED_MEM;
msg.u.shared_mem.base = mfg->shared_mem_phys;
msg.u.shared_mem.size = mfg->shared_mem_size;
ret = mtk_mfg_send_ipi(mfg, &msg);
if (ret)
return ret;
if (readl(mfg->shared_mem + GF_REG_MAGIC) != GPUEB_MEM_MAGIC) {
dev_err(dev, "EB did not initialise shared memory correctly\n");
return -EIO;
}
return 0;
}
static int mtk_mfg_power_control(struct mtk_mfg *mfg, bool enabled)
{
struct mtk_mfg_ipi_msg msg = {};
msg.cmd = CMD_POWER_CONTROL;
msg.u.power_state = enabled ? 1 : 0;
return mtk_mfg_send_ipi(mfg, &msg);
}
static int mtk_mfg_set_oppidx(struct mtk_mfg *mfg, unsigned int opp_idx)
{
struct mtk_mfg_ipi_msg msg = {};
int ret;
if (opp_idx >= mfg->num_gpu_opps)
return -EINVAL;
msg.cmd = CMD_FIX_DUAL_TARGET_OPPIDX;
msg.u.dual_commit.gpu_oppidx = opp_idx;
msg.u.dual_commit.stack_oppidx = opp_idx;
ret = mtk_mfg_send_ipi(mfg, &msg);
if (ret) {
dev_err(&mfg->pdev->dev, "Failed to set OPP %u: %pe\n",
opp_idx, ERR_PTR(ret));
return ret;
}
return 0;
}
static int mtk_mfg_read_opp_tables(struct mtk_mfg *mfg)
{
struct device *dev = &mfg->pdev->dev;
struct mtk_mfg_opp_entry e = {};
unsigned int i;
mfg->num_gpu_opps = readl(mfg->shared_mem + GF_REG_GPU_OPP_NUM);
mfg->num_stack_opps = readl(mfg->shared_mem + GF_REG_STK_OPP_NUM);
if (mfg->num_gpu_opps > MAX_OPP_NUM || mfg->num_gpu_opps == 0) {
dev_err(dev, "GPU OPP count (%u) out of range %u >= count > 0\n",
mfg->num_gpu_opps, MAX_OPP_NUM);
return -EINVAL;
}
if (mfg->num_stack_opps && mfg->num_stack_opps > MAX_OPP_NUM) {
dev_err(dev, "Stack OPP count (%u) out of range %u >= count >= 0\n",
mfg->num_stack_opps, MAX_OPP_NUM);
return -EINVAL;
}
mfg->gpu_opps = devm_kcalloc(dev, mfg->num_gpu_opps,
sizeof(struct dev_pm_opp_data), GFP_KERNEL);
if (!mfg->gpu_opps)
return -ENOMEM;
if (mfg->num_stack_opps) {
mfg->stack_opps = devm_kcalloc(dev, mfg->num_stack_opps,
sizeof(struct dev_pm_opp_data), GFP_KERNEL);
if (!mfg->stack_opps)
return -ENOMEM;
}
for (i = 0; i < mfg->num_gpu_opps; i++) {
memcpy_fromio(&e, mfg->shared_mem + GF_REG_OPP_TABLE_GPU + i * sizeof(e),
sizeof(e));
if (mem_is_zero(&e, sizeof(e))) {
dev_err(dev, "ran into an empty GPU OPP at index %u\n",
i);
return -EINVAL;
}
mfg->gpu_opps[i].freq = e.freq_khz * HZ_PER_KHZ;
mfg->gpu_opps[i].u_volt = e.voltage_core * 10;
mfg->gpu_opps[i].level = i;
if (i < mfg->variant->turbo_below)
mfg->gpu_opps[i].turbo = true;
}
for (i = 0; i < mfg->num_stack_opps; i++) {
memcpy_fromio(&e, mfg->shared_mem + GF_REG_OPP_TABLE_STK + i * sizeof(e),
sizeof(e));
if (mem_is_zero(&e, sizeof(e))) {
dev_err(dev, "ran into an empty Stack OPP at index %u\n",
i);
return -EINVAL;
}
mfg->stack_opps[i].freq = e.freq_khz * HZ_PER_KHZ;
mfg->stack_opps[i].u_volt = e.voltage_core * 10;
mfg->stack_opps[i].level = i;
if (i < mfg->variant->turbo_below)
mfg->stack_opps[i].turbo = true;
}
return 0;
}
static const char *const mtk_mfg_mt8196_clk_names[] = {
"core",
"stack0",
"stack1",
};
static const char *const mtk_mfg_mt8196_regulators[] = {
"core",
"stack",
"sram",
};
static int mtk_mfg_mt8196_init(struct mtk_mfg *mfg)
{
void __iomem *e2_base;
e2_base = devm_platform_ioremap_resource_byname(mfg->pdev, "hw-revision");
if (IS_ERR(e2_base))
return dev_err_probe(&mfg->pdev->dev, PTR_ERR(e2_base),
"Couldn't get hw-revision register\n");
clk_prepare_enable(mfg->clk_eb);
if (readl(e2_base) == MFG_MT8196_E2_ID)
mfg->ghpm_en_reg = RPC_DUMMY_REG_2;
else
mfg->ghpm_en_reg = RPC_GHPM_CFG0_CON;
clk_disable_unprepare(mfg->clk_eb);
return 0;
}
static const struct mtk_mfg_variant mtk_mfg_mt8196_variant = {
.clk_names = mtk_mfg_mt8196_clk_names,
.num_clks = ARRAY_SIZE(mtk_mfg_mt8196_clk_names),
.regulator_names = mtk_mfg_mt8196_regulators,
.num_regulators = ARRAY_SIZE(mtk_mfg_mt8196_regulators),
.turbo_below = 7,
.init = mtk_mfg_mt8196_init,
};
static void mtk_mfg_mbox_rx_callback(struct mbox_client *cl, void *mssg)
{
struct mtk_mfg_mbox *mb = container_of(cl, struct mtk_mfg_mbox, cl);
if (mb->rx_data)
mb->rx_data = memcpy(mb->rx_data, mssg, GPUEB_MBOX_MAX_RX_SIZE);
complete(&mb->rx_done);
}
static int mtk_mfg_attach_dev(struct generic_pm_domain *pd, struct device *dev)
{
struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd);
struct dev_pm_opp_data *so = mfg->stack_opps;
struct dev_pm_opp_data *go = mfg->gpu_opps;
struct dev_pm_opp_data *prev_o;
struct dev_pm_opp_data *o;
int i, ret;
for (i = mfg->num_gpu_opps - 1; i >= 0; i--) {
/*
* Adding the lower of the two OPPs avoids gaps of indices in
* situations where the GPU OPPs are duplicated a couple of
* times when only the Stack OPP is being lowered at that index.
*/
if (i >= mfg->num_stack_opps || go[i].freq < so[i].freq)
o = &go[i];
else
o = &so[i];
/*
* Skip indices where both GPU and Stack OPPs are equal. Nominally,
* OPP core shouldn't care about dupes, but not doing so will cause
* dev_pm_opp_find_freq_ceil_indexed to -ERANGE later down the line.
*/
if (prev_o && prev_o->freq == o->freq)
continue;
ret = dev_pm_opp_add_dynamic(dev, o);
if (ret) {
dev_err(dev, "Failed to add OPP level %u from PD %s: %pe\n",
o->level, pd->name, ERR_PTR(ret));
dev_pm_opp_remove_all_dynamic(dev);
return ret;
}
prev_o = o;
}
return 0;
}
static void mtk_mfg_detach_dev(struct generic_pm_domain *pd, struct device *dev)
{
dev_pm_opp_remove_all_dynamic(dev);
}
static int mtk_mfg_set_performance(struct generic_pm_domain *pd,
unsigned int state)
{
struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd);
/*
* pmdomain core intentionally sets a performance state before turning
* a domain on, and after turning it off. For the GPUEB however, it's
* only possible to act on performance requests when the GPUEB is
* powered on. To do this, return cleanly without taking action, and
* defer setting what pmdomain core set in mtk_mfg_power_on.
*/
if (mfg->pd.status != GENPD_STATE_ON)
return 0;
return mtk_mfg_set_oppidx(mfg, state);
}
static int mtk_mfg_power_on(struct generic_pm_domain *pd)
{
struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd);
int ret;
ret = regulator_bulk_enable(mfg->variant->num_regulators,
mfg->gpu_regs);
if (ret)
return ret;
ret = clk_prepare_enable(mfg->clk_eb);
if (ret)
goto err_disable_regulators;
ret = clk_bulk_prepare_enable(mfg->variant->num_clks, mfg->gpu_clks);
if (ret)
goto err_disable_eb_clk;
ret = mtk_mfg_eb_on(mfg);
if (ret)
goto err_disable_clks;
mfg->ipi_magic = readl(mfg->gpr + GPR_IPI_MAGIC);
ret = mtk_mfg_power_control(mfg, true);
if (ret)
goto err_eb_off;
/* Don't try to set a OPP in probe before OPPs have been read from EB */
if (mfg->gpu_opps) {
/* The aforementioned deferred setting of pmdomain's state */
ret = mtk_mfg_set_oppidx(mfg, pd->performance_state);
if (ret)
dev_warn(&mfg->pdev->dev, "Failed to set oppidx in %s\n", __func__);
}
return 0;
err_eb_off:
mtk_mfg_eb_off(mfg);
err_disable_clks:
clk_bulk_disable_unprepare(mfg->variant->num_clks, mfg->gpu_clks);
err_disable_eb_clk:
clk_disable_unprepare(mfg->clk_eb);
err_disable_regulators:
regulator_bulk_disable(mfg->variant->num_regulators, mfg->gpu_regs);
return ret;
}
static int mtk_mfg_power_off(struct generic_pm_domain *pd)
{
struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd);
struct device *dev = &mfg->pdev->dev;
int ret;
ret = mtk_mfg_power_control(mfg, false);
if (ret) {
dev_err(dev, "power_control failed: %pe\n", ERR_PTR(ret));
return ret;
}
ret = mtk_mfg_eb_off(mfg);
if (ret) {
dev_err(dev, "eb_off failed: %pe\n", ERR_PTR(ret));
return ret;
}
clk_bulk_disable_unprepare(mfg->variant->num_clks, mfg->gpu_clks);
clk_disable_unprepare(mfg->clk_eb);
ret = regulator_bulk_disable(mfg->variant->num_regulators, mfg->gpu_regs);
if (ret) {
dev_err(dev, "Disabling regulators failed: %pe\n", ERR_PTR(ret));
return ret;
}
return 0;
}
static int mtk_mfg_init_mbox(struct mtk_mfg *mfg)
{
struct device *dev = &mfg->pdev->dev;
struct mtk_mfg_mbox *gf;
struct mtk_mfg_mbox *slp;
gf = devm_kzalloc(dev, sizeof(*gf), GFP_KERNEL);
if (!gf)
return -ENOMEM;
gf->rx_data = devm_kzalloc(dev, GPUEB_MBOX_MAX_RX_SIZE, GFP_KERNEL);
if (!gf->rx_data)
return -ENOMEM;
gf->mfg = mfg;
init_completion(&gf->rx_done);
gf->cl.dev = dev;
gf->cl.rx_callback = mtk_mfg_mbox_rx_callback;
gf->cl.tx_tout = GPUEB_TIMEOUT_US / USEC_PER_MSEC;
gf->ch = mbox_request_channel_byname(&gf->cl, "gpufreq");
if (IS_ERR(gf->ch))
return PTR_ERR(gf->ch);
mfg->gf_mbox = gf;
slp = devm_kzalloc(dev, sizeof(*slp), GFP_KERNEL);
if (!slp)
return -ENOMEM;
slp->mfg = mfg;
init_completion(&slp->rx_done);
slp->cl.dev = dev;
slp->cl.tx_tout = GPUEB_TIMEOUT_US / USEC_PER_MSEC;
slp->cl.tx_block = true;
slp->ch = mbox_request_channel_byname(&slp->cl, "sleep");
if (IS_ERR(slp->ch)) {
mbox_free_channel(gf->ch);
return PTR_ERR(slp->ch);
}
mfg->slp_mbox = slp;
return 0;
}
static int mtk_mfg_init_clk_provider(struct mtk_mfg *mfg)
{
struct device *dev = &mfg->pdev->dev;
struct clk_hw_onecell_data *clk_data;
int ret;
clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, 2), GFP_KERNEL);
if (!clk_data)
return -ENOMEM;
clk_data->num = 2;
mfg->clk_core_hw.init = &mtk_mfg_clk_gpu_init;
mfg->clk_stack_hw.init = &mtk_mfg_clk_stack_init;
ret = devm_clk_hw_register(dev, &mfg->clk_core_hw);
if (ret)
return dev_err_probe(dev, ret, "Couldn't register GPU core clock\n");
ret = devm_clk_hw_register(dev, &mfg->clk_stack_hw);
if (ret)
return dev_err_probe(dev, ret, "Couldn't register GPU stack clock\n");
clk_data->hws[0] = &mfg->clk_core_hw;
clk_data->hws[1] = &mfg->clk_stack_hw;
ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
if (ret)
return dev_err_probe(dev, ret, "Couldn't register clock provider\n");
return 0;
}
static int mtk_mfg_probe(struct platform_device *pdev)
{
struct mtk_mfg *mfg;
struct device *dev = &pdev->dev;
const struct mtk_mfg_variant *data = of_device_get_match_data(dev);
struct resource res;
int ret, i;
mfg = devm_kzalloc(dev, sizeof(*mfg), GFP_KERNEL);
if (!mfg)
return -ENOMEM;
mfg->pdev = pdev;
mfg->variant = data;
dev_set_drvdata(dev, mfg);
mfg->gpr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mfg->gpr))
return dev_err_probe(dev, PTR_ERR(mfg->gpr),
"Couldn't retrieve GPR MMIO registers\n");
mfg->rpc = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(mfg->rpc))
return dev_err_probe(dev, PTR_ERR(mfg->rpc),
"Couldn't retrieve RPC MMIO registers\n");
mfg->clk_eb = devm_clk_get(dev, "eb");
if (IS_ERR(mfg->clk_eb))
return dev_err_probe(dev, PTR_ERR(mfg->clk_eb),
"Couldn't get 'eb' clock\n");
mfg->gpu_clks = devm_kcalloc(dev, data->num_clks, sizeof(*mfg->gpu_clks),
GFP_KERNEL);
if (!mfg->gpu_clks)
return -ENOMEM;
for (i = 0; i < data->num_clks; i++)
mfg->gpu_clks[i].id = data->clk_names[i];
ret = devm_clk_bulk_get(dev, data->num_clks, mfg->gpu_clks);
if (ret)
return dev_err_probe(dev, ret, "Couldn't get GPU clocks\n");
mfg->gpu_regs = devm_kcalloc(dev, data->num_regulators,
sizeof(*mfg->gpu_regs), GFP_KERNEL);
if (!mfg->gpu_regs)
return -ENOMEM;
for (i = 0; i < data->num_regulators; i++)
mfg->gpu_regs[i].supply = data->regulator_names[i];
ret = devm_regulator_bulk_get(dev, data->num_regulators, mfg->gpu_regs);
if (ret)
return dev_err_probe(dev, ret, "Couldn't get GPU regulators\n");
ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &res);
if (ret)
return dev_err_probe(dev, ret, "Couldn't get GPUEB shared memory\n");
mfg->shared_mem = devm_ioremap(dev, res.start, resource_size(&res));
if (!mfg->shared_mem)
return dev_err_probe(dev, -ENOMEM, "Can't ioremap GPUEB shared memory\n");
mfg->shared_mem_size = resource_size(&res);
mfg->shared_mem_phys = res.start;
if (data->init) {
ret = data->init(mfg);
if (ret)
return dev_err_probe(dev, ret, "Variant init failed\n");
}
mfg->pd.name = dev_name(dev);
mfg->pd.attach_dev = mtk_mfg_attach_dev;
mfg->pd.detach_dev = mtk_mfg_detach_dev;
mfg->pd.power_off = mtk_mfg_power_off;
mfg->pd.power_on = mtk_mfg_power_on;
mfg->pd.set_performance_state = mtk_mfg_set_performance;
mfg->pd.flags = GENPD_FLAG_OPP_TABLE_FW;
ret = pm_genpd_init(&mfg->pd, NULL, false);
if (ret)
return dev_err_probe(dev, ret, "Failed to initialise power domain\n");
ret = mtk_mfg_init_mbox(mfg);
if (ret) {
dev_err_probe(dev, ret, "Couldn't initialise mailbox\n");
goto err_remove_genpd;
}
ret = mtk_mfg_power_on(&mfg->pd);
if (ret) {
dev_err_probe(dev, ret, "Failed to power on MFG\n");
goto err_free_mbox;
}
ret = mtk_mfg_init_shared_mem(mfg);
if (ret) {
dev_err_probe(dev, ret, "Couldn't initialize EB shared memory\n");
goto err_power_off;
}
ret = mtk_mfg_read_opp_tables(mfg);
if (ret) {
dev_err_probe(dev, ret, "Error reading OPP tables from EB\n");
goto err_power_off;
}
ret = mtk_mfg_init_clk_provider(mfg);
if (ret)
goto err_power_off;
ret = of_genpd_add_provider_simple(dev->of_node, &mfg->pd);
if (ret) {
dev_err_probe(dev, ret, "Failed to add pmdomain provider\n");
goto err_power_off;
}
return 0;
err_power_off:
mtk_mfg_power_off(&mfg->pd);
err_free_mbox:
mbox_free_channel(mfg->slp_mbox->ch);
mfg->slp_mbox->ch = NULL;
mbox_free_channel(mfg->gf_mbox->ch);
mfg->gf_mbox->ch = NULL;
err_remove_genpd:
pm_genpd_remove(&mfg->pd);
return ret;
}
static const struct of_device_id mtk_mfg_of_match[] = {
{ .compatible = "mediatek,mt8196-gpufreq", .data = &mtk_mfg_mt8196_variant },
{}
};
MODULE_DEVICE_TABLE(of, mtk_mfg_of_match);
static void mtk_mfg_remove(struct platform_device *pdev)
{
struct mtk_mfg *mfg = dev_get_drvdata(&pdev->dev);
if (mtk_mfg_is_powered_on(mfg))
mtk_mfg_power_off(&mfg->pd);
of_genpd_del_provider(pdev->dev.of_node);
pm_genpd_remove(&mfg->pd);
mbox_free_channel(mfg->gf_mbox->ch);
mfg->gf_mbox->ch = NULL;
mbox_free_channel(mfg->slp_mbox->ch);
mfg->slp_mbox->ch = NULL;
}
static struct platform_driver mtk_mfg_driver = {
.driver = {
.name = "mtk-mfg-pmdomain",
.of_match_table = mtk_mfg_of_match,
.suppress_bind_attrs = true,
},
.probe = mtk_mfg_probe,
.remove = mtk_mfg_remove,
};
module_platform_driver(mtk_mfg_driver);
MODULE_AUTHOR("Nicolas Frattaroli <nicolas.frattaroli@collabora.com>");
MODULE_DESCRIPTION("MediaTek MFlexGraphics Power Domain Driver");
MODULE_LICENSE("GPL");