From: Uri Yanai <uri.yanai@xxxxxxxxxxx> Register to thermal_zone interface and implement the thermal ops. The thermal sets the temperature thresholds the Thermal Manager would be notified on crossing thresholds. The thermal interface adds a new thermal zone device sensor under /sys/class/thermal/ folder. Signed-off-by: Uri Yanai <uri.yanai@xxxxxxxxxxx> --- drivers/scsi/ufs/ufs-sysfs.c | 3 + drivers/scsi/ufs/ufshcd.c | 180 +++++++++++++++++++++++++++++++++++++++++++ drivers/scsi/ufs/ufshcd.h | 33 ++++++++ 3 files changed, 216 insertions(+) diff --git a/drivers/scsi/ufs/ufs-sysfs.c b/drivers/scsi/ufs/ufs-sysfs.c index eda4ddb..859511d 100644 --- a/drivers/scsi/ufs/ufs-sysfs.c +++ b/drivers/scsi/ufs/ufs-sysfs.c @@ -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, }; diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 5004c94..b843ef0 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -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); diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index 69ba744..474d2d9 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -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) { -- 2.7.4