scsi: ufs: core: Probe for temperature notification support
authorAvri Altman <avri.altman@wdc.com>
Wed, 15 Sep 2021 06:04:06 +0000 (09:04 +0300)
committerMartin K. Petersen <martin.petersen@oracle.com>
Wed, 22 Sep 2021 04:07:42 +0000 (00:07 -0400)
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>
drivers/scsi/ufs/Kconfig
drivers/scsi/ufs/Makefile
drivers/scsi/ufs/ufs-hwmon.c [new file with mode: 0644]
drivers/scsi/ufs/ufs.h
drivers/scsi/ufs/ufshcd.c
drivers/scsi/ufs/ufshcd.h

index 432df76e6318a6f909d5071609f86a72e264c663..565e8aa6319d429979d2ee631a216291e6fe2c6c 100644 (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.
index c407da9b517132f22c67f617ebc9993e3a47f6d0..966048875b50386118cd6baf44d51aa18f43d76f 100644 (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
diff --git a/drivers/scsi/ufs/ufs-hwmon.c b/drivers/scsi/ufs/ufs-hwmon.c
new file mode 100644 (file)
index 0000000..33b6673
--- /dev/null
@@ -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);
+}
index 8c6b38b1b1422178f7b7b7aa07390bae11f43d90..0bfdca3e648eb00e87371a0e70663d59aa44cf12 100644 (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 {
index 3841ab49f55604f3cf4f9f45178ed4a0d293e033..ce22340024ceb8d4161b94727b6df7921ca97553 100644 (file)
@@ -7469,6 +7469,29 @@ wb_disabled:
        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);
index 52ea6f350b181b8b7ef4af93547e03dd70b3a337..021c858955afc5723b6e30c746d12e25445aac40 100644 (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);