xref: /openbmc/linux/drivers/leds/leds-nic78bx.c (revision c942fddf)
1c942fddfSThomas 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