]> Git Repo - J-linux.git/blob - drivers/video/backlight/mp3309c.c
Merge tag 'vfs-6.13-rc7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
[J-linux.git] / drivers / video / backlight / mp3309c.c
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 <[email protected]>
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(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          *  - enable/disable synchronous mode
101          *  - set overvoltage protection (OVP)
102          */
103         reg_val = 0x00;
104         if (chip->pdata->sync_mode)
105                 reg_val |= REG_I2C_1_SYNC;
106         reg_val |= chip->pdata->over_voltage_protection;
107         ret = regmap_write(chip->regmap, REG_I2C_1, reg_val);
108         if (ret)
109                 return ret;
110
111         return 0;
112 }
113
114 static int mp3309c_bl_update_status(struct backlight_device *bl)
115 {
116         struct mp3309c_chip *chip = bl_get_data(bl);
117         int brightness = backlight_get_brightness(bl);
118         struct pwm_state pwmstate;
119         unsigned int analog_val, bits_val;
120         int i, ret;
121
122         if (chip->pdata->dimming_mode == DIMMING_PWM) {
123                 /*
124                  * PWM control mode
125                  */
126                 pwm_get_state(chip->pwmd, &pwmstate);
127                 pwm_set_relative_duty_cycle(&pwmstate,
128                                             chip->pdata->levels[brightness],
129                                             chip->pdata->levels[chip->pdata->max_brightness]);
130                 pwmstate.enabled = true;
131                 ret = pwm_apply_might_sleep(chip->pwmd, &pwmstate);
132                 if (ret)
133                         return ret;
134
135                 switch (chip->pdata->status) {
136                 case FIRST_POWER_ON:
137                 case BACKLIGHT_OFF:
138                         /*
139                          * After 20ms of low pwm signal level, the chip turns
140                          * off automatically. In this case, before enabling the
141                          * chip again, we must wait about 10ms for pwm signal to
142                          * stabilize.
143                          */
144                         if (brightness > 0) {
145                                 msleep(10);
146                                 mp3309c_enable_device(chip);
147                                 chip->pdata->status = BACKLIGHT_ON;
148                         } else {
149                                 chip->pdata->status = BACKLIGHT_OFF;
150                         }
151                         break;
152                 case BACKLIGHT_ON:
153                         if (brightness == 0)
154                                 chip->pdata->status = BACKLIGHT_OFF;
155                         break;
156                 }
157         } else {
158                 /*
159                  * Analog (by I2C command) control mode
160                  *
161                  * The first time, before setting brightness, we must enable the
162                  * device
163                  */
164                 if (chip->pdata->status == FIRST_POWER_ON)
165                         mp3309c_enable_device(chip);
166
167                 /*
168                  * Dimming mode I2C command (fixed dimming range 0..31)
169                  *
170                  * The 5 bits of the dimming analog value D4..D0 is allocated
171                  * in the I2C register #0, in the following way:
172                  *
173                  *     +--+--+--+--+--+--+--+--+
174                  *     |EN|D0|D1|D2|D3|D4|XX|XX|
175                  *     +--+--+--+--+--+--+--+--+
176                  */
177                 analog_val = brightness;
178                 bits_val = 0;
179                 for (i = 0; i <= 5; i++)
180                         bits_val += ((analog_val >> i) & 0x01) << (6 - i);
181                 ret = regmap_update_bits(chip->regmap, REG_I2C_0,
182                                          ANALOG_I2C_REG_MASK, bits_val);
183                 if (ret)
184                         return ret;
185
186                 if (brightness > 0)
187                         chip->pdata->status = BACKLIGHT_ON;
188                 else
189                         chip->pdata->status = BACKLIGHT_OFF;
190         }
191
192         return 0;
193 }
194
195 static const struct backlight_ops mp3309c_bl_ops = {
196         .update_status = mp3309c_bl_update_status,
197 };
198
199 static int mp3309c_parse_fwnode(struct mp3309c_chip *chip,
200                                 struct mp3309c_platform_data *pdata)
201 {
202         int ret, i;
203         unsigned int tmp_value;
204         struct device *dev = chip->dev;
205         int num_levels;
206
207         if (!dev_fwnode(dev))
208                 return dev_err_probe(dev, -ENODEV, "failed to get firmware node\n");
209
210         /*
211          * Dimming mode: the MP3309C provides two dimming control mode:
212          *
213          * - PWM mode
214          * - Analog by I2C control mode (default)
215          *
216          * I2C control mode is assumed as default but, if the pwms property is
217          * found in the backlight node, the mode switches to PWM mode.
218          */
219         pdata->dimming_mode = DIMMING_ANALOG_I2C;
220         if (device_property_present(dev, "pwms")) {
221                 chip->pwmd = devm_pwm_get(dev, NULL);
222                 if (IS_ERR(chip->pwmd))
223                         return dev_err_probe(dev, PTR_ERR(chip->pwmd), "error getting pwm data\n");
224                 pdata->dimming_mode = DIMMING_PWM;
225                 pwm_apply_args(chip->pwmd);
226         }
227
228         /*
229          * In I2C control mode the dimming levels (0..31) are fixed by the
230          * hardware, while in PWM control mode they can be chosen by the user,
231          * to allow nonlinear mappings.
232          */
233         if  (pdata->dimming_mode == DIMMING_ANALOG_I2C) {
234                 /*
235                  * Analog (by I2C commands) control mode: fixed 0..31 brightness
236                  * levels
237                  */
238                 num_levels = ANALOG_I2C_NUM_LEVELS;
239
240                 /* Enable GPIO used in I2C dimming mode only */
241                 chip->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH);
242                 if (IS_ERR(chip->enable_gpio))
243                         return dev_err_probe(dev, PTR_ERR(chip->enable_gpio),
244                                              "error getting enable gpio\n");
245         } else {
246                 /*
247                  * PWM control mode: check for brightness level in DT
248                  */
249                 if (device_property_present(dev, "brightness-levels")) {
250                         /* Read brightness levels from DT */
251                         num_levels = device_property_count_u32(dev, "brightness-levels");
252                         if (num_levels < 2)
253                                 return -EINVAL;
254                 } else {
255                         /* Use default brightness levels */
256                         num_levels = MP3309C_PWM_DEFAULT_NUM_LEVELS;
257                 }
258         }
259
260         /* Fill brightness levels array */
261         pdata->levels = devm_kcalloc(dev, num_levels, sizeof(*pdata->levels), GFP_KERNEL);
262         if (!pdata->levels)
263                 return -ENOMEM;
264         if (device_property_present(dev, "brightness-levels")) {
265                 ret = device_property_read_u32_array(dev, "brightness-levels",
266                                                      pdata->levels, num_levels);
267                 if (ret < 0)
268                         return ret;
269         } else {
270                 for (i = 0; i < num_levels; i++)
271                         pdata->levels[i] = i;
272         }
273
274         pdata->max_brightness = num_levels - 1;
275
276         ret = device_property_read_u32(dev, "default-brightness", &pdata->default_brightness);
277         if (ret)
278                 pdata->default_brightness = pdata->max_brightness;
279         if (pdata->default_brightness > pdata->max_brightness) {
280                 dev_err_probe(dev, -ERANGE, "default brightness exceeds max brightness\n");
281                 pdata->default_brightness = pdata->max_brightness;
282         }
283
284         /*
285          * Over-voltage protection (OVP)
286          *
287          * This (optional) property values are:
288          *
289          *  - 13.5V
290          *  - 24V
291          *  - 35.5V (hardware default setting)
292          *
293          * If missing, the default value for OVP is 35.5V
294          */
295         pdata->over_voltage_protection = REG_I2C_1_OVP1;
296         ret = device_property_read_u32(dev, "mps,overvoltage-protection-microvolt", &tmp_value);
297         if (!ret) {
298                 switch (tmp_value) {
299                 case 13500000:
300                         pdata->over_voltage_protection = 0x00;
301                         break;
302                 case 24000000:
303                         pdata->over_voltage_protection = REG_I2C_1_OVP0;
304                         break;
305                 case 35500000:
306                         pdata->over_voltage_protection = REG_I2C_1_OVP1;
307                         break;
308                 default:
309                         return -EINVAL;
310                 }
311         }
312
313         /* Synchronous (default) and non-synchronous mode */
314         pdata->sync_mode = !device_property_read_bool(dev, "mps,no-sync-mode");
315
316         return 0;
317 }
318
319 static int mp3309c_probe(struct i2c_client *client)
320 {
321         struct device *dev = &client->dev;
322         struct mp3309c_platform_data *pdata = dev_get_platdata(dev);
323         struct mp3309c_chip *chip;
324         struct backlight_properties props;
325         struct pwm_state pwmstate;
326         int ret;
327
328         if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
329                 return dev_err_probe(dev, -EOPNOTSUPP, "failed to check i2c functionality\n");
330
331         chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
332         if (!chip)
333                 return -ENOMEM;
334
335         chip->dev = dev;
336
337         chip->regmap = devm_regmap_init_i2c(client, &mp3309c_regmap);
338         if (IS_ERR(chip->regmap))
339                 return dev_err_probe(dev, PTR_ERR(chip->regmap),
340                                      "failed to allocate register map\n");
341
342         i2c_set_clientdata(client, chip);
343
344         if (!pdata) {
345                 pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
346                 if (!pdata)
347                         return -ENOMEM;
348
349                 ret = mp3309c_parse_fwnode(chip, pdata);
350                 if (ret)
351                         return ret;
352         }
353         chip->pdata = pdata;
354
355         /* Backlight properties */
356         memset(&props, 0, sizeof(struct backlight_properties));
357         props.brightness = pdata->default_brightness;
358         props.max_brightness = pdata->max_brightness;
359         props.scale = BACKLIGHT_SCALE_LINEAR;
360         props.type = BACKLIGHT_RAW;
361         props.power = BACKLIGHT_POWER_ON;
362         chip->bl = devm_backlight_device_register(dev, "mp3309c", dev, chip,
363                                                   &mp3309c_bl_ops, &props);
364         if (IS_ERR(chip->bl))
365                 return dev_err_probe(dev, PTR_ERR(chip->bl),
366                                      "error registering backlight device\n");
367
368         /* In PWM dimming mode, enable pwm device */
369         if (chip->pdata->dimming_mode == DIMMING_PWM) {
370                 pwm_init_state(chip->pwmd, &pwmstate);
371                 pwm_set_relative_duty_cycle(&pwmstate,
372                                             chip->pdata->default_brightness,
373                                             chip->pdata->max_brightness);
374                 pwmstate.enabled = true;
375                 ret = pwm_apply_might_sleep(chip->pwmd, &pwmstate);
376                 if (ret)
377                         return dev_err_probe(dev, ret, "error setting pwm device\n");
378         }
379
380         chip->pdata->status = FIRST_POWER_ON;
381         backlight_update_status(chip->bl);
382
383         return 0;
384 }
385
386 static void mp3309c_remove(struct i2c_client *client)
387 {
388         struct mp3309c_chip *chip = i2c_get_clientdata(client);
389         struct backlight_device *bl = chip->bl;
390
391         bl->props.power = BACKLIGHT_POWER_OFF;
392         bl->props.brightness = 0;
393         backlight_update_status(chip->bl);
394 }
395
396 static const struct of_device_id mp3309c_match_table[] = {
397         { .compatible = "mps,mp3309c", },
398         { },
399 };
400 MODULE_DEVICE_TABLE(of, mp3309c_match_table);
401
402 static const struct i2c_device_id mp3309c_id[] = {
403         { "mp3309c" },
404         { }
405 };
406 MODULE_DEVICE_TABLE(i2c, mp3309c_id);
407
408 static struct i2c_driver mp3309c_i2c_driver = {
409         .driver = {
410                         .name           = KBUILD_MODNAME,
411                         .of_match_table = mp3309c_match_table,
412         },
413         .probe          = mp3309c_probe,
414         .remove         = mp3309c_remove,
415         .id_table       = mp3309c_id,
416 };
417
418 module_i2c_driver(mp3309c_i2c_driver);
419
420 MODULE_DESCRIPTION("Backlight Driver for MPS MP3309C");
421 MODULE_AUTHOR("Flavio Suligoi <[email protected]>");
422 MODULE_LICENSE("GPL");
This page took 0.050507 seconds and 4 git commands to generate.