mirror of https://github.com/torvalds/linux.git
1045 lines
28 KiB
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");
|