i.MX6 LVDS 컨트롤러 드라이버 및 장치 초기화 분석

장치 초기화 및 플랫폼 디바이스 등록

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;
}

태그: i.MX6 LVDS Linux kernel Driver Development Display Driver

8월 16일 05:04에 게시됨