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

source code of linux/drivers/video/backlight/mp3309c.c