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