Regulator 프레임워크

Regulator 프레임워크를 SoC 전력 도메인 안정성과 보드 전원 시퀀스 관점에서 심층 분석합니다. regulator_desc/regulator_ops 기반 공급자 드라이버 작성, consumer API(regulator_get/enable/set_voltage) 사용 원칙, always-on/boot-on 제약과 전원 의존성 그래프, PMIC 레일 공유 충돌 해결, runtime PM 및 DVFS와의 연동, 과전류·저전압 이벤트 대응, Device Tree 전원 트리 모델링과 디버깅까지 안전한 전원 제어를 위한 실무 포인트를 다룹니다.

전제 조건: 디바이스 드라이버전원관리 문서를 먼저 읽으세요. 제어형 디바이스는 안전 한계와 상태 전이가 중심이므로, 설정값의 적용 시점과 실패 복구 절차를 먼저 정리해야 합니다.
일상 비유: 이 주제는 설비 안전 제어반 운영과 비슷합니다. 온도/전압/타이머를 임계값 안에서 관리하듯이, 커널에서도 보호 경계와 즉시 복구 경로가 핵심입니다.

핵심 요약

  • 초기화 순서 — 탐색, 바인딩, 자원 등록 순서를 점검합니다.
  • 제어/데이터 분리 — 빠른 경로와 설정 경로를 분리 설계합니다.
  • IRQ/작업 분할 — 즉시 처리와 지연 처리를 구분합니다.
  • 안전 한계 — 전원/열/타이밍 임계값을 함께 관리합니다.
  • 운영 복구 — 오류 시 재초기화와 롤백 경로를 준비합니다.

단계별 이해

  1. 장치 수명주기 확인
    probe부터 remove까지 흐름을 점검합니다.
  2. 비동기 경로 설계
    IRQ, 워크큐, 타이머 역할을 분리합니다.
  3. 자원 정합성 검증
    DMA/클록/전원 참조를 교차 확인합니다.
  4. 현장 조건 테스트
    연결 끊김/복구/부하 상황을 재현합니다.
관련 표준: RS-232C (UART), SCSI-3, NVMe Specification — 입출력 장치 인터페이스 표준입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
관련 페이지: 기본 디바이스 드라이버 모델은 디바이스 드라이버, 버스 프레임워크는 I2C/SPI/GPIO 페이지를 참고하세요.

전원 트리에서 Regulator의 역할

Regulator는 시스템 전원 트리에서 "전압/전류 정책 계층"을 담당합니다. 소비자 드라이버는 필요한 전압 범위만 요청하고, 실제 PMIC 제어와 제약 검증은 프레임워크가 수행해 보드 의존성을 크게 줄입니다.

Regulator 서브시스템

Regulator 서브시스템(drivers/regulator/)은 시스템의 전압 및 전류 공급 장치(레귤레이터)를 추상화하여 관리하는 프레임워크입니다. PMIC(Power Management IC), DC-DC 컨버터, LDO(Low-Dropout Regulator), 스위치 레귤레이터 등 다양한 전원 공급 장치를 통합 관리하며, 디바이스 드라이버가 필요한 전압/전류를 요청하면 프레임워크가 하드웨어 제약 조건을 검증하고 적용합니다.

Regulator Framework의 핵심 목표: 디바이스 드라이버를 전원 공급 하드웨어의 구체적 구현(특정 PMIC 레지스터 등)에서 분리합니다. 드라이버는 "3.3V 필요"라고만 요청하고, 프레임워크가 실제 하드웨어에 맞게 처리합니다. 이를 통해 동일한 디바이스 드라이버가 서로 다른 보드/PMIC에서 재사용될 수 있습니다.
MMC driver vmmc-supply USB PHY vbus-supply Wi-Fi driver vdd-supply Display vddio-supply Sensor vcc-supply CPU DVS cpu-supply regulator_get · regulator_enable · regulator_set_voltage · regulator_disable Regulator Framework (drivers/regulator/core.c) constraints · coupling · supply chain · voltage/current negotiation TPS65219 TI PMIC MAX77686 Maxim PMIC AXP20x X-Powers PMIC fixed-regulator GPIO 스위치 pwm-regulator PWM 전압 PMIC (I2C/SPI) Buck/LDO 레지스터 DC-DC Converter 스위칭 레귤레이터 LDO Low-Dropout 선형 GPIO Switch 고정 전압 ON/OFF

핵심 데이터 구조

Regulator 프레임워크의 핵심 구조체는 regulator_desc(레귤레이터 하드웨어 기술), regulator_ops(하드웨어 제어 콜백), regulator_config(등록 시 설정), regulation_constraints(전압/전류 제한)입니다.

/* include/linux/regulator/driver.h */

struct regulator_desc {
    const char             *name;           /* 레귤레이터 이름 (예: "BUCK1") */
    const char             *supply_name;    /* 입력 공급 이름 (예: "vin1") */
    int                    id;              /* 인스턴스 ID */
    enum regulator_type    type;            /* REGULATOR_VOLTAGE / CURRENT */
    const struct regulator_ops *ops;        /* HW 제어 콜백 */

    /* 전압 테이블 방식 (이산 전압 목록) */
    unsigned int           n_voltages;      /* 전압 단계 수 */
    const struct regulator_linear_range *linear_ranges;
    int                    n_linear_ranges;

    /* 선형 범위: min_uV + step × selector */
    unsigned int           min_uV;          /* 최소 전압 (µV) */
    unsigned int           uV_step;         /* 전압 단계 크기 (µV) */

    /* 레지스터 매핑 (regmap 기반 자동화) */
    unsigned int           vsel_reg;        /* 전압 선택 레지스터 */
    unsigned int           vsel_mask;       /* 전압 선택 비트마스크 */
    unsigned int           enable_reg;      /* 활성화 레지스터 */
    unsigned int           enable_mask;     /* 활성화 비트마스크 */
    unsigned int           enable_val;      /* 활성화 시 쓸 값 */
    unsigned int           disable_val;     /* 비활성화 시 쓸 값 */

    unsigned int           enable_time;     /* 안정화 시간 (µs) */
    unsigned int           ramp_delay;      /* 전압 변경 속도 (µV/µs) */

    unsigned int           active_discharge_reg;
    unsigned int           active_discharge_mask;
    unsigned int           active_discharge_on;
    unsigned int           active_discharge_off;
};
/* Regulator 하드웨어 제어 콜백 */
struct regulator_ops {
    /* 전압 제어 */
    int (*list_voltage)(struct regulator_dev *, unsigned int selector);
    int (*set_voltage_sel)(struct regulator_dev *, unsigned int sel);
    int (*get_voltage_sel)(struct regulator_dev *);
    int (*map_voltage)(struct regulator_dev *, int min_uV, int max_uV);

    /* 활성화/비활성화 */
    int (*enable)(struct regulator_dev *);
    int (*disable)(struct regulator_dev *);
    int (*is_enabled)(struct regulator_dev *);

    /* 전류 제한 */
    int (*set_current_limit)(struct regulator_dev *, int min_uA, int max_uA);
    int (*get_current_limit)(struct regulator_dev *);

    /* 동작 모드 (NORMAL, IDLE, STANDBY, FAST) */
    int (*set_mode)(struct regulator_dev *, unsigned int mode);
    unsigned int (*get_mode)(struct regulator_dev *);

    /* Suspend 상태 제어 */
    int (*set_suspend_voltage)(struct regulator_dev *, int uV);
    int (*set_suspend_enable)(struct regulator_dev *);
    int (*set_suspend_disable)(struct regulator_dev *);
    int (*set_suspend_mode)(struct regulator_dev *, unsigned int mode);

    /* 전압 변경 완료 시간 (µs) */
    int (*set_ramp_delay)(struct regulator_dev *, int ramp_delay);

    /* 에러/이벤트 감지 */
    int (*get_error_flags)(struct regulator_dev *, unsigned int *flags);
};
/* include/linux/regulator/machine.h — 제약 조건 */
struct regulation_constraints {
    const char        *name;          /* 설명 문자열 */

    int               min_uV;         /* 허용 최소 전압 (µV) */
    int               max_uV;         /* 허용 최대 전압 (µV) */
    int               min_uA;         /* 허용 최소 전류 (µA) */
    int               max_uA;         /* 허용 최대 전류 (µA) */

    unsigned int      valid_modes_mask;  /* 허용 동작 모드 */
    unsigned int      valid_ops_mask;    /* 허용 작업 (아래 표 참조) */

    int               input_uV;       /* 입력 전압 (µV) */
    unsigned int      ramp_delay;     /* 전압 변경 속도 (µV/µs) */
    unsigned int      settling_time;  /* 안정화 대기 시간 (µs) */
    unsigned int      enable_time;    /* 활성화 후 안정화 시간 (µs) */

    unsigned          always_on:1;     /* 항상 켜짐 (끌 수 없음) */
    unsigned          boot_on:1;       /* 부팅 시 자동 활성화 */
    unsigned          apply_uV:1;      /* 등록 시 즉시 전압 적용 */

