234 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2 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 0x0213#define RT4831_REG_BLDIML 0x0414#define RT4831_REG_ENABLE 0x0815#define RT4831_REG_BLOPT2 0x1116 17#define RT4831_BLMAX_BRIGHTNESS 204818 19#define RT4831_BLOVP_MASK GENMASK(7, 5)20#define RT4831_BLOVP_SHIFT 521#define RT4831_BLPWMEN_MASK BIT(0)22#define RT4831_BLEN_MASK BIT(4)23#define RT4831_BLCH_MASK GENMASK(3, 0)24#define RT4831_BLDIML_MASK GENMASK(2, 0)25#define RT4831_BLDIMH_MASK GENMASK(10, 3)26#define RT4831_BLDIMH_SHIFT 327#define RT4831_BLOCP_MASK GENMASK(1, 0)28 29#define RT4831_BLOCP_MINUA 90000030#define RT4831_BLOCP_MAXUA 180000031#define RT4831_BLOCP_STEPUA 30000032 33struct rt4831_priv {34 struct device *dev;35 struct regmap *regmap;36 struct backlight_device *bl;37};38 39static int rt4831_bl_update_status(struct backlight_device *bl_dev)40{41 struct rt4831_priv *priv = bl_get_data(bl_dev);42 int brightness = backlight_get_brightness(bl_dev);43 unsigned int enable = brightness ? RT4831_BLEN_MASK : 0;44 u8 v[2];45 int ret;46 47 if (brightness) {48 v[0] = (brightness - 1) & RT4831_BLDIML_MASK;49 v[1] = ((brightness - 1) & RT4831_BLDIMH_MASK) >> RT4831_BLDIMH_SHIFT;50 51 ret = regmap_raw_write(priv->regmap, RT4831_REG_BLDIML, v, sizeof(v));52 if (ret)53 return ret;54 }55 56 return regmap_update_bits(priv->regmap, RT4831_REG_ENABLE, RT4831_BLEN_MASK, enable);57 58}59 60static int rt4831_bl_get_brightness(struct backlight_device *bl_dev)61{62 struct rt4831_priv *priv = bl_get_data(bl_dev);63 unsigned int val;64 u8 v[2];65 int ret;66 67 ret = regmap_read(priv->regmap, RT4831_REG_ENABLE, &val);68 if (ret)69 return ret;70 71 if (!(val & RT4831_BLEN_MASK))72 return 0;73 74 ret = regmap_raw_read(priv->regmap, RT4831_REG_BLDIML, v, sizeof(v));75 if (ret)76 return ret;77 78 ret = (v[1] << RT4831_BLDIMH_SHIFT) + (v[0] & RT4831_BLDIML_MASK) + 1;79 80 return ret;81}82 83static const struct backlight_ops rt4831_bl_ops = {84 .options = BL_CORE_SUSPENDRESUME,85 .update_status = rt4831_bl_update_status,86 .get_brightness = rt4831_bl_get_brightness,87};88 89static int rt4831_parse_backlight_properties(struct rt4831_priv *priv,90 struct backlight_properties *bl_props)91{92 struct device *dev = priv->dev;93 u8 propval;94 u32 brightness, ocp_uA;95 unsigned int val = 0;96 int ret;97 98 /* common properties */99 ret = device_property_read_u32(dev, "max-brightness", &brightness);100 if (ret)101 brightness = RT4831_BLMAX_BRIGHTNESS;102 103 bl_props->max_brightness = min_t(u32, brightness, RT4831_BLMAX_BRIGHTNESS);104 105 ret = device_property_read_u32(dev, "default-brightness", &brightness);106 if (ret)107 brightness = bl_props->max_brightness;108 109 bl_props->brightness = min_t(u32, brightness, bl_props->max_brightness);110 111 /* vendor properties */112 if (device_property_read_bool(dev, "richtek,pwm-enable"))113 val = RT4831_BLPWMEN_MASK;114 115 ret = regmap_update_bits(priv->regmap, RT4831_REG_BLCFG, RT4831_BLPWMEN_MASK, val);116 if (ret)117 return ret;118 119 ret = device_property_read_u8(dev, "richtek,bled-ovp-sel", &propval);120 if (ret)121 propval = RT4831_BLOVPLVL_21V;122 123 propval = min_t(u8, propval, RT4831_BLOVPLVL_29V);124 ret = regmap_update_bits(priv->regmap, RT4831_REG_BLCFG, RT4831_BLOVP_MASK,125 propval << RT4831_BLOVP_SHIFT);126 if (ret)127 return ret;128 129 /*130 * This OCP level is used to protect and limit the inductor current.131 * If inductor peak current reach the level, low-side MOSFET will be132 * turned off. Meanwhile, the output channel current may be limited.133 * To match the configured channel current, the inductor chosen must134 * be higher than the OCP level.135 *136 * Not like the OVP level, the default 21V can be used in the most137 * application. But if the chosen OCP level is smaller than needed,138 * it will also affect the backlight channel output current to be139 * smaller than the register setting.140 */141 ret = device_property_read_u32(dev, "richtek,bled-ocp-microamp",142 &ocp_uA);143 if (!ret) {144 ocp_uA = clamp_val(ocp_uA, RT4831_BLOCP_MINUA,145 RT4831_BLOCP_MAXUA);146 val = DIV_ROUND_UP(ocp_uA - RT4831_BLOCP_MINUA,147 RT4831_BLOCP_STEPUA);148 ret = regmap_update_bits(priv->regmap, RT4831_REG_BLOPT2,149 RT4831_BLOCP_MASK, val);150 if (ret)151 return ret;152 }153 154 ret = device_property_read_u8(dev, "richtek,channel-use", &propval);155 if (ret) {156 dev_err(dev, "richtek,channel-use DT property missing\n");157 return ret;158 }159 160 if (!(propval & RT4831_BLCH_MASK)) {161 dev_err(dev, "No channel specified\n");162 return -EINVAL;163 }164 165 return regmap_update_bits(priv->regmap, RT4831_REG_ENABLE, RT4831_BLCH_MASK, propval);166}167 168static int rt4831_bl_probe(struct platform_device *pdev)169{170 struct rt4831_priv *priv;171 struct backlight_properties bl_props = { .type = BACKLIGHT_RAW,172 .scale = BACKLIGHT_SCALE_LINEAR };173 int ret;174 175 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);176 if (!priv)177 return -ENOMEM;178 179 priv->dev = &pdev->dev;180 181 priv->regmap = dev_get_regmap(pdev->dev.parent, NULL);182 if (!priv->regmap) {183 dev_err(&pdev->dev, "Failed to init regmap\n");184 return -ENODEV;185 }186 187 ret = rt4831_parse_backlight_properties(priv, &bl_props);188 if (ret) {189 dev_err(&pdev->dev, "Failed to parse backlight properties\n");190 return ret;191 }192 193 priv->bl = devm_backlight_device_register(&pdev->dev, pdev->name, &pdev->dev, priv,194 &rt4831_bl_ops, &bl_props);195 if (IS_ERR(priv->bl)) {196 dev_err(&pdev->dev, "Failed to register backlight\n");197 return PTR_ERR(priv->bl);198 }199 200 backlight_update_status(priv->bl);201 platform_set_drvdata(pdev, priv);202 203 return 0;204}205 206static void rt4831_bl_remove(struct platform_device *pdev)207{208 struct rt4831_priv *priv = platform_get_drvdata(pdev);209 struct backlight_device *bl_dev = priv->bl;210 211 bl_dev->props.brightness = 0;212 backlight_update_status(priv->bl);213}214 215static const struct of_device_id __maybe_unused rt4831_bl_of_match[] = {216 { .compatible = "richtek,rt4831-backlight", },217 {}218};219MODULE_DEVICE_TABLE(of, rt4831_bl_of_match);220 221static struct platform_driver rt4831_bl_driver = {222 .driver = {223 .name = "rt4831-backlight",224 .of_match_table = rt4831_bl_of_match,225 },226 .probe = rt4831_bl_probe,227 .remove_new = rt4831_bl_remove,228};229module_platform_driver(rt4831_bl_driver);230 231MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");232MODULE_DESCRIPTION("Richtek RT4831 Backlight Driver");233MODULE_LICENSE("GPL v2");234