linux/drivers/leds/leds-88pm860x.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * LED driver for Marvell 88PM860x
   4 *
   5 * Copyright (C) 2009 Marvell International Ltd.
   6 *      Haojian Zhuang <haojian.zhuang@marvell.com>
   7 */
   8
   9#include <linux/kernel.h>
  10#include <linux/of.h>
  11#include <linux/platform_device.h>
  12#include <linux/i2c.h>
  13#include <linux/leds.h>
  14#include <linux/slab.h>
  15#include <linux/mfd/88pm860x.h>
  16#include <linux/module.h>
  17
  18#define LED_PWM_MASK            (0x1F)
  19#define LED_CURRENT_MASK        (0x07 << 5)
  20
  21#define LED_BLINK_MASK          (0x7F)
  22
  23#define LED_ON_CONTINUOUS       (0x0F << 3)
  24
  25#define LED1_BLINK_EN           (1 << 1)
  26#define LED2_BLINK_EN           (1 << 2)
  27
  28struct pm860x_led {
  29        struct led_classdev cdev;
  30        struct i2c_client *i2c;
  31        struct pm860x_chip *chip;
  32        struct mutex lock;
  33        char name[MFD_NAME_SIZE];
  34
  35        int port;
  36        int iset;
  37        unsigned char brightness;
  38        unsigned char current_brightness;
  39
  40        int reg_control;
  41        int reg_blink;
  42        int blink_mask;
  43};
  44
  45static int led_power_set(struct pm860x_chip *chip, int port, int on)
  46{
  47        int ret = -EINVAL;
  48
  49        switch (port) {
  50        case 0:
  51        case 1:
  52        case 2:
  53                ret = on ? pm8606_osc_enable(chip, RGB1_ENABLE) :
  54                        pm8606_osc_disable(chip, RGB1_ENABLE);
  55                break;
  56        case 3:
  57        case 4:
  58        case 5:
  59                ret = on ? pm8606_osc_enable(chip, RGB2_ENABLE) :
  60                        pm8606_osc_disable(chip, RGB2_ENABLE);
  61                break;
  62        }
  63        return ret;
  64}
  65
  66static int pm860x_led_set(struct led_classdev *cdev,
  67                           enum led_brightness value)
  68{
  69        struct pm860x_led *led = container_of(cdev, struct pm860x_led, cdev);
  70        struct pm860x_chip *chip;
  71        unsigned char buf[3];
  72        int ret;
  73
  74        chip = led->chip;
  75        mutex_lock(&led->lock);
  76        led->brightness = value >> 3;
  77
  78        if ((led->current_brightness == 0) && led->brightness) {
  79                led_power_set(chip, led->port, 1);
  80                if (led->iset) {
  81                        pm860x_set_bits(led->i2c, led->reg_control,
  82                                        LED_CURRENT_MASK, led->iset);
  83                }
  84                pm860x_set_bits(led->i2c, led->reg_blink,
  85                                LED_BLINK_MASK, LED_ON_CONTINUOUS);
  86                pm860x_set_bits(led->i2c, PM8606_WLED3B, led->blink_mask,
  87                                led->blink_mask);
  88        }
  89        pm860x_set_bits(led->i2c, led->reg_control, LED_PWM_MASK,
  90                        led->brightness);
  91
  92        if (led->brightness == 0) {
  93                pm860x_bulk_read(led->i2c, led->reg_control, 3, buf);
  94                ret = buf[0] & LED_PWM_MASK;
  95                ret |= buf[1] & LED_PWM_MASK;
  96                ret |= buf[2] & LED_PWM_MASK;
  97                if (ret == 0) {
  98                        /* unset current since no led is lighting */
  99                        pm860x_set_bits(led->i2c, led->reg_control,
 100                                        LED_CURRENT_MASK, 0);
 101                        pm860x_set_bits(led->i2c, PM8606_WLED3B,
 102                                        led->blink_mask, 0);
 103                        led_power_set(chip, led->port, 0);
 104                }
 105        }
 106        led->current_brightness = led->brightness;
 107        dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n",
 108                led->reg_control, led->brightness);
 109        mutex_unlock(&led->lock);
 110
 111        return 0;
 112}
 113
 114#ifdef CONFIG_OF
 115static int pm860x_led_dt_init(struct platform_device *pdev,
 116                              struct pm860x_led *data)
 117{
 118        struct device_node *nproot, *np;
 119        int iset = 0;
 120
 121        if (!pdev->dev.parent->of_node)
 122                return -ENODEV;
 123        nproot = of_get_child_by_name(pdev->dev.parent->of_node, "leds");
 124        if (!nproot) {
 125                dev_err(&pdev->dev, "failed to find leds node\n");
 126                return -ENODEV;
 127        }
 128        for_each_child_of_node(nproot, np) {
 129                if (of_node_name_eq(np, data->name)) {
 130                        of_property_read_u32(np, "marvell,88pm860x-iset",
 131                                             &iset);
 132                        data->iset = PM8606_LED_CURRENT(iset);
 133                        of_node_put(np);
 134                        break;
 135                }
 136        }
 137        of_node_put(nproot);
 138        return 0;
 139}
 140#else
 141#define pm860x_led_dt_init(x, y)        (-1)
 142#endif
 143
 144static int pm860x_led_probe(struct platform_device *pdev)
 145{
 146        struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
 147        struct pm860x_led_pdata *pdata = dev_get_platdata(&pdev->dev);
 148        struct pm860x_led *data;
 149        struct resource *res;
 150        int ret = 0;
 151
 152        data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_led), GFP_KERNEL);
 153        if (data == NULL)
 154                return -ENOMEM;
 155        res = platform_get_resource_byname(pdev, IORESOURCE_REG, "control");
 156        if (!res) {
 157                dev_err(&pdev->dev, "No REG resource for control\n");
 158                return -ENXIO;
 159        }
 160        data->reg_control = res->start;
 161        res = platform_get_resource_byname(pdev, IORESOURCE_REG, "blink");
 162        if (!res) {
 163                dev_err(&pdev->dev, "No REG resource for blink\n");
 164                return -ENXIO;
 165        }
 166        data->reg_blink = res->start;
 167        memset(data->name, 0, MFD_NAME_SIZE);
 168        switch (pdev->id) {
 169        case 0:
 170                data->blink_mask = LED1_BLINK_EN;
 171                sprintf(data->name, "led0-red");
 172                break;
 173        case 1:
 174                data->blink_mask = LED1_BLINK_EN;
 175                sprintf(data->name, "led0-green");
 176                break;
 177        case 2:
 178                data->blink_mask = LED1_BLINK_EN;
 179                sprintf(data->name, "led0-blue");
 180                break;
 181        case 3:
 182                data->blink_mask = LED2_BLINK_EN;
 183                sprintf(data->name, "led1-red");
 184                break;
 185        case 4:
 186                data->blink_mask = LED2_BLINK_EN;
 187                sprintf(data->name, "led1-green");
 188                break;
 189        case 5:
 190                data->blink_mask = LED2_BLINK_EN;
 191                sprintf(data->name, "led1-blue");
 192                break;
 193        }
 194        data->chip = chip;
 195        data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion;
 196        data->port = pdev->id;
 197        if (pm860x_led_dt_init(pdev, data))
 198                if (pdata)
 199                        data->iset = pdata->iset;
 200
 201        data->current_brightness = 0;
 202        data->cdev.name = data->name;
 203        data->cdev.brightness_set_blocking = pm860x_led_set;
 204        mutex_init(&data->lock);
 205
 206        ret = devm_led_classdev_register(chip->dev, &data->cdev);
 207        if (ret < 0) {
 208                dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
 209                return ret;
 210        }
 211        pm860x_led_set(&data->cdev, 0);
 212        return 0;
 213}
 214
 215
 216static struct platform_driver pm860x_led_driver = {
 217        .driver = {
 218                .name   = "88pm860x-led",
 219        },
 220        .probe  = pm860x_led_probe,
 221};
 222
 223module_platform_driver(pm860x_led_driver);
 224
 225MODULE_DESCRIPTION("LED driver for Marvell PM860x");
 226MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
 227MODULE_LICENSE("GPL");
 228MODULE_ALIAS("platform:88pm860x-led");
 229