瀏覽代碼

leds: add BCM6358 LED driver

This adds support for the LED controller on Broadcom's BCM6358.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
Acked-by: Jacek Anaszewski <j.anaszewski@samsung.com>
Signed-off-by: Bryan Wu <cooloney@gmail.com>
Álvaro Fernández Rojas 10 年之前
父節點
當前提交
589fca16c1
共有 3 個文件被更改,包括 252 次插入0 次删除
  1. 8 0
      drivers/leds/Kconfig
  2. 1 0
      drivers/leds/Makefile
  3. 243 0
      drivers/leds/leds-bcm6358.c

+ 8 - 0
drivers/leds/Kconfig

@@ -58,6 +58,14 @@ config LEDS_BCM6328
 	  This option enables support for LEDs connected to the BCM6328
 	  This option enables support for LEDs connected to the BCM6328
 	  LED HW controller accessed via MMIO registers.
 	  LED HW controller accessed via MMIO registers.
 
 
+config LEDS_BCM6358
+	tristate "LED Support for Broadcom BCM6358"
+	depends on LEDS_CLASS
+	depends on OF
+	help
+	  This option enables support for LEDs connected to the BCM6358
+	  LED HW controller accessed via MMIO registers.
+
 config LEDS_LM3530
 config LEDS_LM3530
 	tristate "LCD Backlight driver for LM3530"
 	tristate "LCD Backlight driver for LM3530"
 	depends on LEDS_CLASS
 	depends on LEDS_CLASS

+ 1 - 0
drivers/leds/Makefile

@@ -9,6 +9,7 @@ obj-$(CONFIG_LEDS_TRIGGERS)		+= led-triggers.o
 obj-$(CONFIG_LEDS_88PM860X)		+= leds-88pm860x.o
 obj-$(CONFIG_LEDS_88PM860X)		+= leds-88pm860x.o
 obj-$(CONFIG_LEDS_AAT1290)		+= leds-aat1290.o
 obj-$(CONFIG_LEDS_AAT1290)		+= leds-aat1290.o
 obj-$(CONFIG_LEDS_BCM6328)		+= leds-bcm6328.o
 obj-$(CONFIG_LEDS_BCM6328)		+= leds-bcm6328.o
+obj-$(CONFIG_LEDS_BCM6358)		+= leds-bcm6358.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o

+ 243 - 0
drivers/leds/leds-bcm6358.c

