Regmap (레지스터 맵 추상화 프레임워크)

Regmap(drivers/base/regmap/)은 하드웨어 레지스터 접근을 버스(I2C, SPI, MMIO, SoundWire, W1 등)로부터 분리하는 커널 프레임워크입니다. 단일 API로 읽기/쓰기/비트 갱신을 수행하고, 레지스터 캐시(flat·rbtree·maple), 변경 감지(dirty), IRQ 컨트롤러(regmap-irq), 값 범위 검증, debugfs 자동 노출까지 제공하여 CODEC·PMIC·MFD 드라이버의 중복 코드를 크게 줄여줍니다.

전제 조건: I2C, SPI, Platform 드라이버 문서를 먼저 읽으세요. Regmap은 버스 드라이버 위에서 동작하는 중간 계층입니다.
일상 비유: Regmap은 통역사와 같습니다. "레지스터 0x10에 0x80을 써라"는 명령을 I2C/SPI/MMIO 각각의 언어로 번역해 전달하고, 캐시된 값이 있으면 실제 버스 통신 없이 빠르게 답합니다.

핵심 요약

  • regmap_config — 레지스터 비트 폭, 값 비트 폭, 캐시 유형, readable/writeable/volatile 마스크, 레지스터 기본값 테이블을 정의하는 설정 구조체입니다.
  • regmap_init_*() — I2C(devm_regmap_init_i2c()), SPI(devm_regmap_init_spi()), MMIO(devm_regmap_init_mmio()) 등 버스별 초기화 함수입니다.
  • regmap_read/write/update_bits() — 레지스터 단일 접근 API입니다. 캐시가 유효하면 버스 통신 없이 반환합니다.
  • regmap_bulk_read/write() — 연속 레지스터 다중 접근으로 버스 트랜잭션 횟수를 줄입니다.
  • 레지스터 캐시REGCACHE_FLAT(배열, 빠름), REGCACHE_RBTREE(희소 맵), REGCACHE_MAPLE(현대적 트리) 중 선택합니다.
  • volatile 레지스터 — STATUS 같이 하드웨어가 자체적으로 값을 바꾸는 레지스터는 volatile로 표시해 항상 버스에서 읽습니다.
  • regmap-irq — 인터럽트 상태/마스크 레지스터를 regmap 기반으로 처리하는 IRQ 컨트롤러 확장입니다.
  • regcache_sync() — 시스템 절전 후 하드웨어가 기본값으로 리셋될 때, 캐시에 저장된 값을 하드웨어에 복원합니다.

단계별 이해

  1. regmap_config 정의 — 레지스터 맵의 특성(비트 폭, 캐시 전략, 읽기/쓰기 가능 레지스터 목록)을 regmap_config에 기술합니다.
  2. 버스별 초기화devm_regmap_init_*()을 probe에서 호출하여 struct regmap *을 획득합니다.
  3. 읽기/쓰기 API 사용regmap_read(), regmap_write(), regmap_update_bits()로 레지스터를 조작합니다.
  4. volatile 처리 이해 — 인터럽트 상태 레지스터 등은 volatile로 마킹하여 캐시 오류를 방지합니다.
  5. regmap-irq 통합 — 다중 IRQ 소스를 갖는 PMIC/CODEC에서 regmap_add_irq_chip()으로 자동 IRQ 디멀티플렉싱을 구성합니다.

개요 — 왜 Regmap인가

Regmap 이전에는 I2C 클라이언트 드라이버가 i2c_smbus_read_byte_data(), SPI 드라이버가 spi_write_then_read()를 각각 직접 호출했습니다. 버스가 달라지면 레지스터 접근 코드 전체를 재작성해야 했고, 캐싱·값 검증·debugfs 노출도 드라이버마다 제각각이었습니다.

Regmap은 이 문제를 해결하기 위해 include/linux/regmap.hdrivers/base/regmap/에 구현된 단일 레지스터 접근 계층입니다. PMIC, CODEC, 센서, MFD 서브 디바이스 등 레지스터 기반 하드웨어 드라이버 대부분이 사용합니다.

