| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- // 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 <ufs/ufshcd.h>
- #include "ufshcd-priv.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 *const 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);
- }
|