Skip to content

Commit b516456

Browse files
petri-klag-linaro
authored andcommitted
backlight: Add Congatec Board Controller (CGBC) backlight support
This driver provides backlight brightness control through the Linux backlight subsystem. It communicates with the board controller to adjust LCD backlight using PWM signals. Communication is done through Congatec Board Controller core driver. Tested-by: Thomas Richard <thomas.richard@bootlin.com> Reviewed-by: Thomas Richard <thomas.richard@bootlin.com> Reviewed-by: Daniel Thompson (RISCstar) <danielt@kernel.org> Signed-off-by: Petri Karhula <petri.karhula@novatron.fi> Link: https://patch.msgid.link/20251205-cgbc-backlight-v6-1-e4175b0bf406@novatron.fi Signed-off-by: Lee Jones <lee@kernel.org>
1 parent 8f0b4cc commit b516456

3 files changed

Lines changed: 192 additions & 0 deletions

File tree

drivers/video/backlight/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,17 @@ config BACKLIGHT_PWM
258258
If you have a LCD backlight adjustable by PWM, say Y to enable
259259
this driver.
260260

261+
config BACKLIGHT_CGBC
262+
tristate "Congatec Board Controller (CGBC) backlight support"
263+
depends on MFD_CGBC && X86
264+
help
265+
Say Y here to enable support for LCD backlight control on Congatec
266+
x86-based boards via the CGBC (Congatec Board Controller).
267+
268+
This driver provides backlight brightness control through the Linux
269+
backlight subsystem. It communicates with the board controller to
270+
adjust LCD backlight using PWM signals.
271+
261272
config BACKLIGHT_DA903X
262273
tristate "Backlight Driver for DA9030/DA9034 using WLED"
263274
depends on PMIC_DA903X

drivers/video/backlight/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ obj-$(CONFIG_BACKLIGHT_APPLE_DWI) += apple_dwi_bl.o
2727
obj-$(CONFIG_BACKLIGHT_AS3711) += as3711_bl.o
2828
obj-$(CONFIG_BACKLIGHT_AW99706) += aw99706.o
2929
obj-$(CONFIG_BACKLIGHT_BD6107) += bd6107.o
30+
obj-$(CONFIG_BACKLIGHT_CGBC) += cgbc_bl.o
3031
obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o
3132
obj-$(CONFIG_BACKLIGHT_DA903X) += da903x_bl.o
3233
obj-$(CONFIG_BACKLIGHT_DA9052) += da9052_bl.o

