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

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: 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
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -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.
+1 −0
Original line number Diff line number Diff line
@@ -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
+198 −0
Original line number Diff line number Diff line
// 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);
}
+7 −0
Original line number Diff line number Diff line
@@ -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 {
+26 −0
Original line number Diff line number Diff line
@@ -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);
Loading