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)까지 실무 기준으로 최대한 상세하게 다룹니다.

전제 조건: PWM, GPU (DRM/KMS), Device Tree, Regulator 프레임워크, 디바이스 드라이버 문서를 먼저 읽으세요. Backlight는 겉으로 보면 "밝기 하나 바꾸는 장치"처럼 보이지만, 실제로는 전원 시퀀스, 사용자 경험, blanking 상태, 패널 연동, 펌웨어(Firmware) hotkey 경로가 얽혀 있습니다.
일상 비유: Backlight는 실내 조명을 조절하는 디머에 가깝습니다. 디머는 "사람이 어떻게 느끼는가"가 중심이고, 단순 on/off가 아니라 blanking(화면 끄기), suspend/resume, hotkey 등 여러 경로에서 밝기가 달라질 수 있습니다. LED class가 "무엇을 알릴까"에 집중한다면, backlight는 "사람이 보기 좋은 화면 밝기"에 집중합니다.
LED와의 관계: Backlight는 LED 서브시스템과 완전히 독립된 프레임워크입니다. 둘 다 PWM을 사용할 수 있지만, sysfs ABI, 정책, 전원 시퀀스가 완전히 다릅니다. LED class의 trigger, blink, multicolor, flash 기능은 LED 서브시스템 문서를 참조하세요.

핵심 요약

  • Backlight core — 패널 밝기 제어를 위한 별도 프레임워크로, blanking, suspend/resume, hotkey 경로가 핵심입니다.
  • blanking 계약 — 사용자가 밝기 200을 설정해도 blank 상태면 effective brightness는 0이 됩니다. 이 차이가 backlight의 근본 특성입니다.
  • PWM은 수단일 뿐 — backlight가 PWM을 쓸 수 있지만, 상위 ABI와 정책은 LED class와 완전히 다릅니다.
  • 밝기 스케일 보정 — PWM 듀티와 체감 밝기는 선형이 아닙니다. 지각 보정 테이블이 제품 품질을 좌우합니다.
  • 품질 포인트 — 깜박임, 저조도 단계, resume 후 복원, white flash 방지가 실제 제품 완성도를 좌우합니다.

단계별 이해

  1. backlight인지 LED인지 먼저 나눕니다
    패널 조명이면 backlight, 상태 표시등이면 LED class가 맞습니다. 패널 조명을 LED class로 등록하면 blanking/hotkey 경로를 잃습니다.
  2. 하드웨어 제어 수단을 확인합니다
    PWM인지, GPU 레지스터인지, ACPI 인터페이스인지 파악합니다.
  3. backlight type을 결정합니다
    BACKLIGHT_RAW(직접 PWM 제어), BACKLIGHT_PLATFORM(vendor EC), BACKLIGHT_FIRMWARE(ACPI) 중 선택합니다.
  4. 전원/blanking 시퀀스를 맞춥니다
    panel prepare/enable/disable/unprepare와 순서를 잘못 맞추면 white flash와 잔광이 생깁니다.
  5. 사람의 눈으로 검증합니다
    계측상 선형 밝기라도 체감상은 비선형이므로, 밝기 테이블과 PWM 주파수를 실제 패널로 검증해야 합니다.

Backlight core: brightness 숫자보다 blanking 계약이 더 중요하다

Backlight core의 핵심은 struct backlight_devicestruct 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_SUSPENDRESUMEsuspend/resume 시 update_status 호출재초기화가 필요한 장치에 유용
backlight_is_blank()display blank 상태 판정driver가 직접 props를 조합하지 말고 helper 사용
backlight_get_brightness()blank 상태 반영한 effective brightnessupdate_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)의 일부로 동작합니다. 아래 다이어그램은 사용자 공간에서 하드웨어까지 밝기 변경 요청이 어떻게 흘러가는지 보여 줍니다.

백라이트 스택 전체 구조 sysfs / 데스크톱 도구 brightnessctl, xbacklight, gnome-power-manager ACPI / 펌웨어 Hotkey Fn+밝기 키, EC notify, WMI 이벤트 Backlight Core (drivers/video/backlight/backlight.c) backlight_update_status() backlight_get_brightness() backlight_force_update() pwm-backlight BACKLIGHT_RAW, PWM + enable GPIO GPU 백라이트 BACKLIGHT_RAW, i915/amdgpu 레지스터 ACPI / Vendor 백라이트 BACKLIGHT_FIRMWARE / PLATFORM DRM/KMS 패널 연동 drm_panel_enable() / drm_panel_disable() — backlight on/off 시퀀스 조율 하드웨어 PWM 출력, enable GPIO, regulator, ACPI _BCM/_BCL, GPU 밝기 레지스터 backlight_force_update()로 firmware hotkey 변경을 sysfs와 동기화합니다

