kernel_samsung_a53x/drivers/scsi/ufs/ufs-exynos-gear.c
2024-06-15 16:02:09 -03:00

224 lines
6 KiB
C
Executable file

// 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");