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