1// SPDX-License-Identifier: GPL-2.0
2/*
3 * UFS hardware monitoring support
4 * Copyright (c) 2021, Western Digital Corporation
5 */
6
7#include <linux/hwmon.h>
8#include <linux/units.h>
9
10#include <ufs/ufshcd.h>
11#include "ufshcd-priv.h"
12
13struct ufs_hwmon_data {
14	struct ufs_hba *hba;
15	u8 mask;
16};
17
18static int ufs_read_temp_enable(struct ufs_hba *hba, u8 mask, long *val)
19{
20	u32 ee_mask;
21	int err;
22
23	err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, QUERY_ATTR_IDN_EE_CONTROL, 0, 0,
24				&ee_mask);
25	if (err)
26		return err;
27
28	*val = (mask & ee_mask & MASK_EE_TOO_HIGH_TEMP) || (mask & ee_mask & MASK_EE_TOO_LOW_TEMP);
29
30	return 0;
31}
32
33static int ufs_get_temp(struct ufs_hba *hba, enum attr_idn idn, long *val)
34{
35	u32 value;
36	int err;
37
38	err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, 0, 0, &value);
39	if (err)
40		return err;
41
42	if (value == 0)
43		return -ENODATA;
44
45	*val = ((long)value - 80) * MILLIDEGREE_PER_DEGREE;
46
47	return 0;
48}
49
50static int ufs_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
51			  long *val)
52{
53	struct ufs_hwmon_data *data = dev_get_drvdata(dev);
54	struct ufs_hba *hba = data->hba;
55	int err;
56
57	down(&hba->host_sem);
58
59	if (!ufshcd_is_user_access_allowed(hba)) {
60		up(&hba->host_sem);
61		return -EBUSY;
62	}
63
64	ufshcd_rpm_get_sync(hba);
65
66	switch (attr) {
67	case hwmon_temp_enable:
68		err = ufs_read_temp_enable(hba, data->mask, val);
69
70		break;
71	case hwmon_temp_crit:
72		err = ufs_get_temp(hba, QUERY_ATTR_IDN_HIGH_TEMP_BOUND, val);
73
74		break;
75	case hwmon_temp_lcrit:
76		err = ufs_get_temp(hba, QUERY_ATTR_IDN_LOW_TEMP_BOUND, val);
77
78		break;
79	case hwmon_temp_input:
80		err = ufs_get_temp(hba, QUERY_ATTR_IDN_CASE_ROUGH_TEMP, val);
81
82		break;
83	default:
84		err = -EOPNOTSUPP;
85
86		break;
87	}
88
89	ufshcd_rpm_put_sync(hba);
90
91	up(&hba->host_sem);
92
93	return err;
94}
95
96static int ufs_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
97			   long val)
98{
99	struct ufs_hwmon_data *data = dev_get_drvdata(dev);
100	struct ufs_hba *hba = data->hba;
101	int err;
102
103	if (attr != hwmon_temp_enable)
104		return -EINVAL;
105
106	if (val != 0 && val != 1)
107		return -EINVAL;
108
109	down(&hba->host_sem);
110
111	if (!ufshcd_is_user_access_allowed(hba)) {
112		up(&hba->host_sem);
113		return -EBUSY;
114	}
115
116	ufshcd_rpm_get_sync(hba);
117
118	if (val == 1)
119		err = ufshcd_update_ee_usr_mask(hba, MASK_EE_URGENT_TEMP, 0);
120	else
121		err = ufshcd_update_ee_usr_mask(hba, 0, MASK_EE_URGENT_TEMP);
122
123	ufshcd_rpm_put_sync(hba);
124
125	up(&hba->host_sem);
126
127	return err;
128}
129
130static umode_t ufs_hwmon_is_visible(const void *data,
131				    enum hwmon_sensor_types type, u32 attr,
132				    int channel)
133{
134	if (type != hwmon_temp)
135		return 0;
136
137	switch (attr) {
138	case hwmon_temp_enable:
139		return 0644;
140	case hwmon_temp_crit:
141	case hwmon_temp_lcrit:
142	case hwmon_temp_input:
143		return 0444;
144	default:
145		break;
146	}
147	return 0;
148}
149
150static const struct hwmon_channel_info *const ufs_hwmon_info[] = {
151	HWMON_CHANNEL_INFO(temp, HWMON_T_ENABLE | HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LCRIT),
152	NULL
153};
154
155static const struct hwmon_ops ufs_hwmon_ops = {
156	.is_visible	= ufs_hwmon_is_visible,
157	.read		= ufs_hwmon_read,
158	.write		= ufs_hwmon_write,
159};
160
161static const struct hwmon_chip_info ufs_hwmon_hba_info = {
162	.ops	= &ufs_hwmon_ops,
163	.info	= ufs_hwmon_info,
164};
165
166void ufs_hwmon_probe(struct ufs_hba *hba, u8 mask)
167{
168	struct device *dev = hba->dev;
169	struct ufs_hwmon_data *data;
170	struct device *hwmon;
171
172	data = kzalloc(sizeof(*data), GFP_KERNEL);
173	if (!data)
174		return;
175
176	data->hba = hba;
177	data->mask = mask;
178
179	hwmon = hwmon_device_register_with_info(dev, "ufs", data, &ufs_hwmon_hba_info, NULL);
180	if (IS_ERR(hwmon)) {
181		dev_warn(dev, "Failed to instantiate hwmon device\n");
182		kfree(data);
183		return;
184	}
185
186	hba->hwmon_device = hwmon;
187}
188
189void ufs_hwmon_remove(struct ufs_hba *hba)
190{
191	struct ufs_hwmon_data *data;
192
193	if (!hba->hwmon_device)
194		return;
195
196	data = dev_get_drvdata(hba->hwmon_device);
197	hwmon_device_unregister(hba->hwmon_device);
198	hba->hwmon_device = NULL;
199	kfree(data);
200}
201
202void ufs_hwmon_notify_event(struct ufs_hba *hba, u8 ee_mask)
203{
204	if (!hba->hwmon_device)
205		return;
206
207	if (ee_mask & MASK_EE_TOO_HIGH_TEMP)
208		hwmon_notify_event(hba->hwmon_device, hwmon_temp, hwmon_temp_max_alarm, 0);
209
210	if (ee_mask & MASK_EE_TOO_LOW_TEMP)
211		hwmon_notify_event(hba->hwmon_device, hwmon_temp, hwmon_temp_min_alarm, 0);
212}
213