Commit | Line | Data |
---|---|---|
90905f7c NH |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */ | |
3 | ||
4 | #include <linux/bits.h> | |
5 | #include <linux/err.h> | |
6 | #include <linux/hwmon.h> | |
7 | #include <linux/io.h> | |
8 | #include <linux/module.h> | |
39f03438 | 9 | #include <linux/mod_devicetable.h> |
90905f7c NH |
10 | #include <linux/platform_device.h> |
11 | ||
12 | #define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */ | |
13 | #define OFS_FAN_FAIL 2 /* Is 2 bytes after base */ | |
14 | #define OFS_SEVSTAT 0 /* Is 0 because fn2 base will be set at SEVSTAT */ | |
15 | #define POWER_BIT 24 | |
16 | ||
17 | struct gxp_fan_ctrl_drvdata { | |
18 | void __iomem *base; | |
19 | void __iomem *plreg; | |
20 | void __iomem *fn2; | |
21 | }; | |
22 | ||
23 | static bool fan_installed(struct device *dev, int fan) | |
24 | { | |
25 | struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); | |
26 | u8 val; | |
27 | ||
28 | val = readb(drvdata->plreg + OFS_FAN_INST); | |
29 | ||
30 | return !!(val & BIT(fan)); | |
31 | } | |
32 | ||
33 | static long fan_failed(struct device *dev, int fan) | |
34 | { | |
35 | struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); | |
36 | u8 val; | |
37 | ||
38 | val = readb(drvdata->plreg + OFS_FAN_FAIL); | |
39 | ||
40 | return !!(val & BIT(fan)); | |
41 | } | |
42 | ||
43 | static long fan_enabled(struct device *dev, int fan) | |
44 | { | |
45 | struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); | |
46 | u32 val; | |
47 | ||
48 | /* | |
49 | * Check the power status as if the platform is off the value | |
50 | * reported for the PWM will be incorrect. Report fan as | |
51 | * disabled. | |
52 | */ | |
53 | val = readl(drvdata->fn2 + OFS_SEVSTAT); | |
54 | ||
55 | return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan)); | |
56 | } | |
57 | ||
58 | static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val) | |
59 | { | |
60 | struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); | |
61 | ||
62 | switch (attr) { | |
63 | case hwmon_pwm_input: | |
64 | if (val > 255 || val < 0) | |
65 | return -EINVAL; | |
66 | writeb(val, drvdata->base + channel); | |
67 | return 0; | |
68 | default: | |
69 | return -EOPNOTSUPP; | |
70 | } | |
71 | } | |
72 | ||
73 | static int gxp_fan_ctrl_write(struct device *dev, enum hwmon_sensor_types type, | |
74 | u32 attr, int channel, long val) | |
75 | { | |
76 | switch (type) { | |
77 | case hwmon_pwm: | |
78 | return gxp_pwm_write(dev, attr, channel, val); | |
79 | default: | |
80 | return -EOPNOTSUPP; | |
81 | } | |
82 | } | |
83 | ||
84 | static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val) | |
85 | { | |
86 | switch (attr) { | |
87 | case hwmon_fan_enable: | |
88 | *val = fan_enabled(dev, channel); | |
89 | return 0; | |
90 | case hwmon_fan_fault: | |
91 | *val = fan_failed(dev, channel); | |
92 | return 0; | |
93 | default: | |
94 | return -EOPNOTSUPP; | |
95 | } | |
96 | } | |
97 | ||
98 | static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val) | |
99 | { | |
100 | struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); | |
101 | u32 reg; | |
102 | ||
103 | /* | |
104 | * Check the power status of the platform. If the platform is off | |
105 | * the value reported for the PWM will be incorrect. In this case | |
106 | * report a PWM of zero. | |
107 | */ | |
108 | ||
109 | reg = readl(drvdata->fn2 + OFS_SEVSTAT); | |
110 | ||
111 | if (reg & BIT(POWER_BIT)) | |
112 | *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0; | |
113 | else | |
114 | *val = 0; | |
115 | ||
116 | return 0; | |
117 | } | |
118 | ||
119 | static int gxp_fan_ctrl_read(struct device *dev, enum hwmon_sensor_types type, | |
120 | u32 attr, int channel, long *val) | |
121 | { | |
122 | switch (type) { | |
123 | case hwmon_fan: | |
124 | return gxp_fan_read(dev, attr, channel, val); | |
125 | case hwmon_pwm: | |
126 | return gxp_pwm_read(dev, attr, channel, val); | |
127 | default: | |
128 | return -EOPNOTSUPP; | |
129 | } | |
130 | } | |
131 | ||
132 | static umode_t gxp_fan_ctrl_is_visible(const void *_data, | |
133 | enum hwmon_sensor_types type, | |
134 | u32 attr, int channel) | |
135 | { | |
136 | umode_t mode = 0; | |
137 | ||
138 | switch (type) { | |
139 | case hwmon_fan: | |
140 | switch (attr) { | |
141 | case hwmon_fan_enable: | |
142 | case hwmon_fan_fault: | |
143 | mode = 0444; | |
144 | break; | |
145 | default: | |
146 | break; | |
147 | } | |
148 | break; | |
149 | case hwmon_pwm: | |
150 | switch (attr) { | |
151 | case hwmon_pwm_input: | |
152 | mode = 0644; | |
153 | break; | |
154 | default: | |
155 | break; | |
156 | } | |
157 | break; | |
158 | default: | |
159 | break; | |
160 | } | |
161 | ||
162 | return mode; | |
163 | } | |
164 | ||
165 | static const struct hwmon_ops gxp_fan_ctrl_ops = { | |
166 | .is_visible = gxp_fan_ctrl_is_visible, | |
167 | .read = gxp_fan_ctrl_read, | |
168 | .write = gxp_fan_ctrl_write, | |
169 | }; | |
170 | ||
d6171e4d | 171 | static const struct hwmon_channel_info * const gxp_fan_ctrl_info[] = { |
90905f7c NH |
172 | HWMON_CHANNEL_INFO(fan, |
173 | HWMON_F_FAULT | HWMON_F_ENABLE, | |
174 | HWMON_F_FAULT | HWMON_F_ENABLE, | |
175 | HWMON_F_FAULT | HWMON_F_ENABLE, | |
176 | HWMON_F_FAULT | HWMON_F_ENABLE, | |
177 | HWMON_F_FAULT | HWMON_F_ENABLE, | |
178 | HWMON_F_FAULT | HWMON_F_ENABLE, | |
179 | HWMON_F_FAULT | HWMON_F_ENABLE, | |
180 | HWMON_F_FAULT | HWMON_F_ENABLE), | |
181 | HWMON_CHANNEL_INFO(pwm, | |
182 | HWMON_PWM_INPUT, | |
183 | HWMON_PWM_INPUT, | |
184 | HWMON_PWM_INPUT, | |
185 | HWMON_PWM_INPUT, | |
186 | HWMON_PWM_INPUT, | |
187 | HWMON_PWM_INPUT, | |
188 | HWMON_PWM_INPUT, | |
189 | HWMON_PWM_INPUT), | |
190 | NULL | |
191 | }; | |
192 | ||
193 | static const struct hwmon_chip_info gxp_fan_ctrl_chip_info = { | |
194 | .ops = &gxp_fan_ctrl_ops, | |
195 | .info = gxp_fan_ctrl_info, | |
196 | ||
197 | }; | |
198 | ||
199 | static int gxp_fan_ctrl_probe(struct platform_device *pdev) | |
200 | { | |
201 | struct gxp_fan_ctrl_drvdata *drvdata; | |
90905f7c NH |
202 | struct device *dev = &pdev->dev; |
203 | struct device *hwmon_dev; | |
204 | ||
205 | drvdata = devm_kzalloc(dev, sizeof(struct gxp_fan_ctrl_drvdata), | |
206 | GFP_KERNEL); | |
207 | if (!drvdata) | |
208 | return -ENOMEM; | |
209 | ||
102be2c2 | 210 | drvdata->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); |
90905f7c NH |
211 | if (IS_ERR(drvdata->base)) |
212 | return dev_err_probe(dev, PTR_ERR(drvdata->base), | |
213 | "failed to map base\n"); | |
214 | ||
215 | drvdata->plreg = devm_platform_ioremap_resource_byname(pdev, | |
216 | "pl"); | |
217 | if (IS_ERR(drvdata->plreg)) | |
218 | return dev_err_probe(dev, PTR_ERR(drvdata->plreg), | |
219 | "failed to map plreg\n"); | |
220 | ||
221 | drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev, | |
222 | "fn2"); | |
223 | if (IS_ERR(drvdata->fn2)) | |
224 | return dev_err_probe(dev, PTR_ERR(drvdata->fn2), | |
225 | "failed to map fn2\n"); | |
226 | ||
227 | hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, | |
228 | "hpe_gxp_fan_ctrl", | |
229 | drvdata, | |
230 | &gxp_fan_ctrl_chip_info, | |
231 | NULL); | |
232 | ||
233 | return PTR_ERR_OR_ZERO(hwmon_dev); | |
234 | } | |
235 | ||
236 | static const struct of_device_id gxp_fan_ctrl_of_match[] = { | |
237 | { .compatible = "hpe,gxp-fan-ctrl", }, | |
238 | {}, | |
239 | }; | |
240 | MODULE_DEVICE_TABLE(of, gxp_fan_ctrl_of_match); | |
241 | ||
242 | static struct platform_driver gxp_fan_ctrl_driver = { | |
243 | .probe = gxp_fan_ctrl_probe, | |
244 | .driver = { | |
245 | .name = "gxp-fan-ctrl", | |
246 | .of_match_table = gxp_fan_ctrl_of_match, | |
247 | }, | |
248 | }; | |
249 | module_platform_driver(gxp_fan_ctrl_driver); | |
250 | ||
251 | MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>"); | |
252 | MODULE_DESCRIPTION("HPE GXP fan controller"); | |
253 | MODULE_LICENSE("GPL"); |