@@ -705,6 +705,9 @@ static struct attribute *ufs_sysfs_attributes[] = {
&dev_attr_ffu_status.attr,
&dev_attr_psa_state.attr,
&dev_attr_psa_data_size.attr,
+ &dev_attr_rough_temp.attr,
+ &dev_attr_too_high_temp.attr,
+ &dev_attr_too_low_temp.attr,
NULL,
};
@@ -42,6 +42,7 @@
#include <linux/nls.h>
#include <linux/of.h>
#include <linux/bitfield.h>
+#include <linux/thermal.h>
#include "ufshcd.h"
#include "ufs_quirks.h"
#include "unipro.h"
@@ -4883,6 +4884,174 @@ static int ufshcd_enable_ee(struct ufs_hba *hba, u16 mask)
return err;
}
+#ifdef CONFIG_THERMAL
+
+#define attr2milicelcius(attr) (((0xFF & attr) - 80) * 1000)
+#define dev_thermal_info dev_info
+
+static int ufshcd_thermal_get_temp(struct thermal_zone_device *device,
+ int *temperature)
+{
+ struct ufs_hba *hba = (struct ufs_hba *)device->devdata;
+ u32 temp;
+
+ const int err = ufshcd_query_attr_retry(hba,
+ UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_ROUGH_TEMP,
+ 0, 0, &temp);
+ if (err)
+ return -EINVAL;
+
+ *temperature = attr2milicelcius(temp);
+ return 0;
+}
+
+static int ufshcd_thermal_get_trip_temp(
+ struct thermal_zone_device *device,
+ int trip, int *temp)
+{
+ struct ufs_hba *hba = (struct ufs_hba *)device->devdata;
+
+ if (trip < 0 || trip >= UFS_THERM_MAX_TRIPS)
+ return -EINVAL;
+
+ if (hba->thermal.trip[UFS_THERM_TOO_HIGH_TRIP].index == trip)
+ *temp = hba->thermal.trip[
+ UFS_THERM_TOO_HIGH_TRIP].temp_boundary;
+ else if (hba->thermal.trip[UFS_THERM_TOO_LOW_TRIP].index == trip)
+ *temp = hba->thermal.trip[UFS_THERM_TOO_LOW_TRIP].temp_boundary;
+
+ return 0;
+}
+
+static int ufshcd_thermal_get_trip_type(
+ struct thermal_zone_device *device,
+ int trip, enum thermal_trip_type *type)
+{
+ if (trip < 0 || trip >= UFS_THERM_MAX_TRIPS)
+ return -EINVAL;
+
+ *type = THERMAL_TRIP_PASSIVE;
+
+ return 0;
+}
+
+static void ufshcd_therm_exception_event_handler(struct ufs_hba *hba,
+ u32 exception_status)
+{
+ if (exception_status & MASK_EE_TOO_HIGH_TEMP) {
+ thermal_notify_framework(hba->thermal.zone,
+ hba->thermal.trip[UFS_THERM_TOO_HIGH_TRIP].index);
+ dev_thermal_info(hba->dev,
+ "High temperature raised\n");
+ } else if (exception_status & MASK_EE_TOO_LOW_TEMP) {
+ thermal_notify_framework(hba->thermal.zone,
+ hba->thermal.trip[UFS_THERM_TOO_LOW_TRIP].index);
+ dev_thermal_info(hba->dev,
+ "Low temperature raised\n");
+ }
+}
+
+static struct thermal_zone_device_ops thermal_ops = {
+ .get_temp = ufshcd_thermal_get_temp,
+ .get_trip_temp = ufshcd_thermal_get_trip_temp,
+ .get_trip_type = ufshcd_thermal_get_trip_type,
+};
+
+static bool ufshcd_thermal_get_boundary(struct ufs_hba *hba,
+ int trip, int *boundary)
+{
+ const u32 atrib = ((trip == UFS_THERM_TOO_HIGH_TRIP) ?
+ QUERY_ATTR_IDN_TOO_HIGH_TEMP :
+ QUERY_ATTR_IDN_TOO_LOW_TEMP);
+ const int err = ufshcd_query_attr_retry(hba,
+ UPIU_QUERY_OPCODE_READ_ATTR,
+ atrib, 0, 0, boundary);
+ if (err) {
+ dev_err(hba->dev,
+ "Failed to get device too %s temperature boundary\n",
+ trip == UFS_THERM_TOO_HIGH_TRIP ? "high" : "low");
+ return false;
+ }
+
+ *boundary = attr2milicelcius(*boundary);
+
+ return true;
+}
+
+static int ufshcd_thermal_enable_ee(struct ufs_hba *hba, int trip)
+{
+ const u16 mask = ((trip == UFS_THERM_TOO_HIGH_TRIP) ?
+ MASK_EE_TOO_HIGH_TEMP : MASK_EE_TOO_LOW_TEMP);
+ const int err = ufshcd_enable_ee(hba, mask);
+
+ if (err) {
+ dev_err(hba->dev,
+ "%s: failed to enable theraml too %s exception event %d\n",
+ __func__, UFS_THERM_TOO_HIGH_TRIP == trip ?
+ "high" : "low", err);
+ }
+ return err;
+}
+
+static bool ufshcd_thermal_set_trip(struct ufs_hba *hba, int trip,
+ int *trip_count)
+{
+ int temp;
+
+ if (ufshcd_thermal_get_boundary(hba, trip, &temp)) {
+ hba->thermal.trip[trip].index = (*trip_count)++;
+ hba->thermal.trip[trip].temp_boundary = temp;
+ return (ufshcd_thermal_enable_ee(hba, trip) == 0);
+ }
+ return false;
+}
+
+static void ufshcd_thermal_register(struct ufs_hba *hba, int thermal_features)
+{
+ char name[] = "ufs_storage";
+ int trip_count = 0;
+
+ BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
+
+ if (thermal_features & UFS_FEATURE_TOO_HIGH_TEMPERATURE)
+ ufshcd_thermal_set_trip(hba, UFS_THERM_TOO_HIGH_TRIP,
+ &trip_count);
+
+ if (thermal_features & UFS_FEATURE_TOO_LOW_TEMPERATURE)
+ ufshcd_thermal_set_trip(hba, UFS_THERM_TOO_LOW_TRIP,
+ &trip_count);
+
+ if (trip_count > 0) {
+ hba->thermal.zone = thermal_zone_device_register("ufs_storage",
+ trip_count, 0, hba, &thermal_ops, NULL, 0, 0);
+ if (IS_ERR(hba->thermal.zone)) {
+ dev_err(hba->dev, "Failed to register to thermal zone (err = %ld)\n",
+ PTR_ERR(hba->thermal.zone));
+ hba->thermal.zone = NULL;
+ } else
+ dev_thermal_info(hba->dev, "Succeeded to register to thermal zone");
+ }
+}
+
+static void ufshcd_thermal_zone_unregister(struct ufs_hba *hba)
+{
+ if (hba->thermal.zone) {
+ dev_dbg(hba->dev, "Thermal zone device unregister\n");
+ thermal_zone_device_unregister(hba->thermal.zone);
+ hba->thermal.zone = NULL;
+ }
+}
+#else
+static void ufshcd_thermal_register(struct ufs_hba *hba, int thermal_features)
+{
+}
+
+static void ufshcd_thermal_zone_unregister(struct ufs_hba *hba)
+{
+}
+#endif /* CONFIG_THERMAL */
+
/**
* ufshcd_enable_auto_bkops - Allow device managed BKOPS
* @hba: per-adapter instance
@@ -5121,6 +5290,10 @@ static void ufshcd_exception_event_handler(struct work_struct *work)
if (status & MASK_EE_URGENT_BKOPS)
ufshcd_bkops_exception_event_handler(hba);
+#ifdef CONFIG_THERMAL
+ if (status & (MASK_EE_TOO_HIGH_TEMP | MASK_EE_TOO_LOW_TEMP))
+ ufshcd_therm_exception_event_handler(hba, status);
+#endif /* CONFIG_THERMAL */
out:
scsi_unblock_requests(hba->host);
@@ -6858,6 +7031,9 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)
* context, no need to scan the host
*/
if (!ufshcd_eh_in_progress(hba) && !hba->pm_op_in_progress) {
+ const int thermal_features = (card.ufs_features &
+ (UFS_FEATURE_TOO_HIGH_TEMPERATURE |
+ UFS_FEATURE_TOO_LOW_TEMPERATURE));
bool flag;
/* clear any previous UFS device information */
@@ -6891,6 +7067,9 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)
scsi_scan_host(hba->host);
pm_runtime_put_sync(hba->dev);
+
+ if (ufshcd_thermal_management_enabled(hba) && thermal_features)
+ ufshcd_thermal_register(hba, thermal_features);
}
if (!hba->is_init_prefetch)
@@ -7444,6 +7623,7 @@ static int ufshcd_hba_init(struct ufs_hba *hba)
static void ufshcd_hba_exit(struct ufs_hba *hba)
{
if (hba->is_powered) {
+ ufshcd_thermal_zone_unregister(hba);
ufshcd_variant_hba_exit(hba);
ufshcd_setup_vreg(hba, false);
ufshcd_suspend_clkscaling(hba);
@@ -447,6 +447,29 @@ struct ufs_stats {
struct ufs_uic_err_reg_hist dme_err;
};
+enum {
+ UFS_THERM_TOO_HIGH_TRIP,
+ UFS_THERM_TOO_LOW_TRIP,
+ UFS_THERM_MAX_TRIPS,
+};
+
+#ifdef CONFIG_THERMAL
+/**
+ *struct ufs_thermal - thermal zone related data
+ * @tzone: thermal zone device data
+ * @trip.temp_boundary: temperature thresholds for report
+ * @trip.index: the trip enumeration
+ * @trip: trip array, high and low if supported
+ */
+struct ufs_thermal {
+ struct thermal_zone_device *zone;
+ struct {
+ int index;
+ int temp_boundary;
+ } trip[UFS_THERM_MAX_TRIPS];
+};
+#endif /*CONFIG_THERMAL*/
+
/**
* struct ufs_hba - per adapter private structure
* @mmio_base: UFSHCI base register address
@@ -693,6 +716,8 @@ struct ufs_hba {
*/
#define UFSHCD_CAP_KEEP_AUTO_BKOPS_ENABLED_EXCEPT_SUSPEND (1 << 5)
+#define UFSHCD_CAP_THERMAL_MANAGEMENT (1 << 6)
+
struct devfreq *devfreq;
struct ufs_clk_scaling clk_scaling;
bool is_sys_suspended;
@@ -706,6 +731,10 @@ struct ufs_hba {
struct device bsg_dev;
struct request_queue *bsg_queue;
+#ifdef CONFIG_THERMAL
+ /*Thermal data*/
+ struct ufs_thermal thermal;
+#endif /*CONFIG_THERMAL*/
};
/* Returns true if clocks can be gated. Otherwise false */
@@ -725,6 +754,10 @@ static inline bool ufshcd_can_autobkops_during_suspend(struct ufs_hba *hba)
{
return hba->caps & UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
}
+static inline bool ufshcd_thermal_management_enabled(struct ufs_hba *hba)
+{
+ return hba->caps & UFSHCD_CAP_THERMAL_MANAGEMENT;
+}
static inline bool ufshcd_is_intr_aggr_allowed(struct ufs_hba *hba)
{