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> |
9 | #include <linux/mod_devicetable.h> |
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(addr: 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(addr: 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(addr: 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, addr: 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, fan: channel); |
89 | return 0; |
90 | case hwmon_fan_fault: |
91 | *val = fan_failed(dev, fan: 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(addr: drvdata->fn2 + OFS_SEVSTAT); |
110 | |
111 | if (reg & BIT(POWER_BIT)) |
112 | *val = fan_installed(dev, fan: channel) ? readb(addr: 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 | |
171 | static const struct hwmon_channel_info * const gxp_fan_ctrl_info[] = { |
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; |
202 | struct device *dev = &pdev->dev; |
203 | struct device *hwmon_dev; |
204 | |
205 | drvdata = devm_kzalloc(dev, size: sizeof(struct gxp_fan_ctrl_drvdata), |
206 | GFP_KERNEL); |
207 | if (!drvdata) |
208 | return -ENOMEM; |
209 | |
210 | drvdata->base = devm_platform_get_and_ioremap_resource(pdev, index: 0, NULL); |
211 | if (IS_ERR(ptr: drvdata->base)) |
212 | return dev_err_probe(dev, err: PTR_ERR(ptr: drvdata->base), |
213 | fmt: "failed to map base\n" ); |
214 | |
215 | drvdata->plreg = devm_platform_ioremap_resource_byname(pdev, |
216 | name: "pl" ); |
217 | if (IS_ERR(ptr: drvdata->plreg)) |
218 | return dev_err_probe(dev, err: PTR_ERR(ptr: drvdata->plreg), |
219 | fmt: "failed to map plreg\n" ); |
220 | |
221 | drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev, |
222 | name: "fn2" ); |
223 | if (IS_ERR(ptr: drvdata->fn2)) |
224 | return dev_err_probe(dev, err: PTR_ERR(ptr: drvdata->fn2), |
225 | fmt: "failed to map fn2\n" ); |
226 | |
227 | hwmon_dev = devm_hwmon_device_register_with_info(dev: &pdev->dev, |
228 | name: "hpe_gxp_fan_ctrl" , |
229 | drvdata, |
230 | info: &gxp_fan_ctrl_chip_info, |
231 | NULL); |
232 | |
233 | return PTR_ERR_OR_ZERO(ptr: 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" ); |
254 | |