Skip to content

Commit 9c6bd71

Browse files
author
Jack Davis
committed
liquidtux: teensyloopctl: add Teensy Fan Controller HID module
1 parent b713c7a commit 9c6bd71

File tree

2 files changed

+366
-1
lines changed

2 files changed

+366
-1
lines changed

Kbuild

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
obj-m := krx62.o grdp3.o
1+
obj-m := krx62.o grdp3.o teensyloopctrl.o

teensyloopctrl.c

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
// SPDX-License-Identifier: GPL-2.0+
2+
/*
3+
* teensyloopctrl.c - hwmon driver for the Teensy Fan Controller
4+
*
5+
* Originally:
6+
* liquidctl.c - hwmon driver for the NZXT Smart Device and Grid+ V3
7+
*
8+
* Copyright 2019 Jonas Malaco <jonas@protocubo.io>
9+
*/
10+
11+
#include <linux/hid.h>
12+
#include <linux/hwmon.h>
13+
#include <linux/kernel.h>
14+
#include <linux/module.h>
15+
16+
#define DRVNAME "teensyloopctrl"
17+
#define MAX_REPORT_SIZE 65
18+
19+
struct tloopctrl_device_data {
20+
struct hid_device *hid_dev;
21+
struct device *hwmon_dev;
22+
23+
int temp_count;
24+
int fan_count;
25+
int pwm_count;
26+
const char *const *temp_label;
27+
const char *const *fan_label;
28+
// const char *const *pwm_label;
29+
u64 *temp_input;
30+
long *fan_input;
31+
u64 *pwm_input;
32+
u8 *buf;
33+
};
34+
35+
static umode_t tloopctrl_is_visible(const void *data,
36+
enum hwmon_sensor_types type,
37+
u32 attr, int channel) {
38+
struct tloopctrl_device_data *ldata;
39+
struct hid_device *hdev;
40+
41+
ldata = (struct tloopctrl_device_data *)data;
42+
hdev = ldata->hid_dev;
43+
44+
if (hdev->dev_rsize < 10 || hdev->dev_rdesc[1] != 0xAB || hdev->dev_rdesc[2] != 0xFF || hdev->dev_rdesc[4] != 0x00 || hdev->dev_rdesc[5] != 0x02) {
45+
// printk(KERN_WARNING "tloopctrl_is_visible() return: 0");
46+
return 0;
47+
}
48+
else {
49+
return S_IRUGO;
50+
}
51+
}
52+
53+
static int tloopctrl_read(struct device *dev, enum hwmon_sensor_types type,
54+
u32 attr, int channel, long *val) {
55+
struct tloopctrl_device_data *ldata = dev_get_drvdata(dev);
56+
57+
switch (type) {
58+
case hwmon_temp:
59+
if (attr != hwmon_temp_input || channel >= ldata->temp_count)
60+
return -EINVAL;
61+
*val = (int)ldata->temp_input[channel];
62+
break;
63+
case hwmon_fan:
64+
if (attr != hwmon_fan_input || channel >= ldata->fan_count)
65+
return -EINVAL;
66+
*val = ldata->fan_input[channel];
67+
break;
68+
case hwmon_pwm:
69+
if (attr != hwmon_pwm_input || channel >= ldata->pwm_count)
70+
return -EINVAL;
71+
*val = (int)ldata->pwm_input[channel];
72+
break;
73+
default:
74+
return -EINVAL;
75+
}
76+
return 0;
77+
}
78+
79+
static int tloopctrl_read_string(struct device *dev,
80+
enum hwmon_sensor_types type, u32 attr,
81+
int channel, const char **str) {
82+
struct tloopctrl_device_data *ldata = dev_get_drvdata(dev);
83+
84+
switch (type) {
85+
case hwmon_temp:
86+
if (attr != hwmon_temp_label || channel >= ldata->temp_count ||
87+
!ldata->temp_label[channel])
88+
return -EINVAL;
89+
*str = ldata->temp_label[channel];
90+
break;
91+
case hwmon_fan:
92+
if (attr != hwmon_fan_label || channel >= ldata->fan_count ||
93+
!ldata->fan_label[channel])
94+
return -EINVAL;
95+
*str = ldata->fan_label[channel];
96+
break;
97+
// case hwmon_pwm:
98+
// if (attr != hwmon_pwm_label || channel >= ldata->pwm_count ||
99+
// !ldata->pwm_label[channel])
100+
// return -EINVAL;
101+
// *str = ldata->pwm_label[channel];
102+
// break;
103+
default:
104+
return -EINVAL;
105+
}
106+
return 0;
107+
}
108+
109+
static const struct hwmon_ops tloopctrl_hwmon_ops = {
110+
.is_visible = tloopctrl_is_visible,
111+
.read = tloopctrl_read,
112+
.read_string = tloopctrl_read_string,
113+
};
114+
115+
#define DEVNAME_TLOOPCTRL_DEVICE "teensyloopctrl"
116+
#define TLOOPCTRL_DEVICE_TEMP_COUNT 7
117+
#define TLOOPCTRL_DEVICE_FAN_COUNT 6
118+
#define TLOOPCTRL_DEVICE_PWM_COUNT 1
119+
120+
static const u32 tloopctrl_device_fan_config[] = {
121+
HWMON_F_INPUT,
122+
HWMON_F_INPUT,
123+
HWMON_F_INPUT,
124+
HWMON_F_INPUT,
125+
HWMON_F_INPUT,
126+
HWMON_F_INPUT,
127+
0
128+
};
129+
130+
static const struct hwmon_channel_info tloopctrl_device_fan = {
131+
.type = hwmon_fan,
132+
.config = tloopctrl_device_fan_config,
133+
};
134+
135+
static const u32 tloopctrl_device_temp_config[] = {
136+
HWMON_T_INPUT,
137+
HWMON_T_INPUT,
138+
HWMON_T_INPUT,
139+
HWMON_T_INPUT,
140+
HWMON_T_INPUT,
141+
HWMON_T_INPUT,
142+
HWMON_T_INPUT,
143+
0
144+
};
145+
146+
static const struct hwmon_channel_info tloopctrl_device_temp = {
147+
.type = hwmon_temp,
148+
.config = tloopctrl_device_temp_config,
149+
};
150+
151+
static const u32 tloopctrl_device_pwm_config[] = {
152+
HWMON_PWM_INPUT,
153+
0
154+
};
155+
156+
static const struct hwmon_channel_info tloopctrl_device_pwm = {
157+
.type = hwmon_pwm,
158+
.config = tloopctrl_device_pwm_config,
159+
};
160+
161+
static const struct hwmon_channel_info *tloopctrl_device_info[] = {
162+
&tloopctrl_device_fan,
163+
&tloopctrl_device_temp,
164+
&tloopctrl_device_pwm,
165+
NULL
166+
};
167+
168+
static const struct hwmon_chip_info tloopctrl_device_chip_info = {
169+
.ops = &tloopctrl_hwmon_ops,
170+
.info = tloopctrl_device_info,
171+
};
172+
173+
#define USB_VENDOR_ID_ARDUINO 0x16C0
174+
#define USB_DEVICE_ID_TLOOPCTRL_DEVICE 0x0486
175+
176+
// TODO
177+
#define STATUS_REPORT_ID 4
178+
#define STATUS_MIN_BYTES 16
179+
180+
#define show_ctx() \
181+
printk(KERN_DEBUG "%s:%d: irq: %lu, serving_softirq: %lu, nmi: %lu, task: %u\n", \
182+
__FUNCTION__, __LINE__, in_irq(), in_serving_softirq(), in_nmi(), in_task());
183+
184+
static int tloopctrl_raw_event(struct hid_device *hdev,
185+
struct hid_report *report, u8 *data, int size) {
186+
struct tloopctrl_device_data *ldata;
187+
188+
/* TODO show_ctx(): in hard irq, how much should we do here? */
189+
190+
if (size <= 0 || report->maxfield != 1 || report->field[0]->application != 0xFFAB0200)
191+
return 0;
192+
193+
ldata = hid_get_drvdata(hdev);
194+
195+
/* TODO reads don't need the latest data, but each store must be atomic */
196+
switch (hdev->product) {
197+
case USB_DEVICE_ID_TLOOPCTRL_DEVICE:
198+
if (data[0] == 0xDA) {
199+
ldata->temp_input[0] = le64_to_cpup((__le64 *) (data + 2)); // supplyTemp
200+
ldata->temp_input[1] = le64_to_cpup((__le64 *) (data + 6)); // returnTemp
201+
ldata->temp_input[2] = le64_to_cpup((__le64 *) (data + 10)); // caseTemp
202+
ldata->temp_input[3] = le64_to_cpup((__le64 *) (data + 14)); // aux1Temp
203+
ldata->temp_input[4] = le64_to_cpup((__le64 *) (data + 18)); // aux2Temp
204+
ldata->temp_input[5] = le64_to_cpup((__le64 *) (data + 22)); // deltaT
205+
// ldata->temp_input[5] = le64_to_cpup((__le64 *) (data + 26)); //
206+
ldata->temp_input[6] = le64_to_cpup((__le64 *) (data + 30)); // setpoint
207+
208+
ldata->fan_input[0] = le16_to_cpup((__le16 *) (data + 34)); // rpm1
209+
ldata->fan_input[1] = le16_to_cpup((__le16 *) (data + 36)); // rpm2
210+
ldata->fan_input[2] = le16_to_cpup((__le16 *) (data + 38)); // rpm3
211+
ldata->fan_input[3] = le16_to_cpup((__le16 *) (data + 40)); // rpm4
212+
ldata->fan_input[4] = le16_to_cpup((__le16 *) (data + 42)); // rpm5
213+
ldata->fan_input[5] = le16_to_cpup((__le16 *) (data + 44)); // rpm6
214+
215+
ldata->pwm_input[0] = 0;
216+
// ldata->pwm_input[0] = le64_to_cpup((__le64 *) (data + 26)); // fan % (PID)
217+
// ldata->pwm_input[0] = le64_to_cpup((__le64 *) (data + 26)); // fan % (%-tbl)
218+
}
219+
break;
220+
default:
221+
return 0;
222+
}
223+
return 0;
224+
}
225+
226+
static int tloopctrl_initialize(struct tloopctrl_device_data *ldata) {
227+
// TODO
228+
int ret;
229+
memset(ldata->buf, '\0', MAX_REPORT_SIZE);
230+
*(ldata->buf) = 0; // report id ?
231+
*(ldata->buf + 1) = 0; //0xC0;
232+
*(ldata->buf + 2) = 0; //0xFF;
233+
ret = hid_hw_output_report(ldata->hid_dev, ldata->buf, MAX_REPORT_SIZE);
234+
//printk(KERN_WARNING "tloopctrl_initialize() ret: %d", ret);
235+
if (ret < 0)
236+
return ret;
237+
if (ret != MAX_REPORT_SIZE)
238+
return -EINVAL;
239+
240+
return 0;
241+
}
242+
243+
static const struct hid_device_id tloopctrl_table[] = {
244+
{HID_USB_DEVICE(USB_VENDOR_ID_ARDUINO, USB_DEVICE_ID_TLOOPCTRL_DEVICE)},
245+
{}
246+
};
247+
248+
MODULE_DEVICE_TABLE(hid, tloopctrl_table
249+
);
250+
251+
static int tloopctrl_probe(struct hid_device *hdev,
252+
const struct hid_device_id *id) {
253+
struct tloopctrl_device_data *ldata;
254+
struct device *hwmon_dev;
255+
const struct hwmon_chip_info *chip_info;
256+
char *chip_name;
257+
int ret;
258+
259+
ldata = devm_kzalloc(&hdev->dev, sizeof(*ldata), GFP_KERNEL);
260+
if (!ldata)
261+
return -ENOMEM;
262+
263+
switch (hdev->product) {
264+
case USB_DEVICE_ID_TLOOPCTRL_DEVICE:
265+
chip_name = DEVNAME_TLOOPCTRL_DEVICE;
266+
ldata->temp_count = TLOOPCTRL_DEVICE_TEMP_COUNT;
267+
ldata->fan_count = TLOOPCTRL_DEVICE_FAN_COUNT;
268+
ldata->pwm_count = TLOOPCTRL_DEVICE_PWM_COUNT;
269+
ldata->temp_label = NULL;
270+
ldata->fan_label = NULL;
271+
// ldata->pwm_label = NULL;
272+
chip_info = &tloopctrl_device_chip_info;
273+
break;
274+
default:
275+
return -EINVAL;
276+
}
277+
hid_info(hdev, "device: %s\n", chip_name);
278+
279+
ldata->buf = devm_kmalloc(&hdev->dev, MAX_REPORT_SIZE, GFP_KERNEL);
280+
if (!ldata->buf)
281+
return -ENOMEM;
282+
283+
ldata->temp_input = devm_kcalloc(&hdev->dev, ldata->temp_count,
284+
sizeof(*ldata->temp_input), GFP_KERNEL);
285+
if (!ldata->temp_input)
286+
return -ENOMEM;
287+
288+
ldata->fan_input = devm_kcalloc(&hdev->dev, ldata->fan_count,
289+
sizeof(*ldata->fan_input), GFP_KERNEL);
290+
if (!ldata->fan_input)
291+
return -ENOMEM;
292+
293+
ldata->pwm_input = devm_kcalloc(&hdev->dev, ldata->pwm_count,
294+
sizeof(*ldata->pwm_input), GFP_KERNEL);
295+
if (!ldata->pwm_input)
296+
return -ENOMEM;
297+
298+
ldata->hid_dev = hdev;
299+
hid_set_drvdata(hdev, ldata);
300+
301+
ret = hid_parse(hdev);
302+
if (ret) {
303+
hid_err(hdev, "hid_parse failed with %d\n", ret);
304+
return ret;
305+
}
306+
307+
/* keep hidraw so user-space can (easily) take care of the other
308+
* features of the device (e.g. LEDs) */
309+
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
310+
if (ret) {
311+
hid_err(hdev, "hid_hw_start failed with %d\n", ret);
312+
goto rec_stop_hid;
313+
}
314+
315+
ret = hid_hw_open(hdev);
316+
if (ret) {
317+
hid_err(hdev, "hid_hw_open failed with %d\n", ret);
318+
goto rec_close_hid;
319+
}
320+
321+
hwmon_dev = devm_hwmon_device_register_with_info(&hdev->dev, chip_name,
322+
ldata, chip_info,
323+
NULL);
324+
if (IS_ERR(hwmon_dev)) {
325+
hid_err(hdev, "failed to register hwmon device\n");
326+
ret = PTR_ERR(hwmon_dev);
327+
goto rec_close_hid;
328+
}
329+
ldata->hwmon_dev = hwmon_dev;
330+
331+
ret = tloopctrl_initialize(ldata);
332+
if (ret) {
333+
hid_err(hdev, "failed to initialize device");
334+
goto rec_close_hid;
335+
}
336+
337+
hid_info(hdev, "probing successful\n");
338+
return 0;
339+
340+
rec_close_hid:
341+
hid_hw_close(hdev);
342+
rec_stop_hid:
343+
hid_hw_stop(hdev);
344+
return ret;
345+
}
346+
347+
static void tloopctrl_remove(struct hid_device *hdev) {
348+
hid_hw_close(hdev);
349+
hid_hw_stop(hdev);
350+
}
351+
352+
static struct hid_driver tloopctrl_driver = {
353+
.name = DRVNAME,
354+
.id_table = tloopctrl_table,
355+
.probe = tloopctrl_probe,
356+
.remove = tloopctrl_remove,
357+
.raw_event = tloopctrl_raw_event,
358+
};
359+
360+
module_hid_driver(tloopctrl_driver);
361+
362+
MODULE_LICENSE("GPL");
363+
MODULE_AUTHOR("Jonas Malaco <jonas@protocubo.io>");
364+
MODULE_AUTHOR("Jack Davis <c@jtd.me>");
365+
MODULE_DESCRIPTION("Hwmon driver for Teensy Fan Controller");

0 commit comments

Comments
 (0)