@@ -0,0 +1,243 @@
+/*
+ * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c
+ *
+ * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
+ *
+ * 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.
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BCM6358_REG_MODE		0x0
+#define BCM6358_REG_CTRL		0x4
+
+#define BCM6358_SLED_CLKDIV_MASK	3
+#define BCM6358_SLED_CLKDIV_1		0
+#define BCM6358_SLED_CLKDIV_2		1
+#define BCM6358_SLED_CLKDIV_4		2
+#define BCM6358_SLED_CLKDIV_8		3
+
+#define BCM6358_SLED_POLARITY		BIT(2)
+#define BCM6358_SLED_BUSY		BIT(3)
+
+#define BCM6358_SLED_MAX_COUNT		32
+#define BCM6358_SLED_WAIT		100
+
+/**
+ * struct bcm6358_led - state container for bcm6358 based LEDs
+ * @cdev: LED class device for this LED
+ * @mem: memory resource
+ * @lock: memory lock
+ * @pin: LED pin number
+ * @active_low: LED is active low
+ */
+struct bcm6358_led {
+	struct led_classdev cdev;
+	void __iomem *mem;
+	spinlock_t *lock;
+	unsigned long pin;
+	bool active_low;
+};
+
+static void bcm6358_led_write(void __iomem *reg, unsigned long data)
+{
+	iowrite32be(data, reg);
+}
+
+static unsigned long bcm6358_led_read(void __iomem *reg)
+{
+	return ioread32be(reg);
+}
+
+static unsigned long bcm6358_led_busy(void __iomem *mem)
+{
+	unsigned long val;
+
+	while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) &
+		BCM6358_SLED_BUSY)
+		udelay(BCM6358_SLED_WAIT);
+
+	return val;
+}
+
+static void bcm6358_led_mode(struct bcm6358_led *led, unsigned long value)
+{
+	unsigned long val;
+
+	bcm6358_led_busy(led->mem);
+
+	val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
+	if ((led->active_low && value == LED_OFF) ||
+	    (!led->active_low && value != LED_OFF))
+		val |= BIT(led->pin);
+	else
+		val &= ~(BIT(led->pin));
+	bcm6358_led_write(led->mem + BCM6358_REG_MODE, val);
+}
+
+static void bcm6358_led_set(struct led_classdev *led_cdev,
+			    enum led_brightness value)
+{
+	struct bcm6358_led *led =
+		container_of(led_cdev, struct bcm6358_led, cdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(led->lock, flags);
+	bcm6358_led_mode(led, value);
+	spin_unlock_irqrestore(led->lock, flags);
+}
+
+static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg,
+		       void __iomem *mem, spinlock_t *lock)
+{
+	struct bcm6358_led *led;
+	unsigned long flags;
+	const char *state;
+	int rc;
+
+	led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->pin = reg;
+	led->mem = mem;
+	led->lock = lock;
+
+	if (of_property_read_bool(nc, "active-low"))
+		led->active_low = true;
+
+	led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name;
+	led->cdev.default_trigger = of_get_property(nc,
+						    "linux,default-trigger",
+						    NULL);
+
+	spin_lock_irqsave(lock, flags);
+	if (!of_property_read_string(nc, "default-state", &state)) {
+		if (!strcmp(state, "on")) {
+			led->cdev.brightness = LED_FULL;
+		} else if (!strcmp(state, "keep")) {
+			unsigned long val;
+
+			bcm6358_led_busy(led->mem);
+
+			val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
+			val &= BIT(led->pin);
+			if ((led->active_low && !val) ||
+			    (!led->active_low && val))
+				led->cdev.brightness = LED_FULL;
+			else
+				led->cdev.brightness = LED_OFF;
+		} else {
+			led->cdev.brightness = LED_OFF;
+		}
+	} else {
+		led->cdev.brightness = LED_OFF;
+	}
+	bcm6358_led_mode(led, led->cdev.brightness);
+	spin_unlock_irqrestore(lock, flags);
+
+	led->cdev.brightness_set = bcm6358_led_set;
+
+	rc = led_classdev_register(dev, &led->cdev);
+	if (rc < 0)
+		return rc;
+
+	dev_dbg(dev, "registered LED %s\n", led->cdev.name);
+
+	return 0;
+}
+
+static int bcm6358_leds_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *child;
+	struct resource *mem_r;
+	void __iomem *mem;
+	spinlock_t *lock; /* memory lock */
+	unsigned long val;
+	u32 clk_div;
+
+	mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem_r)
+		return -EINVAL;
+
+	mem = devm_ioremap_resource(dev, mem_r);
+	if (IS_ERR(mem))
+		return PTR_ERR(mem);
+
+	lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
+	if (!lock)
+		return -ENOMEM;
+
+	spin_lock_init(lock);
+
+	val = bcm6358_led_busy(mem);
+	val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK);
+	if (of_property_read_bool(np, "brcm,clk-dat-low"))
+		val |= BCM6358_SLED_POLARITY;
+	of_property_read_u32(np, "brcm,clk-div", &clk_div);
+	switch (clk_div) {
+	case 8:
+		val |= BCM6358_SLED_CLKDIV_8;
+		break;
+	case 4:
+		val |= BCM6358_SLED_CLKDIV_4;
+		break;
+	case 2:
+		val |= BCM6358_SLED_CLKDIV_2;
+		break;
+	default:
+		val |= BCM6358_SLED_CLKDIV_1;
+		break;
+	}
+	bcm6358_led_write(mem + BCM6358_REG_CTRL, val);
+
+	for_each_available_child_of_node(np, child) {
+		int rc;
+		u32 reg;
+
+		if (of_property_read_u32(child, "reg", &reg))
+			continue;
+
+		if (reg >= BCM6358_SLED_MAX_COUNT) {
+			dev_err(dev, "invalid LED (%u >= %d)\n", reg,
+				BCM6358_SLED_MAX_COUNT);
+			continue;
+		}
+
+		rc = bcm6358_led(dev, child, reg, mem, lock);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id bcm6358_leds_of_match[] = {
+	{ .compatible = "brcm,bcm6358-leds", },
+	{ },
+};
+
+static struct platform_driver bcm6358_leds_driver = {
+	.probe = bcm6358_leds_probe,
+	.driver = {
+		.name = "leds-bcm6358",
+		.of_match_table = bcm6358_leds_of_match,
+	},
+};
+
+module_platform_driver(bcm6358_leds_driver);
+
+MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
+MODULE_DESCRIPTION("LED driver for BCM6358 controllers");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-bcm6358");