1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Surface Fan driver for Surface System Aggregator Module. It provides access |
4 | * to the fan's rpm through the hwmon system. |
5 | * |
6 | * Copyright (C) 2023 Ivor Wanders <ivor@iwanders.net> |
7 | */ |
8 | |
9 | #include <linux/hwmon.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/surface_aggregator/device.h> |
13 | #include <linux/types.h> |
14 | |
15 | // SSAM |
16 | SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_fan_rpm_get, __le16, { |
17 | .target_category = SSAM_SSH_TC_FAN, |
18 | .command_id = 0x01, |
19 | }); |
20 | |
21 | // hwmon |
22 | static umode_t surface_fan_hwmon_is_visible(const void *drvdata, |
23 | enum hwmon_sensor_types type, u32 attr, |
24 | int channel) |
25 | { |
26 | return 0444; |
27 | } |
28 | |
29 | static int surface_fan_hwmon_read(struct device *dev, |
30 | enum hwmon_sensor_types type, u32 attr, |
31 | int channel, long *val) |
32 | { |
33 | struct ssam_device *sdev = dev_get_drvdata(dev); |
34 | int ret; |
35 | __le16 value; |
36 | |
37 | ret = __ssam_fan_rpm_get(sdev, ret: &value); |
38 | if (ret) |
39 | return ret; |
40 | |
41 | *val = le16_to_cpu(value); |
42 | |
43 | return 0; |
44 | } |
45 | |
46 | static const struct hwmon_channel_info *const surface_fan_info[] = { |
47 | HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), |
48 | NULL |
49 | }; |
50 | |
51 | static const struct hwmon_ops surface_fan_hwmon_ops = { |
52 | .is_visible = surface_fan_hwmon_is_visible, |
53 | .read = surface_fan_hwmon_read, |
54 | }; |
55 | |
56 | static const struct hwmon_chip_info surface_fan_chip_info = { |
57 | .ops = &surface_fan_hwmon_ops, |
58 | .info = surface_fan_info, |
59 | }; |
60 | |
61 | static int surface_fan_probe(struct ssam_device *sdev) |
62 | { |
63 | struct device *hdev; |
64 | |
65 | hdev = devm_hwmon_device_register_with_info(dev: &sdev->dev, |
66 | name: "surface_fan" , drvdata: sdev, |
67 | info: &surface_fan_chip_info, |
68 | NULL); |
69 | |
70 | return PTR_ERR_OR_ZERO(ptr: hdev); |
71 | } |
72 | |
73 | static const struct ssam_device_id ssam_fan_match[] = { |
74 | { SSAM_SDEV(FAN, SAM, 0x01, 0x01) }, |
75 | {}, |
76 | }; |
77 | MODULE_DEVICE_TABLE(ssam, ssam_fan_match); |
78 | |
79 | static struct ssam_device_driver surface_fan = { |
80 | .probe = surface_fan_probe, |
81 | .match_table = ssam_fan_match, |
82 | .driver = { |
83 | .name = "surface_fan" , |
84 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
85 | }, |
86 | }; |
87 | module_ssam_device_driver(surface_fan); |
88 | |
89 | MODULE_AUTHOR("Ivor Wanders <ivor@iwanders.net>" ); |
90 | MODULE_DESCRIPTION("Fan Driver for Surface System Aggregator Module" ); |
91 | MODULE_LICENSE("GPL" ); |
92 | |