1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Siemens SIMATIC IPC driver for LEDs |
4 | * |
5 | * Copyright (c) Siemens AG, 2018-2021 |
6 | * |
7 | * Authors: |
8 | * Henning Schild <henning.schild@siemens.com> |
9 | * Jan Kiszka <jan.kiszka@siemens.com> |
10 | * Gerd Haeussler <gerd.haeussler.ext@siemens.com> |
11 | */ |
12 | |
13 | #include <linux/ioport.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/leds.h> |
16 | #include <linux/module.h> |
17 | #include <linux/pci.h> |
18 | #include <linux/platform_data/x86/simatic-ipc-base.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/sizes.h> |
21 | #include <linux/spinlock.h> |
22 | |
23 | #define SIMATIC_IPC_LED_PORT_BASE 0x404E |
24 | |
25 | struct simatic_ipc_led { |
26 | unsigned int value; /* mask for io */ |
27 | char *name; |
28 | struct led_classdev cdev; |
29 | }; |
30 | |
31 | static struct simatic_ipc_led simatic_ipc_leds_io[] = { |
32 | {1 << 15, "green:" LED_FUNCTION_STATUS "-1" }, |
33 | {1 << 7, "yellow:" LED_FUNCTION_STATUS "-1" }, |
34 | {1 << 14, "red:" LED_FUNCTION_STATUS "-2" }, |
35 | {1 << 6, "yellow:" LED_FUNCTION_STATUS "-2" }, |
36 | {1 << 13, "red:" LED_FUNCTION_STATUS "-3" }, |
37 | {1 << 5, "yellow:" LED_FUNCTION_STATUS "-3" }, |
38 | { } |
39 | }; |
40 | |
41 | static struct resource simatic_ipc_led_io_res = |
42 | DEFINE_RES_IO_NAMED(SIMATIC_IPC_LED_PORT_BASE, SZ_2, KBUILD_MODNAME); |
43 | |
44 | static DEFINE_SPINLOCK(reg_lock); |
45 | |
46 | static inline struct simatic_ipc_led *cdev_to_led(struct led_classdev *led_cd) |
47 | { |
48 | return container_of(led_cd, struct simatic_ipc_led, cdev); |
49 | } |
50 | |
51 | static void simatic_ipc_led_set_io(struct led_classdev *led_cd, |
52 | enum led_brightness brightness) |
53 | { |
54 | struct simatic_ipc_led *led = cdev_to_led(led_cd); |
55 | unsigned long flags; |
56 | unsigned int val; |
57 | |
58 | spin_lock_irqsave(®_lock, flags); |
59 | |
60 | val = inw(SIMATIC_IPC_LED_PORT_BASE); |
61 | if (brightness == LED_OFF) |
62 | outw(value: val | led->value, SIMATIC_IPC_LED_PORT_BASE); |
63 | else |
64 | outw(value: val & ~led->value, SIMATIC_IPC_LED_PORT_BASE); |
65 | |
66 | spin_unlock_irqrestore(lock: ®_lock, flags); |
67 | } |
68 | |
69 | static enum led_brightness simatic_ipc_led_get_io(struct led_classdev *led_cd) |
70 | { |
71 | struct simatic_ipc_led *led = cdev_to_led(led_cd); |
72 | |
73 | return inw(SIMATIC_IPC_LED_PORT_BASE) & led->value ? LED_OFF : led_cd->max_brightness; |
74 | } |
75 | |
76 | static int simatic_ipc_leds_probe(struct platform_device *pdev) |
77 | { |
78 | const struct simatic_ipc_platform *plat = pdev->dev.platform_data; |
79 | struct device *dev = &pdev->dev; |
80 | struct simatic_ipc_led *ipcled; |
81 | struct led_classdev *cdev; |
82 | struct resource *res; |
83 | int err; |
84 | |
85 | switch (plat->devmode) { |
86 | case SIMATIC_IPC_DEVICE_227D: |
87 | case SIMATIC_IPC_DEVICE_427E: |
88 | res = &simatic_ipc_led_io_res; |
89 | ipcled = simatic_ipc_leds_io; |
90 | /* on 227D the two bytes work the other way araound */ |
91 | if (plat->devmode == SIMATIC_IPC_DEVICE_227D) { |
92 | while (ipcled->value) { |
93 | ipcled->value = swab16(ipcled->value); |
94 | ipcled++; |
95 | } |
96 | ipcled = simatic_ipc_leds_io; |
97 | } |
98 | if (!devm_request_region(dev, res->start, resource_size(res), KBUILD_MODNAME)) { |
99 | dev_err(dev, "Unable to register IO resource at %pR\n" , res); |
100 | return -EBUSY; |
101 | } |
102 | break; |
103 | default: |
104 | return -ENODEV; |
105 | } |
106 | |
107 | while (ipcled->value) { |
108 | cdev = &ipcled->cdev; |
109 | cdev->brightness_set = simatic_ipc_led_set_io; |
110 | cdev->brightness_get = simatic_ipc_led_get_io; |
111 | cdev->max_brightness = LED_ON; |
112 | cdev->name = ipcled->name; |
113 | |
114 | err = devm_led_classdev_register(parent: dev, led_cdev: cdev); |
115 | if (err < 0) |
116 | return err; |
117 | ipcled++; |
118 | } |
119 | |
120 | return 0; |
121 | } |
122 | |
123 | static struct platform_driver simatic_ipc_led_driver = { |
124 | .probe = simatic_ipc_leds_probe, |
125 | .driver = { |
126 | .name = KBUILD_MODNAME, |
127 | } |
128 | }; |
129 | module_platform_driver(simatic_ipc_led_driver); |
130 | |
131 | MODULE_LICENSE("GPL v2" ); |
132 | MODULE_ALIAS("platform:" KBUILD_MODNAME); |
133 | MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>" ); |
134 | |