// SPDX-License-Identifier: GPL-2.0 /* * UFS hardware monitoring support * Copyright (c) 2021, Western Digital Corporation */ #include #include #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); } void ufs_hwmon_notify_event(struct ufs_hba *hba, u8 ee_mask) { if (!hba->hwmon_device) return; if (ee_mask & MASK_EE_TOO_HIGH_TEMP) hwmon_notify_event(hba->hwmon_device, hwmon_temp, hwmon_temp_max_alarm, 0); if (ee_mask & MASK_EE_TOO_LOW_TEMP) hwmon_notify_event(hba->hwmon_device, hwmon_temp, hwmon_temp_min_alarm, 0); }