장치 초기화 및 플랫폼 디바이스 등록
i.MX6의 LVDS 디바이스 초기화는 보드 레벨의 초기화 코드에서 시작됩니다. board-mx6q_sabresd.c 파일 내의 MACHINE_START 매크로를 통해 시스템이 부팅될 때 보드 특정 설정이 진행됩니다.
// 보드 레벨 초기화 (board-mx6q_sabresd.c)
static void __init sabre_sd_init_machine(void)
{
/* ... 기타 장치 초기화 ... */
// IPU(Image Processing Unit) 추가
imx6q_add_ipuv3(0, &ipu_conf_data[0]);
// LDB(LVDS Display Bridge) 플랫폼 장치 등록
imx6q_add_ldb(&lvds_bridge_data);
}
// LDB 플랫폼 데이터 구조체 정의
static struct fsl_mxc_ldb_platform_data lvds_bridge_data = {
.ipu_id = 0,
.disp_id = 1,
.ext_ref = 1, // 외부 참조 저항 사용 여부
.mode = LDB_SEP1, // LVDS 모드 설정 (Separate 모드 등)
.sec_ipu_id = 0,
.sec_disp_id = 0,
};
imx6q_add_ldb 함수는 내부적으로 mxc_ldb라는 이름의 플랫폼 장치를 커널에 등록하며, 이는 이후 드라이버의 probe 함수가 호출되는 계기가 됩니다.
드라이버 초기화 및 Probe 과정
LVDS 브릿지 드라이버인 ldb.c는 플랫폼 드라이버 모델을 따릅니다. ldb_init 함수가 호출되면 mxc_ldb 드라이버가 등록되고, 장치 이름이 매칭될 때 ldb_probe가 실행됩니다.
// 드라이버 구조체 정의 (ldb.c)
static struct platform_driver imx_lvds_driver = {
.driver = {
.name = "mxc_ldb",
},
.probe = lvds_ctrl_probe,
.remove = lvds_ctrl_remove,
/* ... suspend, resume ... */
};
static int lvds_ctrl_probe(struct platform_device *pdev)
{
struct lvds_info *ctrl_data;
int error = 0;
ctrl_data = kzalloc(sizeof(struct lvds_info), GFP_KERNEL);
if (!ctrl_data) return -ENOMEM;
ctrl_data->pdev = pdev;
// 디스플레이 드라이버 레이어에 LVDS 드라이버 등록
ctrl_data->disp_handle = mxc_dispdrv_register(&imx_lvds_ops);
mxc_dispdrv_setdata(ctrl_data->disp_handle, ctrl_data);
dev_set_drvdata(&pdev->dev, ctrl_data);
return error;
}
디스플레이 초기화 로직 (ldb_disp_init)
ldb_disp_init 함수는 실제 하드웨어 레지스터를 설정하는 핵심적인 역할을 수행합니다. 메모리 맵핑, 데이터 매핑 방식(SPWG 등), 데이터 폭(18/24bit), 그리고 LVDS 채널 모드를 결정합니다.
static int imx_lvds_disp_init(struct mxc_dispdrv_handle *handle,
struct mxc_dispdrv_setting *config)
{
struct lvds_info *ctrl = mxc_dispdrv_getdata(handle);
struct fsl_mxc_ldb_platform_data *pdata = ctrl->pdev->dev.platform_data;
struct resource *mem_res;
uint32_t ctrl_reg_val;
if (!ctrl->is_ready) {
// 메모리 자원 획득 및 맵핑
mem_res = platform_get_resource(ctrl->pdev, IORESOURCE_MEM, 0);
ctrl->base = ioremap(mem_res->start, resource_size(mem_res));
// 제어 레지스터 및 GPR 레지스터 주소 설정
ctrl->reg_ctrl = ctrl->base + 0x08; // 예시 오프셋
ctrl_reg_val = readl(ctrl->reg_ctrl);
// 데이터 매핑 및 비트 폭 설정 (SPWG 표준 기반)
ctrl_reg_val &= ~(LDB_BIT_MAP_CH0_MASK | LDB_DATA_WIDTH_CH0_MASK);
if (bits_per_pixel(config->if_fmt) == 24)
ctrl_reg_val |= LDB_DATA_WIDTH_CH0_24 | LDB_BIT_MAP_CH0_SPWG;
else
ctrl_reg_val |= LDB_DATA_WIDTH_CH0_18 | LDB_BIT_MAP_CH0_SPWG;
// 모드 분기 처리 (Single, Dual, Separate 등)
if (ctrl->mode == LDB_SIN0) {
ctrl_reg_val &= ~LDB_SPLIT_MODE_EN;
ctrl_reg_val |= LDB_CH0_MODE_EN_TO_DI0;
}
/* ... 기타 모드 설정 생략 ... */
writel(ctrl_reg_val, ctrl->reg_ctrl);
// 클럭 소스 부모 설정 (LDB 클럭을 IPU DI 클럭의 부모로 지정)
clk_set_parent(ctrl->di_clk, ctrl->ldb_di_clk);
ctrl->is_ready = true;
}
return 0;
}
IPU와 LVDS의 신호 라우팅
i.MX6 시스템에서 IPU의 Display Interface(DI) 신호를 어떤 LVDS 채널로 보낼지는 IOMUXC의 GPR(General Purpose Register)을 통해 결정됩니다. 이는 ldb_ipu_ldb_route 함수에서 처리됩니다.
static int route_ipu_to_ldb(int ipu_id, int di_id, struct lvds_info *ctrl)
{
uint32_t gpr3_val;
int ch_idx;
gpr3_val = readl(ctrl->gpr3_reg);
// 모드에 따른 MUX 설정
if (ctrl->mode < LDB_SIN0) {
// Dual/Split 모드: 양쪽 채널 모두 동일한 IPU-DI 연결
gpr3_val &= ~(LVDS0_MUX_MASK | LVDS1_MUX_MASK);
gpr3_val |= (MUX_VAL(ipu_id, di_id) << LVDS0_SHIFT) |
(MUX_VAL(ipu_id, di_id) << LVDS1_SHIFT);
} else {
// Single/Separate 모드: 특정 채널에만 IPU-DI 연결
ch_idx = (ctrl->mode == LDB_SEP1) ? 1 : 0;
gpr3_val &= ~(MUX_MASK << (LVDS0_SHIFT + ch_idx * 2));
gpr3_val |= MUX_VAL(ipu_id, di_id) << (LVDS0_SHIFT + ch_idx * 2);
}
writel(gpr3_val, ctrl->gpr3_reg);
return 0;
}
디스플레이 셋업 및 클럭 구성
최종적인 출력 설정은 ldb_disp_setup에서 이루어집니다. 프레임버퍼의 설정값(Sync 극성 등)을 레지스터에 반영하고, LVDS 직렬화기에 필요한 7배수 클럭(또는 Split 모드 시 3.5배수)을 계산하여 설정합니다.
static int imx_lvds_setup(struct mxc_dispdrv_handle *handle, struct fb_info *fbi)
{
struct lvds_info *ctrl = mxc_dispdrv_getdata(handle);
uint32_t pixel_hz, target_clk;
// 픽셀 클럭 계산 (picoseconds to Hz)
pixel_hz = (PICOS2KHZ(fbi->var.pixclock)) * 1000UL;
// LVDS는 픽셀당 7비트를 전송하므로 7배 클럭 필요
if (ctrl->mode == LDB_SPL_DI0 || ctrl->mode == LDB_SPL_DI1)
target_clk = pixel_hz * 7 / 2;
else
target_clk = pixel_hz * 7;
// 부모 클럭 속도 조절 및 클럭 활성화
struct clk *parent = clk_get_parent(ctrl->ldb_di_clk);
clk_set_rate(parent, target_clk);
clk_enable(ctrl->ldb_di_clk);
return 0;
}