root/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.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");