344 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2#include <linux/backlight.h>3#include <linux/delay.h>4#include <linux/gpio/consumer.h>5#include <linux/i2c.h>6#include <linux/module.h>7#include <linux/regmap.h>8 9#define LM3509_NAME "lm3509_bl"10 11#define LM3509_SINK_MAIN 012#define LM3509_SINK_SUB 113#define LM3509_NUM_SINKS 214 15#define LM3509_DEF_BRIGHTNESS 0x1216#define LM3509_MAX_BRIGHTNESS 0x1F17 18#define REG_GP 0x1019#define REG_BMAIN 0xA020#define REG_BSUB 0xB021#define REG_MAX 0xFF22 23enum {24 REG_GP_ENM_BIT = 0,25 REG_GP_ENS_BIT,26 REG_GP_UNI_BIT,27 REG_GP_RMP0_BIT,28 REG_GP_RMP1_BIT,29 REG_GP_OLED_BIT,30};31 32struct lm3509_bl {33 struct regmap *regmap;34 struct backlight_device *bl_main;35 struct backlight_device *bl_sub;36 struct gpio_desc *reset_gpio;37};38 39struct lm3509_bl_led_data {40 const char *label;41 int led_sources;42 u32 brightness;43 u32 max_brightness;44};45 46static void lm3509_reset(struct lm3509_bl *data)47{48 if (data->reset_gpio) {49 gpiod_set_value(data->reset_gpio, 1);50 udelay(1);51 gpiod_set_value(data->reset_gpio, 0);52 udelay(10);53 }54}55 56static int lm3509_update_status(struct backlight_device *bl,57 unsigned int en_mask, unsigned int br_reg)58{59 struct lm3509_bl *data = bl_get_data(bl);60 int ret;61 bool en;62 63 ret = regmap_write(data->regmap, br_reg, backlight_get_brightness(bl));64 if (ret < 0)65 return ret;66 67 en = !backlight_is_blank(bl);68 return regmap_update_bits(data->regmap, REG_GP, en_mask,69 en ? en_mask : 0);70}71 72static int lm3509_main_update_status(struct backlight_device *bl)73{74 return lm3509_update_status(bl, BIT(REG_GP_ENM_BIT), REG_BMAIN);75}76 77static const struct backlight_ops lm3509_main_ops = {78 .options = BL_CORE_SUSPENDRESUME,79 .update_status = lm3509_main_update_status,80};81 82static int lm3509_sub_update_status(struct backlight_device *bl)83{84 return lm3509_update_status(bl, BIT(REG_GP_ENS_BIT), REG_BSUB);85}86 87static const struct backlight_ops lm3509_sub_ops = {88 .options = BL_CORE_SUSPENDRESUME,89 .update_status = lm3509_sub_update_status,90};91 92static struct backlight_device *93lm3509_backlight_register(struct device *dev, const char *name_suffix,94 struct lm3509_bl *data,95 const struct backlight_ops *ops,96 const struct lm3509_bl_led_data *led_data)97 98{99 struct backlight_device *bd;100 struct backlight_properties props;101 const char *label = led_data->label;102 char name[64];103 104 memset(&props, 0, sizeof(props));105 props.type = BACKLIGHT_RAW;106 props.brightness = led_data->brightness;107 props.max_brightness = led_data->max_brightness;108 props.scale = BACKLIGHT_SCALE_NON_LINEAR;109 110 if (!label) {111 snprintf(name, sizeof(name), "lm3509-%s-%s", dev_name(dev),112 name_suffix);113 label = name;114 }115 116 bd = devm_backlight_device_register(dev, label, dev, data, ops, &props);117 if (IS_ERR(bd))118 return bd;119 120 backlight_update_status(bd);121 return bd;122}123 124static const struct regmap_config lm3509_regmap = {125 .reg_bits = 8,126 .val_bits = 8,127 .max_register = REG_MAX,128};129 130static int lm3509_parse_led_sources(struct device_node *node,131 int default_led_sources)132{133 u32 sources[LM3509_NUM_SINKS];134 int ret, num_sources, i;135 136 num_sources = of_property_count_u32_elems(node, "led-sources");137 if (num_sources < 0)138 return default_led_sources;139 else if (num_sources > ARRAY_SIZE(sources))140 return -EINVAL;141 142 ret = of_property_read_u32_array(node, "led-sources", sources,143 num_sources);144 if (ret)145 return ret;146 147 for (i = 0; i < num_sources; i++) {148 if (sources[i] >= LM3509_NUM_SINKS)149 return -EINVAL;150 151 ret |= BIT(sources[i]);152 }153 154 return ret;155}156 157static int lm3509_parse_dt_node(struct device *dev,158 struct lm3509_bl_led_data *led_data)159{160 int seen_led_sources = 0;161 162 for_each_child_of_node_scoped(dev->of_node, child) {163 struct lm3509_bl_led_data *ld;164 int ret;165 u32 reg;166 int valid_led_sources;167 168 ret = of_property_read_u32(child, "reg", ®);169 if (ret < 0)170 return ret;171 if (reg >= LM3509_NUM_SINKS)172 return -EINVAL;173 ld = &led_data[reg];174 175 ld->led_sources = lm3509_parse_led_sources(child, BIT(reg));176 if (ld->led_sources < 0)177 return ld->led_sources;178 179 if (reg == 0)180 valid_led_sources = BIT(LM3509_SINK_MAIN) |181 BIT(LM3509_SINK_SUB);182 else183 valid_led_sources = BIT(LM3509_SINK_SUB);184 185 if (ld->led_sources != (ld->led_sources & valid_led_sources))186 return -EINVAL;187 188 if (seen_led_sources & ld->led_sources)189 return -EINVAL;190 191 seen_led_sources |= ld->led_sources;192 193 ld->label = NULL;194 of_property_read_string(child, "label", &ld->label);195 196 ld->max_brightness = LM3509_MAX_BRIGHTNESS;197 of_property_read_u32(child, "max-brightness",198 &ld->max_brightness);199 ld->max_brightness =200 min_t(u32, ld->max_brightness, LM3509_MAX_BRIGHTNESS);201 202 ld->brightness = LM3509_DEF_BRIGHTNESS;203 of_property_read_u32(child, "default-brightness",204 &ld->brightness);205 ld->brightness = min_t(u32, ld->brightness, ld->max_brightness);206 }207 208 return 0;209}210 211static int lm3509_probe(struct i2c_client *client)212{213 struct lm3509_bl *data;214 struct device *dev = &client->dev;215 int ret;216 bool oled_mode = false;217 unsigned int reg_gp_val = 0;218 struct lm3509_bl_led_data led_data[LM3509_NUM_SINKS];219 u32 rate_of_change = 0;220 221 if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {222 dev_err(dev, "i2c functionality check failed\n");223 return -EOPNOTSUPP;224 }225 226 data = devm_kzalloc(dev, sizeof(struct lm3509_bl), GFP_KERNEL);227 if (!data)228 return -ENOMEM;229 230 data->regmap = devm_regmap_init_i2c(client, &lm3509_regmap);231 if (IS_ERR(data->regmap))232 return PTR_ERR(data->regmap);233 i2c_set_clientdata(client, data);234 235 data->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);236 if (IS_ERR(data->reset_gpio))237 return dev_err_probe(dev, PTR_ERR(data->reset_gpio),238 "Failed to get 'reset' gpio\n");239 240 lm3509_reset(data);241 242 memset(led_data, 0, sizeof(led_data));243 ret = lm3509_parse_dt_node(dev, led_data);244 if (ret)245 return ret;246 247 oled_mode = of_property_read_bool(dev->of_node, "ti,oled-mode");248 249 if (!of_property_read_u32(dev->of_node,250 "ti,brightness-rate-of-change-us",251 &rate_of_change)) {252 switch (rate_of_change) {253 case 51:254 reg_gp_val = 0;255 break;256 case 13000:257 reg_gp_val = BIT(REG_GP_RMP1_BIT);258 break;259 case 26000:260 reg_gp_val = BIT(REG_GP_RMP0_BIT);261 break;262 case 52000:263 reg_gp_val = BIT(REG_GP_RMP0_BIT) |264 BIT(REG_GP_RMP1_BIT);265 break;266 default:267 dev_warn(dev, "invalid rate of change %u\n",268 rate_of_change);269 break;270 }271 }272 273 if (led_data[0].led_sources ==274 (BIT(LM3509_SINK_MAIN) | BIT(LM3509_SINK_SUB)))275 reg_gp_val |= BIT(REG_GP_UNI_BIT);276 if (oled_mode)277 reg_gp_val |= BIT(REG_GP_OLED_BIT);278 279 ret = regmap_write(data->regmap, REG_GP, reg_gp_val);280 if (ret < 0)281 return dev_err_probe(dev, ret, "failed to write register\n");282 283 if (led_data[0].led_sources) {284 data->bl_main = lm3509_backlight_register(285 dev, "main", data, &lm3509_main_ops, &led_data[0]);286 if (IS_ERR(data->bl_main)) {287 return dev_err_probe(288 dev, PTR_ERR(data->bl_main),289 "failed to register main backlight\n");290 }291 }292 293 if (led_data[1].led_sources) {294 data->bl_sub = lm3509_backlight_register(295 dev, "sub", data, &lm3509_sub_ops, &led_data[1]);296 if (IS_ERR(data->bl_sub)) {297 return dev_err_probe(298 dev, PTR_ERR(data->bl_sub),299 "failed to register secondary backlight\n");300 }301 }302 303 return 0;304}305 306static void lm3509_remove(struct i2c_client *client)307{308 struct lm3509_bl *data = i2c_get_clientdata(client);309 310 regmap_write(data->regmap, REG_GP, 0x00);311}312 313static const struct i2c_device_id lm3509_id[] = {314 { LM3509_NAME },315 {}316};317 318MODULE_DEVICE_TABLE(i2c, lm3509_id);319 320static const struct of_device_id lm3509_match_table[] = {321 {322 .compatible = "ti,lm3509",323 },324 {},325};326 327MODULE_DEVICE_TABLE(of, lm3509_match_table);328 329static struct i2c_driver lm3509_i2c_driver = {330 .driver = {331 .name = LM3509_NAME,332 .of_match_table = lm3509_match_table,333 },334 .probe = lm3509_probe,335 .remove = lm3509_remove,336 .id_table = lm3509_id,337};338 339module_i2c_driver(lm3509_i2c_driver);340 341MODULE_DESCRIPTION("Texas Instruments Backlight driver for LM3509");342MODULE_AUTHOR("Patrick Gansterer <paroga@paroga.com>");343MODULE_LICENSE("GPL");344