linux/drivers/video/backlight/tps65217_bl.c
<<
>>
Prefs
   1/*
   2 * tps65217_bl.c
   3 *
   4 * TPS65217 backlight driver
   5 *
   6 * Copyright (C) 2012 Matthias Kaehlcke
   7 * Author: Matthias Kaehlcke <matthias@kaehlcke.net>
   8 *
   9 * This program is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU General Public License as
  11 * published by the Free Software Foundation version 2.
  12 *
  13 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
  14 * kind, whether express or implied; without even the implied warranty
  15 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 */
  18
  19#include <linux/kernel.h>
  20#include <linux/backlight.h>
  21#include <linux/err.h>
  22#include <linux/fb.h>
  23#include <linux/mfd/tps65217.h>
  24#include <linux/module.h>
  25#include <linux/platform_device.h>
  26#include <linux/slab.h>
  27
  28struct tps65217_bl {
  29        struct tps65217 *tps;
  30        struct device *dev;
  31        struct backlight_device *bl;
  32        bool is_enabled;
  33};
  34
  35static int tps65217_bl_enable(struct tps65217_bl *tps65217_bl)
  36{
  37        int rc;
  38
  39        rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1,
  40                        TPS65217_WLEDCTRL1_ISINK_ENABLE,
  41                        TPS65217_WLEDCTRL1_ISINK_ENABLE, TPS65217_PROTECT_NONE);
  42        if (rc) {
  43                dev_err(tps65217_bl->dev,
  44                        "failed to enable backlight: %d\n", rc);
  45                return rc;
  46        }
  47
  48        tps65217_bl->is_enabled = true;
  49
  50        dev_dbg(tps65217_bl->dev, "backlight enabled\n");
  51
  52        return 0;
  53}
  54
  55static int tps65217_bl_disable(struct tps65217_bl *tps65217_bl)
  56{
  57        int rc;
  58
  59        rc = tps65217_clear_bits(tps65217_bl->tps,
  60                                TPS65217_REG_WLEDCTRL1,
  61                                TPS65217_WLEDCTRL1_ISINK_ENABLE,
  62                                TPS65217_PROTECT_NONE);
  63        if (rc) {
  64                dev_err(tps65217_bl->dev,
  65                        "failed to disable backlight: %d\n", rc);
  66                return rc;
  67        }
  68
  69        tps65217_bl->is_enabled = false;
  70
  71        dev_dbg(tps65217_bl->dev, "backlight disabled\n");
  72
  73        return 0;
  74}
  75
  76static int tps65217_bl_update_status(struct backlight_device *bl)
  77{
  78        struct tps65217_bl *tps65217_bl = bl_get_data(bl);
  79        int rc;
  80        int brightness = bl->props.brightness;
  81
  82        if (bl->props.state & BL_CORE_SUSPENDED)
  83                brightness = 0;
  84
  85        if ((bl->props.power != FB_BLANK_UNBLANK) ||
  86                (bl->props.fb_blank != FB_BLANK_UNBLANK))
  87                /* framebuffer in low power mode or blanking active */
  88                brightness = 0;
  89
  90        if (brightness > 0) {
  91                rc = tps65217_reg_write(tps65217_bl->tps,
  92                                        TPS65217_REG_WLEDCTRL2,
  93                                        brightness - 1,
  94                                        TPS65217_PROTECT_NONE);
  95                if (rc) {
  96                        dev_err(tps65217_bl->dev,
  97                                "failed to set brightness level: %d\n", rc);
  98                        return rc;
  99                }
 100
 101                dev_dbg(tps65217_bl->dev, "brightness set to %d\n", brightness);
 102
 103                if (!tps65217_bl->is_enabled)
 104                        rc = tps65217_bl_enable(tps65217_bl);
 105        } else {
 106                rc = tps65217_bl_disable(tps65217_bl);
 107        }
 108
 109        return rc;
 110}
 111
 112static const struct backlight_ops tps65217_bl_ops = {
 113        .options        = BL_CORE_SUSPENDRESUME,
 114        .update_status  = tps65217_bl_update_status,
 115};
 116
 117static int tps65217_bl_hw_init(struct tps65217_bl *tps65217_bl,
 118                        struct tps65217_bl_pdata *pdata)
 119{
 120        int rc;
 121
 122        rc = tps65217_bl_disable(tps65217_bl);
 123        if (rc)
 124                return rc;
 125
 126        switch (pdata->isel) {
 127        case TPS65217_BL_ISET1:
 128                /* select ISET_1 current level */
 129                rc = tps65217_clear_bits(tps65217_bl->tps,
 130                                        TPS65217_REG_WLEDCTRL1,
 131                                        TPS65217_WLEDCTRL1_ISEL,
 132                                        TPS65217_PROTECT_NONE);
 133                if (rc) {
 134                        dev_err(tps65217_bl->dev,
 135                                "failed to select ISET1 current level: %d)\n",
 136                                rc);
 137                        return rc;
 138                }
 139
 140                dev_dbg(tps65217_bl->dev, "selected ISET1 current level\n");
 141
 142                break;
 143
 144        case TPS65217_BL_ISET2:
 145                /* select ISET2 current level */
 146                rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1,
 147                                TPS65217_WLEDCTRL1_ISEL,
 148                                TPS65217_WLEDCTRL1_ISEL, TPS65217_PROTECT_NONE);
 149                if (rc) {
 150                        dev_err(tps65217_bl->dev,
 151                                "failed to select ISET2 current level: %d\n",
 152                                rc);
 153                        return rc;
 154                }
 155
 156                dev_dbg(tps65217_bl->dev, "selected ISET2 current level\n");
 157
 158                break;
 159
 160        default:
 161                dev_err(tps65217_bl->dev,
 162                        "invalid value for current level: %d\n", pdata->isel);
 163                return -EINVAL;
 164        }
 165
 166        /* set PWM frequency */
 167        rc = tps65217_set_bits(tps65217_bl->tps,
 168                        TPS65217_REG_WLEDCTRL1,
 169                        TPS65217_WLEDCTRL1_FDIM_MASK,
 170                        pdata->fdim,
 171                        TPS65217_PROTECT_NONE);
 172        if (rc) {
 173                dev_err(tps65217_bl->dev,
 174                        "failed to select PWM dimming frequency: %d\n",
 175                        rc);
 176                return rc;
 177        }
 178
 179        return 0;
 180}
 181
 182#ifdef CONFIG_OF
 183static struct tps65217_bl_pdata *
 184tps65217_bl_parse_dt(struct platform_device *pdev)
 185{
 186        struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
 187        struct device_node *node = of_node_get(tps->dev->of_node);
 188        struct tps65217_bl_pdata *pdata, *err;
 189        u32 val;
 190
 191        node = of_find_node_by_name(node, "backlight");
 192        if (!node)
 193                return ERR_PTR(-ENODEV);
 194
 195        pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
 196        if (!pdata) {
 197                err = ERR_PTR(-ENOMEM);
 198                goto err;
 199        }
 200
 201        pdata->isel = TPS65217_BL_ISET1;
 202        if (!of_property_read_u32(node, "isel", &val)) {
 203                if (val < TPS65217_BL_ISET1 ||
 204                        val > TPS65217_BL_ISET2) {
 205                        dev_err(&pdev->dev,
 206                                "invalid 'isel' value in the device tree\n");
 207                        err = ERR_PTR(-EINVAL);
 208                        goto err;
 209                }
 210
 211                pdata->isel = val;
 212        }
 213
 214        pdata->fdim = TPS65217_BL_FDIM_200HZ;
 215        if (!of_property_read_u32(node, "fdim", &val)) {
 216                switch (val) {
 217                case 100:
 218                        pdata->fdim = TPS65217_BL_FDIM_100HZ;
 219                        break;
 220
 221                case 200:
 222                        pdata->fdim = TPS65217_BL_FDIM_200HZ;
 223                        break;
 224
 225                case 500:
 226                        pdata->fdim = TPS65217_BL_FDIM_500HZ;
 227                        break;
 228
 229                case 1000:
 230                        pdata->fdim = TPS65217_BL_FDIM_1000HZ;
 231                        break;
 232
 233                default:
 234                        dev_err(&pdev->dev,
 235                                "invalid 'fdim' value in the device tree\n");
 236                        err = ERR_PTR(-EINVAL);
 237                        goto err;
 238                }
 239        }
 240
 241        if (!of_property_read_u32(node, "default-brightness", &val)) {
 242                if (val < 0 ||
 243                        val > 100) {
 244                        dev_err(&pdev->dev,
 245                                "invalid 'default-brightness' value in the device tree\n");
 246                        err = ERR_PTR(-EINVAL);
 247                        goto err;
 248                }
 249
 250                pdata->dft_brightness = val;
 251        }
 252
 253        of_node_put(node);
 254
 255        return pdata;
 256
 257err:
 258        of_node_put(node);
 259
 260        return err;
 261}
 262#else
 263static struct tps65217_bl_pdata *
 264tps65217_bl_parse_dt(struct platform_device *pdev)
 265{
 266        return NULL;
 267}
 268#endif
 269
 270static int tps65217_bl_probe(struct platform_device *pdev)
 271{
 272        int rc;
 273        struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
 274        struct tps65217_bl *tps65217_bl;
 275        struct tps65217_bl_pdata *pdata;
 276        struct backlight_properties bl_props;
 277
 278        if (tps->dev->of_node) {
 279                pdata = tps65217_bl_parse_dt(pdev);
 280                if (IS_ERR(pdata))
 281                        return PTR_ERR(pdata);
 282        } else {
 283                pdata = dev_get_platdata(&pdev->dev);
 284                if (!pdata) {
 285                        dev_err(&pdev->dev, "no platform data provided\n");
 286                        return -EINVAL;
 287                }
 288        }
 289
 290        tps65217_bl = devm_kzalloc(&pdev->dev, sizeof(*tps65217_bl),
 291                                GFP_KERNEL);
 292        if (tps65217_bl == NULL)
 293                return -ENOMEM;
 294
 295        tps65217_bl->tps = tps;
 296        tps65217_bl->dev = &pdev->dev;
 297        tps65217_bl->is_enabled = false;
 298
 299        rc = tps65217_bl_hw_init(tps65217_bl, pdata);
 300        if (rc)
 301                return rc;
 302
 303        memset(&bl_props, 0, sizeof(struct backlight_properties));
 304        bl_props.type = BACKLIGHT_RAW;
 305        bl_props.max_brightness = 100;
 306
 307        tps65217_bl->bl = devm_backlight_device_register(&pdev->dev, pdev->name,
 308                                                tps65217_bl->dev, tps65217_bl,
 309                                                &tps65217_bl_ops, &bl_props);
 310        if (IS_ERR(tps65217_bl->bl)) {
 311                dev_err(tps65217_bl->dev,
 312                        "registration of backlight device failed: %d\n", rc);
 313                return PTR_ERR(tps65217_bl->bl);
 314        }
 315
 316        tps65217_bl->bl->props.brightness = pdata->dft_brightness;
 317        backlight_update_status(tps65217_bl->bl);
 318        platform_set_drvdata(pdev, tps65217_bl);
 319
 320        return 0;
 321}
 322
 323#ifdef CONFIG_OF
 324static const struct of_device_id tps65217_bl_of_match[] = {
 325        { .compatible = "ti,tps65217-bl", },
 326        { /* sentinel */ },
 327};
 328MODULE_DEVICE_TABLE(of, tps65217_bl_of_match);
 329#endif
 330
 331static struct platform_driver tps65217_bl_driver = {
 332        .probe          = tps65217_bl_probe,
 333        .driver         = {
 334                .name   = "tps65217-bl",
 335                .of_match_table = of_match_ptr(tps65217_bl_of_match),
 336        },
 337};
 338
 339module_platform_driver(tps65217_bl_driver);
 340
 341MODULE_DESCRIPTION("TPS65217 Backlight driver");
 342MODULE_LICENSE("GPL v2");
 343MODULE_AUTHOR("Matthias Kaehlcke <matthias@kaehlcke.net>");
 344