1 // SPDX-License-Identifier: GPL-2.0+
3 * Thermal sensor subsystem driver for Surface System Aggregator Module (SSAM).
5 * Copyright (C) 2022-2023 Maximilian Luz <luzmaximilian@gmail.com>
8 #include <linux/bitops.h>
9 #include <linux/hwmon.h>
10 #include <linux/kernel.h>
11 #include <linux/module.h>
12 #include <linux/types.h>
14 #include <linux/surface_aggregator/controller.h>
15 #include <linux/surface_aggregator/device.h>
17 /* -- SAM interface. -------------------------------------------------------- */
20 * Available sensors are indicated by a 16-bit bitfield, where a 1 marks the
21 * presence of a sensor. So we have at most 16 possible sensors/channels.
23 #define SSAM_TMP_SENSOR_MAX_COUNT 16
26 * All names observed so far are 6 characters long, but there's only
27 * zeros after the name, so perhaps they can be longer. This number reflects
28 * the maximum zero-padded space observed in the returned buffer.
30 #define SSAM_TMP_SENSOR_NAME_LENGTH 18
32 struct ssam_tmp_get_name_rsp
{
35 char name
[SSAM_TMP_SENSOR_NAME_LENGTH
];
38 static_assert(sizeof(struct ssam_tmp_get_name_rsp
) == 21);
40 SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors
, __le16
, {
41 .target_category
= SSAM_SSH_TC_TMP
,
45 SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature
, __le16
, {
46 .target_category
= SSAM_SSH_TC_TMP
,
50 SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_name
, struct ssam_tmp_get_name_rsp
, {
51 .target_category
= SSAM_SSH_TC_TMP
,
55 static int ssam_tmp_get_available_sensors(struct ssam_device
*sdev
, s16
*sensors
)
60 status
= __ssam_tmp_get_available_sensors(sdev
, &sensors_le
);
64 *sensors
= le16_to_cpu(sensors_le
);
68 static int ssam_tmp_get_temperature(struct ssam_device
*sdev
, u8 iid
, long *temperature
)
73 status
= __ssam_tmp_get_temperature(sdev
->ctrl
, sdev
->uid
.target
, iid
, &temp_le
);
77 /* Convert 1/10 °K to 1/1000 °C */
78 *temperature
= (le16_to_cpu(temp_le
) - 2731) * 100L;
82 static int ssam_tmp_get_name(struct ssam_device
*sdev
, u8 iid
, char *buf
, size_t buf_len
)
84 struct ssam_tmp_get_name_rsp name_rsp
;
87 status
= __ssam_tmp_get_name(sdev
->ctrl
, sdev
->uid
.target
, iid
, &name_rsp
);
92 * This should not fail unless the name in the returned struct is not
93 * null-terminated or someone changed something in the struct
94 * definitions above, since our buffer and struct have the same
95 * capacity by design. So if this fails, log an error message. Since
96 * the more likely cause is that the returned string isn't
97 * null-terminated, we might have received garbage (as opposed to just
98 * an incomplete string), so also fail the function.
100 status
= strscpy(buf
, name_rsp
.name
, buf_len
);
102 dev_err(&sdev
->dev
, "received non-null-terminated sensor name string\n");
109 /* -- Driver.---------------------------------------------------------------- */
112 struct ssam_device
*sdev
;
114 char names
[SSAM_TMP_SENSOR_MAX_COUNT
][SSAM_TMP_SENSOR_NAME_LENGTH
];
117 static umode_t
ssam_temp_hwmon_is_visible(const void *data
,
118 enum hwmon_sensor_types type
,
119 u32 attr
, int channel
)
121 const struct ssam_temp
*ssam_temp
= data
;
123 if (!(ssam_temp
->sensors
& BIT(channel
)))
129 static int ssam_temp_hwmon_read(struct device
*dev
,
130 enum hwmon_sensor_types type
,
131 u32 attr
, int channel
, long *value
)
133 const struct ssam_temp
*ssam_temp
= dev_get_drvdata(dev
);
135 return ssam_tmp_get_temperature(ssam_temp
->sdev
, channel
+ 1, value
);
138 static int ssam_temp_hwmon_read_string(struct device
*dev
,
139 enum hwmon_sensor_types type
,
140 u32 attr
, int channel
, const char **str
)
142 const struct ssam_temp
*ssam_temp
= dev_get_drvdata(dev
);
144 *str
= ssam_temp
->names
[channel
];
148 static const struct hwmon_channel_info
* const ssam_temp_hwmon_info
[] = {
149 HWMON_CHANNEL_INFO(chip
,
150 HWMON_C_REGISTER_TZ
),
151 HWMON_CHANNEL_INFO(temp
,
152 HWMON_T_INPUT
| HWMON_T_LABEL
,
153 HWMON_T_INPUT
| HWMON_T_LABEL
,
154 HWMON_T_INPUT
| HWMON_T_LABEL
,
155 HWMON_T_INPUT
| HWMON_T_LABEL
,
156 HWMON_T_INPUT
| HWMON_T_LABEL
,
157 HWMON_T_INPUT
| HWMON_T_LABEL
,
158 HWMON_T_INPUT
| HWMON_T_LABEL
,
159 HWMON_T_INPUT
| HWMON_T_LABEL
,
160 HWMON_T_INPUT
| HWMON_T_LABEL
,
161 HWMON_T_INPUT
| HWMON_T_LABEL
,
162 HWMON_T_INPUT
| HWMON_T_LABEL
,
163 HWMON_T_INPUT
| HWMON_T_LABEL
,
164 HWMON_T_INPUT
| HWMON_T_LABEL
,
165 HWMON_T_INPUT
| HWMON_T_LABEL
,
166 HWMON_T_INPUT
| HWMON_T_LABEL
,
167 HWMON_T_INPUT
| HWMON_T_LABEL
),
171 static const struct hwmon_ops ssam_temp_hwmon_ops
= {
172 .is_visible
= ssam_temp_hwmon_is_visible
,
173 .read
= ssam_temp_hwmon_read
,
174 .read_string
= ssam_temp_hwmon_read_string
,
177 static const struct hwmon_chip_info ssam_temp_hwmon_chip_info
= {
178 .ops
= &ssam_temp_hwmon_ops
,
179 .info
= ssam_temp_hwmon_info
,
182 static int ssam_temp_probe(struct ssam_device
*sdev
)
184 struct ssam_temp
*ssam_temp
;
185 struct device
*hwmon_dev
;
190 status
= ssam_tmp_get_available_sensors(sdev
, &sensors
);
194 ssam_temp
= devm_kzalloc(&sdev
->dev
, sizeof(*ssam_temp
), GFP_KERNEL
);
198 ssam_temp
->sdev
= sdev
;
199 ssam_temp
->sensors
= sensors
;
201 /* Retrieve the name for each available sensor. */
202 for (channel
= 0; channel
< SSAM_TMP_SENSOR_MAX_COUNT
; channel
++) {
203 if (!(sensors
& BIT(channel
)))
206 status
= ssam_tmp_get_name(sdev
, channel
+ 1, ssam_temp
->names
[channel
],
207 SSAM_TMP_SENSOR_NAME_LENGTH
);
212 hwmon_dev
= devm_hwmon_device_register_with_info(&sdev
->dev
, "surface_thermal", ssam_temp
,
213 &ssam_temp_hwmon_chip_info
, NULL
);
214 return PTR_ERR_OR_ZERO(hwmon_dev
);
217 static const struct ssam_device_id ssam_temp_match
[] = {
218 { SSAM_SDEV(TMP
, SAM
, 0x00, 0x02) },
221 MODULE_DEVICE_TABLE(ssam
, ssam_temp_match
);
223 static struct ssam_device_driver ssam_temp
= {
224 .probe
= ssam_temp_probe
,
225 .match_table
= ssam_temp_match
,
227 .name
= "surface_temp",
228 .probe_type
= PROBE_PREFER_ASYNCHRONOUS
,
231 module_ssam_device_driver(ssam_temp
);
233 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
234 MODULE_DESCRIPTION("Thermal sensor subsystem driver for Surface System Aggregator Module");
235 MODULE_LICENSE("GPL");