[PATCH v2 5/8] Add Advantech EIO Backlight driver

Ramiro Oliveira <[email protected]>
Newsgroups dev.linux.lists.mfd,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-fbdev,org.kernel.vger.linux-gpio,org.kernel.vger.linux-hwmon,org.kernel.vger.linux-i2c,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pm,org.kernel.vger.linux-watchdog
Message-ID <[email protected]>
This driver controls the Video Backlight block of the Advantech EIO chip.

Signed-off-by: Ramiro Oliveira <[email protected]>
---
 MAINTAINERS                      |   1 +
 drivers/video/backlight/Kconfig  |   6 +
 drivers/video/backlight/Makefile |   1 +
 drivers/video/backlight/eio_bl.c | 243 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 251 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index e228d12a2326..d2d7d815ed44 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -616,6 +616,7 @@ F:	drivers/gpio/gpio-eio.c
 F:	drivers/hwmon/eio-hwmon.c
 F:	drivers/i2c/busses/i2c-eio.c
 F:	drivers/mfd/eio_core.c
+F:	drivers/video/backlight/eio_bl.c
 F:	include/linux/mfd/eio.h
 
 ADVANTECH SWBTN DRIVER
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 7c66b8840d88..a5559a7db75d 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -522,6 +522,12 @@ config BACKLIGHT_RAVE_SP
 	help
 	  Support for backlight control on RAVE SP device.
 
+config BACKLIGHT_EIO
+	tristate "Advantech EIO Backlight"
+	depends on MFD_EIO && BACKLIGHT_CLASS_DEVICE
+	help
+	  Backlight driver for Advantech EIO.
+
 config BACKLIGHT_LED
 	tristate "Generic LED based Backlight Driver"
 	depends on LEDS_CLASS && OF
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 34469711c6cd..724246242526 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_BACKLIGHT_CGBC)		+= cgbc_bl.o
 obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE)	+= backlight.o
 obj-$(CONFIG_BACKLIGHT_DA903X)		+= da903x_bl.o
 obj-$(CONFIG_BACKLIGHT_DA9052)		+= da9052_bl.o
+obj-$(CONFIG_BACKLIGHT_EIO)		+= eio_bl.o
 obj-$(CONFIG_BACKLIGHT_EP93XX)		+= ep93xx_bl.o
 obj-$(CONFIG_BACKLIGHT_GPIO)		+= gpio_backlight.o
 obj-$(CONFIG_BACKLIGHT_HP680)		+= hp680_bl.o
