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