LED / Backlight 서브시스템

리눅스 LED class와 backlight core를 "둘 다 밝기를 다루지만 계약(contract)이 완전히 다른 프레임워크"라는 관점에서 정리합니다. led_classdev, trigger, hardware blink 오프로딩, multicolor LED, flash LED, backlight_device, backlight_ops, pwm-backlight, DRM 패널 연동, sysfs ABI, Device Tree, 전원 관리, hotkey/firmware 경로, 깜박임과 white flash 디버깅까지 실무 기준으로 최대한 상세하게 다룹니다.

전제 조건: PWM, GPU (DRM/KMS), Device Tree, Regulator 프레임워크, 디바이스 드라이버 문서를 먼저 읽으세요. LED와 backlight는 겉으로 보면 "밝기 하나 바꾸는 장치"처럼 보이지만, 실제로는 전원 시퀀스, 사용자 경험, 하드웨어 오프로딩, 패널 blanking, 펌웨어 hotkey 경로가 얽혀 있습니다.
일상 비유: LED class는 건물의 상태 표시등이고, backlight는 실내 조명을 조절하는 디머에 가깝습니다. 상태 표시등은 "무엇을 알릴까"가 중심이고, 실내 조명은 "사람이 어떻게 느끼는가"가 중심입니다. 그래서 둘 다 밝기 숫자를 쓰지만 정책과 실패 양상이 다릅니다.

핵심 요약

  • LED class — 단색 LED, RGB LED, 키보드 LED, 충전 표시등처럼 상태 표현이 중심인 광원을 위한 공통 프레임워크입니다.
  • Trigger — heartbeat, disk-activity, netdev 같은 커널 이벤트를 LED 동작과 연결하는 얇은 정책 계층입니다.
  • Multicolor / Flash — RGB LED와 카메라 플래시는 일반 LED보다 더 풍부한 모델이 필요해 별도 class 확장을 갖습니다.
  • Backlight core — 패널 밝기 제어를 위한 별도 프레임워크로, blanking, suspend/resume, hotkey 경로가 핵심입니다.
  • PWM은 수단일 뿐 — LED와 backlight가 둘 다 PWM을 쓸 수 있지만, 상위 ABI와 정책은 완전히 다릅니다.
  • 품질 포인트 — 깜박임, 저조도 단계, resume 후 복원, white flash 방지가 실제 제품 완성도를 좌우합니다.

단계별 이해

  1. 무엇을 제어하는지 먼저 나눈다
    상태 표시등인지, 패널 조명인지, 카메라 플래시인지 먼저 분리해야 맞는 class를 선택할 수 있습니다.
  2. 하드웨어 제어 수단을 확인한다
    GPIO on/off인지, PWM인지, current sink 레지스터인지, 외부 LED 컨트롤러인지 파악합니다.
  3. 정책이 커널 쪽인지 사용자 공간 쪽인지 결정한다
    heartbeat/netdev처럼 커널 event trigger가 필요한지, 데스크톱 밝기 키처럼 사용자 공간 정책이 필요한지 구분합니다.
  4. 전원/blanking 시퀀스를 맞춘다
    특히 backlight는 panel prepare/enable/disable/unprepare와 순서를 잘못 맞추면 white flash와 잔광이 생깁니다.
  5. 사람의 눈으로 검증한다
    계측상 선형 밝기라도 체감상은 비선형이므로, 밝기 테이블과 PWM 주파수를 실제 패널/LED로 검증해야 합니다.

LED class, Flash LED, Backlight는 왜 따로 존재하는가

세 프레임워크는 모두 빛을 낸다는 점에서는 비슷하지만, 커널이 모델링해야 하는 책임이 다릅니다. 일반 LED는 "상태 표시", flash LED는 "시간 제한이 있는 고강도 strobe", backlight는 "디스플레이 가독성"이 중심입니다. 이 차이를 무시하고 전부 GPIO/PWM 토글로 처리하면, 사용자 공간 ABI, suspend/resume, hotkey, 전원 시퀀스가 금세 뒤엉킵니다.

