leds: Add user LED driver for NIC78bx device
authorHui Chun Ong <hui.chun.ong@ni.com>
Fri, 4 Nov 2016 05:31:47 +0000 (13:31 +0800)
committerJacek Anaszewski <j.anaszewski@samsung.com>
Tue, 22 Nov 2016 11:07:04 +0000 (12:07 +0100)
Add the driver to support User LEDs on PXI Embedded Controller.

Signed-off-by: Hui Chun Ong <hui.chun.ong@ni.com>
Signed-off-by: Brad Mouring <brad.mouring@ni.com>
Acked-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Jacek Anaszewski <j.anaszewski@samsung.com>
drivers/leds/Kconfig
drivers/leds/Makefile
drivers/leds/leds-nic78bx.c [new file with mode: 0644]

index 5fd3f4ca0c3cdc2e34f2334efbad4a3540f51c07..fb6304e974228f3d0f48cbe6c41d2ac13fd03cb6 100644 (file)
@@ -667,6 +667,17 @@ config LEDS_USER
          support in kernel. To compile this driver as a module, choose 'm' here:
          the module will be called uleds.
 
+config LEDS_NIC78BX
+       tristate "LED support for NI PXI NIC78bx devices"
+       depends on LEDS_CLASS
+       depends on X86 && ACPI
+       help
+         This option enables support for the User1 and User2 LEDs on NI
+         PXI NIC78bx devices.
+
+         To compile this driver as a module, choose M here: the module
+         will be called leds-nic78bx.
+
 comment "LED Triggers"
 source "drivers/leds/trigger/Kconfig"
 
index d5331ff5b563c4d656878d613f39726f9a486c9f..6b8273736478a623424e4c0b74fbe5a40844732b 100644 (file)
@@ -71,6 +71,7 @@ obj-$(CONFIG_LEDS_IS31FL319X)         += leds-is31fl319x.o
 obj-$(CONFIG_LEDS_IS31FL32XX)          += leds-is31fl32xx.o
 obj-$(CONFIG_LEDS_PM8058)              += leds-pm8058.o
 obj-$(CONFIG_LEDS_MLXCPLD)             += leds-mlxcpld.o
