scsi: ufs: core: Probe for temperature notification support

Probe the dExtendedUFSFeaturesSupport register for the device's temperature
notification support and, if supported, add a hardware monitor device.

Link: https://lore.kernel.org/r/20210915060407.40-2-avri.altman@wdc.com
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Reviewed-by: Bean Huo <beanhuo@micron.com>
Reviewed-by: Daejun Park <daejun7.park@samsung.com>
Signed-off-by: Avri Altman <avri.altman@wdc.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
This commit is contained in:
Avri Altman 2021-09-15 09:04:06 +03:00 committed by Martin K. Petersen
parent e76b7c5e25
commit e88e2d3220
6 changed files with 259 additions and 0 deletions

View file

@ -199,3 +199,12 @@ config SCSI_UFS_FAULT_INJECTION
help
Enable fault injection support in the UFS driver. This makes it easier
to test the UFS error handler and abort handler.
config SCSI_UFS_HWMON
bool "UFS Temperature Notification"
depends on SCSI_UFSHCD && HWMON
help
This provides support for UFS hardware monitoring. If enabled,
a hardware monitoring device will be created for the UFS device.
If unsure, say N.

View file

@ -10,6 +10,7 @@ ufshcd-core-$(CONFIG_SCSI_UFS_BSG) += ufs_bsg.o
ufshcd-core-$(CONFIG_SCSI_UFS_CRYPTO) += ufshcd-crypto.o
ufshcd-core-$(CONFIG_SCSI_UFS_HPB) += ufshpb.o
ufshcd-core-$(CONFIG_SCSI_UFS_FAULT_INJECTION) += ufs-fault-injection.o
ufshcd-core-$(CONFIG_SCSI_UFS_HWMON) += ufs-hwmon.o
obj-$(CONFIG_SCSI_UFS_DWC_TC_PCI) += tc-dwc-g210-pci.o ufshcd-dwc.o tc-dwc-g210.o
obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-dwc-g210.o

View file

