212 lines
4.2 KiB
C
212 lines
4.2 KiB
C
|
// 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 *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);
|
||
|
}
|