xref: /linux/drivers/leds/leds-nic78bx.c (revision d2c9a99135da931377240942d44f3dea104cedb8)
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