File Index
Files and directories
Sign in
FILE

aw99706.c

File preview with syntax highlighting.

linux-6.19.14/drivers/video/backlight/aw99706.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * aw99706 - Backlight driver for the AWINIC AW99706
  4 *
  5 * Copyright (C) 2025 Junjie Cao <caojunjie650@gmail.com>
  6 * Copyright (C) 2025 Pengyu Luo <mitltlatltl@gmail.com>
  7 *
  8 * Based on vendor driver:
  9 * Copyright (c) 2023 AWINIC Technology CO., LTD
 10 */
 11
 12#include <linux/backlight.h>
 13#include <linux/bitfield.h>
 14#include <linux/delay.h>
 15#include <linux/gpio/consumer.h>
 16#include <linux/i2c.h>
 17#include <linux/kernel.h>
 18#include <linux/module.h>
 19#include <linux/regmap.h>
 20
 21#define AW99706_MAX_BRT_LVL		4095
 22#define AW99706_REG_MAX			0x1F
 23#define AW99706_ID			0x07
 24
 25/* registers list */
 26#define AW99706_CFG0_REG			0x00
 27#define AW99706_DIM_MODE_MASK			GENMASK(1, 0)
 28
 29#define AW99706_CFG1_REG			0x01
 30#define AW99706_SW_FREQ_MASK			GENMASK(3, 0)
 31#define AW99706_SW_ILMT_MASK			GENMASK(5, 4)
 32
 33#define AW99706_CFG2_REG			0x02
 34#define AW99706_ILED_MAX_MASK			GENMASK(6, 0)
 35#define AW99706_UVLOSEL_MASK			BIT(7)
 36
 37#define AW99706_CFG3_REG			0x03
 38#define AW99706_CFG4_REG			0x04
 39#define AW99706_BRT_MSB_MASK			GENMASK(3, 0)
 40
 41#define AW99706_CFG5_REG			0x05
 42#define AW99706_BRT_LSB_MASK			GENMASK(7, 0)
 43
 44#define AW99706_CFG6_REG			0x06
 45#define AW99706_RAMP_CTL_MASK			GENMASK(7, 6)
 46
 47#define AW99706_CFG7_REG			0x07
 48#define AW99706_CFG8_REG			0x08
 49#define AW99706_CFG9_REG			0x09
 50#define AW99706_CFGA_REG			0x0A
 51#define AW99706_CFGB_REG			0x0B
 52#define AW99706_CFGC_REG			0x0C
 53#define AW99706_CFGD_REG			0x0D
 54#define AW99706_FLAG_REG			0x10
 55#define AW99706_BACKLIGHT_EN_MASK		BIT(7)
 56
 57#define AW99706_CHIPID_REG			0x11
 58#define AW99706_LED_OPEN_FLAG_REG		0x12
 59#define AW99706_LED_SHORT_FLAG_REG		0x13
 60#define AW99706_MTPLDOSEL_REG			0x1E
 61#define AW99706_MTPRUN_REG			0x1F
 62
 63#define RESV	0
 64
 65/* Boost switching frequency table, in Hz */
 66static const u32 aw99706_sw_freq_tbl[] = {
 67	RESV, RESV, RESV, RESV, 300000, 400000, 500000, 600000,
 68	660000, 750000, 850000, 1000000, 1200000, 1330000, 1500000, 1700000
 69};
 70
 71/* Switching current limitation table, in uA */
 72static const u32 aw99706_sw_ilmt_tbl[] = {
 73	1500000, 2000000, 2500000, 3000000
 74};
 75
 76/* ULVO threshold table, in uV */
 77static const u32 aw99706_ulvo_thres_tbl[] = {
 78	2200000, 5000000
 79};
 80
 81struct aw99706_dt_prop {
 82	const char * const name;
 83	int (*lookup)(const struct aw99706_dt_prop *prop, u32 dt_val, u8 *val);
 84	const u32 * const lookup_tbl;
 85	u8 tbl_size;
 86	u8 reg;
 87	u8 mask;
 88	u32 def_val;
 89};
 90
 91static int aw99706_dt_property_lookup(const struct aw99706_dt_prop *prop,
 92				      u32 dt_val, u8 *val)
 93{
 94	int i;
 95
 96	if (!prop->lookup_tbl) {
 97		*val = dt_val;
 98		return 0;
 99	}
100
101	for (i = 0; i < prop->tbl_size; i++)
102		if (prop->lookup_tbl[i] == dt_val)
103			break;
104
105	*val = i;
106
107	return i == prop->tbl_size ? -1 : 0;
108}
109
110#define MIN_ILED_MAX	5000
111#define MAX_ILED_MAX	50000
112#define STEP_ILED_MAX	500
113
114static int
115aw99706_dt_property_iled_max_convert(const struct aw99706_dt_prop *prop,
116				     u32 dt_val, u8 *val)
117{
118	if (dt_val > MAX_ILED_MAX || dt_val < MIN_ILED_MAX)
119		return -1;
120
121	*val = (dt_val - MIN_ILED_MAX) / STEP_ILED_MAX;
122
123	return (dt_val - MIN_ILED_MAX) % STEP_ILED_MAX;
124}
125
126static const struct aw99706_dt_prop aw99706_dt_props[] = {
127	{
128		"awinic,dim-mode", aw99706_dt_property_lookup,
129		NULL, 0,
130		AW99706_CFG0_REG, AW99706_DIM_MODE_MASK, 1,
131	},
132	{
133		"awinic,sw-freq", aw99706_dt_property_lookup,
134		aw99706_sw_freq_tbl, ARRAY_SIZE(aw99706_sw_freq_tbl),
135		AW99706_CFG1_REG, AW99706_SW_FREQ_MASK, 750000,
136	},
137	{
138		"awinic,sw-ilmt", aw99706_dt_property_lookup,
139		aw99706_sw_ilmt_tbl, ARRAY_SIZE(aw99706_sw_ilmt_tbl),
140		AW99706_CFG1_REG, AW99706_SW_ILMT_MASK, 3000000,
141	},
142	{
143		"awinic,iled-max", aw99706_dt_property_iled_max_convert,
144		NULL, 0,
145		AW99706_CFG2_REG, AW99706_ILED_MAX_MASK, 20000,
146
147	},
148	{
149		"awinic,uvlo-thres", aw99706_dt_property_lookup,
150		aw99706_ulvo_thres_tbl, ARRAY_SIZE(aw99706_ulvo_thres_tbl),
151		AW99706_CFG2_REG, AW99706_UVLOSEL_MASK, 2200000,
152	},
153	{
154		"awinic,ramp-ctl", aw99706_dt_property_lookup,
155		NULL, 0,
156		AW99706_CFG6_REG, AW99706_RAMP_CTL_MASK, 2,
157	}
158};
159
160struct reg_init_data {
161	u8 reg;
162	u8 mask;
163	u8 val;
164};
165
166struct aw99706_device {
167	struct i2c_client *client;
168	struct device *dev;
169	struct regmap *regmap;
170	struct backlight_device *bl_dev;
171	struct gpio_desc *hwen_gpio;
172	struct reg_init_data init_tbl[ARRAY_SIZE(aw99706_dt_props)];
173	bool bl_enable;
174};
175
176enum reg_access {
177	REG_NONE_ACCESS	= 0,
178	REG_RD_ACCESS	= 1,
179	REG_WR_ACCESS	= 2,
180};
181
182static const u8 aw99706_regs[AW99706_REG_MAX + 1] = {
183	[AW99706_CFG0_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
184	[AW99706_CFG1_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
185	[AW99706_CFG2_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
186	[AW99706_CFG3_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
187	[AW99706_CFG4_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
188	[AW99706_CFG5_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
189	[AW99706_CFG6_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
190	[AW99706_CFG7_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
191	[AW99706_CFG8_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
192	[AW99706_CFG9_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
193	[AW99706_CFGA_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
194	[AW99706_CFGB_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
195	[AW99706_CFGC_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
196	[AW99706_CFGD_REG]		= REG_RD_ACCESS | REG_WR_ACCESS,
197	[AW99706_FLAG_REG]		= REG_RD_ACCESS,
198	[AW99706_CHIPID_REG]		= REG_RD_ACCESS,
199	[AW99706_LED_OPEN_FLAG_REG]	= REG_RD_ACCESS,
200	[AW99706_LED_SHORT_FLAG_REG]	= REG_RD_ACCESS,
201
202	/*
203	 * Write bit is dropped here, writing BIT(0) to MTPLDOSEL will unlock
204	 * Multi-time Programmable (MTP).
205	 */
206	[AW99706_MTPLDOSEL_REG]		= REG_RD_ACCESS,
207	[AW99706_MTPRUN_REG]		= REG_NONE_ACCESS,
208};
209
210static bool aw99706_readable_reg(struct device *dev, unsigned int reg)
211{
212	return aw99706_regs[reg] & REG_RD_ACCESS;
213}
214
215static bool aw99706_writeable_reg(struct device *dev, unsigned int reg)
216{
217	return aw99706_regs[reg] & REG_WR_ACCESS;
218}
219
220static inline int aw99706_i2c_read(struct aw99706_device *aw, u8 reg,
221				   unsigned int *val)
222{
223	return regmap_read(aw->regmap, reg, val);
224}
225
226static inline int aw99706_i2c_write(struct aw99706_device *aw, u8 reg, u8 val)
227{
228	return regmap_write(aw->regmap, reg, val);
229}
230
231static inline int aw99706_i2c_update_bits(struct aw99706_device *aw, u8 reg,
232					  u8 mask, u8 val)
233{
234	return regmap_update_bits(aw->regmap, reg, mask, val);
235}
236
237static void aw99706_dt_parse(struct aw99706_device *aw,
238			     struct backlight_properties *bl_props)
239{
240	const struct aw99706_dt_prop *prop;
241	u32 dt_val;
242	int ret, i;
243	u8 val;
244
245	for (i = 0; i < ARRAY_SIZE(aw99706_dt_props); i++) {
246		prop = &aw99706_dt_props[i];
247		ret = device_property_read_u32(aw->dev, prop->name, &dt_val);
248		if (ret < 0)
249			dt_val = prop->def_val;
250
251		if (prop->lookup(prop, dt_val, &val)) {
252			dev_warn(aw->dev, "invalid value %d for property %s, using default value %d\n",
253				 dt_val, prop->name, prop->def_val);
254
255			prop->lookup(prop, prop->def_val, &val);
256		}
257
258		aw->init_tbl[i].reg = prop->reg;
259		aw->init_tbl[i].mask = prop->mask;
260		aw->init_tbl[i].val = val << __ffs(prop->mask);
261	}
262
263	bl_props->brightness = AW99706_MAX_BRT_LVL >> 1;
264	bl_props->max_brightness = AW99706_MAX_BRT_LVL;
265	device_property_read_u32(aw->dev, "default-brightness",
266				 &bl_props->brightness);
267	device_property_read_u32(aw->dev, "max-brightness",
268				 &bl_props->max_brightness);
269
270	if (bl_props->max_brightness > AW99706_MAX_BRT_LVL)
271		bl_props->max_brightness = AW99706_MAX_BRT_LVL;
272
273	if (bl_props->brightness > bl_props->max_brightness)
274		bl_props->brightness = bl_props->max_brightness;
275}
276
277static int aw99706_hw_init(struct aw99706_device *aw)
278{
279	int ret, i;
280
281	gpiod_set_value_cansleep(aw->hwen_gpio, 1);
282
283	for (i = 0; i < ARRAY_SIZE(aw->init_tbl); i++) {
284		ret = aw99706_i2c_update_bits(aw, aw->init_tbl[i].reg,
285					      aw->init_tbl[i].mask,
286					      aw->init_tbl[i].val);
287		if (ret < 0) {
288			dev_err(aw->dev, "Failed to write init data %d\n", ret);
289			return ret;
290		}
291	}
292
293	return 0;
294}
295
296static int aw99706_bl_enable(struct aw99706_device *aw, bool en)
297{
298	int ret;
299	u8 val;
300
301	val = FIELD_PREP(AW99706_BACKLIGHT_EN_MASK, en);
302	ret = aw99706_i2c_update_bits(aw, AW99706_CFGD_REG,
303				      AW99706_BACKLIGHT_EN_MASK, val);
304	if (ret)
305		dev_err(aw->dev, "Failed to enable backlight!\n");
306
307	return ret;
308}
309
310static int aw99706_update_brightness(struct aw99706_device *aw, u32 brt_lvl)
311{
312	bool bl_enable_now = !!brt_lvl;
313	int ret;
314
315	ret = aw99706_i2c_write(aw, AW99706_CFG4_REG,
316				(brt_lvl >> 8) & AW99706_BRT_MSB_MASK);
317	if (ret < 0)
318		return ret;
319
320	ret = aw99706_i2c_write(aw, AW99706_CFG5_REG,
321				brt_lvl & AW99706_BRT_LSB_MASK);
322	if (ret < 0)
323		return ret;
324
325	if (aw->bl_enable != bl_enable_now) {
326		ret = aw99706_bl_enable(aw, bl_enable_now);
327		if (!ret)
328			aw->bl_enable = bl_enable_now;
329	}
330
331	return ret;
332}
333
334static int aw99706_bl_update_status(struct backlight_device *bl)
335{
336	struct aw99706_device *aw = bl_get_data(bl);
337
338	return aw99706_update_brightness(aw, bl->props.brightness);
339}
340
341static const struct backlight_ops aw99706_bl_ops = {
342	.options = BL_CORE_SUSPENDRESUME,
343	.update_status = aw99706_bl_update_status,
344};
345
346static const struct regmap_config aw99706_regmap_config = {
347	.reg_bits = 8,
348	.val_bits = 8,
349	.max_register = AW99706_REG_MAX,
350	.writeable_reg = aw99706_writeable_reg,
351	.readable_reg = aw99706_readable_reg,
352};
353
354static int aw99706_chip_id_read(struct aw99706_device *aw)
355{
356	int ret;
357	unsigned int val;
358
359	ret = aw99706_i2c_read(aw, AW99706_CHIPID_REG, &val);
360	if (ret < 0)
361		return ret;
362
363	return val;
364}
365
366static int aw99706_probe(struct i2c_client *client)
367{
368	struct device *dev = &client->dev;
369	struct aw99706_device *aw;
370	struct backlight_device *bl_dev;
371	struct backlight_properties props = {};
372	int ret = 0;
373
374	aw = devm_kzalloc(dev, sizeof(*aw), GFP_KERNEL);
375	if (!aw)
376		return -ENOMEM;
377
378	aw->client = client;
379	aw->dev = dev;
380	i2c_set_clientdata(client, aw);
381
382	aw->regmap = devm_regmap_init_i2c(client, &aw99706_regmap_config);
383	if (IS_ERR(aw->regmap))
384		return dev_err_probe(dev, PTR_ERR(aw->regmap),
385				     "Failed to init regmap\n");
386
387	ret = aw99706_chip_id_read(aw);
388	if (ret != AW99706_ID)
389		return dev_err_probe(dev, -ENODEV,
390				     "Unknown chip id 0x%02x\n", ret);
391
392	aw99706_dt_parse(aw, &props);
393
394	aw->hwen_gpio = devm_gpiod_get(aw->dev, "enable", GPIOD_OUT_LOW);
395	if (IS_ERR(aw->hwen_gpio))
396		return dev_err_probe(dev, PTR_ERR(aw->hwen_gpio),
397				     "Failed to get enable gpio\n");
398
399	ret = aw99706_hw_init(aw);
400	if (ret < 0)
401		return dev_err_probe(dev, ret,
402				     "Failed to initialize the chip\n");
403
404	props.type = BACKLIGHT_RAW;
405	props.scale = BACKLIGHT_SCALE_LINEAR;
406
407	bl_dev = devm_backlight_device_register(dev, "aw99706-backlight", dev,
408						aw, &aw99706_bl_ops, &props);
409	if (IS_ERR(bl_dev))
410		return dev_err_probe(dev, PTR_ERR(bl_dev),
411				     "Failed to register backlight!\n");
412
413	aw->bl_dev = bl_dev;
414
415	return 0;
416}
417
418static void aw99706_remove(struct i2c_client *client)
419{
420	struct aw99706_device *aw = i2c_get_clientdata(client);
421
422	aw99706_update_brightness(aw, 0);
423
424	msleep(50);
425
426	gpiod_set_value_cansleep(aw->hwen_gpio, 0);
427}
428
429static int aw99706_suspend(struct device *dev)
430{
431	struct aw99706_device *aw = dev_get_drvdata(dev);
432
433	return aw99706_update_brightness(aw, 0);
434}
435
436static int aw99706_resume(struct device *dev)
437{
438	struct aw99706_device *aw = dev_get_drvdata(dev);
439
440	return aw99706_hw_init(aw);
441}
442
443static DEFINE_SIMPLE_DEV_PM_OPS(aw99706_pm_ops, aw99706_suspend, aw99706_resume);
444
445static const struct i2c_device_id aw99706_ids[] = {
446	{ "aw99706" },
447	{ }
448};
449MODULE_DEVICE_TABLE(i2c, aw99706_ids);
450
451static const struct of_device_id aw99706_match_table[] = {
452	{ .compatible = "awinic,aw99706", },
453	{ }
454};
455MODULE_DEVICE_TABLE(of, aw99706_match_table);
456
457static struct i2c_driver aw99706_i2c_driver = {
458	.probe = aw99706_probe,
459	.remove = aw99706_remove,
460	.id_table = aw99706_ids,
461	.driver = {
462		.name = "aw99706",
463		.of_match_table = aw99706_match_table,
464		.pm = pm_ptr(&aw99706_pm_ops),
465	},
466};
467
468module_i2c_driver(aw99706_i2c_driver);
469
470MODULE_LICENSE("GPL v2");
471MODULE_DESCRIPTION("BackLight driver for aw99706");
~/ Read-only file preview.