225 lines
6 KiB
C
225 lines
6 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-only
|
||
|
/*
|
||
|
* Gear scale with UFS
|
||
|
*
|
||
|
* Copyright (C) 2020 Samsung Electronics Co., Ltd.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
*/
|
||
|
#include <linux/of.h>
|
||
|
#include "ufs-exynos-perf.h"
|
||
|
#include "ufs-cal-if.h"
|
||
|
#include "ufs-exynos.h"
|
||
|
#include "ufs-vs-regs.h"
|
||
|
#if IS_ENABLED(CONFIG_EXYNOS_CPUPM)
|
||
|
#include <soc/samsung/exynos-cpupm.h>
|
||
|
#endif
|
||
|
#include <linux/reboot.h>
|
||
|
|
||
|
#include <trace/events/ufs_exynos_perf.h>
|
||
|
|
||
|
static int exynos_ufs_reboot_handler(struct notifier_block *nb, unsigned long l, void *p)
|
||
|
{
|
||
|
struct ufs_perf_stat_v2 *stat = container_of(nb, struct ufs_perf_stat_v2, reboot_nb);
|
||
|
|
||
|
stat->exynos_stat = UFS_EXYNOS_REBOOT;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int exynos_gear_change_pmc(struct ufs_hba *hba, struct uic_pwr_mode *pmd)
|
||
|
{
|
||
|
struct exynos_ufs *ufs = to_exynos_ufs(hba);
|
||
|
int ret = 0;
|
||
|
|
||
|
/* info gear update */
|
||
|
hba->pwr_info.gear_rx = pmd->gear;
|
||
|
hba->pwr_info.gear_tx = pmd->gear;
|
||
|
hba->pwr_info.lane_rx = pmd->lane;
|
||
|
hba->pwr_info.lane_tx = pmd->lane;
|
||
|
hba->pwr_info.pwr_rx = FAST_MODE;
|
||
|
hba->pwr_info.pwr_tx = FAST_MODE;
|
||
|
hba->pwr_info.hs_rate = PA_HS_MODE_B;
|
||
|
|
||
|
/* Unipro Attribute set */
|
||
|
unipro_writel(&ufs->handle, pmd->gear, UNIP_PA_RXGEAR); /* PA_RxGear */
|
||
|
unipro_writel(&ufs->handle, pmd->gear, UNIP_PA_TXGEAR); /* PA_TxGear */
|
||
|
unipro_writel(&ufs->handle, pmd->lane, UNIP_PA_ACTIVERXDATALENS); /* PA_ActiveRxDataLanes */
|
||
|
unipro_writel(&ufs->handle, pmd->lane, UNIP_PA_ACTIVETXDATALENS); /* PA_ActiveTxDataLanes */
|
||
|
unipro_writel(&ufs->handle, 1, UNIP_PA_RXTERMINATION); /* PA_RxTermination */
|
||
|
unipro_writel(&ufs->handle, 1, UNIP_PA_TXTERMINATION); /* PA_TxTermination */
|
||
|
unipro_writel(&ufs->handle, pmd->hs_series, UNIP_PA_HSSERIES); /* PA_HSSeries */
|
||
|
|
||
|
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODE), (1 << 4 | 1 << 0));
|
||
|
|
||
|
return ret;
|
||
|
};
|
||
|
|
||
|
int ufs_gear_change(struct ufs_hba *hba, bool en)
|
||
|
{
|
||
|
struct exynos_ufs *ufs = to_exynos_ufs(hba);
|
||
|
struct uic_pwr_mode *pmd = &ufs->req_pmd_parm;
|
||
|
struct uic_pwr_mode *act_pmd = &ufs->act_pmd_parm;
|
||
|
struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info;
|
||
|
int res = 0;
|
||
|
u32 set;
|
||
|
|
||
|
if (en) {
|
||
|
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS) || IS_ENABLED(CONFIG_EXYNOS_PM_QOS_MODULE)
|
||
|
if (ufs->pm_qos_int_value)
|
||
|
exynos_pm_qos_update_request(&ufs->pm_qos_int,
|
||
|
ufs->pm_qos_int_value);
|
||
|
#endif
|
||
|
pmd->gear = ufs->cal_param.max_gear;
|
||
|
} else {
|
||
|
pmd->gear = UFS_PWM_G1;
|
||
|
}
|
||
|
|
||
|
pmd->mode = act_pmd->mode;
|
||
|
pmd->hs_series = act_pmd->hs_series;
|
||
|
pmd->lane = act_pmd->lane;
|
||
|
|
||
|
/* pre pmc */
|
||
|
ufs->cal_param.pmd = pmd;
|
||
|
|
||
|
ufshcd_auto_hibern8_update(hba, 0);
|
||
|
res = ufs_call_cal(ufs, 0, ufs_cal_pre_pmc);
|
||
|
if (res) {
|
||
|
dev_info(ufs->dev, "cal pre pmc fail\n");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
set = ufshcd_readl(hba, REG_INTERRUPT_ENABLE);
|
||
|
set &= ~(UIC_POWER_MODE);
|
||
|
ufshcd_writel(hba, set, REG_INTERRUPT_ENABLE);
|
||
|
|
||
|
/* gear change */
|
||
|
res = exynos_gear_change_pmc(hba, pmd);
|
||
|
if (res) {
|
||
|
dev_info(ufs->dev, "pmc set fail\n");
|
||
|
goto out;
|
||
|
} else {
|
||
|
set = ufshcd_readl(hba, REG_INTERRUPT_STATUS);
|
||
|
set &= ~(UIC_POWER_MODE);
|
||
|
ufshcd_writel(hba, set, REG_INTERRUPT_STATUS);
|
||
|
}
|
||
|
|
||
|
/* post pmc */
|
||
|
res = ufs_call_cal(ufs, 0, ufs_cal_post_pmc);
|
||
|
if (res) {
|
||
|
dev_info(ufs->dev, "cal post pmc fail\n");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* W/A for AH8
|
||
|
* have to use dme_peer cmd after send uic cmd
|
||
|
*/
|
||
|
ufshcd_dme_peer_get(hba, UIC_ARG_MIB(PA_MAXRXHSGEAR), &pwr_info->gear_tx);
|
||
|
|
||
|
ufshcd_auto_hibern8_update(hba, ufs->ah8_ahit);
|
||
|
dev_info(ufs->dev, "ufs power mode change: m(%d)g(%d)l(%d)hs-series(%d)\n",
|
||
|
(pmd->mode & 0xF), pmd->gear, pmd->lane, pmd->hs_series);
|
||
|
out:
|
||
|
if (pmd->gear != ufs->cal_param.max_gear) {
|
||
|
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS) || IS_ENABLED(CONFIG_EXYNOS_PM_QOS_MODULE)
|
||
|
if (ufs->pm_qos_int_value)
|
||
|
exynos_pm_qos_update_request(&ufs->pm_qos_int, 0);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static int __ctrl_gear(struct ufs_perf *perf, enum ctrl_op op)
|
||
|
{
|
||
|
struct ufs_perf_stat_v2 *stat = &perf->stat_v2;
|
||
|
struct ufs_hba *hba = perf->hba;
|
||
|
|
||
|
if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL)
|
||
|
return 0;
|
||
|
|
||
|
if (stat->exynos_stat != UFS_EXYNOS_PERF_OPERATIONAL)
|
||
|
return 0;
|
||
|
|
||
|
ufs_gear_change(hba, stat->g_scale_en);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static enum policy_res __update_v2(struct ufs_perf *perf, u32 qd,
|
||
|
enum ufs_perf_op op, enum ufs_perf_entry entry)
|
||
|
{
|
||
|
struct ufs_perf_stat_v2 *stat = &perf->stat_v2;
|
||
|
enum __traffic traffic = 0;
|
||
|
enum policy_res res = R_OK;
|
||
|
s64 diff;
|
||
|
ktime_t time = ktime_get();
|
||
|
|
||
|
if (stat->start_count_time == -1LL) {
|
||
|
stat->start_count_time = time;
|
||
|
traffic = TRAFFIC_NONE;
|
||
|
} else {
|
||
|
diff = ktime_to_ms(ktime_sub(time,
|
||
|
stat->start_count_time));
|
||
|
if (diff >= stat->th_duration) {
|
||
|
if (stat->count / diff >= stat->th_count) {
|
||
|
stat->start_count_time = -1LL;
|
||
|
stat->g_scale_en = 1;
|
||
|
stat->count = 0;
|
||
|
traffic = TRAFFIC_HIGH;
|
||
|
|
||
|
res = R_CTRL;
|
||
|
} else {
|
||
|
stat->start_count_time = -1LL;
|
||
|
stat->count = 0;
|
||
|
stat->g_scale_en = 0;
|
||
|
traffic = TRAFFIC_LOW;
|
||
|
|
||
|
res = R_CTRL;
|
||
|
}
|
||
|
} else {
|
||
|
traffic = TRAFFIC_NONE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (traffic != TRAFFIC_NONE) {
|
||
|
if (stat->o_traffic != traffic) {
|
||
|
perf->ctrl_handle[__CTRL_REQ_GEAR] = stat->g_scale_en ?
|
||
|
CTRL_OP_UP : CTRL_OP_DOWN;
|
||
|
stat->o_traffic = traffic;
|
||
|
} else {
|
||
|
perf->ctrl_handle[__CTRL_REQ_GEAR] = CTRL_OP_NONE;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
void ufs_gear_scale_init(struct ufs_perf *perf)
|
||
|
{
|
||
|
struct ufs_perf_stat_v2 *stat = &perf->stat_v2;
|
||
|
|
||
|
/* register callbacks */
|
||
|
perf->update[__UPDATE_GEAR] = __update_v2;
|
||
|
perf->ctrl[__CTRL_REQ_GEAR] = __ctrl_gear;
|
||
|
|
||
|
/* default thresholds for stats */
|
||
|
stat->start_count_time = -1LL;
|
||
|
stat->th_duration = 100;
|
||
|
stat->th_count = 180000;
|
||
|
stat->o_traffic = TRAFFIC_HIGH;
|
||
|
stat->exynos_stat = UFS_EXYNOS_PERF_OPERATIONAL;
|
||
|
|
||
|
stat->reboot_nb.notifier_call = exynos_ufs_reboot_handler;
|
||
|
register_reboot_notifier(&stat->reboot_nb);
|
||
|
}
|
||
|
|
||
|
MODULE_DESCRIPTION("Exynos UFS gear scale");
|
||
|
MODULE_AUTHOR("Hoyoung Seo <hy50.seo@samsung.com>");
|
||
|
MODULE_LICENSE("GPL");
|
||
|
MODULE_VERSION("0.1");
|