1*c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
28338eab5SHui Chun Ong /*
38338eab5SHui Chun Ong * Copyright (C) 2016 National Instruments Corp.
48338eab5SHui Chun Ong */
58338eab5SHui Chun Ong
68338eab5SHui Chun Ong #include <linux/acpi.h>
78338eab5SHui Chun Ong #include <linux/leds.h>
88338eab5SHui Chun Ong #include <linux/module.h>
98338eab5SHui Chun Ong #include <linux/platform_device.h>
108338eab5SHui Chun Ong #include <linux/spinlock.h>
118338eab5SHui Chun Ong
128338eab5SHui Chun Ong #define NIC78BX_USER1_LED_MASK 0x3
138338eab5SHui Chun Ong #define NIC78BX_USER1_GREEN_LED BIT(0)
148338eab5SHui Chun Ong #define NIC78BX_USER1_YELLOW_LED BIT(1)
158338eab5SHui Chun Ong
168338eab5SHui Chun Ong #define NIC78BX_USER2_LED_MASK 0xC
178338eab5SHui Chun Ong #define NIC78BX_USER2_GREEN_LED BIT(2)
188338eab5SHui Chun Ong #define NIC78BX_USER2_YELLOW_LED BIT(3)
198338eab5SHui Chun Ong
208338eab5SHui Chun Ong #define NIC78BX_LOCK_REG_OFFSET 1
218338eab5SHui Chun Ong #define NIC78BX_LOCK_VALUE 0xA5
228338eab5SHui Chun Ong #define NIC78BX_UNLOCK_VALUE 0x5A
238338eab5SHui Chun Ong
248338eab5SHui Chun Ong #define NIC78BX_USER_LED_IO_SIZE 2
258338eab5SHui Chun Ong
268338eab5SHui Chun Ong struct nic78bx_led_data {
278338eab5SHui Chun Ong u16 io_base;
288338eab5SHui Chun Ong spinlock_t lock;
298338eab5SHui Chun Ong struct platform_device *pdev;
308338eab5SHui Chun Ong };
318338eab5SHui Chun Ong
328338eab5SHui Chun Ong struct nic78bx_led {
338338eab5SHui Chun Ong u8 bit;
348338eab5SHui Chun Ong u8 mask;
358338eab5SHui Chun Ong struct nic78bx_led_data *data;
368338eab5SHui Chun Ong struct led_classdev cdev;
378338eab5SHui Chun Ong };
388338eab5SHui Chun Ong
to_nic78bx_led(struct led_classdev * cdev)398338eab5SHui Chun Ong static inline struct nic78bx_led *to_nic78bx_led(struct led_classdev *cdev)
408338eab5SHui Chun Ong {
418338eab5SHui Chun Ong return container_of(cdev, struct nic78bx_led, cdev);
428338eab5SHui Chun Ong }
438338eab5SHui Chun Ong
nic78bx_brightness_set(struct led_classdev * cdev,enum led_brightness brightness)448338eab5SHui Chun Ong static void nic78bx_brightness_set(struct led_classdev *cdev,
458338eab5SHui Chun Ong enum led_brightness brightness)
468338eab5SHui Chun Ong {
478338eab5SHui Chun Ong struct nic78bx_led *nled = to_nic78bx_led(cdev);
488338eab5SHui Chun Ong unsigned long flags;
498338eab5SHui Chun Ong u8 value;
508338eab5SHui Chun Ong
518338eab5SHui Chun Ong spin_lock_irqsave(&nled->data->lock, flags);
528338eab5SHui Chun Ong value = inb(nled->data->io_base);
538338eab5SHui Chun Ong
548338eab5SHui Chun Ong if (brightness) {
558338eab5SHui Chun Ong value &= ~nled->mask;
568338eab5SHui Chun Ong value |= nled->bit;
578338eab5SHui Chun Ong } else {
588338eab5SHui Chun Ong value &= ~nled->bit;
598338eab5SHui Chun Ong }
608338eab5SHui Chun Ong
618338eab5SHui Chun Ong outb(value, nled->data->io_base);
628338eab5SHui Chun Ong spin_unlock_irqrestore(&nled->data->lock, flags);
638338eab5SHui Chun Ong }
648338eab5SHui Chun Ong
nic78bx_brightness_get(struct led_classdev * cdev)658338eab5SHui Chun Ong static enum led_brightness nic78bx_brightness_get(struct led_classdev *cdev)
668338eab5SHui Chun Ong {
678338eab5SHui Chun Ong struct nic78bx_led *nled = to_nic78bx_led(cdev);
688338eab5SHui Chun Ong unsigned long flags;
698338eab5SHui Chun Ong u8 value;
708338eab5SHui Chun Ong
718338eab5SHui Chun Ong spin_lock_irqsave(&nled->data->lock, flags);
728338eab5SHui Chun Ong value = inb(nled->data->io_base);
738338eab5SHui Chun Ong spin_unlock_irqrestore(&nled->data->lock, flags);
748338eab5SHui Chun Ong
758338eab5SHui Chun Ong return (value & nled->bit) ? 1 : LED_OFF;
768338eab5SHui Chun Ong }
778338eab5SHui Chun Ong
788338eab5SHui Chun Ong static struct nic78bx_led nic78bx_leds[] = {
798338eab5SHui Chun Ong {
808338eab5SHui Chun Ong .bit = NIC78BX_USER1_GREEN_LED,
818338eab5SHui Chun Ong .mask = NIC78BX_USER1_LED_MASK,
828338eab5SHui Chun Ong .cdev = {
838338eab5SHui Chun Ong .name = "nilrt:green:user1",
848338eab5SHui Chun Ong .max_brightness = 1,
858338eab5SHui Chun Ong .brightness_set = nic78bx_brightness_set,
868338eab5SHui Chun Ong .brightness_get = nic78bx_brightness_get,
878338eab5SHui Chun Ong }
888338eab5SHui Chun Ong },
898338eab5SHui Chun Ong {
908338eab5SHui Chun Ong .bit = NIC78BX_USER1_YELLOW_LED,
918338eab5SHui Chun Ong .mask = NIC78BX_USER1_LED_MASK,
928338eab5SHui Chun Ong .cdev = {
938338eab5SHui Chun Ong .name = "nilrt:yellow:user1",
948338eab5SHui Chun Ong .max_brightness = 1,
958338eab5SHui Chun Ong .brightness_set = nic78bx_brightness_set,
968338eab5SHui Chun Ong .brightness_get = nic78bx_brightness_get,
978338eab5SHui Chun Ong }
988338eab5SHui Chun Ong },
998338eab5SHui Chun Ong {
1008338eab5SHui Chun Ong .bit = NIC78BX_USER2_GREEN_LED,
1018338eab5SHui Chun Ong .mask = NIC78BX_USER2_LED_MASK,
1028338eab5SHui Chun Ong .cdev = {
1038338eab5SHui Chun Ong .name = "nilrt:green:user2",
1048338eab5SHui Chun Ong .max_brightness = 1,
1058338eab5SHui Chun Ong .brightness_set = nic78bx_brightness_set,
1068338eab5SHui Chun Ong .brightness_get = nic78bx_brightness_get,
1078338eab5SHui Chun Ong }
1088338eab5SHui Chun Ong },
1098338eab5SHui Chun Ong {
1108338eab5SHui Chun Ong .bit = NIC78BX_USER2_YELLOW_LED,
1118338eab5SHui Chun Ong .mask = NIC78BX_USER2_LED_MASK,
1128338eab5SHui Chun Ong .cdev = {
1138338eab5SHui Chun Ong .name = "nilrt:yellow:user2",
1148338eab5SHui Chun Ong .max_brightness = 1,
1158338eab5SHui Chun Ong .brightness_set = nic78bx_brightness_set,
1168338eab5SHui Chun Ong .brightness_get = nic78bx_brightness_get,
1178338eab5SHui Chun Ong }
1188338eab5SHui Chun Ong }
1198338eab5SHui Chun Ong };
1208338eab5SHui Chun Ong
nic78bx_probe(struct platform_device * pdev)1218338eab5SHui Chun Ong static int nic78bx_probe(struct platform_device *pdev)
1228338eab5SHui Chun Ong {
1238338eab5SHui Chun Ong struct device *dev = &pdev->dev;
1248338eab5SHui Chun Ong struct nic78bx_led_data *led_data;
1258338eab5SHui Chun Ong struct resource *io_rc;
1268338eab5SHui Chun Ong int ret, i;
1278338eab5SHui Chun Ong
1288338eab5SHui Chun Ong led_data = devm_kzalloc(dev, sizeof(*led_data), GFP_KERNEL);
1298338eab5SHui Chun Ong if (!led_data)
1308338eab5SHui Chun Ong return -ENOMEM;
1318338eab5SHui Chun Ong
1328338eab5SHui Chun Ong led_data->pdev = pdev;
1338338eab5SHui Chun Ong platform_set_drvdata(pdev, led_data);
1348338eab5SHui Chun Ong
1358338eab5SHui Chun Ong io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
1368338eab5SHui Chun Ong if (!io_rc) {
1378338eab5SHui Chun Ong dev_err(dev, "missing IO resources\n");
1388338eab5SHui Chun Ong return -EINVAL;
1398338eab5SHui Chun Ong }
1408338eab5SHui Chun Ong
1418338eab5SHui Chun Ong if (resource_size(io_rc) < NIC78BX_USER_LED_IO_SIZE) {
1428338eab5SHui Chun Ong dev_err(dev, "IO region too small\n");
1438338eab5SHui Chun Ong return -EINVAL;
1448338eab5SHui Chun Ong }
1458338eab5SHui Chun Ong
1468338eab5SHui Chun Ong if (!devm_request_region(dev, io_rc->start, resource_size(io_rc),
1478338eab5SHui Chun Ong KBUILD_MODNAME)) {
1488338eab5SHui Chun Ong dev_err(dev, "failed to get IO region\n");
1498338eab5SHui Chun Ong return -EBUSY;
1508338eab5SHui Chun Ong }
1518338eab5SHui Chun Ong
1528338eab5SHui Chun Ong led_data->io_base = io_rc->start;
1538338eab5SHui Chun Ong spin_lock_init(&led_data->lock);
1548338eab5SHui Chun Ong
1558338eab5SHui Chun Ong for (i = 0; i < ARRAY_SIZE(nic78bx_leds); i++) {
1568338eab5SHui Chun Ong nic78bx_leds[i].data = led_data;
1578338eab5SHui Chun Ong
1588338eab5SHui Chun Ong ret = devm_led_classdev_register(dev, &nic78bx_leds[i].cdev);
1598338eab5SHui Chun Ong if (ret)
1608338eab5SHui Chun Ong return ret;
1618338eab5SHui Chun Ong }
1628338eab5SHui Chun Ong
1638338eab5SHui Chun Ong /* Unlock LED register */
1648338eab5SHui Chun Ong outb(NIC78BX_UNLOCK_VALUE,
1658338eab5SHui Chun Ong led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
1668338eab5SHui Chun Ong
1678338eab5SHui Chun Ong return ret;
1688338eab5SHui Chun Ong }
1698338eab5SHui Chun Ong
nic78bx_remove(struct platform_device * pdev)1708338eab5SHui Chun Ong static int nic78bx_remove(struct platform_device *pdev)
1718338eab5SHui Chun Ong {
1728338eab5SHui Chun Ong struct nic78bx_led_data *led_data = platform_get_drvdata(pdev);
1738338eab5SHui Chun Ong
1748338eab5SHui Chun Ong /* Lock LED register */
1758338eab5SHui Chun Ong outb(NIC78BX_LOCK_VALUE,
1768338eab5SHui Chun Ong led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
1778338eab5SHui Chun Ong
1788338eab5SHui Chun Ong return 0;
1798338eab5SHui Chun Ong }
1808338eab5SHui Chun Ong
1818338eab5SHui Chun Ong static const struct acpi_device_id led_device_ids[] = {
1828338eab5SHui Chun Ong {"NIC78B3", 0},
1838338eab5SHui Chun Ong {"", 0},
1848338eab5SHui Chun Ong };
1858338eab5SHui Chun Ong MODULE_DEVICE_TABLE(acpi, led_device_ids);
1868338eab5SHui Chun Ong
1878338eab5SHui Chun Ong static struct platform_driver led_driver = {
1888338eab5SHui Chun Ong .probe = nic78bx_probe,
1898338eab5SHui Chun Ong .remove = nic78bx_remove,
1908338eab5SHui Chun Ong .driver = {
1918338eab5SHui Chun Ong .name = KBUILD_MODNAME,
1928338eab5SHui Chun Ong .acpi_match_table = ACPI_PTR(led_device_ids),
1938338eab5SHui Chun Ong },
1948338eab5SHui Chun Ong };
1958338eab5SHui Chun Ong
1968338eab5SHui Chun Ong module_platform_driver(led_driver);
1978338eab5SHui Chun Ong
1988338eab5SHui Chun Ong MODULE_DESCRIPTION("National Instruments PXI User LEDs driver");
1998338eab5SHui Chun Ong MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>");
2008338eab5SHui Chun Ong MODULE_LICENSE("GPL");
201