|
| 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"); |
0 commit comments