완전한 백라이트 드라이버 예제

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() 설계 패턴: update_status()는 "현재 속성 상태를 하드웨어에 반영하라"는 의미입니다. 이 콜백 안에서 backlight_get_brightness()를 호출하면 blank 상태가 자동으로 반영된 effective brightness를 얻습니다. 드라이버는 이 값이 0이면 "꺼야 한다", 양수이면 "해당 밝기로 켜야 한다"고 판단하기만 하면 됩니다.

pwm-backlight 드라이버 내부: 표준 백라이트의 핵심

커널에 내장된 pwm_bl.c 드라이버는 대부분의 임베디드 시스템에서 표준 백라이트 역할을 합니다. 이 드라이버가 처리하는 핵심 로직을 분석합니다.

DT 프로퍼티역할기본값
pwmsPWM 장치, 채널, 주기, 극성 지정필수
brightness-levels사용자 밝기 단계를 PWM 듀티로 매핑하는 테이블없으면 0~max 선형
default-brightness-level부팅 시 초기 밝기 인덱스테이블 길이의 절반
num-interpolated-steps테이블 항목 간 보간 단계 수보간 없음
power-supply백라이트 전원 regulator 참조dummy regulator
enable-gpios백라이트 활성화 GPIO없으면 사용 안 함
post-pwm-on-delay-msPWM 시작 후 enable GPIO 전 대기 시간0
pwm-off-delay-msenable 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>;
};
num-interpolated-steps의 효과: brightness-levels에 22개 항목을 넣고 num-interpolated-steps를 8로 설정하면, 드라이버는 인접 항목 사이에 8단계를 선형 보간하여 총 169단계의 밝기를 제공합니다. 이렇게 하면 DT에는 핵심 곡선 포인트만 적고, 세밀한 제어는 드라이버가 자동 보간하므로 DT 크기와 제어 정밀도를 동시에 달성할 수 있습니다.
backlight core가 brightness를 해석하는 방법 requested brightness /sys/class/backlight/*/brightness blank state power, fb_blank, state backlight_get_brightness() blank면 0, 아니면 requested update_status() PWM/regulator/hardware write 핵심 규칙 driver는 props를 직접 조합하기보다 backlight_is_blank() / backlight_get_brightness()를 사용해야 합니다. fb_blank는 deprecated 경로이고, blanking 정책을 직접 재구현하면 suspend/resume에서 쉽게 어긋납니다.

ACPI/Firmware Backlight: 노트북 플랫폼의 밝기 제어 경로

노트북과 일체형 PC에서는 backlight를 순수 커널 드라이버만으로 제어하는 경우가 드뭅니다. 대부분 ACPI/UEFI 펌웨어가 밝기 제어 인터페이스를 제공하고, 커널은 이를 BACKLIGHT_FIRMWARE 타입으로 감싸서 사용자 공간에 노출합니다. 이때 가장 흔한 문제는 여러 backlight 장치가 동시에 등록되는 충돌입니다.

밝기 제어 경로backlight type대표 드라이버우선순위(Priority)
ACPI _BCM/_BCLBACKLIGHT_FIRMWAREacpi_video일반적으로 기본
vendor-specific ECBACKLIGHT_PLATFORMthinkpad_acpi, asus-wmi, dell-laptopvendor 특화
GPU 레지스터 직접 제어BACKLIGHT_RAWi915, 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'
노트북 밝기 키 이슈: 밝기 조절 hotkey가 안 먹히는 경우 대부분 backlight type 충돌이 원인입니다. /sys/class/backlight/에 장치가 2개 이상 보이면, 사용자 공간 데몬(GNOME power-daemon 등)이 잘못된 장치에 값을 쓰고 있을 수 있습니다. acpi_backlight=nativeacpi_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>;
선형 PWM 듀티 vs 지각 밝기 선형 테이블 (문제 있음) PWM 단계 (0 ~ max) 체감 밝기 PWM 듀티 (선형) 체감 밝기 (로그) 저밝기 구간 차이가 거의 없음 지각 보정 테이블 (권장) 사용자 단계 (0 ~ max) 체감 밝기 체감 밝기 (균등) PWM 듀티 (비선형) 각 단계가 체감상 균등하게 느껴짐
CIE 1931 밝기 보정: CIE 1931 표준은 인간의 밝기 감각을 수식으로 모델링합니다. 커널에서 직접 이 수식을 적용하기보다는, 미리 계산된 룩업 테이블을 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 onPWM/enable GPIO/regulator 활성화순서가 빠르면 white flash, 느리면 첫 프레임이 어둡게 시작
blank/suspendbacklight 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>;
};
panel + backlight 올바른 시퀀스 regulator on 전원 준비 panel prepare reset/초기 명령 panel enable 픽셀 출력 시작 backlight on PWM/enable GPIO 사용자 체감 화면 첫 프레임부터 안정적으로 보임 잘못된 순서 예 PWM을 panel enable보다 먼저 켜면 패널이 준비되지 않은 동안 백라이트만 먼저 켜져 white flash가 생길 수 있습니다. 반대로 suspend에서 backlight를 늦게 끄면 패널은 꺼졌는데 조명만 남아 잔광처럼 보일 수 있습니다.

밝기 테이블도 중요합니다. brightness-levels를 선형으로 잡으면 저밝기 구간이 뭉개지고, 사용자는 "1단계에서 너무 갑자기 밝아진다"고 느낄 수 있습니다. 그래서 많은 제품이 비선형 또는 지각(perceptual) 보정 테이블을 씁니다. 헤더에도 backlight scale로 BACKLIGHT_SCALE_LINEARBACKLIGHT_SCALE_NON_LINEAR가 정의되어 있습니다.

커널 빌드 설정: Backlight Kconfig 옵션

Backlight 서브시스템을 사용하려면 커널 빌드 시 관련 Kconfig 옵션을 활성화해야 합니다.

Kconfig 옵션역할의존성 / 비고
CONFIG_BACKLIGHT_CLASS_DEVICEBacklight class 지원LED와 독립. 별도 서브시스템
CONFIG_BACKLIGHT_PWMPWM backlight 드라이버BACKLIGHT_CLASS_DEVICE + PWM
CONFIG_BACKLIGHT_GPIOGPIO backlight 드라이버BACKLIGHT_CLASS_DEVICE + GPIOLIB
# Backlight 서브시스템
CONFIG_BACKLIGHT_CLASS_DEVICE=y
CONFIG_BACKLIGHT_PWM=m
임베디드 최소 구성: PWM backlight만 필요한 임베디드 보드라면 CONFIG_BACKLIGHT_CLASS_DEVICECONFIG_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>/)
brightnessrw정수요청된 밝기 (requested)
actual_brightnessr정수하드웨어 실제 밝기
max_brightnessr정수최대 밝기값
bl_powerrw정수전원 상태 (0=unblank, 4=blank)
typer문자열backlight 유형 (raw, platform, firmware)
scaler문자열밝기 스케일 (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 드라이버의 품질은 하드웨어 제어 자체보다 올바른 콜백 의미 준수와 전원 시퀀스 설계에서 갈립니다.

  1. backlight에서는 blank helper를 신뢰합니다
    backlight_is_blank(), backlight_get_brightness()를 사용해 effective brightness를 계산합니다.
  2. 전원 시퀀스를 도구화합니다
    regulator, enable GPIO, PWM enable/disable 순서를 명확한 helper로 묶어 두면 white flash를 줄이기 쉽습니다.
  3. 외부 변경 경로를 반영합니다
    firmware hotkey면 backlight_force_update()를 검토합니다.
  4. managed API를 사용합니다
    devm_backlight_device_register()로 등록하면 해제 코드가 필요 없습니다.
  5. 밝기 스케일을 명시합니다
    backlight_properties.scaleBACKLIGHT_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 드라이버가 직접 관리
resume 순서 주의: backlight의 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 flashpanel보다 backlight를 먼저 켬panel enable 이후 backlight on

노트북처럼 펌웨어가 backlight를 일부 통제하는 플랫폼에서는 커널 드라이버, ACPI hotkey, 사용자 공간 power daemon이 동시에 영향을 줄 수 있습니다. 이런 환경에서는 "누가 최종 권한자냐"를 정하지 않으면 밝기 튐이나 race가 생깁니다.

디버깅 절차: sysfs, PWM 파형, blank 상태, panel 시퀀스

Backlight 문제는 대부분 다음 네 층 중 하나에서 발생합니다.

  1. 클래스 계층
    장치가 제대로 등록되었는가, 올바른 sysfs 경로가 생겼는가
  2. 정책 계층
    hotkey, blanking, suspend/resume 로직이 값을 덮어쓰지 않는가
  3. 하드웨어 제어 계층
    PWM duty/frequency, enable GPIO, regulator가 실제로 움직이는가
  4. 패널 타이밍 계층
    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 시퀀스 문제일 가능성이 큽니다.

계측 포인트: backlight 품질 이슈는 sysfs만으로 끝나지 않습니다. 오실로스코프로 PWM 주파수와 듀티를 보고, 패널 enable 신호와 backlight enable 신호가 어떤 순서로 바뀌는지 시간축에서 비교해야 원인을 정확히 잡을 수 있습니다.

고급 디버깅: 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/pwmPWM 주기, 듀티, 활성 상태밝기가 예상과 다를 때
/sys/kernel/debug/gpioenable GPIO 상태backlight가 완전히 안 켜질 때
/sys/kernel/debug/regulator/전원 레일 on/off 상태전원 시퀀스 문제 의심 시
ftrace backlight_*backlight 함수 호출 순서/타이밍resume/blank 시퀀스 문제
pm_debug_messagesPM 이벤트 순서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를 제어하는 주요 사용자 공간 도구를 정리합니다.

도구대상주요 기능설치
brightnessctlbacklight + LED밝기 조회/설정, 상대값 변경 (+5%, -10%)패키지 매니저
xbacklightX11 backlightX11 RandR 기반 백라이트 제어xorg-x11-utils
lightbacklight + LEDroot 없이 밝기 변경 (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

노트북 플랫폼에서 자주 보는 흐름을 따라가 보겠습니다.

  1. 부팅 시 등록
    펌웨어 또는 GPU/패널 드라이버가 backlight 장치를 등록하고, 기본 brightness를 세팅합니다.
  2. 사용자 공간이 밝기 120 설정
    /sys/class/backlight/*/brightness에 120을 쓰면 update_status()가 실행되어 PWM 듀티가 갱신됩니다.
  3. 화면 끄기(blank)
    DPMS 또는 suspend로 blank 상태가 오면 backlight_is_blank()가 true가 되어 effective brightness는 0이 됩니다.
  4. resume
    BL_CORE_SUSPENDRESUME가 설정된 드라이버는 update_status()를 다시 받아 이전 requested brightness 120을 안전한 시퀀스로 복원합니다.
  5. 밝기 hotkey 입력
    ACPI/firmware가 값을 바꾸면 드라이버는 backlight_force_update(BACKLIGHT_UPDATE_HOTKEY)로 코어와 사용자 공간에 알립니다.
