blob: 9a6e928180c8a7e47aad1991dc5e8b78769ab02f [file]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2022 MediaTek Inc.
*/
#include <linux/clk.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/remoteproc/mtk_ccu.h>
#include <linux/sched/clock.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <soc/mediatek/smi.h>
#include <linux/rpmsg.h>
#include <linux/rpmsg/mtk_rpmsg.h>
#include <linux/iommu.h>
#include "clk-mtk.h"
//#include "clk-mux.h"
#include "mtk-mmdvfs-v3.h"
#include "vcp.h"
#include "vcp_reg.h"
#include "vcp_status.h"
static u8 mmdvfs_clk_num;
static struct mtk_mmdvfs_clk *mtk_mmdvfs_clks;
static u8 mmdvfs_pwr_opp[PWR_MMDVFS_NUM];
static struct clk *mmdvfs_pwr_clk[PWR_MMDVFS_NUM];
static u64 mmdvfs_vcp_ipi_data;
static DEFINE_MUTEX(mmdvfs_vcp_ipi_mutex);
static int log_level;
static bool mmdvfs_init_done;
static DEFINE_MUTEX(mmdvfs_vcp_pwr_mutex);
static int vcp_power;
static phys_addr_t mmdvfs_vcp_base;
static bool mmdvfs_free_run;
static struct device *cam_larb_dev;
static DEFINE_MUTEX(mmdvfs_vmm_pwr_mutex);
static int vmm_power;
static int last_vmm_vote_step = -1;
static int last_vmm_force_step = MAX_OPP;
static struct platform_device *ccu_pdev;
static struct rproc *ccu_rproc;
static DEFINE_MUTEX(mmdvfs_ccu_pwr_mutex);
static int ccu_power;
enum mmdvfs_log_level {
log_ipi,
log_ccf_cb,
log_vcp,
};
static inline struct mtk_mmdvfs_clk *to_mtk_mmdvfs_clk(struct clk_hw *hw)
{
return container_of(hw, struct mtk_mmdvfs_clk, clk_hw);
}
int mtk_mmdvfs_enable_ccu(bool enable)
{
int ret = 0;
if (!mmdvfs_free_run) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return 0;
}
if (!ccu_rproc) {
MMDVFS_ERR("there is no ccu_rproc");
return -1;
}
mutex_lock(&mmdvfs_ccu_pwr_mutex);
if (enable) {
if (ccu_power == 0) {
#if IS_ENABLED(CONFIG_MTK_CCU_DEBUG)
ret = rproc_bootx(ccu_rproc, RPROC_UID_MMDVFS);
#else
ret = rproc_boot(ccu_rproc);
#endif
if (ret) {
MMDVFS_ERR("boot ccu rproc fail");
mutex_unlock(&mmdvfs_ccu_pwr_mutex);
return ret;
}
}
ccu_power++;
} else {
if (ccu_power == 0) {
MMDVFS_ERR("disable vcp when vcp_power==0");
mutex_unlock(&mmdvfs_ccu_pwr_mutex);
return -1;
}
if (ccu_power == 1) {
#if IS_ENABLED(CONFIG_MTK_CCU_DEBUG)
rproc_shutdownx(ccu_rproc, RPROC_UID_MMDVFS);
#else
rproc_shutdown(ccu_rproc);
#endif
}
ccu_power--;
}
mutex_unlock(&mmdvfs_ccu_pwr_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(mtk_mmdvfs_enable_ccu);
static int mtk_mmdvfs_enable_vmm(int enable)
{
int result = 0;
if (!cam_larb_dev) {
MMDVFS_ERR("cam_larb_dev is null");
return -EINVAL;
}
mutex_lock(&mmdvfs_vmm_pwr_mutex);
if (enable) {
if (vmm_power == 0) {
mtk_smi_larb_get(cam_larb_dev);
MMDVFS_DBG("power on vmm successfully");
}
vmm_power++;
} else {
if (vmm_power == 0) {
MMDVFS_ERR("power off vmm when vmm_power==0");
mutex_unlock(&mmdvfs_vmm_pwr_mutex);
return -1;
}
if (vmm_power == 1) {
mtk_smi_larb_put(cam_larb_dev);
MMDVFS_DBG("power off vmm successfully");
}
vmm_power--;
}
mutex_unlock(&mmdvfs_vmm_pwr_mutex);
return result;
}
static int set_vmm_enable(const char *val, const struct kernel_param *kp)
{
int result, enable;
result = kstrtoint(val, 0, &enable);
if (result) {
MMDVFS_ERR("failed: %d", result);
return result;
}
result = mtk_mmdvfs_enable_vmm(enable);
return result;
}
static struct kernel_param_ops enable_vmm_ops = {
.set = set_vmm_enable,
};
module_param_cb(enable_vmm, &enable_vmm_ops, NULL, 0644);
MODULE_PARM_DESC(enable_vmm, "enable vmm");
static int mmdvfs_vcp_is_ready(void)
{
int retry = 0;
while (!is_vcp_ready_ex(VCP_A_ID)) {
if (++retry > 100) {
MMDVFS_DBG("VCP_A_ID:%d not ready", VCP_A_ID);
return 0;
}
msleep(100);
}
return 1;
}
int mtk_mmdvfs_enable_vcp(bool enable)
{
int ret;
if (!mmdvfs_free_run) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return 0;
}
mutex_lock(&mmdvfs_vcp_pwr_mutex);
if (enable) {
if (vcp_power == 0) {
ret = vcp_register_feature_ex(MMDVFS_FEATURE_ID);
if (ret) {
MMDVFS_ERR("vcp_register_feature failed:%d", ret);
mutex_unlock(&mmdvfs_vcp_pwr_mutex);
return ret;
}
}
vcp_power++;
} else {
if (vcp_power == 0) {
MMDVFS_ERR("disable vcp when vcp_power==0");
mutex_unlock(&mmdvfs_vcp_pwr_mutex);
return -1;
}
if (vcp_power == 1) {
ret = vcp_deregister_feature_ex(MMDVFS_FEATURE_ID);
if (ret) {
MMDVFS_ERR("vcp_deregister_feature failed:%d", ret);
mutex_unlock(&mmdvfs_vcp_pwr_mutex);
return ret;
}
}
vcp_power--;
}
if (log_level & 1 << log_vcp)
MMDVFS_DBG("enable:%d vcp_power:%d", enable, vcp_power);
mutex_unlock(&mmdvfs_vcp_pwr_mutex);
return 0;
}
EXPORT_SYMBOL(mtk_mmdvfs_enable_vcp);
static int mmdvfs_vcp_ipi_send(const u8 func, const u8 idx, const u8 opp,
const u8 ack) // ap > vcp
{
struct mmdvfs_ipi_data slot = {func, idx, opp, ack, 0U};
int retry = 0, ret;
if (!mmdvfs_init_done) {
MMDVFS_ERR("mmdvfs is not init done");
return -ETIMEDOUT;
}
if (!mmdvfs_vcp_is_ready()) {
MMDVFS_ERR("vcp is not ready");
return -ETIMEDOUT;
}
if (!mmdvfs_vcp_base)
mmdvfs_vcp_base = vcp_get_reserve_mem_phys_ex(MMDVFS_MEM_ID);
if (mmdvfs_vcp_base && ack == MAX_OPP) {
slot.ack = mmdvfs_vcp_base >> 32;
slot.base = (u32)mmdvfs_vcp_base;
}
mutex_lock(&mmdvfs_vcp_ipi_mutex);
mmdvfs_vcp_ipi_data = *(u64 *)(unsigned long *)&slot;
ret = mtk_ipi_send(vcp_get_ipidev(), IPI_OUT_MMDVFS, IPI_SEND_WAIT,
&slot, PIN_OUT_SIZE_MMDVFS, IPI_TIMEOUT_MS);
if (ret != IPI_ACTION_DONE) {
MMDVFS_ERR("mtk_ipi_send failed:%d slot:%#llx data:%#llx",
ret, *(u64 *)(unsigned long *)&slot, mmdvfs_vcp_ipi_data);
mutex_unlock(&mmdvfs_vcp_ipi_mutex);
return ret;
}
while ((mmdvfs_vcp_ipi_data & 0xff) == func) {
if (++retry > 100) {
ret = IPI_COMPL_TIMEOUT;
MMDVFS_ERR("ipi ack timeout:%d slot:%#llx data:%#llx",
ret, *(u64 *)(unsigned long *)&slot, mmdvfs_vcp_ipi_data);
break;
}
udelay(100);
}
mutex_unlock(&mmdvfs_vcp_ipi_mutex);
return ret;
}
static int mmdvfs_vcp_ipi_cb(unsigned int ipi_id, void *prdata, void *data,
unsigned int len) // vcp > ap
{
struct mmdvfs_ipi_data slot;
if (ipi_id != IPI_IN_MMDVFS || !data)
return 0;
slot = *(struct mmdvfs_ipi_data *)data;
if (log_level & 1 << log_ipi)
MMDVFS_DBG("ipi_id:%u slot:%#x func:%hhu idx:%hhu opp:%hhu ack:%hhu",
ipi_id, slot, slot.func, slot.idx, slot.opp, slot.ack);
return 0;
}
static int mtk_mmdvfs_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct mtk_mmdvfs_clk *mmdvfs_clk = to_mtk_mmdvfs_clk(hw);
u8 i, opp, level, pwr_opp = MAX_OPP, user_opp = MAX_OPP;
struct mmdvfs_ipi_data slot;
int ret = 0;
unsigned int img_clk = rate / 1000000UL;
for (i = 0; i < mmdvfs_clk->freq_num; i++)
if (rate <= mmdvfs_clk->freqs[i])
break;
level = (i == mmdvfs_clk->freq_num) ? (i-1) : i;
opp = (mmdvfs_clk->freq_num - level - 1);
if (log_level & 1 << log_ccf_cb)
MMDVFS_DBG("clk=%u user=%u freq=%lu old_opp=%d new_opp=%d",
mmdvfs_clk->clk_id, mmdvfs_clk->user_id, rate, mmdvfs_clk->opp, opp);
if (mmdvfs_clk->opp == opp)
return 0;
mmdvfs_clk->opp = opp;
for (i = 0; i < mmdvfs_clk_num; i++) {
if (mmdvfs_clk->pwr_id == mtk_mmdvfs_clks[i].pwr_id
&& mtk_mmdvfs_clks[i].opp < pwr_opp)
pwr_opp = mtk_mmdvfs_clks[i].opp;
}
/* Choose max step among all users of special independence */
if (mmdvfs_clk->spec_type == SPEC_MMDVFS_ALONE) {
for (i = 0; i < mmdvfs_clk_num; i++) {
if (mmdvfs_clk->user_id == mtk_mmdvfs_clks[i].user_id
&& mtk_mmdvfs_clks[i].opp < user_opp)
user_opp = mtk_mmdvfs_clks[i].opp;
}
if (mmdvfs_free_run)
ret = mmdvfs_vcp_ipi_send(FUNC_SET_OPP,
mmdvfs_clk->user_id, user_opp, MAX_OPP);
} else {
if (pwr_opp == mmdvfs_pwr_opp[mmdvfs_clk->pwr_id])
return 0;
if (mmdvfs_free_run) {
if (mmdvfs_clk->ipi_type == IPI_MMDVFS_CCU) {
if (ccu_pdev) {
mtk_mmdvfs_enable_ccu(true);
ret = mtk_ccu_rproc_ipc_send(
ccu_pdev,
MTK_CCU_FEATURE_ISPDVFS,
4, /*DVFS_IMG_CLK*/
(void *)&img_clk, sizeof(unsigned int));
if (ret)
MMDVFS_ERR("mtk_ccu_rproc_ipc_send fail(%d)", ret);
mtk_mmdvfs_enable_ccu(false);
}
} else {
ret = mmdvfs_vcp_ipi_send(FUNC_SET_OPP,
mmdvfs_clk->user_id, pwr_opp, MAX_OPP);
}
}
}
mmdvfs_pwr_opp[mmdvfs_clk->pwr_id] = pwr_opp;
slot = *(struct mmdvfs_ipi_data *)(u32 *)&mmdvfs_vcp_ipi_data;
if (log_level & 1 << log_ipi)
MMDVFS_DBG("ret:%d slot:%#x idx:%hhu opp:%hhu", ret, slot, slot.idx, slot.ack);
return 0;
}
static long mtk_mmdvfs_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct mtk_mmdvfs_clk *mmdvfs_clk = to_mtk_mmdvfs_clk(hw);
if (log_level & 1 << log_ccf_cb)
MMDVFS_DBG("clk=%u rate=%lu parent_rate=%lu\n",
mmdvfs_clk->clk_id, rate, *parent_rate);
return rate;
}
static unsigned long mtk_mmdvfs_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct mtk_mmdvfs_clk *mmdvfs_clk = to_mtk_mmdvfs_clk(hw);
unsigned long ret;
u8 level;
if (mmdvfs_clk->opp == MAX_OPP)
return 0;
level = mmdvfs_clk->freq_num - mmdvfs_clk->opp - 1;
ret = mmdvfs_clk->freqs[level];
if (log_level & 1 << log_ccf_cb)
MMDVFS_DBG("clk=%u parent_rate=%lu freq=%lu",
mmdvfs_clk->clk_id, parent_rate, ret);
return ret;
}
static const struct clk_ops mtk_mmdvfs_req_ops = {
.set_rate = mtk_mmdvfs_set_rate,
.round_rate = mtk_mmdvfs_round_rate,
.recalc_rate = mtk_mmdvfs_recalc_rate,
};
void *mtk_mmdvfs_vcp_get_base(phys_addr_t *pa)
{
struct mtk_ipi_device *vcp_ipi_dev = vcp_get_ipidev();
phys_addr_t va = vcp_get_reserve_mem_virt_ex(MMDVFS_MEM_ID);
struct iommu_domain *domain;
if (!mmdvfs_free_run || !va) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return NULL;
}
domain = iommu_get_domain_for_dev(&vcp_ipi_dev->mrpdev->pdev->dev);
if (domain) {
*pa = iommu_iova_to_phys(
domain, vcp_get_reserve_mem_phys_ex(MMDVFS_MEM_ID));
} else {
MMDVFS_ERR("domain is null");
return NULL;
}
return (void *)va;
}
EXPORT_SYMBOL_GPL(mtk_mmdvfs_vcp_get_base);
int mtk_mmdvfs_camera_notify(const bool enable)
{
// TODO : replace from_mmqos
return 0;
}
EXPORT_SYMBOL_GPL(mtk_mmdvfs_camera_notify);
int mtk_mmdvfs_camera_notify_from_mmqos(const bool enable)
{
struct mmdvfs_ipi_data slot;
int ret;
if (!mmdvfs_free_run) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return 0;
}
mtk_mmdvfs_enable_vcp(true);
ret = mmdvfs_vcp_ipi_send(FUNC_CAMERA_ON, enable, MAX_OPP, MAX_OPP);
mtk_mmdvfs_enable_vcp(false);
slot = *(struct mmdvfs_ipi_data *)(u32 *)&mmdvfs_vcp_ipi_data;
MMDVFS_DBG("ret:%d slot:%#x ena:%hhu", ret, slot, slot.ack);
return 0;
}
EXPORT_SYMBOL_GPL(mtk_mmdvfs_camera_notify_from_mmqos);
bool mtk_is_mmdvfs_init_done(void)
{
if (!mmdvfs_free_run) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return true;
}
return mmdvfs_init_done;
}
EXPORT_SYMBOL_GPL(mtk_is_mmdvfs_init_done);
int mtk_mmdvfs_v3_set_force_step(u16 pwr_idx, s16 opp)
{
struct mmdvfs_ipi_data slot;
int ret;
if (!mmdvfs_free_run) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return 0;
}
if (pwr_idx >= PWR_MMDVFS_NUM || opp >= MAX_OPP) {
MMDVFS_ERR("wrong pwr_idx:%hu opp:%hd", pwr_idx, opp);
return -EINVAL;
}
if (opp < 0)
opp = MAX_OPP;
if (pwr_idx == PWR_MMDVFS_VMM) {
if (opp == last_vmm_force_step)
return 0;
if (last_vmm_force_step == MAX_OPP) {
mtk_mmdvfs_enable_vcp(true);
if (cam_larb_dev)
mtk_mmdvfs_enable_vmm(true);
}
}
ret = mmdvfs_vcp_ipi_send(FUNC_FORCE_OPP, pwr_idx, opp, MAX_OPP);
slot = *(struct mmdvfs_ipi_data *)(u32 *)&mmdvfs_vcp_ipi_data;
MMDVFS_DBG("ret:%d slot:%#x idx:%hhu opp:%hhu", ret, slot, slot.idx, slot.ack);
if (pwr_idx == PWR_MMDVFS_VMM) {
if (opp == MAX_OPP) {
if (cam_larb_dev)
mtk_mmdvfs_enable_vmm(false);
mtk_mmdvfs_enable_vcp(false);
}
last_vmm_force_step = opp;
}
return ret;
}
EXPORT_SYMBOL_GPL(mtk_mmdvfs_v3_set_force_step);
static int mmdvfs_set_force_step(const char *val, const struct kernel_param *kp)
{
int ret;
u16 idx = 0;
s16 opp = 0;
ret = sscanf(val, "%hu %hd", &idx, &opp);
if (ret != 2 || idx >= PWR_MMDVFS_NUM || opp >= MAX_OPP) {
MMDVFS_ERR("input failed:%d idx:%hu opp:%hd", ret, idx, opp);
return -EINVAL;
}
ret = mtk_mmdvfs_v3_set_force_step(idx, opp);
if (ret)
MMDVFS_ERR("mtk_mmdvfs_v3_set_force_step failed:%d idx:%hu opp:%hd",
ret, idx, opp);
return ret;
}
static struct kernel_param_ops mmdvfs_force_step_ops = {
.set = mmdvfs_set_force_step,
};
module_param_cb(force_step, &mmdvfs_force_step_ops, NULL, 0644);
MODULE_PARM_DESC(force_step, "force mmdvfs to specified step");
int mtk_mmdvfs_v3_set_vote_step(u16 pwr_idx, s16 opp)
{
u32 freq = 0;
int ret = 0, i;
if (!mmdvfs_free_run) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return 0;
}
if (pwr_idx >= PWR_MMDVFS_NUM || opp >= MAX_OPP) {
MMDVFS_ERR("failed:%d pwr_idx:%hu opp:%hd", ret, pwr_idx, opp);
return -EINVAL;
}
if (pwr_idx == PWR_MMDVFS_VMM) {
if (opp == last_vmm_vote_step)
return 0;
if (last_vmm_vote_step == -1) {
mtk_mmdvfs_enable_vcp(true);
if (cam_larb_dev)
mtk_mmdvfs_enable_vmm(true);
}
}
for (i = mmdvfs_clk_num - 1; i >= 0; i--)
if (pwr_idx == mtk_mmdvfs_clks[i].pwr_id) {
if (opp >= mtk_mmdvfs_clks[i].freq_num) {
MMDVFS_ERR("i:%d invalid opp:%hd freq_num:%hhu",
i, opp, mtk_mmdvfs_clks[i].freq_num);
break;
}
freq = (opp < 0) ? 0 : mtk_mmdvfs_clks[i].freqs[
mtk_mmdvfs_clks[i].freq_num - 1 - opp];
ret = clk_set_rate(mmdvfs_pwr_clk[pwr_idx], freq);
if (ret)
MMDVFS_ERR("clk_set_rate failed:%d pwr_idx:%hu freq:%hu",
ret, pwr_idx, freq);
break;
}
MMDVFS_DBG("pwr_idx:%hu clk:%p opp:%hd i:%d freq_num:%hhu freq:%u", pwr_idx,
mmdvfs_pwr_clk[pwr_idx], opp, i, mtk_mmdvfs_clks[i].freq_num, freq);
if (pwr_idx == PWR_MMDVFS_VMM) {
if (opp == -1) {
if (cam_larb_dev)
mtk_mmdvfs_enable_vmm(false);
mtk_mmdvfs_enable_vcp(false);
}
last_vmm_vote_step = opp;
}
return ret;
}
EXPORT_SYMBOL_GPL(mtk_mmdvfs_v3_set_vote_step);
static int mmdvfs_set_vote_step(const char *val, const struct kernel_param *kp)
{
int ret;
u16 idx = 0;
s16 opp = 0;
ret = sscanf(val, "%hu %hd", &idx, &opp);
if (ret != 2 || idx >= PWR_MMDVFS_NUM || opp >= MAX_OPP) {
MMDVFS_ERR("failed:%d idx:%hu opp:%hd", ret, idx, opp);
return -EINVAL;
}
ret = mtk_mmdvfs_v3_set_vote_step(idx, opp);
if (ret)
MMDVFS_ERR("mtk_mmdvfs_v3_set_vote_step failed:%d idx:%hu opp:%hd",
ret, idx, opp);
return ret;
}
static struct kernel_param_ops mmdvfs_vote_step_ops = {
.set = mmdvfs_set_vote_step,
};
module_param_cb(vote_step, &mmdvfs_vote_step_ops, NULL, 0644);
MODULE_PARM_DESC(vote_step, "vote mmdvfs to specified step");
static int lpm_spm_suspend_pm_event(struct notifier_block *notifier,
unsigned long pm_event, void *unused)
{
switch (pm_event) {
case PM_HIBERNATION_PREPARE:
return NOTIFY_DONE;
case PM_RESTORE_PREPARE:
return NOTIFY_DONE;
case PM_POST_HIBERNATION:
return NOTIFY_DONE;
case PM_SUSPEND_PREPARE:
MMDVFS_DBG("start suspend");
if (last_vmm_vote_step != -1 && cam_larb_dev)
mtk_mmdvfs_v3_set_vote_step(PWR_MMDVFS_VMM, -1);
if (last_vmm_force_step != MAX_OPP && cam_larb_dev)
mtk_mmdvfs_v3_set_force_step(PWR_MMDVFS_VMM, -1);
//enable_aoc_iso(true);
return NOTIFY_DONE;
case PM_POST_SUSPEND:
return NOTIFY_DONE;
}
return NOTIFY_OK;
}
static struct notifier_block lpm_spm_suspend_pm_notifier_func = {
.notifier_call = lpm_spm_suspend_pm_event,
.priority = 0,
};
int mmdvfs_set_ccu_ipi(const char *val, const struct kernel_param *kp)
{
unsigned int freq = 0;
int ret;
ret = kstrtou32(val, 0, &freq);
if (ret) {
MMDVFS_ERR("failed:%d freq:%hu", ret, freq);
return ret;
}
if (ccu_pdev) {
mtk_mmdvfs_enable_vcp(true);
mtk_mmdvfs_enable_ccu(true);
ret = mtk_ccu_rproc_ipc_send(
ccu_pdev,
MTK_CCU_FEATURE_ISPDVFS,
4, /*DVFS_IMG_CLK*/
(void *)&freq, sizeof(unsigned int));
if (ret)
MMDVFS_ERR("mtk_ccu_rproc_ipc_send fail(%d)", ret);
mtk_mmdvfs_enable_ccu(false);
mtk_mmdvfs_enable_vcp(false);
MMDVFS_DBG("mtk_ccu_rproc_ipc_send freq:%u done", freq);
} else {
MMDVFS_DBG("ccu_pdev is not ready");
}
return 0;
}
static struct kernel_param_ops mmdvfs_ccu_ipi_ops = {
.set = mmdvfs_set_ccu_ipi,
};
module_param_cb(ccu_ipi_test, &mmdvfs_ccu_ipi_ops, NULL, 0644);
MODULE_PARM_DESC(ccu_ipi_test, "trigger ccu ipi test");
int mmdvfs_dump_setting(char *buf, const struct kernel_param *kp)
{
struct mmdvfs_ipi_data slot;
int i, len = 0, ret;
if (!mmdvfs_free_run) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return 0;
}
len += snprintf(buf + len, PAGE_SIZE - len, "user request opp");
mtk_mmdvfs_enable_vcp(true);
for (i = 0; i < USER_NUM; i += 3) {
ret = mmdvfs_vcp_ipi_send(FUNC_GET_OPP, i, i + 1, i + 2);
slot = *(struct mmdvfs_ipi_data *)(u32 *)&mmdvfs_vcp_ipi_data;
MMDVFS_DBG("slot:%#x i:%d opp:%hhu %hhu %hhu",
slot, i, slot.idx, slot.opp, slot.ack);
if (i + 1 >= USER_NUM) {
len += snprintf(buf + len, PAGE_SIZE - len,
", %hhu", slot.idx);
break;
}
if (i + 2 >= USER_NUM) {
len += snprintf(buf + len, PAGE_SIZE - len,
", %hhu, %hhu", slot.idx, slot.opp);
break;
}
len += snprintf(buf + len, PAGE_SIZE - len,
", %hhu, %hhu, %hhu", slot.idx, slot.opp, slot.ack);
}
mtk_mmdvfs_enable_vcp(false);
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
if (len >= PAGE_SIZE) {
len = PAGE_SIZE - 1;
buf[len] = '\n';
}
return len;
}
static struct kernel_param_ops mmdvfs_dump_setting_ops = {
.get = mmdvfs_dump_setting,
};
module_param_cb(dump_setting, &mmdvfs_dump_setting_ops, NULL, 0444);
MODULE_PARM_DESC(dump_setting, "dump mmdvfs current setting");
int mmdvfs_set_vcp_stress(const char *val, const struct kernel_param *kp)
{
struct mmdvfs_ipi_data slot;
u16 ena = 0;
int ret;
if (!mmdvfs_free_run) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return 0;
}
ret = kstrtou16(val, 0, &ena);
if (ret) {
MMDVFS_ERR("failed:%d ena:%hu", ret, ena);
return ret;
}
if (ena)
mtk_mmdvfs_enable_vcp(true);
ret = mmdvfs_vcp_ipi_send(FUNC_STRESS, ena, MAX_OPP, MAX_OPP);
if (!ena)
mtk_mmdvfs_enable_vcp(false);
slot = *(struct mmdvfs_ipi_data *)(u32 *)&mmdvfs_vcp_ipi_data;
MMDVFS_DBG("ret:%d slot:%#x ena:%d ena:%#x", ret, slot, ena, slot.ack);
return 0;
}
static struct kernel_param_ops mmdvfs_vcp_stress_ops = {
.set = mmdvfs_set_vcp_stress,
};
module_param_cb(vcp_stress, &mmdvfs_vcp_stress_ops, NULL, 0644);
MODULE_PARM_DESC(vcp_stress, "trigger mmdvfs vcp stress");
int mmdvfs_get_vcp_log(char *buf, const struct kernel_param *kp)
{
struct mmdvfs_ipi_data slot;
int len = 0, ret;
if (!mmdvfs_free_run) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return 0;
}
mtk_mmdvfs_enable_vcp(true);
ret = mmdvfs_vcp_ipi_send(FUNC_SET_LOG, LOG_NUM, MAX_OPP, MAX_OPP);
mtk_mmdvfs_enable_vcp(false);
slot = *(struct mmdvfs_ipi_data *)(u32 *)&mmdvfs_vcp_ipi_data;
len += snprintf(
buf + len, PAGE_SIZE - len, "mmdvfs vcp log:%#x", slot.ack);
return len;
}
int mmdvfs_set_vcp_log(const char *val, const struct kernel_param *kp)
{
struct mmdvfs_ipi_data slot;
u16 log = 0;
int ret;
if (!mmdvfs_free_run) {
MMDVFS_DBG("mmdvfs_v3 not supported!");
return 0;
}
ret = kstrtou16(val, 0, &log);
if (ret || log >= LOG_NUM) {
MMDVFS_ERR("failed:%d log:%hu", ret, log);
return ret;
}
mtk_mmdvfs_enable_vcp(true);
ret = mmdvfs_vcp_ipi_send(FUNC_SET_LOG, log, MAX_OPP, MAX_OPP);
mtk_mmdvfs_enable_vcp(false);
slot = *(struct mmdvfs_ipi_data *)(u32 *)&mmdvfs_vcp_ipi_data;
MMDVFS_DBG("ret:%d slot:%#x log:%hu log:%#x", ret, slot, log, slot.ack);
return 0;
}
static struct kernel_param_ops mmdvfs_set_vcp_log_ops = {
.get = mmdvfs_get_vcp_log,
.set = mmdvfs_set_vcp_log,
};
module_param_cb(vcp_log, &mmdvfs_set_vcp_log_ops, NULL, 0644);
MODULE_PARM_DESC(vcp_log, "mmdvfs vcp log");
module_param(log_level, uint, 0644);
MODULE_PARM_DESC(log_level, "mmdvfs log level");
static const struct of_device_id of_match_mmdvfs_v3[] = {
{
.compatible = "mediatek,mtk-mmdvfs-v3",
}, {}
};
static int mmdvfs_vcp_init_thread(void *data)
{
static struct mtk_ipi_device *vcp_ipi_dev;
int ret = 0, retry = 0;
while (mtk_mmdvfs_enable_vcp(true)) {
if (++retry > 100) {
MMDVFS_ERR("vcp is not powered on yet");
return -ETIMEDOUT;
}
msleep(1000);
}
retry = 0;
while (!mmdvfs_vcp_is_ready()) {
if (++retry > 100) {
MMDVFS_ERR("vcp is not ready yet");
return -ETIMEDOUT;
}
msleep(1000);
}
retry = 0;
while (!(vcp_ipi_dev = vcp_get_ipidev())) {
if (++retry > 100) {
MMDVFS_ERR("cannot get vcp ipidev");
return -ETIMEDOUT;
}
}
ret = mtk_ipi_register(vcp_ipi_dev, IPI_IN_MMDVFS,
mmdvfs_vcp_ipi_cb, NULL, &mmdvfs_vcp_ipi_data);
if (ret) {
MMDVFS_ERR("mtk_ipi_register failed:%d ipi_id:%d", ret, IPI_IN_MMDVFS);
return ret;
}
mmdvfs_init_done = true;
return ret;
}
static int mmdvfs_ccu_init_thread(void *data)
{
phandle handle;
struct device_node *node = (struct device_node *) data;
struct device_node *rproc_np = NULL;
int ret = 0, retry = 0;
ret = of_property_read_u32(node, "mediatek,ccu_rproc", &handle);
if (ret < 0) {
MMDVFS_DBG("get CCU phandle fail");
return ret;
}
rproc_np = of_find_node_by_phandle(handle);
if (rproc_np) {
ccu_pdev = of_find_device_by_node(rproc_np);
if (!ccu_pdev) {
MMDVFS_ERR("find ccu rproc pdev fail");
ret = -EINVAL;
goto error_handle;
}
while (!(ccu_rproc = rproc_get_by_phandle(handle))) {
msleep(1000);
retry++;
if (retry == 100) {
MMDVFS_ERR("rproc_get_by_phandle fail");
ret = -EINVAL;
goto error_handle;
}
}
of_node_put(rproc_np);
MMDVFS_DBG("get ccu proc pdev successfully\n");
}
return ret;
error_handle:
of_node_put(rproc_np);
return ret;
}
static int mmdvfs_v3_probe(struct platform_device *pdev)
{
const char *MMDVFS_CLK_NAMES = "mediatek,mmdvfs-clock-names";
const char *MMDVFS_CLKS = "mediatek,mmdvfs-clocks";
struct device_node *node = pdev->dev.of_node;
struct clk_onecell_data *clk_data;
struct task_struct *kthr_vcp, *kthr_ccu;
struct device_node *larbnode;
struct platform_device *larbdev;
struct clk *clk;
int i, ret;
ret = of_property_count_strings(node, MMDVFS_CLK_NAMES);
if (ret <= 0) {
MMDVFS_ERR("%s invalid:%d", MMDVFS_CLK_NAMES, ret);
return ret;
}
mmdvfs_clk_num = ret;
mtk_mmdvfs_clks =
kcalloc(mmdvfs_clk_num, sizeof(*mtk_mmdvfs_clks), GFP_KERNEL);
if (!mtk_mmdvfs_clks) {
MMDVFS_ERR("mtk_mmdvfs_clks without memory");
return -ENOMEM;
}
clk_data = mtk_alloc_clk_data(mmdvfs_clk_num);
if (!clk_data) {
MMDVFS_ERR("allocate clk_data failed num:%hhu", mmdvfs_clk_num);
return -ENOMEM;
}
for (i = 0; i < mmdvfs_clk_num; i++) {
struct device_node *table, *opp = NULL;
struct of_phandle_args spec;
struct clk_init_data init = {};
u8 idx = 0;
of_property_read_string_index(
node, MMDVFS_CLK_NAMES, i, &mtk_mmdvfs_clks[i].name);
ret = of_parse_phandle_with_args(
node, MMDVFS_CLKS, "#mmdvfs,clock-cells", i, &spec);
if (ret) {
MMDVFS_ERR("parse %s i:%d failed:%d",
MMDVFS_CLKS, i, ret);
return ret;
}
mtk_mmdvfs_clks[i].clk_id = spec.args[0];
mtk_mmdvfs_clks[i].pwr_id = spec.args[1];
mtk_mmdvfs_clks[i].user_id = spec.args[2];
mtk_mmdvfs_clks[i].ipi_type = spec.args[3];
mtk_mmdvfs_clks[i].spec_type = spec.args[4];
table = of_find_node_by_phandle(spec.args[5]);
of_node_put(spec.np);
do {
u64 freq;
opp = of_get_next_available_child(table, opp);
if (opp) {
of_property_read_u64(opp, "opp-hz", &freq);
mtk_mmdvfs_clks[i].freqs[idx] = freq;
idx += 1;
}
} while (opp);
of_node_put(table);
mtk_mmdvfs_clks[i].opp = MAX_OPP;
mtk_mmdvfs_clks[i].freq_num = idx;
MMDVFS_DBG(
"i:%d name:%s clk:%hhu pwr:%hhu user:%hhu ipi:%hhu spec:%hhu opp:%hhu freq:%hhu",
i, mtk_mmdvfs_clks[i].name, mtk_mmdvfs_clks[i].clk_id,
mtk_mmdvfs_clks[i].pwr_id, mtk_mmdvfs_clks[i].user_id,
mtk_mmdvfs_clks[i].ipi_type,
mtk_mmdvfs_clks[i].spec_type,
mtk_mmdvfs_clks[i].opp, mtk_mmdvfs_clks[i].freq_num);
if (!IS_ERR_OR_NULL(clk_data->clks[i]))
continue;
init.name = mtk_mmdvfs_clks[i].name;
init.ops = &mtk_mmdvfs_req_ops;
mtk_mmdvfs_clks[i].clk_hw.init = &init;
clk = clk_register(NULL, &mtk_mmdvfs_clks[i].clk_hw);
if (IS_ERR_OR_NULL(clk))
MMDVFS_ERR("i:%d clk:%s register failed:%d",
i, mtk_mmdvfs_clks[idx].name, PTR_ERR(clk));
else
clk_data->clks[i] = clk;
}
ret = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
if (ret) {
MMDVFS_ERR("add clk provider failed:%d", ret);
mtk_free_clk_data(clk_data);
return ret;
}
for (i = 0; i < ARRAY_SIZE(mmdvfs_pwr_opp); i++) {
mmdvfs_pwr_opp[i] = MAX_OPP;
clk = of_clk_get(node, i);
if (IS_ERR_OR_NULL(clk))
MMDVFS_DBG("i:%d clk get failed:%d", i, PTR_ERR(clk));
else
mmdvfs_pwr_clk[i] = clk;
}
larbnode = of_parse_phandle(pdev->dev.of_node, "mediatek,larbs", 0);
if (larbnode) {
larbdev = of_find_device_by_node(larbnode);
if (larbdev) {
cam_larb_dev = &larbdev->dev;
register_pm_notifier(&lpm_spm_suspend_pm_notifier_func);
}
of_node_put(larbnode);
}
kthr_vcp = kthread_run(mmdvfs_vcp_init_thread, NULL, "mmdvfs-vcp");
kthr_ccu = kthread_run(mmdvfs_ccu_init_thread, node, "mmdvfs-ccu");
if (of_property_read_bool(node, "mmdvfs-free-run"))
mmdvfs_free_run = true;
return ret;
}
static struct platform_driver clk_mmdvfs_drv = {
.probe = mmdvfs_v3_probe,
.driver = {
.name = "mtk-mmdvfs-v3",
.owner = THIS_MODULE,
.of_match_table = of_match_mmdvfs_v3,
},
};
static int __init clk_mmdvfs_init(void)
{
return platform_driver_register(&clk_mmdvfs_drv);
}
static void __exit clk_mmdvfs_exit(void)
{
platform_driver_unregister(&clk_mmdvfs_drv);
}
module_init(clk_mmdvfs_init);
module_exit(clk_mmdvfs_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MediaTek MMDVFS");
MODULE_AUTHOR("MediaTek Inc.");