]>
Commit | Line | Data |
---|---|---|
3f697027 MW |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * sl28cpld hardware monitoring driver | |
4 | * | |
5 | * Copyright 2020 Kontron Europe GmbH | |
6 | */ | |
7 | ||
8 | #include <linux/bitfield.h> | |
9 | #include <linux/hwmon.h> | |
10 | #include <linux/kernel.h> | |
11 | #include <linux/mod_devicetable.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/platform_device.h> | |
14 | #include <linux/property.h> | |
15 | #include <linux/regmap.h> | |
16 | ||
17 | #define FAN_INPUT 0x00 | |
18 | #define FAN_SCALE_X8 BIT(7) | |
19 | #define FAN_VALUE_MASK GENMASK(6, 0) | |
20 | ||
21 | struct sl28cpld_hwmon { | |
22 | struct regmap *regmap; | |
23 | u32 offset; | |
24 | }; | |
25 | ||
26 | static umode_t sl28cpld_hwmon_is_visible(const void *data, | |
27 | enum hwmon_sensor_types type, | |
28 | u32 attr, int channel) | |
29 | { | |
30 | return 0444; | |
31 | } | |
32 | ||
33 | static int sl28cpld_hwmon_read(struct device *dev, | |
34 | enum hwmon_sensor_types type, u32 attr, | |
35 | int channel, long *input) | |
36 | { | |
37 | struct sl28cpld_hwmon *hwmon = dev_get_drvdata(dev); | |
38 | unsigned int value; | |
39 | int ret; | |
40 | ||
41 | switch (attr) { | |
42 | case hwmon_fan_input: | |
43 | ret = regmap_read(hwmon->regmap, hwmon->offset + FAN_INPUT, | |
44 | &value); | |
45 | if (ret) | |
46 | return ret; | |
47 | /* | |
48 | * The register has a 7 bit value and 1 bit which indicates the | |
49 | * scale. If the MSB is set, then the lower 7 bit has to be | |
50 | * multiplied by 8, to get the correct reading. | |
51 | */ | |
52 | if (value & FAN_SCALE_X8) | |
53 | value = FIELD_GET(FAN_VALUE_MASK, value) << 3; | |
54 | ||
55 | /* | |
56 | * The counter period is 1000ms and the sysfs specification | |
57 | * says we should asssume 2 pulses per revolution. | |
58 | */ | |
59 | value *= 60 / 2; | |
60 | ||
61 | break; | |
62 | default: | |
63 | return -EOPNOTSUPP; | |
64 | } | |
65 | ||
66 | *input = value; | |
67 | return 0; | |
68 | } | |
69 | ||
70 | static const u32 sl28cpld_hwmon_fan_config[] = { | |
71 | HWMON_F_INPUT, | |
72 | 0 | |
73 | }; | |
74 | ||
75 | static const struct hwmon_channel_info sl28cpld_hwmon_fan = { | |
76 | .type = hwmon_fan, | |
77 | .config = sl28cpld_hwmon_fan_config, | |
78 | }; | |
79 | ||
80 | static const struct hwmon_channel_info *sl28cpld_hwmon_info[] = { | |
81 | &sl28cpld_hwmon_fan, | |
82 | NULL | |
83 | }; | |
84 | ||
85 | static const struct hwmon_ops sl28cpld_hwmon_ops = { | |
86 | .is_visible = sl28cpld_hwmon_is_visible, | |
87 | .read = sl28cpld_hwmon_read, | |
88 | }; | |
89 | ||
90 | static const struct hwmon_chip_info sl28cpld_hwmon_chip_info = { | |
91 | .ops = &sl28cpld_hwmon_ops, | |
92 | .info = sl28cpld_hwmon_info, | |
93 | }; | |
94 | ||
95 | static int sl28cpld_hwmon_probe(struct platform_device *pdev) | |
96 | { | |
97 | struct sl28cpld_hwmon *hwmon; | |
98 | struct device *hwmon_dev; | |
99 | int ret; | |
100 | ||
101 | if (!pdev->dev.parent) | |
102 | return -ENODEV; | |
103 | ||
104 | hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL); | |
105 | if (!hwmon) | |
106 | return -ENOMEM; | |
107 | ||
108 | hwmon->regmap = dev_get_regmap(pdev->dev.parent, NULL); | |
109 | if (!hwmon->regmap) | |
110 | return -ENODEV; | |
111 | ||
112 | ret = device_property_read_u32(&pdev->dev, "reg", &hwmon->offset); | |
113 | if (ret) | |
114 | return -EINVAL; | |
115 | ||
116 | hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, | |
117 | "sl28cpld_hwmon", hwmon, | |
118 | &sl28cpld_hwmon_chip_info, NULL); | |
119 | if (IS_ERR(hwmon_dev)) | |
120 | dev_err(&pdev->dev, "failed to register as hwmon device"); | |
121 | ||
122 | return PTR_ERR_OR_ZERO(hwmon_dev); | |
123 | } | |
124 | ||
125 | static const struct of_device_id sl28cpld_hwmon_of_match[] = { | |
126 | { .compatible = "kontron,sl28cpld-fan" }, | |
127 | {} | |
128 | }; | |
129 | MODULE_DEVICE_TABLE(of, sl28cpld_hwmon_of_match); | |
130 | ||
131 | static struct platform_driver sl28cpld_hwmon_driver = { | |
132 | .probe = sl28cpld_hwmon_probe, | |
133 | .driver = { | |
134 | .name = "sl28cpld-fan", | |
135 | .of_match_table = sl28cpld_hwmon_of_match, | |
136 | }, | |
137 | }; | |
138 | module_platform_driver(sl28cpld_hwmon_driver); | |
139 | ||
140 | MODULE_DESCRIPTION("sl28cpld Hardware Monitoring Driver"); | |
141 | MODULE_AUTHOR("Michael Walle <[email protected]>"); | |
142 | MODULE_LICENSE("GPL"); |