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 int nic78bx_probe(struct platform_device *pdev) 122 { 123 struct device *dev = &pdev->dev; 124 struct nic78bx_led_data *led_data; 125 struct resource *io_rc; 126 int ret, i; 127 128 led_data = devm_kzalloc(dev, sizeof(*led_data), GFP_KERNEL); 129 if (!led_data) 130 return -ENOMEM; 131 132 led_data->pdev = pdev; 133 platform_set_drvdata(pdev, led_data); 134 135 io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0); 136 if (!io_rc) { 137 dev_err(dev, "missing IO resources\n"); 138 return -EINVAL; 139 } 140 141 if (resource_size(io_rc) < NIC78BX_USER_LED_IO_SIZE) { 142 dev_err(dev, "IO region too small\n"); 143 return -EINVAL; 144 } 145 146 if (!devm_request_region(dev, io_rc->start, resource_size(io_rc), 147 KBUILD_MODNAME)) { 148 dev_err(dev, "failed to get IO region\n"); 149 return -EBUSY; 150 } 151 152 led_data->io_base = io_rc->start; 153 spin_lock_init(&led_data->lock); 154 155 for (i = 0; i < ARRAY_SIZE(nic78bx_leds); i++) { 156 nic78bx_leds[i].data = led_data; 157 158 ret = devm_led_classdev_register(dev, &nic78bx_leds[i].cdev); 159 if (ret) 160 return ret; 161 } 162 163 /* Unlock LED register */ 164 outb(NIC78BX_UNLOCK_VALUE, 165 led_data->io_base + NIC78BX_LOCK_REG_OFFSET); 166 167 return ret; 168 } 169 170 static void nic78bx_remove(struct platform_device *pdev) 171 { 172 struct nic78bx_led_data *led_data = platform_get_drvdata(pdev); 173 174 /* Lock LED register */ 175 outb(NIC78BX_LOCK_VALUE, 176 led_data->io_base + NIC78BX_LOCK_REG_OFFSET); 177 } 178 179 static const struct acpi_device_id led_device_ids[] = { 180 {"NIC78B3", 0}, 181 {"", 0}, 182 }; 183 MODULE_DEVICE_TABLE(acpi, led_device_ids); 184 185 static struct platform_driver led_driver = { 186 .probe = nic78bx_probe, 187 .remove_new = nic78bx_remove, 188 .driver = { 189 .name = KBUILD_MODNAME, 190 .acpi_match_table = ACPI_PTR(led_device_ids), 191 }, 192 }; 193 194 module_platform_driver(led_driver); 195 196 MODULE_DESCRIPTION("National Instruments PXI User LEDs driver"); 197 MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>"); 198 MODULE_LICENSE("GPL"); 199