]>
Commit | Line | Data |
---|---|---|
be4fdf99 VP |
1 | /* |
2 | * drivers/leds/leds-mlxcpld.c | |
3 | * Copyright (c) 2016 Mellanox Technologies. All rights reserved. | |
4 | * Copyright (c) 2016 Vadim Pasternak <[email protected]> | |
5 | * | |
6 | * Redistribution and use in source and binary forms, with or without | |
7 | * modification, are permitted provided that the following conditions are met: | |
8 | * | |
9 | * 1. Redistributions of source code must retain the above copyright | |
10 | * notice, this list of conditions and the following disclaimer. | |
11 | * 2. Redistributions in binary form must reproduce the above copyright | |
12 | * notice, this list of conditions and the following disclaimer in the | |
13 | * documentation and/or other materials provided with the distribution. | |
14 | * 3. Neither the names of the copyright holders nor the names of its | |
15 | * contributors may be used to endorse or promote products derived from | |
16 | * this software without specific prior written permission. | |
17 | * | |
18 | * Alternatively, this software may be distributed under the terms of the | |
19 | * GNU General Public License ("GPL") version 2 as published by the Free | |
20 | * Software Foundation. | |
21 | * | |
22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
23 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
26 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
27 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
28 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
29 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
31 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
32 | * POSSIBILITY OF SUCH DAMAGE. | |
33 | */ | |
34 | ||
35 | #include <linux/acpi.h> | |
36 | #include <linux/device.h> | |
37 | #include <linux/dmi.h> | |
38 | #include <linux/hwmon.h> | |
39 | #include <linux/hwmon-sysfs.h> | |
40 | #include <linux/io.h> | |
41 | #include <linux/leds.h> | |
42 | #include <linux/module.h> | |
43 | #include <linux/mod_devicetable.h> | |
44 | #include <linux/platform_device.h> | |
45 | #include <linux/slab.h> | |
46 | ||
47 | #define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 /* LPC bus access */ | |
48 | ||
49 | /* Color codes for LEDs */ | |
50 | #define MLXCPLD_LED_OFFSET_HALF 0x01 /* Offset from solid: 3Hz blink */ | |
51 | #define MLXCPLD_LED_OFFSET_FULL 0x02 /* Offset from solid: 6Hz blink */ | |
52 | #define MLXCPLD_LED_IS_OFF 0x00 /* Off */ | |
53 | #define MLXCPLD_LED_RED_STATIC_ON 0x05 /* Solid red */ | |
54 | #define MLXCPLD_LED_RED_BLINK_HALF (MLXCPLD_LED_RED_STATIC_ON + \ | |
55 | MLXCPLD_LED_OFFSET_HALF) | |
56 | #define MLXCPLD_LED_RED_BLINK_FULL (MLXCPLD_LED_RED_STATIC_ON + \ | |
57 | MLXCPLD_LED_OFFSET_FULL) | |
58 | #define MLXCPLD_LED_GREEN_STATIC_ON 0x0D /* Solid green */ | |
59 | #define MLXCPLD_LED_GREEN_BLINK_HALF (MLXCPLD_LED_GREEN_STATIC_ON + \ | |
60 | MLXCPLD_LED_OFFSET_HALF) | |
61 | #define MLXCPLD_LED_GREEN_BLINK_FULL (MLXCPLD_LED_GREEN_STATIC_ON + \ | |
62 | MLXCPLD_LED_OFFSET_FULL) | |
63 | #define MLXCPLD_LED_BLINK_3HZ 167 /* ~167 msec off/on */ | |
64 | #define MLXCPLD_LED_BLINK_6HZ 83 /* ~83 msec off/on */ | |
65 | ||
66 | /** | |
67 | * mlxcpld_param - LED access parameters: | |
68 | * @offset - offset for LED access in CPLD device | |
69 | * @mask - mask for LED access in CPLD device | |
70 | * @base_color - base color code for LED | |
71 | **/ | |
72 | struct mlxcpld_param { | |
73 | u8 offset; | |
74 | u8 mask; | |
75 | u8 base_color; | |
76 | }; | |
77 | ||
78 | /** | |
79 | * mlxcpld_led_priv - LED private data: | |
80 | * @cled - LED class device instance | |
81 | * @param - LED CPLD access parameters | |
82 | **/ | |
83 | struct mlxcpld_led_priv { | |
84 | struct led_classdev cdev; | |
85 | struct mlxcpld_param param; | |
86 | }; | |
87 | ||
88 | #define cdev_to_priv(c) container_of(c, struct mlxcpld_led_priv, cdev) | |
89 | ||
90 | /** | |
91 | * mlxcpld_led_profile - system LED profile (defined per system class): | |
92 | * @offset - offset for LED access in CPLD device | |
93 | * @mask - mask for LED access in CPLD device | |
94 | * @base_color - base color code | |
95 | * @brightness - default brightness setting (on/off) | |
96 | * @name - LED name | |
97 | **/ | |
98 | struct mlxcpld_led_profile { | |
99 | u8 offset; | |
100 | u8 mask; | |
101 | u8 base_color; | |
102 | enum led_brightness brightness; | |
103 | const char *name; | |
104 | }; | |
105 | ||
106 | /** | |
107 | * mlxcpld_led_pdata - system LED private data | |
108 | * @pdev - platform device pointer | |
109 | * @pled - LED class device instance | |
110 | * @profile - system configuration profile | |
111 | * @num_led_instances - number of LED instances | |
112 | * @lock - device access lock | |
113 | **/ | |
114 | struct mlxcpld_led_pdata { | |
115 | struct platform_device *pdev; | |
116 | struct mlxcpld_led_priv *pled; | |
117 | struct mlxcpld_led_profile *profile; | |
118 | int num_led_instances; | |
119 | spinlock_t lock; | |
120 | }; | |
121 | ||
122 | static struct mlxcpld_led_pdata *mlxcpld_led; | |
123 | ||
124 | /* Default profile fit the next Mellanox systems: | |
125 | * "msx6710", "msx6720", "msb7700", "msn2700", "msx1410", | |
126 | * "msn2410", "msb7800", "msn2740" | |
127 | */ | |
128 | static struct mlxcpld_led_profile mlxcpld_led_default_profile[] = { | |
129 | { | |
130 | 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, | |
131 | "mlxcpld:fan1:green", | |
132 | }, | |
133 | { | |
134 | 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, | |
135 | "mlxcpld:fan1:red", | |
136 | }, | |
137 | { | |
138 | 0x21, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, | |
139 | "mlxcpld:fan2:green", | |
140 | }, | |
141 | { | |
142 | 0x21, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, | |
143 | "mlxcpld:fan2:red", | |
144 | }, | |
145 | { | |
146 | 0x22, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, | |
147 | "mlxcpld:fan3:green", | |
148 | }, | |
149 | { | |
150 | 0x22, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, | |
151 | "mlxcpld:fan3:red", | |
152 | }, | |
153 | { | |
154 | 0x22, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, | |
155 | "mlxcpld:fan4:green", | |
156 | }, | |
157 | { | |
158 | 0x22, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, | |
159 | "mlxcpld:fan4:red", | |
160 | }, | |
161 | { | |
162 | 0x20, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, | |
163 | "mlxcpld:psu:green", | |
164 | }, | |
165 | { | |
166 | 0x20, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, | |
167 | "mlxcpld:psu:red", | |
168 | }, | |
169 | { | |
170 | 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, | |
171 | "mlxcpld:status:green", | |
172 | }, | |
173 | { | |
174 | 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, | |
175 | "mlxcpld:status:red", | |
176 | }, | |
177 | }; | |
178 | ||
179 | /* Profile fit the Mellanox systems based on "msn2100" */ | |
180 | static struct mlxcpld_led_profile mlxcpld_led_msn2100_profile[] = { | |
181 | { | |
182 | 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, | |
183 | "mlxcpld:fan:green", | |
184 | }, | |
185 | { | |
186 | 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, | |
187 | "mlxcpld:fan:red", | |
188 | }, | |
189 | { | |
190 | 0x23, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, | |
191 | "mlxcpld:psu1:green", | |
192 | }, | |
193 | { | |
194 | 0x23, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, | |
195 | "mlxcpld:psu1:red", | |
196 | }, | |
197 | { | |
198 | 0x23, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, | |
199 | "mlxcpld:psu2:green", | |
200 | }, | |
201 | { | |
202 | 0x23, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, | |
203 | "mlxcpld:psu2:red", | |
204 | }, | |
205 | { | |
206 | 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, | |
207 | "mlxcpld:status:green", | |
208 | }, | |
209 | { | |
210 | 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, | |
211 | "mlxcpld:status:red", | |
212 | }, | |
213 | { | |
214 | 0x24, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, LED_OFF, | |
215 | "mlxcpld:uid:blue", | |
216 | }, | |
217 | }; | |
218 | ||
219 | enum mlxcpld_led_platform_types { | |
220 | MLXCPLD_LED_PLATFORM_DEFAULT, | |
221 | MLXCPLD_LED_PLATFORM_MSN2100, | |
222 | }; | |
223 | ||
224 | static const char *mlx_product_names[] = { | |
225 | "DEFAULT", | |
226 | "MSN2100", | |
227 | }; | |
228 | ||
229 | static enum | |
230 | mlxcpld_led_platform_types mlxcpld_led_platform_check_sys_type(void) | |
231 | { | |
232 | const char *mlx_product_name; | |
233 | int i; | |
234 | ||
235 | mlx_product_name = dmi_get_system_info(DMI_PRODUCT_NAME); | |
236 | if (!mlx_product_name) | |
237 | return MLXCPLD_LED_PLATFORM_DEFAULT; | |
238 | ||
239 | for (i = 1; i < ARRAY_SIZE(mlx_product_names); i++) { | |
240 | if (strstr(mlx_product_name, mlx_product_names[i])) | |
241 | return i; | |
242 | } | |
243 | ||
244 | return MLXCPLD_LED_PLATFORM_DEFAULT; | |
245 | } | |
246 | ||
247 | static void mlxcpld_led_bus_access_func(u16 base, u8 offset, u8 rw_flag, | |
248 | u8 *data) | |
249 | { | |
250 | u32 addr = base + offset; | |
251 | ||
252 | if (rw_flag == 0) | |
253 | outb(*data, addr); | |
254 | else | |
255 | *data = inb(addr); | |
256 | } | |
257 | ||
258 | static void mlxcpld_led_store_hw(u8 mask, u8 off, u8 vset) | |
259 | { | |
260 | u8 nib, val; | |
261 | ||
262 | /* | |
263 | * Each LED is controlled through low or high nibble of the relevant | |
264 | * CPLD register. Register offset is specified by off parameter. | |
265 | * Parameter vset provides color code: 0x0 for off, 0x5 for solid red, | |
266 | * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink | |
267 | * green. | |
268 | * Parameter mask specifies which nibble is used for specific LED: mask | |
269 | * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f - | |
270 | * higher nibble (bits from 4 to 7). | |
271 | */ | |
272 | spin_lock(&mlxcpld_led->lock); | |
273 | mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 1, | |
274 | &val); | |
275 | nib = (mask == 0xf0) ? vset : (vset << 4); | |
276 | val = (val & mask) | nib; | |
277 | mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 0, | |
278 | &val); | |
279 | spin_unlock(&mlxcpld_led->lock); | |
280 | } | |
281 | ||
282 | static void mlxcpld_led_brightness_set(struct led_classdev *led, | |
283 | enum led_brightness value) | |
284 | { | |
285 | struct mlxcpld_led_priv *pled = cdev_to_priv(led); | |
286 | ||
287 | if (value) { | |
288 | mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, | |
289 | pled->param.base_color); | |
290 | return; | |
291 | } | |
292 | ||
293 | mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, | |
294 | MLXCPLD_LED_IS_OFF); | |
295 | } | |
296 | ||
297 | static int mlxcpld_led_blink_set(struct led_classdev *led, | |
298 | unsigned long *delay_on, | |
299 | unsigned long *delay_off) | |
300 | { | |
301 | struct mlxcpld_led_priv *pled = cdev_to_priv(led); | |
302 | ||
303 | /* | |
304 | * HW supports two types of blinking: full (6Hz) and half (3Hz). | |
305 | * For delay on/off zero default setting 3Hz is used. | |
306 | */ | |
307 | if (!(*delay_on == 0 && *delay_off == 0) && | |
308 | !(*delay_on == MLXCPLD_LED_BLINK_3HZ && | |
309 | *delay_off == MLXCPLD_LED_BLINK_3HZ) && | |
310 | !(*delay_on == MLXCPLD_LED_BLINK_6HZ && | |
311 | *delay_off == MLXCPLD_LED_BLINK_6HZ)) | |
312 | return -EINVAL; | |
313 | ||
314 | if (*delay_on == MLXCPLD_LED_BLINK_6HZ) | |
315 | mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, | |
316 | pled->param.base_color + | |
317 | MLXCPLD_LED_OFFSET_FULL); | |
318 | else | |
319 | mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, | |
320 | pled->param.base_color + | |
321 | MLXCPLD_LED_OFFSET_HALF); | |
322 | ||
323 | return 0; | |
324 | } | |
325 | ||
326 | static int mlxcpld_led_config(struct device *dev, | |
327 | struct mlxcpld_led_pdata *cpld) | |
328 | { | |
329 | int i; | |
330 | int err; | |
331 | ||
a86854d0 KC |
332 | cpld->pled = devm_kcalloc(dev, |
333 | cpld->num_led_instances, | |
334 | sizeof(struct mlxcpld_led_priv), | |
335 | GFP_KERNEL); | |
be4fdf99 VP |
336 | if (!cpld->pled) |
337 | return -ENOMEM; | |
338 | ||
339 | for (i = 0; i < cpld->num_led_instances; i++) { | |
340 | cpld->pled[i].cdev.name = cpld->profile[i].name; | |
341 | cpld->pled[i].cdev.brightness = cpld->profile[i].brightness; | |
342 | cpld->pled[i].cdev.max_brightness = 1; | |
343 | cpld->pled[i].cdev.brightness_set = mlxcpld_led_brightness_set; | |
344 | cpld->pled[i].cdev.blink_set = mlxcpld_led_blink_set; | |
345 | cpld->pled[i].cdev.flags = LED_CORE_SUSPENDRESUME; | |
346 | err = devm_led_classdev_register(dev, &cpld->pled[i].cdev); | |
347 | if (err) | |
348 | return err; | |
349 | ||
350 | cpld->pled[i].param.offset = mlxcpld_led->profile[i].offset; | |
351 | cpld->pled[i].param.mask = mlxcpld_led->profile[i].mask; | |
352 | cpld->pled[i].param.base_color = | |
353 | mlxcpld_led->profile[i].base_color; | |
354 | ||
355 | if (mlxcpld_led->profile[i].brightness) | |
356 | mlxcpld_led_brightness_set(&cpld->pled[i].cdev, | |
357 | mlxcpld_led->profile[i].brightness); | |
358 | } | |
359 | ||
360 | return 0; | |
361 | } | |
362 | ||
363 | static int __init mlxcpld_led_probe(struct platform_device *pdev) | |
364 | { | |
365 | enum mlxcpld_led_platform_types mlxcpld_led_plat = | |
366 | mlxcpld_led_platform_check_sys_type(); | |
367 | ||
368 | mlxcpld_led = devm_kzalloc(&pdev->dev, sizeof(*mlxcpld_led), | |
369 | GFP_KERNEL); | |
370 | if (!mlxcpld_led) | |
371 | return -ENOMEM; | |
372 | ||
373 | mlxcpld_led->pdev = pdev; | |
374 | ||
375 | switch (mlxcpld_led_plat) { | |
376 | case MLXCPLD_LED_PLATFORM_MSN2100: | |
377 | mlxcpld_led->profile = mlxcpld_led_msn2100_profile; | |
378 | mlxcpld_led->num_led_instances = | |
379 | ARRAY_SIZE(mlxcpld_led_msn2100_profile); | |
380 | break; | |
381 | ||
382 | default: | |
383 | mlxcpld_led->profile = mlxcpld_led_default_profile; | |
384 | mlxcpld_led->num_led_instances = | |
385 | ARRAY_SIZE(mlxcpld_led_default_profile); | |
386 | break; | |
387 | } | |
388 | ||
389 | spin_lock_init(&mlxcpld_led->lock); | |
390 | ||
391 | return mlxcpld_led_config(&pdev->dev, mlxcpld_led); | |
392 | } | |
393 | ||
394 | static struct platform_driver mlxcpld_led_driver = { | |
395 | .driver = { | |
396 | .name = KBUILD_MODNAME, | |
397 | }, | |
398 | }; | |
399 | ||
400 | static int __init mlxcpld_led_init(void) | |
401 | { | |
402 | struct platform_device *pdev; | |
403 | int err; | |
404 | ||
dca897e2 VP |
405 | if (!dmi_match(DMI_CHASSIS_VENDOR, "Mellanox Technologies Ltd.")) |
406 | return -ENODEV; | |
407 | ||
be4fdf99 VP |
408 | pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); |
409 | if (IS_ERR(pdev)) { | |
410 | pr_err("Device allocation failed\n"); | |
411 | return PTR_ERR(pdev); | |
412 | } | |
413 | ||
414 | err = platform_driver_probe(&mlxcpld_led_driver, mlxcpld_led_probe); | |
415 | if (err) { | |
416 | pr_err("Probe platform driver failed\n"); | |
417 | platform_device_unregister(pdev); | |
418 | } | |
419 | ||
420 | return err; | |
421 | } | |
422 | ||
423 | static void __exit mlxcpld_led_exit(void) | |
424 | { | |
425 | platform_device_unregister(mlxcpld_led->pdev); | |
426 | platform_driver_unregister(&mlxcpld_led_driver); | |
427 | } | |
428 | ||
429 | module_init(mlxcpld_led_init); | |
430 | module_exit(mlxcpld_led_exit); | |
431 | ||
432 | MODULE_AUTHOR("Vadim Pasternak <[email protected]>"); | |
433 | MODULE_DESCRIPTION("Mellanox board LED driver"); | |
dca897e2 | 434 | MODULE_LICENSE("Dual BSD/GPL"); |
be4fdf99 | 435 | MODULE_ALIAS("platform:leds_mlxcpld"); |