상태 전이: requested brightness와 effective brightness boot req=80 / eff=80 userspace write req=120 / eff=120 blank/suspend req=120 / eff=0 resume req=120 / eff=120 hotkey update req=90 / eff=90 requested 값과 effective 값이 다를 수 있다는 사실을 이해하면 backlight 디버깅 절반은 이미 끝난다.

커널 소스 트리 탐색 가이드

Backlight 관련 소스 코드는 커널 소스 트리 여러 곳에 분산되어 있습니다. 기능별로 어디를 찾아봐야 하는지 정리합니다.

경로내용
include/linux/backlight.hBacklight core 구조체/API 정의
drivers/video/backlight/backlight.cBacklight 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 classBacklight core
주요 목적상태 표시, 알림, 장식디스플레이 패널 가독성
핵심 구조체led_classdevbacklight_device
sysfs 경로/sys/class/leds//sys/class/backlight/
밝기 의미광원 자체의 밝기blank 상태 반영한 유효 밝기
trigger 지원heartbeat, netdev, timer, pattern 등없음 (외부 정책에 위임)
blanking없음핵심 기능 (backlight_is_blank())
hotkey 경로해당 없음backlight_force_update()
전원 관리LED_CORE_SUSPENDRESUMEBL_CORE_SUSPENDRESUME
DT 바인딩gpio-leds, pwm-ledspwm-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 APIdevm_backlight_device_register() 사용
검증저밝기 깜빡임밝기 1단계에서 육안으로 깜빡임 없는지 확인
suspend/resumesuspend 전 밝기가 resume 후 정확히 복원되는지 확인
white flash부팅과 resume 시 화면이 번쩍이지 않는지 확인