1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Thermal sensor subsystem driver for Surface System Aggregator Module (SSAM). 4 * 5 * Copyright (C) 2022-2023 Maximilian Luz <luzmaximilian@gmail.com> 6 */ 7 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> 13 14 #include <linux/surface_aggregator/controller.h> 15 #include <linux/surface_aggregator/device.h> 16 17 /* -- SAM interface. -------------------------------------------------------- */ 18 19 /* 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. 22 */ 23 #define SSAM_TMP_SENSOR_MAX_COUNT 16 24 25 /* 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. 29 */ 30 #define SSAM_TMP_SENSOR_NAME_LENGTH 18 31 32 struct ssam_tmp_get_name_rsp { 33 __le16 unknown1; 34 char unknown2; 35 char name[SSAM_TMP_SENSOR_NAME_LENGTH]; 36 } __packed; 37 38 static_assert(sizeof(struct ssam_tmp_get_name_rsp) == 21); 39 40 SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, { 41 .target_category = SSAM_SSH_TC_TMP, 42 .command_id = 0x04, 43 }); 44 45 SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, { 46 .target_category = SSAM_SSH_TC_TMP, 47 .command_id = 0x01, 48 }); 49 50 SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_name, struct ssam_tmp_get_name_rsp, { 51 .target_category = SSAM_SSH_TC_TMP, 52 .command_id = 0x0e, 53 }); 54 55 static int ssam_tmp_get_available_sensors(struct ssam_device *sdev, s16 *sensors) 56 { 57 __le16 sensors_le; 58 int status; 59 60 status = __ssam_tmp_get_available_sensors(sdev, &sensors_le); 61 if (status) 62 return status; 63 64 *sensors = le16_to_cpu(sensors_le); 65 return 0; 66 } 67 68 static int ssam_tmp_get_temperature(struct ssam_device *sdev, u8 iid, long *temperature) 69 { 70 __le16 temp_le; 71 int status; 72 73 status = __ssam_tmp_get_temperature(sdev->ctrl, sdev->uid.target, iid, &temp_le); 74 if (status) 75 return status; 76 77 /* Convert 1/10 °K to 1/1000 °C */ 78 *temperature = (le16_to_cpu(temp_le) - 2731) * 100L; 79 return 0; 80 } 81 82 static int ssam_tmp_get_name(struct ssam_device *sdev, u8 iid, char *buf, size_t buf_len) 83 { 84 struct ssam_tmp_get_name_rsp name_rsp; 85 int status; 86 87 status = __ssam_tmp_get_name(sdev->ctrl, sdev->uid.target, iid, &name_rsp); 88 if (status) 89 return status; 90 91 /* 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. 99 */ 100 status = strscpy(buf, name_rsp.name, buf_len); 101 if (status < 0) { 102 dev_err(&sdev->dev, "received non-null-terminated sensor name string\n"); 103 return status; 104 } 105 106 return 0; 107 } 108 109 /* -- Driver.---------------------------------------------------------------- */ 110 111 struct ssam_temp { 112 struct ssam_device *sdev; 113 s16 sensors; 114 char names[SSAM_TMP_SENSOR_MAX_COUNT][SSAM_TMP_SENSOR_NAME_LENGTH]; 115 }; 116 117 static umode_t ssam_temp_hwmon_is_visible(const void *data, 118 enum hwmon_sensor_types type, 119 u32 attr, int channel) 120 { 121 const struct ssam_temp *ssam_temp = data; 122 123 if (!(ssam_temp->sensors & BIT(channel))) 124 return 0; 125 126 return 0444; 127 } 128 129 static int ssam_temp_hwmon_read(struct device *dev, 130 enum hwmon_sensor_types type, 131 u32 attr, int channel, long *value) 132 { 133 const struct ssam_temp *ssam_temp = dev_get_drvdata(dev); 134 135 return ssam_tmp_get_temperature(ssam_temp->sdev, channel + 1, value); 136 } 137 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) 141 { 142 const struct ssam_temp *ssam_temp = dev_get_drvdata(dev); 143 144 *str = ssam_temp->names[channel]; 145 return 0; 146 } 147 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), 168 NULL 169 }; 170 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, 175 }; 176 177 static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = { 178 .ops = &ssam_temp_hwmon_ops, 179 .info = ssam_temp_hwmon_info, 180 }; 181 182 static int ssam_temp_probe(struct ssam_device *sdev) 183 { 184 struct ssam_temp *ssam_temp; 185 struct device *hwmon_dev; 186 s16 sensors; 187 int channel; 188 int status; 189 190 status = ssam_tmp_get_available_sensors(sdev, &sensors); 191 if (status) 192 return status; 193 194 ssam_temp = devm_kzalloc(&sdev->dev, sizeof(*ssam_temp), GFP_KERNEL); 195 if (!ssam_temp) 196 return -ENOMEM; 197 198 ssam_temp->sdev = sdev; 199 ssam_temp->sensors = sensors; 200 201 /* Retrieve the name for each available sensor. */ 202 for (channel = 0; channel < SSAM_TMP_SENSOR_MAX_COUNT; channel++) { 203 if (!(sensors & BIT(channel))) 204 continue; 205 206 status = ssam_tmp_get_name(sdev, channel + 1, ssam_temp->names[channel], 207 SSAM_TMP_SENSOR_NAME_LENGTH); 208 if (status) 209 return status; 210 } 211 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); 215 } 216 217 static const struct ssam_device_id ssam_temp_match[] = { 218 { SSAM_SDEV(TMP, SAM, 0x00, 0x02) }, 219 { }, 220 }; 221 MODULE_DEVICE_TABLE(ssam, ssam_temp_match); 222 223 static struct ssam_device_driver ssam_temp = { 224 .probe = ssam_temp_probe, 225 .match_table = ssam_temp_match, 226 .driver = { 227 .name = "surface_temp", 228 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 229 }, 230 }; 231 module_ssam_device_driver(ssam_temp); 232 233 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); 234 MODULE_DESCRIPTION("Thermal sensor subsystem driver for Surface System Aggregator Module"); 235 MODULE_LICENSE("GPL"); 236