    /* Suspend 상태별 설정 */
    struct regulator_state state_mem;  /* S3 Suspend-to-RAM */
    struct regulator_state state_disk; /* S4 Hibernate */
    struct regulator_state state_standby;
};
valid_ops_mask 플래그허용하는 소비자 API 호출
REGULATOR_CHANGE_VOLTAGEregulator_set_voltage()
REGULATOR_CHANGE_CURRENTregulator_set_current_limit()
REGULATOR_CHANGE_MODEregulator_set_mode()
REGULATOR_CHANGE_STATUSregulator_enable() / regulator_disable()
REGULATOR_CHANGE_DRMSDynamic Regulator Mode Switching (자동 모드 전환)

Consumer API — Regulator 사용하기

/* Regulator Consumer API — 주요 함수 */
#include <linux/regulator/consumer.h>

/* 1. Regulator 획득 */
struct regulator *devm_regulator_get(struct device *dev,
                                     const char *id);
/* id = "vdd" → DT에서 vdd-supply 프로퍼티 검색 */

struct regulator *devm_regulator_get_optional(struct device *dev,
                                              const char *id);
/* optional: 없으면 -ENODEV 반환 (dummy 생성 안 함) */

/* 벌크 획득 (여러 regulator를 한 번에) */
int devm_regulator_bulk_get(struct device *dev, int num_consumers,
                           struct regulator_bulk_data *consumers);

/* 2. 활성화/비활성화 (참조 카운팅됨) */
int regulator_enable(struct regulator *reg);
int regulator_disable(struct regulator *reg);
int regulator_is_enabled(struct regulator *reg);

/* 벌크 활성화/비활성화 */
int regulator_bulk_enable(int num, struct regulator_bulk_data *consumers);
int regulator_bulk_disable(int num, struct regulator_bulk_data *consumers);

/* 3. 전압 제어 */
int regulator_set_voltage(struct regulator *reg,
                         int min_uV, int max_uV);
int regulator_get_voltage(struct regulator *reg);

/* 4. 전류 제한 */
int regulator_set_current_limit(struct regulator *reg,
                               int min_uA, int max_uA);

/* 5. 동작 모드 */
int regulator_set_load(struct regulator *reg, int load_uA);
/* DRMS: 부하 전류에 따라 자동으로 최적 모드 선택 */

/* 6. 이벤트 알림 */
int regulator_register_notifier(struct regulator *reg,
                               struct notifier_block *nb);
/* REGULATOR_EVENT_UNDER_VOLTAGE, OVER_CURRENT, OVER_TEMP 등 */
/* 소비자 사용 예제 — 센서 디바이스 드라이버 */

struct my_sensor {
    struct regulator  *vdd;       /* 코어 전원 */
    struct regulator  *vddio;     /* I/O 전원 */
    struct i2c_client *client;
};

static int my_sensor_probe(struct i2c_client *client)
{
    struct device *dev = &client->dev;
    struct my_sensor *sensor;
    int ret;

    sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
    if (!sensor)
        return -ENOMEM;

    /* DT에서 vdd-supply, vddio-supply 프로퍼티로 레귤레이터 획득 */
    sensor->vdd = devm_regulator_get(dev, "vdd");
    if (IS_ERR(sensor->vdd))
        return dev_err_probe(dev, PTR_ERR(sensor->vdd),
                              "failed to get vdd regulator\\n");

    sensor->vddio = devm_regulator_get(dev, "vddio");
    if (IS_ERR(sensor->vddio))
        return dev_err_probe(dev, PTR_ERR(sensor->vddio),
                              "failed to get vddio regulator\\n");

    /* 전압 설정 (필요한 경우) */
    ret = regulator_set_voltage(sensor->vdd, 2800000, 2800000);
    if (ret)
        return dev_err_probe(dev, ret, "failed to set vdd voltage\\n");

    /* 전원 활성화 — 순서 중요! (코어 → I/O) */
    ret = regulator_enable(sensor->vdd);
    if (ret)
        return ret;

    usleep_range(1000, 1500);  /* 전원 안정화 대기 */

    ret = regulator_enable(sensor->vddio);
    if (ret) {
        regulator_disable(sensor->vdd);
        return ret;
    }

    usleep_range(5000, 6000);  /* 디바이스 부팅 대기 */

    /* ... 센서 초기화 ... */
    return 0;
}

static void my_sensor_remove(struct i2c_client *client)
{
    struct my_sensor *sensor = i2c_get_clientdata(client);

    /* 전원 해제 — 역순! (I/O → 코어) */
    regulator_disable(sensor->vddio);
    regulator_disable(sensor->vdd);
}
참조 카운팅: regulator_enable()/regulator_disable()은 참조 카운팅됩니다. 동일 레귤레이터를 여러 소비자가 enable해도 마지막 disable까지 전원이 유지됩니다. 이 때문에 enable/disable 쌍을 반드시 맞춰야 합니다. 불균형은 under-voltage 또는 전원 누수를 유발합니다.

Provider 드라이버 구현

PMIC 레귤레이터 드라이버는 regulator_desc에 레지스터 매핑을 기술하면 프레임워크의 regmap 헬퍼 함수가 대부분의 regulator_ops를 자동 구현합니다.

/* drivers/regulator/example-regulator.c — PMIC 드라이버 예제 */
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>

/* PMIC 레지스터 맵 */
#define PMIC_BUCK1_CTRL    0x10   /* [0]: enable */
#define PMIC_BUCK1_VSEL    0x11   /* [6:0]: voltage selector */
#define PMIC_LDO1_CTRL     0x30
#define PMIC_LDO1_VSEL     0x31

/* regulator_ops — regmap 헬퍼 함수 사용으로 직접 구현 최소화 */
static const struct regulator_ops example_buck_ops = {
    .list_voltage     = regulator_list_voltage_linear,
    .map_voltage      = regulator_map_voltage_linear,
    .get_voltage_sel  = regulator_get_voltage_sel_regmap,
    .set_voltage_sel  = regulator_set_voltage_sel_regmap,
    .enable           = regulator_enable_regmap,
    .disable          = regulator_disable_regmap,
    .is_enabled       = regulator_is_enabled_regmap,
};

static const struct regulator_ops example_ldo_ops = {
    .list_voltage     = regulator_list_voltage_linear_range,
    .map_voltage      = regulator_map_voltage_linear_range,
    .get_voltage_sel  = regulator_get_voltage_sel_regmap,
    .set_voltage_sel  = regulator_set_voltage_sel_regmap,
    .enable           = regulator_enable_regmap,
    .disable          = regulator_disable_regmap,
    .is_enabled       = regulator_is_enabled_regmap,
};

/* LDO용 비선형 범위: 0.8V~1.5V(50mV step) + 1.8V~3.3V(100mV step) */
static const struct regulator_linear_range example_ldo_ranges[] = {
    REGULATOR_LINEAR_RANGE(800000,  0,  14, 50000),   /* 0.8V~1.5V */
    REGULATOR_LINEAR_RANGE(1800000, 15, 30, 100000),  /* 1.8V~3.3V */
};

enum { EXAMPLE_BUCK1, EXAMPLE_LDO1, EXAMPLE_NUM_REGULATORS };

static const struct regulator_desc example_regulators[] = {
    [EXAMPLE_BUCK1] = {
        .name            = "BUCK1",
        .supply_name     = "vin1",
        .id              = EXAMPLE_BUCK1,
        .type            = REGULATOR_VOLTAGE,
        .ops             = &example_buck_ops,
        .n_voltages      = 128,          /* 7비트 selector */
        .min_uV          = 600000,       /* 0.6V */
        .uV_step         = 12500,        /* 12.5mV step */
        /* 0.6V + 12.5mV × 127 = 2.1875V */
        .vsel_reg        = PMIC_BUCK1_VSEL,
        .vsel_mask       = 0x7F,
        .enable_reg      = PMIC_BUCK1_CTRL,
        .enable_mask     = BIT(0),
        .enable_val      = BIT(0),
        .disable_val     = 0,
        .enable_time     = 300,          /* 300µs 안정화 */
        .ramp_delay      = 12500,        /* 12.5mV/µs */
        .owner           = THIS_MODULE,
    },
    [EXAMPLE_LDO1] = {
        .name            = "LDO1",
        .supply_name     = "vin2",
        .id              = EXAMPLE_LDO1,
        .type            = REGULATOR_VOLTAGE,
        .ops             = &example_ldo_ops,
        .n_voltages      = 31,
        .linear_ranges   = example_ldo_ranges,
        .n_linear_ranges = ARRAY_SIZE(example_ldo_ranges),
        .vsel_reg        = PMIC_LDO1_VSEL,
        .vsel_mask       = 0x1F,
        .enable_reg      = PMIC_LDO1_CTRL,
        .enable_mask     = BIT(0),
        .enable_val      = BIT(0),
        .disable_val     = 0,
        .enable_time     = 200,
        .owner           = THIS_MODULE,
    },
};

static const struct regmap_config example_regmap_config = {
    .reg_bits  = 8,
    .val_bits  = 8,
    .max_register = 0x80,
};

