linux/drivers/leds/leds-syscon.c
<<
>>
Prefs
   1/*
   2 * Generic Syscon LEDs Driver
   3 *
   4 * Copyright (c) 2014, Linaro Limited
   5 * Author: Linus Walleij <linus.walleij@linaro.org>
   6 *
   7 * This program is free software; you can redistribute it and/or
   8 * modify it under the terms of the GNU General Public License as
   9 * published by the Free Software Foundation; either version 2 of
  10 * the License, or (at your option) any later version.
  11 *
  12 * This program is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 * GNU General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU General Public License
  18 * along with this program; if not, write to the Free Software
  19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  20 * MA 02111-1307 USA
  21 */
  22#include <linux/io.h>
  23#include <linux/init.h>
  24#include <linux/of_device.h>
  25#include <linux/of_address.h>
  26#include <linux/platform_device.h>
  27#include <linux/stat.h>
  28#include <linux/slab.h>
  29#include <linux/mfd/syscon.h>
  30#include <linux/regmap.h>
  31#include <linux/leds.h>
  32
  33/**
  34 * struct syscon_led - state container for syscon based LEDs
  35 * @cdev: LED class device for this LED
  36 * @map: regmap to access the syscon device backing this LED
  37 * @offset: the offset into the syscon regmap for the LED register
  38 * @mask: the bit in the register corresponding to the LED
  39 * @state: current state of the LED
  40 */
  41struct syscon_led {
  42        struct led_classdev cdev;
  43        struct regmap *map;
  44        u32 offset;
  45        u32 mask;
  46        bool state;
  47};
  48
  49static void syscon_led_set(struct led_classdev *led_cdev,
  50        enum led_brightness value)
  51{
  52        struct syscon_led *sled =
  53                container_of(led_cdev, struct syscon_led, cdev);
  54        u32 val;
  55        int ret;
  56
  57        if (value == LED_OFF) {
  58                val = 0;
  59                sled->state = false;
  60        } else {
  61                val = sled->mask;
  62                sled->state = true;
  63        }
  64
  65        ret = regmap_update_bits(sled->map, sled->offset, sled->mask, val);
  66        if (ret < 0)
  67                dev_err(sled->cdev.dev, "error updating LED status\n");
  68}
  69
  70static int syscon_led_probe(struct platform_device *pdev)
  71{
  72        struct device *dev = &pdev->dev;
  73        struct device_node *np = dev->of_node;
  74        struct device *parent;
  75        struct regmap *map;
  76        struct syscon_led *sled;
  77        const char *state;
  78        int ret;
  79
  80        parent = dev->parent;
  81        if (!parent) {
  82                dev_err(dev, "no parent for syscon LED\n");
  83                return -ENODEV;
  84        }
  85        map = syscon_node_to_regmap(parent->of_node);
  86        if (IS_ERR(map)) {
  87                dev_err(dev, "no regmap for syscon LED parent\n");
  88                return PTR_ERR(map);
  89        }
  90
  91        sled = devm_kzalloc(dev, sizeof(*sled), GFP_KERNEL);
  92        if (!sled)
  93                return -ENOMEM;
  94
  95        sled->map = map;
  96
  97        if (of_property_read_u32(np, "offset", &sled->offset))
  98                return -EINVAL;
  99        if (of_property_read_u32(np, "mask", &sled->mask))
 100                return -EINVAL;
 101        sled->cdev.name =
 102                of_get_property(np, "label", NULL) ? : np->name;
 103        sled->cdev.default_trigger =
 104                of_get_property(np, "linux,default-trigger", NULL);
 105
 106        state = of_get_property(np, "default-state", NULL);
 107        if (state) {
 108                if (!strcmp(state, "keep")) {
 109                        u32 val;
 110
 111                        ret = regmap_read(map, sled->offset, &val);
 112                        if (ret < 0)
 113                                return ret;
 114                        sled->state = !!(val & sled->mask);
 115                } else if (!strcmp(state, "on")) {
 116                        sled->state = true;
 117                        ret = regmap_update_bits(map, sled->offset,
 118                                                 sled->mask,
 119                                                 sled->mask);
 120                        if (ret < 0)
 121                                return ret;
 122                } else {
 123                        sled->state = false;
 124                        ret = regmap_update_bits(map, sled->offset,
 125                                                 sled->mask, 0);
 126                        if (ret < 0)
 127                                return ret;
 128                }
 129        }
 130        sled->cdev.brightness_set = syscon_led_set;
 131
 132        ret = led_classdev_register(dev, &sled->cdev);
 133        if (ret < 0)
 134                return ret;
 135
 136        platform_set_drvdata(pdev, sled);
 137        dev_info(dev, "registered LED %s\n", sled->cdev.name);
 138
 139        return 0;
 140}
 141
 142static const struct of_device_id of_syscon_leds_match[] = {
 143        { .compatible = "register-bit-led", },
 144        {},
 145};
 146
 147static struct platform_driver syscon_led_driver = {
 148        .probe          = syscon_led_probe,
 149        .driver         = {
 150                .name   = "leds-syscon",
 151                .of_match_table = of_syscon_leds_match,
 152                .suppress_bind_attrs = true,
 153        },
 154};
 155builtin_platform_driver(syscon_led_driver);
 156