프레임워크대표 장치핵심 객체중심 관심사대표 ABI
LED classstatus LED, RGB, 키보드 LEDstruct led_classdev밝기, 점멸, trigger, 상태 표현/sys/class/leds/*
Flash LED class카메라 torch/flashstruct led_classdev_flashstrobe, timeout, fault, 고전류 보호LED sysfs 확장 + V4L2 연계
Backlight coreLCD/eDP/MIPI 패널 조명struct backlight_deviceblanking, hotkey, panel UX, 전원 시퀀스/sys/class/backlight/*
빛을 내는 하드웨어를 커널이 나누는 방법 LED class 상태 표시, trigger, 단색/RGB, 하드웨어 blink 정책: 무엇을 알릴 것인가 Flash LED class torch / flash / strobe / timeout / fault 정책: 짧고 강한 광원 제어 Backlight core panel blanking, hotkey, suspend/resume, UX 정책: 사람이 보기 좋은 화면 밝기 공통 하드웨어 수단 GPIO on/off, PWM, current sink, regulator, enable GPIO, I2C LED controller 같은 PWM을 써도 상위 프레임워크가 다르면 sysfs ABI와 power sequence가 달라진다
빠른 선택 규칙: 카메라 플래시를 일반 LED로 모델링하면 timeout/fault/strobe를 잃고, 패널 조명을 LED class로 모델링하면 blanking/hotkey/가독성 정책을 잃습니다. class 선택은 단순 이름 문제가 아니라 ABI 계약의 선택입니다.

같은 밝기라도 계약이 다르다: 상태 표현, 광원 제어, 디스플레이 UX

LED와 backlight를 함께 설계할 때 가장 중요한 관점은 "밝기 숫자 하나가 같은 의미가 아니다"는 점입니다.

그래서 backlight 코어는 backlight_is_blank(), backlight_get_brightness() 같은 helper를 제공하고, LED class는 반대로 trigger와 blink/pattern 계열 API에 힘을 줍니다. 이것이 같은 PWM 하드웨어를 공유하더라도 상위 프레임워크를 따로 둔 이유입니다.

LED class 핵심 구조체와 이름 규칙

LED class의 중심은 struct led_classdev입니다. 헤더를 보면 이름, 현재 밝기, 최대 밝기, color, flags, brightness set/get 콜백, blink callback, pattern callback, default trigger, trigger 관련 하드웨어 오프로딩 콜백까지 매우 많은 필드를 가집니다.

헤더와 공식 문서가 강조하는 이름 규칙도 중요합니다. LED 이름은 보통 <devicename:color:function> 또는 <color:function> 형태를 따르며, 새 드라이버는 가능하면 fwnode/DT 정보와 led_init_data를 이용해 코어가 이름을 조합하도록 두는 편이 좋습니다. 헤더에 적혀 있듯 default_label은 하위 호환용에 가깝고, 새 드라이버는 하드코딩 이름보다 colorfunction 중심 구성을 선호해야 합니다.

필드/콜백의미실무 포인트
nameLED sysfs 경로 이름가능하면 코어가 fwnode 기반으로 조합하게 둠
brightness_setsleep 불가 brightness 변경GPIO처럼 빠른 경로에 적합
brightness_set_blockingsleep 가능 brightness 변경I2C/SPI LED 컨트롤러면 보통 이쪽이 맞음
brightness_get현재 밝기 읽기외부 하드웨어 변화가 있으면 읽기 경로 중요
blink_set하드웨어 blink 설정지원하면 CPU 소모를 줄일 수 있음
pattern_set / pattern_clear패턴 재생RGB나 알림 LED에 유용
default_trigger초기 triggerheartbeat, disk-activity 등 자동 정책 시작점
flags동작 보조 플래그LED_CORE_SUSPENDRESUME, LED_RETAIN_AT_SHUTDOWN, LED_PANIC_INDICATOR
주요 flag의미언제 유용한가
LED_CORE_SUSPENDRESUMEsuspend/resume 동안 코어가 상태 복원을 도와줌절전 복귀 뒤 LED 상태가 중요할 때
LED_RETAIN_AT_SHUTDOWNshutdown 때 상태 유지BMC, 유지보수 표시등, 종료 후에도 남겨야 하는 상태 LED
LED_PANIC_INDICATORpanic 상황의 가시성 확보헤드리스 장비나 랙 서버
LED_HW_PLUGGABLE핫플러그 장치 성격 표시USB/NIC 등 이름 구성과 정책이 장치 인스턴스에 의존할 때
LED_BRIGHT_HW_CHANGED하드웨어가 밝기를 바꿀 수 있음을 표현펌웨어/외부 컨트롤러와 공동 제어하는 LED
#include <linux/leds.h>

struct board_led {
    struct led_classdev cdev;
    struct regmap *regmap;
};

static int board_led_set_blocking(struct led_classdev *cdev,
                                  enum led_brightness brightness)
{
    struct board_led *led = container_of(cdev, struct board_led, cdev);
    /* I2C/SPI 레지스터 쓰기처럼 sleep 가능한 경로 */
    return regmap_write(led->regmap, 0x10, brightness);
}

