227 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c4 *5 * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>6 */7#include <linux/delay.h>8#include <linux/io.h>9#include <linux/leds.h>10#include <linux/module.h>11#include <linux/of.h>12#include <linux/platform_device.h>13#include <linux/spinlock.h>14 15#define BCM6358_REG_MODE 0x016#define BCM6358_REG_CTRL 0x417 18#define BCM6358_SLED_CLKDIV_MASK 319#define BCM6358_SLED_CLKDIV_1 020#define BCM6358_SLED_CLKDIV_2 121#define BCM6358_SLED_CLKDIV_4 222#define BCM6358_SLED_CLKDIV_8 323 24#define BCM6358_SLED_POLARITY BIT(2)25#define BCM6358_SLED_BUSY BIT(3)26 27#define BCM6358_SLED_MAX_COUNT 3228#define BCM6358_SLED_WAIT 10029 30/**31 * struct bcm6358_led - state container for bcm6358 based LEDs32 * @cdev: LED class device for this LED33 * @mem: memory resource34 * @lock: memory lock35 * @pin: LED pin number36 * @active_low: LED is active low37 */38struct bcm6358_led {39 struct led_classdev cdev;40 void __iomem *mem;41 spinlock_t *lock;42 unsigned long pin;43 bool active_low;44};45 46static void bcm6358_led_write(void __iomem *reg, unsigned long data)47{48#ifdef CONFIG_CPU_BIG_ENDIAN49 iowrite32be(data, reg);50#else51 writel(data, reg);52#endif53}54 55static unsigned long bcm6358_led_read(void __iomem *reg)56{57#ifdef CONFIG_CPU_BIG_ENDIAN58 return ioread32be(reg);59#else60 return readl(reg);61#endif62}63 64static unsigned long bcm6358_led_busy(void __iomem *mem)65{66 unsigned long val;67 68 while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) &69 BCM6358_SLED_BUSY)70 udelay(BCM6358_SLED_WAIT);71 72 return val;73}74 75static void bcm6358_led_set(struct led_classdev *led_cdev,76 enum led_brightness value)77{78 struct bcm6358_led *led =79 container_of(led_cdev, struct bcm6358_led, cdev);80 unsigned long flags, val;81 82 spin_lock_irqsave(led->lock, flags);83 bcm6358_led_busy(led->mem);84 val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);85 if ((led->active_low && value == LED_OFF) ||86 (!led->active_low && value != LED_OFF))87 val |= BIT(led->pin);88 else89 val &= ~(BIT(led->pin));90 bcm6358_led_write(led->mem + BCM6358_REG_MODE, val);91 spin_unlock_irqrestore(led->lock, flags);92}93 94static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg,95 void __iomem *mem, spinlock_t *lock)96{97 struct led_init_data init_data = {};98 struct bcm6358_led *led;99 enum led_default_state state;100 unsigned long val;101 int rc;102 103 led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);104 if (!led)105 return -ENOMEM;106 107 led->pin = reg;108 led->mem = mem;109 led->lock = lock;110 111 if (of_property_read_bool(nc, "active-low"))112 led->active_low = true;113 114 init_data.fwnode = of_fwnode_handle(nc);115 116 state = led_init_default_state_get(init_data.fwnode);117 switch (state) {118 case LEDS_DEFSTATE_ON:119 led->cdev.brightness = LED_FULL;120 break;121 case LEDS_DEFSTATE_KEEP:122 val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);123 val &= BIT(led->pin);124 if ((led->active_low && !val) || (!led->active_low && val))125 led->cdev.brightness = LED_FULL;126 else127 led->cdev.brightness = LED_OFF;128 break;129 default:130 led->cdev.brightness = LED_OFF;131 }132 133 bcm6358_led_set(&led->cdev, led->cdev.brightness);134 135 led->cdev.brightness_set = bcm6358_led_set;136 137 rc = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);138 if (rc < 0)139 return rc;140 141 dev_dbg(dev, "registered LED %s\n", led->cdev.name);142 143 return 0;144}145 146static int bcm6358_leds_probe(struct platform_device *pdev)147{148 struct device *dev = &pdev->dev;149 struct device_node *np = dev_of_node(&pdev->dev);150 void __iomem *mem;151 spinlock_t *lock; /* memory lock */152 unsigned long val;153 u32 clk_div;154 155 mem = devm_platform_ioremap_resource(pdev, 0);156 if (IS_ERR(mem))157 return PTR_ERR(mem);158 159 lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);160 if (!lock)161 return -ENOMEM;162 163 spin_lock_init(lock);164 165 val = bcm6358_led_busy(mem);166 val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK);167 if (of_property_read_bool(np, "brcm,clk-dat-low"))168 val |= BCM6358_SLED_POLARITY;169 of_property_read_u32(np, "brcm,clk-div", &clk_div);170 switch (clk_div) {171 case 8:172 val |= BCM6358_SLED_CLKDIV_8;173 break;174 case 4:175 val |= BCM6358_SLED_CLKDIV_4;176 break;177 case 2:178 val |= BCM6358_SLED_CLKDIV_2;179 break;180 default:181 val |= BCM6358_SLED_CLKDIV_1;182 break;183 }184 bcm6358_led_write(mem + BCM6358_REG_CTRL, val);185 186 for_each_available_child_of_node_scoped(np, child) {187 int rc;188 u32 reg;189 190 if (of_property_read_u32(child, "reg", ®))191 continue;192 193 if (reg >= BCM6358_SLED_MAX_COUNT) {194 dev_err(dev, "invalid LED (%u >= %d)\n", reg,195 BCM6358_SLED_MAX_COUNT);196 continue;197 }198 199 rc = bcm6358_led(dev, child, reg, mem, lock);200 if (rc < 0)201 return rc;202 }203 204 return 0;205}206 207static const struct of_device_id bcm6358_leds_of_match[] = {208 { .compatible = "brcm,bcm6358-leds", },209 { },210};211MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match);212 213static struct platform_driver bcm6358_leds_driver = {214 .probe = bcm6358_leds_probe,215 .driver = {216 .name = "leds-bcm6358",217 .of_match_table = bcm6358_leds_of_match,218 },219};220 221module_platform_driver(bcm6358_leds_driver);222 223MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");224MODULE_DESCRIPTION("LED driver for BCM6358 controllers");225MODULE_LICENSE("GPL v2");226MODULE_ALIAS("platform:leds-bcm6358");227