Re: [PATCH v1 2/2] backlight: mp3309c: Add support for MPS MP3309C
From: Krzysztof Kozlowski <hidden>
Date: 2023-08-29 10:55:01
Also in:
dri-devel, linux-devicetree, linux-leds, lkml
On 29/08/2023 12:15, Flavio Suligoi wrote:
quoted hunk ↗ jump to hunk
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> --- 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.cdiff --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.suligoi@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.linux@gmail.com> L: linux-media@vger.kernel.orgdiff --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
quoted hunk ↗ jump to hunk
tristate "Backlight driver for TI LP855X" depends on I2C && PWMdiff --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.odiff --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.suligoi@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