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 | |
49 | enum mp3309c_status_value { |
50 | FIRST_POWER_ON, |
51 | BACKLIGHT_OFF, |
52 | BACKLIGHT_ON, |
53 | }; |
54 | |
55 | enum mp3309c_dimming_mode_value { |
56 | DIMMING_PWM, |
57 | DIMMING_ANALOG_I2C, |
58 | }; |
59 | |
60 | struct 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 | |
70 | struct 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 | |
79 | static 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 | |
87 | static 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 | |
119 | static 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 | |
200 | static const struct backlight_ops mp3309c_bl_ops = { |
201 | .update_status = mp3309c_bl_update_status, |
202 | }; |
203 | |
204 | static 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 | |
323 | static 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 | |
391 | static 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 | |
401 | static const struct of_device_id mp3309c_match_table[] = { |
402 | { .compatible = "mps,mp3309c" , }, |
403 | { }, |
404 | }; |
405 | MODULE_DEVICE_TABLE(of, mp3309c_match_table); |
406 | |
407 | static const struct i2c_device_id mp3309c_id[] = { |
408 | { "mp3309c" , 0 }, |
409 | { } |
410 | }; |
411 | MODULE_DEVICE_TABLE(i2c, mp3309c_id); |
412 | |
413 | static 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 | |
423 | module_i2c_driver(mp3309c_i2c_driver); |
424 | |
425 | MODULE_DESCRIPTION("Backlight Driver for MPS MP3309C" ); |
426 | MODULE_AUTHOR("Flavio Suligoi <f.suligoi@asem.it>" ); |
427 | MODULE_LICENSE("GPL" ); |
428 | |