드라이버 계층 (Consumers) CODEC 드라이버 PMIC 드라이버 센서 드라이버 GPIO 드라이버 MFD 서브 디바이스 기타 드라이버 Regmap API regmap_read · regmap_write · regmap_update_bits · regmap_bulk_read · regmap_field_* 캐시 계층 (Cache Layer) REGCACHE_FLAT (배열) REGCACHE_RBTREE (희소) REGCACHE_MAPLE (현대적) REGCACHE_NONE 버스 추상화 계층 (regmap_bus) I2C SPI MMIO SoundWire I3C Custom Bus 물리 하드웨어 (I2C 슬레이브 / SPI 디바이스 / MMIO 레지스터) regmap_read/write 캐시 miss 시 버스 접근 bus-specific 전송

regmap_config 정의

struct regmap_config는 하드웨어 레지스터 맵의 특성 전체를 기술합니다.

static const struct regmap_config my_chip_regmap_config = {
    /* 레지스터 주소 비트 수 (기본 8) */
    .reg_bits    = 8,
    /* 레지스터 값 비트 수 (기본 8) */
    .val_bits    = 8,
    /* 레지스터 주소 증분 (기본 1, 16비트 레지스터면 2) */
    .reg_stride  = 1,

    /* 최대 레지스터 주소 (캐시 크기 계산에 사용) */
    .max_register = MY_CHIP_MAX_REG,

    /* 레지스터 기본값 테이블 (power-on reset 값) */
    .reg_defaults     = my_chip_reg_defaults,
    .num_reg_defaults = ARRAY_SIZE(my_chip_reg_defaults),

    /* 캐시 전략: REGCACHE_NONE / FLAT / RBTREE / MAPLE */
    .cache_type = REGCACHE_RBTREE,

    /* 특정 레지스터 접근 가능 여부 콜백 (NULL이면 모두 허용) */
    .readable_reg  = my_chip_readable_reg,
    .writeable_reg = my_chip_writeable_reg,
    /* volatile: 항상 버스에서 읽음 (캐시 무시) */
    .volatile_reg  = my_chip_volatile_reg,
    /* precious: 읽기가 부작용을 일으키는 레지스터 (debugfs 자동 읽기 금지) */
    .precious_reg  = my_chip_precious_reg,
};

레지스터 기본값 테이블

static const struct reg_default my_chip_reg_defaults[] = {
    /* { 주소, 기본값 } */
    { MY_CHIP_REG_CTRL,   0x00 },
    { MY_CHIP_REG_CONFIG, 0x40 },
    { MY_CHIP_REG_GAIN,   0x10 },
};

레지스터 속성 콜백

static bool my_chip_readable_reg(struct device *dev, unsigned int reg)
{
    switch (reg) {
    case MY_CHIP_REG_CTRL ... MY_CHIP_REG_STATUS:
    case MY_CHIP_REG_ID:
        return true;
    default:
        return false;
    }
}

static bool my_chip_volatile_reg(struct device *dev, unsigned int reg)
{
    /* STATUS, IRQ_STATUS는 하드웨어가 자체 변경 → 항상 버스에서 읽기 */
    switch (reg) {
    case MY_CHIP_REG_STATUS:
    case MY_CHIP_REG_IRQ_STATUS:
        return true;
    default:
        return false;
    }
}

초기화 — 버스별 init 함수

버스별 초기화 함수는 모두 devm_regmap_init_*() 형태로 제공됩니다. devm_ 접두사가 붙은 함수는 드라이버가 언바인드되거나 probe에서 에러를 반환할 때 자동으로 해제되므로, remove 콜백에서 별도로 정리할 필요가 없습니다. I2C와 SPI는 클라이언트 포인터를 직접 받아 버스 핸들을 관리하고, MMIO는 이미 ioremap된 주소를 받습니다. 초기화에 실패하면 IS_ERR()로 확인하고 PTR_ERR()를 반환합니다.

버스초기화 함수헤더
I2Cdevm_regmap_init_i2c(client, config)linux/regmap.h
SPIdevm_regmap_init_spi(spi, config)linux/regmap.h
MMIO (platform)devm_regmap_init_mmio(dev, base, config)linux/regmap.h
I3Cdevm_regmap_init_i3c(i3cdev, config)linux/regmap.h
SoundWiredevm_regmap_init_sdw(sdw, config)linux/regmap.h
커스텀 버스devm_regmap_init(dev, bus, bus_context, config)linux/regmap.h

I2C 드라이버 probe 예제

