linux/drivers/leds/leds-bcm6358.c
Thomas Gleixner 2874c5fd28 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152
Based on 1 normalized pattern(s):

  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

extracted by the scancode license scanner the SPDX license identifier

  GPL-2.0-or-later

has been chosen to replace the boilerplate/reference in 3029 file(s).

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Allison Randal <allison@lohutok.net>
Cc: linux-spdx@vger.kernel.org
Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-30 11:26:32 -07:00

237 lines
5.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c
*
* Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
*/
#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)
{
#ifdef CONFIG_CPU_BIG_ENDIAN
iowrite32be(data, reg);
#else
writel(data, reg);
#endif
}
static unsigned long bcm6358_led_read(void __iomem *reg)
{
#ifdef CONFIG_CPU_BIG_ENDIAN
return ioread32be(reg);
#else
return readl(reg);
#endif
}
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_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, val;
spin_lock_irqsave(led->lock, flags);
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);
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;
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);
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;
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_set(&led->cdev, led->cdev.brightness);
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) {
of_node_put(child);
return rc;
}
}
return 0;
}
static const struct of_device_id bcm6358_leds_of_match[] = {
{ .compatible = "brcm,bcm6358-leds", },
{ },
};
MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match);
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");