@@ -122,6 +122,17 @@ config LEDS_BCM6358
This option enables support for LEDs connected to the BCM6358
LED HW controller accessed via MMIO registers.
+config LEDS_BCM63XXX
+ tristate "LED Support for Broadcom BCM63xxx SoCs"
+ depends on LEDS_CLASS
+ depends on ARCH_BCM4908 || BCM63XX || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ default ARCH_BCM4908
+ help
+ This option enables support for LED controller that is part of
+ BCM63xxx family SoCs.
+
config LEDS_CPCAP
tristate "LED Support for Motorola CPCAP"
depends on LEDS_CLASS
@@ -18,6 +18,7 @@ obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
+obj-$(CONFIG_LEDS_BCM63XXX) += leds-bcm63xxx.o
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
new file mode 100644
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BCM63XXX_MAX_LEDS 32
+
+#define BCM63XXX_GLB_CTRL 0x00
+#define BCM63XXX_MASK 0x04
+#define BCM63XXX_HW_LED_EN 0x08
+#define BCM63XXX_SERIAL_LED_SHIFT_SEL 0x0c
+#define BCM63XXX_FLASH_RATE_CTRL1 0x10
+#define BCM63XXX_FLASH_RATE_CTRL2 0x14
+#define BCM63XXX_FLASH_RATE_CTRL3 0x18
+#define BCM63XXX_FLASH_RATE_CTRL4 0x1c
+#define BCM63XXX_BRIGHT_CTRL1 0x20
+#define BCM63XXX_BRIGHT_CTRL2 0x24
+#define BCM63XXX_BRIGHT_CTRL3 0x28
+#define BCM63XXX_BRIGHT_CTRL4 0x2c
+#define BCM63XXX_POWER_LED_CFG 0x30
+#define BCM63XXX_HW_POLARITY 0xb4
+#define BCM63XXX_SW_DATA 0xb8
+#define BCM63XXX_SW_POLARITY 0xbc
+#define BCM63XXX_PARALLEL_LED_POLARITY 0xc0
+#define BCM63XXX_SERIAL_LED_POLARITY 0xc4
+#define BCM63XXX_HW_LED_STATUS 0xc8
+#define BCM63XXX_FLASH_CTRL_STATUS 0xcc
+#define BCM63XXX_FLASH_BRT_CTRL 0xd0
+#define BCM63XXX_FLASH_P_LED_OUT_STATUS 0xd4
+#define BCM63XXX_FLASH_S_LED_OUT_STATUS 0xd8
+
+struct bcm63xxx_leds {
+ struct device *dev;
+ void __iomem *base;
+ spinlock_t lock;
+};
+
+struct bcm63xxx_led {
+ struct bcm63xxx_leds *leds;
+ struct led_classdev cdev;
+ u32 pin;
+ bool active_low;
+};
+
+/*
+ * I/O access
+ */
+
+static void bcm63xxx_leds_write(struct bcm63xxx_leds *leds, unsigned int reg,
+ u32 data)
+{
+ writel(data, leds->base + reg);
+}
+
+static unsigned long bcm63xxx_leds_read(struct bcm63xxx_leds *leds,
+ unsigned int reg)
+{
+ return readl(leds->base + reg);
+}
+
+static void bcm63xxx_leds_update_bits(struct bcm63xxx_leds *leds,
+ unsigned int reg, u32 mask, u32 val)
+{
+ WARN_ON(val & ~mask);
+
+ bcm63xxx_leds_write(leds, reg, (bcm63xxx_leds_read(leds, reg) & ~mask) | (val & mask));
+}
+
+/*
+ * Helpers
+ */
+
+static void bcm63xxx_leds_set_flash_rate(struct bcm63xxx_leds *leds,
+ struct bcm63xxx_led *led,
+ u8 value)
+{
+ int reg_offset = (led->pin >> 3) * 4;
+ int shift = (led->pin & 0xf) * 4;
+
+ bcm63xxx_leds_update_bits(leds, BCM63XXX_FLASH_RATE_CTRL1 + reg_offset,
+ 0xf << shift, value << shift);
+}
+
+static void bcm63xxx_leds_enable_led(struct bcm63xxx_leds *leds,
+ struct bcm63xxx_led *led,
+ enum led_brightness value)
+{
+ u32 bit = BIT(led->pin);
+
+ bcm63xxx_leds_update_bits(leds, BCM63XXX_SW_DATA, bit,
+ value == LED_OFF ? 0 : bit);
+}
+
+/*
+ * API callbacks
+ */
+
+static void bcm63xxx_leds_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct bcm63xxx_led *led = container_of(led_cdev, struct bcm63xxx_led, cdev);
+ struct bcm63xxx_leds *leds = led->leds;
+ unsigned long flags;
+
+ spin_lock_irqsave(&leds->lock, flags);
+
+ bcm63xxx_leds_enable_led(leds, led, value);
+ if (value == LED_OFF)
+ bcm63xxx_leds_set_flash_rate(leds, led, 0);
+
+ spin_unlock_irqrestore(&leds->lock, flags);
+}
+
+static int bcm63xxx_leds_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct bcm63xxx_led *led = container_of(led_cdev, struct bcm63xxx_led, cdev);
+ struct bcm63xxx_leds *leds = led->leds;
+ unsigned long flags;
+ u8 value;
+
+ if (!*delay_on && !*delay_off) {
+ *delay_on = 640;
+ *delay_off = 640;
+ }
+
+ if (*delay_on != *delay_off) {
+ dev_dbg(led_cdev->dev, "Blinking at unequal delays is not supported\n");
+ return -EINVAL;
+ }
+
+ switch (*delay_on) {
+ case 1152 ... 1408: /* 1280 ms ± 10% */
+ value = 0x7;
+ break;
+ case 576 ... 704: /* 640 ms ± 10% */
+ value = 0x6;
+ break;
+ case 288 ... 352: /* 320 ms ± 10% */
+ value = 0x5;
+ break;
+ case 126 ... 154: /* 140 ms ± 10% */
+ value = 0x4;
+ break;
+ case 59 ... 72: /* 65 ms ± 10% */
+ value = 0x3;
+ break;
+ default:
+ dev_dbg(led_cdev->dev, "Blinking delay value %lu is unsupported\n",
+ *delay_on);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&leds->lock, flags);
+
+ bcm63xxx_leds_enable_led(leds, led, LED_FULL);
+ bcm63xxx_leds_set_flash_rate(leds, led, value);
+
+ spin_unlock_irqrestore(&leds->lock, flags);
+
+ return 0;
+}
+
+/*
+ * LED driver
+ */
+
+static int bcm63xxx_leds_create_led(struct bcm63xxx_leds *leds, struct device_node *np)
+{
+ struct led_init_data init_data = {
+ .fwnode = of_fwnode_handle(np),
+ };
+ struct device *dev = leds->dev;
+ struct bcm63xxx_led *led;
+ struct pinctrl *pinctrl;
+ const char *state;
+ u32 bit;
+ int err;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->leds = leds;
+
+ if (of_property_read_u32(np, "reg", &led->pin)) {
+ dev_err(dev, "Missing \"reg\" property in %pOF\n", np);
+ err = -ENOENT;
+ goto err_out;
+ }
+
+ if (led->pin >= BCM63XXX_MAX_LEDS) {
+ dev_err(dev, "Invalid \"reg\" value %d\n", led->pin);
+ err = -EINVAL;
+ goto err_out;
+ }
+
+ led->active_low = of_property_read_bool(np, "active-low");
+
+ if (!of_property_read_string(np, "default-state", &state)) {
+ if (!strcmp(state, "on"))
+ led->cdev.brightness = LED_FULL;
+ else
+ led->cdev.brightness = LED_OFF;
+ } else {
+ led->cdev.brightness = LED_OFF;
+ }
+
+ led->cdev.brightness_set = bcm63xxx_leds_brightness_set;
+ led->cdev.blink_set = bcm63xxx_leds_blink_set;
+
+ err = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (err) {
+ dev_err(dev, "Failed to register LED %pOF: %d\n", np, err);
+ goto err_out;
+ }
+
+ pinctrl = devm_pinctrl_get_select_default(led->cdev.dev);
+ if (IS_ERR(pinctrl) && PTR_ERR(pinctrl) != -ENODEV) {
+ dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %ld\n",
+ np, PTR_ERR(pinctrl));
+ }
+
+ bit = BIT(led->pin);
+ bcm63xxx_leds_update_bits(leds, BCM63XXX_PARALLEL_LED_POLARITY, bit,
+ led->active_low ? 0 : bit);
+ bcm63xxx_leds_update_bits(leds, BCM63XXX_HW_LED_EN, bit, 0);
+ bcm63xxx_leds_set_flash_rate(leds, led, 0);
+ bcm63xxx_leds_enable_led(leds, led, led->cdev.brightness);
+
+ return 0;
+
+err_out:
+ devm_kfree(dev, led);
+ return err;
+}
+
+static int bcm63xxx_leds_probe(struct platform_device *pdev)
+{
+ struct device_node *np = dev_of_node(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ struct bcm63xxx_leds *leds;
+ struct device_node *child;
+
+ leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ leds->dev = dev;
+
+ leds->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(leds->base))
+ return PTR_ERR(leds->base);
+
+ spin_lock_init(&leds->lock);
+
+ bcm63xxx_leds_write(leds, BCM63XXX_GLB_CTRL, 0xa);
+ bcm63xxx_leds_write(leds, BCM63XXX_BRIGHT_CTRL1, 0x88888888);
+ bcm63xxx_leds_write(leds, BCM63XXX_BRIGHT_CTRL2, 0x88888888);
+ bcm63xxx_leds_write(leds, BCM63XXX_BRIGHT_CTRL3, 0x88888888);
+ bcm63xxx_leds_write(leds, BCM63XXX_BRIGHT_CTRL4, 0x88888888);
+ bcm63xxx_leds_write(leds, BCM63XXX_HW_LED_EN, 0);
+ bcm63xxx_leds_write(leds, BCM63XXX_SERIAL_LED_POLARITY, 0);
+ bcm63xxx_leds_write(leds, BCM63XXX_PARALLEL_LED_POLARITY, 0);
+
+ for_each_available_child_of_node(np, child) {
+ bcm63xxx_leds_create_led(leds, child);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id bcm63xxx_leds_of_match_table[] = {
+ { .compatible = "brcm,bcm63xxx-leds", },
+ { },
+};
+
+static struct platform_driver bcm63xxx_leds_driver = {
+ .probe = bcm63xxx_leds_probe,
+ .driver = {
+ .name = "leds-bcm63xxx",
+ .of_match_table = bcm63xxx_leds_of_match_table,
+ },
+};
+
+module_platform_driver(bcm63xxx_leds_driver);
+
+MODULE_AUTHOR("Rafał Miłecki");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(of, bcm63xxx_leds_of_match_table);