linux/drivers/video/backlight/sky81452-backlight.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * sky81452-backlight.c SKY81452 backlight driver
   4 *
   5 * Copyright 2014 Skyworks Solutions Inc.
   6 * Author : Gyungoh Yoo <jack.yoo@skyworksinc.com>
   7 */
   8
   9#include <linux/backlight.h>
  10#include <linux/err.h>
  11#include <linux/gpio/consumer.h>
  12#include <linux/init.h>
  13#include <linux/kernel.h>
  14#include <linux/module.h>
  15#include <linux/of.h>
  16#include <linux/platform_device.h>
  17#include <linux/regmap.h>
  18#include <linux/slab.h>
  19
  20/* registers */
  21#define SKY81452_REG0   0x00
  22#define SKY81452_REG1   0x01
  23#define SKY81452_REG2   0x02
  24#define SKY81452_REG4   0x04
  25#define SKY81452_REG5   0x05
  26
  27/* bit mask */
  28#define SKY81452_CS     0xFF
  29#define SKY81452_EN     0x3F
  30#define SKY81452_IGPW   0x20
  31#define SKY81452_PWMMD  0x10
  32#define SKY81452_PHASE  0x08
  33#define SKY81452_ILIM   0x04
  34#define SKY81452_VSHRT  0x03
  35#define SKY81452_OCP    0x80
  36#define SKY81452_OTMP   0x40
  37#define SKY81452_SHRT   0x3F
  38#define SKY81452_OPN    0x3F
  39
  40#define SKY81452_DEFAULT_NAME "lcd-backlight"
  41#define SKY81452_MAX_BRIGHTNESS (SKY81452_CS + 1)
  42
  43/**
  44 * struct sky81452_platform_data
  45 * @name:       backlight driver name.
  46 *              If it is not defined, default name is lcd-backlight.
  47 * @gpiod_enable:GPIO descriptor which control EN pin
  48 * @enable:     Enable mask for current sink channel 1, 2, 3, 4, 5 and 6.
  49 * @ignore_pwm: true if DPWMI should be ignored.
  50 * @dpwm_mode:  true is DPWM dimming mode, otherwise Analog dimming mode.
  51 * @phase_shift:true is phase shift mode.
  52 * @short_detection_threshold:  It should be one of 4, 5, 6 and 7V.
  53 * @boost_current_limit:        It should be one of 2300, 2750mA.
  54 */
  55struct sky81452_bl_platform_data {
  56        const char *name;
  57        struct gpio_desc *gpiod_enable;
  58        unsigned int enable;
  59        bool ignore_pwm;
  60        bool dpwm_mode;
  61        bool phase_shift;
  62        unsigned int short_detection_threshold;
  63        unsigned int boost_current_limit;
  64};
  65
  66#define CTZ(b) __builtin_ctz(b)
  67
  68static int sky81452_bl_update_status(struct backlight_device *bd)
  69{
  70        const struct sky81452_bl_platform_data *pdata =
  71                        dev_get_platdata(bd->dev.parent);
  72        const unsigned int brightness = (unsigned int)bd->props.brightness;
  73        struct regmap *regmap = bl_get_data(bd);
  74        int ret;
  75
  76        if (brightness > 0) {
  77                ret = regmap_write(regmap, SKY81452_REG0, brightness - 1);
  78                if (ret < 0)
  79                        return ret;
  80
  81                return regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN,
  82                                        pdata->enable << CTZ(SKY81452_EN));
  83        }
  84
  85        return regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN, 0);
  86}
  87
  88static const struct backlight_ops sky81452_bl_ops = {
  89        .update_status = sky81452_bl_update_status,
  90};
  91
  92static ssize_t sky81452_bl_store_enable(struct device *dev,
  93                struct device_attribute *attr, const char *buf, size_t count)
  94{
  95        struct regmap *regmap = bl_get_data(to_backlight_device(dev));
  96        unsigned long value;
  97        int ret;
  98
  99        ret = kstrtoul(buf, 16, &value);
 100        if (ret < 0)
 101                return ret;
 102
 103        ret = regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN,
 104                                        value << CTZ(SKY81452_EN));
 105        if (ret < 0)
 106                return ret;
 107
 108        return count;
 109}
 110
 111static ssize_t sky81452_bl_show_open_short(struct device *dev,
 112                struct device_attribute *attr, char *buf)
 113{
 114        struct regmap *regmap = bl_get_data(to_backlight_device(dev));
 115        unsigned int reg, value = 0;
 116        char tmp[3];
 117        int i, ret;
 118
 119        reg = !strcmp(attr->attr.name, "open") ? SKY81452_REG5 : SKY81452_REG4;
 120        ret = regmap_read(regmap, reg, &value);
 121        if (ret < 0)
 122                return ret;
 123
 124        if (value & SKY81452_SHRT) {
 125                *buf = 0;
 126                for (i = 0; i < 6; i++) {
 127                        if (value & 0x01) {
 128                                sprintf(tmp, "%d ", i + 1);
 129                                strcat(buf, tmp);
 130                        }
 131                        value >>= 1;
 132                }
 133                strcat(buf, "\n");
 134        } else {
 135                strcpy(buf, "none\n");
 136        }
 137
 138        return strlen(buf);
 139}
 140
 141static ssize_t sky81452_bl_show_fault(struct device *dev,
 142                struct device_attribute *attr, char *buf)
 143{
 144        struct regmap *regmap = bl_get_data(to_backlight_device(dev));
 145        unsigned int value = 0;
 146        int ret;
 147
 148        ret = regmap_read(regmap, SKY81452_REG4, &value);
 149        if (ret < 0)
 150                return ret;
 151
 152        *buf = 0;
 153
 154        if (value & SKY81452_OCP)
 155                strcat(buf, "over-current ");
 156
 157        if (value & SKY81452_OTMP)
 158                strcat(buf, "over-temperature");
 159
 160        strcat(buf, "\n");
 161        return strlen(buf);
 162}
 163
 164static DEVICE_ATTR(enable, S_IWGRP | S_IWUSR, NULL, sky81452_bl_store_enable);
 165static DEVICE_ATTR(open, S_IRUGO, sky81452_bl_show_open_short, NULL);
 166static DEVICE_ATTR(short, S_IRUGO, sky81452_bl_show_open_short, NULL);
 167static DEVICE_ATTR(fault, S_IRUGO, sky81452_bl_show_fault, NULL);
 168
 169static struct attribute *sky81452_bl_attribute[] = {
 170        &dev_attr_enable.attr,
 171        &dev_attr_open.attr,
 172        &dev_attr_short.attr,
 173        &dev_attr_fault.attr,
 174        NULL
 175};
 176
 177static const struct attribute_group sky81452_bl_attr_group = {
 178        .attrs = sky81452_bl_attribute,
 179};
 180
 181#ifdef CONFIG_OF
 182static struct sky81452_bl_platform_data *sky81452_bl_parse_dt(
 183                                                        struct device *dev)
 184{
 185        struct device_node *np = of_node_get(dev->of_node);
 186        struct sky81452_bl_platform_data *pdata;
 187        int num_entry;
 188        unsigned int sources[6];
 189        int ret;
 190
 191        if (!np) {
 192                dev_err(dev, "backlight node not found.\n");
 193                return ERR_PTR(-ENODATA);
 194        }
 195
 196        pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
 197        if (!pdata) {
 198                of_node_put(np);
 199                return ERR_PTR(-ENOMEM);
 200        }
 201
 202        of_property_read_string(np, "name", &pdata->name);
 203        pdata->ignore_pwm = of_property_read_bool(np, "skyworks,ignore-pwm");
 204        pdata->dpwm_mode = of_property_read_bool(np, "skyworks,dpwm-mode");
 205        pdata->phase_shift = of_property_read_bool(np, "skyworks,phase-shift");
 206        pdata->gpiod_enable = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH);
 207
 208        ret = of_property_count_u32_elems(np, "led-sources");
 209        if (ret < 0) {
 210                pdata->enable = SKY81452_EN >> CTZ(SKY81452_EN);
 211        } else {
 212                num_entry = ret;
 213                if (num_entry > 6)
 214                        num_entry = 6;
 215
 216                ret = of_property_read_u32_array(np, "led-sources", sources,
 217                                        num_entry);
 218                if (ret < 0) {
 219                        dev_err(dev, "led-sources node is invalid.\n");
 220                        of_node_put(np);
 221                        return ERR_PTR(-EINVAL);
 222                }
 223
 224                pdata->enable = 0;
 225                while (--num_entry)
 226                        pdata->enable |= (1 << sources[num_entry]);
 227        }
 228
 229        ret = of_property_read_u32(np,
 230                        "skyworks,short-detection-threshold-volt",
 231                        &pdata->short_detection_threshold);
 232        if (ret < 0)
 233                pdata->short_detection_threshold = 7;
 234
 235        ret = of_property_read_u32(np, "skyworks,current-limit-mA",
 236                        &pdata->boost_current_limit);
 237        if (ret < 0)
 238                pdata->boost_current_limit = 2750;
 239
 240        of_node_put(np);
 241        return pdata;
 242}
 243#else
 244static struct sky81452_bl_platform_data *sky81452_bl_parse_dt(
 245                                                        struct device *dev)
 246{
 247        return ERR_PTR(-EINVAL);
 248}
 249#endif
 250
 251static int sky81452_bl_init_device(struct regmap *regmap,
 252                struct sky81452_bl_platform_data *pdata)
 253{
 254        unsigned int value;
 255
 256        value = pdata->ignore_pwm ? SKY81452_IGPW : 0;
 257        value |= pdata->dpwm_mode ? SKY81452_PWMMD : 0;
 258        value |= pdata->phase_shift ? 0 : SKY81452_PHASE;
 259
 260        if (pdata->boost_current_limit == 2300)
 261                value |= SKY81452_ILIM;
 262        else if (pdata->boost_current_limit != 2750)
 263                return -EINVAL;
 264
 265        if (pdata->short_detection_threshold < 4 ||
 266                                pdata->short_detection_threshold > 7)
 267                return -EINVAL;
 268        value |= (7 - pdata->short_detection_threshold) << CTZ(SKY81452_VSHRT);
 269
 270        return regmap_write(regmap, SKY81452_REG2, value);
 271}
 272
 273static int sky81452_bl_probe(struct platform_device *pdev)
 274{
 275        struct device *dev = &pdev->dev;
 276        struct regmap *regmap = dev_get_drvdata(dev->parent);
 277        struct sky81452_bl_platform_data *pdata;
 278        struct backlight_device *bd;
 279        struct backlight_properties props;
 280        const char *name;
 281        int ret;
 282
 283        pdata = sky81452_bl_parse_dt(dev);
 284        if (IS_ERR(pdata))
 285                return PTR_ERR(pdata);
 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 (pdata->gpiod_enable)
 327                gpiod_set_value_cansleep(pdata->gpiod_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