diff --git a/drivers/video/backlight/eio_bl.c b/drivers/video/backlight/eio_bl.c
new file mode 100644
index 000000000000..c2826400de45
--- /dev/null
+++ b/drivers/video/backlight/eio_bl.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Backlight driver for Advantech EIO Embedded controller.
+ *
+ * Copyright (C) 2025 Advantech Corporation. All rights reserved.
+ */
+
+#include <linux/backlight.h>
+#include <linux/errno.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+
+#define PMC_BL_WRITE		0x20
+#define PMC_BL_READ		0x21
+
+#define BL_CTRL_STATUS		0x00
+#define BL_CTRL_ENABLE		0x12
+#define BL_CTRL_ENABLE_INVERT	0x13
+#define BL_CTRL_DUTY		0x14
+#define BL_CTRL_INVERT		0x15
+#define BL_CTRL_FREQ		0x16
+
+#define BL_MAX			2
+
+#define BL_STATUS_AVAIL		0x01
+#define BL_ENABLE_OFF		0x00
+#define BL_ENABLE_ON		0x01
+#define BL_ENABLE_AUTO		BIT(1)
+
+#define USE_DEFAULT		-1
+#define THERMAL_MAX		100
+
+static uint bri_freq = USE_DEFAULT;
+module_param(bri_freq, uint, 0444);
+MODULE_PARM_DESC(bri_freq, "Setup backlight PWM frequency.\n");
+
+static int bri_invert = USE_DEFAULT;
+module_param(bri_invert, int, 0444);
+MODULE_PARM_DESC(bri_invert, "Setup backlight PWM polarity.\n");
+
+static int bl_power_invert = USE_DEFAULT;
+module_param(bl_power_invert, int, 0444);
+MODULE_PARM_DESC(bl_power_invert, "Setup backlight enable pin polarity.\n");
+
+struct eio_bl_dev {
+	struct device *mfd;
+	u8 id;
+};
+
+static int pmc_write(struct device *mfd, u8 ctrl, u8 dev_id, void *data)
+{
+	struct pmc_op op = {
+		.cmd       = PMC_BL_WRITE,
+		.control   = ctrl,
+		.device_id = dev_id,
+		.payload   = (u8 *)data,
+		.size      = (ctrl == BL_CTRL_FREQ) ? 4 : 1,
+	};
+
+	return eio_core_pmc_operation(mfd, &op);
+}
+
+static int pmc_read(struct device *mfd, u8 ctrl, u8 dev_id, void *data)
+{
+	struct pmc_op op = {
+		.cmd       = PMC_BL_READ,
+		.control   = ctrl,
+		.device_id = dev_id,
+		.payload   = (u8 *)data,
+		.size      = (ctrl == BL_CTRL_FREQ) ? 4 : 1,
+	};
+
+	return eio_core_pmc_operation(mfd, &op);
+}
+
+static int bl_update_status(struct backlight_device *bl)
+{
+	struct eio_bl_dev *eio_bl = bl_get_data(bl);
+	u8 duty = clamp_val(backlight_get_brightness(bl), 0, THERMAL_MAX);
+	u8 sw = backlight_is_blank(bl);
+	int ret;
+
+	/* Setup PWM duty */
+	ret = pmc_write(eio_bl->mfd, BL_CTRL_DUTY, eio_bl->id, &duty);
+	if (ret)
+		return ret;
+
+	/* Setup backlight enable pin */
+	return pmc_write(eio_bl->mfd, BL_CTRL_ENABLE, eio_bl->id, &sw);
+}
+
+static int bl_get_brightness(struct backlight_device *bl)
+{
+	struct eio_bl_dev *eio_bl = bl_get_data(bl);
+	u8 duty = 0;
+	int ret;
+
+	ret = pmc_read(eio_bl->mfd, BL_CTRL_DUTY, eio_bl->id, &duty);
+
+	if (ret)
+		return ret;
+
+	return duty;
+}
+
+static const struct backlight_ops bl_ops = {
+	.get_brightness = bl_get_brightness,
+	.update_status	= bl_update_status,
+	.options	= BL_CORE_SUSPENDRESUME,
+};
+
+static int bl_init(struct device *dev, int id,
+		   struct backlight_properties *props)
+{
+	int ret;
+	u8 enabled = 0;
+	u8 status = 0;
+
+	/* Check EC-supported backlight */
+	ret = pmc_read(dev, BL_CTRL_STATUS, id, &status);
+	if (ret)
+		return ret;
+
+	if (!(status & BL_STATUS_AVAIL)) {
+		dev_dbg(dev, "eio_bl%d hardware report disabled.\n", id);
+		return -ENODEV;
+	}
+
+	ret = pmc_read(dev, BL_CTRL_DUTY, id, &props->brightness);
+	if (ret)
+		return ret;
+
+	/* Invert PWM */
+	if (bri_invert > USE_DEFAULT) {
+		ret = pmc_write(dev, BL_CTRL_INVERT, id, &bri_invert);
+		if (ret)
+			return ret;
+	}
+
+	ret = pmc_read(dev, BL_CTRL_INVERT, id, &bri_invert);
+	if (ret)
+		return ret;
+
+	if (bri_freq != USE_DEFAULT) {
+		ret = pmc_write(dev, BL_CTRL_FREQ, id, &bri_freq);
+		if (ret)
+			return ret;
+	}
+
+	ret = pmc_read(dev, BL_CTRL_FREQ, id, &bri_freq);
+	if (ret)
+		return ret;
+
+	if (bl_power_invert >= USE_DEFAULT) {
+		ret = pmc_write(dev, BL_CTRL_ENABLE_INVERT, id, &bl_power_invert);
+		if (ret)
+			return ret;
+	}
+
+	ret = pmc_read(dev, BL_CTRL_ENABLE_INVERT, id, &bl_power_invert);
+	if (ret)
+		return ret;
+
+	/* Read power state */
+	ret = pmc_read(dev, BL_CTRL_ENABLE, id, &enabled);
+	if (ret)
+		return ret;
+
+	props->power = enabled ? BACKLIGHT_POWER_OFF : BACKLIGHT_POWER_ON;
+
+	return 0;
+}
+
+static int bl_probe(struct platform_device *pdev)
+{
+	u8 id;
+	int ret;
+	struct device *dev = &pdev->dev;
+	struct eio_dev *eio_dev = dev_get_drvdata(dev->parent);
+
+	if (!eio_dev) {
+		dev_err(dev, "eio_core not present\n");
+		return -ENODEV;
+	}
+
+	for (id = 0; id < BL_MAX; id++) {
+		char name[32];
+		struct backlight_properties props;
+		struct eio_bl_dev *eio_bl;
+		struct backlight_device *bl;
+
+		memset(&props, 0, sizeof(props));
+		props.type           = BACKLIGHT_RAW;
+		props.max_brightness = THERMAL_MAX;
+		props.power          = BACKLIGHT_POWER_OFF;
+		props.brightness     = props.max_brightness;
+		props.scale	     = BACKLIGHT_SCALE_NON_LINEAR;
+
+		eio_bl = devm_kzalloc(dev, sizeof(*eio_bl), GFP_KERNEL);
+		if (!eio_bl)
+			return -ENOMEM;
+
+		eio_bl->mfd = dev->parent;
+		eio_bl->id  = id;
+
+		ret = bl_init(eio_bl->mfd, id, &props);
+		if (ret) {
+			dev_info(dev, "%d No Backlight %u enabled!\n", ret, id);
+			continue;
+		}
+
+		snprintf(name, sizeof(name), "%s%u", pdev->name, id);
+
+		bl = devm_backlight_device_register(dev, name, dev, eio_bl,
+						    &bl_ops, &props);
+
+		if (IS_ERR(bl)) {
+			ret = PTR_ERR(bl);
+			if (ret == -EPROBE_DEFER)
+				return ret;
+
+			dev_err(dev, "register %s failed: %d\n", name, ret);
+			continue;
+		}
+	}
+	return ret;
+}
+
+static struct platform_driver bl_driver = {
+	.probe  = bl_probe,
+	.driver = {
+		.name = "eio_bl",
+	},
+};
+
+module_platform_driver(bl_driver);
+
+MODULE_AUTHOR("Wenkai Chung <[email protected]>");
+MODULE_AUTHOR("Ramiro Oliveira <[email protected]>");
+MODULE_DESCRIPTION("Backlight driver for Advantech EIO embedded controller");
+MODULE_LICENSE("GPL");

-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.