File Index
Files and directories
Sign in
FILE

leds-lp8864.c

File preview with syntax highlighting.

linux-6.19.14/drivers/leds/leds-lp8864.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * TI LP8864/LP8866 4/6 Channel LED Driver
  4 *
  5 * Copyright (C) 2024 Siemens AG
  6 *
  7 * Based on LP8860 driver by Dan Murphy <dmurphy@ti.com>
  8 */
  9
 10#include <linux/gpio/consumer.h>
 11#include <linux/i2c.h>
 12#include <linux/init.h>
 13#include <linux/leds.h>
 14#include <linux/module.h>
 15#include <linux/mutex.h>
 16#include <linux/of.h>
 17#include <linux/regmap.h>
 18#include <linux/regulator/consumer.h>
 19#include <linux/slab.h>
 20
 21#define LP8864_BRT_CONTROL		0x00
 22#define LP8864_USER_CONFIG1		0x04
 23#define   LP8864_BRT_MODE_MASK		GENMASK(9, 8)
 24#define   LP8864_BRT_MODE_REG		BIT(9)		/* Brightness control by DISPLAY_BRT reg */
 25#define LP8864_SUPPLY_STATUS		0x0e
 26#define LP8864_BOOST_STATUS		0x10
 27#define LP8864_LED_STATUS		0x12
 28#define   LP8864_LED_STATUS_WR_MASK	GENMASK(14, 9)	/* Writeable bits in the LED_STATUS reg */
 29
 30/* Textual meaning for status bits, starting from bit 1 */
 31static const char *const lp8864_supply_status_msg[] = {
 32	"Vin under-voltage fault",
 33	"Vin over-voltage fault",
 34	"Vdd under-voltage fault",
 35	"Vin over-current fault",
 36	"Missing charge pump fault",
 37	"Charge pump fault",
 38	"Missing boost sync fault",
 39	"CRC error fault ",
 40};
 41
 42/* Textual meaning for status bits, starting from bit 1 */
 43static const char *const lp8864_boost_status_msg[] = {
 44	"Boost OVP low fault",
 45	"Boost OVP high fault",
 46	"Boost over-current fault",
 47	"Missing boost FSET resistor fault",
 48	"Missing MODE SEL resistor fault",
 49	"Missing LED resistor fault",
 50	"ISET resistor short to ground fault",
 51	"Thermal shutdown fault",
 52};
 53
 54/* Textual meaning for every register bit */
 55static const char *const lp8864_led_status_msg[] = {
 56	"LED 1 fault",
 57	"LED 2 fault",
 58	"LED 3 fault",
 59	"LED 4 fault",
 60	"LED 5 fault",
 61	"LED 6 fault",
 62	"LED open fault",
 63	"LED internal short fault",
 64	"LED short to GND fault",
 65	NULL, NULL, NULL,
 66	"Invalid string configuration fault",
 67	NULL,
 68	"I2C time out fault",
 69};
 70
 71/**
 72 * struct lp8864_led
 73 * @client: Pointer to the I2C client
 74 * @led_dev: led class device pointer
 75 * @regmap: Devices register map
 76 * @led_status_mask: Helps to report LED fault only once
 77 */
 78struct lp8864_led {
 79	struct i2c_client *client;
 80	struct led_classdev led_dev;
 81	struct regmap *regmap;
 82	u16 led_status_mask;
 83};
 84
 85static int lp8864_fault_check(struct lp8864_led *led)
 86{
 87	int ret, i;
 88	unsigned int val;
 89
 90	ret = regmap_read(led->regmap, LP8864_SUPPLY_STATUS, &val);
 91	if (ret)
 92		goto err;
 93
 94	/* Odd bits are status bits, even bits are clear bits */
 95	for (i = 0; i < ARRAY_SIZE(lp8864_supply_status_msg); i++)
 96		if (val & BIT(i * 2 + 1))
 97			dev_warn(&led->client->dev, "%s\n", lp8864_supply_status_msg[i]);
 98
 99	/*
100	 * Clear bits have an index preceding the corresponding Status bits;
101	 * both have to be written "1" simultaneously to clear the corresponding
102	 * Status bit.
103	 */
104	if (val)
105		ret = regmap_write(led->regmap, LP8864_SUPPLY_STATUS, val >> 1 | val);
106	if (ret)
107		goto err;
108
109	ret = regmap_read(led->regmap, LP8864_BOOST_STATUS, &val);
110	if (ret)
111		goto err;
112
113	/* Odd bits are status bits, even bits are clear bits */
114	for (i = 0; i < ARRAY_SIZE(lp8864_boost_status_msg); i++)
115		if (val & BIT(i * 2 + 1))
116			dev_warn(&led->client->dev, "%s\n", lp8864_boost_status_msg[i]);
117
118	if (val)
119		ret = regmap_write(led->regmap, LP8864_BOOST_STATUS, val >> 1 | val);
120	if (ret)
121		goto err;
122
123	ret = regmap_read(led->regmap, LP8864_LED_STATUS, &val);
124	if (ret)
125		goto err;
126
127	/*
128	 * Clear already reported faults that maintain their value until device
129	 * power-down
130	 */
131	val &= ~led->led_status_mask;
132
133	for (i = 0; i < ARRAY_SIZE(lp8864_led_status_msg); i++)
134		if (lp8864_led_status_msg[i] && val & BIT(i))
135			dev_warn(&led->client->dev, "%s\n", lp8864_led_status_msg[i]);
136
137	/*
138	 * Mark those which maintain their value until device power-down as
139	 * "already reported"
140	 */
141	led->led_status_mask |= val & ~LP8864_LED_STATUS_WR_MASK;
142
143	/*
144	 * Only bits 14, 12, 10 have to be cleared here, but others are RO,
145	 * we don't care what we write to them.
146	 */
147	if (val & LP8864_LED_STATUS_WR_MASK)
148		ret = regmap_write(led->regmap, LP8864_LED_STATUS, val >> 1 | val);
149	if (ret)
150		goto err;
151
152	return 0;
153
154err:
155	dev_err(&led->client->dev, "Failed to read/clear faults (%pe)\n", ERR_PTR(ret));
156
157	return ret;
158}
159
160static int lp8864_brightness_set(struct led_classdev *led_cdev,
161				 enum led_brightness brt_val)
162{
163	struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev);
164	/* Scale 0..LED_FULL into 16-bit HW brightness */
165	unsigned int val = brt_val * 0xffff / LED_FULL;
166	int ret;
167
168	ret = lp8864_fault_check(led);
169	if (ret)
170		return ret;
171
172	ret = regmap_write(led->regmap, LP8864_BRT_CONTROL, val);
173	if (ret)
174		dev_err(&led->client->dev, "Failed to write brightness value\n");
175
176	return ret;
177}
178
179static enum led_brightness lp8864_brightness_get(struct led_classdev *led_cdev)
180{
181	struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev);
182	unsigned int val;
183	int ret;
184
185	ret = regmap_read(led->regmap, LP8864_BRT_CONTROL, &val);
186	if (ret) {
187		dev_err(&led->client->dev, "Failed to read brightness value\n");
188		return ret;
189	}
190
191	/* Scale 16-bit HW brightness into 0..LED_FULL */
192	return val * LED_FULL / 0xffff;
193}
194
195static const struct regmap_config lp8864_regmap_config = {
196	.reg_bits		= 8,
197	.val_bits		= 16,
198	.val_format_endian	= REGMAP_ENDIAN_LITTLE,
199};
200
201static void lp8864_disable_gpio(void *data)
202{
203	struct gpio_desc *gpio = data;
204
205	gpiod_set_value(gpio, 0);
206}
207
208static int lp8864_probe(struct i2c_client *client)
209{
210	int ret;
211	struct lp8864_led *led;
212	struct device_node *np = dev_of_node(&client->dev);
213	struct device_node *child_node;
214	struct led_init_data init_data = {};
215	struct gpio_desc *enable_gpio;
216
217	led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
218	if (!led)
219		return -ENOMEM;
220
221	child_node = of_get_next_available_child(np, NULL);
222	if (!child_node) {
223		dev_err(&client->dev, "No LED function defined\n");
224		return -EINVAL;
225	}
226
227	ret = devm_regulator_get_enable_optional(&client->dev, "vled");
228	if (ret && ret != -ENODEV)
229		return dev_err_probe(&client->dev, ret, "Failed to enable vled regulator\n");
230
231	enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH);
232	if (IS_ERR(enable_gpio))
233		return dev_err_probe(&client->dev, PTR_ERR(enable_gpio),
234				     "Failed to get enable GPIO\n");
235
236	ret = devm_add_action_or_reset(&client->dev, lp8864_disable_gpio, enable_gpio);
237	if (ret)
238		return ret;
239
240	led->client = client;
241	led->led_dev.brightness_set_blocking = lp8864_brightness_set;
242	led->led_dev.brightness_get = lp8864_brightness_get;
243
244	led->regmap = devm_regmap_init_i2c(client, &lp8864_regmap_config);
245	if (IS_ERR(led->regmap))
246		return dev_err_probe(&client->dev, PTR_ERR(led->regmap),
247				     "Failed to allocate regmap\n");
248
249	/* Control brightness by DISPLAY_BRT register */
250	ret = regmap_update_bits(led->regmap, LP8864_USER_CONFIG1, LP8864_BRT_MODE_MASK,
251								   LP8864_BRT_MODE_REG);
252	if (ret) {
253		dev_err(&led->client->dev, "Failed to set brightness control mode\n");
254		return ret;
255	}
256
257	ret = lp8864_fault_check(led);
258	if (ret)
259		return ret;
260
261	init_data.fwnode = of_fwnode_handle(child_node);
262	init_data.devicename = "lp8864";
263	init_data.default_label = ":display_cluster";
264
265	ret = devm_led_classdev_register_ext(&client->dev, &led->led_dev, &init_data);
266	if (ret)
267		dev_err(&client->dev, "Failed to register LED device (%pe)\n", ERR_PTR(ret));
268
269	return ret;
270}
271
272static const struct i2c_device_id lp8864_id[] = {
273	{ "lp8864" },
274	{}
275};
276MODULE_DEVICE_TABLE(i2c, lp8864_id);
277
278static const struct of_device_id of_lp8864_leds_match[] = {
279	{ .compatible = "ti,lp8864" },
280	{}
281};
282MODULE_DEVICE_TABLE(of, of_lp8864_leds_match);
283
284static struct i2c_driver lp8864_driver = {
285	.driver = {
286		.name	= "lp8864",
287		.of_match_table = of_lp8864_leds_match,
288	},
289	.probe		= lp8864_probe,
290	.id_table	= lp8864_id,
291};
292module_i2c_driver(lp8864_driver);
293
294MODULE_DESCRIPTION("Texas Instruments LP8864/LP8866 LED driver");
295MODULE_AUTHOR("Alexander Sverdlin <alexander.sverdlin@siemens.com>");
296MODULE_LICENSE("GPL");
~/ Read-only file preview.