static int example_pmic_probe(struct i2c_client *client)
{
    struct device *dev = &client->dev;
    struct regmap *regmap;
    struct regulator_config config = {};
    int i;

    regmap = devm_regmap_init_i2c(client, &example_regmap_config);
    if (IS_ERR(regmap))
        return PTR_ERR(regmap);

    config.dev     = dev;
    config.regmap  = regmap;

    for (i = 0; i < EXAMPLE_NUM_REGULATORS; i++) {
        struct regulator_dev *rdev;

        rdev = devm_regulator_register(dev, &example_regulators[i],
                                        &config);
        if (IS_ERR(rdev))
            return dev_err_probe(dev, PTR_ERR(rdev),
                                  "failed to register %s\\n",
                                  example_regulators[i].name);
    }

    dev_info(dev, "PMIC registered (%d regulators)\\n",
             EXAMPLE_NUM_REGULATORS);
    return 0;
}

static const struct of_device_id example_pmic_of_match[] = {
    { .compatible = "vendor,example-pmic" },
    { }
};
MODULE_DEVICE_TABLE(of, example_pmic_of_match);

static struct i2c_driver example_pmic_driver = {
    .probe  = example_pmic_probe,
    .driver = {
        .name           = "example-pmic",
        .of_match_table = example_pmic_of_match,
    },
};
module_i2c_driver(example_pmic_driver);

Device Tree 바인딩:

/* PMIC 노드와 레귤레이터 정의 */
pmic@48 {
    compatible = "vendor,example-pmic";
    reg = <0x48>;

    regulators {
        buck1: BUCK1 {
            regulator-name = "vdd_cpu";
            regulator-min-microvolt = <800000>;   /* 0.8V */
            regulator-max-microvolt = <1500000>;  /* 1.5V */
            regulator-always-on;
            regulator-boot-on;
            regulator-ramp-delay = <12500>;      /* 12.5mV/µs */
        };

        ldo1: LDO1 {
            regulator-name = "vdd_sensor";
            regulator-min-microvolt = <2800000>;  /* 2.8V */
            regulator-max-microvolt = <2800000>;  /* 고정 2.8V */
        };
    };
};

/* 소비자 노드에서 레귤레이터 참조 */
sensor@68 {
    compatible = "vendor,my-sensor";
    reg = <0x68>;
    vdd-supply = <&ldo1>;        /* 코어 전원 */
    vddio-supply = <&buck1>;     /* I/O 전원 */
};

/* 고정 전압 레귤레이터 (GPIO 스위치) */
reg_3v3: regulator-3v3 {
    compatible = "regulator-fixed";
    regulator-name = "vdd_3v3";
    regulator-min-microvolt = <3300000>;
    regulator-max-microvolt = <3300000>;
    gpio = <&gpio1 5 GPIO_ACTIVE_HIGH>;
    enable-active-high;
    regulator-boot-on;
};

Regulator 디버깅

# === Regulator sysfs/debugfs 인터페이스 ===

# 등록된 모든 레귤레이터 목록
ls /sys/class/regulator/

# 개별 레귤레이터 정보
cat /sys/class/regulator/regulator.0/name
cat /sys/class/regulator/regulator.0/state          # enabled/disabled
cat /sys/class/regulator/regulator.0/microvolts     # 현재 전압 (µV)
cat /sys/class/regulator/regulator.0/min_microvolts
cat /sys/class/regulator/regulator.0/max_microvolts
cat /sys/class/regulator/regulator.0/num_users      # 활성 소비자 수
cat /sys/class/regulator/regulator.0/type           # voltage/current

# debugfs — 상세 정보 (CONFIG_DEBUG_FS 필요)
cat /sys/kernel/debug/regulator/regulator_summary
# 트리 형태로 공급 체인, 전압, 소비자, use_count 표시

# dmesg에서 regulator 관련 로그
dmesg | grep -i regulator
dmesg | grep -i "supply\|voltage\|ldo\|buck"

# Device Tree에서 supply 바인딩 확인
grep -r "supply" /proc/device-tree/ 2>/dev/null | head -20
레귤레이터 유형설명효율노이즈용도
Buck (DC-DC)스위칭 방식 강압 컨버터높음 (85~95%)높음 (스위칭 리플)CPU/GPU 코어 전원, 고전류 부하
Boost (DC-DC)스위칭 방식 승압 컨버터높음높음LED 드라이버, USB VBUS 생성
LDO선형 저드롭아웃 레귤레이터낮음 (Vin/Vout 비율)매우 낮음아날로그 회로, PLL, ADC 전원
Fixed고정 전압 (GPIO 스위치)N/AN/A전원 도메인 ON/OFF 제어
PWM RegulatorPWM 듀티로 전압 제어중간중간간이 전압 조절 (저비용 SoC)
문제증상원인해결
부팅 시 레귤레이터 비활성화 디바이스 동작 불능, regulator_summary에서 use_count=0 DT에 regulator-always-on 또는 소비자 누락 DT에 always-on 추가하거나 소비자 드라이버의 regulator_enable() 호출 확인
전압 설정 실패 (-EINVAL) regulator_set_voltage() 반환 오류 요청 범위가 constraints의 min/max_uV 밖 DT의 regulator-min/max-microvolt 값 확인
disable 호출해도 꺼지지 않음 is_enabled 계속 1 다른 소비자가 아직 enable 중 (참조 카운트 > 0) regulator_summary에서 use_count 확인, 모든 소비자의 enable/disable 쌍 검증
부팅 느림 / 전원 시퀀싱 실패 프로브 순서 문제, -EPROBE_DEFER 반복 PMIC가 소비자보다 늦게 프로브됨 dev_err_probe() 사용으로 자동 재시도, DT에 supply 의존성 올바르게 기술
Suspend/resume 후 전압 이상 Resume 후 디바이스 오동작 PMIC의 suspend 전압 설정 누락 DT에 regulator-suspend-* 프로퍼티 추가, 또는 드라이버의 PM 콜백에서 재설정
Regulator 드라이버 개발 주의사항:
  • 전원 시퀀싱 — 디바이스마다 전원 투입 순서(power-on sequence)가 다릅니다. 데이터시트의 타이밍 요구사항을 반드시 준수하세요. 프레임워크의 enable_timeramp_delay로 자동 대기를 설정할 수 있습니다
  • regmap 헬퍼 활용regulator_*_regmap() 함수를 사용하면 regulator_ops의 대부분을 직접 구현할 필요가 없습니다. regulator_desc에 레지스터/마스크만 정확히 기술하면 됩니다
  • Constraints 검증valid_ops_mask에 허용하지 않은 작업은 소비자가 호출해도 -EPERM으로 거부됩니다. 보안상 중요한 레귤레이터(CPU 코어 전압 등)는 제약 조건을 최소한으로 설정하세요
  • Supply Chain — PMIC의 Buck이 다른 LDO의 입력이 될 수 있습니다. DT의 *-supply 프로퍼티로 공급 체인을 올바르게 기술해야 전원 관리가 정상 동작합니다
  • DRMS — Dynamic Regulator Mode Switching을 사용하면 부하에 따라 자동으로 모드가 전환됩니다 (예: 경부하 시 ECO 모드). regulator_set_load()를 호출하는 소비자 드라이버가 필요합니다

PMIC 아키텍처와 멀티레일 토폴로지

PMIC(Power Management IC)는 하나의 칩 안에 다수의 레귤레이터(Buck/LDO/Switch)를 통합한 전원 관리 전용 반도체입니다. 배터리나 외부 전원 입력으로부터 SoC, DDR, I/O, 센서 등 각 도메인에 필요한 전압을 개별적으로 생성하며, 전원 시퀀싱·과전압/과전류 보호·열 관리까지 하드웨어 수준에서 처리합니다.

PMIC 멀티레일 전원 트리 Battery 3.7~4.2V Li-Ion Single Cell PMIC (e.g., TPS65217 / AXP209) DCDC1 — 1.1V (CPU) DCDC2 — 1.8V (DDR) DCDC3 — 3.3V (I/O) LDO1 — 1.8V (PLL) LDO2 — 2.8V (Sensor) LDO3 — 3.0V (eMMC) Switch — GPIO제어 CPU Core DDR Memory GPIO / UART / SPI PLL / Clock Gen IMU Sensor eMMC / SD Card WiFi / BT Module I2C / SPI Bus SoC → PMIC 레지스터 regmap 인터페이스

PMIC와 SoC 사이의 통신은 주로 I2C 또는 SPI 버스를 사용합니다. 커널의 regmap API를 통해 버스 종류와 무관한 레지스터 접근 추상화가 가능하며, 대부분의 PMIC 드라이버는 regmap 기반으로 구현됩니다.

PMIC 칩제조사레일 수인터페이스주요 대상 플랫폼
TPS65217TI3 DCDC + 4 LDOI2CBeagleBone, AM335x
AXP209X-Powers3 DCDC + 5 LDOI2CAllwinner A10/A20
MAX77686Maxim9 Buck + 26 LDOI2CSamsung Exynos 4
STPMIC1ST4 Buck + 6 LDOI2CSTM32MP1
RK808Rockchip4 Buck + 8 LDOI2CRockchip RK3288/RK3399
DA9063Dialog6 Buck + 11 LDOI2Ci.MX6 Sabre

Buck (DCDC) vs LDO 특성 비교

PMIC 내부의 레귤레이터는 크게 스위칭 레귤레이터(Buck/Boost)선형 레귤레이터(LDO)로 나뉩니다. 각각의 장단점이 뚜렷하므로 전원 설계 시 적재적소에 배치해야 합니다.

특성Buck (DCDC)LDO
변환 방식스위칭 (인덕터/캐패시터)선형 (트랜지스터 드롭)
효율85~95% (고효율)Vout/Vin 비율 의존
출력 전류수 A까지 가능수백 mA 이하
출력 노이즈상대적으로 높음 (스위칭 리플)매우 낮음 (클린 출력)
드롭아웃승/강압 자유Vin > Vout + Vdrop 필수
외부 부품인덕터, 캐패시터 필요캐패시터만 필요
용도CPU, GPU, DDR 전원PLL, ADC, 아날로그 회로

I2C/SPI 레지스터 접근과 regmap

PMIC 드라이버의 핵심은 regmap을 통한 레지스터 읽기/쓰기입니다. regmap은 I2C, SPI, MMIO 등 다양한 버스에 대해 동일한 API를 제공하며, 캐시·범위 검증·바이트 스왑 등을 자동 처리합니다.

/* === PMIC 드라이버에서 regmap 기반 레귤레이터 구현 예시 === */

/* regmap 설정 (I2C PMIC 예시) */
static const struct regmap_config pmic_regmap_config = {
    .reg_bits     = 8,          /* 레지스터 주소 8비트 */
    .val_bits     = 8,          /* 레지스터 값 8비트 */
    .max_register = 0xFF,       /* 최대 레지스터 주소 */
    .cache_type   = REGCACHE_RBTREE,  /* 레지스터 캐시 전략 */
};

/* regulator_desc에 regmap 정보 기술 → regmap 헬퍼 자동 사용 */
static const struct regulator_desc my_buck1_desc = {
    .name           = "BUCK1",
    .id             = 0,
    .ops            = &regulator_voltage_regmap_ops,  /* regmap 헬퍼! */
    .type           = REGULATOR_VOLTAGE,
    .owner          = THIS_MODULE,
    .n_voltages     = 64,
    .min_uV         = 600000,      /* 0.6V */
    .uV_step        = 12500,       /* 12.5mV 스텝 */
    .vsel_reg       = 0x10,        /* 전압 선택 레지스터 */
    .vsel_mask      = 0x3F,        /* [5:0] 비트 */
    .enable_reg     = 0x20,        /* 활성화 레지스터 */
    .enable_mask    = BIT(0),      /* bit 0 = enable */
    .enable_time    = 500,         /* 안정화 시간 500µs */
    .ramp_delay     = 12500,       /* 12.5mV/µs 슬루레이트 */
};

/* probe에서 regmap 생성 및 레귤레이터 등록 */
static int my_pmic_probe(struct i2c_client *client)
{
    struct regmap *regmap;
    struct regulator_config config = { };

    regmap = devm_regmap_init_i2c(client, &pmic_regmap_config);
    if (IS_ERR(regmap))
        return dev_err_probe(&client->dev, PTR_ERR(regmap),
                             "regmap init failed\n");

    config.dev = &client->dev;
    config.regmap = regmap;
    config.of_node = client->dev.of_node;

    /* regulator_desc + regmap → 프레임워크가 ops를 자동 처리 */
    devm_regulator_register(&client->dev, &my_buck1_desc, &config);
    /* ... LDO, Switch 등도 동일 패턴으로 등록 ... */

    return 0;
}
regmap 헬퍼의 위력: regulator_voltage_regmap_ops를 사용하면 set_voltage_sel, get_voltage_sel, enable, disable, is_enabled 등의 ops를 직접 구현할 필요가 없습니다. regulator_desc에 레지스터 주소와 마스크만 정확히 기술하면 프레임워크가 regmap을 통해 모든 것을 자동 처리합니다.

주요 PMIC 드라이버 커널 소스 경로

PMIC커널 소스 경로비고
TPS65217drivers/regulator/tps65217-regulator.cMFD 기반, I2C
AXP20x 시리즈drivers/regulator/axp20x-regulator.cAXP152/209/221/803 등 통합
MAX77686drivers/regulator/max77686-regulator.cSamsung Exynos 레퍼런스
STPMIC1drivers/regulator/stpmic1_regulator.cSTM32MP1 공식 PMIC
RK808drivers/regulator/rk808-regulator.cRockchip 공식, regmap 활용

Device Tree Regulator 바인딩

리눅스 커널에서 레귤레이터의 전기적 제약 조건(constraints)공급 체인(supply chain)은 Device Tree(DT)를 통해 기술됩니다. 보드마다 서로 다른 전압 범위·시퀀싱·의존 관계를 소스 코드 변경 없이 DTS 파일만으로 정의할 수 있어, 하나의 PMIC 드라이버가 다양한 보드에서 재사용됩니다.

Regulator 공급 체인 계층 (vin-supply) Battery 3.7~4.2V PMIC DCDC1 (1.1V) DCDC2 (3.3V) LDO1 (1.8V) LDO2 (2.8V) vin-supply CPU Core vdd-supply = &dcdc1 LDO_ext (1.8V) vin-supply WiFi Module PLL Clock Camera Sensor DT vin-supply 체인: Battery → PMIC 내부 레일 → 소비자 디바이스. 카스케이드(DCDC→외부LDO→소비자) 구성도 가능.

핵심 DT 프로퍼티

프로퍼티타입설명
regulator-min-microvoltu32소비자가 요청할 수 있는 최소 전압 (µV)
regulator-max-microvoltu32소비자가 요청할 수 있는 최대 전압 (µV)
regulator-min-microampu32최소 전류 제한 (µA)
regulator-max-microampu32최대 전류 제한 (µA)
regulator-always-onbool시스템 동작 중 항상 활성 유지
regulator-boot-onbool부트로더가 켠 상태 유지 (초기 비활성화 방지)
regulator-ramp-delayu32전압 변경 슬루레이트 (µV/µs)
regulator-enable-ramp-delayu32enable 후 안정화 대기 시간 (µs)
regulator-settling-time-up-usu32전압 상승 시 정착 시간 (µs)
vin-supplyphandle상위 공급 레귤레이터 (공급 체인)
regulator-suspend-microvoltu32시스템 suspend 시 전압 (µV)
regulator-coupled-withphandle결합 레귤레이터 지정

실전 DTS 예시: BeagleBone TPS65217

/* BeagleBone Black — TPS65217 PMIC DTS (arch/arm/boot/dts/am335x-bone-common.dtsi) */

&i2c0 {
    tps: tps@24 {
        compatible = "ti,tps65217";
        reg = <0x24>;
        interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;

        regulators {
            dcdc1_reg: dcdc1 {
                regulator-name = "vdds_dpr";
                regulator-min-microvolt = <1500000>;
                regulator-max-microvolt = <1500000>;
                regulator-always-on;
                regulator-boot-on;
            };

            dcdc2_reg: dcdc2 {
                /* CPU 코어 전압 — DVFS 대상 */
                regulator-name = "vdd_mpu";
                regulator-min-microvolt = <925000>;
                regulator-max-microvolt = <1325000>;
                regulator-always-on;
                regulator-boot-on;
            };

            dcdc3_reg: dcdc3 {
                regulator-name = "vdd_core";
                regulator-min-microvolt = <1100000>;
                regulator-max-microvolt = <1100000>;
                regulator-always-on;
                regulator-boot-on;
            };

            ldo1_reg: ldo1 {
                regulator-name = "vio";
                regulator-min-microvolt = <1800000>;
                regulator-max-microvolt = <1800000>;
                regulator-always-on;
                regulator-boot-on;
            };

            ldo2_reg: ldo2 {
                regulator-name = "vdd_3v3aux";
                regulator-min-microvolt = <3300000>;
                regulator-max-microvolt = <3300000>;
                regulator-always-on;
                regulator-boot-on;
            };

            ldo3_reg: ldo3 {
                /* 고정 전압 — 내부 디지털 코어용 */
                regulator-name = "vdd_1v8";
                regulator-min-microvolt = <1800000>;
                regulator-max-microvolt = <1800000>;
                regulator-always-on;
                regulator-boot-on;
            };

            ldo4_reg: ldo4 {
                regulator-name = "vdd_3v3a";
                regulator-min-microvolt = <3300000>;
                regulator-max-microvolt = <3300000>;
                regulator-always-on;
                regulator-boot-on;
            };
        };
    };
};

/* 소비자 측: CPU에서 DCDC2를 DVFS 전원으로 참조 */
&cpu0 {
    cpu0-supply = <&dcdc2_reg>;
};

실전 DTS 예시: i.MX8 보드

/* i.MX8M Mini EVK — PCA9450 PMIC 바인딩 */

&i2c1 {
    pmic: pca9450@25 {
        compatible = "nxp,pca9450c";
        reg = <0x25>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_pmic>;
        interrupt-parent = <&gpio1>;
        interrupts = <3 IRQ_TYPE_LEVEL_LOW>;

        regulators {
            buck1: BUCK1 {
                regulator-name = "BUCK1";
                regulator-min-microvolt = <600000>;
                regulator-max-microvolt = <2187500>;
                regulator-boot-on;
                regulator-always-on;
                regulator-ramp-delay = <3125>;
            };

            buck4: BUCK4 {
                regulator-name = "BUCK4";
                regulator-min-microvolt = <600000>;
                regulator-max-microvolt = <3400000>;
                regulator-boot-on;
                regulator-always-on;
            };

            ldo1: LDO1 {
                regulator-name = "LDO1";
                regulator-min-microvolt = <1600000>;
                regulator-max-microvolt = <3300000>;
                regulator-boot-on;
                regulator-always-on;
            };
        };
    };
};

/* GPU 소비자: 별도의 전원 레일 참조 */
&gpu {
    vin-supply = <&buck4>;
};
DT 바인딩 문서 위치: 각 PMIC의 상세 바인딩 규격은 커널 소스의 Documentation/devicetree/bindings/regulator/ 디렉터리에 YAML 또는 텍스트 파일로 정의되어 있습니다. 새로운 PMIC 드라이버를 작성할 때 반드시 바인딩 문서를 함께 작성해야 합니다.

결합 레귤레이터 (Regulator Coupling)

일부 SoC는 두 개 이상의 레귤레이터가 전압 차이 제약(voltage spread constraint)을 가집니다. 예를 들어 CPU 코어 전압과 GPU 코어 전압의 차이가 일정 범위를 넘으면 안 되는 경우가 있으며, 이를 위해 커널은 Regulator Coupling 메커니즘을 제공합니다.

결합 레귤레이터의 필요성

서로 다른 전원 도메인의 레귤레이터가 물리적으로 같은 전원 평면(power plane)을 공유하거나, 크로스토크(crosstalk) 문제가 있을 때 결합 제약이 필요합니다. 한쪽 전압만 급격히 변경하면 다른 도메인에 과도 응답(transient)이 발생하여 시스템 불안정을 야기할 수 있습니다.

/* Device Tree에서 결합 레귤레이터 지정 */
cpu_vdd: regulator-cpu {
    compatible = "regulator-fixed";
    regulator-name = "vdd_cpu";
    regulator-coupled-with = <&gpu_vdd>;
    regulator-coupled-max-spread = <150000>;  /* 최대 150mV 차이 허용 */
    regulator-min-microvolt = <800000>;
    regulator-max-microvolt = <1200000>;
};

gpu_vdd: regulator-gpu {
    compatible = "regulator-fixed";
    regulator-name = "vdd_gpu";
    regulator-coupled-with = <&cpu_vdd>;
    regulator-coupled-max-spread = <150000>;
    regulator-min-microvolt = <800000>;
    regulator-max-microvolt = <1200000>;
};

커스텀 Coupler 구현

기본 결합 로직으로 충분하지 않은 경우, 플랫폼별 커스텀 coupler를 등록할 수 있습니다. 대표적인 예가 Qualcomm의 CPR(Corner Power Reduction) 메커니즘입니다.

/* === 커스텀 regulator_coupler 구현 예시 === */

static int my_coupler_balance_voltage(
    struct regulator_coupler *coupler,
    struct regulator_dev *rdev,
    struct regulator_dev **c_rdevs,
    int n_coupled)
{
    int i, max_spread, ret;
    int desired_uV, coupled_uV;

    /* 현재 레귤레이터의 요청 전압 */
    desired_uV = regulator_get_voltage_rdev(rdev);

    /* 결합된 모든 레귤레이터와의 전압 차이 검증 */
    for (i = 0; i < n_coupled; i++) {
        if (c_rdevs[i] == rdev)
            continue;

        coupled_uV = regulator_get_voltage_rdev(c_rdevs[i]);
        max_spread = regulator_get_max_spread(rdev, c_rdevs[i]);

        if (abs(desired_uV - coupled_uV) > max_spread) {
            /* 결합 파트너도 함께 전압 조정 필요 */
            int new_coupled = desired_uV > coupled_uV
                ? desired_uV - max_spread
                : desired_uV + max_spread;
            ret = regulator_set_voltage_rdev(c_rdevs[i],
                    new_coupled, new_coupled);
            if (ret)
                return ret;
        }
    }

    return regulator_do_balance_voltage(rdev, desired_uV);
}

static struct regulator_coupler my_coupler = {
    .balance_voltage = my_coupler_balance_voltage,
};

/* 초기화 시 coupler 등록 */
static int __init my_coupler_init(void)
{
    return regulator_coupler_register(&my_coupler);
}
arch_initcall(my_coupler_init);
Qualcomm CPR: Qualcomm SoC에서는 CPU/GPU 전압 도메인 간 결합을 drivers/regulator/qcom-rpmh-regulator.c에서 관리합니다. 실리콘 공정 편차에 따라 개별 다이마다 최적 전압이 다르므로, CPR이 런타임에 전압을 미세 조정하며 이때 결합 제약을 함께 만족시킵니다.

전원 도메인 계층 구조

임베디드 시스템에서 전원은 단순히 "켜고 끄는" 것이 아니라 계층적 의존 관계를 가집니다. 부모 레귤레이터가 비활성화되면 자식 레귤레이터도 전원이 차단되며, 이 관계를 프레임워크가 자동으로 추적·관리합니다.

Consumer-Provider 전원 도메인 계층 Supply Layer Regulator Layer Consumer Layer Main Supply Battery / DC-In BUCK1 (1.1V) use_count: 3 always_on: true BUCK2 (3.3V) use_count: 2 always_on: false LDO1 (1.8V) use_count: 1 vin: BUCK2 parent CPU Core 0 CPU Core 1 L2 Cache UART / SPI SD Card ADC / PLL 전원 도메인 규칙: 1. 부모 레귤레이터가 disable되면 모든 자식 소비자도 전원 차단 2. use_count가 0이 되어야만 실제 disable 수행 (참조 카운트 관리) 3. 카스케이드 공급(BUCK2→LDO1)에서 BUCK2 disable 시 LDO1도 자동 비활성화

genpd (Generic Power Domain) 연동

Regulator 프레임워크는 genpd(Generic Power Domain) 서브시스템과 연동하여 하드웨어 전원 도메인의 ON/OFF를 제어합니다. genpd는 SoC의 물리적 전원 아일랜드(power island)를 관리하며, 레귤레이터와 결합하면 더 세밀한 전원 관리가 가능합니다.

/* === genpd + Regulator 연동 패턴 === */

/* genpd power_on 콜백에서 레귤레이터 활성화 */
static int my_pd_power_on(struct generic_pm_domain *domain)
{
    struct my_power_domain *pd = to_my_pd(domain);
    int ret;

    /* 1. 공급 레귤레이터 활성화 */
    ret = regulator_enable(pd->supply);
    if (ret) {
        dev_err(pd->dev, "supply enable failed: %d\n", ret);
        return ret;
    }

    /* 2. 전원 안정화 대기 */
    usleep_range(100, 200);

    /* 3. 클록 활성화 */
    ret = clk_prepare_enable(pd->clk);
    if (ret) {
        regulator_disable(pd->supply);
        return ret;
    }

    return 0;
}

/* genpd power_off 콜백에서 레귤레이터 비활성화 */
static int my_pd_power_off(struct generic_pm_domain *domain)
{
    struct my_power_domain *pd = to_my_pd(domain);

    clk_disable_unprepare(pd->clk);
    regulator_disable(pd->supply);

    return 0;
}

Runtime PM과 Regulator 상호작용

디바이스 드라이버의 Runtime PM 콜백 안에서 레귤레이터를 제어하면, 디바이스가 실제로 사용될 때만 전원을 공급하여 전력 소비를 최소화할 수 있습니다.

/* === Runtime PM + Regulator 패턴 (센서 드라이버 예시) === */

static int sensor_runtime_resume(struct device *dev)
{
    struct sensor_data *data = dev_get_drvdata(dev);
    int ret;

    /* 센서 전원 ON */
    ret = regulator_enable(data->vdd);
    if (ret)
        return ret;

    ret = regulator_enable(data->vddio);
    if (ret) {
        regulator_disable(data->vdd);
        return ret;
    }

    /* 센서 초기화 시퀀스 (전원 안정화 후) */
    usleep_range(5000, 10000);
    sensor_hw_init(data);

    return 0;
}

static int sensor_runtime_suspend(struct device *dev)
{
    struct sensor_data *data = dev_get_drvdata(dev);

    /* 역순으로 전원 OFF */
    regulator_disable(data->vddio);
    regulator_disable(data->vdd);

    return 0;
}

static const struct dev_pm_ops sensor_pm_ops = {
    SET_RUNTIME_PM_OPS(sensor_runtime_suspend,
                       sensor_runtime_resume, NULL)
};
전원 시퀀싱 주의: 다수의 레귤레이터를 사용하는 디바이스에서는 전원 투입 순서(power-on sequence)가 중요합니다. 일반적으로 코어 전원(vdd) → I/O 전원(vddio) 순서로 켜고, 끌 때는 역순입니다. 순서가 뒤바뀌면 래치업(latch-up)이나 누설 전류 경로가 생길 수 있습니다.

DVFS 연동 (Dynamic Voltage Frequency Scaling)

DVFS(Dynamic Voltage Frequency Scaling)는 프로세서의 부하에 따라 동작 주파수와 공급 전압을 동적으로 조정하여 전력 소비를 최적화하는 기술입니다. 리눅스 커널에서 DVFS는 cpufreq 서브시스템, OPP(Operating Performance Points) 테이블, 그리고 Regulator 프레임워크의 긴밀한 협력으로 구현됩니다.

DVFS 전압/주파수 조정 흐름 cpufreq Governor (ondemand / schedutil) 부하 모니터링 → OPP 선택 target_freq cpufreq Driver dev_pm_opp_set_rate() OPP 테이블 조회 OPP Table 300 MHz → 0.90V 600 MHz → 0.95V 1000 MHz → 1.10V ← current 1200 MHz → 1.20V 1500 MHz → 1.35V lookup set_voltage set_rate Regulator Framework regulator_set_voltage() Clock Framework clk_set_rate() PMIC (I2C/SPI) 전압 레지스터 기록 PLL / Clock Divider 주파수 레지스터 기록 DVFS 전압/주파수 변경 순서: 주파수 상승 시: 전압 먼저 올리고 → 주파수 변경 | 주파수 하강 시: 주파수 먼저 낮추고 → 전압 변경

OPP 테이블과 Regulator 연동

OPP 테이블은 주파수-전압 쌍을 Device Tree로 정의합니다. cpufreq 드라이버가 OPP를 변경할 때, 내부적으로 regulator_set_voltage()를 호출하여 전압을 조정합니다.

/* Device Tree OPP 테이블 정의 */
cpu0_opp_table: opp-table {
    compatible = "operating-points-v2";
    opp-shared;    /* 모든 CPU 코어가 공유 */

    opp-300000000 {
        opp-hz = /bits/ 64 <300000000>;
        opp-microvolt = <900000 900000 1200000>;
        /*              target   min      max */
    };

    opp-600000000 {
        opp-hz = /bits/ 64 <600000000>;
        opp-microvolt = <950000 950000 1200000>;
    };

    opp-1000000000 {
        opp-hz = /bits/ 64 <1000000000>;
        opp-microvolt = <1100000 1100000 1200000>;
    };

    opp-1200000000 {
        opp-hz = /bits/ 64 <1200000000>;
        opp-microvolt = <1200000 1200000 1350000>;
        opp-suspend;     /* suspend 시 사용할 OPP */
    };

    opp-1500000000 {
        opp-hz = /bits/ 64 <1500000000>;
        opp-microvolt = <1350000 1350000 1350000>;
        turbo-mode;      /* 터보 부스트 전용 */
    };
};

&cpu0 {
    operating-points-v2 = <&cpu0_opp_table>;
    cpu-supply = <&dcdc2_reg>;   /* DVFS 제어할 레귤레이터 */
};

DVFS 커널 API 흐름

/* === cpufreq 드라이버에서 DVFS 수행 흐름 === */

/* dev_pm_opp_set_rate()는 내부적으로 다음 단계를 수행: */
/*
 * 1. OPP 테이블에서 target_freq에 해당하는 전압 조회
 * 2. 주파수 상승 시: 전압을 먼저 올림 (regulator_set_voltage)
 * 3. 클록 주파수 변경 (clk_set_rate)
 * 4. 주파수 하강 시: 전압을 나중에 낮춤 (regulator_set_voltage)
 */

static int my_cpufreq_set_target(struct cpufreq_policy *policy,
                                unsigned int target_freq,
                                unsigned int relation)
{
    struct device *cpu_dev = get_cpu_device(policy->cpu);
    int ret;

    /* OPP 기반 전압+주파수 동시 설정 (순서 자동 관리) */
    ret = dev_pm_opp_set_rate(cpu_dev, target_freq * 1000);
    if (ret)
        dev_err(cpu_dev, "DVFS failed for %u kHz: %d\n",
                target_freq, ret);

    return ret;
}

/* 수동으로 전압-주파수 순서를 관리하는 경우 */
static int manual_dvfs(struct device *dev,
                       unsigned long old_freq,
                       unsigned long new_freq,
                       int old_uV, int new_uV)
{
    int ret;

    if (new_freq > old_freq) {
        /* 주파수 상승: 전압 먼저 올림 (언더볼트 방지) */
        ret = regulator_set_voltage(supply, new_uV, new_uV);
        if (ret) return ret;
        ret = clk_set_rate(clk, new_freq);
    } else {
        /* 주파수 하강: 주파수 먼저 낮춤 (과전압 방지) */
        ret = clk_set_rate(clk, new_freq);
        if (ret) return ret;
        ret = regulator_set_voltage(supply, new_uV, new_uV);
    }

    return ret;
}
Energy-Aware Scheduling(EAS) 연동: 커널 5.0 이후의 EAS는 각 OPP의 에너지 효율(성능/와트)을 고려하여 태스크를 적절한 CPU 코어에 배치합니다. em_dev_register_perf_domain()을 통해 에너지 모델이 등록되면, 스케줄러가 주파수 변경 시 regulator_set_voltage의 에너지 비용까지 고려하여 최적의 OPP를 선택합니다.
DVFS 관련 함수설명사용 위치
dev_pm_opp_set_rate()OPP 기반 전압+주파수 동시 설정cpufreq 드라이버
dev_pm_opp_find_freq_ceil()지정 주파수 이상의 OPP 검색OPP 테이블 조회
dev_pm_opp_find_freq_floor()지정 주파수 이하의 OPP 검색OPP 테이블 조회
dev_pm_opp_get_voltage()OPP의 전압 값 반환수동 DVFS 구현
regulator_set_voltage()레귤레이터 전압 직접 설정저수준 전압 제어
em_dev_register_perf_domain()EAS 에너지 모델 등록플랫폼 드라이버

Regulator 이벤트 알림

Regulator 프레임워크는 전압/전류 이상 상태를 소비자에게 알리기 위한 이벤트 알림(notifier) 체인을 제공합니다. PMIC가 과전압(OVP), 저전압(UVP), 과전류(OCP), 과열(OTP) 등을 감지하면 IRQ를 통해 커널에 알리고, 프레임워크가 등록된 소비자 콜백을 호출합니다.

Regulator 이벤트 알림 흐름 PMIC 하드웨어 OVP/UVP/OCP 감지 IRQ PMIC IRQ Handler regulator_notifier_call_chain() Regulator Core notifier_chain 순회 모든 소비자 콜백 호출 Consumer A emergency_stop() Consumer B reduce_load() Consumer C power_lost() 흐름: PMIC HW 이상 → IRQ → PMIC 드라이버 → regulator_notifier_call_chain() → 소비자 콜백 반환값: NOTIFY_OK (정상), NOTIFY_DONE (무관), NOTIFY_BAD (처리 실패)

REGULATOR_EVENT 유형

이벤트설명일반적 대응
REGULATOR_EVENT_UNDER_VOLTAGE0x01전압이 하한 미만디바이스 비활성화, 에러 로그
REGULATOR_EVENT_OVER_CURRENT0x02전류가 상한 초과부하 차단, 보호 동작
REGULATOR_EVENT_REGULATION_OUT0x04출력 전압 정밀도 범위 이탈재조정 시도
REGULATOR_EVENT_FAIL0x08레귤레이터 하드웨어 장애시스템 긴급 종료
REGULATOR_EVENT_OVER_TEMP0x10PMIC/레귤레이터 과열부하 경감, 쓰로틀링
REGULATOR_EVENT_FORCE_DISABLE0x20하드웨어에 의한 강제 비활성화소비자에게 전원 상실 알림
REGULATOR_EVENT_VOLTAGE_CHANGE0x40전압 변경 완료소비자 재보정
REGULATOR_EVENT_DISABLE0x80레귤레이터 비활성화됨소비자 상태 정리
REGULATOR_EVENT_PRE_VOLTAGE_CHANGE0x100전압 변경 전 사전 알림캐시 플러시, DMA 중단
REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE0x200전압 변경 취소이전 상태 복원

Notifier 등록 및 핸들링

/* === Regulator 이벤트 Notifier 등록/처리 === */

/* 이벤트 콜백 함수 */
static int my_regulator_event(struct notifier_block *nb,
                              unsigned long event, void *data)
{
    struct my_device *mydev = container_of(nb,
                    struct my_device, reg_nb);

    switch (event) {
    case REGULATOR_EVENT_UNDER_VOLTAGE:
        dev_err(mydev->dev, "under-voltage detected!\n");
        /* 하드웨어를 안전 상태로 전환 */
        my_device_emergency_stop(mydev);
        break;

    case REGULATOR_EVENT_OVER_CURRENT:
        dev_warn(mydev->dev, "over-current detected!\n");
        /* 부하 경감 시도 */
        my_device_reduce_load(mydev);
        break;

    case REGULATOR_EVENT_OVER_TEMP:
        dev_crit(mydev->dev, "thermal shutdown imminent!\n");
        /* 쓰로틀링 모드 진입 */
        my_device_throttle(mydev);
        break;

    case REGULATOR_EVENT_FORCE_DISABLE:
        dev_err(mydev->dev, "supply force-disabled!\n");
        /* 전원 상실에 대한 긴급 정리 */
        my_device_power_lost(mydev);
        break;

    case REGULATOR_EVENT_PRE_VOLTAGE_CHANGE:
        dev_dbg(mydev->dev, "voltage change pending\n");
        /* DMA 전송 완료 대기, 캐시 플러시 */
        my_device_prepare_voltage_change(mydev);
        break;

    default:
        dev_dbg(mydev->dev, "regulator event: 0x%lx\n", event);
    }

    return NOTIFY_OK;
}

/* probe에서 notifier 등록 */
static int my_device_probe(struct platform_device *pdev)
{
    struct my_device *mydev;
    int ret;

    mydev = devm_kzalloc(&pdev->dev, sizeof(*mydev), GFP_KERNEL);
    mydev->dev = &pdev->dev;

    mydev->supply = devm_regulator_get(&pdev->dev, "vdd");
    if (IS_ERR(mydev->supply))
        return dev_err_probe(&pdev->dev, PTR_ERR(mydev->supply),
                             "failed to get supply\n");

    /* notifier 콜백 설정 및 등록 */
    mydev->reg_nb.notifier_call = my_regulator_event;
    ret = devm_regulator_register_notifier(mydev->supply,
                                           &mydev->reg_nb);
    if (ret)
        dev_warn(&pdev->dev, "notifier registration failed\n");

    return 0;
}

Provider 측 이벤트 발생

/* === PMIC 드라이버의 IRQ 핸들러에서 이벤트 알림 === */

static irqreturn_t pmic_ovp_irq_handler(int irq, void *data)
{
    struct regulator_dev *rdev = data;

    /* 프레임워크를 통해 모든 소비자에게 이벤트 전파 */
    regulator_notifier_call_chain(rdev,
            REGULATOR_EVENT_OVER_CURRENT, NULL);

    return IRQ_HANDLED;
}
devm 자동 정리: devm_regulator_register_notifier()를 사용하면 드라이버 언바인드 시 notifier가 자동으로 해제됩니다. 수동으로 regulator_unregister_notifier()를 호출할 필요가 없어 리소스 누수를 방지합니다.

동작 모드 (Operating Modes)

레귤레이터는 부하 상태에 따라 동작 모드(operating mode)를 전환하여 전력 효율을 최적화할 수 있습니다. 고부하 시에는 빠른 과도 응답을 위해 고전력 모드를 사용하고, 경부하 시에는 소비 전력을 줄이기 위해 에코(ECO) 모드로 전환합니다.

Regulator 상태 머신 (동작 모드 전이) DISABLED 출력 OFF STANDBY 최소 전력, 느린 응답 IDLE 저전력, 중간 응답 NORMAL 표준 동작 FAST 고성능, 빠른 과도응답 BYPASS Vin ≈ Vout 패스스루 enable disable 부하 증가 부하 감소 suspend resume 고성능 표준 bypass DRMS: regulator_set_load()로 부하를 알리면 프레임워크가 자동으로 최적 모드 선택 BYPASS: LDO에서 Vin≈Vout일 때 드롭아웃 손실 없이 패스스루 (하드웨어 지원 필요)

모드 관련 API

/* === Regulator 동작 모드 API === */

/* 모드 직접 설정 */
ret = regulator_set_mode(regulator, REGULATOR_MODE_FAST);
ret = regulator_set_mode(regulator, REGULATOR_MODE_NORMAL);
ret = regulator_set_mode(regulator, REGULATOR_MODE_IDLE);
ret = regulator_set_mode(regulator, REGULATOR_MODE_STANDBY);

/* 현재 모드 조회 */
unsigned int mode = regulator_get_mode(regulator);

/* DRMS: 부하 전류 알림 → 자동 모드 전환 */
/* 소비자가 예상 부하를 프레임워크에 알림 */
ret = regulator_set_load(regulator, 100000);  /* 100mA 부하 */
/*
 * 프레임워크가 모든 소비자의 부하 합산 후
 * regulator_ops의 get_optimum_mode()를 호출하여
 * 최적 모드를 자동 결정·적용
 */

/* Provider 측: get_optimum_mode 구현 */
static unsigned int my_get_optimum_mode(
    struct regulator_dev *rdev,
    int input_uV, int output_uV, int load_uA)
{
    if (load_uA < 10000)         /* < 10mA */
        return REGULATOR_MODE_IDLE;
    else if (load_uA < 300000)  /* < 300mA */
        return REGULATOR_MODE_NORMAL;
    else
        return REGULATOR_MODE_FAST;
}
모드상수전력 소비과도 응답용도
FASTREGULATOR_MODE_FAST높음빠름 (수 µs)CPU 터보, GPU 렌더링
NORMALREGULATOR_MODE_NORMAL중간표준일반 동작 상태
IDLEREGULATOR_MODE_IDLE낮음느림 (수십 µs)경부하/대기 상태
STANDBYREGULATOR_MODE_STANDBY최소매우 느림시스템 sleep/suspend
DRMS 활용 팁: 소비자 드라이버에서 regulator_set_load()를 적극 활용하세요. 예를 들어 디스플레이 드라이버에서 화면 ON 시 고부하(200mA)를, 화면 OFF 시 저부하(0µA)를 알리면, PMIC가 자동으로 모드를 전환하여 대기 전력을 크게 절감할 수 있습니다.

벌크 Regulator API

다수의 전원 레일을 사용하는 디바이스(디스플레이 패널, 카메라 모듈, SoC 등)에서는 레귤레이터를 하나씩 관리하는 대신 벌크(bulk) API를 사용하여 코드를 간결하게 유지할 수 있습니다.

Bulk API 함수 목록

함수설명
regulator_bulk_get()다수의 레귤레이터를 한 번에 획득
devm_regulator_bulk_get()devm 버전 (자동 해제)
regulator_bulk_enable()모든 레귤레이터를 한 번에 활성화
regulator_bulk_disable()모든 레귤레이터를 한 번에 비활성화
regulator_bulk_free()비-devm 레귤레이터 일괄 해제
regulator_bulk_set_supply_names()supply 이름 배열 설정 (v6.1+)

디스플레이 패널 예시

/* === Bulk Regulator API — 디스플레이 패널 드라이버 === */

#define NUM_SUPPLIES 3

struct panel_data {
    struct regulator_bulk_data supplies[NUM_SUPPLIES];
    struct gpio_desc *reset_gpio;
    /* ... */
};

static int panel_probe(struct mipi_dsi_device *dsi)
{
    struct panel_data *panel;
    int ret;

    panel = devm_kzalloc(&dsi->dev, sizeof(*panel), GFP_KERNEL);

    /* supply 이름 배열 설정 */
    panel->supplies[0].supply = "vdd";     /* 코어 전원 */
    panel->supplies[1].supply = "vddio";   /* I/O 전원 */
    panel->supplies[2].supply = "avdd";    /* 아날로그 전원 */

    /* 한 번에 3개 레귤레이터 획득 (devm 자동 해제) */
    ret = devm_regulator_bulk_get(&dsi->dev,
                                  NUM_SUPPLIES,
                                  panel->supplies);
    if (ret)
        return dev_err_probe(&dsi->dev, ret,
                             "failed to get supplies\n");

    return 0;
}

static int panel_enable(struct panel_data *panel)
{
    int ret;

    /* 3개 레귤레이터 한 번에 활성화 */
    ret = regulator_bulk_enable(NUM_SUPPLIES, panel->supplies);
    if (ret) {
        dev_err(panel->dev, "supply enable failed: %d\n", ret);
        return ret;
    }

    /* 전원 안정화 대기 후 리셋 해제 */
    usleep_range(10000, 15000);
    gpiod_set_value_cansleep(panel->reset_gpio, 0);
    usleep_range(5000, 10000);

    return 0;
}

static int panel_disable(struct panel_data *panel)
{
    gpiod_set_value_cansleep(panel->reset_gpio, 1);
    usleep_range(1000, 2000);

    /* 3개 레귤레이터 한 번에 비활성화 */
    regulator_bulk_disable(NUM_SUPPLIES, panel->supplies);

    return 0;
}

Camera 모듈 예시 (다중 전원 레일)

/* === Camera 센서 드라이버 — 4개 전원 레일 === */

static const char * const imx219_supply_names[] = {
    "VANA",   /* 아날로그 전원 2.8V */
    "VDIG",   /* 디지털 코어 1.2V */
    "VDDL",   /* I/O 전원 1.8V */
    "VDDA",   /* AF 액추에이터 2.8V */
};

#define IMX219_NUM_SUPPLIES ARRAY_SIZE(imx219_supply_names)

struct imx219 {
    struct regulator_bulk_data supplies[IMX219_NUM_SUPPLIES];
    struct clk *xclk;
    struct gpio_desc *reset;
};

static int imx219_power_on(struct device *dev)
{
    struct imx219 *imx = dev_get_drvdata(dev);
    int ret;

    ret = regulator_bulk_enable(IMX219_NUM_SUPPLIES, imx->supplies);
    if (ret) {
        dev_err(dev, "Failed to enable supplies: %d\n", ret);
        return ret;
    }

    ret = clk_prepare_enable(imx->xclk);
    if (ret) {
        regulator_bulk_disable(IMX219_NUM_SUPPLIES, imx->supplies);
        return ret;
    }

    /* 데이터시트: 전원 안정화 후 1ms 대기 → 리셋 해제 */
    usleep_range(1000, 2000);
    gpiod_set_value_cansleep(imx->reset, 0);
    usleep_range(6000, 8000);   /* T3: 리셋 후 초기화 시간 */

    return 0;
}
Bulk vs 개별 API 선택 기준:
  • 전원 레일이 2개 이하이고 시퀀싱이 중요하면 → 개별 devm_regulator_get()
  • 전원 레일이 3개 이상이고 동시에 켜/끄기 가능하면 → devm_regulator_bulk_get()
  • 전원 투입 순서가 레일마다 달라야 하면 → 개별 API로 순서 제어

Regulator Constraints 심화

Regulator 프레임워크의 constraints 메커니즘은 소비자가 레귤레이터를 오용하는 것을 방지하는 보안/안전 경계입니다. 소비자가 요청한 전압/전류/모드가 constraints 범위를 벗어나면 프레임워크가 -EPERM 또는 -EINVAL로 거부합니다.

machine_constraints 구조체

/* === regulation_constraints 주요 필드 === */

struct regulation_constraints {
    const char *name;

    /* 전압 범위 제약 */
    int min_uV;                /* 소비자가 요청 가능한 최소 전압 */
    int max_uV;                /* 소비자가 요청 가능한 최대 전압 */
    int uV_offset;             /* 전압 보정 오프셋 */

    /* 전류 범위 제약 */
    int min_uA;
    int max_uA;

    /* 허용 작업 마스크 */
    unsigned int valid_ops_mask;
    /*
     * REGULATOR_CHANGE_VOLTAGE  — 전압 변경 허용
     * REGULATOR_CHANGE_CURRENT  — 전류 변경 허용
     * REGULATOR_CHANGE_MODE     — 모드 변경 허용
     * REGULATOR_CHANGE_STATUS   — 활성화/비활성화 허용
     * REGULATOR_CHANGE_DRMS     — 동적 모드 전환 허용
     * REGULATOR_CHANGE_BYPASS   — 바이패스 모드 허용
     */

    /* 허용 모드 마스크 */
    unsigned int valid_modes_mask;

    /* 초기 상태 */
    unsigned int always_on      : 1;  /* 항상 활성 유지 */
    unsigned int boot_on        : 1;  /* 부팅 시 활성 유지 */
    unsigned int apply_uV      : 1;  /* 초기 전압 즉시 적용 */
    unsigned int pull_down      : 1;  /* 비활성화 시 풀다운 */
    unsigned int over_current_protection : 1;

    /* Suspend 상태별 제약 */
    struct regulator_state state_mem;    /* suspend-to-RAM */
    struct regulator_state state_disk;   /* suspend-to-disk */
    struct regulator_state state_standby; /* standby */

    /* 전압 변경 속도 */
    unsigned int ramp_delay;              /* µV/µs */
    unsigned int settling_time_up;        /* 상승 정착 시간 (µs) */
    unsigned int settling_time_down;      /* 하강 정착 시간 (µs) */
    unsigned int enable_time;             /* 활성화 시간 (µs) */
};

Constraints 검증 흐름

Regulator Constraints 검증 흐름 Consumer set_voltage(min, max) 1. ops 마스크 CHANGE_VOLTAGE 비트 확인 2. 범위 교집합 constraints ∩ consumer 요청 3. 다중 소비자 모든 소비자의 요청 범위 조율 4. HW 스텝 정렬 uV_step 단위 반올림 5. 하드웨어 적용 regmap 레지스터 기록 6. ramp 대기 ramp_delay × ΔV -EPERM -EINVAL 검증 실패 시: • ops 마스크 불일치 → -EPERM (권한 없음) • 범위 교집합 없음 → -EINVAL (유효하지 않은 범위)

소비자가 regulator_set_voltage()를 호출하면 프레임워크는 다음 순서로 검증합니다:

  1. valid_ops_mask 확인REGULATOR_CHANGE_VOLTAGE 비트가 없으면 -EPERM
  2. 범위 교집합 계산 — 소비자 요청 범위와 constraints의 min_uV/max_uV 교집합
  3. 복수 소비자 조율 — 다른 소비자의 요청 범위와도 교집합 (가장 좁은 범위 선택)
  4. 하드웨어 스텝 정렬 — 실제 하드웨어가 설정 가능한 가장 가까운 값으로 정렬
  5. ramp_delay 적용 — 전압 변경 후 안정화 대기
보안상 중요한 constraints 설정:
  • CPU 코어 전압valid_ops_mask에서 REGULATOR_CHANGE_VOLTAGE만 허용하고, REGULATOR_CHANGE_STATUS는 제거하여 소비자가 임의로 끌 수 없도록 합니다
  • DDR 전원always_on 필수. 메모리 전원이 끊기면 데이터 손실
  • 보안 키 저장소valid_ops_mask = 0으로 설정하여 어떤 소비자도 제어 불가

Regulator 성능 최적화

전원 관리 경로는 시스템 전반의 성능과 반응성에 직접 영향을 미칩니다. 특히 DVFS 경로에서 regulator_set_voltage()의 지연 시간은 주파수 전환 속도를 결정하는 핵심 요소입니다.

I2C 병목 최소화

Regulator I2C/regcache 최적화 경로 Fast Path (캐시 히트) get_voltage() regcache (RAM) ~0 µs I2C 불필요 Slow Path (I2C 트랜잭션) set_voltage() regmap write I2C Bus 100~400kHz 100~500 µs + ramp delay SPI PMIC (고속 대안) SPI Bus (MHz) 1~10 µs + ramp delay

대부분의 PMIC는 I2C로 통신하며, I2C 트랜잭션은 수백 µs가 소요됩니다. DVFS의 critical path에서 I2C 지연은 심각한 성능 저하를 유발할 수 있습니다.

최적화 기법설명효과
regmap 캐시RBTREE/FLAT 캐시로 읽기 트랜잭션 제거get_voltage 시 I2C 접근 불필요
regcache_syncsuspend/resume 시 변경된 레지스터만 동기화resume 시간 단축
bulk writeregmap_bulk_write()로 연속 레지스터 한 번에 기록I2C 트랜잭션 수 감소
Fast-mode I2CI2C 클록을 400kHz 또는 1MHz로 설정개별 트랜잭션 시간 단축
SPI PMIC고속 SPI 버스 사용 (수 MHz)I2C 대비 10~100배 빠른 통신
bypass 모드Vin≈Vout일 때 레귤레이터 우회LDO 전력 손실 제거

regcache 최적화 예시

/* === regcache를 활용한 resume 최적화 === */

static int pmic_suspend(struct device *dev)
{
    struct my_pmic *pmic = dev_get_drvdata(dev);

    /* 캐시 마킹: 모든 레지스터를 "dirty" 상태로 */
    regcache_mark_dirty(pmic->regmap);
    /* 캐시만 사용하도록 전환 (I2C 접근 차단) */
    regcache_cache_only(pmic->regmap, true);

    return 0;
}

static int pmic_resume(struct device *dev)
{
    struct my_pmic *pmic = dev_get_drvdata(dev);

    /* I2C 접근 재개 */
    regcache_cache_only(pmic->regmap, false);
    /* 변경된(dirty) 레지스터만 하드웨어에 다시 기록 */
    regcache_sync(pmic->regmap);

    return 0;
}

전압 변경 지연 시간 프로파일링

# === Regulator 전압 변경 지연 시간 측정 ===

# ftrace로 regulator 이벤트 추적
echo 1 > /sys/kernel/debug/tracing/events/regulator/enable
echo 1 > /sys/kernel/debug/tracing/tracing_on

# 부하 변경을 유발하는 작업 수행 (예: CPU 주파수 변경)
echo performance > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor
sleep 1
echo powersave > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor

# 트레이스 확인
cat /sys/kernel/debug/tracing/trace | grep regulator
# 출력 예:
# regulator_set_voltage: name=vdd_cpu (1100000 <=> 1100000)
# regulator_set_voltage_complete: name=vdd_cpu, val=950000
# 타임스탬프 차이 = 실제 전압 변경 지연 시간

# 전체 regulator 상태 덤프
cat /sys/kernel/debug/regulator/regulator_summary
DVFS 지연 시간 목표: 모바일 SoC에서 전압 전환은 일반적으로 50~200µs 이내여야 합니다. I2C PMIC의 경우 트랜잭션 자체가 100µs 이상 소요될 수 있으므로, DVFS가 빈번한 경우 SPI PMIC나 regmap 캐시를 적극 활용하세요.

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.