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