static int board_led_blink_set(struct led_classdev *cdev,
                               unsigned long *delay_on,
                               unsigned long *delay_off)
{
    /* 하드웨어가 정확히 못 맞추면 가장 가까운 값으로 보정해 되돌려 준다 */
    if (!*delay_on && !*delay_off) {
        *delay_on = 125;
        *delay_off = 125;
    }
    return 0;
}

static int board_led_probe(struct platform_device *pdev)
{
    struct board_led *led;
    struct led_init_data init_data = {
        .fwnode = dev_fwnode(&pdev->dev),
        .devicename = "board0",
    };

    led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
    if (!led)
        return -ENOMEM;

    led->cdev.max_brightness = 255;
    led->cdev.brightness_set_blocking = board_led_set_blocking;
    led->cdev.blink_set = board_led_blink_set;
    led->cdev.default_trigger = "heartbeat";
    led->cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;

    return devm_led_classdev_register_ext(&pdev->dev, &led->cdev, &init_data);
}
LED class 동작 흐름 userspace / trigger brightness, trigger, delay_on/off LED core led_classdev, naming, flags driver callbacks brightness_set, blink_set, get hardware GPIO / PWM / sink 하드웨어 오프로딩이 있는 경우 LED core는 hw_control_is_supported(), hw_control_set()로 소프트웨어 trigger를 하드웨어 blink로 내릴 수 있다. 지원하지 않으면 소프트웨어 fallback으로 같은 ABI를 유지한다.

brightness_set는 sleep하면 안 되고, brightness_set_blocking은 sleep 가능한 경로입니다. 레지스터 접근이 I2C/SPI 전송을 동반한다면 거의 항상 blocking 변형이 더 맞습니다. 이 구분을 무시하면 atomic context에서 sleep하거나, 반대로 needless workqueue를 만들게 됩니다.

헤더에는 LED_BRIGHT_HW_CHANGEDled_classdev_notify_brightness_hw_changed()도 있습니다. 외부 컨트롤러나 펌웨어가 LED 밝기를 바꿀 수 있는 장치라면, 커널은 이 경로로 사용자 공간에 상태 변화를 알려야 실제 값과 sysfs 캐시가 어긋나지 않습니다.

Trigger 모델: heartbeat에서 hardware blink 오프로딩까지

LED trigger는 커널 이벤트를 LED 동작과 연결하는 정책 계층입니다. 가장 단순한 형태는 heartbeatdisk-activity처럼 정해진 패턴을 LED에 입히는 것이고, 더 복잡한 형태는 netdev, cpu, audio, panic, camera trigger처럼 도메인 지식을 가진 trigger입니다.

헤더를 보면 trigger는 struct led_trigger로 모델링되며, activate/deactivate 콜백, brightness 값, attribute group, LED 목록을 가집니다. simple trigger는 led_trigger_register_simple() 계열로, 복잡한 trigger는 led_trigger_register()로 등록합니다.

대표 trigger용도자주 보이는 부가 속성
heartbeat시스템 생존 표시보통 추가 속성 없음
disk-activity블록 I/O 활동 표시단순 on/off 또는 blink
timer주기 점멸delay_on, delay_off
netdev링크/트래픽/속도 표시인터페이스 선택, RX/TX 모드
panic커널 패닉 가시성 확보panic indicator LED와 조합
camera카메라 플래시/indicator 연동flash LED class와 밀접
# LED 목록과 기본 상태
ls /sys/class/leds/
cat /sys/class/leds/board0:green:status/brightness
cat /sys/class/leds/board0:green:status/max_brightness

# 사용 가능한 trigger 확인
cat /sys/class/leds/board0:green:status/trigger

# timer trigger 적용
echo timer > /sys/class/leds/board0:green:status/trigger
echo 100 > /sys/class/leds/board0:green:status/delay_on
echo 100 > /sys/class/leds/board0:green:status/delay_off

