kernel-brax3-ubuntu-touch/drivers/scsi/ufs/vendor/ufshid.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

1029 lines
24 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Universal Flash Storage Host Initiated Defrag (UFS HID)
*
* Copyright (C) 2019-2019 Samsung Electronics Co., Ltd.
*
* Authors:
* Yongmyung Lee <ymhungry.lee@samsung.com>
* Jieon Seol <jieon.seol@samsung.com>
*
* 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.
* See the COPYING file in the top-level directory or visit
* <http://www.gnu.org/licenses/gpl-2.0.html>
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This program is provided "AS IS" and "WITH ALL FAULTS" and
* without warranty of any kind. You are solely responsible for
* determining the appropriateness of using and distributing
* the program and assume all risks associated with your exercise
* of rights with respect to the program, including but not limited
* to infringement of third party rights, the risks and costs of
* program errors, damage to or loss of data, programs or equipment,
* and unavailability or interruption of operations. Under no
* circumstances will the contributor of this Program be liable for
* any damages of any kind arising from your use or distribution of
* this program.
*
* The Linux Foundation chooses to take subject only to the GPLv2
* license terms, and distributes only under these terms.
*/
#include "ufshcd.h"
#include "ufsfeature.h"
#include "ufshid.h"
static int ufshid_create_sysfs(struct ufshid_dev *hid);
inline int ufshid_get_state(struct ufsf_feature *ufsf)
{
return atomic_read(&ufsf->hid_state);
}
inline void ufshid_set_state(struct ufsf_feature *ufsf, int state)
{
atomic_set(&ufsf->hid_state, state);
}
static inline int ufshid_is_not_present(struct ufshid_dev *hid)
{
enum UFSHID_STATE cur_state = ufshid_get_state(hid->ufsf);
if (cur_state != HID_PRESENT) {
INFO_MSG("hid_state != HID_PRESENT (%d)", cur_state);
return -ENODEV;
}
return 0;
}
static int ufshid_read_attr(struct ufshid_dev *hid, u8 idn, u32 *attr_val)
{
struct ufs_hba *hba = hid->ufsf->hba;
int ret = 0;
pm_runtime_get_sync(hba->dev);
ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, 0,
UFSFEATURE_SELECTOR, attr_val);
if (ret) {
ERR_MSG("read attr [0x%.2X] fail. (%d)", idn, ret);
goto err_out;
}
HID_DEBUG(hid, "hid_attr read [0x%.2X] %u (0x%X)", idn, *attr_val,
*attr_val);
TMSG(hid->ufsf, 0, "[ufshid] read_attr IDN %s (%d)",
idn == QUERY_ATTR_IDN_HID_OPERATION ? "HID_OP" :
idn == QUERY_ATTR_IDN_HID_FRAG_LEVEL ? "HID_LEV" : "UNKNOWN", idn);
err_out:
pm_runtime_mark_last_busy(hba->dev);
pm_runtime_put_noidle(hba->dev);
return ret;
}
static int ufshid_write_attr(struct ufshid_dev *hid, u8 idn, u32 val)
{
struct ufs_hba *hba = hid->ufsf->hba;
int ret = 0;
pm_runtime_get_sync(hba->dev);
ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, idn, 0,
UFSFEATURE_SELECTOR, &val);
if (ret) {
ERR_MSG("write attr [0x%.2X] fail. (%d)", idn, ret);
goto err_out;
}
HID_DEBUG(hid, "hid_attr write [0x%.2X] %u (0x%X)", idn, val, val);
TMSG(hid->ufsf, 0, "[ufshid] write_attr IDN %s (%d)",
idn == QUERY_ATTR_IDN_HID_OPERATION ? "HID_OP" :
idn == QUERY_ATTR_IDN_HID_FRAG_LEVEL ? "HID_LEV" : "UNKNOWN", idn);
err_out:
pm_runtime_mark_last_busy(hba->dev);
pm_runtime_put_noidle(hba->dev);
return ret;
}
static inline int ufshid_version_check(int spec_version)
{
INFO_MSG("Support HID Spec : Driver = (%.4x), Device = (%.4x)",
UFSHID_VER, spec_version);
INFO_MSG("HID Driver version (%.6X%s)",
UFSHID_DD_VER, UFSHID_DD_VER_POST);
if (spec_version != UFSHID_VER) {
ERR_MSG("UFS HID version mismatched");
return -ENODEV;
}
return 0;
}
void ufshid_get_dev_info(struct ufsf_feature *ufsf, u8 *desc_buf)
{
int ret = 0, spec_version;
ufsf->hid_dev = NULL;
if (!(LI_EN_32(&desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP]) &
UFS_FEATURE_SUPPORT_HID_BIT)) {
INFO_MSG("bUFSExFeaturesSupport: HID not support");
goto err_out;
}
INFO_MSG("bUFSExFeaturesSupport: HID support");
spec_version =
LI_EN_16(&desc_buf[DEVICE_DESC_PARAM_HID_VER]);
ret = ufshid_version_check(spec_version);
if (ret)
goto err_out;
ufsf->hid_dev = kzalloc(sizeof(struct ufshid_dev), GFP_KERNEL);
if (!ufsf->hid_dev) {
ERR_MSG("hid_dev memalloc fail");
goto err_out;
}
ufsf->hid_dev->ufsf = ufsf;
return;
err_out:
ufshid_set_state(ufsf, HID_FAILED);
}
static int ufshid_get_analyze_and_issue_execute(struct ufshid_dev *hid)
{
u32 attr_val;
int frag_level;
if (ufshid_write_attr(hid, QUERY_ATTR_IDN_HID_OPERATION,
HID_OP_EXECUTE))
return -EINVAL;
if (ufshid_read_attr(hid, QUERY_ATTR_IDN_HID_FRAG_LEVEL, &attr_val))
return -EINVAL;
frag_level = attr_val & HID_FRAG_LEVEL_MASK;
HID_DEBUG(hid, "Frag_lv %d Freg_stat %d HID_need_exec %d",
frag_level, HID_FRAG_UPDATE_STAT(attr_val),
HID_EXECUTE_REQ_STAT(attr_val));
if (frag_level == HID_LEV_GRAY)
return -EAGAIN;
return (HID_EXECUTE_REQ_STAT(attr_val)) ?
HID_REQUIRED : HID_NOT_REQUIRED;
}
static int ufshid_issue_disable(struct ufshid_dev *hid)
{
u32 attr_val;
if (ufshid_write_attr(hid, QUERY_ATTR_IDN_HID_OPERATION,
HID_OP_DISABLE))
return -EINVAL;
if (ufshid_read_attr(hid, QUERY_ATTR_IDN_HID_FRAG_LEVEL, &attr_val))
return -EINVAL;
HID_DEBUG(hid, "Frag_lv %d Freg_stat %d HID_need_exec %d",
attr_val & HID_FRAG_LEVEL_MASK,
HID_FRAG_UPDATE_STAT(attr_val),
HID_EXECUTE_REQ_STAT(attr_val));
return 0;
}
/*
* Lock status: hid_sysfs lock was held when called.
*/
static void ufshid_auto_hibern8_enable(struct ufshid_dev *hid,
unsigned int val)
{
struct ufs_hba *hba = hid->ufsf->hba;
unsigned long flags;
u32 reg;
val = !!val;
/* Update auto hibern8 timer value if supported */
if (!ufshcd_is_auto_hibern8_supported(hba))
return;
pm_runtime_get_sync(hba->dev);
ufshcd_hold(hba, false);
down_write(&hba->clk_scaling_lock);
ufsf_scsi_block_requests(hba);
/* wait for all the outstanding requests to finish */
ufsf_wait_for_doorbell_clr(hba, U64_MAX);
spin_lock_irqsave(hba->host->host_lock, flags);
reg = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
INFO_MSG("ahit-reg 0x%X", reg);
if (val ^ (reg != 0)) {
if (val) {
hba->ahit = hid->ahit;
} else {
/*
* Store current ahit value.
* We don't know who set the ahit value to different
* from the initial value
*/
hid->ahit = reg;
hba->ahit = 0;
}
ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
/* Make sure the timer gets applied before further operations */
mb();
INFO_MSG("[Before] is_auto_enabled %d", hid->is_auto_enabled);
hid->is_auto_enabled = val;
reg = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
INFO_MSG("[After] is_auto_enabled %d ahit-reg 0x%X",
hid->is_auto_enabled, reg);
} else {
INFO_MSG("is_auto_enabled %d. so it does not changed",
hid->is_auto_enabled);
}
spin_unlock_irqrestore(hba->host->host_lock, flags);
ufsf_scsi_unblock_requests(hba);
up_write(&hba->clk_scaling_lock);
ufshcd_release(hba);
pm_runtime_mark_last_busy(hba->dev);
pm_runtime_put_noidle(hba->dev);
}
static void ufshid_block_enter_suspend(struct ufshid_dev *hid)
{
struct ufs_hba *hba = hid->ufsf->hba;
unsigned long flags;
#if defined(CONFIG_UFSHID_POC)
if (unlikely(hid->block_suspend))
return;
hid->block_suspend = true;
#endif
pm_runtime_get_sync(hba->dev);
ufshcd_hold(hba, false);
spin_lock_irqsave(hba->host->host_lock, flags);
HID_DEBUG(hid,
"dev->power.usage_count %d hba->clk_gating.active_reqs %d",
atomic_read(&hba->dev->power.usage_count),
hba->clk_gating.active_reqs);
spin_unlock_irqrestore(hba->host->host_lock, flags);
}
/*
* If the return value is not err, pm_runtime_put_noidle() must be called once.
* IMPORTANT : ufshid_hold_runtime_pm() & ufshid_release_runtime_pm() pair.
*/
static int ufshid_hold_runtime_pm(struct ufshid_dev *hid)
{
struct ufs_hba *hba = hid->ufsf->hba;
if (ufshid_get_state(hid->ufsf) == HID_SUSPEND) {
/* Check that device was suspended by System PM */
pm_runtime_get_sync(hba->dev);
/* If it success, device was suspended by Runtime PM */
if (ufshid_get_state(hid->ufsf) == HID_PRESENT &&
hba->curr_dev_pwr_mode == UFS_ACTIVE_PWR_MODE &&
hba->uic_link_state == UIC_LINK_ACTIVE_STATE)
goto resume_success;
INFO_MSG("RPM resume failed. Maybe it was SPM suspend");
INFO_MSG("UFS state (POWER = %d LINK = %d)",
hba->curr_dev_pwr_mode, hba->uic_link_state);
pm_runtime_mark_last_busy(hba->dev);
pm_runtime_put_noidle(hba->dev);
return -ENODEV;
}
if (ufshid_is_not_present(hid))
return -ENODEV;
pm_runtime_get_sync(hba->dev);
resume_success:
return 0;
}
static inline void ufshid_release_runtime_pm(struct ufshid_dev *hid)
{
struct ufs_hba *hba = hid->ufsf->hba;
pm_runtime_mark_last_busy(hba->dev);
pm_runtime_put_noidle(hba->dev);
}
/*
* Lock status: hid_sysfs lock was held when called.
*/
static int ufshid_trigger_on(struct ufshid_dev *hid)
__must_hold(&hid->sysfs_lock)
{
int ret;
if (hid->hid_trigger)
return 0;
ret = ufshid_hold_runtime_pm(hid);
if (ret)
return ret;
hid->hid_trigger = true;
HID_DEBUG(hid, "trigger 0 -> 1");
ufshid_block_enter_suspend(hid);
ufshid_auto_hibern8_enable(hid, 0);
schedule_delayed_work(&hid->hid_trigger_work, 0);
ufshid_release_runtime_pm(hid);
return 0;
}
static void ufshid_allow_enter_suspend(struct ufshid_dev *hid)
{
struct ufs_hba *hba = hid->ufsf->hba;
unsigned long flags;
#if defined(CONFIG_UFSHID_POC)
if (unlikely(!hid->block_suspend))
return;
hid->block_suspend = false;
#endif
ufshcd_release(hba);
pm_runtime_mark_last_busy(hba->dev);
pm_runtime_put_noidle(hba->dev);
spin_lock_irqsave(hba->host->host_lock, flags);
HID_DEBUG(hid,
"dev->power.usage_count %d hba->clk_gating.active_reqs %d",
atomic_read(&hba->dev->power.usage_count),
hba->clk_gating.active_reqs);
spin_unlock_irqrestore(hba->host->host_lock, flags);
}
/*
* Lock status: hid_sysfs lock was held when called.
*/
static int ufshid_trigger_off(struct ufshid_dev *hid)
__must_hold(&hid->sysfs_lock)
{
int ret;
if (!hid->hid_trigger)
return 0;
ret = ufshid_hold_runtime_pm(hid);
if (ret)
return ret;
hid->hid_trigger = false;
HID_DEBUG(hid, "hid_trigger 1 -> 0");
ufshid_issue_disable(hid);
ufshid_auto_hibern8_enable(hid, 1);
ufshid_allow_enter_suspend(hid);
ufshid_release_runtime_pm(hid);
return 0;
}
#if defined(CONFIG_UFSHID_DEBUG)
static void ufshid_print_hid_info(struct ufshid_dev *hid)
{
struct ufs_hba *hba = hid->ufsf->hba;
unsigned long flags;
spin_lock_irqsave(hba->host->host_lock, flags);
HID_DEBUG(hid, "r_cnt %llu w_cnt %llu r_sectors %llu w_sectors %llu "
"w_query_cnt %llu\n", hid->read_cnt, hid->write_cnt,
hid->read_sec, hid->write_sec, hid->write_query_cnt);
hid->read_cnt = hid->write_cnt = hid->read_sec = hid->write_sec = 0;
hid->write_query_cnt = 0;
spin_unlock_irqrestore(hba->host->host_lock, flags);
}
/*
* Lock status: hba->host->host_lock was held when called.
* So, Don't need to use atomic operation for stats.
*/
void ufshid_acc_io_stat_during_trigger(struct ufsf_feature *ufsf,
struct ufshcd_lrb *lrbp)
{
struct ufs_hba *hba = ufsf->hba;
struct scsi_cmnd *scmd = lrbp->cmd;
struct ufshid_dev *hid = ufsf->hid_dev;
struct ufs_query_req *request;
if (!hid || !hid->hid_trigger)
return;
if (scmd) {
if (scmd->sc_data_direction == DMA_FROM_DEVICE) {
hid->read_cnt++;
hid->read_sec += blk_rq_sectors(scmd->request);
} else {
hid->write_cnt++;
hid->write_sec += blk_rq_sectors(scmd->request);
}
} else {
request = &hba->dev_cmd.query.request;
switch (request->upiu_req.opcode) {
case UPIU_QUERY_OPCODE_WRITE_DESC:
case UPIU_QUERY_OPCODE_WRITE_ATTR:
if (request->upiu_req.idn ==
QUERY_ATTR_IDN_HID_OPERATION)
break;
case UPIU_QUERY_OPCODE_SET_FLAG:
case UPIU_QUERY_OPCODE_CLEAR_FLAG:
case UPIU_QUERY_OPCODE_TOGGLE_FLAG:
hid->write_query_cnt++;
break;
default:
break;
}
}
}
#endif
static void ufshid_trigger_work_fn(struct work_struct *dwork)
{
struct ufshid_dev *hid;
int ret;
hid = container_of(dwork, struct ufshid_dev, hid_trigger_work.work);
if (ufshid_is_not_present(hid))
return;
HID_DEBUG(hid, "start hid_trigger_work_fn");
ret = ufshid_get_analyze_and_issue_execute(hid);
mutex_lock(&hid->sysfs_lock);
if (!hid->hid_trigger) {
HID_DEBUG(hid, "hid_trigger == false, return");
goto finish_work;
}
if (ret == HID_NOT_REQUIRED) {
ret = ufshid_trigger_off(hid);
if (likely(!ret))
goto finish_work;
WARN_MSG("trigger off fail.. must check it");
} else if (ret == HID_REQUIRED) {
HID_DEBUG(hid, "HID_REQUIRED, so sched (%d ms)",
hid->hid_trigger_delay);
} else {
HID_DEBUG(hid, "issue_HID ERR(%X), so resched for retry", ret);
}
mutex_unlock(&hid->sysfs_lock);
#if defined(CONFIG_UFSHID_DEBUG)
ufshid_print_hid_info(hid);
#endif
schedule_delayed_work(&hid->hid_trigger_work,
msecs_to_jiffies(hid->hid_trigger_delay));
HID_DEBUG(hid, "end hid_trigger_work_fn");
return;
finish_work:
mutex_unlock(&hid->sysfs_lock);
}
void ufshid_init(struct ufsf_feature *ufsf)
{
struct ufshid_dev *hid;
int ret;
INFO_MSG("HID_INIT_START");
hid = ufsf->hid_dev;
BUG_ON(!hid);
hid->hid_trigger = false;
hid->hid_trigger_delay = HID_TRIGGER_WORKER_DELAY_MS_DEFAULT;
INIT_DELAYED_WORK(&hid->hid_trigger_work, ufshid_trigger_work_fn);
hid->hid_debug = false;
#if defined(CONFIG_UFSHID_POC)
hid->hid_debug = true;
hid->block_suspend = false;
#endif
/* If HCI supports auto hibern8, UFS Driver use it default */
if (ufshcd_is_auto_hibern8_supported(ufsf->hba))
hid->is_auto_enabled = true;
else
hid->is_auto_enabled = false;
/* Save default Auto-Hibernate Idle Timer register value */
hid->ahit = ufsf->hba->ahit;
ret = ufshid_create_sysfs(hid);
if (ret) {
ERR_MSG("sysfs init fail. so hid driver disabled");
kfree(hid);
ufshid_set_state(ufsf, HID_FAILED);
return;
}
INFO_MSG("UFS HID create sysfs finished");
ufshid_set_state(ufsf, HID_PRESENT);
}
void ufshid_reset_host(struct ufsf_feature *ufsf)
{
struct ufshid_dev *hid = ufsf->hid_dev;
if (!hid)
return;
ufshid_set_state(ufsf, HID_RESET);
cancel_delayed_work_sync(&hid->hid_trigger_work);
}
void ufshid_reset(struct ufsf_feature *ufsf)
{
struct ufshid_dev *hid = ufsf->hid_dev;
if (!hid)
return;
ufshid_set_state(ufsf, HID_PRESENT);
/*
* hid_trigger will be checked under sysfs_lock in worker.
*/
if (hid->hid_trigger)
schedule_delayed_work(&hid->hid_trigger_work, 0);
INFO_MSG("reset completed.");
}
static inline void ufshid_remove_sysfs(struct ufshid_dev *hid)
{
int ret;
ret = kobject_uevent(&hid->kobj, KOBJ_REMOVE);
INFO_MSG("kobject removed (%d)", ret);
kobject_del(&hid->kobj);
}
void ufshid_remove(struct ufsf_feature *ufsf)
{
struct ufshid_dev *hid = ufsf->hid_dev;
int ret;
if (!hid)
return;
INFO_MSG("start HID release");
mutex_lock(&hid->sysfs_lock);
ret = ufshid_trigger_off(hid);
if (unlikely(ret))
ERR_MSG("trigger off fail ret (%d)", ret);
ufshid_remove_sysfs(hid);
ufshid_set_state(ufsf, HID_FAILED);
mutex_unlock(&hid->sysfs_lock);
cancel_delayed_work_sync(&hid->hid_trigger_work);
kfree(hid);
INFO_MSG("end HID release");
}
void ufshid_suspend(struct ufsf_feature *ufsf)
{
struct ufshid_dev *hid = ufsf->hid_dev;
if (!hid)
return;
if (unlikely(hid->hid_trigger))
ERR_MSG("hid_trigger was set to block the suspend. so weird");
ufshid_set_state(ufsf, HID_SUSPEND);
cancel_delayed_work_sync(&hid->hid_trigger_work);
}
void ufshid_resume(struct ufsf_feature *ufsf)
{
struct ufshid_dev *hid = ufsf->hid_dev;
if (!hid)
return;
if (unlikely(hid->hid_trigger))
ERR_MSG("hid_trigger need to off");
ufshid_set_state(ufsf, HID_PRESENT);
}
/*
* this function is called in irq context.
* so cancel_delayed_work_sync() do not use due to waiting.
*/
void ufshid_on_idle(struct ufsf_feature *ufsf)
{
struct ufshid_dev *hid = ufsf->hid_dev;
if (!hid)
return;
/*
* When hid_trigger_work will be scheduled,
* check hid_trigger under sysfs_lock.
*/
if (!hid->hid_trigger)
return;
if (delayed_work_pending(&hid->hid_trigger_work))
cancel_delayed_work(&hid->hid_trigger_work);
schedule_delayed_work(&hid->hid_trigger_work, 0);
}
/* sysfs function */
static ssize_t ufshid_sysfs_show_version(struct ufshid_dev *hid, char *buf)
{
INFO_MSG("HID version (%.4X) D/D version (%.6X%s)",
UFSHID_VER, UFSHID_DD_VER, UFSHID_DD_VER_POST);
return snprintf(buf, PAGE_SIZE,
"HID version (%.4X) D/D version (%.6X%s)\n",
UFSHID_VER, UFSHID_DD_VER, UFSHID_DD_VER_POST);
}
static ssize_t ufshid_sysfs_show_trigger(struct ufshid_dev *hid, char *buf)
{
INFO_MSG("hid_trigger %d", hid->hid_trigger);
return snprintf(buf, PAGE_SIZE, "%d\n", hid->hid_trigger);
}
static ssize_t ufshid_sysfs_store_trigger(struct ufshid_dev *hid,
const char *buf, size_t count)
{
unsigned long val;
ssize_t ret;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
if (val != 0 && val != 1)
return -EINVAL;
INFO_MSG("HID_trigger %lu", val);
if (val == hid->hid_trigger)
return count;
if (val)
ret = ufshid_trigger_on(hid);
else
ret = ufshid_trigger_off(hid);
if (ret) {
INFO_MSG("Changing trigger val %lu is fail (%ld)", val, ret);
return ret;
}
return count;
}
static ssize_t ufshid_sysfs_show_trigger_interval(struct ufshid_dev *hid,
char *buf)
{
INFO_MSG("hid_trigger_interval %d", hid->hid_trigger_delay);
return snprintf(buf, PAGE_SIZE, "%d\n", hid->hid_trigger_delay);
}
static ssize_t ufshid_sysfs_store_trigger_interval(struct ufshid_dev *hid,
const char *buf,
size_t count)
{
unsigned long val;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
if (val < HID_TRIGGER_WORKER_DELAY_MS_MIN ||
val > HID_TRIGGER_WORKER_DELAY_MS_MAX) {
INFO_MSG("hid_trigger_interval (min) %4dms ~ (max) %4dms",
HID_TRIGGER_WORKER_DELAY_MS_MIN,
HID_TRIGGER_WORKER_DELAY_MS_MAX);
return -EINVAL;
}
hid->hid_trigger_delay = (unsigned int)val;
INFO_MSG("hid_trigger_interval %d", hid->hid_trigger_delay);
return count;
}
static ssize_t ufshid_sysfs_show_debug(struct ufshid_dev *hid, char *buf)
{
INFO_MSG("debug %d", hid->hid_debug);
return snprintf(buf, PAGE_SIZE, "%d\n", hid->hid_debug);
}
static ssize_t ufshid_sysfs_store_debug(struct ufshid_dev *hid, const char *buf,
size_t count)
{
unsigned long val;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
if (val != 0 && val != 1)
return -EINVAL;
hid->hid_debug = val ? true : false;
INFO_MSG("debug %d", hid->hid_debug);
return count;
}
static ssize_t ufshid_sysfs_show_color(struct ufshid_dev *hid, char *buf)
{
u32 attr_val;
int frag_level;
if (ufshid_write_attr(hid, QUERY_ATTR_IDN_HID_OPERATION,
HID_OP_ANALYZE)) {
ERR_MSG("query HID_OPERATION fail");
return -EINVAL;
}
if (ufshid_read_attr(hid, QUERY_ATTR_IDN_HID_FRAG_LEVEL, &attr_val)) {
ERR_MSG("query HID_FRAG_LEVEL fail");
return -EINVAL;
}
frag_level = attr_val & HID_FRAG_LEVEL_MASK;
INFO_MSG("Frag_lv %d Freg_stat %d HID_need_exec %d", frag_level,
HID_FRAG_UPDATE_STAT(attr_val),
HID_EXECUTE_REQ_STAT(attr_val));
return snprintf(buf, PAGE_SIZE, "%s\n",
frag_level == HID_LEV_RED ? "RED" :
frag_level == HID_LEV_YELLOW ? "YELLOW" :
frag_level == HID_LEV_GREEN ? "GREEN" :
frag_level == HID_LEV_GRAY ? "GRAY" : "UNKNOWN");
}
#if defined(CONFIG_UFSHID_POC)
static ssize_t ufshid_sysfs_show_debug_op(struct ufshid_dev *hid, char *buf)
{
u32 attr_val;
if (ufshid_read_attr(hid, QUERY_ATTR_IDN_HID_OPERATION, &attr_val))
return -EINVAL;
INFO_MSG("hid_op %d", attr_val);
return snprintf(buf, PAGE_SIZE, "%d\n", attr_val);
}
static ssize_t ufshid_sysfs_store_debug_op(struct ufshid_dev *hid,
const char *buf, size_t count)
{
unsigned long val;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
if (val >= HID_OP_MAX)
return -EINVAL;
if (hid->hid_trigger) {
ERR_MSG("debug_op cannot change, current hid_trigger is ON");
return -EINVAL;
}
if (ufshid_write_attr(hid, QUERY_ATTR_IDN_HID_OPERATION, val))
return -EINVAL;
INFO_MSG("hid_op %ld is set!", val);
return count;
}
static ssize_t ufshid_sysfs_show_block_suspend(struct ufshid_dev *hid,
char *buf)
{
INFO_MSG("block suspend %d", hid->block_suspend);
return snprintf(buf, PAGE_SIZE, "%d\n", hid->block_suspend);
}
static ssize_t ufshid_sysfs_store_block_suspend(struct ufshid_dev *hid,
const char *buf, size_t count)
{
unsigned long val;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
if (val != 0 && val != 1)
return -EINVAL;
INFO_MSG("HID_block_suspend %lu", val);
if (val == hid->block_suspend)
return count;
if (val)
ufshid_block_enter_suspend(hid);
else
ufshid_allow_enter_suspend(hid);
hid->block_suspend = val ? true : false;
return count;
}
static ssize_t ufshid_sysfs_show_auto_hibern8_enable(struct ufshid_dev *hid,
char *buf)
{
INFO_MSG("HCI auto hibern8 %d", hid->is_auto_enabled);
return snprintf(buf, PAGE_SIZE, "%d\n", hid->is_auto_enabled);
}
static ssize_t ufshid_sysfs_store_auto_hibern8_enable(struct ufshid_dev *hid,
const char *buf,
size_t count)
{
unsigned long val;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
if (val != 0 && val != 1)
return -EINVAL;
ufshid_auto_hibern8_enable(hid, val);
return count;
}
#endif
/* SYSFS DEFINE */
#define define_sysfs_ro(_name) __ATTR(_name, 0444, \
ufshid_sysfs_show_##_name, NULL)
#define define_sysfs_rw(_name) __ATTR(_name, 0644, \
ufshid_sysfs_show_##_name, \
ufshid_sysfs_store_##_name)
static struct ufshid_sysfs_entry ufshid_sysfs_entries[] = {
define_sysfs_ro(version),
define_sysfs_ro(color),
define_sysfs_rw(trigger),
define_sysfs_rw(trigger_interval),
/* debug */
define_sysfs_rw(debug),
#if defined(CONFIG_UFSHID_POC)
/* Attribute (RAW) */
define_sysfs_rw(debug_op),
define_sysfs_rw(block_suspend),
define_sysfs_rw(auto_hibern8_enable),
#endif
__ATTR_NULL
};
static ssize_t ufshid_attr_show(struct kobject *kobj, struct attribute *attr,
char *page)
{
struct ufshid_sysfs_entry *entry;
struct ufshid_dev *hid;
ssize_t error;
entry = container_of(attr, struct ufshid_sysfs_entry, attr);
if (!entry->show)
return -EIO;
hid = container_of(kobj, struct ufshid_dev, kobj);
error = ufshid_hold_runtime_pm(hid);
if (error)
return error;
mutex_lock(&hid->sysfs_lock);
error = entry->show(hid, page);
mutex_unlock(&hid->sysfs_lock);
ufshid_release_runtime_pm(hid);
return error;
}
static ssize_t ufshid_attr_store(struct kobject *kobj, struct attribute *attr,
const char *page, size_t length)
{
struct ufshid_sysfs_entry *entry;
struct ufshid_dev *hid;
ssize_t error;
entry = container_of(attr, struct ufshid_sysfs_entry, attr);
if (!entry->store)
return -EIO;
hid = container_of(kobj, struct ufshid_dev, kobj);
error = ufshid_hold_runtime_pm(hid);
if (error)
return error;
mutex_lock(&hid->sysfs_lock);
error = entry->store(hid, page, length);
mutex_unlock(&hid->sysfs_lock);
ufshid_release_runtime_pm(hid);
return error;
}
static const struct sysfs_ops ufshid_sysfs_ops = {
.show = ufshid_attr_show,
.store = ufshid_attr_store,
};
static struct kobj_type ufshid_ktype = {
.sysfs_ops = &ufshid_sysfs_ops,
.release = NULL,
};
static int ufshid_create_sysfs(struct ufshid_dev *hid)
{
struct device *dev = hid->ufsf->hba->dev;
struct ufshid_sysfs_entry *entry;
int err;
hid->sysfs_entries = ufshid_sysfs_entries;
kobject_init(&hid->kobj, &ufshid_ktype);
mutex_init(&hid->sysfs_lock);
INFO_MSG("ufshid creates sysfs ufshid %p dev->kobj %p",
&hid->kobj, &dev->kobj);
err = kobject_add(&hid->kobj, kobject_get(&dev->kobj), "ufshid");
if (!err) {
for (entry = hid->sysfs_entries; entry->attr.name != NULL;
entry++) {
INFO_MSG("ufshid sysfs attr creates: %s",
entry->attr.name);
err = sysfs_create_file(&hid->kobj, &entry->attr);
if (err) {
ERR_MSG("create entry(%s) failed",
entry->attr.name);
goto kobj_del;
}
}
kobject_uevent(&hid->kobj, KOBJ_ADD);
} else {
ERR_MSG("kobject_add failed");
}
return err;
kobj_del:
err = kobject_uevent(&hid->kobj, KOBJ_REMOVE);
INFO_MSG("kobject removed (%d)", err);
kobject_del(&hid->kobj);
return -EINVAL;
}
MODULE_LICENSE("GPL v2");