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