고급 장치에서는 trigger를 하드웨어로 내릴 수 있습니다. led_classdev에는 hw_control_is_supported(), hw_control_set(), hw_control_get(), hw_control_get_device()가 있으며, PHY LED나 스마트 LED 컨트롤러처럼 하드웨어가 자체 blink/link indication을 할 수 있는 경우 CPU 개입 없이 LED를 구동할 수 있습니다. 지원되지 않으면 코어가 소프트웨어 fallback을 선택할 수 있으므로, 같은 trigger ABI를 유지하면서 구현만 바뀌는 것이 이 모델의 장점입니다.

현장 판단: 상태 LED가 매우 빈번한 이벤트에 반응해야 하거나 절전 상태에서도 살아 있어야 한다면, software blink보다 hardware offload가 훨씬 낫습니다. 반대로 패턴이 자주 바뀌고 하드웨어 제약이 많다면 software trigger가 단순합니다.

Multicolor LED: RGB를 "LED 3개"가 아니라 "하나의 광원"으로 다루기

RGB LED를 각각 독립된 단색 LED로만 노출하면 사용자 공간은 색 혼합의 의미를 알기 어렵고, 전역 brightness와 채널별 intensity를 분리하기 힘듭니다. 이를 위해 커널은 struct led_classdev_mcstruct mc_subled를 제공합니다.

필드의미
num_colorsRGB/RGBW 등 서브 LED 수
subled_info[]각 채널의 color index, brightness, intensity, hardware channel
led_mc_calc_color_components()전역 brightness와 색 비율을 각 서브 채널 값으로 분해
#include <linux/led-class-multicolor.h>

static struct mc_subled rgb_subleds[] = {
    { .color_index = LED_COLOR_ID_RED,   .channel = 0, .intensity = 255 },
    { .color_index = LED_COLOR_ID_GREEN, .channel = 1, .intensity = 128 },
    { .color_index = LED_COLOR_ID_BLUE,  .channel = 2, .intensity = 32  },
};

static struct led_classdev_mc rgb_led = {
    .led_cdev = {
        .max_brightness = 255,
    },
    .num_colors = ARRAY_SIZE(rgb_subleds),
    .subled_info = rgb_subleds,
};

static int rgb_apply(struct led_classdev_mc *mcled)
{
    return led_mc_calc_color_components(mcled,
                                        mcled->led_cdev.brightness);
}

Multicolor class는 사용자 공간이 "이 LED는 빨강 100%, 초록 50%, 파랑 12% 비율의 하나의 RGB 광원"으로 사고하게 도와줍니다. 키보드 zone, RGB status bar, 장식용 조명처럼 색 의미가 중요한 제품에서는 이 추상화가 일반 LED 3개보다 훨씬 낫습니다.

Flash LED: torch, strobe, timeout, fault

카메라 플래시는 일반 LED보다 훨씬 엄격한 모델을 요구합니다. 밝기뿐 아니라 strobe on/off, 최대 timeout, 과전류/과열 fault, torch 모드와 flash 모드의 분리가 필요합니다. 이를 위해 커널은 struct led_classdev_flashstruct led_flash_ops를 제공합니다.

요소의미
flash_brightness_set/get플래시 광량 설정과 읽기. 단위는 보통 마이크로암페어
strobe_set/get플래시 점등 시작/상태 조회
timeout_setstrobe 지속 시간 제어. 단위는 마이크로초
fault_get과전압, 과온, 쇼트, 과전류 등 fault 비트 조회
#include <linux/led-class-flash.h>

static const struct led_flash_ops cam_flash_ops = {
    .flash_brightness_set = cam_flash_brightness_set,
    .flash_brightness_get = cam_flash_brightness_get,
    .strobe_set = cam_flash_strobe_set,
    .strobe_get = cam_flash_strobe_get,
    .timeout_set = cam_flash_timeout_set,
    .fault_get = cam_flash_fault_get,
};

static struct led_classdev_flash cam_flash = {
    .led_cdev = {
        .max_brightness = 255,
    },
    .ops = &cam_flash_ops,
    .brightness = { .min = 50000, .max = 800000, .step = 50000, .val = 200000 },
    .timeout = { .min = 10000, .max = 200000, .step = 10000, .val = 80000 },
};

