236 lines · c
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 the21 * presence of a sensor. So we have at most 16 possible sensors/channels.22 */23#define SSAM_TMP_SENSOR_MAX_COUNT 1624 25/*26 * All names observed so far are 6 characters long, but there's only27 * zeros after the name, so perhaps they can be longer. This number reflects28 * the maximum zero-padded space observed in the returned buffer.29 */30#define SSAM_TMP_SENSOR_NAME_LENGTH 1831 32struct ssam_tmp_get_name_rsp {33 __le16 unknown1;34 char unknown2;35 char name[SSAM_TMP_SENSOR_NAME_LENGTH];36} __packed;37 38static_assert(sizeof(struct ssam_tmp_get_name_rsp) == 21);39 40SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, {41 .target_category = SSAM_SSH_TC_TMP,42 .command_id = 0x04,43});44 45SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, {46 .target_category = SSAM_SSH_TC_TMP,47 .command_id = 0x01,48});49 50SSAM_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 55static 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 68static 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 82static 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 not93 * null-terminated or someone changed something in the struct94 * definitions above, since our buffer and struct have the same95 * capacity by design. So if this fails, log an error message. Since96 * the more likely cause is that the returned string isn't97 * null-terminated, we might have received garbage (as opposed to just98 * 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 111struct 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 117static 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 129static 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 138static 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 148static 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 NULL169};170 171static 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 177static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = {178 .ops = &ssam_temp_hwmon_ops,179 .info = ssam_temp_hwmon_info,180};181 182static 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 217static const struct ssam_device_id ssam_temp_match[] = {218 { SSAM_SDEV(TMP, SAM, 0x00, 0x02) },219 { },220};221MODULE_DEVICE_TABLE(ssam, ssam_temp_match);222 223static 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};231module_ssam_device_driver(ssam_temp);232 233MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");234MODULE_DESCRIPTION("Thermal sensor subsystem driver for Surface System Aggregator Module");235MODULE_LICENSE("GPL");236