static int my_chip_i2c_probe(struct i2c_client *client)
{
    struct my_chip_priv *priv;
    struct regmap *regmap;
    unsigned int val;
    int ret;

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

    /* Regmap 초기화 — devm_이므로 언바인드 시 자동 해제 */
    regmap = devm_regmap_init_i2c(client, &my_chip_regmap_config);
    if (IS_ERR(regmap))
        return dev_err_probe(&client->dev, PTR_ERR(regmap),
                             "regmap init failed\n");

    priv->regmap = regmap;
    i2c_set_clientdata(client, priv);

    /* 칩 ID 확인 */
    ret = regmap_read(regmap, MY_CHIP_REG_ID, &val);
    if (ret)
        return ret;

    if ((val & MY_CHIP_ID_MASK) != MY_CHIP_ID_VALUE)
        return dev_err_probe(&client->dev, -ENODEV,
                             "unexpected chip ID 0x%02x\n", val);

    dev_info(&client->dev, "chip rev 0x%02x detected\n",
             val & MY_CHIP_REV_MASK);
    return 0;
}

MMIO (platform) 드라이버 예제

static int my_dev_probe(struct platform_device *pdev)
{
    struct my_dev_priv *priv;
    void __iomem *base;

    base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(base))
        return PTR_ERR(base);

    priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
                                         &my_dev_regmap_config);
    if (IS_ERR(priv->regmap))
        return PTR_ERR(priv->regmap);

    /* 이후 regmap_read/write로 MMIO 접근 */
    return 0;
}

읽기 / 쓰기 / 비트 조작 API

모든 읽기/쓰기 함수는 성공 시 0, 실패 시 음수 errno를 반환합니다. 캐시가 활성화된 레지스터를 읽으면 버스 통신 없이 즉시 반환합니다. volatile으로 표시된 레지스터는 캐시를 무시하고 항상 버스에서 읽습니다. regmap_update_bits()는 내부적으로 read-modify-write를 수행하며 regmap 내부 mutex가 원자성을 보장합니다. 단, MMIO 기반 regmap에서 regmap 범위 바깥의 다른 레지스터와 하드웨어 레벨 의존성이 있다면 별도 동기화가 필요합니다.

struct regmap *map = priv->regmap;
unsigned int val;
int ret;

/* 단일 레지스터 읽기 */
ret = regmap_read(map, MY_CHIP_REG_STATUS, &val);
if (ret)
    return ret;

/* 단일 레지스터 쓰기 */
ret = regmap_write(map, MY_CHIP_REG_CTRL, 0x01);

/* 비트 마스크 갱신: mask로 선택된 비트에 val을 씀 */
/* REG_CTRL의 [1:0] 비트를 0b10으로 설정 */
ret = regmap_update_bits(map, MY_CHIP_REG_CTRL,
                         0x03,   /* mask */
                         0x02);  /* val */

/* 연속 레지스터 읽기 (bulk) */
u8 buf[4];
ret = regmap_bulk_read(map, MY_CHIP_REG_DATA, buf, 4);

/* 연속 레지스터 쓰기 */
ret = regmap_bulk_write(map, MY_CHIP_REG_CONFIG, buf, 4);

Raw / noinc API

/* 버스 프로토콜로 직접 전송 (캐시 우회, 엔디안 변환 없음) */
ret = regmap_raw_read(map, reg, buf, len);
ret = regmap_raw_write(map, reg, buf, len);

/* 주소 자동 증분 없이 반복 읽기 (FIFO 레지스터) */
ret = regmap_noinc_read(map, FIFO_REG, buf, len);
ret = regmap_noinc_write(map, FIFO_REG, buf, len);

레지스터 캐시

캐시는 읽기 횟수를 줄이고 suspend/resume 시 레지스터 값을 보존합니다. 캐시에는 기본값(reg_defaults)과 다른 레지스터만 저장되므로, 실제 메모리 사용량은 변경된 레지스터 수에 비례합니다. 레지스터 맵의 밀도와 크기에 따라 적합한 캐시 유형을 선택하면 메모리와 탐색 속도 사이의 균형을 맞출 수 있습니다.

