linux/drivers/leds/leds-adp5520.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * LEDs driver for Analog Devices ADP5520/ADP5501 MFD PMICs
   4 *
   5 * Copyright 2009 Analog Devices Inc.
   6 *
   7 * Loosely derived from leds-da903x:
   8 * Copyright (C) 2008 Compulab, Ltd.
   9 *      Mike Rapoport <mike@compulab.co.il>
  10 *
  11 * Copyright (C) 2006-2008 Marvell International Ltd.
  12 *      Eric Miao <eric.miao@marvell.com>
  13 */
  14
  15#include <linux/module.h>
  16#include <linux/kernel.h>
  17#include <linux/platform_device.h>
  18#include <linux/leds.h>
  19#include <linux/mfd/adp5520.h>
  20#include <linux/slab.h>
  21
  22struct adp5520_led {
  23        struct led_classdev     cdev;
  24        struct device           *master;
  25        int                     id;
  26        int                     flags;
  27};
  28
  29static int adp5520_led_set(struct led_classdev *led_cdev,
  30                           enum led_brightness value)
  31{
  32        struct adp5520_led *led;
  33
  34        led = container_of(led_cdev, struct adp5520_led, cdev);
  35        return adp5520_write(led->master, ADP5520_LED1_CURRENT + led->id - 1,
  36                         value >> 2);
  37}
  38
  39static int adp5520_led_setup(struct adp5520_led *led)
  40{
  41        struct device *dev = led->master;
  42        int flags = led->flags;
  43        int ret = 0;
  44
  45        switch (led->id) {
  46        case FLAG_ID_ADP5520_LED1_ADP5501_LED0:
  47                ret |= adp5520_set_bits(dev, ADP5520_LED_TIME,
  48                                        (flags >> ADP5520_FLAG_OFFT_SHIFT) &
  49                                        ADP5520_FLAG_OFFT_MASK);
  50                ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL,
  51                                        ADP5520_LED1_EN);
  52                break;
  53        case FLAG_ID_ADP5520_LED2_ADP5501_LED1:
  54                ret |= adp5520_set_bits(dev,  ADP5520_LED_TIME,
  55                                        ((flags >> ADP5520_FLAG_OFFT_SHIFT) &
  56                                        ADP5520_FLAG_OFFT_MASK) << 2);
  57                ret |= adp5520_clr_bits(dev, ADP5520_LED_CONTROL,
  58                                         ADP5520_R3_MODE);
  59                ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL,
  60                                        ADP5520_LED2_EN);
  61                break;
  62        case FLAG_ID_ADP5520_LED3_ADP5501_LED2:
  63                ret |= adp5520_set_bits(dev,  ADP5520_LED_TIME,
  64                                        ((flags >> ADP5520_FLAG_OFFT_SHIFT) &
  65                                        ADP5520_FLAG_OFFT_MASK) << 4);
  66                ret |= adp5520_clr_bits(dev, ADP5520_LED_CONTROL,
  67                                        ADP5520_C3_MODE);
  68                ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL,
  69                                        ADP5520_LED3_EN);
  70                break;
  71        }
  72
  73        return ret;
  74}
  75
  76static int adp5520_led_prepare(struct platform_device *pdev)
  77{
  78        struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
  79        struct device *dev = pdev->dev.parent;
  80        int ret = 0;
  81
  82        ret |= adp5520_write(dev, ADP5520_LED1_CURRENT, 0);
  83        ret |= adp5520_write(dev, ADP5520_LED2_CURRENT, 0);
  84        ret |= adp5520_write(dev, ADP5520_LED3_CURRENT, 0);
  85        ret |= adp5520_write(dev, ADP5520_LED_TIME, pdata->led_on_time << 6);
  86        ret |= adp5520_write(dev, ADP5520_LED_FADE, FADE_VAL(pdata->fade_in,
  87                 pdata->fade_out));
  88
  89        return ret;
  90}
  91
  92static int adp5520_led_probe(struct platform_device *pdev)
  93{
  94        struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
  95        struct adp5520_led *led, *led_dat;
  96        struct led_info *cur_led;
  97        int ret, i;
  98
  99        if (pdata == NULL) {
 100                dev_err(&pdev->dev, "missing platform data\n");
 101                return -ENODEV;
 102        }
 103
 104        if (pdata->num_leds > ADP5520_01_MAXLEDS) {
 105                dev_err(&pdev->dev, "can't handle more than %d LEDS\n",
 106                                 ADP5520_01_MAXLEDS);
 107                return -EFAULT;
 108        }
 109
 110        led = devm_kcalloc(&pdev->dev, pdata->num_leds, sizeof(*led),
 111                                GFP_KERNEL);
 112        if (!led)
 113                return -ENOMEM;
 114
 115        ret = adp5520_led_prepare(pdev);
 116        if (ret) {
 117                dev_err(&pdev->dev, "failed to write\n");
 118                return ret;
 119        }
 120
 121        for (i = 0; i < pdata->num_leds; ++i) {
 122                cur_led = &pdata->leds[i];
 123                led_dat = &led[i];
 124
 125                led_dat->cdev.name = cur_led->name;
 126                led_dat->cdev.default_trigger = cur_led->default_trigger;
 127                led_dat->cdev.brightness_set_blocking = adp5520_led_set;
 128                led_dat->cdev.brightness = LED_OFF;
 129
 130                if (cur_led->flags & ADP5520_FLAG_LED_MASK)
 131                        led_dat->flags = cur_led->flags;
 132                else
 133                        led_dat->flags = i + 1;
 134
 135                led_dat->id = led_dat->flags & ADP5520_FLAG_LED_MASK;
 136
 137                led_dat->master = pdev->dev.parent;
 138
 139                ret = led_classdev_register(led_dat->master, &led_dat->cdev);
 140                if (ret) {
 141                        dev_err(&pdev->dev, "failed to register LED %d\n",
 142                                led_dat->id);
 143                        goto err;
 144                }
 145
 146                ret = adp5520_led_setup(led_dat);
 147                if (ret) {
 148                        dev_err(&pdev->dev, "failed to write\n");
 149                        i++;
 150                        goto err;
 151                }
 152        }
 153
 154        platform_set_drvdata(pdev, led);
 155        return 0;
 156
 157err:
 158        if (i > 0) {
 159                for (i = i - 1; i >= 0; i--)
 160                        led_classdev_unregister(&led[i].cdev);
 161        }
 162
 163        return ret;
 164}
 165
 166static int adp5520_led_remove(struct platform_device *pdev)
 167{
 168        struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
 169        struct adp5520_led *led;
 170        int i;
 171
 172        led = platform_get_drvdata(pdev);
 173
 174        adp5520_clr_bits(led->master, ADP5520_LED_CONTROL,
 175                 ADP5520_LED1_EN | ADP5520_LED2_EN | ADP5520_LED3_EN);
 176
 177        for (i = 0; i < pdata->num_leds; i++) {
 178                led_classdev_unregister(&led[i].cdev);
 179        }
 180
 181        return 0;
 182}
 183
 184static struct platform_driver adp5520_led_driver = {
 185        .driver = {
 186                .name   = "adp5520-led",
 187        },
 188        .probe          = adp5520_led_probe,
 189        .remove         = adp5520_led_remove,
 190};
 191
 192module_platform_driver(adp5520_led_driver);
 193
 194MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
 195MODULE_DESCRIPTION("LEDS ADP5520(01) Driver");
 196MODULE_LICENSE("GPL");
 197MODULE_ALIAS("platform:adp5520-led");
 198