linux/drivers/video/backlight/wm831x_bl.c
<<
>>
Prefs
   1/*
   2 * Backlight driver for Wolfson Microelectronics WM831x PMICs
   3 *
   4 * Copyright 2009 Wolfson Microelectonics plc
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 */
  10
  11#include <linux/kernel.h>
  12#include <linux/init.h>
  13#include <linux/platform_device.h>
  14#include <linux/fb.h>
  15#include <linux/backlight.h>
  16
  17#include <linux/mfd/wm831x/core.h>
  18#include <linux/mfd/wm831x/pdata.h>
  19#include <linux/mfd/wm831x/regulator.h>
  20
  21struct wm831x_backlight_data {
  22        struct wm831x *wm831x;
  23        int isink_reg;
  24        int current_brightness;
  25};
  26
  27static int wm831x_backlight_set(struct backlight_device *bl, int brightness)
  28{
  29        struct wm831x_backlight_data *data = bl_get_data(bl);
  30        struct wm831x *wm831x = data->wm831x;
  31        int power_up = !data->current_brightness && brightness;
  32        int power_down = data->current_brightness && !brightness;
  33        int ret;
  34
  35        if (power_up) {
  36                /* Enable the ISINK */
  37                ret = wm831x_set_bits(wm831x, data->isink_reg,
  38                                      WM831X_CS1_ENA, WM831X_CS1_ENA);
  39                if (ret < 0)
  40                        goto err;
  41
  42                /* Enable the DC-DC */
  43                ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE,
  44                                      WM831X_DC4_ENA, WM831X_DC4_ENA);
  45                if (ret < 0)
  46                        goto err;
  47        }
  48
  49        if (power_down) {
  50                /* DCDC first */
  51                ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE,
  52                                      WM831X_DC4_ENA, 0);
  53                if (ret < 0)
  54                        goto err;
  55
  56                /* ISINK */
  57                ret = wm831x_set_bits(wm831x, data->isink_reg,
  58                                      WM831X_CS1_DRIVE | WM831X_CS1_ENA, 0);
  59                if (ret < 0)
  60                        goto err;
  61        }
  62
  63        /* Set the new brightness */
  64        ret = wm831x_set_bits(wm831x, data->isink_reg,
  65                              WM831X_CS1_ISEL_MASK, brightness);
  66        if (ret < 0)
  67                goto err;
  68
  69        if (power_up) {
  70                /* Drive current through the ISINK */
  71                ret = wm831x_set_bits(wm831x, data->isink_reg,
  72                                      WM831X_CS1_DRIVE, WM831X_CS1_DRIVE);
  73                if (ret < 0)
  74                        return ret;
  75        }
  76
  77        data->current_brightness = brightness;
  78
  79        return 0;
  80
  81err:
  82        /* If we were in the middle of a power transition always shut down
  83         * for safety.
  84         */
  85        if (power_up || power_down) {
  86                wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0);
  87                wm831x_set_bits(wm831x, data->isink_reg, WM831X_CS1_ENA, 0);
  88        }
  89
  90        return ret;
  91}
  92
  93static int wm831x_backlight_update_status(struct backlight_device *bl)
  94{
  95        int brightness = bl->props.brightness;
  96
  97        if (bl->props.power != FB_BLANK_UNBLANK)
  98                brightness = 0;
  99
 100        if (bl->props.fb_blank != FB_BLANK_UNBLANK)
 101                brightness = 0;
 102
 103        if (bl->props.state & BL_CORE_SUSPENDED)
 104                brightness = 0;
 105
 106        return wm831x_backlight_set(bl, brightness);
 107}
 108
 109static int wm831x_backlight_get_brightness(struct backlight_device *bl)
 110{
 111        struct wm831x_backlight_data *data = bl_get_data(bl);
 112        return data->current_brightness;
 113}
 114
 115static struct backlight_ops wm831x_backlight_ops = {
 116        .options = BL_CORE_SUSPENDRESUME,
 117        .update_status  = wm831x_backlight_update_status,
 118        .get_brightness = wm831x_backlight_get_brightness,
 119};
 120
 121static int wm831x_backlight_probe(struct platform_device *pdev)
 122{
 123        struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
 124        struct wm831x_pdata *wm831x_pdata;
 125        struct wm831x_backlight_pdata *pdata;
 126        struct wm831x_backlight_data *data;
 127        struct backlight_device *bl;
 128        int ret, i, max_isel, isink_reg, dcdc_cfg;
 129
 130        /* We need platform data */
 131        if (pdev->dev.parent->platform_data) {
 132                wm831x_pdata = pdev->dev.parent->platform_data;
 133                pdata = wm831x_pdata->backlight;
 134        } else {
 135                pdata = NULL;
 136        }
 137
 138        if (!pdata) {
 139                dev_err(&pdev->dev, "No platform data supplied\n");
 140                return -EINVAL;
 141        }
 142
 143        /* Figure out the maximum current we can use */
 144        for (i = 0; i < WM831X_ISINK_MAX_ISEL; i++) {
 145                if (wm831x_isinkv_values[i] > pdata->max_uA)
 146                        break;
 147        }
 148
 149        if (i == 0) {
 150                dev_err(&pdev->dev, "Invalid max_uA: %duA\n", pdata->max_uA);
 151                return -EINVAL;
 152        }
 153        max_isel = i - 1;
 154
 155        if (pdata->max_uA != wm831x_isinkv_values[max_isel])
 156                dev_warn(&pdev->dev,
 157                         "Maximum current is %duA not %duA as requested\n",
 158                         wm831x_isinkv_values[max_isel], pdata->max_uA);
 159
 160        switch (pdata->isink) {
 161        case 1:
 162                isink_reg = WM831X_CURRENT_SINK_1;
 163                dcdc_cfg = 0;
 164                break;
 165        case 2:
 166                isink_reg = WM831X_CURRENT_SINK_2;
 167                dcdc_cfg = WM831X_DC4_FBSRC;
 168                break;
 169        default:
 170                dev_err(&pdev->dev, "Invalid ISINK %d\n", pdata->isink);
 171                return -EINVAL;
 172        }
 173
 174        /* Configure the ISINK to use for feedback */
 175        ret = wm831x_reg_unlock(wm831x);
 176        if (ret < 0)
 177                return ret;
 178
 179        ret = wm831x_set_bits(wm831x, WM831X_DC4_CONTROL, WM831X_DC4_FBSRC,
 180                              dcdc_cfg);
 181
 182        wm831x_reg_lock(wm831x);
 183        if (ret < 0)
 184                return ret;
 185
 186        data = kzalloc(sizeof(*data), GFP_KERNEL);
 187        if (data == NULL)
 188                return -ENOMEM;
 189
 190        data->wm831x = wm831x;
 191        data->current_brightness = 0;
 192        data->isink_reg = isink_reg;
 193
 194        bl = backlight_device_register("wm831x", &pdev->dev,
 195                        data, &wm831x_backlight_ops);
 196        if (IS_ERR(bl)) {
 197                dev_err(&pdev->dev, "failed to register backlight\n");
 198                kfree(data);
 199                return PTR_ERR(bl);
 200        }
 201
 202        bl->props.max_brightness = max_isel;
 203        bl->props.brightness = max_isel;
 204
 205        platform_set_drvdata(pdev, bl);
 206
 207        /* Disable the DCDC if it was started so we can bootstrap */
 208        wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0);
 209
 210
 211        backlight_update_status(bl);
 212
 213        return 0;
 214}
 215
 216static int wm831x_backlight_remove(struct platform_device *pdev)
 217{
 218        struct backlight_device *bl = platform_get_drvdata(pdev);
 219        struct wm831x_backlight_data *data = bl_get_data(bl);
 220
 221        backlight_device_unregister(bl);
 222        kfree(data);
 223        return 0;
 224}
 225
 226static struct platform_driver wm831x_backlight_driver = {
 227        .driver         = {
 228                .name   = "wm831x-backlight",
 229                .owner  = THIS_MODULE,
 230        },
 231        .probe          = wm831x_backlight_probe,
 232        .remove         = wm831x_backlight_remove,
 233};
 234
 235static int __init wm831x_backlight_init(void)
 236{
 237        return platform_driver_register(&wm831x_backlight_driver);
 238}
 239module_init(wm831x_backlight_init);
 240
 241static void __exit wm831x_backlight_exit(void)
 242{
 243        platform_driver_unregister(&wm831x_backlight_driver);
 244}
 245module_exit(wm831x_backlight_exit);
 246
 247MODULE_DESCRIPTION("Backlight Driver for WM831x PMICs");
 248MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com");
 249MODULE_LICENSE("GPL");
 250MODULE_ALIAS("platform:wm831x-backlight");
 251