일반 상태 LED와 camera flash를 하나의 brightness 파일로 합쳐 버리면, 카메라 스택이나 안전 보호 로직이 필요한 정보를 잃게 됩니다. 헤더에도 LED_FAULT_OVER_VOLTAGE, LED_FAULT_TIMEOUT, LED_FAULT_OVER_TEMPERATURE 같은 fault 비트가 따로 정의되어 있습니다. 즉, flash LED class는 단순 광원 제어가 아니라 제한 시간 내 고에너지 장치를 안전하게 다루는 프레임워크입니다.

특수 LED class 확장 Multicolor LED 하나의 RGB 광원 = 전역 brightness + 채널별 intensity subled: R/G/B/W 채널 매핑 용도: 키보드 zone, RGB 상태등, 장식 조명 RED GREEN BLUE Flash LED torch / flash / strobe / timeout / fault 용도: 카메라 보조광, 플래시 고전류 보호와 타임아웃이 필수 fault: OVP, OCP, timeout, over-temp RGB LED와 camera flash는 모두 "LED"지만 필요한 상태와 제약이 달라서 일반 led_classdev만으로는 모델이 부족하다.

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가 바뀌었다는 사실을 반영할 수 있습니다.

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에서 쉽게 어긋난다.

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 예시 */
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가 정의되어 있습니다.

sysfs ABI와 사용자 공간 제어

LED class와 backlight는 사용자 공간에서 완전히 다른 sysfs 경로를 가집니다.

경로대표 파일의미
/sys/class/leds/<name>/brightness, max_brightness, trigger상태 LED 및 trigger 제어
/sys/class/backlight/<name>/brightness, actual_brightness, max_brightness, bl_power, type패널 밝기와 blank 상태 제어
# LED 수동 제어
echo 255 > /sys/class/leds/board0:green:status/brightness
echo none > /sys/class/leds/board0:green:status/trigger

# 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만 보는 프레임워크가 아닙니다.

Device Tree 바인딩: gpio-leds, pwm-leds, pwm-backlight, panel 연결

LED와 backlight는 DT에서 자주 등장합니다. 중요한 점은 단순히 GPIO/PWM 번호만 적는 것이 아니라, 이름, 색, 기능, 기본 상태, trigger, 전원, 패널 연결까지 ABI로 고정한다는 것입니다.

/* GPIO LED */
leds {
    compatible = "gpio-leds";

    status_led: led-0 {
        gpios = <&gpio1 5 1>;
        color = <LED_COLOR_ID_GREEN>;
        function = LED_FUNCTION_STATUS;
        linux,default-trigger = "heartbeat";
        default-state = "off";
    };
};

/* PWM LEDs */
pwmleds {
    compatible = "pwm-leds";

    kbd_led: kbd-backlight {
        pwms = <&pwm1 0 50000 0>;
        max-brightness = <255>;
    };
};