REGCACHE_FLAT 연속 배열 (인덱스 = 레지스터 주소) 0x00 0x01 0x02 0x03 0x04 0x05 0x06 ··· O(1) 접근 — 레지스터 0x00 ~ max 모두 배열에 연속 저장 적합: 연속 밀도 높은 맵 (CODEC, 수백 개 레지스터) REGCACHE_RBTREE 레드-블랙 트리 (희소 저장) 0x10: val 0x05: val 0x80: val 변경된 레지스터만 노드 저장 O(log n) 탐색 적합: 희소 맵 (PMIC, 주소 공간이 넓고 드문 경우) REGCACHE_MAPLE Maple 트리 (범위 기반, Linux 6.1+) 범위 [0x00–0x0F]: 8개 값 [0x80–0x83] 빈 범위 (없음) 연속 범위를 단일 노드로 압축 희소+밀도 혼합 맵에 효율적 적합: 범용 (rbtree 대체, 신규 드라이버 권장)
캐시 유형자료구조적합한 상황
REGCACHE_NONE없음캐시 불필요, 항상 버스 접근
REGCACHE_FLAT배열레지스터가 연속적이고 밀도 높음 (CODEC 등)
REGCACHE_RBTREE레드-블랙 트리희소 레지스터 맵 (PMIC 등)
REGCACHE_MAPLEMaple tree (6.1+)범용 — 희소 맵에 rbtree 대체

캐시 제어 API

/* 캐시와 하드웨어 동기화 (suspend 후 resume 시 호출) */
ret = regcache_sync(map);

/* 특정 레지스터 범위만 동기화 */
ret = regcache_sync_region(map, first_reg, last_reg);

/* 캐시를 bypass하여 직접 하드웨어 읽기 */
regcache_cache_bypass(map, true);
regmap_read(map, reg, &val);
regcache_cache_bypass(map, false);

/* 캐시 무효화 (다음 읽기에서 하드웨어에서 재읽기) */
regcache_drop_region(map, first_reg, last_reg);

/* 캐시만 업데이트, 하드웨어 쓰기 지연 */
regcache_cache_only(map, true);  /* suspend 전 */
/* ... 캐시에만 write ... */
regcache_cache_only(map, false); /* resume 후 */
regcache_sync(map);              /* 하드웨어에 일괄 write */

regmap-irq — IRQ 컨트롤러 통합

PMIC나 오디오 CODEC처럼 단일 인터럽트 라인으로 여러 이벤트를 보고하는 칩에서, regmap-irq는 상태/마스크 레지스터를 regmap으로 읽어 개별 IRQ 도메인을 노출합니다.

static const struct regmap_irq my_chip_irqs[] = {
    /* { 레지스터 오프셋, 비트 마스크 } */
    REGMAP_IRQ_REG(MY_CHIP_IRQ_OVP,    0, BIT(0)),  /* Over-Voltage */
    REGMAP_IRQ_REG(MY_CHIP_IRQ_OCP,    0, BIT(1)),  /* Over-Current */
    REGMAP_IRQ_REG(MY_CHIP_IRQ_TEMP,   0, BIT(2)),  /* Temperature */
    REGMAP_IRQ_REG(MY_CHIP_IRQ_PGOOD,  1, BIT(0)),  /* Power-Good */
};

static const struct regmap_irq_chip my_chip_irq_chip = {
    .name           = "my-chip",
    .irqs           = my_chip_irqs,
    .num_irqs       = ARRAY_SIZE(my_chip_irqs),
    .num_regs       = 2,                    /* status/mask 레지스터 개수 */
    .status_base    = MY_CHIP_REG_IRQ_STATUS0,
    .mask_base      = MY_CHIP_REG_IRQ_MASK0,
    .ack_base       = MY_CHIP_REG_IRQ_ACK0,
    .mask_invert    = true,  /* 마스크 비트가 1이면 인터럽트 비활성 */
};

static int my_chip_probe(struct i2c_client *client)
{
    struct regmap_irq_chip_data *irq_data;
    int irq, ret;

    /* ... regmap 초기화 ... */

    irq = client->irq;
    ret = devm_regmap_add_irq_chip(&client->dev, priv->regmap,
                                   irq, IRQF_ONESHOT, 0,
                                   &my_chip_irq_chip, &irq_data);
    if (ret)
        return ret;

    /* 소비자가 virq를 사용하도록 irq_domain 노출 */
    priv->irq_data = irq_data;

    /* 예: sub-driver에 가상 IRQ 전달 */
    int pgood_irq = regmap_irq_get_virq(irq_data, MY_CHIP_IRQ_PGOOD);
    return 0;
}

regmap_field — 비트 필드 추상화

하나의 레지스터 내 특정 비트 필드를 반복적으로 접근할 때 regmap_field를 사용하면 마스크/시프트 계산을 숨길 수 있습니다.

