| /* Copyright (c) 2008-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. |
| * |
| */ |
| #include <linux/clk.h> |
| #include "msm_fb.h" |
| #include "mipi_dsi.h" |
| |
| /* multimedia sub system sfpb */ |
| char *mmss_sfpb_base; |
| void __iomem *periph_base; |
| |
| static struct dsi_clk_desc dsicore_clk; |
| static struct dsi_clk_desc dsi_pclk; |
| |
| static struct clk *dsi_byte_div_clk; |
| static struct clk *dsi_esc_clk; |
| static struct clk *dsi_pixel_clk; |
| static struct clk *dsi_clk; |
| static struct clk *dsi_ref_clk; |
| static struct clk *mdp_dsi_pclk; |
| static struct clk *ahb_m_clk; |
| static struct clk *ahb_s_clk; |
| static struct clk *ebi1_dsi_clk; |
| int mipi_dsi_clk_on; |
| |
| int mipi_dsi_clk_init(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| dsi_esc_clk = clk_get(dev, "esc_clk"); |
| if (IS_ERR_OR_NULL(dsi_esc_clk)) { |
| printk(KERN_ERR "can't find dsi_esc_clk\n"); |
| dsi_esc_clk = NULL; |
| goto mipi_dsi_clk_err; |
| } |
| |
| dsi_byte_div_clk = clk_get(dev, "byte_clk"); |
| if (IS_ERR_OR_NULL(dsi_byte_div_clk)) { |
| pr_err("can't find dsi_byte_div_clk\n"); |
| dsi_byte_div_clk = NULL; |
| goto mipi_dsi_clk_err; |
| } |
| |
| dsi_pixel_clk = clk_get(dev, "pixel_clk"); |
| if (IS_ERR_OR_NULL(dsi_pixel_clk)) { |
| pr_err("can't find dsi_pixel_clk\n"); |
| dsi_pixel_clk = NULL; |
| goto mipi_dsi_clk_err; |
| } |
| |
| dsi_clk = clk_get(dev, "core_clk"); |
| if (IS_ERR_OR_NULL(dsi_clk)) { |
| pr_err("can't find dsi_clk\n"); |
| dsi_clk = NULL; |
| goto mipi_dsi_clk_err; |
| } |
| |
| dsi_ref_clk = clk_get(dev, "ref_clk"); |
| if (IS_ERR_OR_NULL(dsi_ref_clk)) { |
| pr_err("can't find dsi_ref_clk\n"); |
| dsi_ref_clk = NULL; |
| goto mipi_dsi_clk_err; |
| } |
| |
| mdp_dsi_pclk = clk_get(dev, "mdp_clk"); |
| if (IS_ERR_OR_NULL(mdp_dsi_pclk)) { |
| pr_err("can't find mdp_dsi_pclk\n"); |
| mdp_dsi_pclk = NULL; |
| goto mipi_dsi_clk_err; |
| } |
| |
| ahb_m_clk = clk_get(dev, "master_iface_clk"); |
| if (IS_ERR_OR_NULL(ahb_m_clk)) { |
| pr_err("can't find ahb_m_clk\n"); |
| ahb_m_clk = NULL; |
| goto mipi_dsi_clk_err; |
| } |
| |
| ahb_s_clk = clk_get(dev, "slave_iface_clk"); |
| if (IS_ERR_OR_NULL(ahb_s_clk)) { |
| pr_err("can't find ahb_s_clk\n"); |
| ahb_s_clk = NULL; |
| goto mipi_dsi_clk_err; |
| } |
| |
| ebi1_dsi_clk = clk_get(dev, "mem_clk"); |
| if (IS_ERR_OR_NULL(ebi1_dsi_clk)) { |
| pr_err("can't find ebi1_dsi_clk\n"); |
| ebi1_dsi_clk = NULL; |
| goto mipi_dsi_clk_err; |
| } |
| |
| return 0; |
| |
| mipi_dsi_clk_err: |
| mipi_dsi_clk_deinit(NULL); |
| return -EPERM; |
| } |
| |
| void mipi_dsi_clk_deinit(struct device *dev) |
| { |
| if (mdp_dsi_pclk) |
| clk_put(mdp_dsi_pclk); |
| if (ahb_m_clk) |
| clk_put(ahb_m_clk); |
| if (ahb_s_clk) |
| clk_put(ahb_s_clk); |
| if (dsi_ref_clk) |
| clk_put(dsi_ref_clk); |
| if (dsi_byte_div_clk) |
| clk_put(dsi_byte_div_clk); |
| if (dsi_esc_clk) |
| clk_put(dsi_esc_clk); |
| if (ebi1_dsi_clk) |
| clk_put(ebi1_dsi_clk); |
| } |
| |
| static void mipi_dsi_clk_ctrl(struct dsi_clk_desc *clk, int clk_en) |
| { |
| uint32 data; |
| if (clk_en) { |
| data = (clk->pre_div_func) << 24 | |
| (clk->m) << 16 | (clk->n) << 8 | |
| ((clk->d) * 2); |
| clk_set_rate(dsi_clk, data); |
| clk_enable(dsi_clk); |
| } else |
| clk_disable(dsi_clk); |
| } |
| |
| static void mipi_dsi_pclk_ctrl(struct dsi_clk_desc *clk, int clk_en) |
| { |
| uint32 data; |
| |
| if (clk_en) { |
| data = (clk->pre_div_func) << 24 | (clk->m) << 16 |
| | (clk->n) << 8 | ((clk->d) * 2); |
| if ((clk_set_rate(dsi_pixel_clk, data)) < 0) |
| pr_err("%s: pixel clk set rate failed\n", __func__); |
| if (clk_enable(dsi_pixel_clk)) |
| pr_err("%s clk enable failed\n", __func__); |
| } else { |
| clk_disable(dsi_pixel_clk); |
| } |
| } |
| |
| static void mipi_dsi_calibration(void) |
| { |
| MIPI_OUTP(MIPI_DSI_BASE + 0xf8, 0x00a105a1); /* cal_hw_ctrl */ |
| } |
| |
| #define PREF_DIV_RATIO 19 |
| struct dsiphy_pll_divider_config pll_divider_config; |
| |
| int mipi_dsi_clk_div_config(uint8 bpp, uint8 lanes, |
| uint32 *expected_dsi_pclk) |
| { |
| u32 fb_divider, rate, vco; |
| u32 div_ratio = 0; |
| struct dsi_clk_mnd_table const *mnd_entry = mnd_table; |
| if (pll_divider_config.clk_rate == 0) |
| pll_divider_config.clk_rate = 454000000; |
| |
| rate = pll_divider_config.clk_rate / 1000000; /* In Mhz */ |
| |
| if (rate < 125) { |
| vco = rate * 8; |
| div_ratio = 8; |
| } else if (rate < 250) { |
| vco = rate * 4; |
| div_ratio = 4; |
| } else if (rate < 500) { |
| vco = rate * 2; |
| div_ratio = 2; |
| } else { |
| vco = rate * 1; |
| div_ratio = 1; |
| } |
| |
| /* find the mnd settings from mnd_table entry */ |
| for (; mnd_entry != mnd_table + ARRAY_SIZE(mnd_table); ++mnd_entry) { |
| if (((mnd_entry->lanes) == lanes) && |
| ((mnd_entry->bpp) == bpp)) |
| break; |
| } |
| |
| if (mnd_entry == mnd_table + ARRAY_SIZE(mnd_table)) { |
| pr_err("%s: requested Lanes, %u & BPP, %u, not supported\n", |
| __func__, lanes, bpp); |
| return -EINVAL; |
| } |
| fb_divider = ((vco * PREF_DIV_RATIO) / 27); |
| pll_divider_config.fb_divider = fb_divider; |
| pll_divider_config.ref_divider_ratio = PREF_DIV_RATIO; |
| pll_divider_config.bit_clk_divider = div_ratio; |
| pll_divider_config.byte_clk_divider = |
| pll_divider_config.bit_clk_divider * 8; |
| pll_divider_config.dsi_clk_divider = |
| (mnd_entry->dsiclk_div) * div_ratio; |
| |
| if ((mnd_entry->dsiclk_d == 0) |
| || (mnd_entry->dsiclk_m == 1)) { |
| dsicore_clk.mnd_mode = 0; |
| dsicore_clk.src = 0x3; |
| dsicore_clk.pre_div_func = (mnd_entry->dsiclk_n - 1); |
| } else { |
| dsicore_clk.mnd_mode = 2; |
| dsicore_clk.src = 0x3; |
| dsicore_clk.m = mnd_entry->dsiclk_m; |
| dsicore_clk.n = mnd_entry->dsiclk_n; |
| dsicore_clk.d = mnd_entry->dsiclk_d; |
| } |
| |
| if ((mnd_entry->pclk_d == 0) |
| || (mnd_entry->pclk_m == 1)) { |
| dsi_pclk.mnd_mode = 0; |
| dsi_pclk.src = 0x3; |
| dsi_pclk.pre_div_func = (mnd_entry->pclk_n - 1); |
| *expected_dsi_pclk = ((vco * 1000000) / |
| ((pll_divider_config.dsi_clk_divider) |
| * (mnd_entry->pclk_n))); |
| } else { |
| dsi_pclk.mnd_mode = 2; |
| dsi_pclk.src = 0x3; |
| dsi_pclk.m = mnd_entry->pclk_m; |
| dsi_pclk.n = mnd_entry->pclk_n; |
| dsi_pclk.d = mnd_entry->pclk_d; |
| *expected_dsi_pclk = ((vco * 1000000 * dsi_pclk.m) / |
| ((pll_divider_config.dsi_clk_divider) |
| * (mnd_entry->pclk_n))); |
| } |
| dsicore_clk.m = 1; |
| dsicore_clk.n = 1; |
| dsicore_clk.d = 2; |
| dsicore_clk.pre_div_func = 0; |
| |
| dsi_pclk.m = 1; |
| dsi_pclk.n = 3; |
| dsi_pclk.d = 2; |
| dsi_pclk.pre_div_func = 0; |
| return 0; |
| } |
| |
| void mipi_dsi_phy_init(int panel_ndx, struct msm_panel_info const *panel_info, |
| int target_type) |
| { |
| struct mipi_dsi_phy_ctrl *pd; |
| int i, off; |
| |
| MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0001);/* start phy sw reset */ |
| wmb(); |
| usleep(1000); |
| MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0000);/* end phy w reset */ |
| wmb(); |
| usleep(1000); |
| MIPI_OUTP(MIPI_DSI_BASE + 0x2cc, 0x0003);/* regulator_ctrl_0 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x2d0, 0x0001);/* regulator_ctrl_1 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x2d4, 0x0001);/* regulator_ctrl_2 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x2d8, 0x0000);/* regulator_ctrl_3 */ |
| #ifdef DSI_POWER |
| MIPI_OUTP(MIPI_DSI_BASE + 0x2dc, 0x0100);/* regulator_ctrl_4 */ |
| #endif |
| |
| pd = (panel_info->mipi).dsi_phy_db; |
| |
| off = 0x02cc; /* regulator ctrl 0 */ |
| for (i = 0; i < 4; i++) { |
| MIPI_OUTP(MIPI_DSI_BASE + off, pd->regulator[i]); |
| wmb(); |
| off += 4; |
| } |
| |
| off = 0x0260; /* phy timig ctrl 0 */ |
| for (i = 0; i < 11; i++) { |
| MIPI_OUTP(MIPI_DSI_BASE + off, pd->timing[i]); |
| wmb(); |
| off += 4; |
| } |
| |
| off = 0x0290; /* ctrl 0 */ |
| for (i = 0; i < 4; i++) { |
| MIPI_OUTP(MIPI_DSI_BASE + off, pd->ctrl[i]); |
| wmb(); |
| off += 4; |
| } |
| |
| off = 0x02a0; /* strength 0 */ |
| for (i = 0; i < 4; i++) { |
| MIPI_OUTP(MIPI_DSI_BASE + off, pd->strength[i]); |
| wmb(); |
| off += 4; |
| } |
| |
| mipi_dsi_calibration(); |
| |
| off = 0x0204; /* pll ctrl 1, skip 0 */ |
| for (i = 1; i < 21; i++) { |
| MIPI_OUTP(MIPI_DSI_BASE + off, pd->pll[i]); |
| wmb(); |
| off += 4; |
| } |
| |
| MIPI_OUTP(MIPI_DSI_BASE + 0x100, 0x67); |
| |
| /* pll ctrl 0 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x0200, pd->pll[0]); |
| wmb(); |
| } |
| |
| void cont_splash_clk_ctrl(int enable) |
| { |
| static int cont_splash_clks_enabled; |
| if (enable && !cont_splash_clks_enabled) { |
| clk_prepare_enable(dsi_ref_clk); |
| clk_prepare_enable(mdp_dsi_pclk); |
| clk_prepare_enable(dsi_byte_div_clk); |
| clk_prepare_enable(dsi_esc_clk); |
| clk_prepare_enable(dsi_pixel_clk); |
| clk_prepare_enable(dsi_clk); |
| cont_splash_clks_enabled = 1; |
| } else if (!enable && cont_splash_clks_enabled) { |
| clk_disable_unprepare(dsi_clk); |
| clk_disable_unprepare(dsi_pixel_clk); |
| clk_disable_unprepare(dsi_esc_clk); |
| clk_disable_unprepare(dsi_byte_div_clk); |
| clk_disable_unprepare(mdp_dsi_pclk); |
| clk_disable_unprepare(dsi_ref_clk); |
| cont_splash_clks_enabled = 0; |
| } |
| } |
| |
| void mipi_dsi_prepare_clocks(void) |
| { |
| clk_prepare(dsi_ref_clk); |
| clk_prepare(ahb_m_clk); |
| clk_prepare(ahb_s_clk); |
| clk_prepare(ebi1_dsi_clk); |
| clk_prepare(mdp_dsi_pclk); |
| clk_prepare(dsi_byte_div_clk); |
| clk_prepare(dsi_esc_clk); |
| clk_prepare(dsi_clk); |
| clk_prepare(dsi_pixel_clk); |
| } |
| |
| void mipi_dsi_unprepare_clocks(void) |
| { |
| clk_unprepare(dsi_esc_clk); |
| clk_unprepare(dsi_byte_div_clk); |
| clk_unprepare(mdp_dsi_pclk); |
| clk_unprepare(ebi1_dsi_clk); |
| clk_unprepare(ahb_m_clk); |
| clk_unprepare(ahb_s_clk); |
| clk_unprepare(dsi_ref_clk); |
| clk_unprepare(dsi_clk); |
| clk_unprepare(dsi_pixel_clk); |
| } |
| |
| void mipi_dsi_ahb_ctrl(u32 enable) |
| { |
| static int ahb_ctrl_done; |
| if (enable) { |
| if (ahb_ctrl_done) { |
| pr_info("%s: ahb clks already ON\n", __func__); |
| return; |
| } |
| clk_enable(dsi_ref_clk); |
| clk_enable(ahb_m_clk); |
| clk_enable(ahb_s_clk); |
| ahb_ctrl_done = 1; |
| } else { |
| if (ahb_ctrl_done == 0) { |
| pr_info("%s: ahb clks already OFF\n", __func__); |
| return; |
| } |
| clk_disable(ahb_m_clk); |
| clk_disable(ahb_s_clk); |
| clk_disable(dsi_ref_clk); |
| ahb_ctrl_done = 0; |
| } |
| } |
| |
| void mipi_dsi_clk_enable(void) |
| { |
| unsigned data = 0; |
| uint32 pll_ctrl; |
| |
| if (mipi_dsi_clk_on) { |
| pr_info("%s: mipi_dsi_clks already ON\n", __func__); |
| return; |
| } |
| if (clk_set_rate(ebi1_dsi_clk, 65000000)) /* 65 MHz */ |
| pr_err("%s: ebi1_dsi_clk set rate failed\n", __func__); |
| clk_enable(ebi1_dsi_clk); |
| |
| pll_ctrl = MIPI_INP(MIPI_DSI_BASE + 0x0200); |
| MIPI_OUTP(MIPI_DSI_BASE + 0x0200, pll_ctrl | 0x01); |
| mb(); |
| |
| clk_set_rate(dsi_byte_div_clk, data); |
| clk_set_rate(dsi_esc_clk, data); |
| clk_enable(mdp_dsi_pclk); |
| clk_enable(dsi_byte_div_clk); |
| clk_enable(dsi_esc_clk); |
| mipi_dsi_pclk_ctrl(&dsi_pclk, 1); |
| mipi_dsi_clk_ctrl(&dsicore_clk, 1); |
| mipi_dsi_clk_on = 1; |
| } |
| |
| void mipi_dsi_clk_disable(void) |
| { |
| if (mipi_dsi_clk_on == 0) { |
| pr_info("%s: mipi_dsi_clks already OFF\n", __func__); |
| return; |
| } |
| mipi_dsi_pclk_ctrl(&dsi_pclk, 0); |
| mipi_dsi_clk_ctrl(&dsicore_clk, 0); |
| clk_disable(dsi_esc_clk); |
| clk_disable(dsi_byte_div_clk); |
| clk_disable(mdp_dsi_pclk); |
| /* DSIPHY_PLL_CTRL_0, disable dsi pll */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x0200, 0x40); |
| if (clk_set_rate(ebi1_dsi_clk, 0)) |
| pr_err("%s: ebi1_dsi_clk set rate failed\n", __func__); |
| clk_disable(ebi1_dsi_clk); |
| mipi_dsi_clk_on = 0; |
| } |
| |
| void mipi_dsi_phy_ctrl(int on) |
| { |
| if (on) { |
| /* DSIPHY_PLL_CTRL_5 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x0214, 0x050); |
| |
| /* DSIPHY_TPA_CTRL_1 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x0258, 0x00f); |
| |
| /* DSIPHY_TPA_CTRL_2 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x025c, 0x000); |
| } else { |
| /* DSIPHY_PLL_CTRL_5 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x0214, 0x05f); |
| |
| /* DSIPHY_TPA_CTRL_1 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x0258, 0x08f); |
| |
| /* DSIPHY_TPA_CTRL_2 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x025c, 0x001); |
| |
| /* DSIPHY_REGULATOR_CTRL_0 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x02cc, 0x02); |
| |
| /* DSIPHY_CTRL_0 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x0290, 0x00); |
| |
| /* DSIPHY_CTRL_1 */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x0294, 0x7f); |
| |
| /* disable dsi clk */ |
| MIPI_OUTP(MIPI_DSI_BASE + 0x0118, 0); |
| } |
| } |
| |
| #ifdef CONFIG_FB_MSM_MDP303 |
| void update_lane_config(struct msm_panel_info *pinfo) |
| { |
| struct mipi_dsi_phy_ctrl *pd; |
| |
| pd = (pinfo->mipi).dsi_phy_db; |
| pinfo->mipi.data_lane1 = FALSE; |
| pd->pll[10] |= 0x08; |
| |
| pinfo->yres = 320; |
| pinfo->lcdc.h_back_porch = 15; |
| pinfo->lcdc.h_front_porch = 21; |
| pinfo->lcdc.h_pulse_width = 5; |
| pinfo->lcdc.v_back_porch = 50; |
| pinfo->lcdc.v_front_porch = 101; |
| pinfo->lcdc.v_pulse_width = 50; |
| } |
| #endif |