xref: /linux/drivers/hwmon/surface_temp.c (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
1*63be321eSMaximilian Luz // SPDX-License-Identifier: GPL-2.0+
2*63be321eSMaximilian Luz /*
3*63be321eSMaximilian Luz  * Thermal sensor subsystem driver for Surface System Aggregator Module (SSAM).
4*63be321eSMaximilian Luz  *
5*63be321eSMaximilian Luz  * Copyright (C) 2022-2023 Maximilian Luz <luzmaximilian@gmail.com>
6*63be321eSMaximilian Luz  */
7*63be321eSMaximilian Luz 
8*63be321eSMaximilian Luz #include <linux/bitops.h>
9*63be321eSMaximilian Luz #include <linux/hwmon.h>
10*63be321eSMaximilian Luz #include <linux/kernel.h>
11*63be321eSMaximilian Luz #include <linux/module.h>
12*63be321eSMaximilian Luz #include <linux/types.h>
13*63be321eSMaximilian Luz 
14*63be321eSMaximilian Luz #include <linux/surface_aggregator/controller.h>
15*63be321eSMaximilian Luz #include <linux/surface_aggregator/device.h>
16*63be321eSMaximilian Luz 
17*63be321eSMaximilian Luz /* -- SAM interface. -------------------------------------------------------- */
18*63be321eSMaximilian Luz 
19*63be321eSMaximilian Luz /*
20*63be321eSMaximilian Luz  * Available sensors are indicated by a 16-bit bitfield, where a 1 marks the
21*63be321eSMaximilian Luz  * presence of a sensor. So we have at most 16 possible sensors/channels.
22*63be321eSMaximilian Luz  */
23*63be321eSMaximilian Luz #define SSAM_TMP_SENSOR_MAX_COUNT	16
24*63be321eSMaximilian Luz 
25*63be321eSMaximilian Luz /*
26*63be321eSMaximilian Luz  * All names observed so far are 6 characters long, but there's only
27*63be321eSMaximilian Luz  * zeros after the name, so perhaps they can be longer. This number reflects
28*63be321eSMaximilian Luz  * the maximum zero-padded space observed in the returned buffer.
29*63be321eSMaximilian Luz  */
30*63be321eSMaximilian Luz #define SSAM_TMP_SENSOR_NAME_LENGTH	18
31*63be321eSMaximilian Luz 
32*63be321eSMaximilian Luz struct ssam_tmp_get_name_rsp {
33*63be321eSMaximilian Luz 	__le16 unknown1;
34*63be321eSMaximilian Luz 	char unknown2;
35*63be321eSMaximilian Luz 	char name[SSAM_TMP_SENSOR_NAME_LENGTH];
36*63be321eSMaximilian Luz } __packed;
37*63be321eSMaximilian Luz 
38*63be321eSMaximilian Luz static_assert(sizeof(struct ssam_tmp_get_name_rsp) == 21);
39*63be321eSMaximilian Luz 
40*63be321eSMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, {
41*63be321eSMaximilian Luz 	.target_category = SSAM_SSH_TC_TMP,
42*63be321eSMaximilian Luz 	.command_id      = 0x04,
43*63be321eSMaximilian Luz });
44*63be321eSMaximilian Luz 
45*63be321eSMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, {
46*63be321eSMaximilian Luz 	.target_category = SSAM_SSH_TC_TMP,
47*63be321eSMaximilian Luz 	.command_id      = 0x01,
48*63be321eSMaximilian Luz });
49*63be321eSMaximilian Luz 
50*63be321eSMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_name, struct ssam_tmp_get_name_rsp, {
51*63be321eSMaximilian Luz 	.target_category = SSAM_SSH_TC_TMP,
52*63be321eSMaximilian Luz 	.command_id      = 0x0e,
53*63be321eSMaximilian Luz });
54*63be321eSMaximilian Luz 
ssam_tmp_get_available_sensors(struct ssam_device * sdev,s16 * sensors)55*63be321eSMaximilian Luz static int ssam_tmp_get_available_sensors(struct ssam_device *sdev, s16 *sensors)
56*63be321eSMaximilian Luz {
57*63be321eSMaximilian Luz 	__le16 sensors_le;
58*63be321eSMaximilian Luz 	int status;
59*63be321eSMaximilian Luz 
60*63be321eSMaximilian Luz 	status = __ssam_tmp_get_available_sensors(sdev, &sensors_le);
61*63be321eSMaximilian Luz 	if (status)
62*63be321eSMaximilian Luz 		return status;
63*63be321eSMaximilian Luz 
64*63be321eSMaximilian Luz 	*sensors = le16_to_cpu(sensors_le);
65*63be321eSMaximilian Luz 	return 0;
66*63be321eSMaximilian Luz }
67*63be321eSMaximilian Luz 
ssam_tmp_get_temperature(struct ssam_device * sdev,u8 iid,long * temperature)68*63be321eSMaximilian Luz static int ssam_tmp_get_temperature(struct ssam_device *sdev, u8 iid, long *temperature)
69*63be321eSMaximilian Luz {
70*63be321eSMaximilian Luz 	__le16 temp_le;
71*63be321eSMaximilian Luz 	int status;
72*63be321eSMaximilian Luz 
73*63be321eSMaximilian Luz 	status = __ssam_tmp_get_temperature(sdev->ctrl, sdev->uid.target, iid, &temp_le);
74*63be321eSMaximilian Luz 	if (status)
75*63be321eSMaximilian Luz 		return status;
76*63be321eSMaximilian Luz 
77*63be321eSMaximilian Luz 	/* Convert 1/10 °K to 1/1000 °C */
78*63be321eSMaximilian Luz 	*temperature = (le16_to_cpu(temp_le) - 2731) * 100L;
79*63be321eSMaximilian Luz 	return 0;
80*63be321eSMaximilian Luz }
81*63be321eSMaximilian Luz 
ssam_tmp_get_name(struct ssam_device * sdev,u8 iid,char * buf,size_t buf_len)82*63be321eSMaximilian Luz static int ssam_tmp_get_name(struct ssam_device *sdev, u8 iid, char *buf, size_t buf_len)
83*63be321eSMaximilian Luz {
84*63be321eSMaximilian Luz 	struct ssam_tmp_get_name_rsp name_rsp;
85*63be321eSMaximilian Luz 	int status;
86*63be321eSMaximilian Luz 
87*63be321eSMaximilian Luz 	status =  __ssam_tmp_get_name(sdev->ctrl, sdev->uid.target, iid, &name_rsp);
88*63be321eSMaximilian Luz 	if (status)
89*63be321eSMaximilian Luz 		return status;
90*63be321eSMaximilian Luz 
91*63be321eSMaximilian Luz 	/*
92*63be321eSMaximilian Luz 	 * This should not fail unless the name in the returned struct is not
93*63be321eSMaximilian Luz 	 * null-terminated or someone changed something in the struct
94*63be321eSMaximilian Luz 	 * definitions above, since our buffer and struct have the same
95*63be321eSMaximilian Luz 	 * capacity by design. So if this fails, log an error message. Since
96*63be321eSMaximilian Luz 	 * the more likely cause is that the returned string isn't
97*63be321eSMaximilian Luz 	 * null-terminated, we might have received garbage (as opposed to just
98*63be321eSMaximilian Luz 	 * an incomplete string), so also fail the function.
99*63be321eSMaximilian Luz 	 */
100*63be321eSMaximilian Luz 	status = strscpy(buf, name_rsp.name, buf_len);
101*63be321eSMaximilian Luz 	if (status < 0) {
102*63be321eSMaximilian Luz 		dev_err(&sdev->dev, "received non-null-terminated sensor name string\n");
103*63be321eSMaximilian Luz 		return status;
104*63be321eSMaximilian Luz 	}
105*63be321eSMaximilian Luz 
106*63be321eSMaximilian Luz 	return 0;
107*63be321eSMaximilian Luz }
108*63be321eSMaximilian Luz 
109*63be321eSMaximilian Luz /* -- Driver.---------------------------------------------------------------- */
110*63be321eSMaximilian Luz 
111*63be321eSMaximilian Luz struct ssam_temp {
112*63be321eSMaximilian Luz 	struct ssam_device *sdev;
113*63be321eSMaximilian Luz 	s16 sensors;
114*63be321eSMaximilian Luz 	char names[SSAM_TMP_SENSOR_MAX_COUNT][SSAM_TMP_SENSOR_NAME_LENGTH];
115*63be321eSMaximilian Luz };
116*63be321eSMaximilian Luz 
ssam_temp_hwmon_is_visible(const void * data,enum hwmon_sensor_types type,u32 attr,int channel)117*63be321eSMaximilian Luz static umode_t ssam_temp_hwmon_is_visible(const void *data,
118*63be321eSMaximilian Luz 					  enum hwmon_sensor_types type,
119*63be321eSMaximilian Luz 					  u32 attr, int channel)
120*63be321eSMaximilian Luz {
121*63be321eSMaximilian Luz 	const struct ssam_temp *ssam_temp = data;
122*63be321eSMaximilian Luz 
123*63be321eSMaximilian Luz 	if (!(ssam_temp->sensors & BIT(channel)))
124*63be321eSMaximilian Luz 		return 0;
125*63be321eSMaximilian Luz 
126*63be321eSMaximilian Luz 	return 0444;
127*63be321eSMaximilian Luz }
128*63be321eSMaximilian Luz 
ssam_temp_hwmon_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * value)129*63be321eSMaximilian Luz static int ssam_temp_hwmon_read(struct device *dev,
130*63be321eSMaximilian Luz 				enum hwmon_sensor_types type,
131*63be321eSMaximilian Luz 				u32 attr, int channel, long *value)
132*63be321eSMaximilian Luz {
133*63be321eSMaximilian Luz 	const struct ssam_temp *ssam_temp = dev_get_drvdata(dev);
134*63be321eSMaximilian Luz 
135*63be321eSMaximilian Luz 	return ssam_tmp_get_temperature(ssam_temp->sdev, channel + 1, value);
136*63be321eSMaximilian Luz }
137*63be321eSMaximilian Luz 
ssam_temp_hwmon_read_string(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,const char ** str)138*63be321eSMaximilian Luz static int ssam_temp_hwmon_read_string(struct device *dev,
139*63be321eSMaximilian Luz 				       enum hwmon_sensor_types type,
140*63be321eSMaximilian Luz 				       u32 attr, int channel, const char **str)
141*63be321eSMaximilian Luz {
142*63be321eSMaximilian Luz 	const struct ssam_temp *ssam_temp = dev_get_drvdata(dev);
143*63be321eSMaximilian Luz 
144*63be321eSMaximilian Luz 	*str = ssam_temp->names[channel];
145*63be321eSMaximilian Luz 	return 0;
146*63be321eSMaximilian Luz }
147*63be321eSMaximilian Luz 
148*63be321eSMaximilian Luz static const struct hwmon_channel_info * const ssam_temp_hwmon_info[] = {
149*63be321eSMaximilian Luz 	HWMON_CHANNEL_INFO(chip,
150*63be321eSMaximilian Luz 			   HWMON_C_REGISTER_TZ),
151*63be321eSMaximilian Luz 	HWMON_CHANNEL_INFO(temp,
152*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
153*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
154*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
155*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
156*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
157*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
158*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
159*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
160*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
161*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
162*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
163*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
164*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
165*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
166*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL,
167*63be321eSMaximilian Luz 			   HWMON_T_INPUT | HWMON_T_LABEL),
168*63be321eSMaximilian Luz 	NULL
169*63be321eSMaximilian Luz };
170*63be321eSMaximilian Luz 
171*63be321eSMaximilian Luz static const struct hwmon_ops ssam_temp_hwmon_ops = {
172*63be321eSMaximilian Luz 	.is_visible = ssam_temp_hwmon_is_visible,
173*63be321eSMaximilian Luz 	.read = ssam_temp_hwmon_read,
174*63be321eSMaximilian Luz 	.read_string = ssam_temp_hwmon_read_string,
175*63be321eSMaximilian Luz };
176*63be321eSMaximilian Luz 
177*63be321eSMaximilian Luz static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = {
178*63be321eSMaximilian Luz 	.ops = &ssam_temp_hwmon_ops,
179*63be321eSMaximilian Luz 	.info = ssam_temp_hwmon_info,
180*63be321eSMaximilian Luz };
181*63be321eSMaximilian Luz 
ssam_temp_probe(struct ssam_device * sdev)182*63be321eSMaximilian Luz static int ssam_temp_probe(struct ssam_device *sdev)
183*63be321eSMaximilian Luz {
184*63be321eSMaximilian Luz 	struct ssam_temp *ssam_temp;
185*63be321eSMaximilian Luz 	struct device *hwmon_dev;
186*63be321eSMaximilian Luz 	s16 sensors;
187*63be321eSMaximilian Luz 	int channel;
188*63be321eSMaximilian Luz 	int status;
189*63be321eSMaximilian Luz 
190*63be321eSMaximilian Luz 	status = ssam_tmp_get_available_sensors(sdev, &sensors);
191*63be321eSMaximilian Luz 	if (status)
192*63be321eSMaximilian Luz 		return status;
193*63be321eSMaximilian Luz 
194*63be321eSMaximilian Luz 	ssam_temp = devm_kzalloc(&sdev->dev, sizeof(*ssam_temp), GFP_KERNEL);
195*63be321eSMaximilian Luz 	if (!ssam_temp)
196*63be321eSMaximilian Luz 		return -ENOMEM;
197*63be321eSMaximilian Luz 
198*63be321eSMaximilian Luz 	ssam_temp->sdev = sdev;
199*63be321eSMaximilian Luz 	ssam_temp->sensors = sensors;
200*63be321eSMaximilian Luz 
201*63be321eSMaximilian Luz 	/* Retrieve the name for each available sensor. */
202*63be321eSMaximilian Luz 	for (channel = 0; channel < SSAM_TMP_SENSOR_MAX_COUNT; channel++) {
203*63be321eSMaximilian Luz 		if (!(sensors & BIT(channel)))
204*63be321eSMaximilian Luz 			continue;
205*63be321eSMaximilian Luz 
206*63be321eSMaximilian Luz 		status = ssam_tmp_get_name(sdev, channel + 1, ssam_temp->names[channel],
207*63be321eSMaximilian Luz 					   SSAM_TMP_SENSOR_NAME_LENGTH);
208*63be321eSMaximilian Luz 		if (status)
209*63be321eSMaximilian Luz 			return status;
210*63be321eSMaximilian Luz 	}
211*63be321eSMaximilian Luz 
212*63be321eSMaximilian Luz 	hwmon_dev = devm_hwmon_device_register_with_info(&sdev->dev, "surface_thermal", ssam_temp,
213*63be321eSMaximilian Luz 							 &ssam_temp_hwmon_chip_info, NULL);
214*63be321eSMaximilian Luz 	return PTR_ERR_OR_ZERO(hwmon_dev);
215*63be321eSMaximilian Luz }
216*63be321eSMaximilian Luz 
217*63be321eSMaximilian Luz static const struct ssam_device_id ssam_temp_match[] = {
218*63be321eSMaximilian Luz 	{ SSAM_SDEV(TMP, SAM, 0x00, 0x02) },
219*63be321eSMaximilian Luz 	{ },
220*63be321eSMaximilian Luz };
221*63be321eSMaximilian Luz MODULE_DEVICE_TABLE(ssam, ssam_temp_match);
222*63be321eSMaximilian Luz 
223*63be321eSMaximilian Luz static struct ssam_device_driver ssam_temp = {
224*63be321eSMaximilian Luz 	.probe = ssam_temp_probe,
225*63be321eSMaximilian Luz 	.match_table = ssam_temp_match,
226*63be321eSMaximilian Luz 	.driver = {
227*63be321eSMaximilian Luz 		.name = "surface_temp",
228*63be321eSMaximilian Luz 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
229*63be321eSMaximilian Luz 	},
230*63be321eSMaximilian Luz };
231*63be321eSMaximilian Luz module_ssam_device_driver(ssam_temp);
232*63be321eSMaximilian Luz 
233*63be321eSMaximilian Luz MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
234*63be321eSMaximilian Luz MODULE_DESCRIPTION("Thermal sensor subsystem driver for Surface System Aggregator Module");
235*63be321eSMaximilian Luz MODULE_LICENSE("GPL");
236