162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * UFS hardware monitoring support 462306a36Sopenharmony_ci * Copyright (c) 2021, Western Digital Corporation 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/hwmon.h> 862306a36Sopenharmony_ci#include <linux/units.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <ufs/ufshcd.h> 1162306a36Sopenharmony_ci#include "ufshcd-priv.h" 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistruct ufs_hwmon_data { 1462306a36Sopenharmony_ci struct ufs_hba *hba; 1562306a36Sopenharmony_ci u8 mask; 1662306a36Sopenharmony_ci}; 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistatic int ufs_read_temp_enable(struct ufs_hba *hba, u8 mask, long *val) 1962306a36Sopenharmony_ci{ 2062306a36Sopenharmony_ci u32 ee_mask; 2162306a36Sopenharmony_ci int err; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, QUERY_ATTR_IDN_EE_CONTROL, 0, 0, 2462306a36Sopenharmony_ci &ee_mask); 2562306a36Sopenharmony_ci if (err) 2662306a36Sopenharmony_ci return err; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci *val = (mask & ee_mask & MASK_EE_TOO_HIGH_TEMP) || (mask & ee_mask & MASK_EE_TOO_LOW_TEMP); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci return 0; 3162306a36Sopenharmony_ci} 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic int ufs_get_temp(struct ufs_hba *hba, enum attr_idn idn, long *val) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci u32 value; 3662306a36Sopenharmony_ci int err; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, 0, 0, &value); 3962306a36Sopenharmony_ci if (err) 4062306a36Sopenharmony_ci return err; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci if (value == 0) 4362306a36Sopenharmony_ci return -ENODATA; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci *val = ((long)value - 80) * MILLIDEGREE_PER_DEGREE; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci return 0; 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic int ufs_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, 5162306a36Sopenharmony_ci long *val) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci struct ufs_hwmon_data *data = dev_get_drvdata(dev); 5462306a36Sopenharmony_ci struct ufs_hba *hba = data->hba; 5562306a36Sopenharmony_ci int err; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci down(&hba->host_sem); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci if (!ufshcd_is_user_access_allowed(hba)) { 6062306a36Sopenharmony_ci up(&hba->host_sem); 6162306a36Sopenharmony_ci return -EBUSY; 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci ufshcd_rpm_get_sync(hba); 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci switch (attr) { 6762306a36Sopenharmony_ci case hwmon_temp_enable: 6862306a36Sopenharmony_ci err = ufs_read_temp_enable(hba, data->mask, val); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci break; 7162306a36Sopenharmony_ci case hwmon_temp_crit: 7262306a36Sopenharmony_ci err = ufs_get_temp(hba, QUERY_ATTR_IDN_HIGH_TEMP_BOUND, val); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci break; 7562306a36Sopenharmony_ci case hwmon_temp_lcrit: 7662306a36Sopenharmony_ci err = ufs_get_temp(hba, QUERY_ATTR_IDN_LOW_TEMP_BOUND, val); 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci break; 7962306a36Sopenharmony_ci case hwmon_temp_input: 8062306a36Sopenharmony_ci err = ufs_get_temp(hba, QUERY_ATTR_IDN_CASE_ROUGH_TEMP, val); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci break; 8362306a36Sopenharmony_ci default: 8462306a36Sopenharmony_ci err = -EOPNOTSUPP; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci break; 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci ufshcd_rpm_put_sync(hba); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci up(&hba->host_sem); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci return err; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic int ufs_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, 9762306a36Sopenharmony_ci long val) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci struct ufs_hwmon_data *data = dev_get_drvdata(dev); 10062306a36Sopenharmony_ci struct ufs_hba *hba = data->hba; 10162306a36Sopenharmony_ci int err; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci if (attr != hwmon_temp_enable) 10462306a36Sopenharmony_ci return -EINVAL; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci if (val != 0 && val != 1) 10762306a36Sopenharmony_ci return -EINVAL; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci down(&hba->host_sem); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci if (!ufshcd_is_user_access_allowed(hba)) { 11262306a36Sopenharmony_ci up(&hba->host_sem); 11362306a36Sopenharmony_ci return -EBUSY; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci ufshcd_rpm_get_sync(hba); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci if (val == 1) 11962306a36Sopenharmony_ci err = ufshcd_update_ee_usr_mask(hba, MASK_EE_URGENT_TEMP, 0); 12062306a36Sopenharmony_ci else 12162306a36Sopenharmony_ci err = ufshcd_update_ee_usr_mask(hba, 0, MASK_EE_URGENT_TEMP); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci ufshcd_rpm_put_sync(hba); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci up(&hba->host_sem); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci return err; 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic umode_t ufs_hwmon_is_visible(const void *data, 13162306a36Sopenharmony_ci enum hwmon_sensor_types type, u32 attr, 13262306a36Sopenharmony_ci int channel) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci if (type != hwmon_temp) 13562306a36Sopenharmony_ci return 0; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci switch (attr) { 13862306a36Sopenharmony_ci case hwmon_temp_enable: 13962306a36Sopenharmony_ci return 0644; 14062306a36Sopenharmony_ci case hwmon_temp_crit: 14162306a36Sopenharmony_ci case hwmon_temp_lcrit: 14262306a36Sopenharmony_ci case hwmon_temp_input: 14362306a36Sopenharmony_ci return 0444; 14462306a36Sopenharmony_ci default: 14562306a36Sopenharmony_ci break; 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci return 0; 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic const struct hwmon_channel_info *const ufs_hwmon_info[] = { 15162306a36Sopenharmony_ci HWMON_CHANNEL_INFO(temp, HWMON_T_ENABLE | HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LCRIT), 15262306a36Sopenharmony_ci NULL 15362306a36Sopenharmony_ci}; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cistatic const struct hwmon_ops ufs_hwmon_ops = { 15662306a36Sopenharmony_ci .is_visible = ufs_hwmon_is_visible, 15762306a36Sopenharmony_ci .read = ufs_hwmon_read, 15862306a36Sopenharmony_ci .write = ufs_hwmon_write, 15962306a36Sopenharmony_ci}; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic const struct hwmon_chip_info ufs_hwmon_hba_info = { 16262306a36Sopenharmony_ci .ops = &ufs_hwmon_ops, 16362306a36Sopenharmony_ci .info = ufs_hwmon_info, 16462306a36Sopenharmony_ci}; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_civoid ufs_hwmon_probe(struct ufs_hba *hba, u8 mask) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct device *dev = hba->dev; 16962306a36Sopenharmony_ci struct ufs_hwmon_data *data; 17062306a36Sopenharmony_ci struct device *hwmon; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci data = kzalloc(sizeof(*data), GFP_KERNEL); 17362306a36Sopenharmony_ci if (!data) 17462306a36Sopenharmony_ci return; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci data->hba = hba; 17762306a36Sopenharmony_ci data->mask = mask; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci hwmon = hwmon_device_register_with_info(dev, "ufs", data, &ufs_hwmon_hba_info, NULL); 18062306a36Sopenharmony_ci if (IS_ERR(hwmon)) { 18162306a36Sopenharmony_ci dev_warn(dev, "Failed to instantiate hwmon device\n"); 18262306a36Sopenharmony_ci kfree(data); 18362306a36Sopenharmony_ci return; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci hba->hwmon_device = hwmon; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_civoid ufs_hwmon_remove(struct ufs_hba *hba) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci struct ufs_hwmon_data *data; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci if (!hba->hwmon_device) 19462306a36Sopenharmony_ci return; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci data = dev_get_drvdata(hba->hwmon_device); 19762306a36Sopenharmony_ci hwmon_device_unregister(hba->hwmon_device); 19862306a36Sopenharmony_ci hba->hwmon_device = NULL; 19962306a36Sopenharmony_ci kfree(data); 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_civoid ufs_hwmon_notify_event(struct ufs_hba *hba, u8 ee_mask) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci if (!hba->hwmon_device) 20562306a36Sopenharmony_ci return; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (ee_mask & MASK_EE_TOO_HIGH_TEMP) 20862306a36Sopenharmony_ci hwmon_notify_event(hba->hwmon_device, hwmon_temp, hwmon_temp_max_alarm, 0); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci if (ee_mask & MASK_EE_TOO_LOW_TEMP) 21162306a36Sopenharmony_ci hwmon_notify_event(hba->hwmon_device, hwmon_temp, hwmon_temp_min_alarm, 0); 21262306a36Sopenharmony_ci} 213