drivers/video/backlight/cgbc_bl.c

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Congatec Board Controller (CGBC) Backlight Driver
4+
*
5+
* This driver provides backlight control for LCD displays connected to
6+
* Congatec boards via the CGBC (Congatec Board Controller). It integrates
7+
* with the Linux backlight subsystem and communicates with hardware through
8+
* the cgbc-core module.
9+
*
10+
* Copyright (C) 2025 Novatron Oy
11+
*
12+
* Author: Petri Karhula <petri.karhula@novatron.fi>
13+
*/
14+
15+
#include <linux/backlight.h>
16+
#include <linux/bitfield.h>
17+
#include <linux/bits.h>
18+
#include <linux/mfd/cgbc.h>
19+
#include <linux/module.h>
20+
#include <linux/platform_device.h>
21+
22+
#define BLT_PWM_DUTY_MASK GENMASK(6, 0)
23+
24+
/* CGBC command for PWM brightness control*/
25+
#define CGBC_CMD_BLT0_PWM 0x75
26+
27+
#define CGBC_BL_MAX_BRIGHTNESS 100
28+
29+
/**
30+
* CGBC backlight driver data
31+
* @dev: Pointer to the platform device
32+
* @cgbc: Pointer to the parent CGBC device data
33+
* @current_brightness: Current brightness level (0-100)
34+
*/
35+
struct cgbc_bl_data {
36+
struct device *dev;
37+
struct cgbc_device_data *cgbc;
38+
unsigned int current_brightness;
39+
};
40+
41+
static int cgbc_bl_read_brightness(struct cgbc_bl_data *bl_data)
42+
{
43+
u8 cmd_buf[4] = { CGBC_CMD_BLT0_PWM };
44+
u8 reply_buf[3];
45+
int ret;
46+
47+
ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf),
48+
reply_buf, sizeof(reply_buf), NULL);
49+
if (ret < 0)
50+
return ret;
51+
52+
/*
53+
* Get only PWM duty factor percentage,
54+
* ignore polarity inversion bit (bit 7)
55+
*/
56+
bl_data->current_brightness = FIELD_GET(BLT_PWM_DUTY_MASK, reply_buf[0]);
57+
58+
return 0;
59+
}
60+
61+
static int cgbc_bl_update_status(struct backlight_device *bl)
62+
{
63+
struct cgbc_bl_data *bl_data = bl_get_data(bl);
64+
u8 cmd_buf[4] = { CGBC_CMD_BLT0_PWM };
65+
u8 reply_buf[3];
66+
u8 brightness;
67+
int ret;
68+
69+
brightness = backlight_get_brightness(bl);
70+
71+
if (brightness != bl_data->current_brightness) {
72+
/* Read the current values */
73+
ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf), reply_buf,
74+
sizeof(reply_buf), NULL);
75+
if (ret < 0) {
76+
dev_err(bl_data->dev, "Failed to read PWM settings: %d\n", ret);
77+
return ret;
78+
}
79+
80+
/*
81+
* Prepare command buffer for writing new settings. Only 2nd byte is changed
82+
* to set new brightness (PWM duty cycle %). Other values (polarity, frequency)
83+
* are preserved from the read values.
84+
*/
85+
cmd_buf[1] = (reply_buf[0] & ~BLT_PWM_DUTY_MASK) |
86+
FIELD_PREP(BLT_PWM_DUTY_MASK, brightness);
87+
cmd_buf[2] = reply_buf[1];
88+
cmd_buf[3] = reply_buf[2];
89+
90+
ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf), reply_buf,
91+
sizeof(reply_buf), NULL);
92+
if (ret < 0) {
93+
dev_err(bl_data->dev, "Failed to set brightness: %d\n", ret);
94+
return ret;
95+
}
96+
97+
bl_data->current_brightness = reply_buf[0] & BLT_PWM_DUTY_MASK;
98+
99+
/* Verify the setting was applied correctly */
100+
if (bl_data->current_brightness != brightness) {
101+
dev_err(bl_data->dev,
102+
"Brightness setting verification failed (got %u, expected %u)\n",
103+
bl_data->current_brightness, (unsigned int)brightness);
104+
return -EIO;
105+
}
106+
}
107+
108+
return 0;
109+
}
110+
111+
static int cgbc_bl_get_brightness(struct backlight_device *bl)
112+
{
113+
struct cgbc_bl_data *bl_data = bl_get_data(bl);
114+
int ret;
115+
116+
ret = cgbc_bl_read_brightness(bl_data);
117+
if (ret < 0) {
118+
dev_err(bl_data->dev, "Failed to read brightness: %d\n", ret);
119+
return ret;
120+
}
121+
122+
return bl_data->current_brightness;
123+
}
124+
125+
static const struct backlight_ops cgbc_bl_ops = {
126+
.options = BL_CORE_SUSPENDRESUME,
127+
.update_status = cgbc_bl_update_status,
128+
.get_brightness = cgbc_bl_get_brightness,
129+
};
130+
131+
static int cgbc_bl_probe(struct platform_device *pdev)
132+
{
133+
struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent);
134+
struct backlight_properties props = { };
135+
struct backlight_device *bl_dev;
136+
struct cgbc_bl_data *bl_data;
137+
int ret;
138+
139+
bl_data = devm_kzalloc(&pdev->dev, sizeof(*bl_data), GFP_KERNEL);
140+
if (!bl_data)
141+
return -ENOMEM;
142+
143+
bl_data->dev = &pdev->dev;
144+
bl_data->cgbc = cgbc;
145+
146+
ret = cgbc_bl_read_brightness(bl_data);
147+
if (ret < 0)
148+
return dev_err_probe(&pdev->dev, ret,
149+
"Failed to read initial brightness\n");
150+
151+
props.type = BACKLIGHT_PLATFORM;
152+
props.max_brightness = CGBC_BL_MAX_BRIGHTNESS;
153+
props.brightness = bl_data->current_brightness;
154+
props.scale = BACKLIGHT_SCALE_LINEAR;
155+
156+
bl_dev = devm_backlight_device_register(&pdev->dev, "cgbc-backlight",
157+
&pdev->dev, bl_data,
158+
&cgbc_bl_ops, &props);
159+
if (IS_ERR(bl_dev))
160+
return dev_err_probe(&pdev->dev, PTR_ERR(bl_dev),
161+
"Failed to register backlight device\n");
162+
163+
platform_set_drvdata(pdev, bl_data);
164+
165+
return 0;
166+
}
167+
168+
static struct platform_driver cgbc_bl_driver = {
169+
.driver = {
170+
.name = "cgbc-backlight",
171+
},
172+
.probe = cgbc_bl_probe,
173+
};
174+
175+
module_platform_driver(cgbc_bl_driver);
176+
177+
MODULE_AUTHOR("Petri Karhula <petri.karhula@novatron.fi>");
178+
MODULE_DESCRIPTION("Congatec Board Controller (CGBC) Backlight Driver");
179+
MODULE_LICENSE("GPL");
180+
MODULE_ALIAS("platform:cgbc-backlight");

0 commit comments

Comments
 (0)