Regmap (레지스터 맵 추상화 프레임워크)
Regmap(drivers/base/regmap/)은 하드웨어 레지스터 접근을 버스(I2C, SPI, MMIO, SoundWire, W1 등)로부터 분리하는 커널 프레임워크입니다. 단일 API로 읽기/쓰기/비트 갱신을 수행하고, 레지스터 캐시(flat·rbtree·maple), 변경 감지(dirty), IRQ 컨트롤러(regmap-irq), 값 범위 검증, debugfs 자동 노출까지 제공하여 CODEC·PMIC·MFD 드라이버의 중복 코드를 크게 줄여줍니다.
핵심 요약
- 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() — 시스템 절전 후 하드웨어가 기본값으로 리셋될 때, 캐시에 저장된 값을 하드웨어에 복원합니다.
단계별 이해
- regmap_config 정의 — 레지스터 맵의 특성(비트 폭, 캐시 전략, 읽기/쓰기 가능 레지스터 목록)을
regmap_config에 기술합니다. - 버스별 초기화 —
devm_regmap_init_*()을 probe에서 호출하여struct regmap *을 획득합니다. - 읽기/쓰기 API 사용 —
regmap_read(),regmap_write(),regmap_update_bits()로 레지스터를 조작합니다. - volatile 처리 이해 — 인터럽트 상태 레지스터 등은 volatile로 마킹하여 캐시 오류를 방지합니다.
- 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.h와 drivers/base/regmap/에 구현된 단일 레지스터 접근 계층입니다. PMIC, CODEC, 센서, MFD 서브 디바이스 등 레지스터 기반 하드웨어 드라이버 대부분이 사용합니다.
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()를 반환합니다.
| 버스 | 초기화 함수 | 헤더 |
|---|---|---|
| I2C | devm_regmap_init_i2c(client, config) | linux/regmap.h |
| SPI | devm_regmap_init_spi(spi, config) | linux/regmap.h |
| MMIO (platform) | devm_regmap_init_mmio(dev, base, config) | linux/regmap.h |
| I3C | devm_regmap_init_i3c(i3cdev, config) | linux/regmap.h |
| SoundWire | devm_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_NONE | 없음 | 캐시 불필요, 항상 버스 접근 |
REGCACHE_FLAT | 배열 | 레지스터가 연속적이고 밀도 높음 (CODEC 등) |
REGCACHE_RBTREE | 레드-블랙 트리 | 희소 레지스터 맵 (PMIC 등) |
REGCACHE_MAPLE | Maple 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_config의 reg_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_regs와 wake_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_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.c | I2C/SPI, REGCACHE_RBTREE, 4 IRQ 레지스터 그룹 |
| WM8994 (CODEC) | sound/soc/codecs/wm8994.c | 16비트 주소/값, REGCACHE_RBTREE, 수천 개 reg_defaults |
| MAX77686 (PMIC) | drivers/mfd/max77686.c | I2C, MFD 서브 디바이스 연동, regmap-irq |
| TPS65912 (PMIC) | drivers/mfd/tps65912-core.c | I2C/SPI 이중 버스, REGCACHE_MAPLE |
| IMX8M CCM (MMIO) | drivers/clk/imx/clk-imx8mm.c | 32비트 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;
}
흔한 실수와 주의사항
- 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()가 잘못된 값을 기록합니다.