Backlight 서브시스템
리눅스 backlight core를 "디스플레이 패널 밝기를 blanking 계약에 맞춰 제어하는 프레임워크"라는 관점에서 정리합니다. backlight_device, backlight_ops, pwm-backlight, ACPI/firmware backlight, DRM 패널 연동, sysfs ABI, Device Tree, 전원 관리(Power Management), hotkey/firmware 경로, 밝기 스케일(Scale) 보정, suspend/resume 시퀀스와 디버깅(Debugging)까지 실무 기준으로 최대한 상세하게 다룹니다.
핵심 요약
- Backlight core — 패널 밝기 제어를 위한 별도 프레임워크로, blanking, suspend/resume, hotkey 경로가 핵심입니다.
- blanking 계약 — 사용자가 밝기 200을 설정해도 blank 상태면 effective brightness는 0이 됩니다. 이 차이가 backlight의 근본 특성입니다.
- PWM은 수단일 뿐 — backlight가 PWM을 쓸 수 있지만, 상위 ABI와 정책은 LED class와 완전히 다릅니다.
- 밝기 스케일 보정 — PWM 듀티와 체감 밝기는 선형이 아닙니다. 지각 보정 테이블이 제품 품질을 좌우합니다.
- 품질 포인트 — 깜박임, 저조도 단계, resume 후 복원, white flash 방지가 실제 제품 완성도를 좌우합니다.
단계별 이해
- backlight인지 LED인지 먼저 나눕니다
패널 조명이면 backlight, 상태 표시등이면 LED class가 맞습니다. 패널 조명을 LED class로 등록하면 blanking/hotkey 경로를 잃습니다. - 하드웨어 제어 수단을 확인합니다
PWM인지, GPU 레지스터인지, ACPI 인터페이스인지 파악합니다. - backlight type을 결정합니다
BACKLIGHT_RAW(직접 PWM 제어),BACKLIGHT_PLATFORM(vendor EC),BACKLIGHT_FIRMWARE(ACPI) 중 선택합니다. - 전원/blanking 시퀀스를 맞춥니다
panel prepare/enable/disable/unprepare와 순서를 잘못 맞추면 white flash와 잔광이 생깁니다. - 사람의 눈으로 검증합니다
계측상 선형 밝기라도 체감상은 비선형이므로, 밝기 테이블과 PWM 주파수를 실제 패널로 검증해야 합니다.
Backlight core: brightness 숫자보다 blanking 계약이 더 중요하다
Backlight core의 핵심은 struct backlight_device와 struct backlight_ops입니다. LED class가 trigger와 blink 중심이라면, backlight는 blanking 상태를 고려해 실제 출력 밝기를 계산하는 것이 중심입니다.
헤더를 보면 backlight_properties에는 brightness, max_brightness, power, fb_blank, type, state, scale가 있습니다. 여기서 중요한 점은 fb_blank가 이미 deprecated로 표시되어 있고, 드라이버는 직접 이 값을 보기보다 backlight_is_blank()를 사용하라고 권장된다는 점입니다.
| backlight type | 의미 | 대표 예 |
|---|---|---|
BACKLIGHT_RAW | 직접 하드웨어 레지스터/PWM 제어 | SoC PWM + panel enable GPIO |
BACKLIGHT_PLATFORM | 플랫폼 특화 인터페이스 | EC, vendor-specific controller |
BACKLIGHT_FIRMWARE | 표준 펌웨어 인터페이스 기반 | ACPI/firmware backlight |
| 요소 | 의미 | 실무 포인트 |
|---|---|---|
update_status() | 속성 변경 반영 | 핵심 콜백. 실제 PWM/regulator/write는 대부분 여기서 수행 |
get_brightness() | 하드웨어 readback | 선택 사항. 없으면 props 값 사용 |
BL_CORE_SUSPENDRESUME | suspend/resume 시 update_status 호출 | 재초기화가 필요한 장치에 유용 |
backlight_is_blank() | display blank 상태 판정 | driver가 직접 props를 조합하지 말고 helper 사용 |
backlight_get_brightness() | blank 상태 반영한 effective brightness | update_status의 기본 입력 |
backlight_force_update() | hotkey/sysfs 등 외부 변경 통보 | firmware/ACPI 경로와 사용자 공간 정책 연결 |
#include <linux/backlight.h>
struct panel_bl {
struct pwm_device *pwm;
};
static int panel_bl_update_status(struct backlight_device *bd)
{
struct panel_bl *bl = bl_get_data(bd);
int brightness = backlight_get_brightness(bd);
if (brightness == 0) {
/* PWM off, enable GPIO off, 필요하면 regulator 단계적 off */
return 0;
}
/* brightness를 듀티로 매핑하고 PWM 적용 */
return pwm_apply_might_sleep(bl->pwm, &state);
}
static const struct backlight_ops panel_bl_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = panel_bl_update_status,
};
static int panel_bl_register(struct device *dev, struct panel_bl *bl)
{
struct backlight_properties props = {
.type = BACKLIGHT_RAW,
.max_brightness = 255,
.brightness = 160,
.power = FB_BLANK_UNBLANK,
.scale = BACKLIGHT_SCALE_NON_LINEAR,
};
bl->bd = devm_backlight_device_register(dev, "panel-backlight",
dev, bl, &panel_bl_ops, &props);
if (IS_ERR(bl->bd))
return PTR_ERR(bl->bd);
return 0;
}
backlight_get_brightness()는 blank 상태면 0을 반환합니다. 즉, 사용자가 이전에 brightness=200을 써 두었더라도 panel blank나 suspend 상태에서는 effective brightness가 0이 될 수 있습니다. 이 차이를 이해하지 못하면 "밝기 값은 살아 있는데 왜 화면이 어둡지?" 같은 혼란이 생깁니다.
또한 check_fb()는 여러 fbdev가 하나의 backlight를 공유할 수 있는 구형 환경에서 어떤 framebuffer가 이 backlight와 연동되는지 거를 때 쓰입니다. 최신 DRM 중심 시스템에서는 존재감이 줄었지만, 오래된 플랫폼이나 혼합 환경에서는 아직 중요한 경계입니다. 코어는 notifier 경로도 제공하며, 외부 hotkey나 펌웨어 변경은 backlight_force_update()를 통해 sysfs 외부에서 brightness가 바뀌었다는 사실을 반영할 수 있습니다.
백라이트 스택(Stack) 전체 구조
백라이트 서브시스템은 LED 서브시스템과 독립적이며, 디스플레이 파이프라인(Display Pipeline)의 일부로 동작합니다. 아래 다이어그램은 사용자 공간에서 하드웨어까지 밝기 변경 요청이 어떻게 흘러가는지 보여 줍니다.
완전한 백라이트 드라이버 예제
PWM 기반 백라이트 드라이버의 전체 구조를 보여 줍니다. 이 예제는 PWM, enable GPIO, regulator를 모두 조합하는 일반적인 임베디드 패널 백라이트입니다.
#include <linux/backlight.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regulator/consumer.h>
struct my_backlight {
struct pwm_device *pwm;
struct gpio_desc *enable_gpio;
struct regulator *supply;
struct backlight_device *bd;
bool enabled;
u32 *levels; /* 비선형 밝기 테이블 */
unsigned int num_levels;
};
static int my_bl_power_on(struct my_backlight *bl)
{
int ret;
if (bl->enabled)
return 0;
ret = regulator_enable(bl->supply);
if (ret)
return ret;
gpiod_set_value_cansleep(bl->enable_gpio, 1);
bl->enabled = true;
return 0;
}
static void my_bl_power_off(struct my_backlight *bl)
{
if (!bl->enabled)
return;
gpiod_set_value_cansleep(bl->enable_gpio, 0);
regulator_disable(bl->supply);
bl->enabled = false;
}
static int my_bl_update_status(struct backlight_device *bd)
{
struct my_backlight *bl = bl_get_data(bd);
int brightness = backlight_get_brightness(bd);
struct pwm_state state;
u32 duty;
int ret;
if (brightness == 0) {
pwm_disable(bl->pwm);
my_bl_power_off(bl);
return 0;
}
/* 비선형 밝기 테이블에서 실제 PWM 듀티 조회 */
duty = bl->levels[brightness];
pwm_get_state(bl->pwm, &state);
state.duty_cycle = DIV_ROUND_UP(
duty * state.period, bl->levels[bl->num_levels - 1]);
state.enabled = true;
ret = my_bl_power_on(bl);
if (ret)
return ret;
return pwm_apply_might_sleep(bl->pwm, &state);
}
static const struct backlight_ops my_bl_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = my_bl_update_status,
};
static int my_bl_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct my_backlight *bl;
struct backlight_properties props = {};
u32 default_level;
int ret;
bl = devm_kzalloc(dev, sizeof(*bl), GFP_KERNEL);
if (!bl)
return -ENOMEM;
/* 리소스 획득 */
bl->pwm = devm_pwm_get(dev, NULL);
if (IS_ERR(bl->pwm))
return dev_err_probe(dev, PTR_ERR(bl->pwm),
"failed to get PWM\n");
bl->enable_gpio = devm_gpiod_get_optional(dev,
"enable",
GPIOD_OUT_LOW);
bl->supply = devm_regulator_get(dev, "power");
/* 밝기 테이블 읽기 */
bl->num_levels = device_property_count_u32(dev,
"brightness-levels");
bl->levels = devm_kcalloc(dev, bl->num_levels,
sizeof(u32), GFP_KERNEL);
device_property_read_u32_array(dev, "brightness-levels",
bl->levels, bl->num_levels);
device_property_read_u32(dev, "default-brightness-level",
&default_level);
/* backlight 등록 */
props.type = BACKLIGHT_RAW;
props.max_brightness = bl->num_levels - 1;
props.brightness = default_level;
props.scale = BACKLIGHT_SCALE_NON_LINEAR;
bl->bd = devm_backlight_device_register(dev,
"panel-backlight",
dev, bl,
&my_bl_ops, &props);
if (IS_ERR(bl->bd))
return PTR_ERR(bl->bd);
/* 초기 밝기 적용 */
backlight_update_status(bl->bd);
return 0;
}
static const struct of_device_id my_bl_of_match[] = {
{ .compatible = "vendor,my-backlight" },
{ }
};
static struct platform_driver my_bl_driver = {
.driver = {
.name = "my-backlight",
.of_match_table = my_bl_of_match,
},
.probe = my_bl_probe,
};
module_platform_driver(my_bl_driver);
update_status()는 "현재 속성 상태를 하드웨어에 반영하라"는 의미입니다. 이 콜백 안에서 backlight_get_brightness()를 호출하면 blank 상태가 자동으로 반영된 effective brightness를 얻습니다. 드라이버는 이 값이 0이면 "꺼야 한다", 양수이면 "해당 밝기로 켜야 한다"고 판단하기만 하면 됩니다.
pwm-backlight 드라이버 내부: 표준 백라이트의 핵심
커널에 내장된 pwm_bl.c 드라이버는 대부분의 임베디드 시스템에서 표준 백라이트 역할을 합니다. 이 드라이버가 처리하는 핵심 로직을 분석합니다.
| DT 프로퍼티 | 역할 | 기본값 |
|---|---|---|
pwms | PWM 장치, 채널, 주기, 극성 지정 | 필수 |
brightness-levels | 사용자 밝기 단계를 PWM 듀티로 매핑하는 테이블 | 없으면 0~max 선형 |
default-brightness-level | 부팅 시 초기 밝기 인덱스 | 테이블 길이의 절반 |
num-interpolated-steps | 테이블 항목 간 보간 단계 수 | 보간 없음 |
power-supply | 백라이트 전원 regulator 참조 | dummy regulator |
enable-gpios | 백라이트 활성화 GPIO | 없으면 사용 안 함 |
post-pwm-on-delay-ms | PWM 시작 후 enable GPIO 전 대기 시간 | 0 |
pwm-off-delay-ms | enable GPIO 해제 후 PWM 정지 전 대기 시간 | 0 |
/* pwm-backlight 고급 설정 예시 */
backlight: backlight {
compatible = "pwm-backlight";
pwms = <&pwm0 0 20000 0>; /* 50kHz PWM */
/* CIE 1931 근사 보정 테이블 (24단계) */
brightness-levels = <
0 1 1 2 3 4
6 8 11 16 22 30
40 52 67 85 106 131
160 194 233 255
>;
default-brightness-level = <16>;
/* 보간: 22개 테이블 항목 사이에 각 8단계 삽입
→ 총 (22-1)*8+1 = 169단계의 세밀한 밝기 제어 */
num-interpolated-steps = <8>;
power-supply = <&vdd_backlight>;
enable-gpios = <&gpio3 15 GPIO_ACTIVE_HIGH>;
/* PWM 시작 후 50ms 대기 후 enable GPIO 활성화
→ white flash 방지에 효과적 */
post-pwm-on-delay-ms = <50>;
pwm-off-delay-ms = <20>;
};
brightness-levels에 22개 항목을 넣고 num-interpolated-steps를 8로 설정하면, 드라이버는 인접 항목 사이에 8단계를 선형 보간하여 총 169단계의 밝기를 제공합니다. 이렇게 하면 DT에는 핵심 곡선 포인트만 적고, 세밀한 제어는 드라이버가 자동 보간하므로 DT 크기와 제어 정밀도를 동시에 달성할 수 있습니다.
ACPI/Firmware Backlight: 노트북 플랫폼의 밝기 제어 경로
노트북과 일체형 PC에서는 backlight를 순수 커널 드라이버만으로 제어하는 경우가 드뭅니다. 대부분 ACPI/UEFI 펌웨어가 밝기 제어 인터페이스를 제공하고, 커널은 이를 BACKLIGHT_FIRMWARE 타입으로 감싸서 사용자 공간에 노출합니다. 이때 가장 흔한 문제는 여러 backlight 장치가 동시에 등록되는 충돌입니다.
| 밝기 제어 경로 | backlight type | 대표 드라이버 | 우선순위(Priority) |
|---|---|---|---|
| ACPI _BCM/_BCL | BACKLIGHT_FIRMWARE | acpi_video | 일반적으로 기본 |
| vendor-specific EC | BACKLIGHT_PLATFORM | thinkpad_acpi, asus-wmi, dell-laptop | vendor 특화 |
| GPU 레지스터 직접 제어 | BACKLIGHT_RAW | i915, amdgpu, nouveau | 가장 정밀 |
커널은 이 충돌을 해결하기 위해 acpi_video_get_backlight_type()과 커널 파라미터 acpi_backlight=를 제공합니다.
# 현재 등록된 backlight 장치와 타입 확인
for bl in /sys/class/backlight/*; do
echo "$(basename $bl): type=$(cat $bl/type)"
done
# ACPI 밝기 제어를 강제 사용/비활성화
# 커널 커맨드라인에 추가:
# acpi_backlight=vendor — vendor 드라이버 우선
# acpi_backlight=video — ACPI video 드라이버 사용
# acpi_backlight=native — GPU 네이티브 제어
# acpi_backlight=none — ACPI backlight 비활성화
# 현재 설정 확인
dmesg | grep -i 'backlight'
/sys/class/backlight/에 장치가 2개 이상 보이면, 사용자 공간 데몬(GNOME power-daemon 등)이 잘못된 장치에 값을 쓰고 있을 수 있습니다. acpi_backlight=native나 acpi_backlight=vendor를 커널 커맨드라인에 추가해 한 가지 경로로 고정하면 해결되는 경우가 많습니다.
/* ACPI video backlight의 핵심 콜백 (개념적) */
static int acpi_video_get_brightness(struct backlight_device *bd)
{
unsigned long long cur_level;
struct acpi_video_device *vd = bl_get_data(bd);
/* _BQC: ACPI 메서드로 현재 밝기 조회 */
if (ACPI_SUCCESS(acpi_video_device_lcd_get_level_current(
vd, &cur_level, 0)))
return (int)cur_level;
return 0;
}
static int acpi_video_set_brightness(struct backlight_device *bd)
{
int request_level = bd->props.brightness;
struct acpi_video_device *vd = bl_get_data(bd);
/* _BCM: ACPI 메서드로 밝기 설정 */
return acpi_video_device_lcd_set_level(vd, request_level);
}
ACPI backlight가 hotkey 이벤트를 보고하는 방식도 중요합니다. 일부 플랫폼은 ACPI가 직접 밝기를 변경한 후 notify만 보내고, 일부는 OS에 "밝기 올려달라"는 이벤트만 보냅니다. 전자는 backlight_force_update(BACKLIGHT_UPDATE_HOTKEY)로 값을 동기화하면 되고, 후자는 사용자 공간이 이벤트를 받아 직접 sysfs에 새 값을 써야 합니다.
밝기 스케일과 지각(Perceptual) 보정
PWM 듀티 비율과 사람이 느끼는 밝기는 선형 관계가 아닙니다. Weber-Fechner 법칙에 따르면 인간의 밝기 감각은 대략 로그 스케일에 가깝습니다. 따라서 0-255를 균등 분할하면 저밝기 구간에서 단계 구분이 안 되고, 고밝기 구간에서 불필요하게 촘촘해집니다.
| 스케일 | Kconfig/속성 | 특성 | 적합한 경우 |
|---|---|---|---|
| 선형(linear) | BACKLIGHT_SCALE_LINEAR | 듀티 비율 = 밝기 단계 | LED 컨트롤러가 자체 보정 테이블을 가진 경우 |
| 비선형(non-linear) | BACKLIGHT_SCALE_NON_LINEAR | 드라이버가 자체 매핑(Mapping) 적용 | pwm-backlight의 brightness-levels 테이블 |
| 알 수 없음 | BACKLIGHT_SCALE_UNKNOWN | 스케일 정보 없음 | 레거시 드라이버 |
/* 선형 vs 비선형 밝기 테이블 비교 */
/* 선형 테이블: 저밝기 구간이 거의 동일하게 느껴짐 (좋지 않음) */
brightness-levels = <0 32 64 96 128 160 192 224 255>;
/* 지각 보정 테이블: 저밝기를 촘촘하게, 고밝기를 넓게 (권장) */
brightness-levels = <0 1 2 4 8 16 32 64 128 255>;
/* 더 세밀한 보정 (CIE 1931 근사) */
brightness-levels = <0 1 1 2 3 4 6 8 11 15 20 27
35 45 57 72 89 110 134 163
196 233 255>;
brightness-levels에 넣는 방식이 실용적입니다. 22~32단계 정도의 보정 테이블이면 대부분의 제품에서 충분한 체감 균등성을 얻을 수 있습니다.
PWM, pwm-backlight, DRM 패널 시퀀스
실무에서 backlight의 상당수는 pwm-backlight로 구현됩니다. 하지만 핵심은 PWM 자체보다 언제 PWM을 켜고 끄는가입니다. 패널이 아직 준비되지 않았는데 PWM을 먼저 올리면 white flash가 생기고, 반대로 패널을 먼저 끄고 PWM을 나중에 끄면 잔광이나 깜박임이 남을 수 있습니다.
DRM/KMS 기반 시스템에서는 보통 panel/bridge/GPU 드라이버가 display pipeline과 backlight를 함께 조율합니다. 패널 노드가 DT에서 backlight = <&backlight>로 연결되고, 드라이버는 필요하면 devm_of_find_backlight()나 of_find_backlight_by_node()로 backlight 장치를 찾습니다.
| 단계 | 주요 동작 | 실패 시 보이는 현상 |
|---|---|---|
| panel prepare | 전원 레일, reset, 초기 명령, 타이밍 준비 | 패널이 아직 준비 안 됨 |
| panel enable | 픽셀 전송 시작, display engine 활성화 | 검은 화면 또는 white flash |
| backlight on | PWM/enable GPIO/regulator 활성화 | 순서가 빠르면 white flash, 느리면 첫 프레임이 어둡게 시작 |
| blank/suspend | backlight 0, panel disable/unprepare | 잔광, 깜박임, resume 후 복원 실패 |
/* pwm-backlight + panel 연결 예시 */
backlight: backlight {
compatible = "pwm-backlight";
pwms = <&pwm0 0 50000 0>;
brightness-levels = <0 2 4 8 16 32 64 128 255>;
default-brightness-level = <7>;
power-supply = <&vdd_bl>;
enable-gpios = <&gpio1 5 1>;
};
panel@0 {
compatible = "vendor,my-panel";
backlight = <&backlight>;
};
밝기 테이블도 중요합니다. brightness-levels를 선형으로 잡으면 저밝기 구간이 뭉개지고, 사용자는 "1단계에서 너무 갑자기 밝아진다"고 느낄 수 있습니다. 그래서 많은 제품이 비선형 또는 지각(perceptual) 보정 테이블을 씁니다. 헤더에도 backlight scale로 BACKLIGHT_SCALE_LINEAR와 BACKLIGHT_SCALE_NON_LINEAR가 정의되어 있습니다.
커널 빌드 설정: Backlight Kconfig 옵션
Backlight 서브시스템을 사용하려면 커널 빌드 시 관련 Kconfig 옵션을 활성화해야 합니다.
| Kconfig 옵션 | 역할 | 의존성 / 비고 |
|---|---|---|
CONFIG_BACKLIGHT_CLASS_DEVICE | Backlight class 지원 | LED와 독립. 별도 서브시스템 |
CONFIG_BACKLIGHT_PWM | PWM backlight 드라이버 | BACKLIGHT_CLASS_DEVICE + PWM |
CONFIG_BACKLIGHT_GPIO | GPIO backlight 드라이버 | BACKLIGHT_CLASS_DEVICE + GPIOLIB |
# Backlight 서브시스템
CONFIG_BACKLIGHT_CLASS_DEVICE=y
CONFIG_BACKLIGHT_PWM=m
CONFIG_BACKLIGHT_CLASS_DEVICE와 CONFIG_BACKLIGHT_PWM만 켜면 됩니다.
sysfs ABI와 사용자 공간 제어
Backlight는 LED class와 완전히 다른 sysfs 경로를 사용합니다.
| 경로 | 대표 파일 | 의미 |
|---|---|---|
/sys/class/backlight/<name>/ | brightness, actual_brightness, max_brightness, bl_power, type | 패널 밝기와 blank 상태 제어 |
# backlight 밝기 조절
cat /sys/class/backlight/backlight/max_brightness
echo 120 > /sys/class/backlight/backlight/brightness
cat /sys/class/backlight/backlight/actual_brightness
cat /sys/class/backlight/backlight/bl_power
cat /sys/class/backlight/backlight/type
Backlight의 brightness는 사용자의 요청값이고, actual_brightness는 하드웨어가 실제로 적용한 값을 보여 줄 수 있습니다. 하드웨어가 coarse step만 지원하거나 blank 상태가 겹치면 두 값이 다를 수 있습니다. 이 차이는 펌웨어 hotkey나 hardware quantization이 있는 플랫폼에서 특히 중요합니다.
hotkey나 펌웨어 경로로 밝기가 바뀌는 플랫폼은 backlight_force_update()를 써서 코어와 사용자 공간에 변경 사실을 알려야 합니다. 헤더에는 이때 이유를 BACKLIGHT_UPDATE_HOTKEY 또는 BACKLIGHT_UPDATE_SYSFS로 구분하는 enum이 정의되어 있습니다. 즉, backlight는 단순 sysfs write path만 보는 프레임워크가 아닙니다.
sysfs API 전체 참조
Backlight의 sysfs 파일을 모두 정리합니다. 사용자 공간 도구 개발자와 시스템 관리자가 참조할 수 있는 완전한 목록입니다.
| sysfs 파일 | 권한 | 타입 | 설명 |
|---|---|---|---|
Backlight (/sys/class/backlight/<name>/) | |||
brightness | rw | 정수 | 요청된 밝기 (requested) |
actual_brightness | r | 정수 | 하드웨어 실제 밝기 |
max_brightness | r | 정수 | 최대 밝기값 |
bl_power | rw | 정수 | 전원 상태 (0=unblank, 4=blank) |
type | r | 문자열 | backlight 유형 (raw, platform, firmware) |
scale | r | 문자열 | 밝기 스케일 (linear, non-linear, unknown) |
# backlight 완전 상태 확인
for bl in /sys/class/backlight/*; do
name=$(basename "$bl")
echo "=== $name ==="
echo " type: $(cat $bl/type)"
echo " brightness: $(cat $bl/brightness) / $(cat $bl/max_brightness)"
echo " actual: $(cat $bl/actual_brightness)"
echo " bl_power: $(cat $bl/bl_power)"
[ -f "$bl/scale" ] && echo " scale: $(cat $bl/scale)"
done
Device Tree 바인딩: pwm-backlight와 panel 연결
Backlight는 DT에서 자주 등장합니다. 중요한 점은 단순히 PWM 번호만 적는 것이 아니라, 밝기 테이블, 전원, 패널 연결까지 ABI로 고정한다는 것입니다.
/* PWM backlight */
backlight: backlight {
compatible = "pwm-backlight";
pwms = <&pwm0 0 50000 0>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
power-supply = <&vdd_bl>;
enable-gpios = <&gpio1 7 0>;
};
panel@0 {
compatible = "vendor,my-panel";
backlight = <&backlight>;
};
드라이버 구현 패턴: backlight 드라이버 작성 가이드
Backlight 드라이버의 품질은 하드웨어 제어 자체보다 올바른 콜백 의미 준수와 전원 시퀀스 설계에서 갈립니다.
- backlight에서는 blank helper를 신뢰합니다
backlight_is_blank(),backlight_get_brightness()를 사용해 effective brightness를 계산합니다. - 전원 시퀀스를 도구화합니다
regulator, enable GPIO, PWM enable/disable 순서를 명확한 helper로 묶어 두면 white flash를 줄이기 쉽습니다. - 외부 변경 경로를 반영합니다
firmware hotkey면backlight_force_update()를 검토합니다. - managed API를 사용합니다
devm_backlight_device_register()로 등록하면 해제 코드가 필요 없습니다. - 밝기 스케일을 명시합니다
backlight_properties.scale에BACKLIGHT_SCALE_LINEAR또는BACKLIGHT_SCALE_NON_LINEAR를 설정하면 사용자 공간 도구가 적절히 해석할 수 있습니다.
Backlight suspend/resume
전원 관리에서 backlight는 LED와 다른 메커니즘으로 상태를 보존합니다. 이 차이를 이해하지 못하면 resume 후 backlight가 최대 밝기로 튀거나 복원되지 않는 등의 문제가 생깁니다.
| 프레임워크 | suspend 동작 | resume 동작 | 드라이버 책임 |
|---|---|---|---|
Backlight (BL_CORE_SUSPENDRESUME) | 코어가 update_status() 호출 (blank=1) | 코어가 update_status() 호출 (이전 밝기 복원) | update_status()에서 blank 처리 필수 |
| Backlight (플래그 없음) | 코어가 아무것도 안 함 | 코어가 아무것도 안 함 | DRM/panel 드라이버가 직접 관리 |
update_status()가 resume 시 호출될 때, PWM 컨트롤러와 regulator가 이미 resume된 상태여야 합니다. PWM이 아직 resume되지 않았는데 backlight update_status()가 먼저 호출되면 pwm_apply_might_sleep()이 실패할 수 있습니다. 이런 의존성은 Device Tree의 power-supply 참조나 dev_pm_domain 설정으로 순서를 보장할 수 있지만, 복잡한 경우 deferred probe나 PM notifier를 사용해야 합니다.
런타임 PM(Runtime PM) 통합
백라이트 드라이버에서 런타임 PM은 장치가 사용되지 않을 때 전력을 절약하는 데 유용합니다. 보통 display idle과 연동됩니다.
| PM 시나리오 | Backlight 동작 |
|---|---|
| 시스템 suspend (S3) | BL_CORE_SUSPENDRESUME 시 코어가 update_status 호출 |
| 런타임 suspend | 보통 display idle과 연동 |
| shutdown | 보통 off (panel off 순서 따름) |
| hibernate (S4) | suspend와 동일 |
전원 관리와 사용자 경험: 깜박임, 저조도 단계, suspend/resume
사람의 눈은 선형이 아닙니다. PWM 듀티를 0, 32, 64, 96, 128처럼 선형으로 나눠도 체감 밝기는 선형이 아니고, 저조도 영역에서는 단계가 거의 구분되지 않거나 반대로 첫 단계가 너무 밝게 느껴질 수 있습니다. 그래서 backlight는 밝기 테이블과 BACKLIGHT_SCALE_NON_LINEAR 개념이 중요합니다.
| 문제 | 원인 | 완화 방법 |
|---|---|---|
| 저밝기 깜박임 | PWM 주파수가 너무 낮음 | 주파수 상향, 저밝기 테이블 재조정, current sink 모드 검토 |
| 1단계가 너무 밝음 | 선형 밝기 테이블 | 지각 기반 비선형 테이블 |
| resume 후 이전 밝기 복원 실패 | BL_CORE_SUSPENDRESUME 미사용 또는 상태 재초기화 누락 | update_status 경로 재검토 |
| white flash | panel보다 backlight를 먼저 켬 | panel enable 이후 backlight on |
노트북처럼 펌웨어가 backlight를 일부 통제하는 플랫폼에서는 커널 드라이버, ACPI hotkey, 사용자 공간 power daemon이 동시에 영향을 줄 수 있습니다. 이런 환경에서는 "누가 최종 권한자냐"를 정하지 않으면 밝기 튐이나 race가 생깁니다.
디버깅 절차: sysfs, PWM 파형, blank 상태, panel 시퀀스
Backlight 문제는 대부분 다음 네 층 중 하나에서 발생합니다.
- 클래스 계층
장치가 제대로 등록되었는가, 올바른 sysfs 경로가 생겼는가 - 정책 계층
hotkey, blanking, suspend/resume 로직이 값을 덮어쓰지 않는가 - 하드웨어 제어 계층
PWM duty/frequency, enable GPIO, regulator가 실제로 움직이는가 - 패널 타이밍 계층
panel prepare/enable와 backlight on/off 순서가 맞는가
# 1. class 장치 확인
ls /sys/class/backlight/
# 2. backlight 상태 확인
cat /sys/class/backlight/backlight/brightness
cat /sys/class/backlight/backlight/actual_brightness
cat /sys/class/backlight/backlight/bl_power
cat /sys/class/backlight/backlight/type
# 3. PWM debug와 커널 로그
cat /sys/kernel/debug/pwm
dmesg | grep -iE 'backlight|pwm|panel|drm'
이 단계에서 sysfs 값이 정상인데 실제 빛이 안 난다면 하드웨어 enable GPIO, regulator, PWM 파형을 먼저 의심합니다. 반대로 PWM도 정상인데 화면이 번쩍이면 panel 시퀀스 문제일 가능성이 큽니다.
고급 디버깅: debugfs, ftrace, 커널 로그 활용
sysfs 기본 확인 이후에도 문제가 해결되지 않으면 더 깊은 디버깅 도구를 동원해야 합니다.
# ===== PWM debugfs를 통한 정밀 분석 =====
# PWM 컨트롤러의 현재 상태를 확인
cat /sys/kernel/debug/pwm
# 출력 예시:
# platform/fe6e0000.pwm, 1 PWM device
# pwm-0 (pwm-backlight ): period: 20000 ns duty: 15680 ns polarity: normal
# enabled
# ===== GPIO 상태 확인 =====
cat /sys/kernel/debug/gpio | grep -i 'backlight\|enable'
# ===== regulator 상태 확인 =====
cat /sys/kernel/debug/regulator/regulator_summary | head -30
# ===== ftrace로 backlight 함수 호출 추적 =====
echo 0 > /sys/kernel/debug/tracing/tracing_on
echo function > /sys/kernel/debug/tracing/current_tracer
echo 'backlight_* pwm_apply*' > /sys/kernel/debug/tracing/set_ftrace_filter
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 이제 밝기를 바꿔 본다
echo 100 > /sys/class/backlight/backlight/brightness
# trace 확인
cat /sys/kernel/debug/tracing/trace
# ===== suspend/resume 디버깅 =====
# PM 이벤트 순서를 기록
echo 1 > /sys/power/pm_debug_messages
# suspend 전후 backlight 상태 스냅샷
for bl in /sys/class/backlight/*; do
echo "$(basename $bl): brightness=$(cat $bl/brightness) actual=$(cat $bl/actual_brightness) power=$(cat $bl/bl_power)"
done
# suspend 실행
echo mem > /sys/power/state
# resume 후 동일한 상태 확인 → 차이가 있으면 복원 실패
| 디버깅 도구 | 확인 대상 | 언제 사용 |
|---|---|---|
/sys/kernel/debug/pwm | PWM 주기, 듀티, 활성 상태 | 밝기가 예상과 다를 때 |
/sys/kernel/debug/gpio | enable GPIO 상태 | backlight가 완전히 안 켜질 때 |
/sys/kernel/debug/regulator/ | 전원 레일 on/off 상태 | 전원 시퀀스 문제 의심 시 |
ftrace backlight_* | backlight 함수 호출 순서/타이밍 | resume/blank 시퀀스 문제 |
pm_debug_messages | PM 이벤트 순서 | suspend/resume 복원 실패 시 |
흔한 실패 패턴과 원인 추적
| 증상 | 흔한 원인 | 점검 포인트 |
|---|---|---|
| 백라이트 값은 200인데 화면은 꺼짐 | blank 상태가 유효 brightness를 0으로 만듦 | backlight_is_blank(), bl_power, suspend 상태 확인 |
| resume 후 밝기 단계가 리셋됨 | driver 재초기화 경로 누락 | BL_CORE_SUSPENDRESUME, update_status 재호출 확인 |
| 화면이 켜질 때 번쩍임 | panel보다 backlight를 먼저 켬 | prepare/enable와 backlight on 순서 확인 |
| 저밝기에서 심한 깜박임 | PWM 주파수 또는 듀티 테이블 부적절 | 주파수, 테이블, current sink 모드 재검토 |
| ACPI 밝기 키가 두 번 동작 | ACPI와 vendor 드라이버 모두 backlight 등록 | acpi_backlight= 커맨드라인으로 한 경로 고정 |
| PWM backlight 밝기 1단계가 너무 밝음 | brightness-levels 테이블이 선형 | 지각 보정 비선형 테이블 또는 num-interpolated-steps 적용 |
사용자 공간 도구와 라이브러리
Backlight를 제어하는 주요 사용자 공간 도구를 정리합니다.
| 도구 | 대상 | 주요 기능 | 설치 |
|---|---|---|---|
brightnessctl | backlight + LED | 밝기 조회/설정, 상대값 변경 (+5%, -10%) | 패키지 매니저 |
xbacklight | X11 backlight | X11 RandR 기반 백라이트 제어 | xorg-x11-utils |
light | backlight + LED | root 없이 밝기 변경 (udev 규칙 기반) | 패키지 매니저 |
ddcutil | 외부 모니터 | DDC/CI 프로토콜 밝기 제어 | ddcutil |
# brightnessctl 사용 예제
brightnessctl list # 모든 장치 나열
brightnessctl -d backlight set 50% # backlight 50%
brightnessctl -d backlight set +10% # 10% 증가
brightnessctl -d backlight set 10%- # 10% 감소
# udev 규칙으로 일반 사용자에게 backlight 권한 부여
# /etc/udev/rules.d/90-backlight.rules
# ACTION=="add", SUBSYSTEM=="backlight", RUN+="/bin/chgrp video /sys/class/backlight/%k/brightness"
# ACTION=="add", SUBSYSTEM=="backlight", RUN+="/bin/chmod g+w /sys/class/backlight/%k/brightness"
끝까지 따라가는 상태 전이 예제: 노트북 backlight hotkey와 suspend/resume
노트북 플랫폼에서 자주 보는 흐름을 따라가 보겠습니다.
- 부팅 시 등록
펌웨어 또는 GPU/패널 드라이버가 backlight 장치를 등록하고, 기본 brightness를 세팅합니다. - 사용자 공간이 밝기 120 설정
/sys/class/backlight/*/brightness에 120을 쓰면update_status()가 실행되어 PWM 듀티가 갱신됩니다. - 화면 끄기(blank)
DPMS 또는 suspend로 blank 상태가 오면backlight_is_blank()가 true가 되어 effective brightness는 0이 됩니다. - resume
BL_CORE_SUSPENDRESUME가 설정된 드라이버는update_status()를 다시 받아 이전 requested brightness 120을 안전한 시퀀스로 복원합니다. - 밝기 hotkey 입력
ACPI/firmware가 값을 바꾸면 드라이버는backlight_force_update(BACKLIGHT_UPDATE_HOTKEY)로 코어와 사용자 공간에 알립니다.
커널 소스 트리 탐색 가이드
Backlight 관련 소스 코드는 커널 소스 트리 여러 곳에 분산되어 있습니다. 기능별로 어디를 찾아봐야 하는지 정리합니다.
| 경로 | 내용 |
|---|---|
include/linux/backlight.h | Backlight core 구조체/API 정의 |
drivers/video/backlight/backlight.c | Backlight core 구현 |
drivers/video/backlight/ | backlight 드라이버 모음 (pwm_bl.c 등) |
Documentation/devicetree/bindings/leds/ | DT 바인딩 문서 (YAML) |
# Backlight 관련 소스 빠르게 검색하기
git grep -l 'backlight_device_register' drivers/
# backlight 드라이버 목록
ls drivers/video/backlight/
API 변천과 마이그레이션(Migration) 가이드
Backlight API는 시간이 지나며 여러 차례 변경되었습니다. 오래된 드라이버를 유지보수하거나 업스트림에 새 드라이버를 제출할 때 현재 권장 API를 사용해야 합니다.
| 구 API (deprecated/old) | 현재 권장 API | 변경 이유 |
|---|---|---|
backlight_device_register() | devm_backlight_device_register() | managed 등록, 해제 자동화 |
fb_blank 직접 비교 | backlight_is_blank() | blanking 정책 추상화 |
check_fb() | DRM panel 연결 | fbdev → DRM 전환 |
pwm_config() + pwm_enable() | pwm_apply_might_sleep() | 원자적 PWM 상태 변경 |
devm_backlight_device_register()를 사용하세요. backlight_device_register()를 사용하면 리뷰어가 managed 버전으로 변환을 요청할 것입니다.
LED class vs Backlight core 비교 요약
두 프레임워크의 핵심 차이를 한눈에 비교합니다. 자세한 LED 서브시스템 내용은 LED 서브시스템 문서를 참조하세요.
| 비교 항목 | LED class | Backlight core |
|---|---|---|
| 주요 목적 | 상태 표시, 알림, 장식 | 디스플레이 패널 가독성 |
| 핵심 구조체 | led_classdev | backlight_device |
| sysfs 경로 | /sys/class/leds/ | /sys/class/backlight/ |
| 밝기 의미 | 광원 자체의 밝기 | blank 상태 반영한 유효 밝기 |
| trigger 지원 | heartbeat, netdev, timer, pattern 등 | 없음 (외부 정책에 위임) |
| blanking | 없음 | 핵심 기능 (backlight_is_blank()) |
| hotkey 경로 | 해당 없음 | backlight_force_update() |
| 전원 관리 | LED_CORE_SUSPENDRESUME | BL_CORE_SUSPENDRESUME |
| DT 바인딩 | gpio-leds, pwm-leds | pwm-backlight, panel 연결 |
| 패널 시퀀스 | 해당 없음 | prepare/enable/disable 순서 필수 |
임베디드 보드 Backlight 설계 체크리스트
새 임베디드 보드에 backlight를 추가할 때 놓치기 쉬운 항목을 정리합니다.
| 단계 | 항목 | 확인 사항 |
|---|---|---|
| 하드웨어 설계 | PWM 주파수 | 패널 backlight는 최소 1kHz 이상 권장 (200Hz 미만은 저밝기에서 깜빡임 발생) |
| 전원 시퀀스 | regulator enable → panel prepare → panel enable → backlight on 순서. 데이터시트의 타이밍 요구사항 확인 | |
| Device Tree | 밝기 테이블 | 선형 테이블 대신 지각 보정 비선형 테이블 사용. num-interpolated-steps 활용 |
| 패널 연결 | panel 노드에 backlight = <&backlight> phandle 설정 | |
| 드라이버 | PM 플래그 | BL_CORE_SUSPENDRESUME 적절히 설정 |
| managed API | devm_backlight_device_register() 사용 | |
| 검증 | 저밝기 깜빡임 | 밝기 1단계에서 육안으로 깜빡임 없는지 확인 |
| suspend/resume | suspend 전 밝기가 resume 후 정확히 복원되는지 확인 | |
| white flash | 부팅과 resume 시 화면이 번쩍이지 않는지 확인 |
관련 문서와 참고 자료
- LED 서브시스템 — LED class, trigger, multicolor, flash LED 전체 프레임워크
- PWM — backlight를 실제로 구동하는 가장 흔한 하드웨어 수단
- GPU (DRM/KMS) — 패널과 backlight 시퀀스
- Framebuffer — fbdev 시대의 blanking/backlight 맥락
- Regulator 프레임워크 — backlight 전원 레일
- Device Tree —
pwm-backlight바인딩 - ACPI — 노트북 밝기 hotkey와 firmware backlight
- 디바이스 드라이버 — 드라이버 모델 기초와 probe/remove
- 전원 관리 — suspend/resume 전체 흐름
- Backlight Support Documentation — 커널 공식 backlight 서브시스템 API 문서입니다
- drivers/video/backlight/ (Bootlin Elixir) — backlight 드라이버 소스 코드를 온라인에서 탐색할 수 있습니다
- include/linux/backlight.h (Bootlin Elixir) — backlight_device, backlight_ops 구조체 정의를 확인할 수 있습니다
- pwm_bl.c (Bootlin Elixir) — pwm-backlight 드라이버 구현 소스를 확인할 수 있습니다
- DRM KMS Helpers Documentation — DRM 패널과 backlight 연동에 관한 헬퍼 API입니다
- PWM Framework Documentation — backlight 구동에 사용되는 PWM 프레임워크 API입니다
- LWN: The battle for the backlight — ACPI와 native backlight 간 우선순위 결정 문제를 다룹니다