@ -0,0 +1,198 @@
// SPDX-License-Identifier: GPL-2.0
/*
* UFS hardware monitoring support
* Copyright (c) 2021, Western Digital Corporation
*/
#include <linux/hwmon.h>
#include <linux/units.h>
#include "ufshcd.h"
struct ufs_hwmon_data {
struct ufs_hba *hba;
u8 mask;
};
static int ufs_read_temp_enable(struct ufs_hba *hba, u8 mask, long *val)
{
u32 ee_mask;
int err;
err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, QUERY_ATTR_IDN_EE_CONTROL, 0, 0,
&ee_mask);
if (err)
return err;
*val = (mask & ee_mask & MASK_EE_TOO_HIGH_TEMP) || (mask & ee_mask & MASK_EE_TOO_LOW_TEMP);
return 0;
}
static int ufs_get_temp(struct ufs_hba *hba, enum attr_idn idn, long *val)
{
u32 value;
int err;
err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, 0, 0, &value);
if (err)
return err;
if (value == 0)
return -ENODATA;
*val = ((long)value - 80) * MILLIDEGREE_PER_DEGREE;
return 0;
}
static int ufs_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long *val)
{
struct ufs_hwmon_data *data = dev_get_drvdata(dev);
struct ufs_hba *hba = data->hba;
int err;
down(&hba->host_sem);
if (!ufshcd_is_user_access_allowed(hba)) {
up(&hba->host_sem);
return -EBUSY;
}
ufshcd_rpm_get_sync(hba);
switch (attr) {
case hwmon_temp_enable:
err = ufs_read_temp_enable(hba, data->mask, val);
break;
case hwmon_temp_crit:
err = ufs_get_temp(hba, QUERY_ATTR_IDN_HIGH_TEMP_BOUND, val);
break;
case hwmon_temp_lcrit:
err = ufs_get_temp(hba, QUERY_ATTR_IDN_LOW_TEMP_BOUND, val);
break;
case hwmon_temp_input:
err = ufs_get_temp(hba, QUERY_ATTR_IDN_CASE_ROUGH_TEMP, val);
break;
default:
err = -EOPNOTSUPP;
break;
}
ufshcd_rpm_put_sync(hba);
up(&hba->host_sem);
return err;
}
static int ufs_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long val)
{
struct ufs_hwmon_data *data = dev_get_drvdata(dev);
struct ufs_hba *hba = data->hba;
int err;
if (attr != hwmon_temp_enable)
return -EINVAL;
if (val != 0 && val != 1)
return -EINVAL;
down(&hba->host_sem);
if (!ufshcd_is_user_access_allowed(hba)) {
up(&hba->host_sem);
return -EBUSY;
}
ufshcd_rpm_get_sync(hba);
if (val == 1)
err = ufshcd_update_ee_usr_mask(hba, MASK_EE_URGENT_TEMP, 0);
else
err = ufshcd_update_ee_usr_mask(hba, 0, MASK_EE_URGENT_TEMP);
ufshcd_rpm_put_sync(hba);
up(&hba->host_sem);
return err;
}
static umode_t ufs_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
int channel)
{
if (type != hwmon_temp)
return 0;
switch (attr) {
case hwmon_temp_enable:
return 0644;
case hwmon_temp_crit:
case hwmon_temp_lcrit:
case hwmon_temp_input:
return 0444;
default:
break;
}
return 0;
}
static const struct hwmon_channel_info *ufs_hwmon_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_ENABLE | HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LCRIT),
NULL
};
static const struct hwmon_ops ufs_hwmon_ops = {
.is_visible = ufs_hwmon_is_visible,
.read = ufs_hwmon_read,
.write = ufs_hwmon_write,
};
static const struct hwmon_chip_info ufs_hwmon_hba_info = {
.ops = &ufs_hwmon_ops,
.info = ufs_hwmon_info,
};
void ufs_hwmon_probe(struct ufs_hba *hba, u8 mask)
{
struct device *dev = hba->dev;
struct ufs_hwmon_data *data;
struct device *hwmon;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return;
data->hba = hba;
data->mask = mask;
hwmon = hwmon_device_register_with_info(dev, "ufs", data, &ufs_hwmon_hba_info, NULL);
if (IS_ERR(hwmon)) {
dev_warn(dev, "Failed to instantiate hwmon device\n");
kfree(data);
return;
}
hba->hwmon_device = hwmon;
}
void ufs_hwmon_remove(struct ufs_hba *hba)
{
struct ufs_hwmon_data *data;
if (!hba->hwmon_device)
return;
data = dev_get_drvdata(hba->hwmon_device);
hwmon_device_unregister(hba->hwmon_device);
hba->hwmon_device = NULL;
kfree(data);
}

View file