/* 필드 정의: 레지스터, 최상위 비트, 최하위 비트 */
static const struct reg_field GAIN_FIELD =
    REG_FIELD(MY_CHIP_REG_GAIN, 4, 7);  /* [7:4] */

static int my_chip_probe(struct i2c_client *client)
{
    struct regmap_field *gain_field;

    gain_field = devm_regmap_field_alloc(&client->dev,
                                         priv->regmap, GAIN_FIELD);
    if (IS_ERR(gain_field))
        return PTR_ERR(gain_field);

    priv->gain_field = gain_field;
    return 0;
}

/* 필드 읽기/쓰기 — 시프트/마스크 자동 처리 */
unsigned int gain;
regmap_field_read(priv->gain_field, &gain);
regmap_field_write(priv->gain_field, 5);

멀티 레지스터 맵 (복합 디바이스)

하나의 SPI/I2C 디바이스가 page-select 레지스터로 레지스터 뱅크를 전환하는 경우, regmap_reinit_cache()나 다중 regmap을 사용합니다.

/* 페이지 0과 1을 위한 별도 regmap (주소 공간이 겹치는 경우) */
priv->regmap_page0 = devm_regmap_init_spi(spi, &config_page0);
priv->regmap_page1 = devm_regmap_init_spi(spi, &config_page1);

/* 페이지 선택 콜백을 custom bus에 구현하는 방법도 있음 */

debugfs 활용

Regmap은 자동으로 /sys/kernel/debug/regmap/에 디버그 항목을 생성합니다.

# 등록된 regmap 목록
ls /sys/kernel/debug/regmap/

# 레지스터 전체 덤프 (precious 레지스터 제외)
cat /sys/kernel/debug/regmap/0-0044/registers

# 캐시 덤프
cat /sys/kernel/debug/regmap/0-0044/cache_only
cat /sys/kernel/debug/regmap/0-0044/cache_dirty

# 단일 레지스터 읽기 (주소 입력 후 data 읽기)
echo 0x10 > /sys/kernel/debug/regmap/0-0044/rw/address
cat /sys/kernel/debug/regmap/0-0044/rw/data

# 레지스터 쓰기
echo 0x80 > /sys/kernel/debug/regmap/0-0044/rw/data

커스텀 버스 구현

표준 버스(I2C/SPI/MMIO)가 아닌 프로토콜을 사용하는 경우 struct regmap_bus를 구현합니다.

static int my_bus_reg_read(void *context, unsigned int reg, unsigned int *val)
{
    struct my_dev *dev = context;
    /* 커스텀 프로토콜로 레지스터 읽기 */
    return my_hw_read(dev, reg, val);
}

static int my_bus_reg_write(void *context, unsigned int reg, unsigned int val)
{
    struct my_dev *dev = context;
    return my_hw_write(dev, reg, val);
}

static const struct regmap_bus my_regmap_bus = {
    .reg_read  = my_bus_reg_read,
    .reg_write = my_bus_reg_write,
};

/* probe 에서: */
priv->regmap = devm_regmap_init(&pdev->dev, &my_regmap_bus,
                                priv, &my_regmap_config);

Suspend / Resume 통합

대부분의 I2C/SPI 장치는 시스템 절전 후 전원이 차단되거나 하드웨어가 Power-On Reset 상태로 돌아옵니다. 이때 regmap 캐시와 실제 하드웨어 값이 불일치하는 문제가 발생합니다. 표준 패턴은 suspend에서 regcache_mark_dirty()로 캐시를 "dirty" 상태로 표시하고 regcache_cache_only()로 하드웨어 접근을 차단한 뒤, resume에서 regcache_sync()로 캐시의 모든 dirty 값을 하드웨어에 일괄 복원하는 것입니다. 이 방식은 버스 트랜잭션 수를 최소화하면서 하드웨어를 정확하게 복원합니다.

static int my_chip_suspend(struct device *dev)
{
    struct my_chip_priv *priv = dev_get_drvdata(dev);

    /* 하드웨어가 resume 후 기본값으로 리셋되는 경우 캐시 only 모드 */
    regcache_cache_only(priv->regmap, true);
    regcache_mark_dirty(priv->regmap);

    return 0;
}

static int my_chip_resume(struct device *dev)
{
    struct my_chip_priv *priv = dev_get_drvdata(dev);
    int ret;

    regcache_cache_only(priv->regmap, false);

    /* 캐시된 값을 하드웨어에 복원 */
    ret = regcache_sync(priv->regmap);
    if (ret)
        dev_err(dev, "regcache sync failed: %d\n", ret);

    return ret;
}

