linux/drivers/leds/leds-88pm860x.c
<<
>>
Prefs
   1/*
   2 * LED driver for Marvell 88PM860x
   3 *
   4 * Copyright (C) 2009 Marvell International Ltd.
   5 *      Haojian Zhuang <haojian.zhuang@marvell.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License version 2 as
   9 * published by the Free Software Foundation.
  10 *
  11 */
  12
  13#include <linux/kernel.h>
  14#include <linux/init.h>
  15#include <linux/platform_device.h>
  16#include <linux/i2c.h>
  17#include <linux/leds.h>
  18#include <linux/slab.h>
  19#include <linux/workqueue.h>
  20#include <linux/mfd/88pm860x.h>
  21#include <linux/module.h>
  22
  23#define LED_PWM_SHIFT           (3)
  24#define LED_PWM_MASK            (0x1F)
  25#define LED_CURRENT_MASK        (0x07 << 5)
  26
  27#define LED_BLINK_ON_MASK       (0x07)
  28#define LED_BLINK_MASK          (0x7F)
  29
  30#define LED_BLINK_ON(x)         ((x & 0x7) * 66 + 66)
  31#define LED_BLINK_ON_MIN        LED_BLINK_ON(0)
  32#define LED_BLINK_ON_MAX        LED_BLINK_ON(0x7)
  33#define LED_ON_CONTINUOUS       (0x0F << 3)
  34#define LED_TO_ON(x)            ((x - 66) / 66)
  35
  36#define LED1_BLINK_EN           (1 << 1)
  37#define LED2_BLINK_EN           (1 << 2)
  38
  39struct pm860x_led {
  40        struct led_classdev cdev;
  41        struct i2c_client *i2c;
  42        struct work_struct work;
  43        struct pm860x_chip *chip;
  44        struct mutex lock;
  45        char name[MFD_NAME_SIZE];
  46
  47        int port;
  48        int iset;
  49        unsigned char brightness;
  50        unsigned char current_brightness;
  51
  52        int blink_data;
  53        int blink_time;
  54        int blink_on;
  55        int blink_off;
  56};
  57
  58/* return offset of color register */
  59static inline int __led_off(int port)
  60{
  61        int ret = -EINVAL;
  62
  63        switch (port) {
  64        case PM8606_LED1_RED:
  65        case PM8606_LED1_GREEN:
  66        case PM8606_LED1_BLUE:
  67                ret = port - PM8606_LED1_RED + PM8606_RGB1B;
  68                break;
  69        case PM8606_LED2_RED:
  70        case PM8606_LED2_GREEN:
  71        case PM8606_LED2_BLUE:
  72                ret = port - PM8606_LED2_RED + PM8606_RGB2B;
  73                break;
  74        }
  75        return ret;
  76}
  77
  78/* return offset of blink register */
  79static inline int __blink_off(int port)
  80{
  81        int ret = -EINVAL;
  82
  83        switch (port) {
  84        case PM8606_LED1_RED:
  85        case PM8606_LED1_GREEN:
  86        case PM8606_LED1_BLUE:
  87                ret = PM8606_RGB1A;
  88                break;
  89        case PM8606_LED2_RED:
  90        case PM8606_LED2_GREEN:
  91        case PM8606_LED2_BLUE:
  92                ret = PM8606_RGB2A;
  93                break;
  94        }
  95        return ret;
  96}
  97
  98static inline int __blink_ctl_mask(int port)
  99{
 100        int ret = -EINVAL;
 101
 102        switch (port) {
 103        case PM8606_LED1_RED:
 104        case PM8606_LED1_GREEN:
 105        case PM8606_LED1_BLUE:
 106                ret = LED1_BLINK_EN;
 107                break;
 108        case PM8606_LED2_RED:
 109        case PM8606_LED2_GREEN:
 110        case PM8606_LED2_BLUE:
 111                ret = LED2_BLINK_EN;
 112                break;
 113        }
 114        return ret;
 115}
 116
 117static int led_power_set(struct pm860x_chip *chip, int port, int on)
 118{
 119        int ret = -EINVAL;
 120
 121        switch (port) {
 122        case PM8606_LED1_RED:
 123        case PM8606_LED1_GREEN:
 124        case PM8606_LED1_BLUE:
 125                ret = on ? pm8606_osc_enable(chip, RGB1_ENABLE) :
 126                        pm8606_osc_disable(chip, RGB1_ENABLE);
 127                break;
 128        case PM8606_LED2_RED:
 129        case PM8606_LED2_GREEN:
 130        case PM8606_LED2_BLUE:
 131                ret = on ? pm8606_osc_enable(chip, RGB2_ENABLE) :
 132                        pm8606_osc_disable(chip, RGB2_ENABLE);
 133                break;
 134        }
 135        return ret;
 136}
 137
 138static void pm860x_led_work(struct work_struct *work)
 139{
 140
 141        struct pm860x_led *led;
 142        struct pm860x_chip *chip;
 143        unsigned char buf[3];
 144        int mask, ret;
 145
 146        led = container_of(work, struct pm860x_led, work);
 147        chip = led->chip;
 148        mutex_lock(&led->lock);
 149        if ((led->current_brightness == 0) && led->brightness) {
 150                led_power_set(chip, led->port, 1);
 151                if (led->iset) {
 152                        pm860x_set_bits(led->i2c, __led_off(led->port),
 153                                        LED_CURRENT_MASK, led->iset);
 154                }
 155                pm860x_set_bits(led->i2c, __blink_off(led->port),
 156                                LED_BLINK_MASK, LED_ON_CONTINUOUS);
 157                mask = __blink_ctl_mask(led->port);
 158                pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, mask);
 159        }
 160        pm860x_set_bits(led->i2c, __led_off(led->port), LED_PWM_MASK,
 161                        led->brightness);
 162
 163        if (led->brightness == 0) {
 164                pm860x_bulk_read(led->i2c, __led_off(led->port), 3, buf);
 165                ret = buf[0] & LED_PWM_MASK;
 166                ret |= buf[1] & LED_PWM_MASK;
 167                ret |= buf[2] & LED_PWM_MASK;
 168                if (ret == 0) {
 169                        /* unset current since no led is lighting */
 170                        pm860x_set_bits(led->i2c, __led_off(led->port),
 171                                        LED_CURRENT_MASK, 0);
 172                        mask = __blink_ctl_mask(led->port);
 173                        pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, 0);
 174                        led_power_set(chip, led->port, 0);
 175                }
 176        }
 177        led->current_brightness = led->brightness;
 178        dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n",
 179                __led_off(led->port), led->brightness);
 180        mutex_unlock(&led->lock);
 181}
 182
 183static void pm860x_led_set(struct led_classdev *cdev,
 184                           enum led_brightness value)
 185{
 186        struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev);
 187
 188        data->brightness = value >> 3;
 189        schedule_work(&data->work);
 190}
 191
 192static int pm860x_led_probe(struct platform_device *pdev)
 193{
 194        struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
 195        struct pm860x_led_pdata *pdata;
 196        struct pm860x_led *data;
 197        struct resource *res;
 198        int ret;
 199
 200        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
 201        if (res == NULL) {
 202                dev_err(&pdev->dev, "No I/O resource!\n");
 203                return -EINVAL;
 204        }
 205
 206        pdata = pdev->dev.platform_data;
 207        if (pdata == NULL) {
 208                dev_err(&pdev->dev, "No platform data!\n");
 209                return -EINVAL;
 210        }
 211
 212        data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_led), GFP_KERNEL);
 213        if (data == NULL)
 214                return -ENOMEM;
 215        strncpy(data->name, res->name, MFD_NAME_SIZE - 1);
 216        dev_set_drvdata(&pdev->dev, data);
 217        data->chip = chip;
 218        data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion;
 219        data->iset = pdata->iset;
 220        data->port = pdata->flags;
 221        if (data->port < 0) {
 222                dev_err(&pdev->dev, "check device failed\n");
 223                return -EINVAL;
 224        }
 225
 226        data->current_brightness = 0;
 227        data->cdev.name = data->name;
 228        data->cdev.brightness_set = pm860x_led_set;
 229        mutex_init(&data->lock);
 230        INIT_WORK(&data->work, pm860x_led_work);
 231
 232        ret = led_classdev_register(chip->dev, &data->cdev);
 233        if (ret < 0) {
 234                dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
 235                return ret;
 236        }
 237        pm860x_led_set(&data->cdev, 0);
 238        return 0;
 239}
 240
 241static int pm860x_led_remove(struct platform_device *pdev)
 242{
 243        struct pm860x_led *data = platform_get_drvdata(pdev);
 244
 245        led_classdev_unregister(&data->cdev);
 246
 247        return 0;
 248}
 249
 250static struct platform_driver pm860x_led_driver = {
 251        .driver = {
 252                .name   = "88pm860x-led",
 253                .owner  = THIS_MODULE,
 254        },
 255        .probe  = pm860x_led_probe,
 256        .remove = pm860x_led_remove,
 257};
 258
 259module_platform_driver(pm860x_led_driver);
 260
 261MODULE_DESCRIPTION("LED driver for Marvell PM860x");
 262MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
 263MODULE_LICENSE("GPL");
 264MODULE_ALIAS("platform:88pm860x-led");
 265
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.