| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * TI LP8864/LP8866 4/6 Channel LED Driver
- *
- * Copyright (C) 2024 Siemens AG
- *
- * Based on LP8860 driver by Dan Murphy <dmurphy@ti.com>
- */
- #include <linux/gpio/consumer.h>
- #include <linux/i2c.h>
- #include <linux/init.h>
- #include <linux/leds.h>
- #include <linux/module.h>
- #include <linux/mutex.h>
- #include <linux/of.h>
- #include <linux/regmap.h>
- #include <linux/regulator/consumer.h>
- #include <linux/slab.h>
- #define LP8864_BRT_CONTROL 0x00
- #define LP8864_USER_CONFIG1 0x04
- #define LP8864_BRT_MODE_MASK GENMASK(9, 8)
- #define LP8864_BRT_MODE_REG BIT(9) /* Brightness control by DISPLAY_BRT reg */
- #define LP8864_SUPPLY_STATUS 0x0e
- #define LP8864_BOOST_STATUS 0x10
- #define LP8864_LED_STATUS 0x12
- #define LP8864_LED_STATUS_WR_MASK GENMASK(14, 9) /* Writeable bits in the LED_STATUS reg */
- /* Textual meaning for status bits, starting from bit 1 */
- static const char *const lp8864_supply_status_msg[] = {
- "Vin under-voltage fault",
- "Vin over-voltage fault",
- "Vdd under-voltage fault",
- "Vin over-current fault",
- "Missing charge pump fault",
- "Charge pump fault",
- "Missing boost sync fault",
- "CRC error fault ",
- };
- /* Textual meaning for status bits, starting from bit 1 */
- static const char *const lp8864_boost_status_msg[] = {
- "Boost OVP low fault",
- "Boost OVP high fault",
- "Boost over-current fault",
- "Missing boost FSET resistor fault",
- "Missing MODE SEL resistor fault",
- "Missing LED resistor fault",
- "ISET resistor short to ground fault",
- "Thermal shutdown fault",
- };
- /* Textual meaning for every register bit */
- static const char *const lp8864_led_status_msg[] = {
- "LED 1 fault",
- "LED 2 fault",
- "LED 3 fault",
- "LED 4 fault",
- "LED 5 fault",
- "LED 6 fault",
- "LED open fault",
- "LED internal short fault",
- "LED short to GND fault",
- NULL, NULL, NULL,
- "Invalid string configuration fault",
- NULL,
- "I2C time out fault",
- };
- /**
- * struct lp8864_led
- * @client: Pointer to the I2C client
- * @led_dev: led class device pointer
- * @regmap: Devices register map
- * @led_status_mask: Helps to report LED fault only once
- */
- struct lp8864_led {
- struct i2c_client *client;
- struct led_classdev led_dev;
- struct regmap *regmap;
- u16 led_status_mask;
- };
- static int lp8864_fault_check(struct lp8864_led *led)
- {
- int ret, i;
- unsigned int val;
- ret = regmap_read(led->regmap, LP8864_SUPPLY_STATUS, &val);
- if (ret)
- goto err;
- /* Odd bits are status bits, even bits are clear bits */
- for (i = 0; i < ARRAY_SIZE(lp8864_supply_status_msg); i++)
- if (val & BIT(i * 2 + 1))
- dev_warn(&led->client->dev, "%s\n", lp8864_supply_status_msg[i]);
- /*
- * Clear bits have an index preceding the corresponding Status bits;
- * both have to be written "1" simultaneously to clear the corresponding
- * Status bit.
- */
- if (val)
- ret = regmap_write(led->regmap, LP8864_SUPPLY_STATUS, val >> 1 | val);
- if (ret)
- goto err;
- ret = regmap_read(led->regmap, LP8864_BOOST_STATUS, &val);
- if (ret)
- goto err;
- /* Odd bits are status bits, even bits are clear bits */
- for (i = 0; i < ARRAY_SIZE(lp8864_boost_status_msg); i++)
- if (val & BIT(i * 2 + 1))
- dev_warn(&led->client->dev, "%s\n", lp8864_boost_status_msg[i]);
- if (val)
- ret = regmap_write(led->regmap, LP8864_BOOST_STATUS, val >> 1 | val);
- if (ret)
- goto err;
- ret = regmap_read(led->regmap, LP8864_LED_STATUS, &val);
- if (ret)
- goto err;
- /*
- * Clear already reported faults that maintain their value until device
- * power-down
- */
- val &= ~led->led_status_mask;
- for (i = 0; i < ARRAY_SIZE(lp8864_led_status_msg); i++)
- if (lp8864_led_status_msg[i] && val & BIT(i))
- dev_warn(&led->client->dev, "%s\n", lp8864_led_status_msg[i]);
- /*
- * Mark those which maintain their value until device power-down as
- * "already reported"
- */
- led->led_status_mask |= val & ~LP8864_LED_STATUS_WR_MASK;
- /*
- * Only bits 14, 12, 10 have to be cleared here, but others are RO,
- * we don't care what we write to them.
- */
- if (val & LP8864_LED_STATUS_WR_MASK)
- ret = regmap_write(led->regmap, LP8864_LED_STATUS, val >> 1 | val);
- if (ret)
- goto err;
- return 0;
- err:
- dev_err(&led->client->dev, "Failed to read/clear faults (%pe)\n", ERR_PTR(ret));
- return ret;
- }
- static int lp8864_brightness_set(struct led_classdev *led_cdev,
- enum led_brightness brt_val)
- {
- struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev);
- /* Scale 0..LED_FULL into 16-bit HW brightness */
- unsigned int val = brt_val * 0xffff / LED_FULL;
- int ret;
- ret = lp8864_fault_check(led);
- if (ret)
- return ret;
- ret = regmap_write(led->regmap, LP8864_BRT_CONTROL, val);
- if (ret)
- dev_err(&led->client->dev, "Failed to write brightness value\n");
- return ret;
- }
- static enum led_brightness lp8864_brightness_get(struct led_classdev *led_cdev)
- {
- struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev);
- unsigned int val;
- int ret;
- ret = regmap_read(led->regmap, LP8864_BRT_CONTROL, &val);
- if (ret) {
- dev_err(&led->client->dev, "Failed to read brightness value\n");
- return ret;
- }
- /* Scale 16-bit HW brightness into 0..LED_FULL */
- return val * LED_FULL / 0xffff;
- }
- static const struct regmap_config lp8864_regmap_config = {
- .reg_bits = 8,
- .val_bits = 16,
- .val_format_endian = REGMAP_ENDIAN_LITTLE,
- };
- static void lp8864_disable_gpio(void *data)
- {
- struct gpio_desc *gpio = data;
- gpiod_set_value(gpio, 0);
- }
- static int lp8864_probe(struct i2c_client *client)
- {
- int ret;
- struct lp8864_led *led;
- struct device_node *np = dev_of_node(&client->dev);
- struct device_node *child_node;
- struct led_init_data init_data = {};
- struct gpio_desc *enable_gpio;
- led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
- if (!led)
- return -ENOMEM;
- child_node = of_get_next_available_child(np, NULL);
- if (!child_node) {
- dev_err(&client->dev, "No LED function defined\n");
- return -EINVAL;
- }
- ret = devm_regulator_get_enable_optional(&client->dev, "vled");
- if (ret && ret != -ENODEV)
- return dev_err_probe(&client->dev, ret, "Failed to enable vled regulator\n");
- enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH);
- if (IS_ERR(enable_gpio))
- return dev_err_probe(&client->dev, PTR_ERR(enable_gpio),
- "Failed to get enable GPIO\n");
- ret = devm_add_action_or_reset(&client->dev, lp8864_disable_gpio, enable_gpio);
- if (ret)
- return ret;
- led->client = client;
- led->led_dev.brightness_set_blocking = lp8864_brightness_set;
- led->led_dev.brightness_get = lp8864_brightness_get;
- led->regmap = devm_regmap_init_i2c(client, &lp8864_regmap_config);
- if (IS_ERR(led->regmap))
- return dev_err_probe(&client->dev, PTR_ERR(led->regmap),
- "Failed to allocate regmap\n");
- /* Control brightness by DISPLAY_BRT register */
- ret = regmap_update_bits(led->regmap, LP8864_USER_CONFIG1, LP8864_BRT_MODE_MASK,
- LP8864_BRT_MODE_REG);
- if (ret) {
- dev_err(&led->client->dev, "Failed to set brightness control mode\n");
- return ret;
- }
- ret = lp8864_fault_check(led);
- if (ret)
- return ret;
- init_data.fwnode = of_fwnode_handle(child_node);
- init_data.devicename = "lp8864";
- init_data.default_label = ":display_cluster";
- ret = devm_led_classdev_register_ext(&client->dev, &led->led_dev, &init_data);
- if (ret)
- dev_err(&client->dev, "Failed to register LED device (%pe)\n", ERR_PTR(ret));
- return ret;
- }
- static const struct i2c_device_id lp8864_id[] = {
- { "lp8864" },
- {}
- };
- MODULE_DEVICE_TABLE(i2c, lp8864_id);
- static const struct of_device_id of_lp8864_leds_match[] = {
- { .compatible = "ti,lp8864" },
- {}
- };
- MODULE_DEVICE_TABLE(of, of_lp8864_leds_match);
- static struct i2c_driver lp8864_driver = {
- .driver = {
- .name = "lp8864",
- .of_match_table = of_lp8864_leds_match,
- },
- .probe = lp8864_probe,
- .id_table = lp8864_id,
- };
- module_i2c_driver(lp8864_driver);
- MODULE_DESCRIPTION("Texas Instruments LP8864/LP8866 LED driver");
- MODULE_AUTHOR("Alexander Sverdlin <alexander.sverdlin@siemens.com>");
- MODULE_LICENSE("GPL");
|