linux/drivers/leds/leds-mc13783.c
<<
>>
Prefs
   1/*
   2 * LEDs driver for Freescale MC13783/MC13892
   3 *
   4 * Copyright (C) 2010 Philippe R├ętornaz
   5 *
   6 * Based on leds-da903x:
   7 * Copyright (C) 2008 Compulab, Ltd.
   8 *      Mike Rapoport <mike@compulab.co.il>
   9 *
  10 * Copyright (C) 2006-2008 Marvell International Ltd.
  11 *      Eric Miao <eric.miao@marvell.com>
  12 *
  13 * This program is free software; you can redistribute it and/or modify
  14 * it under the terms of the GNU General Public License version 2 as
  15 * published by the Free Software Foundation.
  16 */
  17
  18#include <linux/module.h>
  19#include <linux/kernel.h>
  20#include <linux/init.h>
  21#include <linux/platform_device.h>
  22#include <linux/leds.h>
  23#include <linux/workqueue.h>
  24#include <linux/mfd/mc13xxx.h>
  25
  26#define MC13XXX_REG_LED_CONTROL(x)      (51 + (x))
  27
  28struct mc13xxx_led_devtype {
  29        int     led_min;
  30        int     led_max;
  31        int     num_regs;
  32};
  33
  34struct mc13xxx_led {
  35        struct led_classdev     cdev;
  36        struct work_struct      work;
  37        struct mc13xxx          *master;
  38        enum led_brightness     new_brightness;
  39        int                     id;
  40};
  41
  42struct mc13xxx_leds {
  43        struct mc13xxx_led_devtype      *devtype;
  44        int                             num_leds;
  45        struct mc13xxx_led              led[0];
  46};
  47
  48static void mc13xxx_led_work(struct work_struct *work)
  49{
  50        struct mc13xxx_led *led = container_of(work, struct mc13xxx_led, work);
  51        int reg, mask, value, bank, off, shift;
  52
  53        switch (led->id) {
  54        case MC13783_LED_MD:
  55                reg = MC13XXX_REG_LED_CONTROL(2);
  56                shift = 9;
  57                mask = 0x0f;
  58                value = led->new_brightness >> 4;
  59                break;
  60        case MC13783_LED_AD:
  61                reg = MC13XXX_REG_LED_CONTROL(2);
  62                shift = 13;
  63                mask = 0x0f;
  64                value = led->new_brightness >> 4;
  65                break;
  66        case MC13783_LED_KP:
  67                reg = MC13XXX_REG_LED_CONTROL(2);
  68                shift = 17;
  69                mask = 0x0f;
  70                value = led->new_brightness >> 4;
  71                break;
  72        case MC13783_LED_R1:
  73        case MC13783_LED_G1:
  74        case MC13783_LED_B1:
  75        case MC13783_LED_R2:
  76        case MC13783_LED_G2:
  77        case MC13783_LED_B2:
  78        case MC13783_LED_R3:
  79        case MC13783_LED_G3:
  80        case MC13783_LED_B3:
  81                off = led->id - MC13783_LED_R1;
  82                bank = off / 3;
  83                reg = MC13XXX_REG_LED_CONTROL(3) + bank;
  84                shift = (off - bank * 3) * 5 + 6;
  85                value = led->new_brightness >> 3;
  86                mask = 0x1f;
  87                break;
  88        case MC13892_LED_MD:
  89                reg = MC13XXX_REG_LED_CONTROL(0);
  90                shift = 3;
  91                mask = 0x3f;
  92                value = led->new_brightness >> 2;
  93                break;
  94        case MC13892_LED_AD:
  95                reg = MC13XXX_REG_LED_CONTROL(0);
  96                shift = 15;
  97                mask = 0x3f;
  98                value = led->new_brightness >> 2;
  99                break;
 100        case MC13892_LED_KP:
 101                reg = MC13XXX_REG_LED_CONTROL(1);
 102                shift = 3;
 103                mask = 0x3f;
 104                value = led->new_brightness >> 2;
 105                break;
 106        case MC13892_LED_R:
 107        case MC13892_LED_G:
 108        case MC13892_LED_B:
 109                off = led->id - MC13892_LED_R;
 110                bank = off / 2;
 111                reg = MC13XXX_REG_LED_CONTROL(2) + bank;
 112                shift = (off - bank * 2) * 12 + 3;
 113                value = led->new_brightness >> 2;
 114                mask = 0x3f;
 115                break;
 116        default:
 117                BUG();
 118        }
 119
 120        mc13xxx_lock(led->master);
 121        mc13xxx_reg_rmw(led->master, reg, mask << shift, value << shift);
 122        mc13xxx_unlock(led->master);
 123}
 124
 125static void mc13xxx_led_set(struct led_classdev *led_cdev,
 126                            enum led_brightness value)
 127{
 128        struct mc13xxx_led *led =
 129                container_of(led_cdev, struct mc13xxx_led, cdev);
 130
 131        led->new_brightness = value;
 132        schedule_work(&led->work);
 133}
 134
 135static int __init mc13xxx_led_setup(struct mc13xxx_led *led, int max_current)
 136{
 137        int shift, mask, reg, ret, bank;
 138
 139        switch (led->id) {
 140        case MC13783_LED_MD:
 141                reg = MC13XXX_REG_LED_CONTROL(2);
 142                shift = 0;
 143                mask = 0x07;
 144                break;
 145        case MC13783_LED_AD:
 146                reg = MC13XXX_REG_LED_CONTROL(2);
 147                shift = 3;
 148                mask = 0x07;
 149                break;
 150        case MC13783_LED_KP:
 151                reg = MC13XXX_REG_LED_CONTROL(2);
 152                shift = 6;
 153                mask = 0x07;
 154                break;
 155        case MC13783_LED_R1:
 156        case MC13783_LED_G1:
 157        case MC13783_LED_B1:
 158        case MC13783_LED_R2:
 159        case MC13783_LED_G2:
 160        case MC13783_LED_B2:
 161        case MC13783_LED_R3:
 162        case MC13783_LED_G3:
 163        case MC13783_LED_B3:
 164                bank = (led->id - MC13783_LED_R1) / 3;
 165                reg = MC13XXX_REG_LED_CONTROL(3) + bank;
 166                shift = ((led->id - MC13783_LED_R1) - bank * 3) * 2;
 167                mask = 0x03;
 168                break;
 169        case MC13892_LED_MD:
 170                reg = MC13XXX_REG_LED_CONTROL(0);
 171                shift = 9;
 172                mask = 0x07;
 173                break;
 174        case MC13892_LED_AD:
 175                reg = MC13XXX_REG_LED_CONTROL(0);
 176                shift = 21;
 177                mask = 0x07;
 178                break;
 179        case MC13892_LED_KP:
 180                reg = MC13XXX_REG_LED_CONTROL(1);
 181                shift = 9;
 182                mask = 0x07;
 183                break;
 184        case MC13892_LED_R:
 185        case MC13892_LED_G:
 186        case MC13892_LED_B:
 187                bank = (led->id - MC13892_LED_R) / 2;
 188                reg = MC13XXX_REG_LED_CONTROL(2) + bank;
 189                shift = ((led->id - MC13892_LED_R) - bank * 2) * 12 + 9;
 190                mask = 0x07;
 191                break;
 192        default:
 193                BUG();
 194        }
 195
 196        mc13xxx_lock(led->master);
 197        ret = mc13xxx_reg_rmw(led->master, reg, mask << shift,
 198                              max_current << shift);
 199        mc13xxx_unlock(led->master);
 200
 201        return ret;
 202}
 203
 204static int __init mc13xxx_led_probe(struct platform_device *pdev)
 205{
 206        struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
 207        struct mc13xxx *mcdev = dev_get_drvdata(pdev->dev.parent);
 208        struct mc13xxx_led_devtype *devtype =
 209                (struct mc13xxx_led_devtype *)pdev->id_entry->driver_data;
 210        struct mc13xxx_leds *leds;
 211        int i, id, num_leds, ret = -ENODATA;
 212        u32 reg, init_led = 0;
 213
 214        if (!pdata) {
 215                dev_err(&pdev->dev, "Missing platform data\n");
 216                return -ENODEV;
 217        }
 218
 219        num_leds = pdata->num_leds;
 220
 221        if ((num_leds < 1) ||
 222            (num_leds > (devtype->led_max - devtype->led_min + 1))) {
 223                dev_err(&pdev->dev, "Invalid LED count %d\n", num_leds);
 224                return -EINVAL;
 225        }
 226
 227        leds = devm_kzalloc(&pdev->dev, num_leds * sizeof(struct mc13xxx_led) +
 228                            sizeof(struct mc13xxx_leds), GFP_KERNEL);
 229        if (!leds)
 230                return -ENOMEM;
 231
 232        leds->devtype = devtype;
 233        leds->num_leds = num_leds;
 234        platform_set_drvdata(pdev, leds);
 235
 236        mc13xxx_lock(mcdev);
 237        for (i = 0; i < devtype->num_regs; i++) {
 238                reg = pdata->led_control[i];
 239                WARN_ON(reg >= (1 << 24));
 240                ret = mc13xxx_reg_write(mcdev, MC13XXX_REG_LED_CONTROL(i), reg);
 241                if (ret)
 242                        break;
 243        }
 244        mc13xxx_unlock(mcdev);
 245
 246        if (ret) {
 247                dev_err(&pdev->dev, "Unable to init LED driver\n");
 248                return ret;
 249        }
 250
 251        for (i = 0; i < num_leds; i++) {
 252                const char *name, *trig;
 253                char max_current;
 254
 255                ret = -EINVAL;
 256
 257                id = pdata->led[i].id;
 258                name = pdata->led[i].name;
 259                trig = pdata->led[i].default_trigger;
 260                max_current = pdata->led[i].max_current;
 261
 262                if ((id > devtype->led_max) || (id < devtype->led_min)) {
 263                        dev_err(&pdev->dev, "Invalid ID %i\n", id);
 264                        break;
 265                }
 266
 267                if (init_led & (1 << id)) {
 268                        dev_warn(&pdev->dev,
 269                                 "LED %i already initialized\n", id);
 270                        break;
 271                }
 272
 273                init_led |= 1 << id;
 274                leds->led[i].id = id;
 275                leds->led[i].master = mcdev;
 276                leds->led[i].cdev.name = name;
 277                leds->led[i].cdev.default_trigger = trig;
 278                leds->led[i].cdev.brightness_set = mc13xxx_led_set;
 279                leds->led[i].cdev.brightness = LED_OFF;
 280
 281                INIT_WORK(&leds->led[i].work, mc13xxx_led_work);
 282
 283                ret = mc13xxx_led_setup(&leds->led[i], max_current);
 284                if (ret) {
 285                        dev_err(&pdev->dev, "Unable to setup LED %i\n", id);
 286                        break;
 287                }
 288                ret = led_classdev_register(pdev->dev.parent,
 289                                            &leds->led[i].cdev);
 290                if (ret) {
 291                        dev_err(&pdev->dev, "Failed to register LED %i\n", id);
 292                        break;
 293                }
 294        }
 295
 296        if (ret)
 297                while (--i >= 0) {
 298                        led_classdev_unregister(&leds->led[i].cdev);
 299                        cancel_work_sync(&leds->led[i].work);
 300                }
 301
 302        return ret;
 303}
 304
 305static int mc13xxx_led_remove(struct platform_device *pdev)
 306{
 307        struct mc13xxx *mcdev = dev_get_drvdata(pdev->dev.parent);
 308        struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
 309        int i;
 310
 311        for (i = 0; i < leds->num_leds; i++) {
 312                led_classdev_unregister(&leds->led[i].cdev);
 313                cancel_work_sync(&leds->led[i].work);
 314        }
 315
 316        mc13xxx_lock(mcdev);
 317        for (i = 0; i < leds->devtype->num_regs; i++)
 318                mc13xxx_reg_write(mcdev, MC13XXX_REG_LED_CONTROL(i), 0);
 319        mc13xxx_unlock(mcdev);
 320
 321        return 0;
 322}
 323
 324static const struct mc13xxx_led_devtype mc13783_led_devtype = {
 325        .led_min        = MC13783_LED_MD,
 326        .led_max        = MC13783_LED_B3,
 327        .num_regs       = 6,
 328};
 329
 330static const struct mc13xxx_led_devtype mc13892_led_devtype = {
 331        .led_min        = MC13892_LED_MD,
 332        .led_max        = MC13892_LED_B,
 333        .num_regs       = 4,
 334};
 335
 336static const struct platform_device_id mc13xxx_led_id_table[] = {
 337        { "mc13783-led", (kernel_ulong_t)&mc13783_led_devtype, },
 338        { "mc13892-led", (kernel_ulong_t)&mc13892_led_devtype, },
 339        { }
 340};
 341MODULE_DEVICE_TABLE(platform, mc13xxx_led_id_table);
 342
 343static struct platform_driver mc13xxx_led_driver = {
 344        .driver = {
 345                .name   = "mc13xxx-led",
 346                .owner  = THIS_MODULE,
 347        },
 348        .remove         = mc13xxx_led_remove,
 349        .id_table       = mc13xxx_led_id_table,
 350};
 351module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe);
 352
 353MODULE_DESCRIPTION("LEDs driver for Freescale MC13XXX PMIC");
 354MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch>");
 355MODULE_LICENSE("GPL");
 356
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.