1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2016 National Instruments Corp.
4 */
5
6 #include <linux/array_size.h>
7 #include <linux/bits.h>
8 #include <linux/container_of.h>
9 #include <linux/device.h>
10 #include <linux/errno.h>
11 #include <linux/io.h>
12 #include <linux/ioport.h>
13 #include <linux/leds.h>
14 #include <linux/module.h>
15 #include <linux/platform_device.h>
16 #include <linux/spinlock.h>
17 #include <linux/types.h>
18
19 #define NIC78BX_USER1_LED_MASK 0x3
20 #define NIC78BX_USER1_GREEN_LED BIT(0)
21 #define NIC78BX_USER1_YELLOW_LED BIT(1)
22
23 #define NIC78BX_USER2_LED_MASK 0xC
24 #define NIC78BX_USER2_GREEN_LED BIT(2)
25 #define NIC78BX_USER2_YELLOW_LED BIT(3)
26
27 #define NIC78BX_LOCK_REG_OFFSET 1
28 #define NIC78BX_LOCK_VALUE 0xA5
29 #define NIC78BX_UNLOCK_VALUE 0x5A
30
31 #define NIC78BX_USER_LED_IO_SIZE 2
32
33 struct nic78bx_led_data {
34 u16 io_base;
35 spinlock_t lock;
36 struct platform_device *pdev;
37 };
38
39 struct nic78bx_led {
40 u8 bit;
41 u8 mask;
42 struct nic78bx_led_data *data;
43 struct led_classdev cdev;
44 };
45
to_nic78bx_led(struct led_classdev * cdev)46 static inline struct nic78bx_led *to_nic78bx_led(struct led_classdev *cdev)
47 {
48 return container_of(cdev, struct nic78bx_led, cdev);
49 }
50
nic78bx_brightness_set(struct led_classdev * cdev,enum led_brightness brightness)51 static void nic78bx_brightness_set(struct led_classdev *cdev,
52 enum led_brightness brightness)
53 {
54 struct nic78bx_led *nled = to_nic78bx_led(cdev);
55 unsigned long flags;
56 u8 value;
57
58 spin_lock_irqsave(&nled->data->lock, flags);
59 value = inb(nled->data->io_base);
60
61 if (brightness) {
62 value &= ~nled->mask;
63 value |= nled->bit;
64 } else {
65 value &= ~nled->bit;
66 }
67
68 outb(value, nled->data->io_base);
69 spin_unlock_irqrestore(&nled->data->lock, flags);
70 }
71
nic78bx_brightness_get(struct led_classdev * cdev)72 static enum led_brightness nic78bx_brightness_get(struct led_classdev *cdev)
73 {
74 struct nic78bx_led *nled = to_nic78bx_led(cdev);
75 unsigned long flags;
76 u8 value;
77
78 spin_lock_irqsave(&nled->data->lock, flags);
79 value = inb(nled->data->io_base);
80 spin_unlock_irqrestore(&nled->data->lock, flags);
81
82 return (value & nled->bit) ? 1 : LED_OFF;
83 }
84
85 static struct nic78bx_led nic78bx_leds[] = {
86 {
87 .bit = NIC78BX_USER1_GREEN_LED,
88 .mask = NIC78BX_USER1_LED_MASK,
89 .cdev = {
90 .name = "nilrt:green:user1",
91 .max_brightness = 1,
92 .brightness_set = nic78bx_brightness_set,
93 .brightness_get = nic78bx_brightness_get,
94 }
95 },
96 {
97 .bit = NIC78BX_USER1_YELLOW_LED,
98 .mask = NIC78BX_USER1_LED_MASK,
99 .cdev = {
100 .name = "nilrt:yellow:user1",
101 .max_brightness = 1,
102 .brightness_set = nic78bx_brightness_set,
103 .brightness_get = nic78bx_brightness_get,
104 }
105 },
106 {
107 .bit = NIC78BX_USER2_GREEN_LED,
108 .mask = NIC78BX_USER2_LED_MASK,
109 .cdev = {
110 .name = "nilrt:green:user2",
111 .max_brightness = 1,
112 .brightness_set = nic78bx_brightness_set,
113 .brightness_get = nic78bx_brightness_get,
114 }
115 },
116 {
117 .bit = NIC78BX_USER2_YELLOW_LED,
118 .mask = NIC78BX_USER2_LED_MASK,
119 .cdev = {
120 .name = "nilrt:yellow:user2",
121 .max_brightness = 1,
122 .brightness_set = nic78bx_brightness_set,
123 .brightness_get = nic78bx_brightness_get,
124 }
125 }
126 };
127
lock_led_reg_action(void * data)128 static void lock_led_reg_action(void *data)
129 {
130 struct nic78bx_led_data *led_data = data;
131
132 /* Lock LED register */
133 outb(NIC78BX_LOCK_VALUE,
134 led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
135 }
136
nic78bx_probe(struct platform_device * pdev)137 static int nic78bx_probe(struct platform_device *pdev)
138 {
139 struct device *dev = &pdev->dev;
140 struct nic78bx_led_data *led_data;
141 struct resource *io_rc;
142 int ret, i;
143
144 led_data = devm_kzalloc(dev, sizeof(*led_data), GFP_KERNEL);
145 if (!led_data)
146 return -ENOMEM;
147
148 led_data->pdev = pdev;
149 platform_set_drvdata(pdev, led_data);
150
151 io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
152 if (!io_rc) {
153 dev_err(dev, "missing IO resources\n");
154 return -EINVAL;
155 }
156
157 if (resource_size(io_rc) < NIC78BX_USER_LED_IO_SIZE) {
158 dev_err(dev, "IO region too small\n");
159 return -EINVAL;
160 }
161
162 if (!devm_request_region(dev, io_rc->start, resource_size(io_rc),
163 KBUILD_MODNAME)) {
164 dev_err(dev, "failed to get IO region\n");
165 return -EBUSY;
166 }
167
168 led_data->io_base = io_rc->start;
169 spin_lock_init(&led_data->lock);
170
171 ret = devm_add_action(dev, lock_led_reg_action, led_data);
172 if (ret)
173 return ret;
174
175 for (i = 0; i < ARRAY_SIZE(nic78bx_leds); i++) {
176 nic78bx_leds[i].data = led_data;
177
178 ret = devm_led_classdev_register(dev, &nic78bx_leds[i].cdev);
179 if (ret)
180 return ret;
181 }
182
183 /* Unlock LED register */
184 outb(NIC78BX_UNLOCK_VALUE,
185 led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
186
187 return ret;
188 }
189
190 static const struct acpi_device_id led_device_ids[] = {
191 { "NIC78B3" },
192 { }
193 };
194 MODULE_DEVICE_TABLE(acpi, led_device_ids);
195
196 static struct platform_driver led_driver = {
197 .probe = nic78bx_probe,
198 .driver = {
199 .name = KBUILD_MODNAME,
200 .acpi_match_table = led_device_ids,
201 },
202 };
203
204 module_platform_driver(led_driver);
205
206 MODULE_DESCRIPTION("National Instruments PXI User LEDs driver");
207 MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>");
208 MODULE_LICENSE("GPL");
209