On 29/08/2023 12:15, Flavio Suligoi wrote:
> The Monolithic Power (MPS) MP3309C is a WLED step-up converter, featuring a
> programmable switching frequency to optimize efficiency.
> The brightness can be controlled either by I2C commands (called "analog"
> mode) or by a PWM input signal (PWM mode).
> This driver supports both modes.
> 
> For DT configuration details, please refer to:
> - Documentation/devicetree/bindings/leds/backlight/mps,mp3309c.yaml
> 
> The datasheet is available at:
> - https://www.monolithicpower.com/en/mp3309c.html
> 
> Signed-off-by: Flavio Suligoi <f.suli...@asem.it>
> ---
>  MAINTAINERS                          |   6 +
>  drivers/video/backlight/Kconfig      |  13 +
>  drivers/video/backlight/Makefile     |   1 +
>  drivers/video/backlight/mp3309c_bl.c | 491 +++++++++++++++++++++++++++
>  4 files changed, 511 insertions(+)
>  create mode 100644 drivers/video/backlight/mp3309c_bl.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3be1bdfe8ecc..895c56ff4f1e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14333,6 +14333,12 @@ S:   Maintained
>  F:   Documentation/driver-api/tty/moxa-smartio.rst
>  F:   drivers/tty/mxser.*
>  
> +MP3309C BACKLIGHT DRIVER
> +M:   Flavio Suligoi <f.suli...@asem.it>
> +S:   Maintained
> +F:   Documentation/devicetree/bindings/leds/backlight/mps,mp3309c.yaml
> +F:   drivers/video/backlight/mp3309c_bl.c
> +
>  MR800 AVERMEDIA USB FM RADIO DRIVER
>  M:   Alexey Klimov <klimov.li...@gmail.com>
>  L:   linux-me...@vger.kernel.org
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index 51387b1ef012..65d0ac9f611d 100644
> --- a/drivers/video/backlight/Kconfig
> +++ b/drivers/video/backlight/Kconfig
> @@ -389,6 +389,19 @@ config BACKLIGHT_LM3639
>       help
>         This supports TI LM3639 Backlight + 1.5A Flash LED Driver
>  
> +config BACKLIGHT_MP3309C

Why is this placed between LM and LP entries? Keep things ordered.

> +     tristate "Backlight Driver for MPS MP3309C"
> +     depends on I2C
> +     select REGMAP_I2C
> +     select NEW_LEDS
> +     select LEDS_CLASS
> +     help
> +       This supports MPS MP3309C backlight WLED Driver in both PWM and
> +       analog/I2C dimming modes.
> +
> +       To compile this driver as a module, choose M here: the module will
> +       be called mp3309c_bl.
> +
>  config BACKLIGHT_LP855X


