From: Jeff Westfahl <[email protected]>

This driver introduces support for hardware features of National
Instruments real-time controllers. This is an ACPI device that exposes
LEDs, switches, and watchdogs.

Signed-off-by: Jeff Westfahl <[email protected]>
Signed-off-by: Kyle Roeschley <[email protected]>
---
 drivers/misc/Kconfig        |   9 +
 drivers/misc/Makefile       |   1 +
 drivers/misc/nirtfeatures.c | 575 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 585 insertions(+)
 create mode 100644 drivers/misc/nirtfeatures.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 1557951..d97f71e 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -526,6 +526,15 @@ config VEXPRESS_SYSCFG
          bus. System Configuration interface is one of the possible means
          of generating transactions on this bus.
 
+config NI_RT_FEATURES
+       bool "NI 903x/913x support"
+       depends on X86 && ACPI
+       help
+         This driver exposes LEDs and other features of NI 903x/913x Real-Time
+         controllers.
+
+         If unsure, say N (but it's safe to say "Y").
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 537d7f3..539127d 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -56,3 +56,4 @@ obj-$(CONFIG_GENWQE)          += genwqe/
 obj-$(CONFIG_ECHO)             += echo/
 obj-$(CONFIG_VEXPRESS_SYSCFG)  += vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)         += cxl/
