All of lore.kernel.org
 help / color / mirror / Atom feed
From: Wim de With <wf@dewith.io>
To: Lee Jones <lee@kernel.org>, Daniel Thompson <danielt@kernel.org>,
	Jingoo Han <jingoohan1@gmail.com>,
	Pavel Machek <pavel@kernel.org>, Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>, Helge Deller <deller@gmx.de>
Cc: Wim de With <wf@dewith.io>,
	dri-devel@lists.freedesktop.org, linux-leds@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-fbdev@vger.kernel.org
Subject: [PATCH 2/2] backlight: Add support for Orient Chip OCP8178
Date: Thu,  6 Aug 2026 22:15:41 +0200	[thread overview]
Message-ID: <20260806201541.101304-3-wf@dewith.io> (raw)
In-Reply-To: <20260806201541.101304-1-wf@dewith.io>

Add a driver for the Orient Chip OCP8178 backlight controller.

The OCP8178 supports either PWM or a vendor-specific onewire protocol
over GPIO. This driver implements the single-wire protocol, which
enables setting static brightness levels without requiring a continuous
PWM signal from the host CPU.

Signed-off-by: Wim de With <wf@dewith.io>
---
 MAINTAINERS                          |   1 +
 drivers/video/backlight/Kconfig      |  10 ++
 drivers/video/backlight/Makefile     |   1 +
 drivers/video/backlight/ocp8178_bl.c | 243 +++++++++++++++++++++++++++
 4 files changed, 255 insertions(+)
 create mode 100644 drivers/video/backlight/ocp8178_bl.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 3ca99d9c32ef1..7146e4dea8d78 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19682,6 +19682,7 @@ OCP8178 BACKLIGHT DRIVER
 M:	Wim de With <wf@dewith.io>
 S:	Maintained
 F:	Documentation/devicetree/bindings/leds/backlight/ocs,ocp8178.yaml
+F:	drivers/video/backlight/ocp8178_bl.c
 
 OCXL (Open Coherent Accelerator Processor Interface OpenCAPI) DRIVER
 M:	Mahesh J Salgaonkar <mahesh@linux.ibm.com>
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 7aa1c4b21111f..aa845230c5f58 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -226,6 +226,16 @@ config BACKLIGHT_LOCOMO
 	  If you have a Sharp Zaurus SL-5500 (Collie) or SL-5600 (Poodle) say y to
 	  enable the LCD/backlight driver.
 
+config BACKLIGHT_OCP8178
+	tristate "OCP8178 Backlight Driver"
+	depends on GPIOLIB
+	help
+	  If you have an Orient Chip OCP8178, say Y to enable the backlight
+	  driver.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called ocp8178_bl.
+
 config BACKLIGHT_OMAP1
 	tristate "OMAP1 PWL-based LCD Backlight"
 	depends on ARCH_OMAP1 || COMPILE_TEST
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 21c8313cfb121..c4e87b708ae21 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_BACKLIGHT_MAX25014)	+= max25014.o
 obj-$(CONFIG_BACKLIGHT_MAX8925)		+= max8925_bl.o
 obj-$(CONFIG_BACKLIGHT_MP3309C)		+= mp3309c.o
 obj-$(CONFIG_BACKLIGHT_MT6370)		+= mt6370-backlight.o
+obj-$(CONFIG_BACKLIGHT_OCP8178)		+= ocp8178_bl.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)		+= omap1_bl.o
 obj-$(CONFIG_BACKLIGHT_PANDORA)		+= pandora_bl.o
 obj-$(CONFIG_BACKLIGHT_PWM)		+= pwm_bl.o