+obj-$(CONFIG_LEDS_NIC78BX)             += leds-nic78bx.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_DAC124S085)          += leds-dac124s085.o
diff --git a/drivers/leds/leds-nic78bx.c b/drivers/leds/leds-nic78bx.c
new file mode 100644 (file)
index 0000000..8d69e2b
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2016 National Instruments Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/acpi.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define NIC78BX_USER1_LED_MASK         0x3
+#define NIC78BX_USER1_GREEN_LED                BIT(0)
+#define NIC78BX_USER1_YELLOW_LED       BIT(1)
+
+#define NIC78BX_USER2_LED_MASK         0xC
+#define NIC78BX_USER2_GREEN_LED                BIT(2)
+#define NIC78BX_USER2_YELLOW_LED       BIT(3)
+
+#define NIC78BX_LOCK_REG_OFFSET                1
+#define NIC78BX_LOCK_VALUE             0xA5
+#define NIC78BX_UNLOCK_VALUE           0x5A
+
+#define NIC78BX_USER_LED_IO_SIZE       2
+
+struct nic78bx_led_data {
+       u16 io_base;
+       spinlock_t lock;
+       struct platform_device *pdev;
+};
+
+struct nic78bx_led {
+       u8 bit;
+       u8 mask;
+       struct nic78bx_led_data *data;
+       struct led_classdev cdev;
+};
+
+static inline struct nic78bx_led *to_nic78bx_led(struct led_classdev *cdev)
+{
+       return container_of(cdev, struct nic78bx_led, cdev);
+}
+
+static void nic78bx_brightness_set(struct led_classdev *cdev,
+                                 enum led_brightness brightness)
+{
+       struct nic78bx_led *nled = to_nic78bx_led(cdev);
+       unsigned long flags;
+       u8 value;
+
+       spin_lock_irqsave(&nled->data->lock, flags);
+       value = inb(nled->data->io_base);
+
+       if (brightness) {
+               value &= ~nled->mask;
+               value |= nled->bit;
+       } else {
+               value &= ~nled->bit;
+       }
+
+       outb(value, nled->data->io_base);
+       spin_unlock_irqrestore(&nled->data->lock, flags);
+}
+
+static enum led_brightness nic78bx_brightness_get(struct led_classdev *cdev)
+{
+       struct nic78bx_led *nled = to_nic78bx_led(cdev);
+       unsigned long flags;
+       u8 value;
+
+       spin_lock_irqsave(&nled->data->lock, flags);
+       value = inb(nled->data->io_base);
+       spin_unlock_irqrestore(&nled->data->lock, flags);
+
+       return (value & nled->bit) ? 1 : LED_OFF;
+}
+
+static struct nic78bx_led nic78bx_leds[] = {
+       {
+               .bit = NIC78BX_USER1_GREEN_LED,
+               .mask = NIC78BX_USER1_LED_MASK,
+               .cdev = {
+                       .name = "nilrt:green:user1",
+                       .max_brightness = 1,
+                       .brightness_set = nic78bx_brightness_set,
+                       .brightness_get = nic78bx_brightness_get,
+               }
+       },
+       {
+               .bit = NIC78BX_USER1_YELLOW_LED,
+               .mask = NIC78BX_USER1_LED_MASK,
+               .cdev = {
+                       .name = "nilrt:yellow:user1",
+                       .max_brightness = 1,
+                       .brightness_set = nic78bx_brightness_set,
+                       .brightness_get = nic78bx_brightness_get,
+               }
+       },
+       {
+               .bit = NIC78BX_USER2_GREEN_LED,
+               .mask = NIC78BX_USER2_LED_MASK,
+               .cdev = {
+                       .name = "nilrt:green:user2",
+                       .max_brightness = 1,
+                       .brightness_set = nic78bx_brightness_set,
+                       .brightness_get = nic78bx_brightness_get,
+               }
+       },
+       {
+               .bit = NIC78BX_USER2_YELLOW_LED,
+               .mask = NIC78BX_USER2_LED_MASK,
+               .cdev = {
+                       .name = "nilrt:yellow:user2",
+                       .max_brightness = 1,
+                       .brightness_set = nic78bx_brightness_set,
+                       .brightness_get = nic78bx_brightness_get,
+               }
+       }
+};
+
+static int nic78bx_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct nic78bx_led_data *led_data;
+       struct resource *io_rc;
+       int ret, i;
+
+       led_data = devm_kzalloc(dev, sizeof(*led_data), GFP_KERNEL);
+       if (!led_data)
+               return -ENOMEM;
+
+       led_data->pdev = pdev;
+       platform_set_drvdata(pdev, led_data);
+
+       io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
+       if (!io_rc) {
+               dev_err(dev, "missing IO resources\n");
+               return -EINVAL;
+       }
+
+       if (resource_size(io_rc) < NIC78BX_USER_LED_IO_SIZE) {
+               dev_err(dev, "IO region too small\n");
+               return -EINVAL;
+       }
+
+       if (!devm_request_region(dev, io_rc->start, resource_size(io_rc),
+                                KBUILD_MODNAME)) {
+               dev_err(dev, "failed to get IO region\n");
+               return -EBUSY;
+       }
+
+       led_data->io_base = io_rc->start;
+       spin_lock_init(&led_data->lock);
+
+       for (i = 0; i < ARRAY_SIZE(nic78bx_leds); i++) {
+               nic78bx_leds[i].data = led_data;
+
+               ret = devm_led_classdev_register(dev, &nic78bx_leds[i].cdev);
+               if (ret)
+                       return ret;
+       }
+
+       /* Unlock LED register */
+       outb(NIC78BX_UNLOCK_VALUE,
+            led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
+
+       return ret;
+}
+
+static int nic78bx_remove(struct platform_device *pdev)
+{
+       struct nic78bx_led_data *led_data = platform_get_drvdata(pdev);
+
+       /* Lock LED register */
+       outb(NIC78BX_LOCK_VALUE,
+            led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
+
+       return 0;
+}
+
+static const struct acpi_device_id led_device_ids[] = {
+       {"NIC78B3", 0},
+       {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, led_device_ids);
+
+static struct platform_driver led_driver = {
+       .probe = nic78bx_probe,
+       .remove = nic78bx_remove,
+       .driver = {
+               .name = KBUILD_MODNAME,
+               .acpi_match_table = ACPI_PTR(led_device_ids),
+       },
+};
+
+module_platform_driver(led_driver);
+
+MODULE_DESCRIPTION("National Instruments PXI User LEDs driver");
+MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>");
+MODULE_LICENSE("GPL");