| /* |
| * drivers/video/tegra/dc/sn65dsi86_dsi2edp.c |
| * |
| * Copyright (c) 2013, NVIDIA Corporation. |
| * |
| * Author: |
| * Bibek Basu <bbasu@nvidia.com> |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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. |
| * |
| */ |
| |
| #include <linux/i2c.h> |
| #include <linux/device.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/regmap.h> |
| #include <linux/swab.h> |
| #include <linux/module.h> |
| #include <mach/dc.h> |
| #include "dc_priv.h" |
| #include "sn65dsi86_dsi2edp.h" |
| #include "dsi.h" |
| |
| static struct tegra_dc_dsi2edp_data *sn65dsi86_dsi2edp; |
| static struct i2c_client *sn65dsi86_i2c_client; |
| |
| enum i2c_transfer_type { |
| I2C_WRITE, |
| I2C_READ, |
| }; |
| |
| static inline int sn65dsi86_reg_write(struct tegra_dc_dsi2edp_data *dsi2edp, |
| unsigned int addr, unsigned int val) |
| { |
| return regmap_write(dsi2edp->regmap, addr, val); |
| } |
| |
| static inline void sn65dsi86_reg_read(struct tegra_dc_dsi2edp_data *dsi2edp, |
| unsigned int addr, unsigned int *val) |
| { |
| regmap_read(dsi2edp->regmap, addr, val); |
| } |
| |
| static const struct regmap_config sn65dsi86_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| }; |
| |
| static int sn65dsi86_dsi2edp_init(struct tegra_dc_dsi_data *dsi) |
| { |
| int err = 0; |
| |
| if (sn65dsi86_dsi2edp) { |
| tegra_dsi_set_outdata(dsi, sn65dsi86_dsi2edp); |
| return err; |
| } |
| |
| sn65dsi86_dsi2edp = devm_kzalloc(&dsi->dc->ndev->dev, |
| sizeof(*sn65dsi86_dsi2edp), |
| GFP_KERNEL); |
| if (!sn65dsi86_dsi2edp) |
| return -ENOMEM; |
| |
| sn65dsi86_dsi2edp->dsi = dsi; |
| |
| sn65dsi86_dsi2edp->client_i2c = sn65dsi86_i2c_client; |
| |
| sn65dsi86_dsi2edp->regmap = devm_regmap_init_i2c(sn65dsi86_i2c_client, |
| &sn65dsi86_regmap_config); |
| if (IS_ERR(sn65dsi86_dsi2edp->regmap)) { |
| err = PTR_ERR(sn65dsi86_dsi2edp->regmap); |
| dev_err(&dsi->dc->ndev->dev, |
| "sn65dsi86_dsi2edp: regmap init failed\n"); |
| goto fail; |
| } |
| |
| sn65dsi86_dsi2edp->mode = &dsi->dc->mode; |
| |
| tegra_dsi_set_outdata(dsi, sn65dsi86_dsi2edp); |
| |
| mutex_init(&sn65dsi86_dsi2edp->lock); |
| fail: |
| return err; |
| } |
| |
| static void sn65dsi86_dsi2edp_destroy(struct tegra_dc_dsi_data *dsi) |
| { |
| struct tegra_dc_dsi2edp_data *dsi2edp = |
| tegra_dsi_get_outdata(dsi); |
| |
| if (!dsi2edp) |
| return; |
| |
| sn65dsi86_dsi2edp = NULL; |
| mutex_destroy(&dsi2edp->lock); |
| } |
| static void sn65dsi86_dsi2edp_enable(struct tegra_dc_dsi_data *dsi) |
| { |
| struct tegra_dc_dsi2edp_data *dsi2edp = tegra_dsi_get_outdata(dsi); |
| unsigned val = 0; |
| |
| if (dsi2edp && dsi2edp->dsi2edp_enabled) |
| return; |
| mutex_lock(&dsi2edp->lock); |
| /* REFCLK 19.2MHz */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_PLL_REFCLK_CFG, 0x02); |
| usleep_range(10000, 12000); |
| /* Single 4 DSI lanes */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_DSI_CFG1, 0x26); |
| usleep_range(10000, 12000); |
| /* DSI CLK FREQ 422.5MHz */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_DSI_CHA_CLK_RANGE, 0x55); |
| usleep_range(10000, 12000); |
| sn65dsi86_reg_read(dsi2edp, SN65DSI86_DSI_CHA_CLK_RANGE, &val); |
| sn65dsi86_reg_read(dsi2edp, SN65DSI86_DSI_CHA_CLK_RANGE, &val); |
| /* enhanced framing */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_FRAMING_CFG, 0x04); |
| usleep_range(10000, 12000); |
| /* Pre0dB 2 lanes no SSC */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_DP_SSC_CFG, 0x20); |
| usleep_range(10000, 12000); |
| /* L0mV HBR */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_DP_CFG, 0x80); |
| usleep_range(10000, 12000); |
| /* PLL ENABLE */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_PLL_EN, 0x01); |
| usleep_range(10000, 12000); |
| /* DP_PLL_LOCK */ |
| sn65dsi86_reg_read(dsi2edp, SN65DSI86_PLL_REFCLK_CFG, &val); |
| usleep_range(10000, 12000); |
| /* POST2 0dB */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_TRAINING_CFG, 0x00); |
| usleep_range(10000, 12000); |
| /* Semi-Auto TRAIN */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_ML_TX_MODE, 0x0a); |
| usleep_range(10000, 12000); |
| /* ADDR 0x96 CFR */ |
| sn65dsi86_reg_read(dsi2edp, SN65DSI86_ML_TX_MODE, &val); |
| msleep(20); |
| /* CHA_ACTIVE_LINE_LENGTH */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_VIDEO_CHA_LINE_LOW, 0x80); |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_VIDEO_CHA_LINE_HIGH, 0x07); |
| usleep_range(10000, 12000); |
| /* CHA_VERTICAL_DISPLAY_SIZE */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_CHA_VERT_DISP_SIZE_LOW, 0x38); |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_CHA_VERT_DISP_SIZE_HIGH, 0x04); |
| usleep_range(10000, 12000); |
| /* CHA_HSYNC_PULSE_WIDTH */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_CHA_HSYNC_PULSE_WIDTH_LOW, 0x10); |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_CHA_HSYNC_PULSE_WIDTH_HIGH, |
| 0x80); |
| usleep_range(10000, 12000); |
| /* CHA_VSYNC_PULSE_WIDTH */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_CHA_VSYNC_PULSE_WIDTH_LOW, 0x0e); |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_CHA_VSYNC_PULSE_WIDTH_HIGH, |
| 0x80); |
| usleep_range(10000, 12000); |
| /* CHA_HORIZONTAL_BACK_PORCH */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_CHA_HORIZONTAL_BACK_PORCH, 0x98); |
| usleep_range(10000, 12000); |
| /* CHA_VERTICAL_BACK_PORCH */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_CHA_VERTICAL_BACK_PORCH, 0x13); |
| usleep_range(10000, 12000); |
| /* CHA_HORIZONTAL_FRONT_PORCH */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_CHA_HORIZONTAL_FRONT_PORCH, |
| 0x10); |
| usleep_range(10000, 12000); |
| /* CHA_VERTICAL_FRONT_PORCH */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_CHA_VERTICAL_FRONT_PORCH, 0x03); |
| usleep_range(10000, 12000); |
| /* DP-18BPP Enable */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_DP_18BPP_EN, 0x00); |
| msleep(100); |
| /* COLOR BAR */ |
| /* sn65dsi86_reg_write(dsi2edp, SN65DSI86_COLOR_BAR_CFG, 0x10);*/ |
| /* enhanced framing and Vstream enable */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_FRAMING_CFG, 0x0c); |
| dsi2edp->dsi2edp_enabled = true; |
| mutex_unlock(&dsi2edp->lock); |
| } |
| |
| static void sn65dsi86_dsi2edp_disable(struct tegra_dc_dsi_data *dsi) |
| { |
| struct tegra_dc_dsi2edp_data *dsi2edp = tegra_dsi_get_outdata(dsi); |
| |
| mutex_lock(&dsi2edp->lock); |
| /* enhanced framing and Vstream disable */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_FRAMING_CFG, 0x04); |
| dsi2edp->dsi2edp_enabled = false; |
| mutex_unlock(&dsi2edp->lock); |
| |
| } |
| |
| #ifdef CONFIG_PM |
| static void sn65dsi86_dsi2edp_suspend(struct tegra_dc_dsi_data *dsi) |
| { |
| struct tegra_dc_dsi2edp_data *dsi2edp = tegra_dsi_get_outdata(dsi); |
| |
| mutex_lock(&dsi2edp->lock); |
| /* configure GPIO1 for suspend */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_GPIO_CTRL_CFG, 0x02); |
| dsi2edp->dsi2edp_enabled = false; |
| mutex_unlock(&dsi2edp->lock); |
| |
| } |
| |
| static void sn65dsi86_dsi2edp_resume(struct tegra_dc_dsi_data *dsi) |
| { |
| struct tegra_dc_dsi2edp_data *dsi2edp = tegra_dsi_get_outdata(dsi); |
| |
| mutex_lock(&dsi2edp->lock); |
| /* disable configure GPIO1 for suspend */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_GPIO_CTRL_CFG, 0x00); |
| /* enhanced framing and Vstream enable */ |
| sn65dsi86_reg_write(dsi2edp, SN65DSI86_FRAMING_CFG, 0x0c); |
| dsi2edp->dsi2edp_enabled = true; |
| mutex_unlock(&dsi2edp->lock); |
| } |
| #endif |
| |
| struct tegra_dsi_out_ops tegra_dsi2edp_ops = { |
| .init = sn65dsi86_dsi2edp_init, |
| .destroy = sn65dsi86_dsi2edp_destroy, |
| .enable = sn65dsi86_dsi2edp_enable, |
| .disable = sn65dsi86_dsi2edp_disable, |
| #ifdef CONFIG_PM |
| .suspend = sn65dsi86_dsi2edp_suspend, |
| .resume = sn65dsi86_dsi2edp_resume, |
| #endif |
| }; |
| |
| static int sn65dsi86_i2c_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| sn65dsi86_i2c_client = client; |
| |
| return 0; |
| } |
| |
| static int sn65dsi86_i2c_remove(struct i2c_client *client) |
| { |
| sn65dsi86_i2c_client = NULL; |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id sn65dsi86_id_table[] = { |
| {"sn65dsi86_dsi2edp", 0}, |
| {}, |
| }; |
| |
| static struct i2c_driver sn65dsi86_i2c_drv = { |
| .driver = { |
| .name = "sn65dsi86_dsi2edp", |
| .owner = THIS_MODULE, |
| }, |
| .probe = sn65dsi86_i2c_probe, |
| .remove = sn65dsi86_i2c_remove, |
| .id_table = sn65dsi86_id_table, |
| }; |
| |
| static int __init sn65dsi86_i2c_client_init(void) |
| { |
| int err = 0; |
| |
| err = i2c_add_driver(&sn65dsi86_i2c_drv); |
| if (err) |
| pr_err("sn65dsi86_dsi2edp: Failed to add i2c client driver\n"); |
| |
| return err; |
| } |
| |
| static void __exit sn65dsi86_i2c_client_exit(void) |
| { |
| i2c_del_driver(&sn65dsi86_i2c_drv); |
| } |
| |
| subsys_initcall(sn65dsi86_i2c_client_init); |
| module_exit(sn65dsi86_i2c_client_exit); |
| |
| MODULE_AUTHOR("Bibek Basu <bbasu@nvidia.com>"); |
| MODULE_DESCRIPTION(" TI SN65DSI86 dsi bridge to edp"); |
| MODULE_LICENSE("GPL"); |