diff --git a/drivers/video/backlight/ocp8178_bl.c b/drivers/video/backlight/ocp8178_bl.c
new file mode 100644
index 0000000000000..4500c9d23f7ef
--- /dev/null
+++ b/drivers/video/backlight/ocp8178_bl.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Orient Chip OCP8178 Backlight Driver
+ *
+ * Copyright (C) 2026 Wim de With
+ *
+ * Author: Wim de With <wf@dewith.io>
+ */
+
+#include <linux/backlight.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/irqflags.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#define OCP8178_MAX_BRIGHTNESS 0x1F /* 5 bits */
+
+#define OCP8178_DEVICE_ADDRESS 0x72
+
+/*
+ * We cannot set RFA (request for acknowledge) unless the GPIO pin is
+ * configured as open drain.
+ */
+#define OCP8178_DATA_RFA BIT(7)
+#define OCP8178_DATA_ADDR GENMASK(6, 5)
+#define OCP8178_DATA_VALUE GENMASK(4, 0)
+
+#define OCP8178_1W_INIT_MAX_RETRIES 5
+#define OCP8178_1W_INIT_SLEEP_MS 50
+
+#define OCP8178_T_OFF_MS 3 /* datasheet specifies at least 2.5 ms */
+#define OCP8178_1W_T_DELAY_US (100 + 10) /* 10 us as safety factor */
+#define OCP8178_1W_T_DETECT_US (260 + 10) /* 10 us as safety factor */
+#define OCP8178_1W_T_START_US 2
+#define OCP8178_1W_T_EOS_US 2
+#define OCP8178_1W_T_WIN_NS (1000 * 1000)
+
+/*
+ * The datasheet specifies 1.7 Kbps to 160 Kbps.
+ * 1 / (160 Kbps) is about 6.67 us, so using 7 us per bit should be fine.
+ * T_HIGH + T_LOW = 7 us
+ * T_HIGH > 2 * T_LOW for high bits
+ * T_LOW > 2 * T_HIGH for low bits
+ */
+#define OCP8178_1W_HIGH_BIT_T_LOW_US 2
+#define OCP8178_1W_HIGH_BIT_T_HIGH_US 5
+#define OCP8178_1W_LOW_BIT_T_LOW_US 5
+#define OCP8178_1W_LOW_BIT_T_HIGH_US 2
+
+struct ocp8178_bl {
+	struct device *dev;
+	struct gpio_desc *gpiod;
+};
+
+static int ocp8178_bl_enable_onewire(struct ocp8178_bl *ocp8178)
+{
+	u64 start, duration;
+
+	dev_dbg(ocp8178->dev, "enabling onewire protocol\n");
+
+	gpiod_set_value_cansleep(ocp8178->gpiod, 0);
+	msleep(OCP8178_T_OFF_MS);
+
+	start = ktime_get_ns();
+
+	gpiod_set_value(ocp8178->gpiod, 1);
+	udelay(OCP8178_1W_T_DELAY_US);
+	gpiod_set_value(ocp8178->gpiod, 0);
+	udelay(OCP8178_1W_T_DETECT_US);
+	gpiod_set_value(ocp8178->gpiod, 1);
+
+	duration = ktime_get_ns() - start;
+
+	if (duration >= OCP8178_1W_T_WIN_NS) {
+		dev_err(ocp8178->dev,
+			"onewire detection window exceeded (%llu ns)\n",
+			duration);
+		return -EAGAIN;
+	}
+
+	return 0;
+}
+
+static void ocp8178_bl_write_u8(struct ocp8178_bl *ocp8178, u8 value)
+{
+	unsigned long flags;
+
+	gpiod_set_value(ocp8178->gpiod, 1);
+	udelay(OCP8178_1W_T_START_US);
+
+	local_irq_save(flags);
+
+	for (int i = 7; i >= 0; i--) {
+		if ((value >> i) & 1) {
+			gpiod_set_value(ocp8178->gpiod, 0);
+			udelay(OCP8178_1W_HIGH_BIT_T_LOW_US);
+			gpiod_set_value(ocp8178->gpiod, 1);
+			udelay(OCP8178_1W_HIGH_BIT_T_HIGH_US);
+		} else {
+			gpiod_set_value(ocp8178->gpiod, 0);
+			udelay(OCP8178_1W_LOW_BIT_T_LOW_US);
+			gpiod_set_value(ocp8178->gpiod, 1);
+			udelay(OCP8178_1W_LOW_BIT_T_HIGH_US);
+		}
+	}
+
+	gpiod_set_value(ocp8178->gpiod, 0);
+
+	local_irq_restore(flags);
+
+	udelay(OCP8178_1W_T_EOS_US);
+	gpiod_set_value(ocp8178->gpiod, 1);
+}
+
+static void ocp8178_bl_set_brightness(struct ocp8178_bl *ocp8178, u8 brightness)
+{
+	u8 data = 0;
+
+	dev_dbg(ocp8178->dev, "setting brightness to %u\n", brightness);
+
+	data |= FIELD_PREP(OCP8178_DATA_ADDR, 0);
+	data |= FIELD_PREP(OCP8178_DATA_VALUE, brightness);
+
+	ocp8178_bl_write_u8(ocp8178, OCP8178_DEVICE_ADDRESS);
+	ocp8178_bl_write_u8(ocp8178, data);
+}
+
+static int ocp8178_bl_update_status(struct backlight_device *bl)
+{
+	struct ocp8178_bl *ocp8178 = bl_get_data(bl);
+	u8 brightness = backlight_get_brightness(bl);
+
+	/*
+	 * Setting brightness to 0 turns the backlight off but retains the
+	 * onewire mode. If we disable the controller, we would need to enable
+	 * the onewire mode again.
+	 */
+	if (backlight_is_blank(bl))
+		brightness = 0;
+
+	ocp8178_bl_set_brightness(ocp8178, brightness);
+	return 0;
+}
+
+static const struct backlight_ops ocp8178_bl_ops = {
+	.options	= BL_CORE_SUSPENDRESUME,
+	.update_status	= ocp8178_bl_update_status,
+};
+
+static int ocp8178_bl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct backlight_device *bl;
+	struct backlight_properties props;
+	struct ocp8178_bl *ocp8178;
+	u32 max_brightness, brightness;
+	int ret, retries;
+
+	ocp8178 = devm_kzalloc(dev, sizeof(*ocp8178), GFP_KERNEL);
+	if (!ocp8178)
+		return -ENOMEM;
+
+	ocp8178->dev = dev;
+
+	ret = device_property_read_u32(dev, "max-brightness", &max_brightness);
+	if (ret)
+		max_brightness = OCP8178_MAX_BRIGHTNESS;
+	if (max_brightness > OCP8178_MAX_BRIGHTNESS) {
+		dev_warn(dev, "max brightness exceeds hardware limit\n");
+		max_brightness = OCP8178_MAX_BRIGHTNESS;
+	}
+
+	ret = device_property_read_u32(dev, "default-brightness", &brightness);
+	if (ret)
+		brightness = max_brightness;
+	if (brightness > max_brightness) {
+		dev_warn(dev, "default brightness exceeds max brightness\n");
+		brightness = max_brightness;
+	}
+
+	ocp8178->gpiod = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+	if (IS_ERR(ocp8178->gpiod))
+		return dev_err_probe(dev, PTR_ERR(ocp8178->gpiod),
+				     "gpio missing or invalid\n");
+	gpiod_set_consumer_name(ocp8178->gpiod, dev_name(dev));
+
+	for (retries = 0; retries < OCP8178_1W_INIT_MAX_RETRIES; retries++) {
+		ret = ocp8178_bl_enable_onewire(ocp8178);
+		if (!ret)
+			break;
+		if (ret != -EAGAIN)
+			return ret;
+		msleep(OCP8178_1W_INIT_SLEEP_MS);
+	}
+	if (retries >= OCP8178_1W_INIT_MAX_RETRIES)
+		return dev_err_probe(dev, -ETIMEDOUT,
+				     "failed to initialize onewire protocol");
+
+	props = (typeof(props)){
+		.type		= BACKLIGHT_RAW,
+		.brightness	= brightness,
+		.max_brightness = max_brightness,
+		.power		= BACKLIGHT_POWER_ON,
+		.scale		= BACKLIGHT_SCALE_NON_LINEAR,
+	};
+
+	bl = devm_backlight_device_register(dev, dev_name(dev), dev, ocp8178,
+					    &ocp8178_bl_ops, &props);
+	if (IS_ERR(bl))
+		return dev_err_probe(dev, PTR_ERR(bl),
+				     "failed to register backlight\n");
+
+	platform_set_drvdata(pdev, bl);
+	backlight_update_status(bl);
+
+	dev_info(dev, "probed, brightness=%u/%u\n", brightness, max_brightness);
+
+	return 0;
+}
+
+static const struct of_device_id ocp8178_bl_of_match[] = {
+	{ .compatible = "ocs,ocp8178" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ocp8178_bl_of_match);
+
+static struct platform_driver ocp8178_bl_driver = {
+	.driver		= {
+		.name		= "ocp8178-bl",
+		.of_match_table	= ocp8178_bl_of_match,
+	},
+	.probe		= ocp8178_bl_probe,
+};
+module_platform_driver(ocp8178_bl_driver);
+
+MODULE_AUTHOR("Wim de With <wf@dewith.io>");
+MODULE_DESCRIPTION("Orient Chip OCP8178 Backlight Driver");
+MODULE_LICENSE("GPL");
-- 
2.55.0


  parent reply	other threads:[~2026-08-06 20:26 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-06 20:15 [PATCH 0/2] backlight: Add support for Orient Chip OCP8178 Wim de With
2026-08-06 20:15 ` [PATCH 1/2] dt-bindings: backlight: Add " Wim de With
2026-08-06 20:33   ` sashiko-bot
2026-08-06 20:15 ` Wim de With [this message]
2026-08-06 20:36   ` [PATCH 2/2] backlight: Add support for " sashiko-bot
2026-08-08 10:28     ` Wim de With
2026-08-07  6:46   ` Uwe Kleine-König
2026-08-08 10:36     ` Wim de With
2026-08-10 11:33   ` Daniel Thompson
2026-08-10 17:10     ` Wim de With

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260806201541.101304-3-wf@dewith.io \
    --to=wf@dewith.io \
    --cc=conor+dt@kernel.org \
    --cc=danielt@kernel.org \
    --cc=deller@gmx.de \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=jingoohan1@gmail.com \
    --cc=krzk+dt@kernel.org \
    --cc=lee@kernel.org \
    --cc=linux-fbdev@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=pavel@kernel.org \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.