kernel_samsung_a34x-permissive/drivers/misc/mediatek/freqhopping/fhctl_new/clk-fhctl-mcupm.c

433 lines
9.8 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 MediaTek Inc.
* Author: Yu-Chang Wang <Yu-Chang.Wang@mediatek.com>
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/slab.h>
#include "clk-fhctl.h"
#include "clk-fhctl-pll.h"
#include "clk-fhctl-util.h"
#include "mcupm_ipi_id.h"
#define FHCTL_TARGET FHCTL_MCUPM
#define IPI_TIMEOUT_MS 10
struct match {
char *name;
struct fh_hdlr *hdlr;
int (*init)(struct pll_dts *array
, struct match *match);
};
struct hdlr_data_v1 {
struct pll_dts *array;
struct mutex *lock;
struct fh_pll_domain *domain;
u8 tr_id;
void __iomem *reg_tr;
};
struct freqhopping_ioctl {
unsigned int pll_id;
struct freqhopping_ssc {
unsigned int idx_pattern; /* idx_pattern: Deprecated Field */
unsigned int dt;
unsigned int df;
unsigned int upbnd;
unsigned int lowbnd;
unsigned int dds; /* dds: Deprecated Field */
} ssc_setting; /* used only when user-define */
int result;
};
struct fhctl_ipi_data {
unsigned int cmd;
union {
struct freqhopping_ioctl fh_ctl;
unsigned int args[8];
} u;
};
enum FH_DEVCTL_CMD_ID {
FH_DCTL_CMD_SSC_ENABLE = 0x1004,
FH_DCTL_CMD_SSC_DISABLE = 0x1005,
FH_DCTL_CMD_GENERAL_DFS = 0x1006,
FH_DCTL_CMD_ARM_DFS = 0x1007,
FH_DCTL_CMD_SSC_TBL_CONFIG = 0x100A,
FH_DCTL_CMD_PLL_PAUSE = 0x100E,
FH_DCTL_CMD_MAX,
FH_DBG_CMD_TR_BEGIN64_LOW = 0x2001,
FH_DBG_CMD_TR_BEGIN64_HIGH = 0x2002,
FH_DBG_CMD_TR_END64_LOW = 0x2003,
FH_DBG_CMD_TR_END64_HIGH = 0x2004,
FH_DBG_CMD_TR_ID = 0x2005,
FH_DBG_CMD_TR_VAL = 0x2006,
FH_DBG_CMD_TR_BEGIN32 = 0x2007,
FH_DBG_CMD_TR_END32 = 0x2008,
};
#define FHCTL_D_LEN (sizeof(struct fhctl_ipi_data)/\
sizeof(unsigned int))
static unsigned int ack_data;
static void ipi_get_data(unsigned int cmd)
{
struct fhctl_ipi_data ipi_data;
int ret;
FHDBG("cmd<%x>\n", cmd);
memset(&ipi_data, 0, sizeof(struct fhctl_ipi_data));
ipi_data.cmd = cmd;
/* 3 sec for debug */
ret = mtk_ipi_send_compl(&mcupm_ipidev, CH_S_FHCTL,
IPI_SEND_POLLING, &ipi_data,
FHCTL_D_LEN, 3000);
FHDBG("ret<%d>, ack_data<%x>\n",
ret, ack_data);
}
static void dump_hw(struct fh_pll_regs *regs,
struct fh_pll_data *data)
{
FHDBG("hp_en<%x>,clk_con<%x>,slope0<%x>,slope1<%x>\n",
readl(regs->reg_hp_en), readl(regs->reg_clk_con),
readl(regs->reg_slope0), readl(regs->reg_slope1));
FHDBG("cfg<%x>,lmt<%x>,dds<%x>,dvfs<%x>,mon<%x>\n",
readl(regs->reg_cfg), readl(regs->reg_updnlmt),
readl(regs->reg_dds), readl(regs->reg_dvfs),
readl(regs->reg_mon));
FHDBG("pcw<%x>\n",
readl(regs->reg_con_pcw));
}
static int mcupm_hopping_v1(void *priv_data, char *domain_name, int fh_id,
unsigned int new_dds, int postdiv)
{
int ret;
struct fhctl_ipi_data ipi_data;
struct hdlr_data_v1 *d = (struct hdlr_data_v1 *)priv_data;
struct mutex *lock = d->lock;
ktime_t ktime;
u64 time_ns;
u8 tr_id_local;
struct fh_pll_domain *domain;
struct fh_pll_regs *regs;
struct fh_pll_data *data;
unsigned int con_pcw;
bool has_err = false;
mutex_lock(lock);
memset(&ipi_data, 0, sizeof(struct fhctl_ipi_data));
ipi_data.cmd = FH_DCTL_CMD_GENERAL_DFS;
ipi_data.u.args[0] = fh_id;
ipi_data.u.args[1] = new_dds;
ipi_data.u.args[2] = postdiv;
ktime = ktime_get();
time_ns = ktime_to_ns(ktime);
d->tr_id ^= (((u8)time_ns) | 0x1);
ipi_data.u.args[7] = d->tr_id;
tr_id_local = d->tr_id;
/* make sure tr_id_local is set before send ipi */
mb();
ret = mtk_ipi_send_compl(&mcupm_ipidev, CH_S_FHCTL,
IPI_SEND_POLLING, &ipi_data,
FHCTL_D_LEN, IPI_TIMEOUT_MS);
domain = d->domain;
regs = &domain->regs[fh_id];
data = &domain->data[fh_id];
con_pcw = readl(regs->reg_con_pcw);
con_pcw &= data->dds_mask;
if (con_pcw != new_dds)
has_err |= true;
if (ret != 0)
has_err |= true;
if (ack_data != d->tr_id)
has_err |= true;
if (has_err) {
u32 val;
FHDBG("---------------------------\n");
FHDBG("domain<%s>, fh_id<%d>\n",
domain_name, fh_id);
FHDBG("ret<%d>, ack<%x>, tr<%x>\n",
ret, ack_data, d->tr_id);
FHDBG("con_pcw<%x>, new_dds<%x>\n",
con_pcw, new_dds);
/* dump HW */
dump_hw(regs, data);
/* tr/time via HW */
FHDBG("time_ns<%lx>\n", time_ns);
if (d->reg_tr) {
val = readl(d->reg_tr);
FHDBG("reg_tr<%x>\n", val);
}
/* time via SW */
ipi_get_data(FH_DBG_CMD_TR_BEGIN64_LOW);
ipi_get_data(FH_DBG_CMD_TR_BEGIN64_HIGH);
ipi_get_data(FH_DBG_CMD_TR_END64_LOW);
ipi_get_data(FH_DBG_CMD_TR_END64_HIGH);
ipi_get_data(FH_DBG_CMD_TR_ID);
ipi_get_data(FH_DBG_CMD_TR_VAL);
ipi_get_data(FH_DBG_CMD_TR_BEGIN32);
ipi_get_data(FH_DBG_CMD_TR_END32);
FHDBG("tr_id_local<%x>\n",
++tr_id_local);
FHDBG("---------------------------\n");
/* notify user that err */
mb();
notify_err();
ret = -1;
} else
ret = 0;
mutex_unlock(lock);
return ret;
}
static int mcupm_ssc_enable_v1(void *priv_data,
char *domain_name, int fh_id, int rate)
{
struct freqhopping_ioctl fh_ctl;
struct fhctl_ipi_data ipi_data;
int ret;
struct hdlr_data_v1 *d = (struct hdlr_data_v1 *)priv_data;
struct mutex *lock = d->lock;
struct pll_dts *array = d->array;
struct fh_pll_data *data = d->domain->data;
mutex_lock(lock);
FHDBG("rate<%d>\n", rate);
fh_ctl.pll_id = fh_id;
fh_ctl.result = 0;
fh_ctl.ssc_setting.dt = data->dt_val;
fh_ctl.ssc_setting.df = data->df_val;
fh_ctl.ssc_setting.upbnd = 0;
fh_ctl.ssc_setting.lowbnd = rate;
memset(&ipi_data, 0, sizeof(struct fhctl_ipi_data));
memcpy(&ipi_data.u.fh_ctl, &fh_ctl,
sizeof(struct freqhopping_ioctl));
ipi_data.cmd = FH_DCTL_CMD_SSC_ENABLE;
ret = mtk_ipi_send_compl(&mcupm_ipidev, CH_S_FHCTL,
IPI_SEND_POLLING, &ipi_data,
FHCTL_D_LEN, IPI_TIMEOUT_MS);
FHDBG("ret<%d>\n", ret);
array->ssc_rate = rate;
mutex_unlock(lock);
return 0;
}
static int mcupm_ssc_disable_v1(void *priv_data,
char *domain_name, int fh_id)
{
struct freqhopping_ioctl fh_ctl;
struct fhctl_ipi_data ipi_data;
int ret;
struct hdlr_data_v1 *d = (struct hdlr_data_v1 *)priv_data;
struct mutex *lock = d->lock;
struct pll_dts *array = d->array;
mutex_lock(lock);
FHDBG("\n");
fh_ctl.pll_id = fh_id;
fh_ctl.result = 0;
memset(&ipi_data, 0, sizeof(struct fhctl_ipi_data));
memcpy(&ipi_data.u.fh_ctl, &fh_ctl,
sizeof(struct freqhopping_ioctl));
ipi_data.cmd = FH_DCTL_CMD_SSC_DISABLE;
ret = mtk_ipi_send_compl(&mcupm_ipidev, CH_S_FHCTL,
IPI_SEND_POLLING, &ipi_data,
FHCTL_D_LEN, IPI_TIMEOUT_MS);
FHDBG("ret<%d>\n", ret);
array->ssc_rate = 0;
mutex_unlock(lock);
return 0;
}
static int mcupm_init_v1(struct pll_dts *array, struct match *match)
{
static bool ipi_inited;
static DEFINE_MUTEX(lock);
struct hdlr_data_v1 *priv_data, *match_data;
struct fh_hdlr *hdlr;
FHDBG("array<%x>, %s\n",
array,
array->pll_name);
if (!ipi_inited) {
int ret;
ret = mtk_ipi_register(&mcupm_ipidev, CH_S_FHCTL, NULL,
NULL, (void *)&ack_data);
if (ret) {
FHDBG("[MCUPM] ipi_register fail, ret %d\n", ret);
return -1;
}
ipi_inited = true;
FHDBG("\n");
}
priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL);
hdlr = kzalloc(sizeof(*hdlr), GFP_KERNEL);
init_fh_domain(array->domain,
array->comp,
array->fhctl_base,
array->apmixed_base);
priv_data->array = array;
priv_data->lock = &lock;
priv_data->domain = get_fh_domain(array->domain);
match_data = match->hdlr->data;
if (match_data && match_data->reg_tr) {
priv_data->reg_tr = array->fhctl_base
+ (unsigned int)match_data->reg_tr;
}
/* hook to array */
hdlr->data = priv_data;
hdlr->ops = match->hdlr->ops;
/* hook hdlr to array is the last step */
mb();
array->hdlr = hdlr;
/* do SSC */
if (array->ssc_rate) {
struct fh_hdlr *hdlr;
hdlr = array->hdlr;
hdlr->ops->ssc_enable(hdlr->data,
array->domain,
array->fh_id,
array->ssc_rate);
}
return 0;
}
static struct fh_operation mcupm_ops_v1 = {
.hopping = mcupm_hopping_v1,
.ssc_enable = mcupm_ssc_enable_v1,
.ssc_disable = mcupm_ssc_disable_v1,
};
struct hdlr_data_v1 hdlr_data_6853 = {
.reg_tr = (void __iomem *)(0xC8 + 0x4),
};
static struct fh_hdlr mcupm_hdlr_6853 = {
.ops = &mcupm_ops_v1,
.data = &hdlr_data_6853,
};
static struct match mt6853_match = {
.name = "mediatek,mt6853-fhctl",
.hdlr = &mcupm_hdlr_6853,
.init = &mcupm_init_v1,
};
struct hdlr_data_v1 hdlr_data_6877 = {
.reg_tr = (void __iomem *)(0x90), /* MEMPL Up/Down Limit */
};
static struct fh_hdlr mcupm_hdlr_6877 = {
.ops = &mcupm_ops_v1,
.data = &hdlr_data_6877,
};
static struct match mt6877_match = {
.name = "mediatek,mt6877-fhctl",
.hdlr = &mcupm_hdlr_6877,
.init = &mcupm_init_v1,
};
struct hdlr_data_v1 hdlr_data_6873 = {
.reg_tr = NULL,
};
static struct fh_hdlr mcupm_hdlr_6873 = {
.ops = &mcupm_ops_v1,
.data = &hdlr_data_6873,
};
static struct match mt6873_match = {
.name = "mediatek,mt6873-fhctl",
.hdlr = &mcupm_hdlr_6873,
.init = &mcupm_init_v1,
};
struct hdlr_data_v1 hdlr_data_6885 = {
.reg_tr = NULL,
};
static struct fh_hdlr mcupm_hdlr_6885 = {
.ops = &mcupm_ops_v1,
.data = &hdlr_data_6885,
};
static struct match mt6885_match = {
.name = "mediatek,mt6885-fhctl",
.hdlr = &mcupm_hdlr_6885,
.init = &mcupm_init_v1,
};
static struct match *matches[] = {
&mt6853_match,
&mt6877_match,
&mt6873_match,
&mt6885_match,
NULL,
};
int fhctl_mcupm_init(struct pll_dts *array)
{
int i;
int num_pll;
struct match **match;
FHDBG("\n");
match = matches;
num_pll = array->num_pll;
/* find match by compatible */
while (*match != NULL) {
char *comp = (*match)->name;
char *target = array->comp;
if (strcmp(comp,
target) == 0) {
break;
}
match++;
}
if (*match == NULL) {
FHDBG("no match!\n");
return -1;
}
/* init flow for every pll */
for (i = 0; i < num_pll ; i++, array++) {
char *method = array->method;
if (strcmp(method,
FHCTL_TARGET) == 0) {
(*match)->init(array, *match);
}
}
FHDBG("\n");
return 0;
}