/* Copyright (c) 2010-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. * */ #ifdef CONFIG_SPI_QUP #include #endif #include #include "msm_fb.h" #include "mipi_dsi.h" #include "mipi_novatek.h" #include "mdp4.h" static struct mipi_dsi_panel_platform_data *mipi_novatek_pdata; static struct dsi_buf novatek_tx_buf; static struct dsi_buf novatek_rx_buf; static int mipi_novatek_lcd_init(void); static int wled_trigger_initialized; #define MIPI_DSI_NOVATEK_SPI_DEVICE_NAME "dsi_novatek_3d_panel_spi" #define HPCI_FPGA_READ_CMD 0x84 #define HPCI_FPGA_WRITE_CMD 0x04 #ifdef CONFIG_SPI_QUP static struct spi_device *panel_3d_spi_client; static void novatek_fpga_write(uint8 addr, uint16 value) { char tx_buf[32]; int rc; struct spi_message m; struct spi_transfer t; u8 data[4] = {0x0, 0x0, 0x0, 0x0}; if (!panel_3d_spi_client) { pr_err("%s panel_3d_spi_client is NULL\n", __func__); return; } data[0] = HPCI_FPGA_WRITE_CMD; data[1] = addr; data[2] = ((value >> 8) & 0xFF); data[3] = (value & 0xFF); memset(&t, 0, sizeof t); memset(tx_buf, 0, sizeof tx_buf); t.tx_buf = data; t.len = 4; spi_setup(panel_3d_spi_client); spi_message_init(&m); spi_message_add_tail(&t, &m); rc = spi_sync(panel_3d_spi_client, &m); if (rc) pr_err("%s: SPI transfer failed\n", __func__); return; } static void novatek_fpga_read(uint8 addr) { char tx_buf[32]; int rc; struct spi_message m; struct spi_transfer t; struct spi_transfer rx; char rx_value[2]; u8 data[4] = {0x0, 0x0}; if (!panel_3d_spi_client) { pr_err("%s panel_3d_spi_client is NULL\n", __func__); return; } data[0] = HPCI_FPGA_READ_CMD; data[1] = addr; memset(&t, 0, sizeof t); memset(tx_buf, 0, sizeof tx_buf); memset(&rx, 0, sizeof rx); memset(rx_value, 0, sizeof rx_value); t.tx_buf = data; t.len = 2; rx.rx_buf = rx_value; rx.len = 2; spi_setup(panel_3d_spi_client); spi_message_init(&m); spi_message_add_tail(&t, &m); spi_message_add_tail(&rx, &m); rc = spi_sync(panel_3d_spi_client, &m); if (rc) pr_err("%s: SPI transfer failed\n", __func__); else pr_info("%s: rx_value = 0x%x, 0x%x\n", __func__, rx_value[0], rx_value[1]); return; } static int __devinit panel_3d_spi_probe(struct spi_device *spi) { panel_3d_spi_client = spi; return 0; } static int __devexit panel_3d_spi_remove(struct spi_device *spi) { panel_3d_spi_client = NULL; return 0; } static struct spi_driver panel_3d_spi_driver = { .probe = panel_3d_spi_probe, .remove = __devexit_p(panel_3d_spi_remove), .driver = { .name = "dsi_novatek_3d_panel_spi", .owner = THIS_MODULE, } }; #else static void novatek_fpga_write(uint8 addr, uint16 value) { return; } static void novatek_fpga_read(uint8 addr) { return; } #endif /* novatek blue panel */ #ifdef NOVETAK_COMMANDS_UNUSED static char display_config_cmd_mode1[] = { /* TYPE_DCS_LWRITE */ 0x2A, 0x00, 0x00, 0x01, 0x3F, 0xFF, 0xFF, 0xFF }; static char display_config_cmd_mode2[] = { /* DTYPE_DCS_LWRITE */ 0x2B, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFF, 0xFF }; static char display_config_cmd_mode3_666[] = { /* DTYPE_DCS_WRITE1 */ 0x3A, 0x66, 0x15, 0x80 /* 666 Packed (18-bits) */ }; static char display_config_cmd_mode3_565[] = { /* DTYPE_DCS_WRITE1 */ 0x3A, 0x55, 0x15, 0x80 /* 565 mode */ }; static char display_config_321[] = { /* DTYPE_DCS_WRITE1 */ 0x66, 0x2e, 0x15, 0x00 /* Reg 0x66 : 2E */ }; static char display_config_323[] = { /* DTYPE_DCS_WRITE */ 0x13, 0x00, 0x05, 0x00 /* Reg 0x13 < Set for Normal Mode> */ }; static char display_config_2lan[] = { /* DTYPE_DCS_WRITE */ 0x61, 0x01, 0x02, 0xff /* Reg 0x61 : 01,02 < Set for 2 Data Lane > */ }; static char display_config_exit_sleep[] = { /* DTYPE_DCS_WRITE */ 0x11, 0x00, 0x05, 0x80 /* Reg 0x11 < exit sleep mode> */ }; static char display_config_TE_ON[] = { /* DTYPE_DCS_WRITE1 */ 0x35, 0x00, 0x15, 0x80 }; static char display_config_39H[] = { /* DTYPE_DCS_WRITE */ 0x39, 0x00, 0x05, 0x80 }; static char display_config_set_tear_scanline[] = { /* DTYPE_DCS_LWRITE */ 0x44, 0x00, 0x00, 0xff }; static char display_config_set_twolane[] = { /* DTYPE_DCS_WRITE1 */ 0xae, 0x03, 0x15, 0x80 }; static char display_config_set_threelane[] = { /* DTYPE_DCS_WRITE1 */ 0xae, 0x05, 0x15, 0x80 }; #else static char sw_reset[2] = {0x01, 0x00}; /* DTYPE_DCS_WRITE */ static char enter_sleep[2] = {0x10, 0x00}; /* DTYPE_DCS_WRITE */ static char exit_sleep[2] = {0x11, 0x00}; /* DTYPE_DCS_WRITE */ static char display_off[2] = {0x28, 0x00}; /* DTYPE_DCS_WRITE */ static char display_on[2] = {0x29, 0x00}; /* DTYPE_DCS_WRITE */ static char rgb_888[2] = {0x3A, 0x77}; /* DTYPE_DCS_WRITE1 */ #if defined(NOVATEK_TWO_LANE) static char set_num_of_lanes[2] = {0xae, 0x03}; /* DTYPE_DCS_WRITE1 */ #else /* 1 lane */ static char set_num_of_lanes[2] = {0xae, 0x01}; /* DTYPE_DCS_WRITE1 */ #endif /* commands by Novatke */ static char novatek_f4[2] = {0xf4, 0x55}; /* DTYPE_DCS_WRITE1 */ static char novatek_8c[16] = { /* DTYPE_DCS_LWRITE */ 0x8C, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x00, 0x30, 0xC0, 0xB7, 0x37}; static char novatek_ff[2] = {0xff, 0x55 }; /* DTYPE_DCS_WRITE1 */ static char set_width[5] = { /* DTYPE_DCS_LWRITE */ 0x2A, 0x00, 0x00, 0x02, 0x1B}; /* 540 - 1 */ static char set_height[5] = { /* DTYPE_DCS_LWRITE */ 0x2B, 0x00, 0x00, 0x03, 0xBF}; /* 960 - 1 */ #endif static char led_pwm2[2] = {0x53, 0x24}; /* DTYPE_DCS_WRITE1 */ static char led_pwm3[2] = {0x55, 0x00}; /* DTYPE_DCS_WRITE1 */ static struct dsi_cmd_desc novatek_video_on_cmds[] = { {DTYPE_DCS_WRITE, 1, 0, 0, 50, sizeof(sw_reset), sw_reset}, {DTYPE_DCS_WRITE, 1, 0, 0, 10, sizeof(exit_sleep), exit_sleep}, {DTYPE_DCS_WRITE, 1, 0, 0, 10, sizeof(display_on), display_on}, {DTYPE_DCS_WRITE1, 1, 0, 0, 10, sizeof(set_num_of_lanes), set_num_of_lanes}, {DTYPE_DCS_WRITE1, 1, 0, 0, 10, sizeof(rgb_888), rgb_888}, {DTYPE_DCS_WRITE1, 1, 0, 0, 10, sizeof(led_pwm2), led_pwm2}, {DTYPE_DCS_WRITE1, 1, 0, 0, 10, sizeof(led_pwm3), led_pwm3}, }; static struct dsi_cmd_desc novatek_cmd_on_cmds[] = { {DTYPE_DCS_WRITE, 1, 0, 0, 50, sizeof(sw_reset), sw_reset}, {DTYPE_DCS_WRITE, 1, 0, 0, 10, sizeof(exit_sleep), exit_sleep}, {DTYPE_DCS_WRITE, 1, 0, 0, 10, sizeof(display_on), display_on}, {DTYPE_DCS_WRITE1, 1, 0, 0, 50, sizeof(novatek_f4), novatek_f4}, {DTYPE_DCS_LWRITE, 1, 0, 0, 50, sizeof(novatek_8c), novatek_8c}, {DTYPE_DCS_WRITE1, 1, 0, 0, 50, sizeof(novatek_ff), novatek_ff}, {DTYPE_DCS_WRITE1, 1, 0, 0, 10, sizeof(set_num_of_lanes), set_num_of_lanes}, {DTYPE_DCS_LWRITE, 1, 0, 0, 50, sizeof(set_width), set_width}, {DTYPE_DCS_LWRITE, 1, 0, 0, 50, sizeof(set_height), set_height}, {DTYPE_DCS_WRITE1, 1, 0, 0, 10, sizeof(rgb_888), rgb_888}, {DTYPE_DCS_WRITE1, 1, 0, 0, 1, sizeof(led_pwm2), led_pwm2}, {DTYPE_DCS_WRITE1, 1, 0, 0, 1, sizeof(led_pwm3), led_pwm3}, }; static struct dsi_cmd_desc novatek_display_off_cmds[] = { {DTYPE_DCS_WRITE, 1, 0, 0, 10, sizeof(display_off), display_off}, {DTYPE_DCS_WRITE, 1, 0, 0, 120, sizeof(enter_sleep), enter_sleep} }; static char manufacture_id[2] = {0x04, 0x00}; /* DTYPE_DCS_READ */ static struct dsi_cmd_desc novatek_manufacture_id_cmd = { DTYPE_DCS_READ, 1, 0, 1, 5, sizeof(manufacture_id), manufacture_id}; static u32 manu_id; static void mipi_novatek_manufacture_cb(u32 data) { manu_id = data; pr_info("%s: manufacture_id=%x\n", __func__, manu_id); } static uint32 mipi_novatek_manufacture_id(struct msm_fb_data_type *mfd) { struct dcs_cmd_req cmdreq; memset(&cmdreq, 0, sizeof(cmdreq)); cmdreq.cmds = &novatek_manufacture_id_cmd; cmdreq.cmds_cnt = 1; cmdreq.flags = CMD_REQ_RX | CMD_REQ_COMMIT; cmdreq.rlen = 3; cmdreq.rbuf = NULL; cmdreq.cb = mipi_novatek_manufacture_cb; /* call back */ mipi_dsi_cmdlist_put(&cmdreq); /* * blocked here, untill call back called */ return manu_id; } static int fpga_addr; static int fpga_access_mode; static bool support_3d; static void mipi_novatek_3d_init(int addr, int mode) { fpga_addr = addr; fpga_access_mode = mode; } static void mipi_dsi_enable_3d_barrier(int mode) { void __iomem *fpga_ptr; uint32_t ptr_value = 0; if (!fpga_addr && support_3d) { pr_err("%s: fpga_addr not set. Failed to enable 3D barrier\n", __func__); return; } if (fpga_access_mode == FPGA_SPI_INTF) { if (mode == LANDSCAPE) novatek_fpga_write(fpga_addr, 1); else if (mode == PORTRAIT) novatek_fpga_write(fpga_addr, 3); else novatek_fpga_write(fpga_addr, 0); mb(); novatek_fpga_read(fpga_addr); } else if (fpga_access_mode == FPGA_EBI2_INTF) { fpga_ptr = ioremap_nocache(fpga_addr, sizeof(uint32_t)); if (!fpga_ptr) { pr_err("%s: FPGA ioremap failed." "Failed to enable 3D barrier\n", __func__); return; } ptr_value = readl_relaxed(fpga_ptr); if (mode == LANDSCAPE) writel_relaxed(((0xFFFF0000 & ptr_value) | 1), fpga_ptr); else if (mode == PORTRAIT) writel_relaxed(((0xFFFF0000 & ptr_value) | 3), fpga_ptr); else writel_relaxed((0xFFFF0000 & ptr_value), fpga_ptr); mb(); iounmap(fpga_ptr); } else pr_err("%s: 3D barrier not configured correctly\n", __func__); } static int mipi_novatek_lcd_on(struct platform_device *pdev) { struct msm_fb_data_type *mfd; struct mipi_panel_info *mipi; struct msm_panel_info *pinfo; struct dcs_cmd_req cmdreq; mfd = platform_get_drvdata(pdev); if (!mfd) return -ENODEV; if (mfd->key != MFD_KEY) return -EINVAL; pinfo = &mfd->panel_info; if (pinfo->is_3d_panel) support_3d = TRUE; mipi = &mfd->panel_info.mipi; if (mipi->mode == DSI_VIDEO_MODE) { cmdreq.cmds = novatek_video_on_cmds; cmdreq.cmds_cnt = ARRAY_SIZE(novatek_video_on_cmds); cmdreq.flags = CMD_REQ_COMMIT; cmdreq.rlen = 0; cmdreq.cb = NULL; mipi_dsi_cmdlist_put(&cmdreq); } else { cmdreq.cmds = novatek_cmd_on_cmds; cmdreq.cmds_cnt = ARRAY_SIZE(novatek_cmd_on_cmds); cmdreq.flags = CMD_REQ_COMMIT; cmdreq.rlen = 0; cmdreq.cb = NULL; mipi_dsi_cmdlist_put(&cmdreq); /* clean up ack_err_status */ mipi_dsi_cmd_bta_sw_trigger(); mipi_novatek_manufacture_id(mfd); } return 0; } static int mipi_novatek_lcd_off(struct platform_device *pdev) { struct msm_fb_data_type *mfd; struct dcs_cmd_req cmdreq; mfd = platform_get_drvdata(pdev); if (!mfd) return -ENODEV; if (mfd->key != MFD_KEY) return -EINVAL; cmdreq.cmds = novatek_display_off_cmds; cmdreq.cmds_cnt = ARRAY_SIZE(novatek_display_off_cmds); cmdreq.flags = CMD_REQ_COMMIT; cmdreq.rlen = 0; cmdreq.cb = NULL; mipi_dsi_cmdlist_put(&cmdreq); return 0; } static int mipi_novatek_lcd_late_init(struct platform_device *pdev) { return 0; } DEFINE_LED_TRIGGER(bkl_led_trigger); static char led_pwm1[2] = {0x51, 0x0}; /* DTYPE_DCS_WRITE1 */ static struct dsi_cmd_desc backlight_cmd = { DTYPE_DCS_LWRITE, 1, 0, 0, 1, sizeof(led_pwm1), led_pwm1}; static void mipi_novatek_set_backlight(struct msm_fb_data_type *mfd) { struct dcs_cmd_req cmdreq; if (mipi_novatek_pdata && mipi_novatek_pdata->gpio_set_backlight) { mipi_novatek_pdata->gpio_set_backlight(mfd->bl_level); return; } if ((mipi_novatek_pdata->enable_wled_bl_ctrl) && (wled_trigger_initialized)) { led_trigger_event(bkl_led_trigger, mfd->bl_level); return; } led_pwm1[1] = (unsigned char)mfd->bl_level; cmdreq.cmds = &backlight_cmd; cmdreq.cmds_cnt = 1; cmdreq.flags = CMD_REQ_COMMIT | CMD_CLK_CTRL; cmdreq.rlen = 0; cmdreq.cb = NULL; mipi_dsi_cmdlist_put(&cmdreq); } static int mipi_dsi_3d_barrier_sysfs_register(struct device *dev); static int barrier_mode; static int __devinit mipi_novatek_lcd_probe(struct platform_device *pdev) { struct msm_fb_data_type *mfd; struct mipi_panel_info *mipi; struct platform_device *current_pdev; static struct mipi_dsi_phy_ctrl *phy_settings; static char dlane_swap; if (pdev->id == 0) { mipi_novatek_pdata = pdev->dev.platform_data; if (mipi_novatek_pdata && mipi_novatek_pdata->phy_ctrl_settings) { phy_settings = (mipi_novatek_pdata->phy_ctrl_settings); } if (mipi_novatek_pdata && mipi_novatek_pdata->dlane_swap) { dlane_swap = (mipi_novatek_pdata->dlane_swap); } if (mipi_novatek_pdata && mipi_novatek_pdata->fpga_3d_config_addr) mipi_novatek_3d_init(mipi_novatek_pdata ->fpga_3d_config_addr, mipi_novatek_pdata->fpga_ctrl_mode); /* create sysfs to control 3D barrier for the Sharp panel */ if (mipi_dsi_3d_barrier_sysfs_register(&pdev->dev)) { pr_err("%s: Failed to register 3d Barrier sysfs\n", __func__); return -ENODEV; } barrier_mode = 0; return 0; } current_pdev = msm_fb_add_device(pdev); if (current_pdev) { mfd = platform_get_drvdata(current_pdev); if (!mfd) return -ENODEV; if (mfd->key != MFD_KEY) return -EINVAL; mipi = &mfd->panel_info.mipi; if (phy_settings != NULL) mipi->dsi_phy_db = phy_settings; if (dlane_swap) mipi->dlane_swap = dlane_swap; } return 0; } static struct platform_driver this_driver = { .probe = mipi_novatek_lcd_probe, .driver = { .name = "mipi_novatek", }, }; static struct msm_fb_panel_data novatek_panel_data = { .on = mipi_novatek_lcd_on, .off = mipi_novatek_lcd_off, .late_init = mipi_novatek_lcd_late_init, .set_backlight = mipi_novatek_set_backlight, }; static ssize_t mipi_dsi_3d_barrier_read(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf((char *)buf, sizeof(buf), "%u\n", barrier_mode); } static ssize_t mipi_dsi_3d_barrier_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\n", __func__); ret = -EINVAL; } else { barrier_mode = data; if (data == 1) mipi_dsi_enable_3d_barrier(LANDSCAPE); else if (data == 2) mipi_dsi_enable_3d_barrier(PORTRAIT); else mipi_dsi_enable_3d_barrier(0); } return count; } static struct device_attribute mipi_dsi_3d_barrier_attributes[] = { __ATTR(enable_3d_barrier, 0664, mipi_dsi_3d_barrier_read, mipi_dsi_3d_barrier_write), }; static int mipi_dsi_3d_barrier_sysfs_register(struct device *dev) { int i; for (i = 0; i < ARRAY_SIZE(mipi_dsi_3d_barrier_attributes); i++) if (device_create_file(dev, mipi_dsi_3d_barrier_attributes + i)) goto error; return 0; error: for (; i >= 0 ; i--) device_remove_file(dev, mipi_dsi_3d_barrier_attributes + i); pr_err("%s: Unable to create interface\n", __func__); return -ENODEV; } static int ch_used[3]; int mipi_novatek_device_register(struct msm_panel_info *pinfo, u32 channel, u32 panel) { struct platform_device *pdev = NULL; int ret; if ((channel >= 3) || ch_used[channel]) return -ENODEV; ch_used[channel] = TRUE; ret = mipi_novatek_lcd_init(); if (ret) { pr_err("mipi_novatek_lcd_init() failed with ret %u\n", ret); return ret; } pdev = platform_device_alloc("mipi_novatek", (panel << 8)|channel); if (!pdev) return -ENOMEM; novatek_panel_data.panel_info = *pinfo; ret = platform_device_add_data(pdev, &novatek_panel_data, sizeof(novatek_panel_data)); if (ret) { printk(KERN_ERR "%s: platform_device_add_data failed!\n", __func__); goto err_device_put; } ret = platform_device_add(pdev); if (ret) { printk(KERN_ERR "%s: platform_device_register failed!\n", __func__); goto err_device_put; } return 0; err_device_put: platform_device_put(pdev); return ret; } static int mipi_novatek_lcd_init(void) { #ifdef CONFIG_SPI_QUP int ret; ret = spi_register_driver(&panel_3d_spi_driver); if (ret) { pr_err("%s: spi register failed: rc=%d\n", __func__, ret); platform_driver_unregister(&this_driver); } else pr_info("%s: SUCCESS (SPI)\n", __func__); #endif led_trigger_register_simple("bkl_trigger", &bkl_led_trigger); pr_info("%s: SUCCESS (WLED TRIGGER)\n", __func__); wled_trigger_initialized = 1; mipi_dsi_buf_alloc(&novatek_tx_buf, DSI_BUF_SIZE); mipi_dsi_buf_alloc(&novatek_rx_buf, DSI_BUF_SIZE); return platform_driver_register(&this_driver); }