/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ /* * Toshiba MIPI-DSI-to-LVDS Bridge driver. * Device Model TC358764XBG/65XBG. * Reference document: TC358764XBG_65XBG_V119.pdf * * The Host sends a DSI Generic Long Write packet (Data ID = 0x29) over the * DSI link for each write access transaction to the chip configuration * registers. * Payload of this packet is 16-bit register address and 32-bit data. * Multiple data values are allowed for sequential addresses. * * The Host sends a DSI Generic Read packet (Data ID = 0x24) over the DSI * link for each read request transaction to the chip configuration * registers. Payload of this packet is further defined as follows: * 16-bit address followed by a 32-bit value (Generic Long Read Response * packet). * * The bridge supports 5 GPIO lines controlled via the GPC register. * * The bridge support I2C Master/Slave. * The I2C slave can be used for read/write to the bridge register instead of * using the DSI interface. * I2C slave address is 0x0F (read/write 0x1F/0x1E). * The I2C Master can be used for communication with the panel if * it has an I2C slave. * * NOTE: The I2C interface is not used in this driver. * Only the DSI interface is used for read/write the bridge registers. * * Pixel data can be transmitted in non-burst or burst fashion. * Non-burst refers to pixel data packet transmission time on DSI link * being roughly the same (to account for packet overhead time) * as active video line time on LVDS output (i.e. DE = 1). * And burst refers to pixel data packet transmission time on DSI link * being less than the active video line time on LVDS output. * Video mode transmission is further differentiated by the types of * timing events being transmitted. * Video pulse mode refers to the case where both sync start and sync end * events (for frame and line) are transmitted. * Video event mode refers to the case where only sync start events * are transmitted. * This is configured via register bit VPCTRL.EVTMODE. * */ /* #define DEBUG 1 */ /** * Use the I2C master to control the panel. */ /* #define TC358764_USE_I2C_MASTER */ #define DRV_NAME "mipi_tc358764" #include #include #include #include #include "msm_fb.h" #include "mdp4.h" #include "mipi_dsi.h" #include "mipi_tc358764_dsi2lvds.h" /* Registers definition */ /* DSI D-PHY Layer Registers */ #define D0W_DPHYCONTTX 0x0004 /* Data Lane 0 DPHY Tx Control */ #define CLW_DPHYCONTRX 0x0020 /* Clock Lane DPHY Rx Control */ #define D0W_DPHYCONTRX 0x0024 /* Data Lane 0 DPHY Rx Control */ #define D1W_DPHYCONTRX 0x0028 /* Data Lane 1 DPHY Rx Control */ #define D2W_DPHYCONTRX 0x002C /* Data Lane 2 DPHY Rx Control */ #define D3W_DPHYCONTRX 0x0030 /* Data Lane 3 DPHY Rx Control */ #define COM_DPHYCONTRX 0x0038 /* DPHY Rx Common Control */ #define CLW_CNTRL 0x0040 /* Clock Lane Control */ #define D0W_CNTRL 0x0044 /* Data Lane 0 Control */ #define D1W_CNTRL 0x0048 /* Data Lane 1 Control */ #define D2W_CNTRL 0x004C /* Data Lane 2 Control */ #define D3W_CNTRL 0x0050 /* Data Lane 3 Control */ #define DFTMODE_CNTRL 0x0054 /* DFT Mode Control */ /* DSI PPI Layer Registers */ #define PPI_STARTPPI 0x0104 /* START control bit of PPI-TX function. */ #define PPI_BUSYPPI 0x0108 #define PPI_LINEINITCNT 0x0110 /* Line Initialization Wait Counter */ #define PPI_LPTXTIMECNT 0x0114 #define PPI_LANEENABLE 0x0134 /* Enables each lane at the PPI layer. */ #define PPI_TX_RX_TA 0x013C /* DSI Bus Turn Around timing parameters */ /* Analog timer function enable */ #define PPI_CLS_ATMR 0x0140 /* Delay for Clock Lane in LPRX */ #define PPI_D0S_ATMR 0x0144 /* Delay for Data Lane 0 in LPRX */ #define PPI_D1S_ATMR 0x0148 /* Delay for Data Lane 1 in LPRX */ #define PPI_D2S_ATMR 0x014C /* Delay for Data Lane 2 in LPRX */ #define PPI_D3S_ATMR 0x0150 /* Delay for Data Lane 3 in LPRX */ #define PPI_D0S_CLRSIPOCOUNT 0x0164 #define PPI_D1S_CLRSIPOCOUNT 0x0168 /* For lane 1 */ #define PPI_D2S_CLRSIPOCOUNT 0x016C /* For lane 2 */ #define PPI_D3S_CLRSIPOCOUNT 0x0170 /* For lane 3 */ #define CLS_PRE 0x0180 /* Digital Counter inside of PHY IO */ #define D0S_PRE 0x0184 /* Digital Counter inside of PHY IO */ #define D1S_PRE 0x0188 /* Digital Counter inside of PHY IO */ #define D2S_PRE 0x018C /* Digital Counter inside of PHY IO */ #define D3S_PRE 0x0190 /* Digital Counter inside of PHY IO */ #define CLS_PREP 0x01A0 /* Digital Counter inside of PHY IO */ #define D0S_PREP 0x01A4 /* Digital Counter inside of PHY IO */ #define D1S_PREP 0x01A8 /* Digital Counter inside of PHY IO */ #define D2S_PREP 0x01AC /* Digital Counter inside of PHY IO */ #define D3S_PREP 0x01B0 /* Digital Counter inside of PHY IO */ #define CLS_ZERO 0x01C0 /* Digital Counter inside of PHY IO */ #define D0S_ZERO 0x01C4 /* Digital Counter inside of PHY IO */ #define D1S_ZERO 0x01C8 /* Digital Counter inside of PHY IO */ #define D2S_ZERO 0x01CC /* Digital Counter inside of PHY IO */ #define D3S_ZERO 0x01D0 /* Digital Counter inside of PHY IO */ #define PPI_CLRFLG 0x01E0 /* PRE Counters has reached set values */ #define PPI_CLRSIPO 0x01E4 /* Clear SIPO values, Slave mode use only. */ #define HSTIMEOUT 0x01F0 /* HS Rx Time Out Counter */ #define HSTIMEOUTENABLE 0x01F4 /* Enable HS Rx Time Out Counter */ #define DSI_STARTDSI 0x0204 /* START control bit of DSI-TX function */ #define DSI_BUSYDSI 0x0208 #define DSI_LANEENABLE 0x0210 /* Enables each lane at the Protocol layer. */ #define DSI_LANESTATUS0 0x0214 /* Displays lane is in HS RX mode. */ #define DSI_LANESTATUS1 0x0218 /* Displays lane is in ULPS or STOP state */ #define DSI_INTSTATUS 0x0220 /* Interrupt Status */ #define DSI_INTMASK 0x0224 /* Interrupt Mask */ #define DSI_INTCLR 0x0228 /* Interrupt Clear */ #define DSI_LPTXTO 0x0230 /* Low Power Tx Time Out Counter */ #define DSIERRCNT 0x0300 /* DSI Error Count */ #define APLCTRL 0x0400 /* Application Layer Control */ #define RDPKTLN 0x0404 /* Command Read Packet Length */ #define VPCTRL 0x0450 /* Video Path Control */ #define HTIM1 0x0454 /* Horizontal Timing Control 1 */ #define HTIM2 0x0458 /* Horizontal Timing Control 2 */ #define VTIM1 0x045C /* Vertical Timing Control 1 */ #define VTIM2 0x0460 /* Vertical Timing Control 2 */ #define VFUEN 0x0464 /* Video Frame Timing Update Enable */ /* Mux Input Select for LVDS LINK Input */ #define LVMX0003 0x0480 /* Bit 0 to 3 */ #define LVMX0407 0x0484 /* Bit 4 to 7 */ #define LVMX0811 0x0488 /* Bit 8 to 11 */ #define LVMX1215 0x048C /* Bit 12 to 15 */ #define LVMX1619 0x0490 /* Bit 16 to 19 */ #define LVMX2023 0x0494 /* Bit 20 to 23 */ #define LVMX2427 0x0498 /* Bit 24 to 27 */ #define LVCFG 0x049C /* LVDS Configuration */ #define LVPHY0 0x04A0 /* LVDS PHY 0 */ #define LVPHY1 0x04A4 /* LVDS PHY 1 */ #define SYSSTAT 0x0500 /* System Status */ #define SYSRST 0x0504 /* System Reset */ /* GPIO Registers */ #define GPIOC 0x0520 /* GPIO Control */ #define GPIOO 0x0524 /* GPIO Output */ #define GPIOI 0x0528 /* GPIO Input */ /* I2C Registers */ #define I2CTIMCTRL 0x0540 /* I2C IF Timing and Enable Control */ #define I2CMADDR 0x0544 /* I2C Master Addressing */ #define WDATAQ 0x0548 /* Write Data Queue */ #define RDATAQ 0x054C /* Read Data Queue */ /* Chip ID and Revision ID Register */ #define IDREG 0x0580 #define TC358764XBG_ID 0x00006500 /* Debug Registers */ #define DEBUG00 0x05A0 /* Debug */ #define DEBUG01 0x05A4 /* LVDS Data */ /* PWM */ static u32 d2l_pwm_freq_hz = (3.921*1000); #define PWM_FREQ_HZ (d2l_pwm_freq_hz) #define PWM_PERIOD_USEC (USEC_PER_SEC / PWM_FREQ_HZ) #define PWM_DUTY_LEVEL (PWM_PERIOD_USEC / PWM_LEVEL) #define CMD_DELAY 100 #define DSI_MAX_LANES 4 #define KHZ 1000 #define MHZ (1000*1000) /** * Command payload for DTYPE_GEN_LWRITE (0x29) / DTYPE_GEN_READ2 (0x24). */ struct wr_cmd_payload { u16 addr; u32 data; } __packed; /* * Driver state. */ static struct msm_panel_common_pdata *d2l_common_pdata; struct msm_fb_data_type *d2l_mfd; static struct dsi_buf d2l_tx_buf; static struct dsi_buf d2l_rx_buf; static int led_pwm; static struct pwm_device *bl_pwm; static struct pwm_device *tn_pwm; static int bl_level; static u32 d2l_gpio_out_mask; static u32 d2l_gpio_out_val; static u32 d2l_3d_gpio_enable; static u32 d2l_3d_gpio_mode; static int d2l_enable_3d; static struct i2c_client *d2l_i2c_client; static struct i2c_driver d2l_i2c_slave_driver; static int mipi_d2l_init(void); static int mipi_d2l_enable_3d(struct msm_fb_data_type *mfd, bool enable, bool mode); static u32 d2l_i2c_read_reg(struct i2c_client *client, u16 reg); static u32 d2l_i2c_write_reg(struct i2c_client *client, u16 reg, u32 val); /** * Read a bridge register * * @param mfd * * @return register data value */ static u32 mipi_d2l_read_reg(struct msm_fb_data_type *mfd, u16 reg) { u32 data; int len = 4; struct dcs_cmd_req cmdreq; struct dsi_cmd_desc cmd_read_reg = { DTYPE_GEN_READ2, 1, 0, 1, 0, /* cmd 0x24 */ sizeof(reg), (char *) ®}; mipi_dsi_buf_init(&d2l_rx_buf); memset(&cmdreq, 0, sizeof(cmdreq)); cmdreq.cmds = &cmd_read_reg; cmdreq.cmds_cnt = 1; cmdreq.flags = CMD_REQ_RX | CMD_REQ_COMMIT | CMD_REQ_NO_MAX_PKT_SIZE; cmdreq.rbuf = &d2l_rx_buf; cmdreq.rlen = 0; cmdreq.cb = NULL; mipi_dsi_cmdlist_put(&cmdreq); data = *(u32 *)d2l_rx_buf.data; if (d2l_rx_buf.len != 4) pr_err("%s: invalid rlen=%d, expecting 4.\n", __func__, len); pr_debug("%s: reg=0x%x.data=0x%08x.\n", __func__, reg, data); return data; } /** * Write a bridge register * * @param mfd * * @return int */ static int mipi_d2l_write_reg(struct msm_fb_data_type *mfd, u16 reg, u32 data) { struct wr_cmd_payload payload; struct dcs_cmd_req cmdreq; struct dsi_cmd_desc cmd_write_reg = { DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(payload), (char *)&payload}; payload.addr = reg; payload.data = data; memset(&cmdreq, 0, sizeof(cmdreq)); cmdreq.cmds = &cmd_write_reg; cmdreq.cmds_cnt = 1; cmdreq.flags = CMD_REQ_COMMIT; cmdreq.rlen = 0; cmdreq.cb = NULL; mipi_dsi_cmdlist_put(&cmdreq); pr_debug("%s: reg=0x%x. data=0x%x.\n", __func__, reg, data); return 0; } static void mipi_d2l_read_status(struct msm_fb_data_type *mfd) { mipi_d2l_read_reg(mfd, DSI_LANESTATUS0); /* 0x214 */ mipi_d2l_read_reg(mfd, DSI_LANESTATUS1); /* 0x218 */ mipi_d2l_read_reg(mfd, DSI_INTSTATUS); /* 0x220 */ mipi_d2l_read_reg(mfd, SYSSTAT); /* 0x500 */ } static void mipi_d2l_read_status_via_i2c(struct i2c_client *client) { u32 tmp = 0; tmp = d2l_i2c_read_reg(client, DSIERRCNT); d2l_i2c_write_reg(client, DSIERRCNT, 0xFFFF0000); d2l_i2c_read_reg(client, DSI_LANESTATUS0); /* 0x214 */ d2l_i2c_read_reg(client, DSI_LANESTATUS1); /* 0x218 */ d2l_i2c_read_reg(client, DSI_INTSTATUS); /* 0x220 */ d2l_i2c_read_reg(client, SYSSTAT); /* 0x500 */ d2l_i2c_write_reg(client, DSIERRCNT, tmp); } /** * Init the D2L bridge via the DSI interface for Video. * * VPCTRL.EVTMODE (0x20) configuration bit is needed to determine whether * video timing information is delivered in pulse mode or event mode. * In pulse mode, both Sync Start and End packets are required. * In event mode, only Sync Start packets are required. * * @param mfd * * @return int */ static int mipi_d2l_dsi_init_sequence(struct msm_fb_data_type *mfd) { struct mipi_panel_info *mipi = &mfd->panel_info.mipi; u32 lanes_enable; u32 vpctrl; u32 htime1; u32 vtime1; u32 htime2; u32 vtime2; u32 ppi_tx_rx_ta; /* BTA Bus-Turn-Around */ u32 lvcfg; u32 hbpr; /* Horizontal Back Porch */ u32 hpw; /* Horizontal Pulse Width */ u32 vbpr; /* Vertical Back Porch */ u32 vpw; /* Vertical Pulse Width */ u32 hfpr; /* Horizontal Front Porch */ u32 hsize; /* Horizontal Active size */ u32 vfpr; /* Vertical Front Porch */ u32 vsize; /* Vertical Active size */ bool vesa_rgb888 = false; lanes_enable = 0x01; /* clock-lane enable */ lanes_enable |= (mipi->data_lane0 << 1); lanes_enable |= (mipi->data_lane1 << 2); lanes_enable |= (mipi->data_lane2 << 3); lanes_enable |= (mipi->data_lane3 << 4); if (mipi->traffic_mode == DSI_NON_BURST_SYNCH_EVENT) vpctrl = 0x01000120; else if (mipi->traffic_mode == DSI_NON_BURST_SYNCH_PULSE) vpctrl = 0x01000100; else { pr_err("%s.unsupported traffic_mode %d.\n", __func__, mipi->traffic_mode); return -EINVAL; } if (mfd->panel_info.clk_rate > 800*1000*1000) { pr_err("%s.unsupported clk_rate %d.\n", __func__, mfd->panel_info.clk_rate); return -EINVAL; } pr_debug("%s.xres=%d.yres=%d.fps=%d.dst_format=%d.\n", __func__, mfd->panel_info.xres, mfd->panel_info.yres, mfd->panel_info.mipi.frame_rate, mfd->panel_info.mipi.dst_format); hbpr = mfd->panel_info.lcdc.h_back_porch; hpw = mfd->panel_info.lcdc.h_pulse_width; vbpr = mfd->panel_info.lcdc.v_back_porch; vpw = mfd->panel_info.lcdc.v_pulse_width; htime1 = (hbpr << 16) + hpw; vtime1 = (vbpr << 16) + vpw; hfpr = mfd->panel_info.lcdc.h_front_porch; hsize = mfd->panel_info.xres; vfpr = mfd->panel_info.lcdc.v_front_porch; vsize = mfd->panel_info.yres; htime2 = (hfpr << 16) + hsize; vtime2 = (vfpr << 16) + vsize; lvcfg = 0x0003; /* PCLK=DCLK/3, Dual Link, LVEN */ vpctrl = 0x01000120; /* Output RGB888 , Event-Mode , */ ppi_tx_rx_ta = 0x00040004; if (mfd->panel_info.xres == 1366) { ppi_tx_rx_ta = 0x00040004; lvcfg = 0x01; /* LVEN */ vesa_rgb888 = true; } if (mfd->panel_info.xres == 1200) { lvcfg = 0x0103; /* PCLK=DCLK/4, Dual Link, LVEN */ vesa_rgb888 = true; } pr_debug("%s.htime1=0x%x.\n", __func__, htime1); pr_debug("%s.vtime1=0x%x.\n", __func__, vtime1); pr_debug("%s.vpctrl=0x%x.\n", __func__, vpctrl); pr_debug("%s.lvcfg=0x%x.\n", __func__, lvcfg); mipi_d2l_write_reg(mfd, SYSRST, 0xFF); msleep(30); if (vesa_rgb888) { /* VESA format instead of JEIDA format for RGB888 */ mipi_d2l_write_reg(mfd, LVMX0003, 0x03020100); mipi_d2l_write_reg(mfd, LVMX0407, 0x08050704); mipi_d2l_write_reg(mfd, LVMX0811, 0x0F0E0A09); mipi_d2l_write_reg(mfd, LVMX1215, 0x100D0C0B); mipi_d2l_write_reg(mfd, LVMX1619, 0x12111716); mipi_d2l_write_reg(mfd, LVMX2023, 0x1B151413); mipi_d2l_write_reg(mfd, LVMX2427, 0x061A1918); } mipi_d2l_write_reg(mfd, PPI_TX_RX_TA, ppi_tx_rx_ta); /* BTA */ mipi_d2l_write_reg(mfd, PPI_LPTXTIMECNT, 0x00000004); mipi_d2l_write_reg(mfd, PPI_D0S_CLRSIPOCOUNT, 0x00000003); mipi_d2l_write_reg(mfd, PPI_D1S_CLRSIPOCOUNT, 0x00000003); mipi_d2l_write_reg(mfd, PPI_D2S_CLRSIPOCOUNT, 0x00000003); mipi_d2l_write_reg(mfd, PPI_D3S_CLRSIPOCOUNT, 0x00000003); mipi_d2l_write_reg(mfd, PPI_LANEENABLE, lanes_enable); mipi_d2l_write_reg(mfd, DSI_LANEENABLE, lanes_enable); mipi_d2l_write_reg(mfd, PPI_STARTPPI, 0x00000001); mipi_d2l_write_reg(mfd, DSI_STARTDSI, 0x00000001); mipi_d2l_write_reg(mfd, VPCTRL, vpctrl); /* RGB888 + Event mode */ mipi_d2l_write_reg(mfd, HTIM1, htime1); mipi_d2l_write_reg(mfd, VTIM1, vtime1); mipi_d2l_write_reg(mfd, HTIM2, htime2); mipi_d2l_write_reg(mfd, VTIM2, vtime2); mipi_d2l_write_reg(mfd, VFUEN, 0x00000001); mipi_d2l_write_reg(mfd, LVCFG, lvcfg); /* Enables LVDS tx */ return 0; } /** * Set Backlight level. * * @param pwm * @param level * * @return int */ static int mipi_d2l_set_backlight_level(struct pwm_device *pwm, int level) { int ret = 0; pr_debug("%s: level=%d.\n", __func__, level); if ((pwm == NULL) || (level > PWM_LEVEL) || (level < 0)) { pr_err("%s.pwm=NULL.\n", __func__); return -EINVAL; } ret = pwm_config(pwm, PWM_DUTY_LEVEL * level, PWM_PERIOD_USEC); if (ret) { pr_err("%s: pwm_config() failed err=%d.\n", __func__, ret); return ret; } ret = pwm_enable(pwm); if (ret) { pr_err("%s: pwm_enable() failed err=%d\n", __func__, ret); return ret; } return 0; } /** * Set TN CLK. * * @param pwm * @param level * * @return int */ static int mipi_d2l_set_tn_clk(struct pwm_device *pwm, u32 usec) { int ret = 0; pr_debug("%s: usec=%d.\n", __func__, usec); ret = pwm_config(pwm, usec/2 , usec); if (ret) { pr_err("%s: pwm_config() failed err=%d.\n", __func__, ret); return ret; } ret = pwm_enable(pwm); if (ret) { pr_err("%s: pwm_enable() failed err=%d\n", __func__, ret); return ret; } return 0; } /** * LCD ON. * * Set LCD On via MIPI interface or I2C-Slave interface. * Set Backlight on. * * @param pdev * * @return int */ static int mipi_d2l_lcd_on(struct platform_device *pdev) { int ret = 0; u32 chip_id; struct msm_fb_data_type *mfd; pr_info("%s.\n", __func__); /* wait for valid clock before sending data over DSI or I2C. */ msleep(30); mfd = platform_get_drvdata(pdev); d2l_mfd = mfd; if (!mfd) return -ENODEV; if (mfd->key != MFD_KEY) return -EINVAL; chip_id = mipi_d2l_read_reg(mfd, IDREG); if (chip_id != TC358764XBG_ID) { pr_err("%s: invalid chip_id=0x%x", __func__, chip_id); return -ENODEV; } ret = mipi_d2l_dsi_init_sequence(mfd); if (ret) return ret; mipi_d2l_write_reg(mfd, GPIOC, d2l_gpio_out_mask); /* Set gpio#4=U/D=0, gpio#3=L/R=1 , gpio#2,1=CABC=0, gpio#0=NA. */ mipi_d2l_write_reg(mfd, GPIOO, d2l_gpio_out_val); d2l_pwm_freq_hz = (3.921*1000); if (bl_level == 0) bl_level = PWM_LEVEL * 2 / 3 ; /* Default ON value */ /* Set backlight via PWM */ if (bl_pwm) { ret = mipi_d2l_set_backlight_level(bl_pwm, bl_level); if (ret) pr_err("%s.mipi_d2l_set_backlight_level.ret=%d", __func__, ret); } mipi_d2l_read_status(mfd); mipi_d2l_enable_3d(mfd, false, false); /* Add I2C driver only after DSI-CLK is running */ if (d2l_i2c_client == NULL) i2c_add_driver(&d2l_i2c_slave_driver); pr_info("%s.ret=%d.\n", __func__, ret); return ret; } /** * LCD OFF. * * @param pdev * * @return int */ static int mipi_d2l_lcd_off(struct platform_device *pdev) { int ret; struct msm_fb_data_type *mfd; pr_info("%s.\n", __func__); mfd = platform_get_drvdata(pdev); if (!mfd) return -ENODEV; if (mfd->key != MFD_KEY) return -EINVAL; ret = mipi_d2l_set_backlight_level(bl_pwm, 1); pr_info("%s.ret=%d.\n", __func__, ret); return ret; } static void mipi_d2l_set_backlight(struct msm_fb_data_type *mfd) { int level = mfd->bl_level; pr_debug("%s.lvl=%d.\n", __func__, level); mipi_d2l_set_backlight_level(bl_pwm, level); bl_level = level; } static struct msm_fb_panel_data d2l_panel_data = { .on = mipi_d2l_lcd_on, .off = mipi_d2l_lcd_off, .set_backlight = mipi_d2l_set_backlight, }; static u32 d2l_i2c_read_reg(struct i2c_client *client, u16 reg) { int rc; u32 val = 0; u8 buf[6]; if (client == NULL) { pr_err("%s.invalid i2c client.\n", __func__); return -EINVAL; } buf[0] = reg >> 8; buf[1] = reg & 0xFF; rc = i2c_master_send(client, buf, sizeof(reg)); rc = i2c_master_recv(client, buf, 4); if (rc >= 0) { val = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); pr_debug("%s.reg=0x%x.val=0x%x.\n", __func__, reg, val); } else pr_err("%s.fail.reg=0x%x.\n", __func__, reg); return val; } static u32 d2l_i2c_write_reg(struct i2c_client *client, u16 reg, u32 val) { int rc; u8 buf[6]; if (client == NULL) { pr_err("%s.invalid i2c client.\n", __func__); return -EINVAL; } buf[0] = reg >> 8; buf[1] = reg & 0xFF; buf[2] = (val >> 0) & 0xFF; buf[3] = (val >> 8) & 0xFF; buf[4] = (val >> 16) & 0xFF; buf[5] = (val >> 24) & 0xFF; rc = i2c_master_send(client, buf, sizeof(buf)); if (rc >= 0) pr_debug("%s.reg=0x%x.val=0x%x.\n", __func__, reg, val); else pr_err("%s.fail.reg=0x%x.\n", __func__, reg); return val; } static int __devinit d2l_i2c_slave_probe(struct i2c_client *client, const struct i2c_device_id *id) { static const u32 i2c_funcs = I2C_FUNC_I2C; d2l_i2c_client = client; if (!i2c_check_functionality(client->adapter, i2c_funcs)) { pr_err("%s.i2c_check_functionality failed.\n", __func__); return -ENOSYS; } else { pr_debug("%s.i2c_check_functionality OK.\n", __func__); } d2l_i2c_read_reg(client, IDREG); mipi_d2l_read_status_via_i2c(d2l_i2c_client); return 0; } static __devexit int d2l_i2c_slave_remove(struct i2c_client *client) { d2l_i2c_client = NULL; return 0; } static const struct i2c_device_id d2l_i2c_id[] = { {"tc358764-i2c", 0}, {} }; static struct i2c_driver d2l_i2c_slave_driver = { .driver = { .name = "tc358764-i2c", .owner = THIS_MODULE }, .probe = d2l_i2c_slave_probe, .remove = __devexit_p(d2l_i2c_slave_remove), .id_table = d2l_i2c_id, }; static int mipi_d2l_enable_3d(struct msm_fb_data_type *mfd, bool enable, bool mode) { u32 tn_usec = 1000000 / 66; /* 66 HZ */ pr_debug("%s.enable=%d.mode=%d.\n", __func__, enable, mode); gpio_direction_output(d2l_3d_gpio_enable, enable); gpio_direction_output(d2l_3d_gpio_mode, mode); mipi_d2l_set_tn_clk(tn_pwm, tn_usec); return 0; } static ssize_t mipi_d2l_enable_3d_read(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf((char *)buf, sizeof(buf), "%u\n", d2l_enable_3d); } static ssize_t mipi_d2l_enable_3d_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret = -1; u32 data = 0; if (sscanf((char *)buf, "%u", &data) != 1) { dev_err(dev, "%s. Invalid input.\n", __func__); ret = -EINVAL; } else { d2l_enable_3d = data; if (data == 1) /* LANDSCAPE */ mipi_d2l_enable_3d(d2l_mfd, true, true); else if (data == 2) /* PORTRAIT */ mipi_d2l_enable_3d(d2l_mfd, true, false); else if (data == 0) mipi_d2l_enable_3d(d2l_mfd, false, false); else if (data == 9) mipi_d2l_read_status_via_i2c(d2l_i2c_client); else pr_err("%s.Invalid value=%d.\n", __func__, data); } return count; } static struct device_attribute mipi_d2l_3d_barrier_attributes[] = { __ATTR(enable_3d_barrier, 0666, mipi_d2l_enable_3d_read, mipi_d2l_enable_3d_write), }; static int mipi_dsi_3d_barrier_sysfs_register(struct device *dev) { int ret; pr_debug("%s.d2l_3d_gpio_enable=%d.\n", __func__, d2l_3d_gpio_enable); pr_debug("%s.d2l_3d_gpio_mode=%d.\n", __func__, d2l_3d_gpio_mode); ret = device_create_file(dev, mipi_d2l_3d_barrier_attributes); if (ret) { pr_err("%s.failed to create 3D sysfs.\n", __func__); goto err_device_create_file; } ret = gpio_request(d2l_3d_gpio_enable, "d2l_3d_gpio_enable"); if (ret) { pr_err("%s.failed to get d2l_3d_gpio_enable=%d.\n", __func__, d2l_3d_gpio_enable); goto err_d2l_3d_gpio_enable; } ret = gpio_request(d2l_3d_gpio_mode, "d2l_3d_gpio_mode"); if (ret) { pr_err("%s.failed to get d2l_3d_gpio_mode=%d.\n", __func__, d2l_3d_gpio_mode); goto err_d2l_3d_gpio_mode; } return 0; err_d2l_3d_gpio_mode: gpio_free(d2l_3d_gpio_enable); err_d2l_3d_gpio_enable: device_remove_file(dev, mipi_d2l_3d_barrier_attributes); err_device_create_file: return ret; } /** * Probe for device. * * Both the "target" and "panel" device use the same probe function. * "Target" device has id=0, "Panel" devic has non-zero id. * Target device should register first, passing msm_panel_common_pdata. * Panel device passing msm_panel_info. * * @param pdev * * @return int */ static int __devinit mipi_d2l_probe(struct platform_device *pdev) { int ret = 0; struct msm_panel_info *pinfo = NULL; pr_debug("%s.id=%d.\n", __func__, pdev->id); if (pdev->id == 0) { d2l_common_pdata = pdev->dev.platform_data; if (d2l_common_pdata == NULL) { pr_err("%s: no PWM gpio specified.\n", __func__); return 0; } led_pwm = d2l_common_pdata->gpio_num[0]; d2l_gpio_out_mask = d2l_common_pdata->gpio_num[1] >> 8; d2l_gpio_out_val = d2l_common_pdata->gpio_num[1] & 0xFF; d2l_3d_gpio_enable = d2l_common_pdata->gpio_num[2]; d2l_3d_gpio_mode = d2l_common_pdata->gpio_num[3]; mipi_dsi_buf_alloc(&d2l_tx_buf, DSI_BUF_SIZE); mipi_dsi_buf_alloc(&d2l_rx_buf, DSI_BUF_SIZE); return 0; } if (d2l_common_pdata == NULL) { pr_err("%s: d2l_common_pdata is NULL.\n", __func__); return -ENODEV; } bl_pwm = NULL; if (led_pwm >= 0) { bl_pwm = pwm_request(led_pwm, "lcd-backlight"); if (bl_pwm == NULL || IS_ERR(bl_pwm)) { pr_err("%s pwm_request() failed.id=%d.bl_pwm=%d.\n", __func__, led_pwm, (int) bl_pwm); bl_pwm = NULL; return -EIO; } else { pr_debug("%s.pwm_request() ok.pwm-id=%d.\n", __func__, led_pwm); } } else { pr_err("%s. led_pwm is invalid.\n", __func__); } tn_pwm = pwm_request(1, "3D_TN_clk"); if (tn_pwm == NULL || IS_ERR(tn_pwm)) { pr_err("%s pwm_request() failed.id=%d.tn_pwm=%d.\n", __func__, 1, (int) tn_pwm); tn_pwm = NULL; return -EIO; } else { pr_debug("%s.pwm_request() ok.pwm-id=%d.\n", __func__, 1); } pinfo = pdev->dev.platform_data; if (pinfo == NULL) { pr_err("%s: pinfo is NULL.\n", __func__); return -ENODEV; } d2l_panel_data.panel_info = *pinfo; pdev->dev.platform_data = &d2l_panel_data; msm_fb_add_device(pdev); if (pinfo->is_3d_panel) mipi_dsi_3d_barrier_sysfs_register(&(pdev->dev)); return ret; } /** * Device removal notification handler. * * @param pdev * * @return int */ static int __devexit mipi_d2l_remove(struct platform_device *pdev) { /* Note: There are no APIs to remove fb device and free DSI buf. */ pr_debug("%s.\n", __func__); if (bl_pwm) { pwm_free(bl_pwm); bl_pwm = NULL; } return 0; } /** * Register the panel device. * * @param pinfo * @param channel_id * @param panel_id * * @return int */ int mipi_tc358764_dsi2lvds_register(struct msm_panel_info *pinfo, u32 channel_id, u32 panel_id) { struct platform_device *pdev = NULL; int ret; /* Use DSI-to-LVDS bridge */ const char driver_name[] = "mipi_tc358764"; pr_debug("%s.\n", __func__); ret = mipi_d2l_init(); if (ret) { pr_err("mipi_d2l_init() failed with ret %u\n", ret); return ret; } /* Note: the device id should be non-zero */ pdev = platform_device_alloc(driver_name, (panel_id << 8)|channel_id); if (pdev == NULL) return -ENOMEM; pdev->dev.platform_data = pinfo; ret = platform_device_add(pdev); if (ret) { pr_err("%s: platform_device_register failed!\n", __func__); goto err_device_put; } return 0; err_device_put: platform_device_put(pdev); return ret; } static struct platform_driver d2l_driver = { .probe = mipi_d2l_probe, .remove = __devexit_p(mipi_d2l_remove), .driver = { .name = DRV_NAME, }, }; /** * Module Init * * @return int */ static int mipi_d2l_init(void) { pr_debug("%s.\n", __func__); d2l_i2c_client = NULL; return platform_driver_register(&d2l_driver); } MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Toshiba MIPI-DSI-to-LVDS bridge driver"); MODULE_AUTHOR("Amir Samuelov ");