| /* Copyright (c) 2013, 2018, 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. |
| * |
| */ |
| #include <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/io.h> |
| #include <linux/slab.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/clk/msm-clk.h> |
| |
| |
| #include "dsi_v2.h" |
| #include "dsi_io_v2.h" |
| #include "dsi_host_v2.h" |
| |
| struct msm_dsi_io_private { |
| struct clk *dsi_byte_clk; |
| struct clk *dsi_esc_clk; |
| struct clk *dsi_pixel_clk; |
| struct clk *dsi_ahb_clk; |
| struct clk *dsi_clk; |
| int msm_dsi_clk_on; |
| int msm_dsi_ahb_clk_on; |
| }; |
| |
| static struct msm_dsi_io_private *dsi_io_private; |
| |
| #define DSI_VDDA_VOLTAGE 1200000 |
| |
| void msm_dsi_ahb_ctrl(int enable) |
| { |
| if (enable) { |
| dsi_io_private->msm_dsi_ahb_clk_on++; |
| if (dsi_io_private->msm_dsi_ahb_clk_on == 1) |
| clk_enable(dsi_io_private->dsi_ahb_clk); |
| } else { |
| dsi_io_private->msm_dsi_ahb_clk_on--; |
| if (dsi_io_private->msm_dsi_ahb_clk_on == 0) |
| clk_disable(dsi_io_private->dsi_ahb_clk); |
| } |
| } |
| |
| int msm_dsi_io_init(struct platform_device *pdev, struct mdss_module_power *mp) |
| { |
| int rc; |
| |
| if (!dsi_io_private) { |
| dsi_io_private = kzalloc(sizeof(struct msm_dsi_io_private), |
| GFP_KERNEL); |
| if (!dsi_io_private) |
| return -ENOMEM; |
| } |
| |
| rc = msm_dsi_clk_init(pdev); |
| if (rc) { |
| pr_err("fail to initialize DSI clock\n"); |
| return rc; |
| } |
| |
| rc = msm_mdss_config_vreg(&pdev->dev, mp->vreg_config, |
| mp->num_vreg, 1); |
| if (rc) { |
| pr_err("fail to initialize DSI regulator\n"); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| void msm_dsi_io_deinit(struct platform_device *pdev, |
| struct mdss_module_power *mp) |
| { |
| if (dsi_io_private) { |
| msm_dsi_clk_deinit(); |
| msm_mdss_config_vreg(&pdev->dev, mp->vreg_config, |
| mp->num_vreg, 0); |
| kfree(dsi_io_private); |
| dsi_io_private = NULL; |
| } |
| } |
| |
| int msm_dsi_clk_init(struct platform_device *dev) |
| { |
| int rc = 0; |
| |
| dsi_io_private->dsi_clk = clk_get(&dev->dev, "dsi_clk"); |
| if (IS_ERR(dsi_io_private->dsi_clk)) { |
| pr_err("can't find dsi core_clk\n"); |
| rc = PTR_ERR(dsi_io_private->dsi_clk); |
| dsi_io_private->dsi_clk = NULL; |
| return rc; |
| } |
| dsi_io_private->dsi_byte_clk = clk_get(&dev->dev, "byte_clk"); |
| if (IS_ERR(dsi_io_private->dsi_byte_clk)) { |
| pr_err("can't find dsi byte_clk\n"); |
| rc = PTR_ERR(dsi_io_private->dsi_byte_clk); |
| dsi_io_private->dsi_byte_clk = NULL; |
| return rc; |
| } |
| |
| dsi_io_private->dsi_esc_clk = clk_get(&dev->dev, "esc_clk"); |
| if (IS_ERR(dsi_io_private->dsi_esc_clk)) { |
| pr_err("can't find dsi esc_clk\n"); |
| rc = PTR_ERR(dsi_io_private->dsi_esc_clk); |
| dsi_io_private->dsi_esc_clk = NULL; |
| return rc; |
| } |
| |
| dsi_io_private->dsi_pixel_clk = clk_get(&dev->dev, "pixel_clk"); |
| if (IS_ERR(dsi_io_private->dsi_pixel_clk)) { |
| pr_err("can't find dsi pixel\n"); |
| rc = PTR_ERR(dsi_io_private->dsi_pixel_clk); |
| dsi_io_private->dsi_pixel_clk = NULL; |
| return rc; |
| } |
| |
| dsi_io_private->dsi_ahb_clk = clk_get(&dev->dev, "iface_clk"); |
| if (IS_ERR(dsi_io_private->dsi_ahb_clk)) { |
| pr_err("can't find dsi iface_clk\n"); |
| rc = PTR_ERR(dsi_io_private->dsi_ahb_clk); |
| dsi_io_private->dsi_ahb_clk = NULL; |
| return rc; |
| } |
| clk_prepare(dsi_io_private->dsi_ahb_clk); |
| |
| return 0; |
| } |
| |
| void msm_dsi_clk_deinit(void) |
| { |
| if (dsi_io_private->dsi_clk) { |
| clk_put(dsi_io_private->dsi_clk); |
| dsi_io_private->dsi_clk = NULL; |
| } |
| if (dsi_io_private->dsi_byte_clk) { |
| clk_put(dsi_io_private->dsi_byte_clk); |
| dsi_io_private->dsi_byte_clk = NULL; |
| } |
| if (dsi_io_private->dsi_esc_clk) { |
| clk_put(dsi_io_private->dsi_esc_clk); |
| dsi_io_private->dsi_esc_clk = NULL; |
| } |
| if (dsi_io_private->dsi_pixel_clk) { |
| clk_put(dsi_io_private->dsi_pixel_clk); |
| dsi_io_private->dsi_pixel_clk = NULL; |
| } |
| if (dsi_io_private->dsi_ahb_clk) { |
| clk_unprepare(dsi_io_private->dsi_ahb_clk); |
| clk_put(dsi_io_private->dsi_ahb_clk); |
| dsi_io_private->dsi_ahb_clk = NULL; |
| } |
| } |
| |
| int msm_dsi_prepare_clocks(void) |
| { |
| clk_prepare(dsi_io_private->dsi_clk); |
| clk_prepare(dsi_io_private->dsi_byte_clk); |
| clk_prepare(dsi_io_private->dsi_esc_clk); |
| clk_prepare(dsi_io_private->dsi_pixel_clk); |
| return 0; |
| } |
| |
| int msm_dsi_unprepare_clocks(void) |
| { |
| clk_unprepare(dsi_io_private->dsi_clk); |
| clk_unprepare(dsi_io_private->dsi_esc_clk); |
| clk_unprepare(dsi_io_private->dsi_byte_clk); |
| clk_unprepare(dsi_io_private->dsi_pixel_clk); |
| return 0; |
| } |
| |
| int msm_dsi_clk_set_rate(unsigned long esc_rate, |
| unsigned long dsi_rate, |
| unsigned long byte_rate, |
| unsigned long pixel_rate) |
| { |
| int rc; |
| |
| rc = clk_set_rate(dsi_io_private->dsi_clk, dsi_rate); |
| if (rc) { |
| pr_err("dsi_esc_clk - clk_set_rate failed =%d\n", rc); |
| return rc; |
| } |
| |
| rc = clk_set_rate(dsi_io_private->dsi_esc_clk, esc_rate); |
| if (rc) { |
| pr_err("dsi_esc_clk - clk_set_rate failed =%d\n", rc); |
| return rc; |
| } |
| |
| rc = clk_set_rate(dsi_io_private->dsi_byte_clk, byte_rate); |
| if (rc) { |
| pr_err("dsi_byte_clk - clk_set_rate faile = %dd\n", rc); |
| return rc; |
| } |
| |
| rc = clk_set_rate(dsi_io_private->dsi_pixel_clk, pixel_rate); |
| if (rc) { |
| pr_err("dsi_pixel_clk - clk_set_rate failed = %d\n", rc); |
| return rc; |
| } |
| return 0; |
| } |
| |
| int msm_dsi_clk_enable(void) |
| { |
| if (dsi_io_private->msm_dsi_clk_on) { |
| pr_debug("dsi_clks on already\n"); |
| return 0; |
| } |
| |
| clk_enable(dsi_io_private->dsi_clk); |
| clk_enable(dsi_io_private->dsi_esc_clk); |
| clk_enable(dsi_io_private->dsi_byte_clk); |
| clk_enable(dsi_io_private->dsi_pixel_clk); |
| |
| dsi_io_private->msm_dsi_clk_on = 1; |
| return 0; |
| } |
| |
| int msm_dsi_clk_disable(void) |
| { |
| if (dsi_io_private->msm_dsi_clk_on == 0) { |
| pr_debug("mdss_dsi_clks already OFF\n"); |
| return 0; |
| } |
| |
| clk_disable(dsi_io_private->dsi_clk); |
| clk_disable(dsi_io_private->dsi_byte_clk); |
| clk_disable(dsi_io_private->dsi_esc_clk); |
| clk_disable(dsi_io_private->dsi_pixel_clk); |
| |
| dsi_io_private->msm_dsi_clk_on = 0; |
| return 0; |
| } |
| |
| static void msm_dsi_phy_strength_init(unsigned char *ctrl_base, |
| struct mdss_dsi_phy_ctrl *pd) |
| { |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_STRENGTH_CTRL_0, pd->strength[0]); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_STRENGTH_CTRL_2, pd->strength[1]); |
| } |
| |
| static void msm_dsi_phy_ctrl_init(unsigned char *ctrl_base, |
| struct mdss_panel_data *pdata) |
| { |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CTRL_0, 0x5f); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CTRL_3, 0x10); |
| } |
| |
| static void msm_dsi_phy_regulator_init(unsigned char *ctrl_base, |
| struct mdss_dsi_phy_ctrl *pd) |
| { |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_LDO_CNTRL, 0x25); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_REGULATOR_CTRL_0, pd->regulator[0]); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_REGULATOR_CTRL_1, pd->regulator[1]); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_REGULATOR_CTRL_2, pd->regulator[2]); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_REGULATOR_CTRL_3, pd->regulator[3]); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_REGULATOR_CTRL_4, pd->regulator[4]); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_REGULATOR_CAL_PWR_CFG, |
| pd->regulator[5]); |
| |
| } |
| |
| static int msm_dsi_phy_calibration(unsigned char *ctrl_base) |
| { |
| int i = 0, term_cnt = 5000, ret = 0, cal_busy; |
| |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CAL_SW_CFG2, 0x0); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CAL_HW_CFG1, 0x5a); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CAL_HW_CFG3, 0x10); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CAL_HW_CFG4, 0x01); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CAL_HW_CFG0, 0x01); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CAL_HW_TRIGGER, 0x01); |
| usleep_range(5000, 5100); /*per DSI controller spec*/ |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CAL_HW_TRIGGER, 0x00); |
| |
| cal_busy = MIPI_INP(ctrl_base + DSI_DSIPHY_REGULATOR_CAL_STATUS0); |
| while (cal_busy & 0x10) { |
| i++; |
| if (i > term_cnt) { |
| ret = -EINVAL; |
| pr_err("msm_dsi_phy_calibration error\n"); |
| break; |
| } |
| cal_busy = MIPI_INP(ctrl_base + |
| DSI_DSIPHY_REGULATOR_CAL_STATUS0); |
| } |
| |
| return ret; |
| } |
| |
| static void msm_dsi_phy_lane_init(unsigned char *ctrl_base, |
| struct mdss_dsi_phy_ctrl *pd) |
| { |
| int ln, index; |
| |
| /*CFG0, CFG1, CFG2, TEST_DATAPATH, TEST_STR0, TEST_STR1*/ |
| for (ln = 0; ln < 5; ln++) { |
| unsigned char *off = ctrl_base + 0x0300 + (ln * 0x40); |
| |
| index = ln * 6; |
| |
| MIPI_OUTP(off, pd->lanecfg[index]); |
| MIPI_OUTP(off + 4, pd->lanecfg[index + 1]); |
| MIPI_OUTP(off + 8, pd->lanecfg[index + 2]); |
| MIPI_OUTP(off + 12, pd->lanecfg[index + 3]); |
| MIPI_OUTP(off + 20, pd->lanecfg[index + 4]); |
| MIPI_OUTP(off + 24, pd->lanecfg[index + 5]); |
| } |
| wmb(); /* ensure write is finished before progressing */ |
| } |
| |
| static void msm_dsi_phy_timing_init(unsigned char *ctrl_base, |
| struct mdss_dsi_phy_ctrl *pd) |
| { |
| int i, off = DSI_DSIPHY_TIMING_CTRL_0; |
| |
| for (i = 0; i < 12; i++) { |
| MIPI_OUTP(ctrl_base + off, pd->timing[i]); |
| off += 4; |
| } |
| wmb(); /* ensure write is finished before progressing */ |
| } |
| |
| static void msm_dsi_phy_bist_init(unsigned char *ctrl_base, |
| struct mdss_dsi_phy_ctrl *pd) |
| { |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_BIST_CTRL4, pd->bistctrl[4]); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_BIST_CTRL1, pd->bistctrl[1]); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_BIST_CTRL0, pd->bistctrl[0]); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_BIST_CTRL4, 0); |
| wmb(); /* ensure write is finished before progressing */ |
| } |
| |
| int msm_dsi_phy_init(unsigned char *ctrl_base, |
| struct mdss_panel_data *pdata) |
| { |
| struct mdss_dsi_phy_ctrl *pd; |
| |
| pd = &(pdata->panel_info.mipi.dsi_phy_db); |
| |
| msm_dsi_phy_strength_init(ctrl_base, pd); |
| |
| msm_dsi_phy_ctrl_init(ctrl_base, pdata); |
| |
| msm_dsi_phy_regulator_init(ctrl_base, pd); |
| |
| msm_dsi_phy_calibration(ctrl_base); |
| |
| msm_dsi_phy_lane_init(ctrl_base, pd); |
| |
| msm_dsi_phy_timing_init(ctrl_base, pd); |
| |
| msm_dsi_phy_bist_init(ctrl_base, pd); |
| |
| return 0; |
| } |
| |
| void msm_dsi_phy_sw_reset(unsigned char *ctrl_base) |
| { |
| /* start phy sw reset */ |
| MIPI_OUTP(ctrl_base + DSI_PHY_SW_RESET, 0x0001); |
| udelay(1000); /*per DSI controller spec*/ |
| wmb(); /* ensure write is finished before progressing */ |
| /* end phy sw reset */ |
| MIPI_OUTP(ctrl_base + DSI_PHY_SW_RESET, 0x0000); |
| udelay(100); /*per DSI controller spec*/ |
| wmb(); /* ensure write is finished before progressing */ |
| } |
| |
| void msm_dsi_phy_off(unsigned char *ctrl_base) |
| { |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_PLL_CTRL_5, 0x05f); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_REGULATOR_CTRL_0, 0x02); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CTRL_0, 0x00); |
| MIPI_OUTP(ctrl_base + DSI_DSIPHY_CTRL_1, 0x7f); |
| MIPI_OUTP(ctrl_base + DSI_CLK_CTRL, 0); |
| } |