/* 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>;
};

LED 바인딩에서는 color, function, linux,default-trigger, default-state가 중요한 의미를 갖습니다. 이름 규칙을 장치 트리에서 구조화해 두면, 드라이버가 불필요하게 문자열을 하드코딩하지 않아도 됩니다.

DT에서의 LED / Backlight 연결 gpio-leds / pwm-leds color, function, default-trigger status / keyboard / charging pwm-backlight pwms, brightness-levels, enable-gpios power-supply, default level panel node backlight = <&backlight> DRM panel/bridge 연결 LED 이름/기능은 사용자 공간에 그대로 보이므로, DT의 color/function 선택은 사실상 ABI 설계다. backlight 노드는 panel과 연결되어 display pipeline의 일부가 된다.

드라이버 구현 패턴: 어떤 class를 선택하고 어떤 콜백을 써야 하나

LED/backlight 드라이버의 품질은 하드웨어 제어 자체보다 올바른 프레임워크 선택과 콜백 의미 준수에서 갈립니다.

  1. sleep 가능 여부부터 결정한다
    sleep 가능 레지스터 접근이면 brightness_set_blocking, 아니면 brightness_set가 맞습니다.
  2. 이름을 하드코딩하지 말고 구조화한다
    led_init_data와 fwnode를 이용해 color:function 또는 devicename:color:function 이름을 조합하는 편이 좋습니다.
  3. class를 올바르게 고른다
    RGB면 multicolor, camera flash면 flash class, panel 조명이면 backlight로 가야 합니다.
  4. backlight에서는 blank helper를 신뢰한다
    backlight_is_blank(), backlight_get_brightness()를 사용해 effective brightness를 계산합니다.
  5. 전원 시퀀스를 도구화한다
    regulator, enable GPIO, PWM enable/disable 순서를 명확한 helper로 묶어 두면 white flash를 줄이기 쉽습니다.
  6. 외부 변경 경로를 반영한다
    firmware hotkey면 backlight_force_update(), 하드웨어 밝기 변화면 led_classdev_notify_brightness_hw_changed()를 검토합니다.
  7. sysfs 잠금/차단도 고려한다
    특수 상황에서는 led_sysfs_disable() / led_sysfs_enable()로 임시 제어권을 정리할 수 있습니다.

hot-pluggable 장치는 led_init_data.devname_mandatory를 켜서 이름에 device section이 반드시 들어가도록 하는 것이 안전합니다. 키보드, USB 장치, NIC처럼 나중에 꽂히는 장치가 status:green 같은 짧은 이름을 독점하면 사용자 공간이 혼란스러워집니다.

전원 관리와 사용자 경험: 깜박임, 저조도 단계, suspend/resume

사람의 눈은 선형이 아닙니다. PWM 듀티를 0, 32, 64, 96, 128처럼 선형으로 나눠도 체감 밝기는 선형이 아니고, 저조도 영역에서는 단계가 거의 구분되지 않거나 반대로 첫 단계가 너무 밝게 느껴질 수 있습니다. 그래서 backlight는 밝기 테이블과 BACKLIGHT_SCALE_NON_LINEAR 개념이 중요하고, LED도 status 표현용이면 사람에게 읽기 쉬운 패턴을 우선해야 합니다.

문제원인완화 방법
저밝기 깜박임PWM 주파수가 너무 낮음주파수 상향, 저밝기 테이블 재조정, current sink 모드 검토
1단계가 너무 밝음선형 밝기 테이블지각 기반 비선형 테이블
resume 후 이전 밝기 복원 실패BL_CORE_SUSPENDRESUME 미사용 또는 상태 재초기화 누락update_status 경로 재검토
shutdown 뒤 LED가 예상과 다름retain/shutdown 정책 부재LED_RETAIN_AT_SHUTDOWN 또는 명시적 off/on 시퀀스
white flashpanel보다 backlight를 먼저 켬panel enable 이후 backlight on

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

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

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

  1. 클래스 계층
    장치가 제대로 등록되었는가, 올바른 sysfs 경로가 생겼는가
  2. 정책 계층
    trigger, hotkey, blanking, suspend/resume 로직이 값을 덮어쓰지 않는가
  3. 하드웨어 제어 계층
    PWM duty/frequency, current sink 전류, enable GPIO, regulator가 실제로 움직이는가
  4. 패널 타이밍 계층
    panel prepare/enable와 backlight on/off 순서가 맞는가
# 1. class 장치 확인
ls /sys/class/leds/
ls /sys/class/backlight/

# 2. LED trigger 상태 확인
cat /sys/class/leds/board0:green:status/trigger
cat /sys/class/leds/board0:green:status/brightness

# 3. 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

# 4. PWM debug와 커널 로그
cat /sys/kernel/debug/pwm
dmesg | grep -iE 'led|backlight|pwm|panel|drm'

이 단계에서 sysfs 값이 정상인데 실제 빛이 안 난다면 하드웨어 enable GPIO, regulator, PWM 파형을 먼저 의심합니다. 반대로 PWM도 정상인데 화면이 번쩍이면 panel 시퀀스 문제일 가능성이 큽니다.

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

흔한 실패 패턴과 원인 추적

증상흔한 원인점검 포인트
LED 밝기 변경이 가끔만 먹힘brightness_set에서 sleep 가능 접근 수행brightness_set_blocking로 바꿔야 하는지 확인
trigger를 바꾸면 LED가 완전히 멈춤하드웨어 blink/소프트웨어 trigger 상호작용 오류blink_set와 brightness off 처리 확인
RGB LED 색이 예상과 다름서브채널 intensity/전역 brightness 계산 오류multicolor class 사용 여부와 채널 매핑 재검토
카메라 flash가 오래 켜짐timeout/fault 모델을 일반 LED처럼 처리flash class와 보호 로직 사용 여부 확인
백라이트 값은 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 모드 재검토

끝까지 따라가는 상태 전이 예제: 노트북 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 디버깅 절반은 이미 끝난다.