>       tristate "Backlight driver for TI LP855X"
>       depends on I2C && PWM
> diff --git a/drivers/video/backlight/Makefile 
> b/drivers/video/backlight/Makefile
> index f72e1c3c59e9..c42c5bccc5ac 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -44,6 +44,7 @@ obj-$(CONFIG_BACKLIGHT_LP855X)              += lp855x_bl.o
>  obj-$(CONFIG_BACKLIGHT_LP8788)               += lp8788_bl.o
>  obj-$(CONFIG_BACKLIGHT_LV5207LP)     += lv5207lp.o
>  obj-$(CONFIG_BACKLIGHT_MAX8925)              += max8925_bl.o
> +obj-$(CONFIG_BACKLIGHT_MP3309C)              += mp3309c_bl.o
>  obj-$(CONFIG_BACKLIGHT_MT6370)               += mt6370-backlight.o
>  obj-$(CONFIG_BACKLIGHT_OMAP1)                += omap1_bl.o
>  obj-$(CONFIG_BACKLIGHT_PANDORA)              += pandora_bl.o
> diff --git a/drivers/video/backlight/mp3309c_bl.c 
> b/drivers/video/backlight/mp3309c_bl.c
> new file mode 100644
> index 000000000000..7cb7a542ceca
> --- /dev/null
> +++ b/drivers/video/backlight/mp3309c_bl.c
> @@ -0,0 +1,491 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Driver for MPS MP3309C White LED driver with I2C interface
> + *
> + * Copyright (C) 2023 ASEM Srl
> + * Author: Flavio Suligoi <f.suli...@asem.it>
> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/pwm.h>
> +#include <linux/regmap.h>
> +#include <linux/workqueue.h>
> +
> +#define REG_I2C_0    0x00
> +#define REG_I2C_1    0x01
> +
> +#define REG_I2C_0_EN 0x80
> +#define REG_I2C_0_D0 0x40
> +#define REG_I2C_0_D1 0x20
> +#define REG_I2C_0_D2 0x10
> +#define REG_I2C_0_D3 0x08
> +#define REG_I2C_0_D4 0x04
> +#define REG_I2C_0_RSRV1      0x02
> +#define REG_I2C_0_RSRV2      0x01
> +
> +#define REG_I2C_1_RSRV1      0x80
> +#define REG_I2C_1_DIMS       0x40
> +#define REG_I2C_1_SYNC       0x20
> +#define REG_I2C_1_OVP0       0x10
> +#define REG_I2C_1_OVP1       0x08
> +#define REG_I2C_1_VOS        0x04
> +#define REG_I2C_1_LEDO       0x02
> +#define REG_I2C_1_OTP        0x01
> +
> +#define ANALOG_MAX_VAL       31
> +#define ANALOG_REG_MASK 0x7c
> +
> +enum backlight_status {
> +     FIRST_POWER_ON,
> +     BACKLIGHT_OFF,
> +     BACKLIGHT_ON,
> +};
> +
> +enum dimming_mode_value {
> +     DIMMING_PWM,
> +     DIMMING_ANALOG_I2C,
> +};
> +
> +struct mp3309c_platform_data {
> +     u32 max_brightness;
> +     u32 brightness;
> +     u32 switch_on_delay_ms;
> +     u32 switch_off_delay_ms;
> +     u32 reset_on_delay_ms;
> +     u32 reset_on_length_ms;
> +     u8  dimming_mode;
> +     u8  reset_pulse_enable;
> +     u8  over_voltage_protection;
> +
> +     unsigned int status;
> +};
> +
> +struct mp3309c_chip {
> +     struct device *dev;
> +     struct mp3309c_platform_data *pdata;
> +     struct backlight_device *bl;
> +     struct gpio_desc *enable_gpio;
> +     struct regmap *regmap;
> +     struct pwm_device *pwmd;
> +
> +     struct delayed_work enable_work;
> +     struct delayed_work reset_gpio_work;
> +     int irq;
> +
> +     struct gpio_desc *reset_gpio;
> +};
> +
> +static const struct regmap_config mp3309c_regmap = {
> +     .name = "mp3309c_regmap",
> +     .reg_bits = 8,
> +     .reg_stride = 1,
> +     .val_bits = 8,
> +     .max_register = REG_I2C_1,
> +};
> +
> +static int pm3309c_parse_dt_node(struct mp3309c_chip *chip,
> +                              struct mp3309c_platform_data *pdata)

This should be just before probe function. It is part of probe path.