+obj-$(CONFIG_NI_RT_FEATURES)   += nirtfeatures.o
diff --git a/drivers/misc/nirtfeatures.c b/drivers/misc/nirtfeatures.c
new file mode 100644
index 0000000..37298f2
--- /dev/null
+++ b/drivers/misc/nirtfeatures.c
@@ -0,0 +1,575 @@
+/*
+ * 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/device.h>
+#include <linux/input.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+
+#define MODULE_NAME "nirtfeatures"
+
+/* Register addresses */
+
+#define NIRTF_YEAR             0x01
+#define NIRTF_MONTH            0x02
+#define NIRTF_DAY              0x03
+#define NIRTF_HOUR             0x04
+#define NIRTF_MINUTE           0x05
+#define NIRTF_SCRATCH          0x06
+#define NIRTF_PLATFORM_MISC    0x07
+#define NIRTF_PROC_RESET_SOURCE        0x11
+#define NIRTF_CONTROLLER_MODE  0x12
+#define NIRTF_SYSTEM_LEDS      0x20
+#define NIRTF_STATUS_LED_SHIFT1        0x21
+#define NIRTF_STATUS_LED_SHIFT0        0x22
+#define NIRTF_RT_LEDS          0x23
+
+#define NIRTF_IO_SIZE          0x40
+
+/* Register values */
+
+#define NIRTF_PLATFORM_MISC_ID_MASK            0x07
+#define NIRTF_PLATFORM_MISC_ID_MANHATTAN       0
+#define NIRTF_PLATFORM_MISC_ID_HAMMERHEAD      4
+#define NIRTF_PLATFORM_MISC_ID_WINGHEAD                5
+
+#define NIRTF_CONTROLLER_MODE_NO_FPGA_SW       0x40
+#define NIRTF_CONTROLLER_MODE_HARD_BOOT_N      0x20
+#define NIRTF_CONTROLLER_MODE_NO_FPGA          0x10
+#define NIRTF_CONTROLLER_MODE_RECOVERY         0x08
+#define NIRTF_CONTROLLER_MODE_CONSOLE_OUT      0x04
+#define NIRTF_CONTROLLER_MODE_IP_RESET         0x02
+#define NIRTF_CONTROLLER_MODE_SAFE             0x01
+
+#define NIRTF_SYSTEM_LEDS_STATUS_RED           0x08
+#define NIRTF_SYSTEM_LEDS_STATUS_YELLOW                0x04
+#define NIRTF_SYSTEM_LEDS_POWER_GREEN          0x02
+#define NIRTF_SYSTEM_LEDS_POWER_YELLOW         0x01
+
+#define NIRTF_RT_LEDS_USER2_GREEN      0x08
+#define NIRTF_RT_LEDS_USER2_YELLOW     0x04
+#define NIRTF_RT_LEDS_USER1_GREEN      0x02
+#define NIRTF_RT_LEDS_USER1_YELLOW     0x01
+
+#define to_nirtfeatures(dev)   acpi_driver_data(to_acpi_device(dev))
+
+/* Structures */
+
+struct nirtfeatures {
+       struct acpi_device *acpi_device;
+       u16 io_base;
+       spinlock_t lock;
+       u8 revision[5];
+       const char *bpstring;
+       struct nirtfeatures_led *extra_leds;
+       unsigned num_extra_leds;
+};
+
+struct nirtfeatures_led {
+       struct led_classdev cdev;
+       struct nirtfeatures *nirtfeatures;
+       u8 address;
+       u8 mask;
+       u8 pattern_hi_addr;
+       u8 pattern_lo_addr;
+};
+
+/* sysfs files */
+
+static ssize_t revision_show(struct device *dev, struct device_attribute *attr,
+                            char *buf)
+{
+       struct nirtfeatures *nirtfeatures = to_nirtfeatures(dev);
+
+       return sprintf(buf, "20%02X/%02X/%02X %02X:%02X\n",
+                      nirtfeatures->revision[0], nirtfeatures->revision[1],
+                      nirtfeatures->revision[2], nirtfeatures->revision[3],
+                      nirtfeatures->revision[4]);
+}
+static DEVICE_ATTR_RO(revision);
+
+static ssize_t scratch_show(struct device *dev, struct device_attribute *attr,
+                           char *buf)
+{
+       struct nirtfeatures *nirtfeatures = to_nirtfeatures(dev);
+       u8 data;
+
+       spin_lock(&nirtfeatures->lock);
+
+       data = inb(nirtfeatures->io_base + NIRTF_SCRATCH);
+
+       spin_unlock(&nirtfeatures->lock);
+
+       return sprintf(buf, "%02x\n", data);
+}
+
+static ssize_t scratch_store(struct device *dev, struct device_attribute *attr,
+                            const char *buf, size_t count)
+{
+       struct nirtfeatures *nirtfeatures = to_nirtfeatures(dev);
+       unsigned long tmp;
+
+       if (kstrtoul(buf, 0, &tmp) || (tmp > 0xFF))
+               return -EINVAL;
+
+       spin_lock(&nirtfeatures->lock);
+
+       outb((u8)tmp, nirtfeatures->io_base + NIRTF_SCRATCH);
+
+       spin_unlock(&nirtfeatures->lock);
+
+       return count;
+}
+static DEVICE_ATTR_RW(scratch);
+
+static ssize_t backplane_id_show(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       struct nirtfeatures *nirtfeatures = to_nirtfeatures(dev);
+
+       return sprintf(buf, "%s\n", nirtfeatures->bpstring);
+}
+static DEVICE_ATTR_RO(backplane_id);
+
+static const char *const nirtfeatures_reset_source_strings[] = {
+       "button", "processor", "fpga", "watchdog", "software",
+};
+
+static ssize_t reset_source_show(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       struct nirtfeatures *nirtfeatures = to_nirtfeatures(dev);
+       u8 data;
+       int i;
+
+       data = inb(nirtfeatures->io_base + NIRTF_PROC_RESET_SOURCE);
+
+       for (i = 0; i < ARRAY_SIZE(nirtfeatures_reset_source_strings); i++)
+               if ((1 << i) & data)
+                       return sprintf(buf, "%s\n",
+                                      nirtfeatures_reset_source_strings[i]);
+
+       return sprintf(buf, "poweron\n");
+}
+static DEVICE_ATTR_RO(reset_source);
+
+static ssize_t mode_show(struct device *dev, char *buf, unsigned int mask)
+{
+       struct nirtfeatures *nirtfeatures = to_nirtfeatures(dev);
+       u8 data;
+
+       data = inb(nirtfeatures->io_base + NIRTF_CONTROLLER_MODE);
+       data &= mask;
+
+       return sprintf(buf, "%u\n", !!data);
+}
+
+static ssize_t no_fpga_sw_show(struct device *dev,
+                              struct device_attribute *attr, char *buf)
+{
+       struct nirtfeatures *nirtfeatures = to_nirtfeatures(dev);
+       int ret;
+
+       spin_lock(&nirtfeatures->lock);
+
+       ret = mode_show(dev, buf, NIRTF_CONTROLLER_MODE_NO_FPGA_SW);
+
+       spin_unlock(&nirtfeatures->lock);
+
+       return ret;
+}
+
+static ssize_t no_fpga_sw_store(struct device *dev,
+                               struct device_attribute *attr,
+                               const char *buf, size_t count)
+{
+       struct nirtfeatures *nirtfeatures = to_nirtfeatures(dev);
+       unsigned long tmp;
+       u8 data;
+
+       if (kstrtoul(buf, 0, &tmp) || (tmp > 1))
+               return -EINVAL;
+
+       spin_lock(&nirtfeatures->lock);
+
+       data = inb(nirtfeatures->io_base + NIRTF_CONTROLLER_MODE);
+
+       if (tmp)
+               data |= NIRTF_CONTROLLER_MODE_NO_FPGA_SW;
+       else
+               data &= ~NIRTF_CONTROLLER_MODE_NO_FPGA_SW;
+
+       outb(data, nirtfeatures->io_base + NIRTF_CONTROLLER_MODE);
+
+       spin_unlock(&nirtfeatures->lock);
+
+       return count;
+}
+static DEVICE_ATTR_RW(no_fpga_sw);
+
+static ssize_t soft_reset_show(struct device *dev,
+                              struct device_attribute *attr, char *buf)
+{
+       return mode_show(dev, buf, NIRTF_CONTROLLER_MODE_HARD_BOOT_N);
+}
+static DEVICE_ATTR_RO(soft_reset);
+
+static ssize_t no_fpga_show(struct device *dev, struct device_attribute *attr,
+                           char *buf)
+{
+       return mode_show(dev, buf, NIRTF_CONTROLLER_MODE_NO_FPGA);
+}
+static DEVICE_ATTR_RO(no_fpga);
+
+static ssize_t recovery_mode_show(struct device *dev,
+                                 struct device_attribute *attr, char *buf)
+{
+       return mode_show(dev, buf, NIRTF_CONTROLLER_MODE_RECOVERY);
+}
+static DEVICE_ATTR_RO(recovery_mode);
+
+static ssize_t console_out_show(struct device *dev,
+                               struct device_attribute *attr, char *buf)
+{
+       return mode_show(dev, buf, NIRTF_CONTROLLER_MODE_CONSOLE_OUT);
+}
+static DEVICE_ATTR_RO(console_out);
+
+static ssize_t ip_reset_show(struct device *dev, struct device_attribute *attr,
+                            char *buf)
+{
+       return mode_show(dev, buf, NIRTF_CONTROLLER_MODE_IP_RESET);
+}
+static DEVICE_ATTR_RO(ip_reset);
+
+static ssize_t safe_mode_show(struct device *dev,
+                             struct device_attribute *attr, char *buf)
+{
+       return mode_show(dev, buf, NIRTF_CONTROLLER_MODE_SAFE);
+}
+static DEVICE_ATTR_RO(safe_mode);
+
+static const struct attribute *nirtfeatures_attrs[] = {
+       &dev_attr_revision.attr,
+       &dev_attr_scratch.attr,
+       &dev_attr_backplane_id.attr,
+       &dev_attr_reset_source.attr,
+       &dev_attr_no_fpga_sw.attr,
+       &dev_attr_soft_reset.attr,
+       &dev_attr_no_fpga.attr,
+       &dev_attr_recovery_mode.attr,
+       &dev_attr_console_out.attr,
+       &dev_attr_ip_reset.attr,
+       &dev_attr_safe_mode.attr,
+       NULL
+};
+
+/* LEDs */
+
+static void nirtfeatures_led_brightness_set(struct led_classdev *led_cdev,
+                                           enum led_brightness brightness)
+{
+       struct nirtfeatures_led *led = (struct nirtfeatures_led *)led_cdev;
+       u8 data;
+
+       spin_lock(&led->nirtfeatures->lock);
+
+       data = inb(led->nirtfeatures->io_base + led->address);
+       data &= ~led->mask;
+       if (!!brightness)
+               data |= led->mask;
+       outb(data, led->nirtfeatures->io_base + led->address);
+
+       if (led->pattern_hi_addr && led->pattern_lo_addr) {
+               /* Write the high byte first. */
+               outb(brightness >> 8,
+                    led->nirtfeatures->io_base + led->pattern_hi_addr);
+               outb(brightness & 0xFF,
+                    led->nirtfeatures->io_base + led->pattern_lo_addr);
+       }
+
+       spin_unlock(&led->nirtfeatures->lock);
+}
+
+static enum led_brightness
+nirtfeatures_led_brightness_get(struct led_classdev *led_cdev)
+{
+       struct nirtfeatures_led *led = (struct nirtfeatures_led *)led_cdev;
+       u8 data;
+
+       data = inb(led->nirtfeatures->io_base + led->address);
+
+       /*
+        * For the yellow status LED, the blink pattern used for brightness
+        * on write is write-only, so we just return on/off for all LEDs.
+        */
+       return (data & led->mask) ? led_cdev->max_brightness : 0;
+}
+
+static struct nirtfeatures_led nirtfeatures_leds_common[] = {
+       {
+               {
+                       .name = "nilrt:user1:green",
+               },
+               .address = NIRTF_RT_LEDS,
+               .mask = NIRTF_RT_LEDS_USER1_GREEN,
+       },
+       {
+               {
+                       .name = "nilrt:user1:yellow",
+               },
+               .address = NIRTF_RT_LEDS,
+               .mask = NIRTF_RT_LEDS_USER1_YELLOW,
+       },
+       {
+               {
+                       .name = "nilrt:status:red",
+               },
+               .address = NIRTF_SYSTEM_LEDS,
+               .mask = NIRTF_SYSTEM_LEDS_STATUS_RED,
+       },
+       {
+               {
+                       .name = "nilrt:status:yellow",
+                       .max_brightness = 0xFFFF,
+               },
+               .address = NIRTF_SYSTEM_LEDS,
+               .mask = NIRTF_SYSTEM_LEDS_STATUS_YELLOW,
+               .pattern_hi_addr = NIRTF_STATUS_LED_SHIFT1,
+               .pattern_lo_addr = NIRTF_STATUS_LED_SHIFT0,
+       },
+       {
+               {
+                       .name = "nilrt:power:green",
+               },
+               .address = NIRTF_SYSTEM_LEDS,
+               .mask = NIRTF_SYSTEM_LEDS_POWER_GREEN,
+       },
+       {
+               {
+                       .name = "nilrt:power:yellow",
+               },
+               .address = NIRTF_SYSTEM_LEDS,
+               .mask = NIRTF_SYSTEM_LEDS_POWER_YELLOW,
+       },
+};
+
+static struct nirtfeatures_led nirtfeatures_leds_cdaq[] = {
+       {
+               {
+                       .name = "nilrt:user2:green",
+               },
+               .address = NIRTF_RT_LEDS,
+               .mask = NIRTF_RT_LEDS_USER2_GREEN,
+       },
+       {
+               {
+                       .name = "nilrt:user2:yellow",
+               },
+               .address = NIRTF_RT_LEDS,
+               .mask = NIRTF_RT_LEDS_USER2_YELLOW,
+       },
+};
+
+static int nirtfeatures_create_leds(struct nirtfeatures *nirtfeatures)
+{
+       int i;
+       int err;
+
+       struct nirtfeatures_led *leds = nirtfeatures_leds_common;
+
+       for (i = 0; i < ARRAY_SIZE(nirtfeatures_leds_common); i++) {
+
+               leds[i].nirtfeatures = nirtfeatures;
+
+               if (leds[i].cdev.max_brightness == 0)
+                       leds[i].cdev.max_brightness = 1;
+
+               leds[i].cdev.brightness_set = nirtfeatures_led_brightness_set;
+               leds[i].cdev.brightness_get = nirtfeatures_led_brightness_get;
+
+               err =
+                   devm_led_classdev_register(&nirtfeatures->acpi_device->dev,
+                                                &leds[i].cdev);
+               if (err)
+                       return err;
+       }
+
+       for (i = 0; i < nirtfeatures->num_extra_leds; ++i) {
+
+               nirtfeatures->extra_leds[i].nirtfeatures = nirtfeatures;
+
+               if (nirtfeatures->extra_leds[i].cdev.max_brightness == 0)
+                       nirtfeatures->extra_leds[i].cdev.max_brightness = 1;
+
+               nirtfeatures->extra_leds[i].cdev.brightness_set =
+                       nirtfeatures_led_brightness_set;
+
+               nirtfeatures->extra_leds[i].cdev.brightness_get =
+                       nirtfeatures_led_brightness_get;
+
+               err = devm_led_classdev_register(
+                               &nirtfeatures->acpi_device->dev,
+                               &nirtfeatures->extra_leds[i].cdev);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
+/* ACPI driver */
+
+static acpi_status nirtfeatures_resources(struct acpi_resource *res,
+                                         void *data)
+{
+       struct nirtfeatures *nirtfeatures = data;
+       u8 io_size;
+
+       if (res->type == ACPI_RESOURCE_TYPE_IO) {
+               if (nirtfeatures->io_base != 0) {
+                       dev_err(&nirtfeatures->acpi_device->dev,
+                               "too many IO resources\n");
+                       return AE_ALREADY_EXISTS;
+               }
+
+               nirtfeatures->io_base = res->data.io.minimum;
+               io_size = res->data.io.address_length;
+
+               if (io_size != NIRTF_IO_SIZE) {
+                       dev_err(&nirtfeatures->acpi_device->dev,
+                               "invalid IO size 0x%02x\n", io_size);
+                       return AE_ERROR;
+               }
+
+               if (!devm_request_region(&nirtfeatures->acpi_device->dev,
+                                        nirtfeatures->io_base, io_size,
+                                        MODULE_NAME)) {
+                       dev_err(&nirtfeatures->acpi_device->dev,
+                               "failed to get memory region\n");
+                       return AE_NO_MEMORY;
+               }
+       }
+
+       return AE_OK;
+}
+
+static int nirtfeatures_acpi_add(struct acpi_device *device)
+{
+       struct nirtfeatures *nirtfeatures;
+       acpi_status acpi_ret;
+       u8 bpinfo;
+       int err;
+
+       nirtfeatures = devm_kzalloc(&device->dev, sizeof(*nirtfeatures),
+                                   GFP_KERNEL);
+       if (!nirtfeatures)
+               return -ENOMEM;
+
+       device->driver_data = nirtfeatures;
+       nirtfeatures->acpi_device = device;
+
+       acpi_ret = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
+                                      nirtfeatures_resources, nirtfeatures);
+       if (ACPI_FAILURE(acpi_ret) || nirtfeatures->io_base == 0) {
+               dev_err(&device->dev, "failed to get resources\n");
+               return -ENODEV;
+       }
+
+       bpinfo = inb(nirtfeatures->io_base + NIRTF_PLATFORM_MISC);
+       bpinfo &= NIRTF_PLATFORM_MISC_ID_MASK;
+
+       switch (bpinfo) {
+       case NIRTF_PLATFORM_MISC_ID_MANHATTAN:
+               nirtfeatures->bpstring = "Manhattan";
+               break;
+       case NIRTF_PLATFORM_MISC_ID_HAMMERHEAD:
+               nirtfeatures->bpstring = "Hammerhead";
+               nirtfeatures->extra_leds = nirtfeatures_leds_cdaq;
+               nirtfeatures->num_extra_leds =
+                       ARRAY_SIZE(nirtfeatures_leds_cdaq);
+               break;
+       case NIRTF_PLATFORM_MISC_ID_WINGHEAD:
+               nirtfeatures->bpstring = "Winghead";
+               nirtfeatures->extra_leds = nirtfeatures_leds_cdaq;
+               nirtfeatures->num_extra_leds =
+                       ARRAY_SIZE(nirtfeatures_leds_cdaq);
+               break;
+       default:
+               dev_err(&nirtfeatures->acpi_device->dev,
+                       "Unrecognized backplane type %u\n", bpinfo);
+               nirtfeatures->bpstring = "Unknown";
+               break;
+       }
+
+       spin_lock_init(&nirtfeatures->lock);
+
+       nirtfeatures->revision[0] = inb(nirtfeatures->io_base + NIRTF_YEAR);
+       nirtfeatures->revision[1] = inb(nirtfeatures->io_base + NIRTF_MONTH);
+       nirtfeatures->revision[2] = inb(nirtfeatures->io_base + NIRTF_DAY);
+       nirtfeatures->revision[3] = inb(nirtfeatures->io_base + NIRTF_HOUR);
+       nirtfeatures->revision[4] = inb(nirtfeatures->io_base + NIRTF_MINUTE);
+
+       err = nirtfeatures_create_leds(nirtfeatures);
+       if (err) {
+               dev_err(&device->dev, "could not create LEDs\n");
+               return err;
+       }
+
+       err = sysfs_create_files(&device->dev.kobj, nirtfeatures_attrs);
+       if (err) {
+               dev_err(&device->dev, "could not create sysfs attributes\n");
+               return err;
+       }
+
+       dev_dbg(&nirtfeatures->acpi_device->dev,
+               "%s backplane, revision 20%02X/%02X/%02X %02X:%02X, io_base 
0x%04X\n",
+               nirtfeatures->bpstring, nirtfeatures->revision[0],
+               nirtfeatures->revision[1], nirtfeatures->revision[2],
+               nirtfeatures->revision[3], nirtfeatures->revision[4],
+               nirtfeatures->io_base);
+
+       return 0;
+}
+
+static int nirtfeatures_acpi_remove(struct acpi_device *device)
+{
+       sysfs_remove_files(&device->dev.kobj, nirtfeatures_attrs);
+
+       return 0;
+}
+
+static const struct acpi_device_id nirtfeatures_device_ids[] = {
+       {"NIC775D", 0},
+       {"", 0},
+};
+
+static struct acpi_driver nirtfeatures_acpi_driver = {
+       .name = MODULE_NAME,
+       .ids = nirtfeatures_device_ids,
+       .ops = {
+               .add = nirtfeatures_acpi_add,
+               .remove = nirtfeatures_acpi_remove,
+       },
+};
+
+module_acpi_driver(nirtfeatures_acpi_driver);
+
+MODULE_DEVICE_TABLE(acpi, nirtfeatures_device_ids);
+MODULE_DESCRIPTION("NI RT Features");
+MODULE_AUTHOR("Jeff Westfahl <[email protected]>");
+MODULE_LICENSE("GPL");
-- 
2.7.0

Reply via email to