@ -152,6 +152,9 @@ enum attr_idn {
QUERY_ATTR_IDN_PSA_STATE = 0x15,
QUERY_ATTR_IDN_PSA_DATA_SIZE = 0x16,
QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME = 0x17,
QUERY_ATTR_IDN_CASE_ROUGH_TEMP = 0x18,
QUERY_ATTR_IDN_HIGH_TEMP_BOUND = 0x19,
QUERY_ATTR_IDN_LOW_TEMP_BOUND = 0x1A,
QUERY_ATTR_IDN_WB_FLUSH_STATUS = 0x1C,
QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE = 0x1D,
QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST = 0x1E,
@ -338,6 +341,9 @@ enum {
/* Possible values for dExtendedUFSFeaturesSupport */
enum {
UFS_DEV_LOW_TEMP_NOTIF = BIT(4),
UFS_DEV_HIGH_TEMP_NOTIF = BIT(5),
UFS_DEV_EXT_TEMP_NOTIF = BIT(6),
UFS_DEV_HPB_SUPPORT = BIT(7),
UFS_DEV_WRITE_BOOSTER_SUP = BIT(8),
};
@ -370,6 +376,7 @@ enum {
MASK_EE_WRITEBOOSTER_EVENT = BIT(5),
MASK_EE_PERFORMANCE_THROTTLING = BIT(6),
};
#define MASK_EE_URGENT_TEMP (MASK_EE_TOO_HIGH_TEMP | MASK_EE_TOO_LOW_TEMP)
/* Background operation status */
enum bkops_status {

View file

@ -7469,6 +7469,29 @@ static void ufshcd_wb_probe(struct ufs_hba *hba, u8 *desc_buf)
hba->caps &= ~UFSHCD_CAP_WB_EN;
}
static void ufshcd_temp_notif_probe(struct ufs_hba *hba, u8 *desc_buf)
{
struct ufs_dev_info *dev_info = &hba->dev_info;
u32 ext_ufs_feature;
u8 mask = 0;
if (!(hba->caps & UFSHCD_CAP_TEMP_NOTIF) || dev_info->wspecversion < 0x300)
return;
ext_ufs_feature = get_unaligned_be32(desc_buf + DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP);
if (ext_ufs_feature & UFS_DEV_LOW_TEMP_NOTIF)
mask |= MASK_EE_TOO_LOW_TEMP;
if (ext_ufs_feature & UFS_DEV_HIGH_TEMP_NOTIF)
mask |= MASK_EE_TOO_HIGH_TEMP;
if (mask) {
ufshcd_enable_ee(hba, mask);
ufs_hwmon_probe(hba, mask);
}
}
void ufshcd_fixup_dev_quirks(struct ufs_hba *hba, struct ufs_dev_fix *fixups)
{
struct ufs_dev_fix *f;
@ -7564,6 +7587,8 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
ufshcd_wb_probe(hba, desc_buf);
ufshcd_temp_notif_probe(hba, desc_buf);
/*
* ufshcd_read_string_desc returns size of the string
* reset the error value
@ -9408,6 +9433,7 @@ void ufshcd_remove(struct ufs_hba *hba)
{
if (hba->sdev_ufs_device)
ufshcd_rpm_get_sync(hba);
ufs_hwmon_remove(hba);
ufs_bsg_remove(hba);
ufshpb_remove(hba);
ufs_sysfs_remove_nodes(hba->dev);

View file

@ -653,6 +653,12 @@ enum ufshcd_caps {
* in order to exit DeepSleep state.
*/
UFSHCD_CAP_DEEPSLEEP = 1 << 10,
/*
* This capability allows the host controller driver to use temperature
* notification if it is supported by the UFS device.
*/
UFSHCD_CAP_TEMP_NOTIF = 1 << 11,
};
struct ufs_hba_variant_params {
@ -789,6 +795,10 @@ struct ufs_hba {
struct scsi_device *sdev_ufs_device;
struct scsi_device *sdev_rpmb;
#ifdef CONFIG_SCSI_UFS_HWMON
struct device *hwmon_device;
#endif
enum ufs_dev_pwr_mode curr_dev_pwr_mode;
enum uic_link_state uic_link_state;
/* Desired UFS power management level during runtime PM */
@ -1049,6 +1059,14 @@ static inline u8 ufshcd_wb_get_query_index(struct ufs_hba *hba)
return 0;
}
#ifdef CONFIG_SCSI_UFS_HWMON
void ufs_hwmon_probe(struct ufs_hba *hba, u8 mask);
void ufs_hwmon_remove(struct ufs_hba *hba);
#else
static inline void ufs_hwmon_probe(struct ufs_hba *hba, u8 mask) {}
static inline void ufs_hwmon_remove(struct ufs_hba *hba) {}
#endif
#ifdef CONFIG_PM
extern int ufshcd_runtime_suspend(struct device *dev);
extern int ufshcd_runtime_resume(struct device *dev);