Linux PWM 서브시스템 분석 및 드라이버 소스 코드 구조 이해

리눅스 커널이 부팅되는 과정에서 다양한 드라이버가 초기화되며, PWM(Pulse Width Modulation) 서브시스템 역시 이 과정에서 활성화됩니다. Jetson Nano와 같은 BSP(Board Support Package)가 제공되는 환경에서는 커널이 제공하는 sysfs 인터페이스를 통해 별도의 응용 프로그램 작성 없이도 드라이버의 동작을 즉각적으로 검증할 수 있습니다. 사용자 공간에서 PWM을 제어하기 위한 일반적인 경로는 다음과 같습니다.
# PWM0 채널 내보내기 (Export)
echo 0 > /sys/class/pwm/pwmchip0/export

# 주기(Period) 설정 (단위: ns, 예: 20ms)
echo 20000000 > /sys/class/pwm/pwmchip0/pwm0/period

# 듀티 사이클(Duty Cycle) 설정 (예: 50% 점유율)
echo 10000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle

# PWM 출력 활성화
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
이러한 명령을 수행하면 `/sys/class/pwm/pwmchip0/` 디렉터리에 `pwm0` 노드가 생성되며, 해당 디렉터리 내부의 속성 파일들을 읽고 써서 원하는 파형을 제어할 수 있습니다.

1. Sysfs 계층 구조 (drivers/pwm/sysfs.c)

이 계층은 사용자 공간의 파일 시스템 조작을 커널 내부 함수로 연결하는 가교 역할을 합니다. 사용자가 `period` 파일을 읽거나 쓸 때 호출되는 콜백 함수의 구조는 다음과 같이 정의됩니다.
// 주기를 확인하는 show 함수
static ssize_t pwm_period_show(struct device *dev,
                               struct device_attribute *attr,
                               char *buf)
{
    const struct pwm_device *pwm_dev = child_to_pwm_device(dev);
    struct pwm_state current_state;

    pwm_get_state(pwm_dev, ¤t_state);

    return sprintf(buf, "%u\n", current_state.period);
}

// 주기를 설정하는 store 함수
static ssize_t pwm_period_store(struct device *dev,
                                struct device_attribute *attr,
                                const char *buf, size_t count)
{
    struct pwm_device *pwm_dev = child_to_pwm_device(dev);
    struct pwm_state target_state;
    unsigned int val;
    int status;

    status = kstrtouint(buf, 0, &val);
    if (status < 0)
        return status;

    pwm_get_state(pwm_dev, &target_state);
    target_state.period = val;

    // 하드웨어에 새로운 상태 적용
    status = pwm_apply_state(pwm_dev, &target_state);

    return status ? : count;
}
위와 같은 함수들은 `DEVICE_ATTR_RW` 매뉴얼에 의해 속성 파일과 매핑되며, 최종적으로 `pwm_class`에 등록되어 `/sys/class/pwm` 경로에 나타나게 됩니다.

2. 커널 핵심 계층 (drivers/pwm/core.c)

커널 핵심(Core) 부분은 PWM 칩을 등록하고 관리하는 로직을 담당합니다. `pwmchip_add` 함수는 드라이버가 로드될 때 호출되어 시스템에 새로운 PWM 컨트롤러를 알립니다.
struct pwm_device *pwm_request_from_chip(struct pwm_chip *controller,
                                         unsigned int index,
                                         const char *user_label)
{
    struct pwm_device *pwm_target;
    int err_code;

    if (!controller || index >= controller->npwm)
        return ERR_PTR(-EINVAL);

    mutex_lock(&pwm_lock);
    
    pwm_target = &controller->pwms[index];
    err_code = pwm_device_request(pwm_target, user_label);
    
    if (err_code < 0)
        pwm_target = ERR_PTR(err_code);

    mutex_unlock(&pwm_lock);
    return pwm_target;
}
이 계층에서는 하드웨어 독립적인 인터페이스를 제공하며, `pwm_apply_state`와 같은 함수를 통해 하드웨어 드라이버의 실제 제어 함수를 호출합니다.

3. 드라이버 하드웨어 추상화 (include/linux/pwm.h)

모든 PWM 컨트롤러 드라이버는 커널이 정의한 표준 오퍼레이션 구조체를 구현해야 합니다.
struct pwm_ops {
    int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
    void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
    int (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
                  int duty_ns, int period_ns);
    int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm,
                        enum pwm_polarity polarity);
    int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm);
    void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);
    // ... 기타 상태 관리 함수
};
하드웨어 제조사는 위 구조체의 함수 포인터에 자사 칩셋의 레지스터를 조작하는 함수를 할당하여 서브시스템에 등록합니다.

4. 하위 물리 드라이버 (drivers/pwm/pwm-tegra.c)

Jetson Nano(Tegra 칩셋)의 실제 드라이버 코드는 플랫폼 드라이버 형태로 구현되어 있습니다. 장치 트리(Device Tree)의 호환성 문자열을 통해 매칭됩니다.
static const struct of_device_id tegra_pwm_match[] = {
    { .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_info },
    { .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_info },
    { .compatible = "nvidia,tegra194-pwm", .data = &tegra194_pwm_info },
    { }
};

static struct platform_driver tegra_pwm_driver = {
    .driver = {
        .name = "tegra-pwm",
        .of_match_table = tegra_pwm_match,
    },
    .probe = tegra_pwm_probe,
    .remove = tegra_pwm_remove,
};

module_platform_driver(tegra_pwm_driver);
`probe` 단계에서 하드웨어 자원을 할당받고 `pwmchip_add`를 호출하여 상위 계층에 자신을 등록합니다. 이후 `tegra_pwm_config`나 `tegra_pwm_enable`과 같은 함수를 통해 실제 전압 레벨과 클록을 조정하여 PWM 파형을 생성하게 됩니다.

태그: Linux kernel PWM Subsystem Jetson Nano Device Driver Sysfs

7월 27일 12:33에 게시됨