| // SPDX-License-Identifier: GPL-2.0 | 
 | // TI LM3697 LED chip family driver | 
 | // Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/ | 
 |  | 
 | #include <linux/bits.h> | 
 | #include <linux/gpio/consumer.h> | 
 | #include <linux/i2c.h> | 
 | #include <linux/mod_devicetable.h> | 
 | #include <linux/module.h> | 
 | #include <linux/property.h> | 
 | #include <linux/regmap.h> | 
 | #include <linux/regulator/consumer.h> | 
 | #include <linux/types.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 | 
 |  * @enabled: LED brightness level (or LED_OFF) | 
 |  * @num_leds: Number of LEDs available | 
 |  */ | 
 | 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 | 
 |  * @bank_cfg: OUTPUT_CONFIG register values | 
 |  * @num_banks: Number of control banks | 
 |  */ | 
 | 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; | 
 | 	int num_banks; | 
 |  | 
 | 	struct lm3697_led leds[] __counted_by(num_banks); | 
 | }; | 
 |  | 
 | 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); | 
 | 	struct device *dev = led->priv->dev; | 
 | 	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(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(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(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 device *dev = priv->dev; | 
 | 	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(dev, "Cannot reset the device\n"); | 
 | 			goto out; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	ret = regmap_write(priv->regmap, LM3697_CTRL_ENABLE, 0x0); | 
 | 	if (ret) { | 
 | 		dev_err(dev, "Cannot write ctrl enable\n"); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	ret = regmap_write(priv->regmap, LM3697_OUTPUT_CONFIG, priv->bank_cfg); | 
 | 	if (ret) | 
 | 		dev_err(dev, "Cannot write OUTPUT config\n"); | 
 |  | 
 | 	for (i = 0; i < priv->num_banks; i++) { | 
 | 		led = &priv->leds[i]; | 
 | 		ret = ti_lmu_common_set_ramp(&led->lmu_data); | 
 | 		if (ret) | 
 | 			dev_err(dev, "Setting the ramp rate failed\n"); | 
 | 	} | 
 | out: | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int lm3697_probe_dt(struct lm3697 *priv) | 
 | { | 
 | 	struct device *dev = priv->dev; | 
 | 	struct lm3697_led *led; | 
 | 	int ret = -EINVAL; | 
 | 	int control_bank; | 
 | 	size_t i = 0; | 
 | 	int j; | 
 |  | 
 | 	priv->enable_gpio = devm_gpiod_get_optional(dev, "enable", | 
 | 						    GPIOD_OUT_LOW); | 
 | 	if (IS_ERR(priv->enable_gpio)) | 
 | 		return dev_err_probe(dev, PTR_ERR(priv->enable_gpio), | 
 | 					  "Failed to get enable GPIO\n"); | 
 |  | 
 | 	priv->regulator = devm_regulator_get(dev, "vled"); | 
 | 	if (IS_ERR(priv->regulator)) | 
 | 		priv->regulator = NULL; | 
 |  | 
 | 	device_for_each_child_node_scoped(dev, child) { | 
 | 		struct led_init_data init_data = {}; | 
 |  | 
 | 		ret = fwnode_property_read_u32(child, "reg", &control_bank); | 
 | 		if (ret) { | 
 | 			dev_err(dev, "reg property missing\n"); | 
 | 			return ret; | 
 | 		} | 
 |  | 
 | 		if (control_bank > LM3697_CONTROL_B) { | 
 | 			dev_err(dev, "reg property is invalid\n"); | 
 | 			return -EINVAL; | 
 | 		} | 
 |  | 
 | 		led = &priv->leds[i]; | 
 |  | 
 | 		ret = ti_lmu_common_get_brt_res(dev, child, &led->lmu_data); | 
 | 		if (ret) | 
 | 			dev_warn(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_count_u32(child, "led-sources"); | 
 | 		if (led->num_leds > LM3697_MAX_LED_STRINGS) { | 
 | 			dev_err(dev, "Too 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(dev, "led-sources property missing\n"); | 
 | 			return ret; | 
 | 		} | 
 |  | 
 | 		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(dev, child, &led->lmu_data); | 
 | 		if (ret) | 
 | 			dev_warn(dev, "runtime-ramp properties missing\n"); | 
 |  | 
 | 		init_data.fwnode = child; | 
 | 		init_data.devicename = priv->client->name; | 
 | 		/* for backwards compatibility if `label` is not present */ | 
 | 		init_data.default_label = ":"; | 
 |  | 
 | 		led->priv = priv; | 
 | 		led->led_dev.max_brightness = led->lmu_data.max_brightness; | 
 | 		led->led_dev.brightness_set_blocking = lm3697_brightness_set; | 
 |  | 
 | 		ret = devm_led_classdev_register_ext(dev, &led->led_dev, | 
 | 						     &init_data); | 
 | 		if (ret) { | 
 | 			dev_err(dev, "led register err: %d\n", ret); | 
 | 			return ret; | 
 | 		} | 
 |  | 
 | 		i++; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int lm3697_probe(struct i2c_client *client) | 
 | { | 
 | 	struct device *dev = &client->dev; | 
 | 	struct lm3697 *led; | 
 | 	int count; | 
 | 	int ret; | 
 |  | 
 | 	count = device_get_child_node_count(dev); | 
 | 	if (!count || count > LM3697_MAX_CONTROL_BANKS) { | 
 | 		dev_err(dev, "Strange device tree!"); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	led = devm_kzalloc(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 = dev; | 
 | 	led->num_banks = count; | 
 | 	led->regmap = devm_regmap_init_i2c(client, &lm3697_regmap_config); | 
 | 	if (IS_ERR(led->regmap)) { | 
 | 		ret = PTR_ERR(led->regmap); | 
 | 		dev_err(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 void lm3697_remove(struct i2c_client *client) | 
 | { | 
 | 	struct lm3697 *led = i2c_get_clientdata(client); | 
 | 	struct device *dev = &led->client->dev; | 
 | 	int ret; | 
 |  | 
 | 	ret = regmap_update_bits(led->regmap, LM3697_CTRL_ENABLE, | 
 | 				 LM3697_CTRL_A_B_EN, 0); | 
 | 	if (ret) | 
 | 		dev_err(dev, "Failed to disable the device\n"); | 
 |  | 
 | 	if (led->enable_gpio) | 
 | 		gpiod_direction_output(led->enable_gpio, 0); | 
 |  | 
 | 	if (led->regulator) { | 
 | 		ret = regulator_disable(led->regulator); | 
 | 		if (ret) | 
 | 			dev_err(dev, "Failed to disable regulator\n"); | 
 | 	} | 
 |  | 
 | 	mutex_destroy(&led->lock); | 
 | } | 
 |  | 
 | static const struct i2c_device_id lm3697_id[] = { | 
 | 	{ "lm3697" }, | 
 | 	{ } | 
 | }; | 
 | 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"); |