> +{
> +     struct device_node *node = chip->dev->of_node;
> +     const char *tmp_string;
> +     int ret;
> +
> +     if (!node) {
> +             dev_err(chip->dev, "failed to get DT node\n");
> +             return -ENODEV;
> +     }
> +
> +     /*
> +      * Dimming mode: the MP3309C provides two dimming methods:
> +      *
> +      * - PWM mode
> +      * - Analog by I2C control mode
> +      */
> +     ret = of_property_read_string(node, "mps,dimming-mode", &tmp_string);
> +     if (ret < 0) {
> +             dev_err(chip->dev, "missed dimming-mode in DT\n");
> +             return ret;
> +     }
> +     if (!strcmp(tmp_string, "pwm")) {
> +             dev_info(chip->dev, "dimming method: PWM\n");

Drop

> +             pdata->dimming_mode = DIMMING_PWM;
> +     }
> +     if (!strcmp(tmp_string, "analog-i2c")) {
> +             dev_info(chip->dev, "dimming method: analog by I2C commands\n");

Drop


> +             pdata->dimming_mode = DIMMING_ANALOG_I2C;
> +     }
> +
> +     /* PWM steps (levels): 0 .. max_brightness */
> +     ret = of_property_read_u32(node, "max-brightness",
> +                                &pdata->max_brightness);
> +     if (ret < 0) {
> +             dev_err(chip->dev, "failed to get max-brightness from DT\n");
> +             return ret;
> +     }
> +
> +     /* Default brightness at startup */
> +     ret = of_property_read_u32(node, "default-brightness",
> +                                &pdata->brightness);
> +     if (ret < 0) {
> +             dev_err(chip->dev,
> +                     "failed to get default-brightness from DT\n");
> +             return ret;
> +     }
> +
> +     /*
> +      * Optional backlight switch-on/off delay
> +      *
> +      * Note: set 10ms as minimal value for switch-on delay, to stabilize
> +      *       video data
> +      */
> +     pdata->switch_on_delay_ms = 50;
> +     of_property_read_u32(node, "mps,switch-on-delay-ms",
> +                          &pdata->switch_on_delay_ms);
> +     if (pdata->switch_on_delay_ms < 10) {
> +             pdata->switch_on_delay_ms = 10;

You miss constraints in the bindings.

> +             dev_warn(chip->dev,
> +                      "switch-on-delay-ms set to 10ms as minimal value\n");
> +     }
> +     pdata->switch_off_delay_ms = 0;
> +     of_property_read_u32(node, "mps,switch-off-delay-ms",
> +                          &pdata->switch_off_delay_ms);
> +
> +     /*
> +      * Reset: GPIO, initial delay and pulse length
> +      *
> +      * Use this optional GPIO to reset an external device (LCD panel, video
> +      * FPGA, etc) when the backlight is switched on
> +      */
> +     pdata->reset_pulse_enable = 0;
> +     chip->reset_gpio = devm_gpiod_get_optional(chip->dev, "mps,reset",
> +                                                GPIOD_OUT_LOW);
> +     if (IS_ERR(chip->reset_gpio)) {
> +             ret = PTR_ERR(chip->reset_gpio);
> +             dev_err(chip->dev, "error acquiring reset gpio: %d\n", ret);
> +             return ret;

return dev_err_probe()

> +     }
> +     if (chip->reset_gpio) {
> +             pdata->reset_pulse_enable = 1;
> +
> +             pdata->reset_on_delay_ms = 10;
> +             of_property_read_u32(node, "mps,reset-on-delay-ms",
> +                                  &pdata->reset_on_delay_ms);
> +             pdata->reset_on_length_ms = 10;
> +             of_property_read_u32(node, "mps,reset-on-length-ms",
> +                                  &pdata->reset_on_length_ms);
> +     }
> +
> +     /*
> +      * Over-voltage protection (OVP)
> +      *
> +      * These (optional) properties are:
> +      *
> +      *  - overvoltage-protection-13v --> OVP point set to 13.5V
> +      *  - overvoltage-protection-24v --> OVP point set to 24V
> +      *  - overvoltage-protection-35v --> OVP point set to 35.5V
> +      *
> +      * If not chosen, the hw default value for OVP is 35.5V
> +      */
> +     pdata->over_voltage_protection = REG_I2C_1_OVP1;
> +     if (of_property_read_bool(node, "mps,overvoltage-protection-13v"))
> +             pdata->over_voltage_protection = 0x00;
> +     if (of_property_read_bool(node, "mps,overvoltage-protection-24v"))
> +             pdata->over_voltage_protection = REG_I2C_1_OVP0;
> +     if (of_property_read_bool(node, "mps,overvoltage-protection-35v"))
> +             pdata->over_voltage_protection = REG_I2C_1_OVP1;
> +
> +     return 0;
> +}
> +

...

