linux/drivers/video/backlight/rt4831-backlight.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2
   3#include <dt-bindings/leds/rt4831-backlight.h>
   4#include <linux/backlight.h>
   5#include <linux/bitops.h>
   6#include <linux/kernel.h>
   7#include <linux/module.h>
   8#include <linux/platform_device.h>
   9#include <linux/property.h>
  10#include <linux/regmap.h>
  11
  12#define RT4831_REG_BLCFG        0x02
  13#define RT4831_REG_BLDIML       0x04
  14#define RT4831_REG_ENABLE       0x08
  15
  16#define RT4831_BLMAX_BRIGHTNESS 2048
  17
  18#define RT4831_BLOVP_MASK       GENMASK(7, 5)
  19#define RT4831_BLOVP_SHIFT      5
  20#define RT4831_BLPWMEN_MASK     BIT(0)
  21#define RT4831_BLEN_MASK        BIT(4)
  22#define RT4831_BLCH_MASK        GENMASK(3, 0)
  23#define RT4831_BLDIML_MASK      GENMASK(2, 0)
  24#define RT4831_BLDIMH_MASK      GENMASK(10, 3)
  25#define RT4831_BLDIMH_SHIFT     3
  26
  27struct rt4831_priv {
  28        struct device *dev;
  29        struct regmap *regmap;
  30        struct backlight_device *bl;
  31};
  32
  33static int rt4831_bl_update_status(struct backlight_device *bl_dev)
  34{
  35        struct rt4831_priv *priv = bl_get_data(bl_dev);
  36        int brightness = backlight_get_brightness(bl_dev);
  37        unsigned int enable = brightness ? RT4831_BLEN_MASK : 0;
  38        u8 v[2];
  39        int ret;
  40
  41        if (brightness) {
  42                v[0] = (brightness - 1) & RT4831_BLDIML_MASK;
  43                v[1] = ((brightness - 1) & RT4831_BLDIMH_MASK) >> RT4831_BLDIMH_SHIFT;
  44
  45                ret = regmap_raw_write(priv->regmap, RT4831_REG_BLDIML, v, sizeof(v));
  46                if (ret)
  47                        return ret;
  48        }
  49
  50        return regmap_update_bits(priv->regmap, RT4831_REG_ENABLE, RT4831_BLEN_MASK, enable);
  51
  52}
  53
  54static int rt4831_bl_get_brightness(struct backlight_device *bl_dev)
  55{
  56        struct rt4831_priv *priv = bl_get_data(bl_dev);
  57        unsigned int val;
  58        u8 v[2];
  59        int ret;
  60
  61        ret = regmap_read(priv->regmap, RT4831_REG_ENABLE, &val);
  62        if (ret)
  63                return ret;
  64
  65        if (!(val & RT4831_BLEN_MASK))
  66                return 0;
  67
  68        ret = regmap_raw_read(priv->regmap, RT4831_REG_BLDIML, v, sizeof(v));
  69        if (ret)
  70                return ret;
  71
  72        ret = (v[1] << RT4831_BLDIMH_SHIFT) + (v[0] & RT4831_BLDIML_MASK) + 1;
  73
  74        return ret;
  75}
  76
  77static const struct backlight_ops rt4831_bl_ops = {
  78        .options = BL_CORE_SUSPENDRESUME,
  79        .update_status = rt4831_bl_update_status,
  80        .get_brightness = rt4831_bl_get_brightness,
  81};
  82
  83static int rt4831_parse_backlight_properties(struct rt4831_priv *priv,
  84                                             struct backlight_properties *bl_props)
  85{
  86        struct device *dev = priv->dev;
  87        u8 propval;
  88        u32 brightness;
  89        unsigned int val = 0;
  90        int ret;
  91
  92        /* common properties */
  93        ret = device_property_read_u32(dev, "max-brightness", &brightness);
  94        if (ret)
  95                brightness = RT4831_BLMAX_BRIGHTNESS;
  96
  97        bl_props->max_brightness = min_t(u32, brightness, RT4831_BLMAX_BRIGHTNESS);
  98
  99        ret = device_property_read_u32(dev, "default-brightness", &brightness);
 100        if (ret)
 101                brightness = bl_props->max_brightness;
 102
 103        bl_props->brightness = min_t(u32, brightness, bl_props->max_brightness);
 104
 105        /* vendor properties */
 106        if (device_property_read_bool(dev, "richtek,pwm-enable"))
 107                val = RT4831_BLPWMEN_MASK;
 108
 109        ret = regmap_update_bits(priv->regmap, RT4831_REG_BLCFG, RT4831_BLPWMEN_MASK, val);
 110        if (ret)
 111                return ret;
 112
 113        ret = device_property_read_u8(dev, "richtek,bled-ovp-sel", &propval);
 114        if (ret)
 115                propval = RT4831_BLOVPLVL_21V;
 116
 117        propval = min_t(u8, propval, RT4831_BLOVPLVL_29V);
 118        ret = regmap_update_bits(priv->regmap, RT4831_REG_BLCFG, RT4831_BLOVP_MASK,
 119                                 propval << RT4831_BLOVP_SHIFT);
 120        if (ret)
 121                return ret;
 122
 123        ret = device_property_read_u8(dev, "richtek,channel-use", &propval);
 124        if (ret) {
 125                dev_err(dev, "richtek,channel-use DT property missing\n");
 126                return ret;
 127        }
 128
 129        if (!(propval & RT4831_BLCH_MASK)) {
 130                dev_err(dev, "No channel specified\n");
 131                return -EINVAL;
 132        }
 133
 134        return regmap_update_bits(priv->regmap, RT4831_REG_ENABLE, RT4831_BLCH_MASK, propval);
 135}
 136
 137static int rt4831_bl_probe(struct platform_device *pdev)
 138{
 139        struct rt4831_priv *priv;
 140        struct backlight_properties bl_props = { .type = BACKLIGHT_RAW,
 141                                                 .scale = BACKLIGHT_SCALE_LINEAR };
 142        int ret;
 143
 144        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 145        if (!priv)
 146                return -ENOMEM;
 147
 148        priv->dev = &pdev->dev;
 149
 150        priv->regmap = dev_get_regmap(pdev->dev.parent, NULL);
 151        if (!priv->regmap) {
 152                dev_err(&pdev->dev, "Failed to init regmap\n");
 153                return -ENODEV;
 154        }
 155
 156        ret = rt4831_parse_backlight_properties(priv, &bl_props);
 157        if (ret) {
 158                dev_err(&pdev->dev, "Failed to parse backlight properties\n");
 159                return ret;
 160        }
 161
 162        priv->bl = devm_backlight_device_register(&pdev->dev, pdev->name, &pdev->dev, priv,
 163                                                  &rt4831_bl_ops, &bl_props);
 164        if (IS_ERR(priv->bl)) {
 165                dev_err(&pdev->dev, "Failed to register backlight\n");
 166                return PTR_ERR(priv->bl);
 167        }
 168
 169        backlight_update_status(priv->bl);
 170        platform_set_drvdata(pdev, priv);
 171
 172        return 0;
 173}
 174
 175static int rt4831_bl_remove(struct platform_device *pdev)
 176{
 177        struct rt4831_priv *priv = platform_get_drvdata(pdev);
 178        struct backlight_device *bl_dev = priv->bl;
 179
 180        bl_dev->props.brightness = 0;
 181        backlight_update_status(priv->bl);
 182
 183        return 0;
 184}
 185
 186static const struct of_device_id __maybe_unused rt4831_bl_of_match[] = {
 187        { .compatible = "richtek,rt4831-backlight", },
 188        {}
 189};
 190MODULE_DEVICE_TABLE(of, rt4831_bl_of_match);
 191
 192static struct platform_driver rt4831_bl_driver = {
 193        .driver = {
 194                .name = "rt4831-backlight",
 195                .of_match_table = rt4831_bl_of_match,
 196        },
 197        .probe = rt4831_bl_probe,
 198        .remove = rt4831_bl_remove,
 199};
 200module_platform_driver(rt4831_bl_driver);
 201
 202MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
 203MODULE_LICENSE("GPL v2");
 204