16/32비트 레지스터 폭과 엔디안

SoC 내부 레지스터나 일부 CODEC은 8비트보다 넓은 레지스터를 사용합니다. regmap_configreg_bits·val_bits·reg_stride·val_format_endian으로 이를 기술합니다.

필드역할일반값 예시
reg_bits레지스터 주소 비트 수8, 16, 32
val_bits레지스터 값 비트 수8, 16, 32
reg_stride주소 자동 증분 단위val_bits/8 (2=16비트, 4=32비트)
val_format_endian값 엔디안REGMAP_ENDIAN_BIG, REGMAP_ENDIAN_LITTLE, REGMAP_ENDIAN_NATIVE
reg_format_endian주소 엔디안 (SPI)SPI 칩에 따라 다름

16비트 주소 + 16비트 값 예제 (WM8994 스타일)

static const struct regmap_config wm8994_style_config = {
    .reg_bits             = 16,   /* 2바이트 레지스터 주소 */
    .val_bits             = 16,   /* 2바이트 레지스터 값   */
    .reg_stride           = 2,    /* 주소 증분 = 2 */
    .val_format_endian    = REGMAP_ENDIAN_BIG,
    .max_register         = 0x6000,
    .cache_type           = REGCACHE_RBTREE,
    .readable_reg  = wm8994_readable_reg,
    .writeable_reg = wm8994_writeable_reg,
    .volatile_reg  = wm8994_volatile_reg,
    .reg_defaults     = wm8994_reg_defaults,
    .num_reg_defaults = ARRAY_SIZE(wm8994_reg_defaults),
};

32비트 MMIO 레지스터 예제

static const struct regmap_config soc_periph_config = {
    .reg_bits   = 32,
    .val_bits   = 32,
    .reg_stride = 4,   /* 4바이트 정렬 — MMIO 표준 */
    .val_format_endian = REGMAP_ENDIAN_NATIVE,
    .max_register = SOC_PERIPH_REG_MAX,
    .cache_type   = REGCACHE_NONE,  /* MMIO는 대개 캐시 불필요 */
    .volatile_reg = soc_periph_volatile,
};

priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
                                     &soc_periph_config);

regmap-irq 심화 — 멀티 레지스터와 Wake

실제 PMIC는 상태(STATUS), 마스크(MASK), ACK 레지스터가 여러 개인 경우가 대부분입니다. num_regswake_base로 이를 처리합니다.

static const struct regmap_irq axp_chip_irqs[] = {
    /* Group 0 (reg 0): 전원 이벤트 */
    REGMAP_IRQ_REG(AXP_IRQ_ACIN_OV,    0, BIT(7)),
    REGMAP_IRQ_REG(AXP_IRQ_ACIN_PLUG,  0, BIT(6)),
    REGMAP_IRQ_REG(AXP_IRQ_VBUS_OV,    0, BIT(4)),
    /* Group 1 (reg 1): 배터리 이벤트 */
    REGMAP_IRQ_REG(AXP_IRQ_BAT_FULL,   1, BIT(7)),
    REGMAP_IRQ_REG(AXP_IRQ_BAT_CHARGE, 1, BIT(6)),
    /* Group 2 (reg 2): 버튼/온도 */
    REGMAP_IRQ_REG(AXP_IRQ_POK_LONG,   2, BIT(0)),
    REGMAP_IRQ_REG(AXP_IRQ_POK_SHORT,  2, BIT(1)),
    REGMAP_IRQ_REG(AXP_IRQ_TEMP_HIGH,  2, BIT(5)),
};

static const struct regmap_irq_chip axp_irq_chip = {
    .name        = "axp-pmic",
    .irqs        = axp_chip_irqs,
    .num_irqs    = ARRAY_SIZE(axp_chip_irqs),
    .num_regs    = 3,
    .status_base = AXP_IRQ_STATUS1,
    .mask_base   = AXP_IRQ_MASK1,
    .ack_base    = AXP_IRQ_STATUS1,  /* 쓰기로 ACK */
    .init_ack_masked = true,
    .wake_base   = AXP_IRQ_MASK1,   /* wakeup 소스 마스크 */
};
Wake IRQ 패턴: wake_base를 설정하면 regmap_irq가 suspend 시 해당 레지스터로 wakeup 마스크를 별도 관리합니다. 전원 버튼(POK)이나 ACIN 감지 같은 wakeup 소스에 활용합니다.

