Commit e88e2d32 authored by Avri Altman's avatar Avri Altman Committed by Martin K. Petersen

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.comReviewed-by: default avatarGuenter Roeck <linux@roeck-us.net>
Reviewed-by: default avatarBean Huo <beanhuo@micron.com>
Reviewed-by: default avatarDaejun Park <daejun7.park@samsung.com>
Signed-off-by: default avatarAvri Altman <avri.altman@wdc.com>
Signed-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent e76b7c5e
...@@ -199,3 +199,12 @@ config SCSI_UFS_FAULT_INJECTION ...@@ -199,3 +199,12 @@ config SCSI_UFS_FAULT_INJECTION
help help
Enable fault injection support in the UFS driver. This makes it easier Enable fault injection support in the UFS driver. This makes it easier
to test the UFS error handler and abort handler. 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.
...@@ -10,6 +10,7 @@ ufshcd-core-$(CONFIG_SCSI_UFS_BSG) += ufs_bsg.o ...@@ -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_CRYPTO) += ufshcd-crypto.o
ufshcd-core-$(CONFIG_SCSI_UFS_HPB) += ufshpb.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_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_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 obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-dwc-g210.o
......
// 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);
}
...@@ -152,6 +152,9 @@ enum attr_idn { ...@@ -152,6 +152,9 @@ enum attr_idn {
QUERY_ATTR_IDN_PSA_STATE = 0x15, QUERY_ATTR_IDN_PSA_STATE = 0x15,
QUERY_ATTR_IDN_PSA_DATA_SIZE = 0x16, QUERY_ATTR_IDN_PSA_DATA_SIZE = 0x16,
QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME = 0x17, 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_WB_FLUSH_STATUS = 0x1C,
QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE = 0x1D, QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE = 0x1D,
QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST = 0x1E, QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST = 0x1E,
...@@ -338,6 +341,9 @@ enum { ...@@ -338,6 +341,9 @@ enum {
/* Possible values for dExtendedUFSFeaturesSupport */ /* Possible values for dExtendedUFSFeaturesSupport */
enum { 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_HPB_SUPPORT = BIT(7),
UFS_DEV_WRITE_BOOSTER_SUP = BIT(8), UFS_DEV_WRITE_BOOSTER_SUP = BIT(8),
}; };
...@@ -370,6 +376,7 @@ enum { ...@@ -370,6 +376,7 @@ enum {
MASK_EE_WRITEBOOSTER_EVENT = BIT(5), MASK_EE_WRITEBOOSTER_EVENT = BIT(5),
MASK_EE_PERFORMANCE_THROTTLING = BIT(6), MASK_EE_PERFORMANCE_THROTTLING = BIT(6),
}; };
#define MASK_EE_URGENT_TEMP (MASK_EE_TOO_HIGH_TEMP | MASK_EE_TOO_LOW_TEMP)
/* Background operation status */ /* Background operation status */
enum bkops_status { enum bkops_status {
......
...@@ -7469,6 +7469,29 @@ static void ufshcd_wb_probe(struct ufs_hba *hba, u8 *desc_buf) ...@@ -7469,6 +7469,29 @@ static void ufshcd_wb_probe(struct ufs_hba *hba, u8 *desc_buf)
hba->caps &= ~UFSHCD_CAP_WB_EN; 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) void ufshcd_fixup_dev_quirks(struct ufs_hba *hba, struct ufs_dev_fix *fixups)
{ {
struct ufs_dev_fix *f; struct ufs_dev_fix *f;
...@@ -7564,6 +7587,8 @@ static int ufs_get_device_desc(struct ufs_hba *hba) ...@@ -7564,6 +7587,8 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
ufshcd_wb_probe(hba, desc_buf); ufshcd_wb_probe(hba, desc_buf);
ufshcd_temp_notif_probe(hba, desc_buf);
/* /*
* ufshcd_read_string_desc returns size of the string * ufshcd_read_string_desc returns size of the string
* reset the error value * reset the error value
...@@ -9408,6 +9433,7 @@ void ufshcd_remove(struct ufs_hba *hba) ...@@ -9408,6 +9433,7 @@ void ufshcd_remove(struct ufs_hba *hba)
{ {
if (hba->sdev_ufs_device) if (hba->sdev_ufs_device)
ufshcd_rpm_get_sync(hba); ufshcd_rpm_get_sync(hba);
ufs_hwmon_remove(hba);
ufs_bsg_remove(hba); ufs_bsg_remove(hba);
ufshpb_remove(hba); ufshpb_remove(hba);
ufs_sysfs_remove_nodes(hba->dev); ufs_sysfs_remove_nodes(hba->dev);
......
...@@ -653,6 +653,12 @@ enum ufshcd_caps { ...@@ -653,6 +653,12 @@ enum ufshcd_caps {
* in order to exit DeepSleep state. * in order to exit DeepSleep state.
*/ */
UFSHCD_CAP_DEEPSLEEP = 1 << 10, 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 { struct ufs_hba_variant_params {
...@@ -789,6 +795,10 @@ struct ufs_hba { ...@@ -789,6 +795,10 @@ struct ufs_hba {
struct scsi_device *sdev_ufs_device; struct scsi_device *sdev_ufs_device;
struct scsi_device *sdev_rpmb; 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 ufs_dev_pwr_mode curr_dev_pwr_mode;
enum uic_link_state uic_link_state; enum uic_link_state uic_link_state;
/* Desired UFS power management level during runtime PM */ /* Desired UFS power management level during runtime PM */
...@@ -1049,6 +1059,14 @@ static inline u8 ufshcd_wb_get_query_index(struct ufs_hba *hba) ...@@ -1049,6 +1059,14 @@ static inline u8 ufshcd_wb_get_query_index(struct ufs_hba *hba)
return 0; 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 #ifdef CONFIG_PM
extern int ufshcd_runtime_suspend(struct device *dev); extern int ufshcd_runtime_suspend(struct device *dev);
extern int ufshcd_runtime_resume(struct device *dev); extern int ufshcd_runtime_resume(struct device *dev);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment