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/작업 분할 — 즉시 처리와 지연 처리를 구분합니다.
- 안전 한계 — 전원/열/타이밍 임계값을 함께 관리합니다.
- 운영 복구 — 오류 시 재초기화와 롤백 경로를 준비합니다.
단계별 이해
- 장치 수명주기 확인
probe부터 remove까지 흐름을 점검합니다. - 비동기 경로 설계
IRQ, 워크큐, 타이머 역할을 분리합니다. - 자원 정합성 검증
DMA/클록/전원 참조를 교차 확인합니다. - 현장 조건 테스트
연결 끊김/복구/부하 상황을 재현합니다.
전원 트리에서 Regulator의 역할
Regulator는 시스템 전원 트리에서 "전압/전류 정책 계층"을 담당합니다. 소비자 드라이버는 필요한 전압 범위만 요청하고, 실제 PMIC 제어와 제약 검증은 프레임워크가 수행해 보드 의존성을 크게 줄입니다.
Regulator 서브시스템
Regulator 서브시스템(drivers/regulator/)은 시스템의 전압 및 전류 공급 장치(레귤레이터)를 추상화하여 관리하는 프레임워크입니다. PMIC(Power Management IC), DC-DC 컨버터, LDO(Low-Dropout Regulator), 스위치 레귤레이터 등 다양한 전원 공급 장치를 통합 관리하며, 디바이스 드라이버가 필요한 전압/전류를 요청하면 프레임워크가 하드웨어 제약 조건을 검증하고 적용합니다.
핵심 데이터 구조
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_VOLTAGE | regulator_set_voltage() |
REGULATOR_CHANGE_CURRENT | regulator_set_current_limit() |
REGULATOR_CHANGE_MODE | regulator_set_mode() |
REGULATOR_CHANGE_STATUS | regulator_enable() / regulator_disable() |
REGULATOR_CHANGE_DRMS | Dynamic 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/A | N/A | 전원 도메인 ON/OFF 제어 |
| PWM Regulator | PWM 듀티로 전압 제어 | 중간 | 중간 | 간이 전압 조절 (저비용 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 콜백에서 재설정 |
- 전원 시퀀싱 — 디바이스마다 전원 투입 순서(power-on sequence)가 다릅니다. 데이터시트의 타이밍 요구사항을 반드시 준수하세요. 프레임워크의
enable_time과ramp_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와 SoC 사이의 통신은 주로 I2C 또는 SPI 버스를 사용합니다. 커널의 regmap API를 통해 버스 종류와 무관한 레지스터 접근 추상화가 가능하며, 대부분의 PMIC 드라이버는 regmap 기반으로 구현됩니다.
| PMIC 칩 | 제조사 | 레일 수 | 인터페이스 | 주요 대상 플랫폼 |
|---|---|---|---|---|
| TPS65217 | TI | 3 DCDC + 4 LDO | I2C | BeagleBone, AM335x |
| AXP209 | X-Powers | 3 DCDC + 5 LDO | I2C | Allwinner A10/A20 |
| MAX77686 | Maxim | 9 Buck + 26 LDO | I2C | Samsung Exynos 4 |
| STPMIC1 | ST | 4 Buck + 6 LDO | I2C | STM32MP1 |
| RK808 | Rockchip | 4 Buck + 8 LDO | I2C | Rockchip RK3288/RK3399 |
| DA9063 | Dialog | 6 Buck + 11 LDO | I2C | i.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 = ®ulator_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;
}
regulator_voltage_regmap_ops를 사용하면 set_voltage_sel, get_voltage_sel, enable, disable, is_enabled 등의 ops를 직접 구현할 필요가 없습니다. regulator_desc에 레지스터 주소와 마스크만 정확히 기술하면 프레임워크가 regmap을 통해 모든 것을 자동 처리합니다.
주요 PMIC 드라이버 커널 소스 경로
| PMIC | 커널 소스 경로 | 비고 |
|---|---|---|
| TPS65217 | drivers/regulator/tps65217-regulator.c | MFD 기반, I2C |
| AXP20x 시리즈 | drivers/regulator/axp20x-regulator.c | AXP152/209/221/803 등 통합 |
| MAX77686 | drivers/regulator/max77686-regulator.c | Samsung Exynos 레퍼런스 |
| STPMIC1 | drivers/regulator/stpmic1_regulator.c | STM32MP1 공식 PMIC |
| RK808 | drivers/regulator/rk808-regulator.c | Rockchip 공식, regmap 활용 |
Device Tree Regulator 바인딩
리눅스 커널에서 레귤레이터의 전기적 제약 조건(constraints)과 공급 체인(supply chain)은 Device Tree(DT)를 통해 기술됩니다. 보드마다 서로 다른 전압 범위·시퀀싱·의존 관계를 소스 코드 변경 없이 DTS 파일만으로 정의할 수 있어, 하나의 PMIC 드라이버가 다양한 보드에서 재사용됩니다.
핵심 DT 프로퍼티
| 프로퍼티 | 타입 | 설명 |
|---|---|---|
regulator-min-microvolt | u32 | 소비자가 요청할 수 있는 최소 전압 (µV) |
regulator-max-microvolt | u32 | 소비자가 요청할 수 있는 최대 전압 (µV) |
regulator-min-microamp | u32 | 최소 전류 제한 (µA) |
regulator-max-microamp | u32 | 최대 전류 제한 (µA) |
regulator-always-on | bool | 시스템 동작 중 항상 활성 유지 |
regulator-boot-on | bool | 부트로더가 켠 상태 유지 (초기 비활성화 방지) |
regulator-ramp-delay | u32 | 전압 변경 슬루레이트 (µV/µs) |
regulator-enable-ramp-delay | u32 | enable 후 안정화 대기 시간 (µs) |
regulator-settling-time-up-us | u32 | 전압 상승 시 정착 시간 (µs) |
vin-supply | phandle | 상위 공급 레귤레이터 (공급 체인) |
regulator-suspend-microvolt | u32 | 시스템 suspend 시 전압 (µV) |
regulator-coupled-with | phandle | 결합 레귤레이터 지정 |
실전 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>;
};
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);
drivers/regulator/qcom-rpmh-regulator.c에서 관리합니다. 실리콘 공정 편차에 따라 개별 다이마다 최적 전압이 다르므로, CPR이 런타임에 전압을 미세 조정하며 이때 결합 제약을 함께 만족시킵니다.
전원 도메인 계층 구조
임베디드 시스템에서 전원은 단순히 "켜고 끄는" 것이 아니라 계층적 의존 관계를 가집니다. 부모 레귤레이터가 비활성화되면 자식 레귤레이터도 전원이 차단되며, 이 관계를 프레임워크가 자동으로 추적·관리합니다.
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)
};
DVFS 연동 (Dynamic Voltage Frequency Scaling)
DVFS(Dynamic Voltage Frequency Scaling)는 프로세서의 부하에 따라 동작 주파수와 공급 전압을 동적으로 조정하여 전력 소비를 최적화하는 기술입니다. 리눅스 커널에서 DVFS는 cpufreq 서브시스템, OPP(Operating Performance Points) 테이블, 그리고 Regulator 프레임워크의 긴밀한 협력으로 구현됩니다.
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;
}
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_EVENT 유형
| 이벤트 | 값 | 설명 | 일반적 대응 |
|---|---|---|---|
REGULATOR_EVENT_UNDER_VOLTAGE | 0x01 | 전압이 하한 미만 | 디바이스 비활성화, 에러 로그 |
REGULATOR_EVENT_OVER_CURRENT | 0x02 | 전류가 상한 초과 | 부하 차단, 보호 동작 |
REGULATOR_EVENT_REGULATION_OUT | 0x04 | 출력 전압 정밀도 범위 이탈 | 재조정 시도 |
REGULATOR_EVENT_FAIL | 0x08 | 레귤레이터 하드웨어 장애 | 시스템 긴급 종료 |
REGULATOR_EVENT_OVER_TEMP | 0x10 | PMIC/레귤레이터 과열 | 부하 경감, 쓰로틀링 |
REGULATOR_EVENT_FORCE_DISABLE | 0x20 | 하드웨어에 의한 강제 비활성화 | 소비자에게 전원 상실 알림 |
REGULATOR_EVENT_VOLTAGE_CHANGE | 0x40 | 전압 변경 완료 | 소비자 재보정 |
REGULATOR_EVENT_DISABLE | 0x80 | 레귤레이터 비활성화됨 | 소비자 상태 정리 |
REGULATOR_EVENT_PRE_VOLTAGE_CHANGE | 0x100 | 전압 변경 전 사전 알림 | 캐시 플러시, DMA 중단 |
REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE | 0x200 | 전압 변경 취소 | 이전 상태 복원 |
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_regulator_register_notifier()를 사용하면 드라이버 언바인드 시 notifier가 자동으로 해제됩니다. 수동으로 regulator_unregister_notifier()를 호출할 필요가 없어 리소스 누수를 방지합니다.
동작 모드 (Operating Modes)
레귤레이터는 부하 상태에 따라 동작 모드(operating mode)를 전환하여 전력 효율을 최적화할 수 있습니다. 고부하 시에는 빠른 과도 응답을 위해 고전력 모드를 사용하고, 경부하 시에는 소비 전력을 줄이기 위해 에코(ECO) 모드로 전환합니다.
모드 관련 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;
}
| 모드 | 상수 | 전력 소비 | 과도 응답 | 용도 |
|---|---|---|---|---|
| FAST | REGULATOR_MODE_FAST | 높음 | 빠름 (수 µs) | CPU 터보, GPU 렌더링 |
| NORMAL | REGULATOR_MODE_NORMAL | 중간 | 표준 | 일반 동작 상태 |
| IDLE | REGULATOR_MODE_IDLE | 낮음 | 느림 (수십 µs) | 경부하/대기 상태 |
| STANDBY | REGULATOR_MODE_STANDBY | 최소 | 매우 느림 | 시스템 sleep/suspend |
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;
}
- 전원 레일이 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_set_voltage()를 호출하면 프레임워크는 다음 순서로 검증합니다:
- valid_ops_mask 확인 —
REGULATOR_CHANGE_VOLTAGE비트가 없으면-EPERM - 범위 교집합 계산 — 소비자 요청 범위와 constraints의 min_uV/max_uV 교집합
- 복수 소비자 조율 — 다른 소비자의 요청 범위와도 교집합 (가장 좁은 범위 선택)
- 하드웨어 스텝 정렬 — 실제 하드웨어가 설정 가능한 가장 가까운 값으로 정렬
- ramp_delay 적용 — 전압 변경 후 안정화 대기
- CPU 코어 전압 —
valid_ops_mask에서REGULATOR_CHANGE_VOLTAGE만 허용하고,REGULATOR_CHANGE_STATUS는 제거하여 소비자가 임의로 끌 수 없도록 합니다 - DDR 전원 —
always_on필수. 메모리 전원이 끊기면 데이터 손실 - 보안 키 저장소 —
valid_ops_mask = 0으로 설정하여 어떤 소비자도 제어 불가
Regulator 성능 최적화
전원 관리 경로는 시스템 전반의 성능과 반응성에 직접 영향을 미칩니다. 특히 DVFS 경로에서 regulator_set_voltage()의 지연 시간은 주파수 전환 속도를 결정하는 핵심 요소입니다.
I2C 병목 최소화
대부분의 PMIC는 I2C로 통신하며, I2C 트랜잭션은 수백 µs가 소요됩니다. DVFS의 critical path에서 I2C 지연은 심각한 성능 저하를 유발할 수 있습니다.
| 최적화 기법 | 설명 | 효과 |
|---|---|---|
| regmap 캐시 | RBTREE/FLAT 캐시로 읽기 트랜잭션 제거 | get_voltage 시 I2C 접근 불필요 |
| regcache_sync | suspend/resume 시 변경된 레지스터만 동기화 | resume 시간 단축 |
| bulk write | regmap_bulk_write()로 연속 레지스터 한 번에 기록 | I2C 트랜잭션 수 감소 |
| Fast-mode I2C | I2C 클록을 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
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.