xref: /linux/drivers/video/backlight/mp3309c.c (revision a10ea943356b9d70c5616a0a06f6fa97cfdaccb1)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Driver for MPS MP3309C White LED driver with I2C interface
4  *
5  * This driver support both analog (by I2C commands) and PWM dimming control
6  * modes.
7  *
8  * Copyright (C) 2023 ASEM Srl
9  * Author: Flavio Suligoi <f.suligoi@asem.it>
10  *
11  * Based on pwm_bl.c
12  */
13 
14 #include <linux/backlight.h>
15 #include <linux/delay.h>
16 #include <linux/gpio/consumer.h>
17 #include <linux/i2c.h>
18 #include <linux/property.h>
19 #include <linux/pwm.h>
20 #include <linux/regmap.h>
21 
22 #define REG_I2C_0	0x00
23 #define REG_I2C_1	0x01
24 
25 #define REG_I2C_0_EN	0x80
26 #define REG_I2C_0_D0	0x40
27 #define REG_I2C_0_D1	0x20
28 #define REG_I2C_0_D2	0x10
29 #define REG_I2C_0_D3	0x08
30 #define REG_I2C_0_D4	0x04
31 #define REG_I2C_0_RSRV1	0x02
32 #define REG_I2C_0_RSRV2	0x01
33 
34 #define REG_I2C_1_RSRV1	0x80
35 #define REG_I2C_1_DIMS	0x40
36 #define REG_I2C_1_SYNC	0x20
37 #define REG_I2C_1_OVP0	0x10
38 #define REG_I2C_1_OVP1	0x08
39 #define REG_I2C_1_VOS	0x04
40 #define REG_I2C_1_LEDO	0x02
41 #define REG_I2C_1_OTP	0x01
42 
43 #define ANALOG_I2C_NUM_LEVELS	32		/* 0..31 */
44 #define ANALOG_I2C_REG_MASK	0x7c
45 
46 #define MP3309C_PWM_DEFAULT_NUM_LEVELS	256	/* 0..255 */
47 
48 enum mp3309c_status_value {
49 	FIRST_POWER_ON,
50 	BACKLIGHT_OFF,
51 	BACKLIGHT_ON,
52 };
53 
54 enum mp3309c_dimming_mode_value {
55 	DIMMING_PWM,
56 	DIMMING_ANALOG_I2C,
57 };
58 
59 struct mp3309c_platform_data {
60 	unsigned int max_brightness;
61 	unsigned int default_brightness;
62 	unsigned int *levels;
63 	u8  dimming_mode;
64 	u8  over_voltage_protection;
65 	bool sync_mode;
66 	u8 status;
67 };
68 
69 struct mp3309c_chip {
70 	struct device *dev;
71 	struct mp3309c_platform_data *pdata;
72 	struct backlight_device *bl;
73 	struct gpio_desc *enable_gpio;
74 	struct regmap *regmap;
75 	struct pwm_device *pwmd;
76 };
77 
78 static const struct regmap_config mp3309c_regmap = {
79 	.name = "mp3309c_regmap",
80 	.reg_bits = 8,
81 	.reg_stride = 1,
82 	.val_bits = 8,
83 	.max_register = REG_I2C_1,
84 };
85 
86 static int mp3309c_enable_device(struct mp3309c_chip *chip)
87 {
88 	u8 reg_val;
89 	int ret;
90 
91 	/* I2C register #0 - Device enable */
92 	ret = regmap_update_bits(chip->regmap, REG_I2C_0, REG_I2C_0_EN,
93 				 REG_I2C_0_EN);
94 	if (ret)
95 		return ret;
96 
97 	/*
98 	 * I2C register #1 - Set working mode:
99 	 *  - enable/disable synchronous mode
100 	 *  - set overvoltage protection (OVP)
101 	 */
102 	reg_val = 0x00;
103 	if (chip->pdata->sync_mode)
104 		reg_val |= REG_I2C_1_SYNC;
105 	reg_val |= chip->pdata->over_voltage_protection;
106 	ret = regmap_write(chip->regmap, REG_I2C_1, reg_val);
107 	if (ret)
108 		return ret;
109 
110 	return 0;
111 }
112 
113 static int mp3309c_bl_update_status(struct backlight_device *bl)
114 {
115 	struct mp3309c_chip *chip = bl_get_data(bl);
116 	int brightness = backlight_get_brightness(bl);
117 	struct pwm_state pwmstate;
118 	unsigned int analog_val, bits_val;
119 	int i, ret;
120 
121 	if (chip->pdata->dimming_mode == DIMMING_PWM) {
122 		/*
123 		 * PWM control mode
124 		 */
125 		pwm_get_state(chip->pwmd, &pwmstate);
126 		pwm_set_relative_duty_cycle(&pwmstate,
127 					    chip->pdata->levels[brightness],
128 					    chip->pdata->levels[chip->pdata->max_brightness]);
129 		pwmstate.enabled = true;
130 		ret = pwm_apply_might_sleep(chip->pwmd, &pwmstate);
131 		if (ret)
132 			return ret;
133 
134 		switch (chip->pdata->status) {
135 		case FIRST_POWER_ON:
136 		case BACKLIGHT_OFF:
137 			/*
138 			 * After 20ms of low pwm signal level, the chip turns
139 			 * off automatically. In this case, before enabling the
140 			 * chip again, we must wait about 10ms for pwm signal to
141 			 * stabilize.
142 			 */
143 			if (brightness > 0) {
144 				msleep(10);
145 				mp3309c_enable_device(chip);
146 				chip->pdata->status = BACKLIGHT_ON;
147 			} else {
148 				chip->pdata->status = BACKLIGHT_OFF;
149 			}
150 			break;
151 		case BACKLIGHT_ON:
152 			if (brightness == 0)
153 				chip->pdata->status = BACKLIGHT_OFF;
154 			break;
155 		}
156 	} else {
157 		/*
158 		 * Analog (by I2C command) control mode
159 		 *
160 		 * The first time, before setting brightness, we must enable the
161 		 * device
162 		 */
163 		if (chip->pdata->status == FIRST_POWER_ON)
164 			mp3309c_enable_device(chip);
165 
166 		/*
167 		 * Dimming mode I2C command (fixed dimming range 0..31)
168 		 *
169 		 * The 5 bits of the dimming analog value D4..D0 is allocated
170 		 * in the I2C register #0, in the following way:
171 		 *
172 		 *     +--+--+--+--+--+--+--+--+
173 		 *     |EN|D0|D1|D2|D3|D4|XX|XX|
174 		 *     +--+--+--+--+--+--+--+--+
175 		 */
176 		analog_val = brightness;
177 		bits_val = 0;
178 		for (i = 0; i <= 5; i++)
179 			bits_val += ((analog_val >> i) & 0x01) << (6 - i);
180 		ret = regmap_update_bits(chip->regmap, REG_I2C_0,
181 					 ANALOG_I2C_REG_MASK, bits_val);
182 		if (ret)
183 			return ret;
184 
185 		if (brightness > 0)
186 			chip->pdata->status = BACKLIGHT_ON;
187 		else
188 			chip->pdata->status = BACKLIGHT_OFF;
189 	}
190 
191 	return 0;
192 }
193 
194 static const struct backlight_ops mp3309c_bl_ops = {
195 	.update_status = mp3309c_bl_update_status,
196 };
197 
198 static int mp3309c_parse_fwnode(struct mp3309c_chip *chip,
199 				struct mp3309c_platform_data *pdata)
200 {
201 	int ret, i;
202 	unsigned int tmp_value;
203 	struct device *dev = chip->dev;
204 	int num_levels;
205 
206 	if (!dev_fwnode(dev))
207 		return dev_err_probe(dev, -ENODEV, "failed to get firmware node\n");
208 
209 	/*
210 	 * Dimming mode: the MP3309C provides two dimming control mode:
211 	 *
212 	 * - PWM mode
213 	 * - Analog by I2C control mode (default)
214 	 *
215 	 * I2C control mode is assumed as default but, if the pwms property is
216 	 * found in the backlight node, the mode switches to PWM mode.
217 	 */
218 	pdata->dimming_mode = DIMMING_ANALOG_I2C;
219 	if (device_property_present(dev, "pwms")) {
220 		chip->pwmd = devm_pwm_get(dev, NULL);
221 		if (IS_ERR(chip->pwmd))
222 			return dev_err_probe(dev, PTR_ERR(chip->pwmd), "error getting pwm data\n");
223 		pdata->dimming_mode = DIMMING_PWM;
224 	}
225 
226 	/*
227 	 * In I2C control mode the dimming levels (0..31) are fixed by the
228 	 * hardware, while in PWM control mode they can be chosen by the user,
229 	 * to allow nonlinear mappings.
230 	 */
231 	if  (pdata->dimming_mode == DIMMING_ANALOG_I2C) {
232 		/*
233 		 * Analog (by I2C commands) control mode: fixed 0..31 brightness
234 		 * levels
235 		 */
236 		num_levels = ANALOG_I2C_NUM_LEVELS;
237 
238 		/* Enable GPIO used in I2C dimming mode only */
239 		chip->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH);
240 		if (IS_ERR(chip->enable_gpio))
241 			return dev_err_probe(dev, PTR_ERR(chip->enable_gpio),
242 					     "error getting enable gpio\n");
243 	} else {
244 		/*
245 		 * PWM control mode: check for brightness level in DT
246 		 */
247 		if (device_property_present(dev, "brightness-levels")) {
248 			/* Read brightness levels from DT */
249 			num_levels = device_property_count_u32(dev, "brightness-levels");
250 			if (num_levels < 2)
251 				return -EINVAL;
252 		} else {
253 			/* Use default brightness levels */
254 			num_levels = MP3309C_PWM_DEFAULT_NUM_LEVELS;
255 		}
256 	}
257 
258 	/* Fill brightness levels array */
259 	pdata->levels = devm_kcalloc(dev, num_levels, sizeof(*pdata->levels), GFP_KERNEL);
260 	if (!pdata->levels)
261 		return -ENOMEM;
262 	if (device_property_present(dev, "brightness-levels")) {
263 		ret = device_property_read_u32_array(dev, "brightness-levels",
264 						     pdata->levels, num_levels);
265 		if (ret < 0)
266 			return ret;
267 	} else {
268 		for (i = 0; i < num_levels; i++)
269 			pdata->levels[i] = i;
270 	}
271 
272 	pdata->max_brightness = num_levels - 1;
273 
274 	ret = device_property_read_u32(dev, "default-brightness", &pdata->default_brightness);
275 	if (ret)
276 		pdata->default_brightness = pdata->max_brightness;
277 	if (pdata->default_brightness > pdata->max_brightness) {
278 		dev_err_probe(dev, -ERANGE, "default brightness exceeds max brightness\n");
279 		pdata->default_brightness = pdata->max_brightness;
280 	}
281 
282 	/*
283 	 * Over-voltage protection (OVP)
284 	 *
285 	 * This (optional) property values are:
286 	 *
287 	 *  - 13.5V
288 	 *  - 24V
289 	 *  - 35.5V (hardware default setting)
290 	 *
291 	 * If missing, the default value for OVP is 35.5V
292 	 */
293 	pdata->over_voltage_protection = REG_I2C_1_OVP1;
294 	ret = device_property_read_u32(dev, "mps,overvoltage-protection-microvolt", &tmp_value);
295 	if (!ret) {
296 		switch (tmp_value) {
297 		case 13500000:
298 			pdata->over_voltage_protection = 0x00;
299 			break;
300 		case 24000000:
301 			pdata->over_voltage_protection = REG_I2C_1_OVP0;
302 			break;
303 		case 35500000:
304 			pdata->over_voltage_protection = REG_I2C_1_OVP1;
305 			break;
306 		default:
307 			return -EINVAL;
308 		}
309 	}
310 
311 	/* Synchronous (default) and non-synchronous mode */
312 	pdata->sync_mode = !device_property_read_bool(dev, "mps,no-sync-mode");
313 
314 	return 0;
315 }
316 
317 static int mp3309c_probe(struct i2c_client *client)
318 {
319 	struct device *dev = &client->dev;
320 	struct mp3309c_platform_data *pdata = dev_get_platdata(dev);
321 	struct mp3309c_chip *chip;
322 	struct backlight_properties props;
323 	struct pwm_state pwmstate;
324 	int ret;
325 
326 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
327 		return dev_err_probe(dev, -EOPNOTSUPP, "failed to check i2c functionality\n");
328 
329 	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
330 	if (!chip)
331 		return -ENOMEM;
332 
333 	chip->dev = dev;
334 
335 	chip->regmap = devm_regmap_init_i2c(client, &mp3309c_regmap);
336 	if (IS_ERR(chip->regmap))
337 		return dev_err_probe(dev, PTR_ERR(chip->regmap),
338 				     "failed to allocate register map\n");
339 
340 	i2c_set_clientdata(client, chip);
341 
342 	if (!pdata) {
343 		pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
344 		if (!pdata)
345 			return -ENOMEM;
346 
347 		ret = mp3309c_parse_fwnode(chip, pdata);
348 		if (ret)
349 			return ret;
350 	}
351 	chip->pdata = pdata;
352 
353 	/* Backlight properties */
354 	props = (typeof(props)){
355 		.brightness = pdata->default_brightness,
356 		.max_brightness = pdata->max_brightness,
357 		.scale = BACKLIGHT_SCALE_LINEAR,
358 		.type = BACKLIGHT_RAW,
359 		.power = BACKLIGHT_POWER_ON,
360 	};
361 	chip->bl = devm_backlight_device_register(dev, "mp3309c", dev, chip,
362 						  &mp3309c_bl_ops, &props);
363 	if (IS_ERR(chip->bl))
364 		return dev_err_probe(dev, PTR_ERR(chip->bl),
365 				     "error registering backlight device\n");
366 
367 	/* In PWM dimming mode, enable pwm device */
368 	if (chip->pdata->dimming_mode == DIMMING_PWM) {
369 		pwm_init_state(chip->pwmd, &pwmstate);
370 		pwm_set_relative_duty_cycle(&pwmstate,
371 					    chip->pdata->default_brightness,
372 					    chip->pdata->max_brightness);
373 		pwmstate.enabled = true;
374 		ret = pwm_apply_might_sleep(chip->pwmd, &pwmstate);
375 		if (ret)
376 			return dev_err_probe(dev, ret, "error setting pwm device\n");
377 	}
378 
379 	chip->pdata->status = FIRST_POWER_ON;
380 	backlight_update_status(chip->bl);
381 
382 	return 0;
383 }
384 
385 static void mp3309c_remove(struct i2c_client *client)
386 {
387 	struct mp3309c_chip *chip = i2c_get_clientdata(client);
388 	struct backlight_device *bl = chip->bl;
389 
390 	bl->props.power = BACKLIGHT_POWER_OFF;
391 	bl->props.brightness = 0;
392 	backlight_update_status(chip->bl);
393 }
394 
395 static const struct of_device_id mp3309c_match_table[] = {
396 	{ .compatible = "mps,mp3309c", },
397 	{ },
398 };
399 MODULE_DEVICE_TABLE(of, mp3309c_match_table);
400 
401 static const struct i2c_device_id mp3309c_id[] = {
402 	{ .name = "mp3309c" },
403 	{ }
404 };
405 MODULE_DEVICE_TABLE(i2c, mp3309c_id);
406 
407 static struct i2c_driver mp3309c_i2c_driver = {
408 	.driver	= {
409 			.name		= KBUILD_MODNAME,
410 			.of_match_table	= mp3309c_match_table,
411 	},
412 	.probe		= mp3309c_probe,
413 	.remove		= mp3309c_remove,
414 	.id_table	= mp3309c_id,
415 };
416 
417 module_i2c_driver(mp3309c_i2c_driver);
418 
419 MODULE_DESCRIPTION("Backlight Driver for MPS MP3309C");
420 MODULE_AUTHOR("Flavio Suligoi <f.suligoi@asem.it>");
421 MODULE_LICENSE("GPL");
422