lvds bridge, 应该是一个I2C接口控制的,采用lvds数据接口,液晶屏驱动器桥。
这也是个I2C 驱动,挂接在 I2C_2 总线上。
1. 初始化如下:
/* ************************************************************************* *\
* FUNCTION: dsi_lvds_bridge_init
*
* DESCRIPTION: Driver Init function for lvds bridge
*
\* ************************************************************************* */
static int __init dsi_lvds_bridge_init(void)
{
int ret = 0;
struct i2c_adapter *adapter = NULL;
struct i2c_client *client = NULL;
struct i2c_board_info info;
printk(KERN_INFO "[DISPLAY] %s: Enter\n", __func__);
ret = misc_register(&dsi_lvds_bridge_dev);
if (ret) {
printk(KERN_ERR "[DISPLAY] %s: Can not register misc device.\n", __func__);
return ret;
}
adapter = i2c_get_adapter(2); /*DV0 is on I2C bus 2 */
if (!adapter) {
printk(KERN_ERR "[DISPLAY] %s: Can not get bridge i2c adapter.\n", __func__);
return 0;
}
/* Setup the i2c board info with the device type and
the device address. */
memset(&info, 0, sizeof(info));
strlcpy(info.type, "i2c_disp_brig", sizeof(info.type));
info.addr = 0x0F; /*I2C2 address 0x0F */
/* Create the i2c client */
client = i2c_new_device(adapter, &info);
if (!client) {
printk(KERN_ERR "[DISPLAY] %s: ERROR, i2c client=NULL\n", __func__);
return -1;
}
ret = i2c_add_driver(&dsi_lvds_bridge_i2c_driver);
printk(KERN_INFO "[DISPLAY] %s: Exit, ret = %d\n", __func__, ret);
return 0;
}
/* ************************************************************************* *\
* FUNCTION: dsi_lvds_bridge_exit
*
* DESCRIPTION: Driver exit function for lvds bridge
*
\* ************************************************************************* */
static void __exit dsi_lvds_bridge_exit(void)
{
printk(KERN_INFO "[DISPLAY] %s\n", __func__);
misc_deregister(&dsi_lvds_bridge_dev);
i2c_del_driver(&dsi_lvds_bridge_i2c_driver);
}
module_init(dsi_lvds_bridge_init);
module_exit(dsi_lvds_bridge_exit);
2. 初始化用到的几个结构体:
static const struct i2c_device_id dsi_lvds_bridge_id[] = {
{ "i2c_disp_brig", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, dsi_lvds_bridge_id);
static struct i2c_driver dsi_lvds_bridge_i2c_driver = {
.driver = {
.name = "i2c_disp_brig",
},
.id_table = dsi_lvds_bridge_id,
.probe = dsi_lvds_bridge_probe,
.remove = dsi_lvds_bridge_remove,
};
static const struct file_operations mipi_dsi_dev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = dsi_lvds_dev_ioctl,
};
static struct miscdevice dsi_lvds_bridge_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mipi_dsi",
.fops = &mipi_dsi_dev_fops,
};
dsi_lvds_bridge_probe()
/* ************************************************************************* *\
* FUNCTION: dsi_lvds_bridge_probe
*
* DESCRIPTION: Probe function for LVDS bridge.
*
\* ************************************************************************* */
static int dsi_lvds_bridge_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk(KERN_INFO "[DISPLAY] %s: Enter\n", __func__);
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
printk(KERN_ERR "[DISPLAY] %s: Check I2C functionality failed.\n", __func__);
return -ENODEV;
}
dsi_lvds_inst = kzalloc(sizeof(struct dsi_lvds_bridge_instance), GFP_KERNEL);
if (dsi_lvds_inst == NULL) {
printk(KERN_ERR "[DISPLAY] %s: Can not allocate memory.\n", __func__);
return -ENOMEM;
}
dsi_lvds_inst->client = client;
i2c_set_clientdata(client, dsi_lvds_inst);
dsi_lvds_inst->client->addr = 0x0F;
printk(KERN_INFO "[DISPLAY] %s: Exit\n", __func__);
return 0;
}
3. use GPIO to reset lvds state
/* ************************************************************************* *\
* FUNCTION: dsi_lvds_set_bridge_reset_state
*
* DESCRIPTION: This function uses GPIO to force in and out of reset state.
\* ************************************************************************* */
void dsi_lvds_set_bridge_reset_state(int state)
{
printk(KERN_INFO "[DISPLAY ] %s: state = %d, gpio = %d\n", __func__, state, gpio_get_value(GPIO_MIPI_BRIDGE_RESET));
if (state) {
gpio_direction_output(GPIO_MIPI_BRIDGE_RESET, 0);
gpio_set_value_cansleep(GPIO_MIPI_BRIDGE_RESET, 0);
mdelay(10);
} else {
gpio_direction_output(GPIO_MIPI_BRIDGE_RESET, 0);
gpio_set_value_cansleep(GPIO_MIPI_BRIDGE_RESET, 0); /*Pull MIPI Bridge reset pin to Low */
mdelay(20);
gpio_direction_output(GPIO_MIPI_BRIDGE_RESET, 1);
gpio_set_value_cansleep(GPIO_MIPI_BRIDGE_RESET, 1); /*Pull MIPI Bridge reset pin to High */
mdelay(40);
}
}
4. dsi_lvds_configure_lvds_bridge () , 用I2C总线,配置lvds bridge设备。
/* ************************************************************************* *\
* FUNCTION: dsi_lvds_configure_lvds_bridge
*
* DESCRIPTION: This function uses I2C interface to set bridge registers.
* to configure timings and MIPI lanes.
\* ************************************************************************* */
void dsi_lvds_configure_lvds_bridge(struct drm_device *dev)
{
if (lvds_disp_init) {
printk(KERN_ALERT "[DISPLAY ] %s is already initialized\n", __func__);
return;
}
printk(KERN_INFO "[DISPLAY ]%s: Enter\n", __func__);
DSI_I2C_ByteWrite(0x013C, 0x00050006, 6); /*PPI_TX_RX_TA, BTA parameters */
DSI_I2C_ByteRead(0x013C, 4);
DSI_I2C_ByteWrite(0x0114, 0x00000004, 6); /*PPI_LPTXTIMCNT */
#ifdef FIXME_MLD // DV0.9 changes
DSI_I2C_ByteWrite(0x0164, 0x00000007, 6); /*PPI_D0S_CLRSIPOCOUNT */
DSI_I2C_ByteWrite(0x0168, 0x00000007, 6); /*PPI_D1S_CLRSIPOCOUNT */
DSI_I2C_ByteWrite(0x016c, 0x00000007, 6); /*PPI_D2S_CLRSIPOCOUNT */
DSI_I2C_ByteWrite(0x0170, 0x00000007, 6); /*PPI_D3S_CLRSIPOCOUNT */
#else
DSI_I2C_ByteWrite(0x0164, 0x00000001, 6); /*PPI_D0S_CLRSIPOCOUNT */
DSI_I2C_ByteWrite(0x0168, 0x00000001, 6); /*PPI_D1S_CLRSIPOCOUNT */
DSI_I2C_ByteWrite(0x016c, 0x00000001, 6); /*PPI_D2S_CLRSIPOCOUNT */
DSI_I2C_ByteWrite(0x0170, 0x00000001, 6); /*PPI_D3S_CLRSIPOCOUNT */
#endif
/*Enabling MIPI & PPI lanes, Enable 4 lanes */
DSI_I2C_ByteWrite(0x0134, 0x0000001F, 6); /*PPI_LANEENABLE */
DSI_I2C_ByteWrite(0x0210, 0x0000001F, 6); /*DSI_LANEENABLE */
DSI_I2C_ByteWrite(0x0104, 0x00000001, 6); /*PPI_SARTPPI */
DSI_I2C_ByteWrite(0x0204, 0x00000001, 6); /*DSI_SARTPPI */
#ifdef FIXME_MLD
/*Setting LVDS output frequency */
DSI_I2C_ByteWrite(0x04A0, 0x00000006, 6); /*LVDS PHY Register 0 (LVPHY0) */
/*Calculating video panel control settings */
/*Setting video panel control register */
DSI_I2C_ByteWrite(0x0450, 0x00000130, 6); /*VPCTRL, Video Path Control, VTGen=ON */
/*Setting display timing registers */
DSI_I2C_ByteWrite(0x0454, 0x0064000A, 6); /*HTIM1, HBPR=100, HPW=10 */
DSI_I2C_ByteWrite(0x0458, 0x00BE0400, 6); /*HTIM2, HFPR=190, HDISPR=1024 */
DSI_I2C_ByteWrite(0x045c, 0x000F0005, 6); /*VTIM1, VBPR=15, VPW=5 */
DSI_I2C_ByteWrite(0x0460, 0x000F0258, 6); /*VTIM2, VFPR=15, VDISPR=600 */
#else
/*Setting LVDS output frequency */
DSI_I2C_ByteWrite(0x04A0, 0x00048006, 6); /*LVDS PHY Register 0 (LVPHY0) */
/*Calculating video panel control settings */
/*Setting video panel control register */
DSI_I2C_ByteWrite(0x0450, 0x00000120, 6); /*VPCTRL, Video Path Control, VTGen=ON */
/*Setting display timing registers */
DSI_I2C_ByteWrite(0x0454, 0x00280028, 6); /*HTIM1, HBPR=100, HPW=10 */
DSI_I2C_ByteWrite(0x0458, 0x00500500, 6); /*HTIM2, HFPR=190, HDISPR=1024 */
DSI_I2C_ByteWrite(0x045c, 0x000e000a, 6); /*VTIM1, VBPR=15, VPW=5 */
DSI_I2C_ByteWrite(0x0460, 0x000e0320, 6); /*VTIM2, VFPR=15, VDISPR=600 */
#endif
DSI_I2C_ByteWrite(0x0464, 0x00000001, 6); /*VFUEN */
/*Setting LVDS bit arrangement */
DSI_I2C_ByteWrite(0x0480, 0x03020100, 6); /*LVMX0003 */
DSI_I2C_ByteWrite(0x0484, 0x08050704, 6); /*LVMX0407 */
DSI_I2C_ByteWrite(0x0488, 0x0F0E0A09, 6); /*LVMX0811 */
DSI_I2C_ByteWrite(0x048C, 0x100D0C0B, 6); /*LVMX1215 */
DSI_I2C_ByteWrite(0x0490, 0x12111716, 6); /*LVMX1619 */
DSI_I2C_ByteWrite(0x0494, 0x1B151413, 6); /*LVMX2023 */
DSI_I2C_ByteWrite(0x0498, 0x061A1918, 6); /*LVMX2427 */
#ifdef FIXME_MLD //DV0.9 changes
DSI_I2C_ByteWrite(0x049c, 0x00000101, 6); /*LVCFG */
#else
DSI_I2C_ByteWrite(0x049c, 0x00000001, 6); /*LVCFG */
#endif
DSI_I2C_ByteWrite(0x0288, 0xFFFFFFFF, 6); /*DSI_INTCLR */
lvds_disp_init = 1;
printk(KERN_INFO "[DISPLAY]%s: Exit\n", __func__);
}
5. panel on/off
/* ************************************************************************* *\
* FUNCTION: dsi_lvds_toshiba_bridge_panel_off
*
* DESCRIPTION: This function uses GPIO to turn OFF panel.
\* ************************************************************************* */
void dsi_lvds_toshiba_bridge_panel_off(void)
{
printk(KERN_INFO "[DISPLAY ] %s\n", __func__);
gpio_direction_output(GPIO_MIPI_LCD_STBYB, 0);
gpio_set_value_cansleep(GPIO_MIPI_LCD_STBYB, 0); /*Pull LCD_STBYB pin to Low */
mdelay(1);
gpio_direction_output(GPIO_MIPI_PANEL_RESET, 0);
gpio_set_value_cansleep(GPIO_MIPI_PANEL_RESET, 0);
mdelay(1);
}
/* ************************************************************************* *\
* FUNCTION: dsi_lvds_toshiba_bridge_panel_on
*
* DESCRIPTION: This function uses GPIO to turn ON panel.
\* ************************************************************************* */
void dsi_lvds_toshiba_bridge_panel_on(void)
{
printk(KERN_INFO "[DISPLAY ] %s\n", __func__);
/* set following pin to low */
gpio_direction_output(GPIO_MIPI_LCD_STBYB, 0);
gpio_set_value_cansleep(GPIO_MIPI_LCD_STBYB, 0); /*Pull LCD_STBYB pin to Low */
gpio_direction_output(GPIO_MIPI_PANEL_RESET, 0);
gpio_set_value_cansleep(GPIO_MIPI_PANEL_RESET, 0);
gpio_direction_output(GPIO_MIPI_LCD_VADD, 0);
gpio_set_value_cansleep(GPIO_MIPI_LCD_VADD, 0);
gpio_direction_output(GPIO_MIPI_LCD_BIAS_EN, 0);
gpio_set_value_cansleep(GPIO_MIPI_LCD_BIAS_EN, 0);
mdelay(10);
/* STBYB */
gpio_direction_output(GPIO_MIPI_LCD_STBYB, 1);
gpio_set_value_cansleep(GPIO_MIPI_LCD_STBYB, 1); /*Pull LCD_STBYB pin to High */
mdelay(10);
/* VADD */
gpio_direction_output(GPIO_MIPI_LCD_VADD, 1);
mdelay(50);
/* RESET */
gpio_direction_output(GPIO_MIPI_PANEL_RESET, 1);
gpio_set_value_cansleep(GPIO_MIPI_PANEL_RESET, 1);
mdelay(100);
/* BIAS */
gpio_direction_output(GPIO_MIPI_LCD_BIAS_EN, 1);
gpio_set_value_cansleep(GPIO_MIPI_LCD_BIAS_EN, 1);
}
6. init / deinit
/* ************************************************************************* *\
* FUNCTION: dsi_lvds_init_lvds_bridge
*
* DESCRIPTION: This function does all one time init. Currently defining
* GPIOs only.
\* ************************************************************************* */
void dsi_lvds_init_lvds_bridge(struct drm_device *dev)
{
/* request GPIOs */
gpio_request(GPIO_MIPI_BRIDGE_RESET, "display");
gpio_request(GPIO_MIPI_PANEL_RESET , "display");
gpio_request(GPIO_MIPI_LCD_STBYB, "display");
gpio_request(GPIO_MIPI_LCD_BIAS_EN, "display");
gpio_request(GPIO_MIPI_LCD_VADD, "display");
}
/* ************************************************************************* *\
* FUNCTION: dsi_lvds_deinit_lvds_bridge
*
* DESCRIPTION: This function does is called during deinit time.
*
\* ************************************************************************* */
void dsi_lvds_deinit_lvds_bridge(struct drm_device *dev)
{
if (!lvds_disp_init) {
printk(KERN_ALERT "[DISPLAY ] %s has not initialized\n", __func__);
return;
}
printk(KERN_INFO "[DISPLAY ] Enter %s\n", __func__);
lvds_disp_init = 0;
}
7. get params
/* ************************************************************************* *\
* FUNCTION: dsi_lvds_bridge_get_display_params
*
* DESCRIPTION: This function is a callback to get the panel resolution and
* timing settings.
*
\* ************************************************************************* */
void dsi_lvds_bridge_get_display_params(struct drm_display_mode *mode)
{
#ifdef FIXME_MLD // Changes for DV0.9 display
/* This settings is for 1024*600 panel*/
mode->hdisplay = 1024;
mode->vdisplay = 600;
mode->hsync_start = 1655;
mode->hsync_end = 1665;
mode->htotal = 1765;
mode->vsync_start = 615;
mode->vsync_end = 620;
mode->vtotal = 635;
mode->clock = 33324;
#else
mode->hdisplay = 1200;
mode->vdisplay = 800;
mode->hsync_start = 1360;
mode->hsync_end = 1400;
mode->htotal = 1440;
mode->vsync_start = 814;
mode->vsync_end = 824;
mode->vtotal = 838;
mode->clock = 33324;
#endif //end FIXME_MLD
printk(KERN_INFO "[DISPLAY]: hdisplay(w) is %d\n", mode->hdisplay);
printk(KERN_INFO "[DISPLAY]: vdisplay(h) is %d\n", mode->vdisplay);
printk(KERN_INFO "[DISPLAY]: HSS is %d\n", mode->hsync_start);
printk(KERN_INFO "[DISPLAY]: HSE is %d\n", mode->hsync_end);
printk(KERN_INFO "[DISPLAY]: htotal is %d\n", mode->htotal);
printk(KERN_INFO "[DISPLAY]: VSS is %d\n", mode->vsync_start);
printk(KERN_INFO "[DISPLAY]: VSE is %d\n", mode->vsync_end);
printk(KERN_INFO "[DISPLAY]: vtotal is %d\n", mode->vtotal);
printk(KERN_INFO "[DISPLAY]: clock is %d\n", mode->clock);
}
8. 具体动作: read / write
/* ************************************************************************* *\
* FUNCTION: DSI_I2C_ByteRead
*
* DESCRIPTION: Local functions to read I2C registers
*
\* ************************************************************************* */
static int DSI_I2C_ByteRead(u16 reg, int count)
{
if (dsi_lvds_inst->client)
return __DSI_I2C_ByteRead(reg, count);
else
return -EIO;
}
/* ************************************************************************* *\
* FUNCTION: __DSI_I2C_ByteRead
*
* DESCRIPTION: Local functions to process read req for I2C registers
*
\* ************************************************************************* */
static int __DSI_I2C_ByteRead(u16 reg, int count)
{
char rxData[4] = {0};
char regData[2] = {0};
struct i2c_msg msgs[] = {
{
.addr = dsi_lvds_inst->client->addr,
.flags = 0,
.len = 2,
},
{
.addr = dsi_lvds_inst->client->addr,
.flags = I2C_M_RD,
.len = count - 2,
},
};
regData[0] = (reg & 0xFF00) >> 8;
regData[1] = reg & 0xFF;
msgs[0].buf = regData;
msgs[1].buf = rxData;
printk(KERN_INFO "Register: 0x%x\n", reg);
if (i2c_transfer(dsi_lvds_inst->client->adapter, msgs, 2) < 0) {
printk(KERN_ERR "[DISPLAY] %s: transfer error\n", __func__);
return -EIO;
} else if (count > 2) {
int i = 0;
for (i = count - 3; i > -1; --i)
printk(KERN_INFO "%02x ", rxData[i]);
printk(KERN_INFO "\n");
return rxData[0];
}
return 0;
}
/* ************************************************************************* *\
* FUNCTION: DSI_I2C_ByteWrite
*
* DESCRIPTION: Local functions to issue write req for I2C registers
*
\* ************************************************************************* */
static int DSI_I2C_ByteWrite(u16 reg, u32 data, int count)
{
if (dsi_lvds_inst->client)
return __DSI_I2C_ByteWrite(reg, data, count);
else
return -EIO;
}
/* ************************************************************************* *\
* FUNCTION: __DSI_I2C_ByteWrite
*
* DESCRIPTION: Local functions to process write req for I2C registers
*
\* ************************************************************************* */
static int __DSI_I2C_ByteWrite(u16 reg, u32 data, int count)
{
char txData[6] = {0};
struct i2c_msg msg[] = {
{
.addr = dsi_lvds_inst->client->addr,
.flags = 0,
.len = count,
},
};
/*Set the register */
txData[0] = (reg & 0xFF00) >> 8;
txData[1] = reg & 0xFF;
if (count == 6) {
/*Set the data */
txData[2] = (data & 0xFF);
txData[3] = (data & 0xFF00) >> 8;
txData[4] = (data & 0xFF0000) >> 16;
txData[5] = (data & 0xFF000000) >> 24;
} else {
/* Not valid for this bridge chipset */
}
printk(KERN_INFO "[DISPLAY] %s: addr = %x, reg = %x, data = %x\n",
__func__, dsi_lvds_inst->client->addr, reg, data);
msg[0].buf = txData;
if (i2c_transfer(dsi_lvds_inst->client->adapter, msg, 1) < 0) {
printk(KERN_ERR "[DISPLAY] %s: transfer error\n", __func__);
return -EIO;
} else {
return 0;
}
}