lkml.org 
[lkml]   [2023]   [Oct]   [23]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    /
    From
    SubjectRE: [PATCH v5] backlight: mp3309c: Add support for MPS MP3309C
    Date
    Hi Conor,

    ...

    > Subject: Re: [PATCH v5] backlight: mp3309c: Add support for MPS MP3309C
    >
    > On Fri, Oct 20, 2023 at 03:54:34PM +0200, 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.suligoi@asem.it>
    > > ---
    > >
    > > v5:
    > > - checked and resend for updated kernel 6.6.0-rc1
    >
    > Why is this v5 patch attached to a 1 patch "series" that only purports to
    > contain a binding patch?

    Sorry Conor, what do you mean for "binding patch"? The cover letter of this patch or the
    other patch about the dt-bindings for the mp3309c backlight ?

    > Thanks,
    > Conor.

    Thanks,
    Flavio.


    >
    > > v4:
    > > - add brightness-levels property
    > > - force fixed 32 brightness levels (0..31) in analog-i2c dimming mode
    > > - remove useless irq and reset_gpio from mp3309c_chip structure
    > > v3:
    > > - fix SPDX obsolete spelling
    > > - in mp3309c_bl_update_status, change from msleep_interruptible() to
    > msleep()
    > > and improve the related comment
    > > v2:
    > > - fix dependecies in Kconfig
    > > - fix Kconfig MP3309C entry order
    > > - remove switch-on-delay-ms property
    > > - remove optional gpio property to reset external devices
    > > - remove dimming-mode property (the analog-i2c dimming mode is the
    > default; the
    > > presence of the pwms property, in DT, selects automatically the pwm
    > dimming
    > > mode)
    > > - substitute three boolean properties, used for the overvoltage-protection
    > > values, with a single enum property
    > > - drop simple tracing messages
    > > - use dev_err_probe() in probe function
    > > - change device name from mp3309c_bl to the simple mp3309c
    > > - remove shutdown function
    > > v1:
    > > - first version
    > >
    > > MAINTAINERS | 7 +
    > > drivers/video/backlight/Kconfig | 11 +
    > > drivers/video/backlight/Makefile | 1 +
    > > drivers/video/backlight/mp3309c.c | 443
    > > ++++++++++++++++++++++++++++++
    > > 4 files changed, 462 insertions(+)
    > > create mode 100644 drivers/video/backlight/mp3309c.c
    > >
    > > diff --git a/MAINTAINERS b/MAINTAINERS index
    > > 90f13281d297..3d23b869e2aa 100644
    > > --- a/MAINTAINERS
    > > +++ b/MAINTAINERS
    > > @@ -14474,6 +14474,13 @@ S: Maintained
    > > F: Documentation/driver-api/tty/moxa-smartio.rst
    > > F: drivers/tty/mxser.*
    > >
    > > +MP3309C BACKLIGHT DRIVER
    > > +M: Flavio Suligoi <f.suligoi@asem.it>
    > > +L: dri-devel@lists.freedesktop.org
    > > +S: Maintained
    > > +F:
    > Documentation/devicetree/bindings/leds/backlight/mps,mp3309c.ya
    > ml
    > > +F: drivers/video/backlight/mp3309c.c
    > > +
    > > MR800 AVERMEDIA USB FM RADIO DRIVER
    > > M: Alexey Klimov <klimov.linux@gmail.com>
    > > L: linux-media@vger.kernel.org
    > > diff --git a/drivers/video/backlight/Kconfig
    > > b/drivers/video/backlight/Kconfig index 51387b1ef012..1144a54a35c0
    > > 100644
    > > --- a/drivers/video/backlight/Kconfig
    > > +++ b/drivers/video/backlight/Kconfig
    > > @@ -402,6 +402,17 @@ config BACKLIGHT_LP8788
    > > help
    > > This supports TI LP8788 backlight driver.
    > >
    > > +config BACKLIGHT_MP3309C
    > > + tristate "Backlight Driver for MPS MP3309C"
    > > + depends on I2C && PWM
    > > + select REGMAP_I2C
    > > + 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.
    > > +
    > > config BACKLIGHT_PANDORA
    > > tristate "Backlight driver for Pandora console"
    > > depends on TWL4030_CORE
    > > diff --git a/drivers/video/backlight/Makefile
    > > b/drivers/video/backlight/Makefile
    > > index f72e1c3c59e9..1af583de665b 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.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.c
    > > b/drivers/video/backlight/mp3309c.c
    > > new file mode 100644
    > > index 000000000000..3fe4469ef43f
    > > --- /dev/null
    > > +++ b/drivers/video/backlight/mp3309c.c
    > > @@ -0,0 +1,443 @@
    > > +// SPDX-License-Identifier: GPL-2.0-or-later
    > > +/*
    > > + * Driver for MPS MP3309C White LED driver with I2C interface
    > > + *
    > > + * This driver support both analog (by I2C commands) and PWM dimming
    > > +control
    > > + * modes.
    > > + *
    > > + * Copyright (C) 2023 ASEM Srl
    > > + * Author: Flavio Suligoi <f.suligoi@asem.it>
    > > + *
    > > + * Based on pwm_bl.c
    > > + */
    > > +
    > > +#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>
    > > +
    > > +#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_I2C_NUM_LEVELS 32 /* 0..31 */
    > > +#define ANALOG_I2C_REG_MASK 0x7c
    > > +
    > > +#define MP3309C_PWM_DEFAULT_NUM_LEVELS 256 /* 0..255 */
    > > +
    > > +enum mp3309c_status_value {
    > > + FIRST_POWER_ON,
    > > + BACKLIGHT_OFF,
    > > + BACKLIGHT_ON,
    > > +};
    > > +
    > > +enum mp3309c_dimming_mode_value {
    > > + DIMMING_PWM,
    > > + DIMMING_ANALOG_I2C,
    > > +};
    > > +
    > > +struct mp3309c_platform_data {
    > > + unsigned int max_brightness;
    > > + unsigned int default_brightness;
    > > + unsigned int *levels;
    > > + u8 dimming_mode;
    > > + u8 over_voltage_protection;
    > > + bool sync_mode;
    > > + u8 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;
    > > +};
    > > +
    > > +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 mp3309c_enable_device(struct mp3309c_chip *chip) {
    > > + u8 reg_val;
    > > + int ret;
    > > +
    > > + /* I2C register #0 - Device enable */
    > > + ret = regmap_update_bits(chip->regmap, REG_I2C_0, REG_I2C_0_EN,
    > > + REG_I2C_0_EN);
    > > + if (ret)
    > > + return ret;
    > > +
    > > + /*
    > > + * I2C register #1 - Set working mode:
    > > + * - set one of the two dimming mode:
    > > + * - PWM dimming using an external PWM dimming signal
    > > + * - analog dimming using I2C commands
    > > + * - enable/disable synchronous mode
    > > + * - set overvoltage protection (OVP)
    > > + */
    > > + reg_val = 0x00;
    > > + if (chip->pdata->dimming_mode == DIMMING_PWM)
    > > + reg_val |= REG_I2C_1_DIMS;
    > > + if (chip->pdata->sync_mode)
    > > + reg_val |= REG_I2C_1_SYNC;
    > > + reg_val |= chip->pdata->over_voltage_protection;
    > > + ret = regmap_write(chip->regmap, REG_I2C_1, reg_val);
    > > + if (ret)
    > > + return ret;
    > > +
    > > + return 0;
    > > +}
    > > +
    > > +static int mp3309c_bl_update_status(struct backlight_device *bl) {
    > > + struct mp3309c_chip *chip = bl_get_data(bl);
    > > + int brightness = backlight_get_brightness(bl);
    > > + struct pwm_state pwmstate;
    > > + unsigned int analog_val, bits_val;
    > > + int i, ret;
    > > +
    > > + if (chip->pdata->dimming_mode == DIMMING_PWM) {
    > > + /*
    > > + * PWM control mode
    > > + */
    > > + pwm_get_state(chip->pwmd, &pwmstate);
    > > + pwm_set_relative_duty_cycle(&pwmstate,
    > > + chip->pdata->levels[brightness],
    > > + chip->pdata->levels[chip->pdata-
    > >max_brightness]);
    > > + pwmstate.enabled = true;
    > > + ret = pwm_apply_state(chip->pwmd, &pwmstate);
    > > + if (ret)
    > > + return ret;
    > > +
    > > + switch (chip->pdata->status) {
    > > + case FIRST_POWER_ON:
    > > + case BACKLIGHT_OFF:
    > > + /*
    > > + * After 20ms of low pwm signal level, the chip turns
    > > + * off automatically. In this case, before enabling the
    > > + * chip again, we must wait about 10ms for pwm
    > signal to
    > > + * stabilize.
    > > + */
    > > + if (brightness > 0) {
    > > + msleep(10);
    > > + mp3309c_enable_device(chip);
    > > + chip->pdata->status = BACKLIGHT_ON;
    > > + } else {
    > > + chip->pdata->status = BACKLIGHT_OFF;
    > > + }
    > > + break;
    > > + case BACKLIGHT_ON:
    > > + if (brightness == 0)
    > > + chip->pdata->status = BACKLIGHT_OFF;
    > > + break;
    > > + }
    > > + } else {
    > > + /*
    > > + * Analog (by I2C command) control mode
    > > + *
    > > + * The first time, before setting brightness, we must enable the
    > > + * device
    > > + */
    > > + if (chip->pdata->status == FIRST_POWER_ON)
    > > + mp3309c_enable_device(chip);
    > > +
    > > + /*
    > > + * Dimming mode I2C command (fixed dimming range 0..31)
    > > + *
    > > + * The 5 bits of the dimming analog value D4..D0 is allocated
    > > + * in the I2C register #0, in the following way:
    > > + *
    > > + * +--+--+--+--+--+--+--+--+
    > > + * |EN|D0|D1|D2|D3|D4|XX|XX|
    > > + * +--+--+--+--+--+--+--+--+
    > > + */
    > > + analog_val = brightness;
    > > + bits_val = 0;
    > > + for (i = 0; i <= 5; i++)
    > > + bits_val += ((analog_val >> i) & 0x01) << (6 - i);
    > > + ret = regmap_update_bits(chip->regmap, REG_I2C_0,
    > > + ANALOG_I2C_REG_MASK, bits_val);
    > > + if (ret)
    > > + return ret;
    > > +
    > > + if (brightness > 0)
    > > + chip->pdata->status = BACKLIGHT_ON;
    > > + else
    > > + chip->pdata->status = BACKLIGHT_OFF;
    > > + }
    > > +
    > > + return 0;
    > > +}
    > > +
    > > +static const struct backlight_ops mp3309c_bl_ops = {
    > > + .update_status = mp3309c_bl_update_status, };
    > > +
    > > +static int pm3309c_parse_dt_node(struct mp3309c_chip *chip,
    > > + struct mp3309c_platform_data *pdata) {
    > > + struct device_node *node = chip->dev->of_node;
    > > + struct property *prop_pwms, *prop_levels;
    > > + int length = 0;
    > > + int ret, i;
    > > + unsigned int num_levels, tmp_value;
    > > +
    > > + if (!node) {
    > > + dev_err(chip->dev, "failed to get DT node\n");
    > > + return -ENODEV;
    > > + }
    > > +
    > > + /*
    > > + * Dimming mode: the MP3309C provides two dimming control mode:
    > > + *
    > > + * - PWM mode
    > > + * - Analog by I2C control mode (default)
    > > + *
    > > + * I2C control mode is assumed as default but, if the pwms property is
    > > + * found in the backlight node, the mode switches to PWM mode.
    > > + */
    > > + pdata->dimming_mode = DIMMING_ANALOG_I2C;
    > > + prop_pwms = of_find_property(node, "pwms", &length);
    > > + if (prop_pwms) {
    > > + chip->pwmd = devm_pwm_get(chip->dev, NULL);
    > > + if (IS_ERR(chip->pwmd))
    > > + return dev_err_probe(chip->dev, PTR_ERR(chip-
    > >pwmd),
    > > + "error getting pwm data\n");
    > > + pdata->dimming_mode = DIMMING_PWM;
    > > + pwm_apply_args(chip->pwmd);
    > > + }
    > > +
    > > + /*
    > > + * In I2C control mode the dimming levels (0..31) are fixed by the
    > > + * hardware, while in PWM control mode they can be chosen by the
    > user,
    > > + * to allow nonlinear mappings.
    > > + */
    > > + if (pdata->dimming_mode == DIMMING_ANALOG_I2C) {
    > > + /*
    > > + * Analog (by I2C commands) control mode: fixed 0..31
    > brightness
    > > + * levels
    > > + */
    > > + num_levels = ANALOG_I2C_NUM_LEVELS;
    > > +
    > > + /* Enable GPIO used in I2C dimming mode only */
    > > + chip->enable_gpio = devm_gpiod_get(chip->dev, "enable",
    > > + GPIOD_OUT_HIGH);
    > > + if (IS_ERR(chip->enable_gpio))
    > > + return dev_err_probe(chip->dev,
    > > + PTR_ERR(chip->enable_gpio),
    > > + "error getting enable gpio\n");
    > > + } else {
    > > + /*
    > > + * PWM control mode: check for brightness level in DT
    > > + */
    > > + prop_levels = of_find_property(node, "brightness-levels",
    > > + &length);
    > > + if (prop_levels) {
    > > + /* Read brightness levels from DT */
    > > + num_levels = length / sizeof(u32);
    > > + if (num_levels < 2)
    > > + return -EINVAL;
    > > + } else {
    > > + /* Use default brightness levels */
    > > + num_levels =
    > MP3309C_PWM_DEFAULT_NUM_LEVELS;
    > > + }
    > > + }
    > > +
    > > + /* Fill brightness levels array */
    > > + pdata->levels = devm_kcalloc(chip->dev, num_levels,
    > > + sizeof(*pdata->levels), GFP_KERNEL);
    > > + if (!pdata->levels)
    > > + return -ENOMEM;
    > > + if (prop_levels) {
    > > + ret = of_property_read_u32_array(node, "brightness-levels",
    > > + pdata->levels,
    > > + num_levels);
    > > + if (ret < 0)
    > > + return ret;
    > > + } else {
    > > + for (i = 0; i < num_levels; i++)
    > > + pdata->levels[i] = i;
    > > + }
    > > +
    > > + pdata->max_brightness = num_levels - 1;
    > > +
    > > + ret = of_property_read_u32(node, "default-brightness",
    > > + &pdata->default_brightness);
    > > + if (ret)
    > > + pdata->default_brightness = pdata->max_brightness;
    > > + if (pdata->default_brightness > pdata->max_brightness) {
    > > + dev_err(chip->dev,
    > > + "default brightness exceeds max brightness\n");
    > > + pdata->default_brightness = pdata->max_brightness;
    > > + }
    > > +
    > > + /*
    > > + * Over-voltage protection (OVP)
    > > + *
    > > + * This (optional) property values are:
    > > + *
    > > + * - 13.5V
    > > + * - 24V
    > > + * - 35.5V (hardware default setting)
    > > + *
    > > + * If missing, the default value for OVP is 35.5V
    > > + */
    > > + pdata->over_voltage_protection = REG_I2C_1_OVP1;
    > > + if (!of_property_read_u32(node, "mps,overvoltage-protection-
    > microvolt",
    > > + &tmp_value)) {
    > > + switch (tmp_value) {
    > > + case 13500000:
    > > + pdata->over_voltage_protection = 0x00;
    > > + break;
    > > + case 24000000:
    > > + pdata->over_voltage_protection = REG_I2C_1_OVP0;
    > > + break;
    > > + case 35500000:
    > > + pdata->over_voltage_protection = REG_I2C_1_OVP1;
    > > + break;
    > > + default:
    > > + return -EINVAL;
    > > + }
    > > + }
    > > +
    > > + /* Synchronous (default) and non-synchronous mode */
    > > + pdata->sync_mode = true;
    > > + if (of_property_read_bool(node, "mps,no-sync-mode"))
    > > + pdata->sync_mode = false;
    > > +
    > > + return 0;
    > > +}
    > > +
    > > +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;
    > > + struct pwm_state pwmstate;
    > > + 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(*chip), GFP_KERNEL);
    > > + if (!chip)
    > > + return -ENOMEM;
    > > +
    > > + chip->dev = &client->dev;
    > > +
    > > + chip->regmap = devm_regmap_init_i2c(client, &mp3309c_regmap);
    > > + if (IS_ERR(chip->regmap))
    > > + return dev_err_probe(&client->dev, PTR_ERR(chip->regmap),
    > > + "failed to allocate register map\n");
    > > +
    > > + i2c_set_clientdata(client, chip);
    > > +
    > > + if (!pdata) {
    > > + pdata = devm_kzalloc(chip->dev, sizeof(*pdata),
    > GFP_KERNEL);
    > > + if (!pdata)
    > > + return -ENOMEM;
    > > +
    > > + ret = pm3309c_parse_dt_node(chip, pdata);
    > > + if (ret)
    > > + return ret;
    > > + }
    > > + chip->pdata = pdata;
    > > +
    > > + /* Backlight properties */
    > > + props.brightness = pdata->default_brightness;
    > > + props.max_brightness = pdata->max_brightness;
    > > + props.scale = BACKLIGHT_SCALE_LINEAR;
    > > + props.type = BACKLIGHT_RAW;
    > > + props.power = FB_BLANK_UNBLANK;
    > > + props.fb_blank = FB_BLANK_UNBLANK;
    > > + chip->bl = devm_backlight_device_register(chip->dev, "mp3309c",
    > > + chip->dev, chip,
    > > + &mp3309c_bl_ops, &props);
    > > + if (IS_ERR(chip->bl))
    > > + return dev_err_probe(chip->dev, PTR_ERR(chip->bl),
    > > + "error registering backlight device\n");
    > > +
    > > + /* In PWM dimming mode, enable pwm device */
    > > + if (chip->pdata->dimming_mode == DIMMING_PWM) {
    > > + pwm_init_state(chip->pwmd, &pwmstate);
    > > + pwm_set_relative_duty_cycle(&pwmstate,
    > > + chip->pdata->default_brightness,
    > > + chip->pdata->max_brightness);
    > > + pwmstate.enabled = true;
    > > + ret = pwm_apply_state(chip->pwmd, &pwmstate);
    > > + if (ret)
    > > + return dev_err_probe(chip->dev, ret,
    > > + "error setting pwm device\n");
    > > + }
    > > +
    > > + chip->pdata->status = FIRST_POWER_ON;
    > > + backlight_update_status(chip->bl);
    > > +
    > > + return 0;
    > > +}
    > > +
    > > +static void mp3309c_remove(struct i2c_client *client) {
    > > + struct mp3309c_chip *chip = i2c_get_clientdata(client);
    > > + struct backlight_device *bl = chip->bl;
    > > +
    > > + bl->props.power = FB_BLANK_POWERDOWN;
    > > + bl->props.brightness = 0;
    > > + backlight_update_status(chip->bl);
    > > +}
    > > +
    > > +static const struct of_device_id mp3309c_match_table[] = {
    > > + { .compatible = "mps,mp3309c", },
    > > + { },
    > > +};
    > > +MODULE_DEVICE_TABLE(of, mp3309c_match_table);
    > > +
    > > +static const struct i2c_device_id mp3309c_id[] = {
    > > + { "mp3309c", 0 },
    > > + { }
    > > +};
    > > +MODULE_DEVICE_TABLE(i2c, mp3309c_id);
    > > +
    > > +static struct i2c_driver mp3309c_i2c_driver = {
    > > + .driver = {
    > > + .name = KBUILD_MODNAME,
    > > + .of_match_table = mp3309c_match_table,
    > > + },
    > > + .probe = mp3309c_probe,
    > > + .remove = mp3309c_remove,
    > > + .id_table = mp3309c_id,
    > > +};
    > > +
    > > +module_i2c_driver(mp3309c_i2c_driver);
    > > +
    > > +MODULE_DESCRIPTION("Backlight Driver for MPS MP3309C");
    > > +MODULE_AUTHOR("Flavio Suligoi <f.suligoi@asem.it>");
    > > +MODULE_LICENSE("GPL");
    > > --
    > > 2.34.1
    > >

    \
     
     \ /
      Last update: 2023-10-23 11:11    [W:4.577 / U:0.012 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site