| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- #include <linux/backlight.h>
- #include <linux/delay.h>
- #include <linux/gpio/consumer.h>
- #include <linux/i2c.h>
- #include <linux/module.h>
- #include <linux/regmap.h>
- #define LM3509_NAME "lm3509_bl"
- #define LM3509_SINK_MAIN 0
- #define LM3509_SINK_SUB 1
- #define LM3509_NUM_SINKS 2
- #define LM3509_DEF_BRIGHTNESS 0x12
- #define LM3509_MAX_BRIGHTNESS 0x1F
- #define REG_GP 0x10
- #define REG_BMAIN 0xA0
- #define REG_BSUB 0xB0
- #define REG_MAX 0xFF
- enum {
- REG_GP_ENM_BIT = 0,
- REG_GP_ENS_BIT,
- REG_GP_UNI_BIT,
- REG_GP_RMP0_BIT,
- REG_GP_RMP1_BIT,
- REG_GP_OLED_BIT,
- };
- struct lm3509_bl {
- struct regmap *regmap;
- struct backlight_device *bl_main;
- struct backlight_device *bl_sub;
- struct gpio_desc *reset_gpio;
- };
- struct lm3509_bl_led_data {
- const char *label;
- int led_sources;
- u32 brightness;
- u32 max_brightness;
- };
- static void lm3509_reset(struct lm3509_bl *data)
- {
- if (data->reset_gpio) {
- gpiod_set_value(data->reset_gpio, 1);
- udelay(1);
- gpiod_set_value(data->reset_gpio, 0);
- udelay(10);
- }
- }
- static int lm3509_update_status(struct backlight_device *bl,
- unsigned int en_mask, unsigned int br_reg)
- {
- struct lm3509_bl *data = bl_get_data(bl);
- int ret;
- bool en;
- ret = regmap_write(data->regmap, br_reg, backlight_get_brightness(bl));
- if (ret < 0)
- return ret;
- en = !backlight_is_blank(bl);
- return regmap_update_bits(data->regmap, REG_GP, en_mask,
- en ? en_mask : 0);
- }
- static int lm3509_main_update_status(struct backlight_device *bl)
- {
- return lm3509_update_status(bl, BIT(REG_GP_ENM_BIT), REG_BMAIN);
- }
- static const struct backlight_ops lm3509_main_ops = {
- .options = BL_CORE_SUSPENDRESUME,
- .update_status = lm3509_main_update_status,
- };
- static int lm3509_sub_update_status(struct backlight_device *bl)
- {
- return lm3509_update_status(bl, BIT(REG_GP_ENS_BIT), REG_BSUB);
- }
- static const struct backlight_ops lm3509_sub_ops = {
- .options = BL_CORE_SUSPENDRESUME,
- .update_status = lm3509_sub_update_status,
- };
- static struct backlight_device *
- lm3509_backlight_register(struct device *dev, const char *name_suffix,
- struct lm3509_bl *data,
- const struct backlight_ops *ops,
- const struct lm3509_bl_led_data *led_data)
- {
- struct backlight_device *bd;
- struct backlight_properties props;
- const char *label = led_data->label;
- char name[64];
- memset(&props, 0, sizeof(props));
- props.type = BACKLIGHT_RAW;
- props.brightness = led_data->brightness;
- props.max_brightness = led_data->max_brightness;
- props.scale = BACKLIGHT_SCALE_NON_LINEAR;
- if (!label) {
- snprintf(name, sizeof(name), "lm3509-%s-%s", dev_name(dev),
- name_suffix);
- label = name;
- }
- bd = devm_backlight_device_register(dev, label, dev, data, ops, &props);
- if (IS_ERR(bd))
- return bd;
- backlight_update_status(bd);
- return bd;
- }
- static const struct regmap_config lm3509_regmap = {
- .reg_bits = 8,
- .val_bits = 8,
- .max_register = REG_MAX,
- };
- static int lm3509_parse_led_sources(struct device_node *node,
- int default_led_sources)
- {
- u32 sources[LM3509_NUM_SINKS];
- int ret, num_sources, i;
- num_sources = of_property_count_u32_elems(node, "led-sources");
- if (num_sources < 0)
- return default_led_sources;
- else if (num_sources > ARRAY_SIZE(sources))
- return -EINVAL;
- ret = of_property_read_u32_array(node, "led-sources", sources,
- num_sources);
- if (ret)
- return ret;
- for (i = 0; i < num_sources; i++) {
- if (sources[i] >= LM3509_NUM_SINKS)
- return -EINVAL;
- ret |= BIT(sources[i]);
- }
- return ret;
- }
- static int lm3509_parse_dt_node(struct device *dev,
- struct lm3509_bl_led_data *led_data)
- {
- int seen_led_sources = 0;
- for_each_child_of_node_scoped(dev->of_node, child) {
- struct lm3509_bl_led_data *ld;
- int ret;
- u32 reg;
- int valid_led_sources;
- ret = of_property_read_u32(child, "reg", ®);
- if (ret < 0)
- return ret;
- if (reg >= LM3509_NUM_SINKS)
- return -EINVAL;
- ld = &led_data[reg];
- ld->led_sources = lm3509_parse_led_sources(child, BIT(reg));
- if (ld->led_sources < 0)
- return ld->led_sources;
- if (reg == 0)
- valid_led_sources = BIT(LM3509_SINK_MAIN) |
- BIT(LM3509_SINK_SUB);
- else
- valid_led_sources = BIT(LM3509_SINK_SUB);
- if (ld->led_sources != (ld->led_sources & valid_led_sources))
- return -EINVAL;
- if (seen_led_sources & ld->led_sources)
- return -EINVAL;
- seen_led_sources |= ld->led_sources;
- ld->label = NULL;
- of_property_read_string(child, "label", &ld->label);
- ld->max_brightness = LM3509_MAX_BRIGHTNESS;
- of_property_read_u32(child, "max-brightness",
- &ld->max_brightness);
- ld->max_brightness =
- min_t(u32, ld->max_brightness, LM3509_MAX_BRIGHTNESS);
- ld->brightness = LM3509_DEF_BRIGHTNESS;
- of_property_read_u32(child, "default-brightness",
- &ld->brightness);
- ld->brightness = min_t(u32, ld->brightness, ld->max_brightness);
- }
- return 0;
- }
- static int lm3509_probe(struct i2c_client *client)
- {
- struct lm3509_bl *data;
- struct device *dev = &client->dev;
- int ret;
- bool oled_mode = false;
- unsigned int reg_gp_val = 0;
- struct lm3509_bl_led_data led_data[LM3509_NUM_SINKS];
- u32 rate_of_change = 0;
- if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
- dev_err(dev, "i2c functionality check failed\n");
- return -EOPNOTSUPP;
- }
- data = devm_kzalloc(dev, sizeof(struct lm3509_bl), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
- data->regmap = devm_regmap_init_i2c(client, &lm3509_regmap);
- if (IS_ERR(data->regmap))
- return PTR_ERR(data->regmap);
- i2c_set_clientdata(client, data);
- data->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
- if (IS_ERR(data->reset_gpio))
- return dev_err_probe(dev, PTR_ERR(data->reset_gpio),
- "Failed to get 'reset' gpio\n");
- lm3509_reset(data);
- memset(led_data, 0, sizeof(led_data));
- ret = lm3509_parse_dt_node(dev, led_data);
- if (ret)
- return ret;
- oled_mode = of_property_read_bool(dev->of_node, "ti,oled-mode");
- if (!of_property_read_u32(dev->of_node,
- "ti,brightness-rate-of-change-us",
- &rate_of_change)) {
- switch (rate_of_change) {
- case 51:
- reg_gp_val = 0;
- break;
- case 13000:
- reg_gp_val = BIT(REG_GP_RMP1_BIT);
- break;
- case 26000:
- reg_gp_val = BIT(REG_GP_RMP0_BIT);
- break;
- case 52000:
- reg_gp_val = BIT(REG_GP_RMP0_BIT) |
- BIT(REG_GP_RMP1_BIT);
- break;
- default:
- dev_warn(dev, "invalid rate of change %u\n",
- rate_of_change);
- break;
- }
- }
- if (led_data[0].led_sources ==
- (BIT(LM3509_SINK_MAIN) | BIT(LM3509_SINK_SUB)))
- reg_gp_val |= BIT(REG_GP_UNI_BIT);
- if (oled_mode)
- reg_gp_val |= BIT(REG_GP_OLED_BIT);
- ret = regmap_write(data->regmap, REG_GP, reg_gp_val);
- if (ret < 0)
- return dev_err_probe(dev, ret, "failed to write register\n");
- if (led_data[0].led_sources) {
- data->bl_main = lm3509_backlight_register(
- dev, "main", data, &lm3509_main_ops, &led_data[0]);
- if (IS_ERR(data->bl_main)) {
- return dev_err_probe(
- dev, PTR_ERR(data->bl_main),
- "failed to register main backlight\n");
- }
- }
- if (led_data[1].led_sources) {
- data->bl_sub = lm3509_backlight_register(
- dev, "sub", data, &lm3509_sub_ops, &led_data[1]);
- if (IS_ERR(data->bl_sub)) {
- return dev_err_probe(
- dev, PTR_ERR(data->bl_sub),
- "failed to register secondary backlight\n");
- }
- }
- return 0;
- }
- static void lm3509_remove(struct i2c_client *client)
- {
- struct lm3509_bl *data = i2c_get_clientdata(client);
- regmap_write(data->regmap, REG_GP, 0x00);
- }
- static const struct i2c_device_id lm3509_id[] = {
- { LM3509_NAME },
- {}
- };
- MODULE_DEVICE_TABLE(i2c, lm3509_id);
- static const struct of_device_id lm3509_match_table[] = {
- {
- .compatible = "ti,lm3509",
- },
- {},
- };
- MODULE_DEVICE_TABLE(of, lm3509_match_table);
- static struct i2c_driver lm3509_i2c_driver = {
- .driver = {
- .name = LM3509_NAME,
- .of_match_table = lm3509_match_table,
- },
- .probe = lm3509_probe,
- .remove = lm3509_remove,
- .id_table = lm3509_id,
- };
- module_i2c_driver(lm3509_i2c_driver);
- MODULE_DESCRIPTION("Texas Instruments Backlight driver for LM3509");
- MODULE_AUTHOR("Patrick Gansterer <paroga@paroga.com>");
- MODULE_LICENSE("GPL");
|