// SPDX-License-Identifier: GPL-2.0 // TI LM3697 LED chip family driver // Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/regulator/consumer.h> #include <linux/leds-ti-lmu-common.h> #define LM3697_REV 0x0 #define LM3697_RESET 0x1 #define LM3697_OUTPUT_CONFIG 0x10 #define LM3697_CTRL_A_RAMP 0x11 #define LM3697_CTRL_B_RAMP 0x12 #define LM3697_CTRL_A_B_RT_RAMP 0x13 #define LM3697_CTRL_A_B_RAMP_CFG 0x14 #define LM3697_CTRL_A_B_BRT_CFG 0x16 #define LM3697_CTRL_A_FS_CURR_CFG 0x17 #define LM3697_CTRL_B_FS_CURR_CFG 0x18 #define LM3697_PWM_CFG 0x1c #define LM3697_CTRL_A_BRT_LSB 0x20 #define LM3697_CTRL_A_BRT_MSB 0x21 #define LM3697_CTRL_B_BRT_LSB 0x22 #define LM3697_CTRL_B_BRT_MSB 0x23 #define LM3697_CTRL_ENABLE 0x24 #define LM3697_SW_RESET BIT(0) #define LM3697_CTRL_A_EN BIT(0) #define LM3697_CTRL_B_EN BIT(1) #define LM3697_CTRL_A_B_EN (LM3697_CTRL_A_EN | LM3697_CTRL_B_EN) #define LM3697_MAX_LED_STRINGS 3 #define LM3697_CONTROL_A 0 #define LM3697_CONTROL_B 1 #define LM3697_MAX_CONTROL_BANKS 2 /** * struct lm3697_led - * @hvled_strings: Array of LED strings associated with a control bank * @label: LED label * @led_dev: LED class device * @priv: Pointer to the device struct * @lmu_data: Register and setting values for common code * @control_bank: Control bank the LED is associated to. 0 is control bank A * 1 is control bank B */ struct lm3697_led { u32 hvled_strings[LM3697_MAX_LED_STRINGS]; char label[LED_MAX_NAME_SIZE]; struct led_classdev led_dev; struct lm3697 *priv; struct ti_lmu_bank lmu_data; int control_bank; int enabled; int num_leds; }; /** * struct lm3697 - * @enable_gpio: Hardware enable gpio * @regulator: LED supply regulator pointer * @client: Pointer to the I2C client * @regmap: Devices register map * @dev: Pointer to the devices device struct * @lock: Lock for reading/writing the device * @leds: Array of LED strings */ struct lm3697 { struct gpio_desc *enable_gpio; struct regulator *regulator; struct i2c_client *client; struct regmap *regmap; struct device *dev; struct mutex lock; int bank_cfg; struct lm3697_led leds[]; }; static const struct reg_default lm3697_reg_defs[] = { {LM3697_OUTPUT_CONFIG, 0x6}, {LM3697_CTRL_A_RAMP, 0x0}, {LM3697_CTRL_B_RAMP, 0x0}, {LM3697_CTRL_A_B_RT_RAMP, 0x0}, {LM3697_CTRL_A_B_RAMP_CFG, 0x0}, {LM3697_CTRL_A_B_BRT_CFG, 0x0}, {LM3697_CTRL_A_FS_CURR_CFG, 0x13}, {LM3697_CTRL_B_FS_CURR_CFG, 0x13}, {LM3697_PWM_CFG, 0xc}, {LM3697_CTRL_A_BRT_LSB, 0x0}, {LM3697_CTRL_A_BRT_MSB, 0x0}, {LM3697_CTRL_B_BRT_LSB, 0x0}, {LM3697_CTRL_B_BRT_MSB, 0x0}, {LM3697_CTRL_ENABLE, 0x0}, }; static const struct regmap_config lm3697_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = LM3697_CTRL_ENABLE, .reg_defaults = lm3697_reg_defs, .num_reg_defaults = ARRAY_SIZE(lm3697_reg_defs), .cache_type = REGCACHE_FLAT, }; static int lm3697_brightness_set(struct led_classdev *led_cdev, enum led_brightness brt_val) { struct lm3697_led *led = container_of(led_cdev, struct lm3697_led, led_dev); int ctrl_en_val = (1 << led->control_bank); int ret; mutex_lock(&led->priv->lock); if (brt_val == LED_OFF) { ret = regmap_update_bits(led->priv->regmap, LM3697_CTRL_ENABLE, ctrl_en_val, ~ctrl_en_val); if (ret) { dev_err(&led->priv->client->dev, "Cannot write ctrl register\n"); goto brightness_out; } led->enabled = LED_OFF; } else { ret = ti_lmu_common_set_brightness(&led->lmu_data, brt_val); if (ret) { dev_err(&led->priv->client->dev, "Cannot write brightness\n"); goto brightness_out; } if (!led->enabled) { ret = regmap_update_bits(led->priv->regmap, LM3697_CTRL_ENABLE, ctrl_en_val, ctrl_en_val); if (ret) { dev_err(&led->priv->client->dev, "Cannot enable the device\n"); goto brightness_out; } led->enabled = brt_val; } } brightness_out: mutex_unlock(&led->priv->lock); return ret; } static int lm3697_init(struct lm3697 *priv) { struct lm3697_led *led; int i, ret; if (priv->enable_gpio) { gpiod_direction_output(priv->enable_gpio, 1); } else { ret = regmap_write(priv->regmap, LM3697_RESET, LM3697_SW_RESET); if (ret) { dev_err(&priv->client->dev, "Cannot reset the device\n"); goto out; } } ret = regmap_write(priv->regmap, LM3697_CTRL_ENABLE, 0x0); if (ret) { dev_err(&priv->client->dev, "Cannot write ctrl enable\n"); goto out; } ret = regmap_write(priv->regmap, LM3697_OUTPUT_CONFIG, priv->bank_cfg); if (ret) dev_err(&priv->client->dev, "Cannot write OUTPUT config\n"); for (i = 0; i < LM3697_MAX_CONTROL_BANKS; i++) { led = &priv->leds[i]; ret = ti_lmu_common_set_ramp(&led->lmu_data); if (ret) dev_err(&priv->client->dev, "Setting the ramp rate failed\n"); } out: return ret; } static int lm3697_probe_dt(struct lm3697 *priv) { struct fwnode_handle *child = NULL; struct lm3697_led *led; const char *name; int control_bank; size_t i = 0; int ret = -EINVAL; int j; priv->enable_gpio = devm_gpiod_get_optional(&priv->client->dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(priv->enable_gpio)) { ret = PTR_ERR(priv->enable_gpio); dev_err(&priv->client->dev, "Failed to get enable gpio: %d\n", ret); return ret; } priv->regulator = devm_regulator_get(&priv->client->dev, "vled"); if (IS_ERR(priv->regulator)) priv->regulator = NULL; device_for_each_child_node(priv->dev, child) { ret = fwnode_property_read_u32(child, "reg", &control_bank); if (ret) { dev_err(&priv->client->dev, "reg property missing\n"); fwnode_handle_put(child); goto child_out; } if (control_bank > LM3697_CONTROL_B) { dev_err(&priv->client->dev, "reg property is invalid\n"); ret = -EINVAL; fwnode_handle_put(child); goto child_out; } led = &priv->leds[i]; ret = ti_lmu_common_get_brt_res(&priv->client->dev, child, &led->lmu_data); if (ret) dev_warn(&priv->client->dev, "brightness resolution property missing\n"); led->control_bank = control_bank; led->lmu_data.regmap = priv->regmap; led->lmu_data.runtime_ramp_reg = LM3697_CTRL_A_RAMP + control_bank; led->lmu_data.msb_brightness_reg = LM3697_CTRL_A_BRT_MSB + led->control_bank * 2; led->lmu_data.lsb_brightness_reg = LM3697_CTRL_A_BRT_LSB + led->control_bank * 2; led->num_leds = fwnode_property_read_u32_array(child, "led-sources", NULL, 0); if (led->num_leds > LM3697_MAX_LED_STRINGS) { dev_err(&priv->client->dev, "To many LED strings defined\n"); continue; } ret = fwnode_property_read_u32_array(child, "led-sources", led->hvled_strings, led->num_leds); if (ret) { dev_err(&priv->client->dev, "led-sources property missing\n"); fwnode_handle_put(child); goto child_out; } for (j = 0; j < led->num_leds; j++) priv->bank_cfg |= (led->control_bank << led->hvled_strings[j]); ret = ti_lmu_common_get_ramp_params(&priv->client->dev, child, &led->lmu_data); if (ret) dev_warn(&priv->client->dev, "runtime-ramp properties missing\n"); fwnode_property_read_string(child, "linux,default-trigger", &led->led_dev.default_trigger); ret = fwnode_property_read_string(child, "label", &name); if (ret) snprintf(led->label, sizeof(led->label), "%s::", priv->client->name); else snprintf(led->label, sizeof(led->label), "%s:%s", priv->client->name, name); led->priv = priv; led->led_dev.name = led->label; led->led_dev.max_brightness = led->lmu_data.max_brightness; led->led_dev.brightness_set_blocking = lm3697_brightness_set; ret = devm_led_classdev_register(priv->dev, &led->led_dev); if (ret) { dev_err(&priv->client->dev, "led register err: %d\n", ret); fwnode_handle_put(child); goto child_out; } i++; } child_out: return ret; } static int lm3697_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct lm3697 *led; int count; int ret; count = device_get_child_node_count(&client->dev); if (!count) { dev_err(&client->dev, "LEDs are not defined in device tree!"); return -ENODEV; } led = devm_kzalloc(&client->dev, struct_size(led, leds, count), GFP_KERNEL); if (!led) return -ENOMEM; mutex_init(&led->lock); i2c_set_clientdata(client, led); led->client = client; led->dev = &client->dev; led->regmap = devm_regmap_init_i2c(client, &lm3697_regmap_config); if (IS_ERR(led->regmap)) { ret = PTR_ERR(led->regmap); dev_err(&client->dev, "Failed to allocate register map: %d\n", ret); return ret; } ret = lm3697_probe_dt(led); if (ret) return ret; return lm3697_init(led); } static int lm3697_remove(struct i2c_client *client) { struct lm3697 *led = i2c_get_clientdata(client); int ret; ret = regmap_update_bits(led->regmap, LM3697_CTRL_ENABLE, LM3697_CTRL_A_B_EN, 0); if (ret) { dev_err(&led->client->dev, "Failed to disable the device\n"); return ret; } if (led->enable_gpio) gpiod_direction_output(led->enable_gpio, 0); if (led->regulator) { ret = regulator_disable(led->regulator); if (ret) dev_err(&led->client->dev, "Failed to disable regulator\n"); } mutex_destroy(&led->lock); return 0; } static const struct i2c_device_id lm3697_id[] = { { "lm3697", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, lm3697_id); static const struct of_device_id of_lm3697_leds_match[] = { { .compatible = "ti,lm3697", }, {}, }; MODULE_DEVICE_TABLE(of, of_lm3697_leds_match); static struct i2c_driver lm3697_driver = { .driver = { .name = "lm3697", .of_match_table = of_lm3697_leds_match, }, .probe = lm3697_probe, .remove = lm3697_remove, .id_table = lm3697_id, }; module_i2c_driver(lm3697_driver); MODULE_DESCRIPTION("Texas Instruments LM3697 LED driver"); MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); MODULE_LICENSE("GPL v2");