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 void pm860x_led_work(struct work_struct *work)
 118{
 119
 120        struct pm860x_led *led;
 121        struct pm860x_chip *chip;
 122        unsigned char buf[3];
 123        int mask, ret;
 124
 125        led = container_of(work, struct pm860x_led, work);
 126        chip = led->chip;
 127        mutex_lock(&led->lock);
 128        if ((led->current_brightness == 0) && led->brightness) {
 129                if (led->iset) {
 130                        pm860x_set_bits(led->i2c, __led_off(led->port),
 131                                        LED_CURRENT_MASK, led->iset);
 132                }
 133                pm860x_set_bits(led->i2c, __blink_off(led->port),
 134                                LED_BLINK_MASK, LED_ON_CONTINUOUS);
 135                mask = __blink_ctl_mask(led->port);
 136                pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, mask);
 137        }
 138        pm860x_set_bits(led->i2c, __led_off(led->port), LED_PWM_MASK,
 139                        led->brightness);
 140
 141        if (led->brightness == 0) {
 142                pm860x_bulk_read(led->i2c, __led_off(led->port), 3, buf);
 143                ret = buf[0] & LED_PWM_MASK;
 144                ret |= buf[1] & LED_PWM_MASK;
 145                ret |= buf[2] & LED_PWM_MASK;
 146                if (ret == 0) {
 147                        /* unset current since no led is lighting */
 148                        pm860x_set_bits(led->i2c, __led_off(led->port),
 149                                        LED_CURRENT_MASK, 0);
 150                        mask = __blink_ctl_mask(led->port);
 151                        pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, 0);
 152                }
 153        }
 154        led->current_brightness = led->brightness;
 155        dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n",
 156                __led_off(led->port), led->brightness);
 157        mutex_unlock(&led->lock);
 158}
 159
 160static void pm860x_led_set(struct led_classdev *cdev,
 161                           enum led_brightness value)
 162{
 163        struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev);
 164
 165        data->brightness = value >> 3;
 166        schedule_work(&data->work);
 167}
 168
 169static int pm860x_led_probe(struct platform_device *pdev)
 170{
 171        struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
 172        struct pm860x_led_pdata *pdata;
 173        struct pm860x_led *data;
 174        struct resource *res;
 175        int ret;
 176
 177        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
 178        if (res == NULL) {
 179                dev_err(&pdev->dev, "No I/O resource!\n");
 180                return -EINVAL;
 181        }
 182
 183        pdata = pdev->dev.platform_data;
 184        if (pdata == NULL) {
 185                dev_err(&pdev->dev, "No platform data!\n");
 186                return -EINVAL;
 187        }
 188
 189        data = kzalloc(sizeof(struct pm860x_led), GFP_KERNEL);
 190        if (data == NULL)
 191                return -ENOMEM;
 192        strncpy(data->name, res->name, MFD_NAME_SIZE - 1);
 193        dev_set_drvdata(&pdev->dev, data);
 194        data->chip = chip;
 195        data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion;
 196        data->iset = pdata->iset;
 197        data->port = pdata->flags;
 198        if (data->port < 0) {
 199                dev_err(&pdev->dev, "check device failed\n");
 200                kfree(data);
 201                return -EINVAL;
 202        }
 203
 204        data->current_brightness = 0;
 205        data->cdev.name = data->name;
 206        data->cdev.brightness_set = pm860x_led_set;
 207        mutex_init(&data->lock);
 208        INIT_WORK(&data->work, pm860x_led_work);
 209
 210        ret = led_classdev_register(chip->dev, &data->cdev);
 211        if (ret < 0) {
 212                dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
 213                goto out;
 214        }
 215        pm860x_led_set(&data->cdev, 0);
 216        return 0;
 217out:
 218        kfree(data);
 219        return ret;
 220}
 221
 222static int pm860x_led_remove(struct platform_device *pdev)
 223{
 224        struct pm860x_led *data = platform_get_drvdata(pdev);
 225
 226        led_classdev_unregister(&data->cdev);
 227        kfree(data);
 228
 229        return 0;
 230}
 231
 232static struct platform_driver pm860x_led_driver = {
 233        .driver = {
 234                .name   = "88pm860x-led",
 235                .owner  = THIS_MODULE,
 236        },
 237        .probe  = pm860x_led_probe,
 238        .remove = pm860x_led_remove,
 239};
 240
 241static int __devinit pm860x_led_init(void)
 242{
 243        return platform_driver_register(&pm860x_led_driver);
 244}
 245module_init(pm860x_led_init);
 246
 247static void __devexit pm860x_led_exit(void)
 248{
 249        platform_driver_unregister(&pm860x_led_driver);
 250}
 251module_exit(pm860x_led_exit);
 252
 253MODULE_DESCRIPTION("LED driver for Marvell PM860x");
 254MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
 255MODULE_LICENSE("GPL");
 256MODULE_ALIAS("platform:88pm860x-led");
 257
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.