mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-28 11:49:32 +00:00
![Thomas Gleixner](/assets/img/avatar_default.png)
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>
237 lines
5.4 KiB
C
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", ®))
|
|
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");
|