linux/drivers/video/backlight/sky81452-backlight.c
<<
>>
Prefs
   1/*
   2 * sky81452-backlight.c SKY81452 backlight driver
   3 *
   4 * Copyright 2014 Skyworks Solutions Inc.
   5 * Author : Gyungoh Yoo <jack.yoo@skyworksinc.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify it
   8 * under the terms of the GNU General Public License version 2
   9 * as published by the Free Software Foundation.
  10 *
  11 * This program is distributed in the hope that it will be useful, but
  12 * WITHOUT ANY WARRANTY; without even the implied warranty of
  13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14 * General Public License for more details.
  15 *
  16 * You should have received a copy of the GNU General Public License along
  17 * with this program; if not, see <http://www.gnu.org/licenses/>.
  18 */
  19
  20#include <linux/backlight.h>
  21#include <linux/err.h>
  22#include <linux/gpio.h>
  23#include <linux/init.h>
  24#include <linux/kernel.h>
  25#include <linux/module.h>
  26#include <linux/of.h>
  27#include <linux/of_gpio.h>
  28#include <linux/platform_device.h>
  29#include <linux/regmap.h>
  30#include <linux/platform_data/sky81452-backlight.h>
  31#include <linux/slab.h>
  32
  33/* registers */
  34#define SKY81452_REG0   0x00
  35#define SKY81452_REG1   0x01
  36#define SKY81452_REG2   0x02
  37#define SKY81452_REG4   0x04
  38#define SKY81452_REG5   0x05
  39
  40/* bit mask */
  41#define SKY81452_CS     0xFF
  42#define SKY81452_EN     0x3F
  43#define SKY81452_IGPW   0x20
  44#define SKY81452_PWMMD  0x10
  45#define SKY81452_PHASE  0x08
  46#define SKY81452_ILIM   0x04
  47#define SKY81452_VSHRT  0x03
  48#define SKY81452_OCP    0x80
  49#define SKY81452_OTMP   0x40
  50#define SKY81452_SHRT   0x3F
  51#define SKY81452_OPN    0x3F
  52
  53#define SKY81452_DEFAULT_NAME "lcd-backlight"
  54#define SKY81452_MAX_BRIGHTNESS (SKY81452_CS + 1)
  55
  56#define CTZ(b) __builtin_ctz(b)
  57
  58static int sky81452_bl_update_status(struct backlight_device *bd)
  59{
  60        const struct sky81452_bl_platform_data *pdata =
  61                        dev_get_platdata(bd->dev.parent);
  62        const unsigned int brightness = (unsigned int)bd->props.brightness;
  63        struct regmap *regmap = bl_get_data(bd);
  64        int ret;
  65
  66        if (brightness > 0) {
  67                ret = regmap_write(regmap, SKY81452_REG0, brightness - 1);
  68                if (ret < 0)
  69                        return ret;
  70
  71                return regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN,
  72                                        pdata->enable << CTZ(SKY81452_EN));
  73        }
  74
  75        return regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN, 0);
  76}
  77
  78static const struct backlight_ops sky81452_bl_ops = {
  79        .update_status = sky81452_bl_update_status,
  80};
  81
  82static ssize_t sky81452_bl_store_enable(struct device *dev,
  83                struct device_attribute *attr, const char *buf, size_t count)
  84{
  85        struct regmap *regmap = bl_get_data(to_backlight_device(dev));
  86        unsigned long value;
  87        int ret;
  88
  89        ret = kstrtoul(buf, 16, &value);
  90        if (ret < 0)
  91                return ret;
  92
  93        ret = regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN,
  94                                        value << CTZ(SKY81452_EN));
  95        if (ret < 0)
  96                return ret;
  97
  98        return count;
  99}
 100
 101static ssize_t sky81452_bl_show_open_short(struct device *dev,
 102                struct device_attribute *attr, char *buf)
 103{
 104        struct regmap *regmap = bl_get_data(to_backlight_device(dev));
 105        unsigned int reg, value = 0;
 106        char tmp[3];
 107        int i, ret;
 108
 109        reg = !strcmp(attr->attr.name, "open") ? SKY81452_REG5 : SKY81452_REG4;
 110        ret = regmap_read(regmap, reg, &value);
 111        if (ret < 0)
 112                return ret;
 113
 114        if (value & SKY81452_SHRT) {
 115                *buf = 0;
 116                for (i = 0; i < 6; i++) {
 117                        if (value & 0x01) {
 118                                sprintf(tmp, "%d ", i + 1);
 119                                strcat(buf, tmp);
 120                        }
 121                        value >>= 1;
 122                }
 123                strcat(buf, "\n");
 124        } else {
 125                strcpy(buf, "none\n");
 126        }
 127
 128        return strlen(buf);
 129}
 130
 131static ssize_t sky81452_bl_show_fault(struct device *dev,
 132                struct device_attribute *attr, char *buf)
 133{
 134        struct regmap *regmap = bl_get_data(to_backlight_device(dev));
 135        unsigned int value = 0;
 136        int ret;
 137
 138        ret = regmap_read(regmap, SKY81452_REG4, &value);
 139        if (ret < 0)
 140                return ret;
 141
 142        *buf = 0;
 143
 144        if (value & SKY81452_OCP)
 145                strcat(buf, "over-current ");
 146
 147        if (value & SKY81452_OTMP)
 148                strcat(buf, "over-temperature");
 149
 150        strcat(buf, "\n");
 151        return strlen(buf);
 152}
 153
 154static DEVICE_ATTR(enable, S_IWGRP | S_IWUSR, NULL, sky81452_bl_store_enable);
 155static DEVICE_ATTR(open, S_IRUGO, sky81452_bl_show_open_short, NULL);
 156static DEVICE_ATTR(short, S_IRUGO, sky81452_bl_show_open_short, NULL);
 157static DEVICE_ATTR(fault, S_IRUGO, sky81452_bl_show_fault, NULL);
 158
 159static struct attribute *sky81452_bl_attribute[] = {
 160        &dev_attr_enable.attr,
 161        &dev_attr_open.attr,
 162        &dev_attr_short.attr,
 163        &dev_attr_fault.attr,
 164        NULL
 165};
 166
 167static const struct attribute_group sky81452_bl_attr_group = {
 168        .attrs = sky81452_bl_attribute,
 169};
 170
 171#ifdef CONFIG_OF
 172static struct sky81452_bl_platform_data *sky81452_bl_parse_dt(
 173                                                        struct device *dev)
 174{
 175        struct device_node *np = of_node_get(dev->of_node);
 176        struct sky81452_bl_platform_data *pdata;
 177        int num_entry;
 178        unsigned int sources[6];
 179        int ret;
 180
 181        if (!np) {
 182                dev_err(dev, "backlight node not found.\n");
 183                return ERR_PTR(-ENODATA);
 184        }
 185
 186        pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
 187        if (!pdata) {
 188                of_node_put(np);
 189                return ERR_PTR(-ENOMEM);
 190        }
 191
 192        of_property_read_string(np, "name", &pdata->name);
 193        pdata->ignore_pwm = of_property_read_bool(np, "skyworks,ignore-pwm");
 194        pdata->dpwm_mode = of_property_read_bool(np, "skyworks,dpwm-mode");
 195        pdata->phase_shift = of_property_read_bool(np, "skyworks,phase-shift");
 196        pdata->gpio_enable = of_get_gpio(np, 0);
 197
 198        ret = of_property_count_u32_elems(np, "led-sources");
 199        if (ret < 0) {
 200                pdata->enable = SKY81452_EN >> CTZ(SKY81452_EN);
 201        } else {
 202                num_entry = ret;
 203                if (num_entry > 6)
 204                        num_entry = 6;
 205
 206                ret = of_property_read_u32_array(np, "led-sources", sources,
 207                                        num_entry);
 208                if (ret < 0) {
 209                        dev_err(dev, "led-sources node is invalid.\n");
 210                        return ERR_PTR(-EINVAL);
 211                }
 212
 213                pdata->enable = 0;
 214                while (--num_entry)
 215                        pdata->enable |= (1 << sources[num_entry]);
 216        }
 217
 218        ret = of_property_read_u32(np,
 219                        "skyworks,short-detection-threshold-volt",
 220                        &pdata->short_detection_threshold);
 221        if (ret < 0)
 222                pdata->short_detection_threshold = 7;
 223
 224        ret = of_property_read_u32(np, "skyworks,current-limit-mA",
 225                        &pdata->boost_current_limit);
 226        if (ret < 0)
 227                pdata->boost_current_limit = 2750;
 228
 229        of_node_put(np);
 230        return pdata;
 231}
 232#else
 233static struct sky81452_bl_platform_data *sky81452_bl_parse_dt(
 234                                                        struct device *dev)
 235{
 236        return ERR_PTR(-EINVAL);
 237}
 238#endif
 239
 240static int sky81452_bl_init_device(struct regmap *regmap,
 241                struct sky81452_bl_platform_data *pdata)
 242{
 243        unsigned int value;
 244
 245        value = pdata->ignore_pwm ? SKY81452_IGPW : 0;
 246        value |= pdata->dpwm_mode ? SKY81452_PWMMD : 0;
 247        value |= pdata->phase_shift ? 0 : SKY81452_PHASE;
 248
 249        if (pdata->boost_current_limit == 2300)
 250                value |= SKY81452_ILIM;
 251        else if (pdata->boost_current_limit != 2750)
 252                return -EINVAL;
 253
 254        if (pdata->short_detection_threshold < 4 ||
 255                                pdata->short_detection_threshold > 7)
 256                return -EINVAL;
 257        value |= (7 - pdata->short_detection_threshold) << CTZ(SKY81452_VSHRT);
 258
 259        return regmap_write(regmap, SKY81452_REG2, value);
 260}
 261
 262static int sky81452_bl_probe(struct platform_device *pdev)
 263{
 264        struct device *dev = &pdev->dev;
 265        struct regmap *regmap = dev_get_drvdata(dev->parent);
 266        struct sky81452_bl_platform_data *pdata = dev_get_platdata(dev);
 267        struct backlight_device *bd;
 268        struct backlight_properties props;
 269        const char *name;
 270        int ret;
 271
 272        if (!pdata) {
 273                pdata = sky81452_bl_parse_dt(dev);
 274                if (IS_ERR(pdata))
 275                        return PTR_ERR(pdata);
 276        }
 277
 278        if (gpio_is_valid(pdata->gpio_enable)) {
 279                ret = devm_gpio_request_one(dev, pdata->gpio_enable,
 280                                        GPIOF_OUT_INIT_HIGH, "sky81452-en");
 281                if (ret < 0) {
 282                        dev_err(dev, "failed to request GPIO. err=%d\n", ret);
 283                        return ret;
 284                }
 285        }
 286
 287        ret = sky81452_bl_init_device(regmap, pdata);
 288        if (ret < 0) {
 289                dev_err(dev, "failed to initialize. err=%d\n", ret);
 290                return ret;
 291        }
 292
 293        memset(&props, 0, sizeof(props));
 294        props.max_brightness = SKY81452_MAX_BRIGHTNESS,
 295        name = pdata->name ? pdata->name : SKY81452_DEFAULT_NAME;
 296        bd = devm_backlight_device_register(dev, name, dev, regmap,
 297                                                &sky81452_bl_ops, &props);
 298        if (IS_ERR(bd)) {
 299                dev_err(dev, "failed to register. err=%ld\n", PTR_ERR(bd));
 300                return PTR_ERR(bd);
 301        }
 302
 303        platform_set_drvdata(pdev, bd);
 304
 305        ret = sysfs_create_group(&bd->dev.kobj, &sky81452_bl_attr_group);
 306        if (ret < 0) {
 307                dev_err(dev, "failed to create attribute. err=%d\n", ret);
 308                return ret;
 309        }
 310
 311        return ret;
 312}
 313
 314static int sky81452_bl_remove(struct platform_device *pdev)
 315{
 316        const struct sky81452_bl_platform_data *pdata =
 317                                                dev_get_platdata(&pdev->dev);
 318        struct backlight_device *bd = platform_get_drvdata(pdev);
 319
 320        sysfs_remove_group(&bd->dev.kobj, &sky81452_bl_attr_group);
 321
 322        bd->props.power = FB_BLANK_UNBLANK;
 323        bd->props.brightness = 0;
 324        backlight_update_status(bd);
 325
 326        if (gpio_is_valid(pdata->gpio_enable))
 327                gpio_set_value_cansleep(pdata->gpio_enable, 0);
 328
 329        return 0;
 330}
 331
 332#ifdef CONFIG_OF
 333static const struct of_device_id sky81452_bl_of_match[] = {
 334        { .compatible = "skyworks,sky81452-backlight", },
 335        { }
 336};
 337MODULE_DEVICE_TABLE(of, sky81452_bl_of_match);
 338#endif
 339
 340static struct platform_driver sky81452_bl_driver = {
 341        .driver = {
 342                .name = "sky81452-backlight",
 343                .of_match_table = of_match_ptr(sky81452_bl_of_match),
 344        },
 345        .probe = sky81452_bl_probe,
 346        .remove = sky81452_bl_remove,
 347};
 348
 349module_platform_driver(sky81452_bl_driver);
 350
 351MODULE_DESCRIPTION("Skyworks SKY81452 backlight driver");
 352MODULE_AUTHOR("Gyungoh Yoo <jack.yoo@skyworksinc.com>");
 353MODULE_LICENSE("GPL v2");
 354