실제 커널 드라이버 패턴 — PMIC/CODEC

PMIC나 오디오 CODEC 드라이버는 거의 예외 없이 3단계 초기화 패턴을 따릅니다. ① devm_regmap_init_*()으로 레지스터 맵을 초기화하고, ② devm_regmap_add_irq_chip()으로 단일 물리 IRQ를 여러 가상 IRQ로 분리하며, ③ devm_mfd_add_devices()로 각 기능(레귤레이터, RTC, GPIO 등)을 독립 platform_device로 등록합니다. 이 패턴 덕분에 각 기능 드라이버가 커널 서브시스템(Regulator, RTC framework 등)의 표준 인터페이스를 그대로 사용할 수 있습니다.

드라이버파일특징
AXP20x (PMIC)drivers/mfd/axp20x.cI2C/SPI, REGCACHE_RBTREE, 4 IRQ 레지스터 그룹
WM8994 (CODEC)sound/soc/codecs/wm8994.c16비트 주소/값, REGCACHE_RBTREE, 수천 개 reg_defaults
MAX77686 (PMIC)drivers/mfd/max77686.cI2C, MFD 서브 디바이스 연동, regmap-irq
TPS65912 (PMIC)drivers/mfd/tps65912-core.cI2C/SPI 이중 버스, REGCACHE_MAPLE
IMX8M CCM (MMIO)drivers/clk/imx/clk-imx8mm.c32비트 MMIO, regmap_update_bits로 클럭 게이팅

AXP20x 스타일 완전 초기화 패턴

struct axp20x_dev {
    struct device               *dev;
    struct regmap               *regmap;
    struct regmap_irq_chip_data *regmap_irqc;
    int                          irq;
};

static int axp20x_i2c_probe(struct i2c_client *i2c)
{
    struct axp20x_dev *axp20x;
    int ret;

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

    axp20x->dev = &i2c->dev;
    axp20x->irq = i2c->irq;
    i2c_set_clientdata(i2c, axp20x);

    /* 1단계: Regmap 초기화 */
    axp20x->regmap = devm_regmap_init_i2c(i2c,
                                           &axp20x_i2c_regmap_config);
    if (IS_ERR(axp20x->regmap))
        return PTR_ERR(axp20x->regmap);

    /* 2단계: regmap-irq 초기화 */
    ret = devm_regmap_add_irq_chip(axp20x->dev, axp20x->regmap,
                                   axp20x->irq,
                                   IRQF_ONESHOT | IRQF_SHARED, -1,
                                   &axp20x_regmap_irq_chip,
                                   &axp20x->regmap_irqc);
    if (ret)
        return dev_err_probe(axp20x->dev, ret,
                             "failed to add irq chip\n");

    /* 3단계: MFD 서브 디바이스 등록 */
    ret = devm_mfd_add_devices(axp20x->dev, PLATFORM_DEVID_NONE,
                               axp20x_cells, ARRAY_SIZE(axp20x_cells),
                               NULL, 0,
                               regmap_irq_get_domain(axp20x->regmap_irqc));
    if (ret)
        return dev_err_probe(axp20x->dev, ret,
                             "failed to add MFD devices\n");

    return 0;
}
관련 페이지: MFD 프레임워크 — MFD 서브 디바이스 등록 방법, Platform 드라이버 — probe/remove 생명주기, I2C 서브시스템 — I2C 클라이언트 드라이버

흔한 실수와 주의사항

주의사항:
  • volatile 누락: STATUS·IRQ 레지스터를 volatile로 표시하지 않으면 캐시된 오래된 값을 반환합니다.
  • precious 누락: FIFO·이벤트 레지스터를 precious로 표시하지 않으면 debugfs의 자동 읽기가 부작용(데이터 소비 등)을 일으킵니다.
  • regmap_update_bits 원자성: MMIO 기반은 read-modify-write이므로 경쟁 조건이 발생할 수 있습니다. 필요 시 별도 락이 필요합니다.
  • max_register 미설정: REGCACHE_FLAT에서 max_register를 설정하지 않으면 캐시 크기 계산이 실패합니다.
  • reg_defaults와 캐시 불일치: reg_defaults는 반드시 실제 하드웨어 POR(Power-On Reset) 값과 일치해야 합니다. 불일치하면 regcache_sync()가 잘못된 값을 기록합니다.