// 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 #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 #endif #include #include 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 "); MODULE_LICENSE("GPL"); MODULE_VERSION("0.1");