> +
> +static const struct backlight_ops mp3309c_bl_ops = {
> +     .update_status = mp3309c_bl_update_status,
> +};
> +
> +static int mp3309c_probe(struct i2c_client *client)
> +{
> +     struct mp3309c_platform_data *pdata = dev_get_platdata(&client->dev);
> +     struct mp3309c_chip *chip;
> +     struct backlight_properties props;
> +     int ret;
> +
> +     if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> +             dev_err(&client->dev, "failed to check i2c functionality\n");
> +             return -EOPNOTSUPP;
> +     }
> +
> +     chip = devm_kzalloc(&client->dev, sizeof(struct mp3309c_chip),

sizeof(*)

> +                         GFP_KERNEL);
> +     if (!chip)
> +             return -ENOMEM;
> +     chip->dev = &client->dev;
> +
> +     chip->regmap = devm_regmap_init_i2c(client, &mp3309c_regmap);
> +     if (IS_ERR(chip->regmap)) {
> +             ret = PTR_ERR(chip->regmap);
> +             dev_err(&client->dev, "failed to allocate register map\n");

Are you sure regmap allocation should be followed by error message?
Allocations must not... so if this is not allocation, then syntax is:

return dev_err_probe()

> +             return ret;
> +     }
> +
> +     i2c_set_clientdata(client, chip);
> +
> +     if (!pdata) {
> +             pdata = devm_kzalloc(chip->dev,
> +                                  sizeof(struct mp3309c_platform_data),

sizeof(*)

> +                                  GFP_KERNEL);
> +             if (!pdata)
> +                     return -ENOMEM;
> +
> +             ret = pm3309c_parse_dt_node(chip, pdata);
> +             if (ret) {
> +                     dev_err(&client->dev, "failed parsing DT node\n");

Why do you print errors multiple times? parse_dt_node already does it.

> +                     return ret;
> +             }
> +     }
> +     chip->pdata = pdata;
> +
> +     chip->enable_gpio = devm_gpiod_get_optional(&client->dev, "enable",
> +                                                 GPIOD_OUT_HIGH);
> +     if (IS_ERR(chip->enable_gpio)) {
> +             ret = PTR_ERR(chip->enable_gpio);
> +             return ret;

return dev_err_probe()

> +     }
> +
> +     /* Backlight */
> +     props.type = BACKLIGHT_RAW;
> +     props.brightness = pdata->brightness;
> +     props.max_brightness = pdata->max_brightness;
> +     props.scale = BACKLIGHT_SCALE_LINEAR;
> +     chip->bl =
> +         devm_backlight_device_register(chip->dev, "mp3309c_bl",
> +                                        chip->dev, chip, &mp3309c_bl_ops,
> +                                        &props);
> +     if (IS_ERR(chip->bl)) {
> +             dev_err(&client->dev, "failed registering backlight\n");
> +             return PTR_ERR(chip->bl);

return dev_err_probe()

> +     }
> +     pdata->status = FIRST_POWER_ON;
> +
> +     /* Enable PWM, if required */
> +     if (pdata->dimming_mode == DIMMING_PWM) {
> +             chip->pwmd = devm_pwm_get(chip->dev, NULL);
> +             if (IS_ERR(chip->pwmd)) {
> +                     dev_err(&client->dev, "failed getting pwm device\n");
> +                     return PTR_ERR(chip->pwmd);

return dev_err_probe()

> +             }
> +             pwm_apply_args(chip->pwmd);
> +     }
> +
> +     /*
> +      * Workqueue for delayed backlight enabling
> +      */
> +     INIT_DELAYED_WORK(&chip->enable_work, mp3309c_enable);
> +
> +     /*
> +      * Workqueue for (optional) external device GPIO reset
> +      */
> +     if (pdata->reset_pulse_enable) {
> +             dev_info(&client->dev, "reset pulse enabled\n");

Drop, not really helpful.

> +             INIT_DELAYED_WORK(&chip->reset_gpio_work, mp3309c_reset_gpio);
> +     }
> +
> +     dev_info(&client->dev, "MP3309C backlight initialized");

Drop simple tracing messages.

> +     return 0;
> +}
> +
> +static int mp3309c_backlight_switch_off(struct pwm_device *pwmd)
> +{
> +     struct pwm_state pwmstate;
> +
> +     /* Switch-off the backlight */
> +     pwm_get_state(pwmd, &pwmstate);
> +     pwmstate.duty_cycle = 0;
> +     pwmstate.enabled = false;
> +     pwm_apply_state(pwmd, &pwmstate);
> +
> +     return 0;
> +}
> +
> +static void mp3309c_remove(struct i2c_client *client)
> +{
> +     struct mp3309c_chip *chip = i2c_get_clientdata(client);
> +
> +     if (chip->pdata->dimming_mode == DIMMING_PWM)
> +             mp3309c_backlight_switch_off(chip->pwmd);
> +     if (chip->pdata->reset_pulse_enable)
> +             cancel_delayed_work(&chip->reset_gpio_work);
> +}
> +
> +static void mp3309c_shutdown(struct i2c_client *client)
> +{
> +     struct mp3309c_chip *chip = i2c_get_clientdata(client);
> +
> +     if (chip->pdata->dimming_mode == DIMMING_PWM)
> +             mp3309c_backlight_switch_off(chip->pwmd);

Why do you need shutdown function?

> +}
> +
> +static const struct of_device_id mp3309c_match_table[] = {
> +     { .compatible = "mps,mp3309c-backlight", },
> +     { },
> +};
> +MODULE_DEVICE_TABLE(of, mp3309c_match_table);
> +


Best regards,
Krzysztof

Reply via email to