mirror of
https://github.com/physwizz/a155-U-u1.git
synced 2024-11-19 13:27:49 +00:00
1016 lines
25 KiB
C
1016 lines
25 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2021 MediaTek Inc.
|
|
*/
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/pm_opp.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/remoteproc/mtk_ccu.h>
|
|
#include <clocksource/arm_arch_timer.h>
|
|
|
|
#define CREATE_TRACE_POINTS
|
|
#include "mtk-vmm-trace.h"
|
|
#include "internal.h"
|
|
#include "mtk-vmm-regulator.h"
|
|
|
|
static struct dentry *ispdvfs_debugfs_dir;
|
|
static struct regulator *vmm_reg;
|
|
static DECLARE_WAIT_QUEUE_HEAD(vmm_wait_queue);
|
|
|
|
#define REGULATOR_ID_VMM 0
|
|
#define AGING_MARGIN_MICROVOLT 12500
|
|
#define PM_QOS_VMM_ID 0x21
|
|
|
|
int mtk_ispdvfs_fix_dvfs;
|
|
EXPORT_SYMBOL(mtk_ispdvfs_fix_dvfs);
|
|
module_param(mtk_ispdvfs_fix_dvfs, int, 0644);
|
|
|
|
int mtk_ispdvfs_dbg_level;
|
|
EXPORT_SYMBOL(mtk_ispdvfs_dbg_level);
|
|
module_param(mtk_ispdvfs_dbg_level, int, 0644);
|
|
|
|
#define ISPDVFS_DBG
|
|
#ifdef ISPDVFS_DBG
|
|
#define ISP_LOGD(fmt, args...) \
|
|
do { \
|
|
if (mtk_ispdvfs_dbg_level & DVFS_DEBUG_LOG) \
|
|
pr_notice("[ISPDVFS] %s(): " fmt "\n",\
|
|
__func__, ##args); \
|
|
} while (0)
|
|
#else
|
|
#define ISPDVFS_DBG(fmt, args...)
|
|
#endif
|
|
#define ISP_LOGI(fmt, args...) \
|
|
pr_notice("[ISPDVFS] %s(): " fmt "\n", \
|
|
__func__, ##args)
|
|
#define ISP_LOGE(fmt, args...) \
|
|
pr_notice("[ISPDVFS] error %s(),%d: " fmt "\n", \
|
|
__func__, __LINE__, ##args)
|
|
#define ISP_LOGF(fmt, args...) \
|
|
pr_notice("[ISPDVFS] fatal %s(),%d: " fmt "\n", \
|
|
__func__, __LINE__, ##args)
|
|
|
|
#define CREATE_REGULATOR(match, _name) \
|
|
.desc = { \
|
|
.name = match, \
|
|
.of_match = of_match_ptr(match), \
|
|
.ops = &vmm_apmcu_ops, \
|
|
.type = REGULATOR_VOLTAGE, \
|
|
.id = REGULATOR_ID_##_name, \
|
|
.owner = THIS_MODULE, \
|
|
}
|
|
|
|
struct vmm_regulator {
|
|
struct regulator_desc desc;
|
|
struct dvfs_driver_data *dvfs_data;
|
|
int is_enable;
|
|
};
|
|
|
|
struct ispdvfs_dbg_data {
|
|
struct dvfs_driver_data *drv_data;
|
|
struct regulator *reg;
|
|
int max_voltage;
|
|
bool reg_enable;
|
|
};
|
|
|
|
static struct ispdvfs_dbg_data *dbg_data;
|
|
static int fix_force_step(const char *val, const struct kernel_param *kp)
|
|
{
|
|
int ret;
|
|
u64 new_force_step;
|
|
struct dvfs_driver_data *drv_data;
|
|
struct dvfs_table *opp_table;
|
|
struct dvfs_info *current_info;
|
|
struct ccu_handle_info *ccu_handle;
|
|
struct dvfs_ipc_info dvfs_ipi;
|
|
|
|
ret = kstrtou64(val, 0, &new_force_step);
|
|
if (ret) {
|
|
ISP_LOGE("force set step failed: %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
if (!dbg_data) {
|
|
ISP_LOGE("dbg_data is NULL\n");
|
|
ret = PTR_ERR(dbg_data);
|
|
goto out;
|
|
}
|
|
|
|
drv_data = dbg_data->drv_data;
|
|
|
|
opp_table = &drv_data->opp_table;
|
|
if (new_force_step >= opp_table->opp_num) {
|
|
ISP_LOGE("Force level(%d) is out of range\n",
|
|
new_force_step);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
current_info = &(drv_data->current_dvfs);
|
|
mutex_lock(¤t_info->voltage_mutex);
|
|
|
|
dvfs_ipi.minOppIdx = new_force_step;
|
|
dvfs_ipi.maxOppIdx = new_force_step;
|
|
ccu_handle = &(drv_data->ccu_handle);
|
|
ret = mtk_ccu_rproc_ipc_send(
|
|
ccu_handle->ccu_pdev,
|
|
MTK_CCU_FEATURE_ISPDVFS,
|
|
DVFS_VOLTAGE_UPDATE,
|
|
(void *)&dvfs_ipi, sizeof(struct dvfs_ipc_info));
|
|
if (ret) {
|
|
ISP_LOGE("mtk_ccu_rproc_ipc_send(DVFS_VOLTAGE_UPDATE) fail(%lu)\n",
|
|
arch_timer_read_counter());
|
|
WARN_ON(1);
|
|
goto Unlock_Mutex;
|
|
}
|
|
current_info->voltage_target = opp_table->voltage[new_force_step];
|
|
trace_vmm__update_voltage(current_info->voltage_target);
|
|
|
|
Unlock_Mutex:
|
|
mutex_unlock(¤t_info->voltage_mutex);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static struct kernel_param_ops set_force_step_ops = {
|
|
.set = fix_force_step,
|
|
};
|
|
module_param_cb(force_step, &set_force_step_ops, NULL, 0644);
|
|
MODULE_PARM_DESC(force_step, "force vmm dvfs to specified step");
|
|
|
|
#define MAX_BUFFER_SIZE (PAGE_SIZE - 1)
|
|
static int show_setting(char *buf, const struct kernel_param *kp)
|
|
{
|
|
struct dvfs_driver_data *drv_data;
|
|
struct dvfs_table *table;
|
|
struct dvfs_info *current_info;
|
|
struct ccu_handle_info *ccu_handle;
|
|
struct dvfs_ipc_vb vb_info;
|
|
u32 i, ret;
|
|
int written = 0;
|
|
int improve;
|
|
|
|
if (!dbg_data) {
|
|
ISP_LOGE("dbg_data is NULL\n");
|
|
return PTR_ERR(dbg_data);
|
|
}
|
|
drv_data = dbg_data->drv_data;
|
|
|
|
written += snprintf(buf + written, MAX_BUFFER_SIZE - written,
|
|
"mux number:%d\n", drv_data->num_muxes);
|
|
written += snprintf(buf + written, MAX_BUFFER_SIZE - written,
|
|
"mux:");
|
|
for (i = 0; i < drv_data->num_muxes; i++) {
|
|
written += snprintf(buf + written, MAX_BUFFER_SIZE - written,
|
|
"%s ", drv_data->muxes[i].mux_name);
|
|
}
|
|
written += snprintf(buf + written, MAX_BUFFER_SIZE - written,
|
|
"\n");
|
|
|
|
written += snprintf(buf + written, MAX_BUFFER_SIZE - written,
|
|
"Support voltage:");
|
|
|
|
table = &drv_data->opp_table;
|
|
for (i = 0; i < table->opp_num; i++)
|
|
written += snprintf(buf + written, MAX_BUFFER_SIZE - written,
|
|
"%d ", table->voltage[i]);
|
|
written += snprintf(buf + written, MAX_BUFFER_SIZE - written,
|
|
"\n");
|
|
|
|
current_info = &(drv_data->current_dvfs);
|
|
written += snprintf(buf + written, MAX_BUFFER_SIZE - written,
|
|
"Current voltage:%d\n", current_info->voltage_target);
|
|
|
|
ccu_handle = &(drv_data->ccu_handle);
|
|
vb_info.efuseValue = 0;
|
|
ret = mtk_ccu_rproc_ipc_send(
|
|
ccu_handle->ccu_pdev,
|
|
MTK_CCU_FEATURE_ISPDVFS,
|
|
DVFS_CCU_QUERY_VB,
|
|
(void *)&vb_info, sizeof(struct dvfs_ipc_vb));
|
|
if (ret) {
|
|
ISP_LOGE("mtk_ccu_rproc_ipc_send(DVFS_CCU_QUERY_VB) fail(%lu)\n",
|
|
arch_timer_read_counter());
|
|
} else {
|
|
if (drv_data->en_vb)
|
|
written += snprintf(buf + written,
|
|
MAX_BUFFER_SIZE - written,
|
|
"Load supports VB\n");
|
|
else
|
|
written += snprintf(buf + written,
|
|
MAX_BUFFER_SIZE - written,
|
|
"Load does not support VB\n");
|
|
written += snprintf(buf + written,
|
|
MAX_BUFFER_SIZE - written,
|
|
"Efuse reg:(0x%x)\n",
|
|
vb_info.efuseValue);
|
|
}
|
|
|
|
if (table->opp_num <= MAX_OPP_STEP) {
|
|
written += snprintf(buf + written, MAX_BUFFER_SIZE - written,
|
|
"Final (After vb) voltage & improve:\n");
|
|
for (i = 0; i < table->opp_num; i++) {
|
|
improve = table->voltage[i] - vb_info.voltage[i];
|
|
improve = (improve * 100)/table->voltage[i];
|
|
written += snprintf(buf + written,
|
|
MAX_BUFFER_SIZE - written,
|
|
"volt(%d), improve(%d %%)\n",
|
|
vb_info.voltage[i], improve);
|
|
}
|
|
}
|
|
|
|
return written;
|
|
}
|
|
static struct kernel_param_ops dump_param_ops = {.get = show_setting};
|
|
module_param_cb(show_setting, &dump_param_ops, NULL, 0444);
|
|
MODULE_PARM_DESC(show_setting, "dump vmm dvfs current setting");
|
|
|
|
static int regulator_trace_consumers(struct regulator_dev *rdev)
|
|
{
|
|
struct regulator *regulator;
|
|
struct regulator_voltage *voltage;
|
|
const char *devname;
|
|
|
|
list_for_each_entry(regulator, &rdev->consumer_list, list) {
|
|
voltage = ®ulator->voltage[PM_SUSPEND_ON];
|
|
devname = regulator->dev ?
|
|
dev_name(regulator->dev) : "deviceless";
|
|
trace_mtk_pm_qos_update_request(PM_QOS_VMM_ID,
|
|
voltage->min_uV, devname);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int force_voltage(void *data, u64 val)
|
|
{
|
|
struct ispdvfs_dbg_data *dbg_data = (struct ispdvfs_dbg_data *)data;
|
|
int voltage = (int)val;
|
|
|
|
ISP_LOGI("Force votage(%d)", voltage);
|
|
|
|
if (!dbg_data->reg_enable) {
|
|
regulator_enable(dbg_data->reg);
|
|
dbg_data->reg_enable = true;
|
|
}
|
|
|
|
/* Disable vmm regulator when voltage is 0 */
|
|
if (dbg_data->reg_enable && !voltage) {
|
|
regulator_disable(dbg_data->reg);
|
|
dbg_data->reg_enable = false;
|
|
}
|
|
|
|
if (IS_ERR(dbg_data->reg)) {
|
|
ISP_LOGE("can't get dvfs regulator\n");
|
|
return PTR_ERR(dbg_data->reg);
|
|
}
|
|
|
|
regulator_set_voltage(dbg_data->reg, voltage, INT_MAX);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int force_opp_level(void *data, u64 val)
|
|
{
|
|
struct ispdvfs_dbg_data *dbg_data = (struct ispdvfs_dbg_data *)data;
|
|
struct dvfs_driver_data *drv_data;
|
|
struct dvfs_table *opp_table;
|
|
|
|
ISP_LOGI("Force opp level(%d)", val);
|
|
|
|
if (IS_ERR(dbg_data)) {
|
|
ISP_LOGE("dbg_data is NULL\n");
|
|
return PTR_ERR(dbg_data);
|
|
}
|
|
|
|
if (IS_ERR(dbg_data->reg)) {
|
|
ISP_LOGE("can't get dvfs regulator\n");
|
|
return PTR_ERR(dbg_data->reg);
|
|
}
|
|
|
|
drv_data = dbg_data->drv_data;
|
|
if (IS_ERR(drv_data)) {
|
|
ISP_LOGE("dbg_data is NULL\n");
|
|
return PTR_ERR(drv_data);
|
|
}
|
|
opp_table = &(drv_data->opp_table);
|
|
|
|
if (!dbg_data->reg_enable) {
|
|
regulator_enable(dbg_data->reg);
|
|
dbg_data->reg_enable = true;
|
|
}
|
|
|
|
if (val < opp_table->opp_num)
|
|
regulator_set_voltage(dbg_data->reg, opp_table->voltage[val], INT_MAX);
|
|
else {
|
|
ISP_LOGI("Opp level is not in range.\n");
|
|
if (dbg_data->reg_enable) {
|
|
regulator_disable(dbg_data->reg);
|
|
dbg_data->reg_enable = false;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_all_muxes(struct dvfs_driver_data *drv_data, u32 opp_level)
|
|
{
|
|
u32 num_muxes = drv_data->num_muxes;
|
|
u32 i;
|
|
struct clk *mux, *clk_src;
|
|
s32 err;
|
|
|
|
for (i = 0; i < num_muxes; i++) {
|
|
mux = drv_data->muxes[i].mux;
|
|
clk_src = drv_data->muxes[i].clk_src[opp_level];
|
|
err = clk_prepare_enable(mux);
|
|
if (err) {
|
|
ISP_LOGE("prepare mux(%s) fail:%d opp_level:%d\n",
|
|
drv_data->muxes[i].mux_name, err, opp_level);
|
|
continue;
|
|
}
|
|
err = clk_set_parent(mux, clk_src);
|
|
if (err)
|
|
ISP_LOGE("set parent(%s) fail:%d opp_level:%d\n",
|
|
drv_data->muxes[i].mux_name, err, opp_level);
|
|
clk_disable_unprepare(mux);
|
|
}
|
|
}
|
|
|
|
static int enable_all_muxes(struct dvfs_driver_data *drv_data)
|
|
{
|
|
u32 num_muxes;
|
|
u32 i, j;
|
|
struct clk *mux;
|
|
s32 err;
|
|
|
|
if (!drv_data) {
|
|
ISP_LOGE("drv_data is NULL");
|
|
return PTR_ERR(drv_data);
|
|
}
|
|
num_muxes = drv_data->num_muxes;
|
|
|
|
for (i = 0; i < num_muxes; i++) {
|
|
mux = drv_data->muxes[i].mux;
|
|
err = clk_prepare_enable(mux);
|
|
if (err) {
|
|
ISP_LOGE("prepare mux(%s) fail:%d\n",
|
|
drv_data->muxes[i].mux_name, err);
|
|
|
|
for (j = 0; j < i; j++) {
|
|
mux = drv_data->muxes[j].mux;
|
|
clk_disable_unprepare(mux);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int disable_all_muxes(struct dvfs_driver_data *drv_data)
|
|
{
|
|
u32 num_muxes;
|
|
u32 i;
|
|
struct clk *mux;
|
|
|
|
if (!drv_data) {
|
|
ISP_LOGE("drv_data is NULL");
|
|
return PTR_ERR(drv_data);
|
|
}
|
|
num_muxes = drv_data->num_muxes;
|
|
|
|
for (i = 0; i < num_muxes; i++) {
|
|
mux = drv_data->muxes[i].mux;
|
|
clk_disable_unprepare(mux);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ccu_ipc_update_dvfs(uint32_t data, uint32_t len, void *priv)
|
|
{
|
|
ISP_LOGD("Current VMM voltage(%d)", data);
|
|
trace_vmm__update_voltage(data);
|
|
}
|
|
|
|
static int vmm_init_dvfs(struct ccu_handle_info *ccu_handle)
|
|
{
|
|
phandle handle;
|
|
struct device_node *node = NULL, *rproc_np = NULL;
|
|
int ret = 0;
|
|
|
|
node = of_find_compatible_node(NULL, NULL, "mediatek,ispdvfs");
|
|
if (node == NULL) {
|
|
ISP_LOGE("of_find mediatek,ispdvfs fail\n");
|
|
ret = PTR_ERR(node);
|
|
goto error_handle;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "mediatek,ccu_rproc", &handle);
|
|
if (ret < 0) {
|
|
ISP_LOGE("get CCU phandle fail\n");
|
|
goto error_handle;
|
|
}
|
|
|
|
rproc_np = of_find_node_by_phandle(handle);
|
|
if (rproc_np) {
|
|
ccu_handle->ccu_pdev = of_find_device_by_node(rproc_np);
|
|
if (ccu_handle->ccu_pdev == NULL) {
|
|
ISP_LOGF("find ccu rproc pdev fail\n");
|
|
ret = PTR_ERR(ccu_handle->ccu_pdev);
|
|
goto error_handle;
|
|
}
|
|
// Register callback
|
|
mtk_ccu_ipc_register(ccu_handle->ccu_pdev,
|
|
MTK_CCU_MSG_TO_APMCU_DVFS_STATUS,
|
|
ccu_ipc_update_dvfs, NULL);
|
|
|
|
ccu_handle->handle = handle;
|
|
|
|
ISP_LOGD("get ccu proc pdev successfully\n");
|
|
}
|
|
|
|
return ret;
|
|
|
|
error_handle:
|
|
ccu_handle->ccu_pdev = NULL;
|
|
WARN_ON(ret);
|
|
return ret;
|
|
}
|
|
|
|
static int get_idx_by_voltage(
|
|
int voltage,
|
|
const struct dvfs_table *table,
|
|
int *min_volt,
|
|
int *max_volt)
|
|
{
|
|
u32 i = 0;
|
|
|
|
if (!table || !min_volt || !max_volt) {
|
|
ISP_LOGE("some arguments are NULL\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < table->opp_num; i++) {
|
|
if (voltage == table->voltage[i]) {
|
|
*min_volt = i;
|
|
*max_volt = i;
|
|
break;
|
|
} else if (voltage < table->voltage[i]) {
|
|
if (i >= 1) {
|
|
*min_volt = i - 1;
|
|
*max_volt = i;
|
|
} else {
|
|
*min_volt = i;
|
|
*max_volt = i;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == table->opp_num) {
|
|
*min_volt = i - 1;
|
|
*max_volt = i - 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ccu_set_voltage(struct regulator_dev *rdev,
|
|
int min_uV, int max_uV, unsigned int *selector)
|
|
{
|
|
int ret = 0;
|
|
struct vmm_regulator *regulator;
|
|
struct dvfs_driver_data *drv_data;
|
|
struct ccu_handle_info *ccu_handle;
|
|
struct dvfs_info *current_info;
|
|
struct dvfs_ipc_info dvfs_ipi;
|
|
|
|
regulator = rdev_get_drvdata(rdev);
|
|
if (!regulator) {
|
|
ISP_LOGE("rdev_get_drvdata ptr is null");
|
|
return PTR_ERR(regulator);
|
|
}
|
|
|
|
drv_data = regulator->dvfs_data;
|
|
if (!drv_data) {
|
|
ISP_LOGE("drv_data ptr is null");
|
|
return PTR_ERR(drv_data);
|
|
}
|
|
|
|
if (drv_data->disable_dvfs || mtk_ispdvfs_fix_dvfs)
|
|
return 0;
|
|
|
|
/* record vmm & related consumer traces */
|
|
regulator_trace_consumers(rdev);
|
|
|
|
current_info = &(drv_data->current_dvfs);
|
|
mutex_lock(¤t_info->voltage_mutex);
|
|
|
|
if (current_info->voltage_target == min_uV) {
|
|
ret = 0;
|
|
goto Unlock_Mutex;
|
|
}
|
|
|
|
ret = get_idx_by_voltage(min_uV,
|
|
&drv_data->opp_table,
|
|
&dvfs_ipi.minOppIdx,
|
|
&dvfs_ipi.maxOppIdx);
|
|
if (ret) {
|
|
ISP_LOGE("get voltage index fail\n");
|
|
goto Unlock_Mutex;
|
|
}
|
|
|
|
ccu_handle = &(drv_data->ccu_handle);
|
|
ret = mtk_ccu_rproc_ipc_send(
|
|
ccu_handle->ccu_pdev,
|
|
MTK_CCU_FEATURE_ISPDVFS,
|
|
DVFS_VOLTAGE_UPDATE,
|
|
(void *)&dvfs_ipi, sizeof(struct dvfs_ipc_info));
|
|
if (ret) {
|
|
ISP_LOGE("mtk_ccu_rproc_ipc_send(DVFS_VOLTAGE_UPDATE) fail(%lu)\n",
|
|
arch_timer_read_counter());
|
|
WARN_ON(1);
|
|
goto Unlock_Mutex;
|
|
}
|
|
|
|
current_info->voltage_target = min_uV;
|
|
|
|
ISP_LOGD("CCU VMM set voltage (%d) max level(%d)",
|
|
min_uV, dvfs_ipi.maxOppIdx);
|
|
|
|
if (dvfs_ipi.maxOppIdx == dvfs_ipi.minOppIdx)
|
|
trace_vmm__update_voltage(min_uV);
|
|
|
|
Unlock_Mutex:
|
|
mutex_unlock(¤t_info->voltage_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int apmcu_set_voltage(struct regulator_dev *rdev,
|
|
int min_uV, int max_uV, unsigned int *selector)
|
|
{
|
|
int ret = 0;
|
|
struct vmm_regulator *regulator;
|
|
struct dvfs_driver_data *drv_data;
|
|
struct dvfs_info *current_info;
|
|
struct dvfs_table *table;
|
|
int target_voltage = min_uV;
|
|
u32 cur_opp_idx = 0, prev_opp_idx = 0;
|
|
u32 i;
|
|
|
|
trace_vmm__update_voltage(min_uV);
|
|
|
|
regulator = rdev_get_drvdata(rdev);
|
|
if (!regulator) {
|
|
ISP_LOGE("rdev_get_drvdata ptr is null");
|
|
return PTR_ERR(regulator);
|
|
}
|
|
|
|
drv_data = regulator->dvfs_data;
|
|
if (!drv_data) {
|
|
ISP_LOGE("drv_data ptr is null");
|
|
return PTR_ERR(drv_data);
|
|
}
|
|
table = &(drv_data->opp_table);
|
|
|
|
current_info = &(drv_data->current_dvfs);
|
|
mutex_lock(¤t_info->voltage_mutex);
|
|
|
|
for (i = 0; i < table->opp_num; i++) {
|
|
if (min_uV <= table->voltage[i]) {
|
|
cur_opp_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
prev_opp_idx = current_info->opp_level;
|
|
|
|
if (drv_data->simulate_aging)
|
|
target_voltage -= AGING_MARGIN_MICROVOLT;
|
|
|
|
if (cur_opp_idx < prev_opp_idx) {
|
|
/* Upldate Frequency firstly */
|
|
set_all_muxes(drv_data, cur_opp_idx);
|
|
|
|
/* Then update voltage */
|
|
regulator_set_voltage(vmm_reg, target_voltage, INT_MAX);
|
|
current_info->voltage_target = min_uV;
|
|
current_info->opp_level = cur_opp_idx;
|
|
mutex_unlock(¤t_info->voltage_mutex);
|
|
} else {
|
|
/* Update voltage firstly */
|
|
regulator_set_voltage(vmm_reg, target_voltage, INT_MAX);
|
|
current_info->voltage_target = min_uV;
|
|
current_info->opp_level = cur_opp_idx;
|
|
mutex_unlock(¤t_info->voltage_mutex);
|
|
|
|
/* Then update frequency */
|
|
set_all_muxes(drv_data, cur_opp_idx);
|
|
}
|
|
|
|
ISP_LOGD("VMM update voltage(%d)", target_voltage);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int vmm_get_voltage(struct regulator_dev *rdev)
|
|
{
|
|
struct vmm_regulator *regulator;
|
|
struct dvfs_driver_data *drv_data;
|
|
struct dvfs_info *current_info;
|
|
int current_voltage = DEFAULT_VOLTAGE;
|
|
|
|
regulator = rdev_get_drvdata(rdev);
|
|
if (!regulator) {
|
|
ISP_LOGE("rdev_get_drvdata ptr is null");
|
|
goto out;
|
|
}
|
|
|
|
drv_data = regulator->dvfs_data;
|
|
if (!drv_data) {
|
|
ISP_LOGE("drv_data ptr is null");
|
|
goto out;
|
|
}
|
|
|
|
current_info = &(drv_data->current_dvfs);
|
|
mutex_lock(¤t_info->voltage_mutex);
|
|
current_voltage = current_info->voltage_target;
|
|
mutex_unlock(¤t_info->voltage_mutex);
|
|
|
|
out:
|
|
return current_voltage;
|
|
}
|
|
|
|
static void vmm_init_opp_table(struct dvfs_driver_data *data)
|
|
{
|
|
struct dvfs_table *opp_table = &(data->opp_table);
|
|
struct dev_pm_opp *opp;
|
|
unsigned long freq = 0;
|
|
int clk_num = 0, i = 0;
|
|
|
|
dev_pm_opp_of_add_table(data->dev);
|
|
clk_num = dev_pm_opp_get_opp_count(data->dev);
|
|
while (!IS_ERR(opp = dev_pm_opp_find_freq_ceil(data->dev, &freq))) {
|
|
opp_table->frequency[i] = freq;
|
|
opp_table->voltage[i] = dev_pm_opp_get_voltage(opp);
|
|
freq++;
|
|
i++;
|
|
dev_pm_opp_put(opp);
|
|
}
|
|
opp_table->opp_num = clk_num;
|
|
for (i = 0; i < opp_table->opp_num; i++) {
|
|
ISP_LOGD("Opp table: idx=%d, clk=%d volt=%d\n",
|
|
i, opp_table->frequency[i], opp_table->voltage[i]);
|
|
}
|
|
}
|
|
|
|
static int vmm_enable_regulator(struct regulator_dev *rdev)
|
|
{
|
|
struct vmm_regulator *regulator;
|
|
struct dvfs_driver_data *drv_data;
|
|
struct ccu_handle_info *ccu_handle;
|
|
struct dvfs_ipc_init dvfs_ipi_init;
|
|
int ret;
|
|
|
|
ISP_LOGI("Enable vmm regulator");
|
|
|
|
regulator = rdev_get_drvdata(rdev);
|
|
if (!regulator) {
|
|
ISP_LOGE("rdev_get_drvdata ptr is null");
|
|
ret = PTR_ERR(regulator);
|
|
goto error_handle;
|
|
}
|
|
|
|
drv_data = regulator->dvfs_data;
|
|
if (!drv_data) {
|
|
ISP_LOGE("dvfs_data ptr is null");
|
|
ret = PTR_ERR(drv_data);
|
|
goto error_handle;
|
|
}
|
|
|
|
if (!drv_data->mux_is_enable) {
|
|
/* Enable mux so that we could keep mux life cycle */
|
|
ret = enable_all_muxes(drv_data);
|
|
if (ret) {
|
|
ISP_LOGE("enable all mux fail\n");
|
|
goto error_handle;
|
|
}
|
|
drv_data->mux_is_enable = true;
|
|
}
|
|
|
|
ccu_handle = &(drv_data->ccu_handle);
|
|
ret = vmm_init_dvfs(ccu_handle);
|
|
if (ret) {
|
|
ISP_LOGE("boot ccu rproc fail\n");
|
|
goto error_handle;
|
|
}
|
|
|
|
dvfs_ipi_init.needVoltageBin = drv_data->en_vb;
|
|
dvfs_ipi_init.needSimAging = drv_data->simulate_aging;
|
|
dvfs_ipi_init.needCbFromMicroP
|
|
= mtk_ispdvfs_dbg_level & DVFS_DEBUG_MICROP;
|
|
ret = mtk_ccu_rproc_ipc_send(
|
|
ccu_handle->ccu_pdev,
|
|
MTK_CCU_FEATURE_ISPDVFS,
|
|
DVFS_CCU_INIT,
|
|
(void *)&dvfs_ipi_init, sizeof(struct dvfs_ipc_init));
|
|
if (ret) {
|
|
ISP_LOGE("mtk_ccu_rproc_ipc_send(DVFS_CCU_INIT) fail\n");
|
|
goto error_handle;
|
|
}
|
|
|
|
|
|
regulator->is_enable = 1;
|
|
|
|
return 0;
|
|
|
|
error_handle:
|
|
WARN_ON(ret);
|
|
return ret;
|
|
}
|
|
|
|
static int vmm_disable_regulator(struct regulator_dev *rdev)
|
|
{
|
|
struct vmm_regulator *regulator;
|
|
struct dvfs_driver_data *dvfs_data;
|
|
struct ccu_handle_info *ccu_handle;
|
|
struct dvfs_info *current_info;
|
|
int exit = 1;
|
|
int ret = 0;
|
|
|
|
ISP_LOGI("Disable vmm regulator");
|
|
|
|
regulator = rdev_get_drvdata(rdev);
|
|
if (!regulator) {
|
|
ISP_LOGE("rdev_get_drvdata ptr is null");
|
|
ret = PTR_ERR(regulator);
|
|
goto error_handle;
|
|
}
|
|
|
|
dvfs_data = regulator->dvfs_data;
|
|
if (!dvfs_data) {
|
|
ISP_LOGE("dvfs_data ptr is null");
|
|
ret = PTR_ERR(dvfs_data);
|
|
goto error_handle;
|
|
}
|
|
|
|
if (dvfs_data->mux_is_enable) {
|
|
disable_all_muxes(dvfs_data);
|
|
dvfs_data->mux_is_enable = false;
|
|
}
|
|
|
|
ccu_handle = &(dvfs_data->ccu_handle);
|
|
ret = mtk_ccu_rproc_ipc_send(
|
|
ccu_handle->ccu_pdev,
|
|
MTK_CCU_FEATURE_ISPDVFS,
|
|
DVFS_CCU_DVFS_RESET,
|
|
(void *)&exit, sizeof(exit));
|
|
if (ret) {
|
|
ISP_LOGE("ccu ipc fail(DVFS_CCU_DVFS_RESET) fail");
|
|
goto error_handle;
|
|
}
|
|
|
|
memset(ccu_handle, 0, sizeof(*ccu_handle));
|
|
|
|
current_info = &(dvfs_data->current_dvfs);
|
|
current_info->voltage_target = DEFAULT_VOLTAGE;
|
|
|
|
regulator->is_enable = 0;
|
|
return 0;
|
|
|
|
error_handle:
|
|
WARN_ON(ret);
|
|
return ret;
|
|
}
|
|
|
|
static int vmm_is_enabled(struct regulator_dev *rdev)
|
|
{
|
|
struct vmm_regulator *regulator = rdev_get_drvdata(rdev);
|
|
|
|
return regulator->is_enable;
|
|
}
|
|
|
|
static const struct regulator_ops vmm_apmcu_ops = {
|
|
.list_voltage = regulator_list_voltage_table,
|
|
.set_voltage = apmcu_set_voltage,
|
|
.get_voltage = vmm_get_voltage,
|
|
.enable = vmm_enable_regulator,
|
|
.disable = vmm_disable_regulator,
|
|
.is_enabled = vmm_is_enabled,
|
|
};
|
|
|
|
static const struct regulator_ops vmm_ccu_ops = {
|
|
.list_voltage = regulator_list_voltage_table,
|
|
.set_voltage = ccu_set_voltage,
|
|
.get_voltage = vmm_get_voltage,
|
|
.enable = vmm_enable_regulator,
|
|
.disable = vmm_disable_regulator,
|
|
.is_enabled = vmm_is_enabled,
|
|
};
|
|
|
|
static struct vmm_regulator platform_regulators = {
|
|
CREATE_REGULATOR("vmm-proxy", VMM),
|
|
};
|
|
|
|
static const struct of_device_id mtk_vmm_regulator_match[] = {
|
|
{
|
|
.compatible = "mediatek,ispdvfs",
|
|
.data = &platform_regulators,
|
|
}, {
|
|
/* sentinel */
|
|
},
|
|
};
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(force_voltage_ops, NULL, force_voltage, "%llu\n");
|
|
DEFINE_SIMPLE_ATTRIBUTE(force_opp_level_ops, NULL, force_opp_level, "%llu\n");
|
|
static int vmm_regulator_probe(struct platform_device *pdev)
|
|
{
|
|
const struct of_device_id *match;
|
|
struct device *dev = &pdev->dev;
|
|
struct regulator_config config = { };
|
|
struct regulator_dev *rdev;
|
|
struct vmm_regulator *regulator;
|
|
struct dvfs_driver_data *dvfs_data;
|
|
struct dvfs_table *opp_table;
|
|
struct dvfs_info *current_dvfs;
|
|
int support_micro_processor = 0;
|
|
u32 simulate_aging = 0, en_vb = 0;
|
|
struct property *mux_prop, *clksrc_prop;
|
|
const char *mux_name, *clksrc_name;
|
|
u32 num_mux = 0;
|
|
u32 num_clksrc;
|
|
char prop_name[32];
|
|
struct dentry *dentry;
|
|
u32 opp_level = DEFAULT_VOLTAGE_LEVEL;
|
|
u32 disable_dvfs = 0;
|
|
|
|
match = of_match_node(mtk_vmm_regulator_match, dev->of_node);
|
|
if (!match) {
|
|
ISP_LOGE("invalid compatible string\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
dvfs_data = devm_kzalloc(dev, sizeof(*dvfs_data), GFP_KERNEL);
|
|
if (!dvfs_data) {
|
|
ISP_LOGE("devm_kzalloc dvfs_data fail\n");
|
|
return -ENODEV;
|
|
}
|
|
dvfs_data->dev = dev;
|
|
|
|
vmm_init_opp_table(dvfs_data);
|
|
|
|
current_dvfs = &(dvfs_data->current_dvfs);
|
|
mutex_init(¤t_dvfs->voltage_mutex);
|
|
opp_table = &(dvfs_data->opp_table);
|
|
current_dvfs->voltage_target = opp_table->voltage[opp_level];
|
|
current_dvfs->opp_level = opp_level;
|
|
|
|
of_property_for_each_string(
|
|
dev->of_node, "mediatek,support_mux", mux_prop, mux_name) {
|
|
if (num_mux >= MAX_MUX_NUM) {
|
|
ISP_LOGE("Too many items in support_mux\n");
|
|
return -EINVAL;
|
|
}
|
|
dvfs_data->muxes[num_mux].mux = devm_clk_get(dev, mux_name);
|
|
dvfs_data->muxes[num_mux].mux_name = mux_name;
|
|
snprintf(prop_name, sizeof(prop_name)-1,
|
|
"mediatek,mux_%s", mux_name);
|
|
num_clksrc = 0;
|
|
of_property_for_each_string(
|
|
dev->of_node, prop_name, clksrc_prop, clksrc_name) {
|
|
if (num_clksrc >= MAX_OPP_STEP) {
|
|
ISP_LOGE("Too many items in %s\n", prop_name);
|
|
return -EINVAL;
|
|
}
|
|
dvfs_data->muxes[num_mux].clk_src[num_clksrc] =
|
|
devm_clk_get(dev, clksrc_name);
|
|
num_clksrc++;
|
|
}
|
|
num_mux++;
|
|
}
|
|
dvfs_data->num_muxes = num_mux;
|
|
dvfs_data->mux_is_enable = false;
|
|
|
|
/* Real regualtor instance which controls vmm directly */
|
|
vmm_reg = devm_regulator_get(dev, "buck-vmm");
|
|
if (IS_ERR(vmm_reg))
|
|
ISP_LOGE("could not get buck-vmm regulator\n");
|
|
|
|
of_property_read_u32(dev->of_node,
|
|
"mediatek,disable_dvfs",
|
|
&disable_dvfs);
|
|
dvfs_data->disable_dvfs = disable_dvfs;
|
|
|
|
of_property_read_u32(dev->of_node,
|
|
"simulate_aging",
|
|
&simulate_aging);
|
|
dvfs_data->simulate_aging = simulate_aging;
|
|
|
|
of_property_read_u32(dev->of_node,
|
|
"en_vb",
|
|
&en_vb);
|
|
dvfs_data->en_vb = en_vb;
|
|
|
|
regulator = (struct vmm_regulator *)(match->data);
|
|
regulator->dvfs_data = dvfs_data;
|
|
regulator->desc.n_voltages = ARRAY_SIZE(opp_table->voltage);
|
|
regulator->desc.volt_table = opp_table->voltage;
|
|
|
|
of_property_read_u32(dev->of_node,
|
|
"mediatek,support_micro_processor",
|
|
&support_micro_processor);
|
|
if (support_micro_processor)
|
|
regulator->desc.ops = &vmm_ccu_ops;
|
|
regulator->is_enable = 0;
|
|
|
|
config.dev = dev;
|
|
config.driver_data = regulator;
|
|
rdev = devm_regulator_register(dev,
|
|
®ulator->desc,
|
|
&config);
|
|
if (IS_ERR(rdev)) {
|
|
ISP_LOGE("failed to register %s\n",
|
|
regulator->desc.name);
|
|
goto fail_destroy_mutex;
|
|
}
|
|
|
|
ispdvfs_debugfs_dir = debugfs_create_dir("ispdvfs", NULL);
|
|
if (IS_ERR(ispdvfs_debugfs_dir))
|
|
ISP_LOGE("Failed to create debugfs dir ispdvfs: %ld\n",
|
|
PTR_ERR(ispdvfs_debugfs_dir));
|
|
|
|
dbg_data = devm_kzalloc(dev, sizeof(*dbg_data), GFP_KERNEL);
|
|
if (!dbg_data) {
|
|
ISP_LOGE("devm_kzalloc fail: %ld\n",
|
|
PTR_ERR(dbg_data));
|
|
goto fail_destroy_mutex;
|
|
}
|
|
dbg_data->drv_data = dvfs_data;
|
|
dbg_data->reg = devm_regulator_get(dev, "dvfs-vmm");
|
|
dbg_data->max_voltage = opp_table->voltage[opp_table->opp_num - 1];
|
|
dbg_data->reg_enable = false;
|
|
dentry = debugfs_create_file("force_voltage", 0200,
|
|
ispdvfs_debugfs_dir, dbg_data, &force_voltage_ops);
|
|
if (IS_ERR(dentry))
|
|
ISP_LOGE("Failed to create debugfs force_voltage: %ld\n",
|
|
PTR_ERR(dentry));
|
|
|
|
/* Need to remove. We do not allow select operating point level */
|
|
dentry = debugfs_create_file("force_opp_level", 0200,
|
|
ispdvfs_debugfs_dir, dbg_data, &force_opp_level_ops);
|
|
if (IS_ERR(dentry))
|
|
ISP_LOGE("Failed to create debugfs force_opp_level: %ld\n",
|
|
PTR_ERR(dentry));
|
|
|
|
return 0;
|
|
|
|
fail_destroy_mutex:
|
|
mutex_destroy(¤t_dvfs->voltage_mutex);
|
|
return -ENODEV;
|
|
}
|
|
|
|
static struct platform_driver mtk_vmm_regulator_driver = {
|
|
.driver = {
|
|
.name = "mtk-vmm-regulator",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = mtk_vmm_regulator_match,
|
|
},
|
|
.probe = vmm_regulator_probe,
|
|
};
|
|
|
|
static int __init mtk_vmm_regulator_init(void)
|
|
{
|
|
s32 status;
|
|
|
|
status = platform_driver_register(&mtk_vmm_regulator_driver);
|
|
if (status) {
|
|
ISP_LOGE("Failed to register VMM driver(%d)\n", status);
|
|
return -ENODEV;
|
|
}
|
|
return 0;
|
|
}
|
|
subsys_initcall(mtk_vmm_regulator_init);
|
|
|
|
static void __exit mtk_vmm_regulator_exit(void)
|
|
{
|
|
platform_driver_unregister(&mtk_vmm_regulator_driver);
|
|
}
|
|
module_exit(mtk_vmm_regulator_exit);
|
|
|
|
MODULE_AUTHOR("Yuan Jung Kuo <yuan-jung.kuo@mediatek.com>");
|
|
MODULE_DESCRIPTION("VMM regulator driver");
|
|
MODULE_LICENSE("GPL v2");
|