Hi,
This version mainly addresses the comments from Philipp Zabel on v8. The comments include a. A common compatible string "snps,dw-mipi-dsi" should be appended to all SoCs' MIPI DSI device tree documentations and nodes. b. Clean up the common clocks needed by the Synopsys DesignWare MIPI DSI host controller.
This version also drops two documentation patches in v8 for adding Himax and Truly vendor prefixes since Rob Herring has taken them.
The i.MX MIPI DSI is a Synopsys DesignWare MIPI DSI host controller IP. This series adds support for a Synopsys DesignWare MIPI DSI host controller DRM bridge driver and a i.MX MIPI DSI specific DRM driver. Currently, the MIPI DSI drivers only support the burst with sync pulse mode.
This series also includes a DRM panel driver for the Truly TFT480800-16-E panel which is driven by the Himax HX8369A driver IC. The driver IC data sheet could be found at [1]. As mentioned by the data sheet, the driver IC supports several interface modes. Currently, the DRM panel driver only supports the MIPI DSI video mode. New interface modes could be added later(perhaps, just like the way the DRM simple panel driver supports both MIPI DSI interface panels and simple(parallel) interface panels).
The MIPI DSI feature is tested on i.MX6Q SabreSD board and i.MX6DL SabreSD board. The MIPI DSI display could be enabled directly on i.MX6Q SabreSD board after applying this series, because the 26.4MHz pixel clock the panel requires could be derived from the IPU HSP clock(264MHz) with an integer divider. On i.MX6DL SabreSD board, we need to manually disable the LVDS and HDMI displays in the device tree blob, since the i.MX6DL IPU HSP clock is 198MHz at present, which makes the pixel clock share the PLL5 video clock source with the LVDS and HDMI, thus, the panel cannot get the pixel clock rate it wants.
Patch 01/20 is needed to get a precise pixel clock rate(26.4MHz) from the PLL5 video clock. If we don't have this patch, the pixel clock rate is about 20MHz, which causes a horitonal shift on the display image.
This series can be applied on the imx-drm/next branch of Philipp Zabel's open git repository.
[1] http://www.allshore.com/pdf/Himax_HX8369-A.pdf
Liu Ying (20): clk: divider: Correct parent clk round rate if no bestdiv is normally found ARM: imx6q: Add GPR3 MIPI muxing control register field shift bits definition ARM: imx6q: clk: Add the video_27m clock ARM: imx6q: clk: Change hdmi_isfr clock's parent to be video_27m clock ARM: imx6q: clk: Change hsi_tx clock to be a shared clock gate ARM: imx6q: clk: Add support for mipi_core_cfg clock as a shared clock gate ARM: imx6q: clk: Add support for mipi_ipg clock as a shared clock gate ARM: dts: imx6qdl: Move existing MIPI DSI ports into a new 'ports' node drm/dsi: Add a helper to get bits per pixel of MIPI DSI pixel format Documentation: dt-bindings: Add bindings for Synopsys DW MIPI DSI DRM bridge driver drm/bridge: Add Synopsys DesignWare MIPI DSI host controller driver Documentation: dt-bindings: Add bindings for i.MX specific Synopsys DW MIPI DSI driver drm: imx: Support Synopsys DesignWare MIPI DSI host controller Documentation: dt-bindings: Add bindings for Himax HX8369A DRM panel driver drm: panel: Add support for Himax HX8369A MIPI DSI panel ARM: dtsi: imx6qdl: Add support for MIPI DSI host controller ARM: dts: imx6qdl-sabresd: Add support for TRULY TFT480800-16-E MIPI DSI panel ARM: imx_v6_v7_defconfig: Cleanup for imx drm being moved out of staging ARM: imx_v6_v7_defconfig: Add support for MIPI DSI host controller ARM: imx_v6_v7_defconfig: Add support for Himax HX8369A panel
.../devicetree/bindings/drm/bridge/dw_mipi_dsi.txt | 76 ++ .../devicetree/bindings/drm/imx/mipi_dsi.txt | 81 ++ .../devicetree/bindings/panel/himax,hx8369a.txt | 39 + arch/arm/boot/dts/imx6q.dtsi | 20 +- arch/arm/boot/dts/imx6qdl-sabresd.dtsi | 20 + arch/arm/boot/dts/imx6qdl.dtsi | 30 +- arch/arm/configs/imx_v6_v7_defconfig | 23 +- arch/arm/mach-imx/clk-imx6q.c | 8 +- drivers/clk/clk-divider.c | 3 +- drivers/gpu/drm/bridge/Kconfig | 10 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/dw_mipi_dsi.c | 1006 ++++++++++++++++++++ drivers/gpu/drm/imx/Kconfig | 7 + drivers/gpu/drm/imx/Makefile | 1 + drivers/gpu/drm/imx/dw_mipi_dsi-imx.c | 230 +++++ drivers/gpu/drm/panel/Kconfig | 5 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-himax-hx8369a.c | 610 ++++++++++++ include/drm/bridge/dw_mipi_dsi.h | 27 + include/drm/drm_mipi_dsi.h | 14 + include/dt-bindings/clock/imx6qdl-clock.h | 5 +- include/linux/mfd/syscon/imx6q-iomuxc-gpr.h | 1 + 22 files changed, 2184 insertions(+), 34 deletions(-) create mode 100644 Documentation/devicetree/bindings/drm/bridge/dw_mipi_dsi.txt create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt create mode 100644 drivers/gpu/drm/bridge/dw_mipi_dsi.c create mode 100644 drivers/gpu/drm/imx/dw_mipi_dsi-imx.c create mode 100644 drivers/gpu/drm/panel/panel-himax-hx8369a.c create mode 100644 include/drm/bridge/dw_mipi_dsi.h
If no best divider is normally found, we will try to use the maximum divider. We should not set the parent clock rate to be 1Hz by force for being rounded. Instead, we should take the maximum divider as a base and calculate a correct parent clock rate for being rounded.
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v4->v5: * None.
v3->v4: * None.
v2->v3: * None.
v1->v2: * None.
drivers/clk/clk-divider.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index c0a842b..f641d4b 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -311,7 +311,8 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
if (!bestdiv) { bestdiv = _get_maxdiv(divider); - *best_parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), 1); + *best_parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), + MULT_ROUND_UP(rate, bestdiv)); }
return bestdiv;
On Thu, Feb 12, 2015 at 02:01:24PM +0800, Liu Ying wrote:
If no best divider is normally found, we will try to use the maximum divider. We should not set the parent clock rate to be 1Hz by force for being rounded. Instead, we should take the maximum divider as a base and calculate a correct parent clock rate for being rounded.
Please add an explanation why you think the current code is wrong and what this actually fixes, maybe an example?
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index c0a842b..f641d4b 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -311,7 +311,8 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
if (!bestdiv) { bestdiv = _get_maxdiv(divider);
*best_parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), 1);
*best_parent_rate = __clk_round_rate(__clk_get_parent(hw->clk),
MULT_ROUND_UP(rate, bestdiv));
When getting into the if(!bestdiv) it means that the lowest possible rate we can archieve is still higher than the target rate, so setting the parent rate as low as possible seems sane to me. Why do you think this is wrong? In which case this even makes a difference?
Sascha
On Thu, Feb 12, 2015 at 10:33:56AM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 02:01:24PM +0800, Liu Ying wrote:
If no best divider is normally found, we will try to use the maximum divider. We should not set the parent clock rate to be 1Hz by force for being rounded. Instead, we should take the maximum divider as a base and calculate a correct parent clock rate for being rounded.
Please add an explanation why you think the current code is wrong and what this actually fixes, maybe an example?
The MIPI DSI panel's pixel clock rate is 26.4MHz and it's derived from PLL5 on the MX6DL SabreSD board.
These are the clock tree summaries with or without the patch applied: 1) With the patch applied: pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 844800048 0 0 pll5_bypass 1 1 844800048 0 0 pll5_video 1 1 844800048 0 0 pll5_post_div 1 1 211200012 0 0 pll5_video_div 1 1 211200012 0 0 ipu1_di0_pre_sel 1 1 211200012 0 0 ipu1_di0_pre 1 1 26400002 0 0 ipu1_di0_sel 1 1 26400002 0 0 ipu1_di0 1 1 26400002 0 0
2) Without the patch applied: pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 648000000 0 0 pll5_bypass 1 1 648000000 0 0 pll5_video 1 1 648000000 0 0 pll5_post_div 1 1 162000000 0 0 pll5_video_div 1 1 40500000 0 0 ipu1_di0_pre_sel 1 1 40500000 0 0 ipu1_di0_pre 1 1 20250000 0 0 ipu1_di0_sel 1 1 20250000 0 0 ipu1_di0 1 1 20250000 0 0
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index c0a842b..f641d4b 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -311,7 +311,8 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
if (!bestdiv) { bestdiv = _get_maxdiv(divider);
*best_parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), 1);
*best_parent_rate = __clk_round_rate(__clk_get_parent(hw->clk),
MULT_ROUND_UP(rate, bestdiv));
When getting into the if(!bestdiv) it means that the lowest possible rate we can archieve is still higher than the target rate, so setting the parent rate as low as possible seems sane to me. Why do you think this is wrong? In which case this even makes a difference?
We still should take the little left chance to get a closest target clock rate we want(26.4MHz, in the example case above), so it looks that the lowest parent clock rate for being rounded should be MULT_ROUND_UP(rate, bestdiv) instead of 1Hz.
Regards, Liu Ying
Sascha
-- Pengutronix e.K. | | Industrial Linux Solutions | http://www.pengutronix.de/ | Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
On Thu, Feb 12, 2015 at 06:39:45PM +0800, Liu Ying wrote:
On Thu, Feb 12, 2015 at 10:33:56AM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 02:01:24PM +0800, Liu Ying wrote:
If no best divider is normally found, we will try to use the maximum divider. We should not set the parent clock rate to be 1Hz by force for being rounded. Instead, we should take the maximum divider as a base and calculate a correct parent clock rate for being rounded.
Please add an explanation why you think the current code is wrong and what this actually fixes, maybe an example?
The MIPI DSI panel's pixel clock rate is 26.4MHz and it's derived from PLL5 on the MX6DL SabreSD board.
These are the clock tree summaries with or without the patch applied:
- With the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 844800048 0 0 pll5_bypass 1 1 844800048 0 0 pll5_video 1 1 844800048 0 0 pll5_post_div 1 1 211200012 0 0 pll5_video_div 1 1 211200012 0 0 ipu1_di0_pre_sel 1 1 211200012 0 0 ipu1_di0_pre 1 1 26400002 0 0 ipu1_di0_sel 1 1 26400002 0 0 ipu1_di0 1 1 26400002 0 0
- Without the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 648000000 0 0 pll5_bypass 1 1 648000000 0 0 pll5_video 1 1 648000000 0 0 pll5_post_div 1 1 162000000 0 0 pll5_video_div 1 1 40500000 0 0 ipu1_di0_pre_sel 1 1 40500000 0 0 ipu1_di0_pre 1 1 20250000 0 0 ipu1_di0_sel 1 1 20250000 0 0 ipu1_di0 1 1 20250000 0 0
This seems to be broken since:
| commit b11d282dbea27db1788893115dfca8a7856bf205 | Author: Tomi Valkeinen tomi.valkeinen@ti.com | Date: Thu Feb 13 12:03:59 2014 +0200 | | clk: divider: fix rate calculation for fractional rates
This patch fixed a case when clk_set_rate(clk_round_rate(rate)) resulted in a lower frequency than clk_round_rate(rate) returned.
Since then the MULT_ROUND_UP in clk_divider_bestdiv() is inconsistent to the rest of the divider. Maybe this should be a simple rate * i now, but I'm unsure what side effects this has.
I think your patch only fixes the behaviour in your case by accident, it's not a correct fix for this issue.
Sascha
On Thu, Feb 12, 2015 at 01:24:05PM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 06:39:45PM +0800, Liu Ying wrote:
On Thu, Feb 12, 2015 at 10:33:56AM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 02:01:24PM +0800, Liu Ying wrote:
If no best divider is normally found, we will try to use the maximum divider. We should not set the parent clock rate to be 1Hz by force for being rounded. Instead, we should take the maximum divider as a base and calculate a correct parent clock rate for being rounded.
Please add an explanation why you think the current code is wrong and what this actually fixes, maybe an example?
The MIPI DSI panel's pixel clock rate is 26.4MHz and it's derived from PLL5 on the MX6DL SabreSD board.
These are the clock tree summaries with or without the patch applied:
- With the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 844800048 0 0 pll5_bypass 1 1 844800048 0 0 pll5_video 1 1 844800048 0 0 pll5_post_div 1 1 211200012 0 0 pll5_video_div 1 1 211200012 0 0 ipu1_di0_pre_sel 1 1 211200012 0 0 ipu1_di0_pre 1 1 26400002 0 0 ipu1_di0_sel 1 1 26400002 0 0 ipu1_di0 1 1 26400002 0 0
- Without the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 648000000 0 0 pll5_bypass 1 1 648000000 0 0 pll5_video 1 1 648000000 0 0 pll5_post_div 1 1 162000000 0 0 pll5_video_div 1 1 40500000 0 0 ipu1_di0_pre_sel 1 1 40500000 0 0 ipu1_di0_pre 1 1 20250000 0 0 ipu1_di0_sel 1 1 20250000 0 0 ipu1_di0 1 1 20250000 0 0
This seems to be broken since:
| commit b11d282dbea27db1788893115dfca8a7856bf205 | Author: Tomi Valkeinen tomi.valkeinen@ti.com | Date: Thu Feb 13 12:03:59 2014 +0200 | | clk: divider: fix rate calculation for fractional rates
This patch fixed a case when clk_set_rate(clk_round_rate(rate)) resulted in a lower frequency than clk_round_rate(rate) returned.
Since then the MULT_ROUND_UP in clk_divider_bestdiv() is inconsistent to the rest of the divider. Maybe this should be a simple rate * i now, but I'm unsure what side effects this has.
I think your patch only fixes the behaviour in your case by accident, it's not a correct fix for this issue.
Well, it's defined that:
new_rate = clk_round_rate(clk, rate);
returns the rate which you would get if you did:
clk_set_rate(clk, rate); new_rate = clk_get_rate(clk);
The reasoning here is that clk_round_rate() gives you a way to query what rate you would get if you were to ask for the rate to be set, without effecting a change in the hardware.
The idea that you should call clk_round_rate() first before clk_set_rate() and pass the returned rounded rate into clk_set_rate() is really idiotic given that. Please don't do it, and please remove code which does it, and in review comment on it. Thanks.
On Thu, Feb 12, 2015 at 12:56:46PM +0000, Russell King - ARM Linux wrote:
On Thu, Feb 12, 2015 at 01:24:05PM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 06:39:45PM +0800, Liu Ying wrote:
On Thu, Feb 12, 2015 at 10:33:56AM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 02:01:24PM +0800, Liu Ying wrote:
If no best divider is normally found, we will try to use the maximum divider. We should not set the parent clock rate to be 1Hz by force for being rounded. Instead, we should take the maximum divider as a base and calculate a correct parent clock rate for being rounded.
Please add an explanation why you think the current code is wrong and what this actually fixes, maybe an example?
The MIPI DSI panel's pixel clock rate is 26.4MHz and it's derived from PLL5 on the MX6DL SabreSD board.
These are the clock tree summaries with or without the patch applied:
- With the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 844800048 0 0 pll5_bypass 1 1 844800048 0 0 pll5_video 1 1 844800048 0 0 pll5_post_div 1 1 211200012 0 0 pll5_video_div 1 1 211200012 0 0 ipu1_di0_pre_sel 1 1 211200012 0 0 ipu1_di0_pre 1 1 26400002 0 0 ipu1_di0_sel 1 1 26400002 0 0 ipu1_di0 1 1 26400002 0 0
- Without the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 648000000 0 0 pll5_bypass 1 1 648000000 0 0 pll5_video 1 1 648000000 0 0 pll5_post_div 1 1 162000000 0 0 pll5_video_div 1 1 40500000 0 0 ipu1_di0_pre_sel 1 1 40500000 0 0 ipu1_di0_pre 1 1 20250000 0 0 ipu1_di0_sel 1 1 20250000 0 0 ipu1_di0 1 1 20250000 0 0
This seems to be broken since:
| commit b11d282dbea27db1788893115dfca8a7856bf205 | Author: Tomi Valkeinen tomi.valkeinen@ti.com | Date: Thu Feb 13 12:03:59 2014 +0200 | | clk: divider: fix rate calculation for fractional rates
This patch fixed a case when clk_set_rate(clk_round_rate(rate)) resulted in a lower frequency than clk_round_rate(rate) returned.
Since then the MULT_ROUND_UP in clk_divider_bestdiv() is inconsistent to the rest of the divider. Maybe this should be a simple rate * i now, but I'm unsure what side effects this has.
I think your patch only fixes the behaviour in your case by accident, it's not a correct fix for this issue.
Well, it's defined that:
new_rate = clk_round_rate(clk, rate);
returns the rate which you would get if you did:
clk_set_rate(clk, rate); new_rate = clk_get_rate(clk);
The reasoning here is that clk_round_rate() gives you a way to query what rate you would get if you were to ask for the rate to be set, without effecting a change in the hardware.
The idea that you should call clk_round_rate() first before clk_set_rate() and pass the returned rounded rate into clk_set_rate() is really idiotic given that. Please don't do it, and please remove code which does it, and in review comment on it. Thanks.
Tomis patch is based on the assumption that clk_set_rate(clk_round_rate(rate)) is equal to clk_round_rate(rate). So when this assumption is wrong then it should simply be reverted. So Liu, could you test if reverting Tomis patch fixes your problem?
Sascha
On Thu, Feb 12, 2015 at 02:41:31PM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 12:56:46PM +0000, Russell King - ARM Linux wrote:
On Thu, Feb 12, 2015 at 01:24:05PM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 06:39:45PM +0800, Liu Ying wrote:
On Thu, Feb 12, 2015 at 10:33:56AM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 02:01:24PM +0800, Liu Ying wrote:
If no best divider is normally found, we will try to use the maximum divider. We should not set the parent clock rate to be 1Hz by force for being rounded. Instead, we should take the maximum divider as a base and calculate a correct parent clock rate for being rounded.
Please add an explanation why you think the current code is wrong and what this actually fixes, maybe an example?
The MIPI DSI panel's pixel clock rate is 26.4MHz and it's derived from PLL5 on the MX6DL SabreSD board.
These are the clock tree summaries with or without the patch applied:
- With the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 844800048 0 0 pll5_bypass 1 1 844800048 0 0 pll5_video 1 1 844800048 0 0 pll5_post_div 1 1 211200012 0 0 pll5_video_div 1 1 211200012 0 0 ipu1_di0_pre_sel 1 1 211200012 0 0 ipu1_di0_pre 1 1 26400002 0 0 ipu1_di0_sel 1 1 26400002 0 0 ipu1_di0 1 1 26400002 0 0
- Without the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 648000000 0 0 pll5_bypass 1 1 648000000 0 0 pll5_video 1 1 648000000 0 0 pll5_post_div 1 1 162000000 0 0 pll5_video_div 1 1 40500000 0 0 ipu1_di0_pre_sel 1 1 40500000 0 0 ipu1_di0_pre 1 1 20250000 0 0 ipu1_di0_sel 1 1 20250000 0 0 ipu1_di0 1 1 20250000 0 0
This seems to be broken since:
| commit b11d282dbea27db1788893115dfca8a7856bf205 | Author: Tomi Valkeinen tomi.valkeinen@ti.com | Date: Thu Feb 13 12:03:59 2014 +0200 | | clk: divider: fix rate calculation for fractional rates
This patch fixed a case when clk_set_rate(clk_round_rate(rate)) resulted in a lower frequency than clk_round_rate(rate) returned.
Since then the MULT_ROUND_UP in clk_divider_bestdiv() is inconsistent to the rest of the divider. Maybe this should be a simple rate * i now, but I'm unsure what side effects this has.
I think your patch only fixes the behaviour in your case by accident, it's not a correct fix for this issue.
Well, it's defined that:
new_rate = clk_round_rate(clk, rate);
returns the rate which you would get if you did:
clk_set_rate(clk, rate); new_rate = clk_get_rate(clk);
The reasoning here is that clk_round_rate() gives you a way to query what rate you would get if you were to ask for the rate to be set, without effecting a change in the hardware.
The idea that you should call clk_round_rate() first before clk_set_rate() and pass the returned rounded rate into clk_set_rate() is really idiotic given that. Please don't do it, and please remove code which does it, and in review comment on it. Thanks.
Tomis patch is based on the assumption that clk_set_rate(clk_round_rate(rate)) is equal to clk_round_rate(rate). So when this assumption is wrong then it should simply be reverted. So Liu, could you test if reverting Tomis patch fixes your problem?
Yes, I'll test tomorrow when I have access to my board.
Regards, Liu Ying
Sascha
-- Pengutronix e.K. | | Industrial Linux Solutions | http://www.pengutronix.de/ | Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
On Thu, Feb 12, 2015 at 10:06:27PM +0800, Liu Ying wrote:
On Thu, Feb 12, 2015 at 02:41:31PM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 12:56:46PM +0000, Russell King - ARM Linux wrote:
On Thu, Feb 12, 2015 at 01:24:05PM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 06:39:45PM +0800, Liu Ying wrote:
On Thu, Feb 12, 2015 at 10:33:56AM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 02:01:24PM +0800, Liu Ying wrote: > If no best divider is normally found, we will try to use the maximum divider. > We should not set the parent clock rate to be 1Hz by force for being rounded. > Instead, we should take the maximum divider as a base and calculate a correct > parent clock rate for being rounded.
Please add an explanation why you think the current code is wrong and what this actually fixes, maybe an example?
The MIPI DSI panel's pixel clock rate is 26.4MHz and it's derived from PLL5 on the MX6DL SabreSD board.
These are the clock tree summaries with or without the patch applied:
- With the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 844800048 0 0 pll5_bypass 1 1 844800048 0 0 pll5_video 1 1 844800048 0 0 pll5_post_div 1 1 211200012 0 0 pll5_video_div 1 1 211200012 0 0 ipu1_di0_pre_sel 1 1 211200012 0 0 ipu1_di0_pre 1 1 26400002 0 0 ipu1_di0_sel 1 1 26400002 0 0 ipu1_di0 1 1 26400002 0 0
- Without the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 648000000 0 0 pll5_bypass 1 1 648000000 0 0 pll5_video 1 1 648000000 0 0 pll5_post_div 1 1 162000000 0 0 pll5_video_div 1 1 40500000 0 0 ipu1_di0_pre_sel 1 1 40500000 0 0 ipu1_di0_pre 1 1 20250000 0 0 ipu1_di0_sel 1 1 20250000 0 0 ipu1_di0 1 1 20250000 0 0
This seems to be broken since:
| commit b11d282dbea27db1788893115dfca8a7856bf205 | Author: Tomi Valkeinen tomi.valkeinen@ti.com | Date: Thu Feb 13 12:03:59 2014 +0200 | | clk: divider: fix rate calculation for fractional rates
This patch fixed a case when clk_set_rate(clk_round_rate(rate)) resulted in a lower frequency than clk_round_rate(rate) returned.
Since then the MULT_ROUND_UP in clk_divider_bestdiv() is inconsistent to the rest of the divider. Maybe this should be a simple rate * i now, but I'm unsure what side effects this has.
I think your patch only fixes the behaviour in your case by accident, it's not a correct fix for this issue.
Well, it's defined that:
new_rate = clk_round_rate(clk, rate);
returns the rate which you would get if you did:
clk_set_rate(clk, rate); new_rate = clk_get_rate(clk);
The reasoning here is that clk_round_rate() gives you a way to query what rate you would get if you were to ask for the rate to be set, without effecting a change in the hardware.
The idea that you should call clk_round_rate() first before clk_set_rate() and pass the returned rounded rate into clk_set_rate() is really idiotic given that. Please don't do it, and please remove code which does it, and in review comment on it. Thanks.
Tomis patch is based on the assumption that clk_set_rate(clk_round_rate(rate)) is equal to clk_round_rate(rate). So when this assumption is wrong then it should simply be reverted. So Liu, could you test if reverting Tomis patch fixes your problem?
Yes, I'll test tomorrow when I have access to my board.
Tomi's patch cannot be reverted directly because of conflicts with the later patches. So, I revert all the clock divider driver patches on top of that. And, yes, after reverting Tomi's patch, I may get the correct 26.4MHz pixel clock rate.
Regards, Liu Ying
Regards, Liu Ying
Sascha
-- Pengutronix e.K. | | Industrial Linux Solutions | http://www.pengutronix.de/ | Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Travis liked your message with Boxer for Android.
On Feb 12, 2015 8:58 PM, Liu Ying Ying.Liu@freescale.com wrote:
On Thu, Feb 12, 2015 at 10:06:27PM +0800, Liu Ying wrote:
On Thu, Feb 12, 2015 at 02:41:31PM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 12:56:46PM +0000, Russell King - ARM Linux wrote:
On Thu, Feb 12, 2015 at 01:24:05PM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 06:39:45PM +0800, Liu Ying wrote:
On Thu, Feb 12, 2015 at 10:33:56AM +0100, Sascha Hauer wrote: > On Thu, Feb 12, 2015 at 02:01:24PM +0800, Liu Ying wrote: > > If no best divider is normally found, we will try to use the maximum divider. > > We should not set the parent clock rate to be 1Hz by force for being rounded. > > Instead, we should take the maximum divider as a base and calculate a correct > > parent clock rate for being rounded. > > Please add an explanation why you think the current code is wrong and > what this actually fixes, maybe an example?
The MIPI DSI panel's pixel clock rate is 26.4MHz and it's derived from PLL5 on the MX6DL SabreSD board.
These are the clock tree summaries with or without the patch applied:
- With the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 844800048 0 0 pll5_bypass 1 1 844800048 0 0 pll5_video 1 1 844800048 0 0 pll5_post_div 1 1 211200012 0 0 pll5_video_div 1 1 211200012 0 0 ipu1_di0_pre_sel 1 1 211200012 0 0 ipu1_di0_pre 1 1 26400002 0 0 ipu1_di0_sel 1 1 26400002 0 0 ipu1_di0 1 1 26400002 0 0
- Without the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 648000000 0 0 pll5_bypass 1 1 648000000 0 0 pll5_video 1 1 648000000 0 0 pll5_post_div 1 1 162000000 0 0 pll5_video_div 1 1 40500000 0 0 ipu1_di0_pre_sel 1 1 40500000 0 0 ipu1_di0_pre 1 1 20250000 0 0 ipu1_di0_sel 1 1 20250000 0 0 ipu1_di0 1 1 20250000 0 0
This seems to be broken since:
| commit b11d282dbea27db1788893115dfca8a7856bf205 | Author: Tomi Valkeinen tomi.valkeinen@ti.com | Date: Thu Feb 13 12:03:59 2014 +0200 | | clk: divider: fix rate calculation for fractional rates
This patch fixed a case when clk_set_rate(clk_round_rate(rate)) resulted in a lower frequency than clk_round_rate(rate) returned.
Since then the MULT_ROUND_UP in clk_divider_bestdiv() is inconsistent to the rest of the divider. Maybe this should be a simple rate * i now, but I'm unsure what side effects this has.
I think your patch only fixes the behaviour in your case by accident, it's not a correct fix for this issue.
Well, it's defined that:
new_rate = clk_round_rate(clk, rate);
returns the rate which you would get if you did:
clk_set_rate(clk, rate); new_rate = clk_get_rate(clk);
The reasoning here is that clk_round_rate() gives you a way to query what rate you would get if you were to ask for the rate to be set, without effecting a change in the hardware.
The idea that you should call clk_round_rate() first before clk_set_rate() and pass the returned rounded rate into clk_set_rate() is really idiotic given that. Please don't do it, and please remove code which does it, and in review comment on it. Thanks.
Tomis patch is based on the assumption that clk_set_rate(clk_round_rate(rate)) is equal to clk_round_rate(rate). So when this assumption is wrong then it should simply be reverted. So Liu, could you test if reverting Tomis patch fixes your problem?
Yes, I'll test tomorrow when I have access to my board.
Tomi's patch cannot be reverted directly because of conflicts with the later patches. So, I revert all the clock divider driver patches on top of that. And, yes, after reverting Tomi's patch, I may get the correct 26.4MHz pixel clock rate.
Regards, Liu Ying
Regards, Liu Ying
Sascha
-- Pengutronix e.K. | | Industrial Linux Solutions | http://www.pengutronix.de/%C2%A0 | Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
-- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
On 12/02/15 15:41, Sascha Hauer wrote:
Tomis patch is based on the assumption that clk_set_rate(clk_round_rate(rate)) is equal to clk_round_rate(rate). So when this assumption is wrong then it should simply be reverted.
When is it not equal?
I agree that doing clk_set_rate(clk, clk_round_rate(clk, rate)) is pointless, but shouldn't it still work?
And we can forget about clk_round_rate. Without my patch, this would behave oddly also:
rate = clk_get_rate(clk); clk_set_rate(clk, rate);
The end result could be something else than 'rate'.
Tomi
On Fri, Feb 13, 2015 at 04:35:36PM +0200, Tomi Valkeinen wrote:
On 12/02/15 15:41, Sascha Hauer wrote:
Tomis patch is based on the assumption that clk_set_rate(clk_round_rate(rate)) is equal to clk_round_rate(rate). So when this assumption is wrong then it should simply be reverted.
When is it not equal?
I agree that doing clk_set_rate(clk, clk_round_rate(clk, rate)) is pointless, but shouldn't it still work?
And we can forget about clk_round_rate. Without my patch, this would behave oddly also:
rate = clk_get_rate(clk); clk_set_rate(clk, rate);
The end result could be something else than 'rate'.
I agree that it's a bit odd, but I think it has to be like this. Consider that you request a rate of 100Hz, but the clock can only produce 99.5Hz, so due to rounding clk_round_rate() returns 99Hz. Now when you request 99Hz from clk_set_rate() the 99.5Hz value can't be used because it's too high.
Sascha
On 13/02/15 20:57, Sascha Hauer wrote:
On Fri, Feb 13, 2015 at 04:35:36PM +0200, Tomi Valkeinen wrote:
On 12/02/15 15:41, Sascha Hauer wrote:
Tomis patch is based on the assumption that clk_set_rate(clk_round_rate(rate)) is equal to clk_round_rate(rate). So when this assumption is wrong then it should simply be reverted.
When is it not equal?
I agree that doing clk_set_rate(clk, clk_round_rate(clk, rate)) is pointless, but shouldn't it still work?
And we can forget about clk_round_rate. Without my patch, this would behave oddly also:
rate = clk_get_rate(clk); clk_set_rate(clk, rate);
The end result could be something else than 'rate'.
I agree that it's a bit odd, but I think it has to be like this. Consider that you request a rate of 100Hz, but the clock can only produce 99.5Hz, so due to rounding clk_round_rate() returns 99Hz. Now when you request 99Hz from clk_set_rate() the 99.5Hz value can't be used because it's too high.
Would that problem better be fixed by changing the clock driver so that when asked for 99Hz, it would look for rates less than 100Hz?
I think the old behavior was so odd that I would call it broken, so I hope the current problems can be fixed via some other ways than breaking it again.
Tomi
On Mon, Feb 16, 2015 at 01:18:13PM +0200, Tomi Valkeinen wrote:
On 13/02/15 20:57, Sascha Hauer wrote:
On Fri, Feb 13, 2015 at 04:35:36PM +0200, Tomi Valkeinen wrote:
On 12/02/15 15:41, Sascha Hauer wrote:
Tomis patch is based on the assumption that clk_set_rate(clk_round_rate(rate)) is equal to clk_round_rate(rate). So when this assumption is wrong then it should simply be reverted.
When is it not equal?
I agree that doing clk_set_rate(clk, clk_round_rate(clk, rate)) is pointless, but shouldn't it still work?
And we can forget about clk_round_rate. Without my patch, this would behave oddly also:
rate = clk_get_rate(clk); clk_set_rate(clk, rate);
The end result could be something else than 'rate'.
I agree that it's a bit odd, but I think it has to be like this. Consider that you request a rate of 100Hz, but the clock can only produce 99.5Hz, so due to rounding clk_round_rate() returns 99Hz. Now when you request 99Hz from clk_set_rate() the 99.5Hz value can't be used because it's too high.
Would that problem better be fixed by changing the clock driver so that when asked for 99Hz, it would look for rates less than 100Hz?
I think the old behavior was so odd that I would call it broken, so I hope the current problems can be fixed via some other ways than breaking it again.
I gave it a try, but so far I have no idea how to implement the divider correctly and bullet proof.
What I have so far is a test which creates some cascaded dividers and sets rates on them. The test iterates over the frequency range and a) calls clk_round_rate with the iterator, b) sets the clk to the iterator, c) sets the clk to the rounded rate.
Be prepared for surprises and try to fix the results... I failed for now and wonder if the approach to the divider is wrong.
Sascha
From 58a46b16d4b67d5cd7df4af6deb5b96e19afe230 Mon Sep 17 00:00:00 2001
From: Sascha Hauer s.hauer@pengutronix.de Date: Tue, 17 Feb 2015 11:24:10 +0100 Subject: [PATCH] clk: clk-divider: Add a simple test for dividers
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- drivers/clk/clk-divider.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+)
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index c0a842b..cd66625 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -463,3 +463,89 @@ struct clk *clk_register_divider_table(struct device *dev, const char *name, width, clk_divider_flags, table, lock); } EXPORT_SYMBOL_GPL(clk_register_divider_table); + +/* + * Simple test of dividers. Try to set rates between 1 and 10000Hz and + * - get output of clk_round_rate() + * - set the current target rate, get the rate + * - set the rate to the rounded rate, get the rate + * + * Whenever a value changed print the results + */ +static void clktest_one(struct clk *clk) +{ + int i, ret; + unsigned long roundsetrate, last_roundsetrate = 0; + unsigned long roundrate, last_roundrate = 0; + unsigned long setrate, last_setrate = 0; + + for (i = 1; i < 10000; i++) { + roundrate = clk_round_rate(clk, i); + + clk_set_rate(clk, i); + setrate = clk_get_rate(clk); + + ret = clk_set_rate(clk, roundrate); + if (ret) { + printk("clkdivtest: Failed to set rate: %d\n", ret); + return; + } + + roundsetrate = clk_get_rate(clk); + + if (last_roundsetrate != roundsetrate || + last_roundrate != roundrate || + last_setrate != setrate) + printk("target rate: %4d rounded: %4ld set: %4ld round/set: %4ld\n", + i, roundrate, setrate, roundsetrate); + + last_roundsetrate = roundsetrate; + last_roundrate = roundrate; + last_setrate = setrate; + } +} + +static int clktest(void) +{ + u32 reg_div1 = 0; + u32 reg_div2 = 0; + u32 reg_div3 = 0; + struct clk *clktest_base = ERR_PTR(-ENODEV); + struct clk *clktest_div1 = ERR_PTR(-ENODEV); + struct clk *clktest_div2 = ERR_PTR(-ENODEV); + struct clk *clktest_div3 = ERR_PTR(-ENODEV); + + clktest_base = clk_register_fixed_rate(NULL, "clktest_base", NULL, 0, 10000); + clktest_div1 = clk_register_divider(NULL, "clktest_div1", "clktest_base", + 0, ®_div1, 0, 4, 0, NULL); + clktest_div2 = clk_register_divider(NULL, "clktest_div2", "clktest_div1", + CLK_SET_RATE_PARENT, ®_div2, 0, 4, 0, NULL); + clktest_div3 = clk_register_divider(NULL, "clktest_div3", "clktest_div2", + CLK_SET_RATE_PARENT, ®_div3, 0, 4, 0, NULL); + + if (IS_ERR(clktest_base) || IS_ERR(clktest_div1) || + IS_ERR(clktest_div2) || IS_ERR(clktest_div3)) { + printk("clkdivtest: Failed to register clocks\n"); + goto err_out; + } + + printk("------------------ Single divider, fin=10000Hz ------------------\n"); + clktest_one(clktest_div1); + printk("--------------- two cascaded dividers, fin=10000Hz --------------\n"); + clktest_one(clktest_div2); + printk("-------------- three cascaded dividers, fin=10000Hz -------------\n"); + clktest_one(clktest_div3); + +err_out: + if (!IS_ERR(clktest_base)) + clk_unregister(clktest_base); + if (!IS_ERR(clktest_div1)) + clk_unregister(clktest_div1); + if (!IS_ERR(clktest_div2)) + clk_unregister(clktest_div2); + if (!IS_ERR(clktest_div3)) + clk_unregister(clktest_div3); + + return 0; +} +late_initcall(clktest);
On Fri, Feb 13, 2015 at 07:57:13PM +0100, Sascha Hauer wrote:
I agree that it's a bit odd, but I think it has to be like this. Consider that you request a rate of 100Hz, but the clock can only produce 99.5Hz, so due to rounding clk_round_rate() returns 99Hz. Now when you request 99Hz from clk_set_rate() the 99.5Hz value can't be used because it's too high.
Math rounding rules normally state that anything of .5 and greater should be rounded up, not rounded down. So, for 99.5Hz, you really ought to be returning 100Hz, not 99Hz.
However, you do have a point for 99.4Hz, which would be returned as 99Hz, and when set, it would result in something which isn't 99.4Hz.
Quoting Russell King - ARM Linux (2015-02-16 03:27:24)
On Fri, Feb 13, 2015 at 07:57:13PM +0100, Sascha Hauer wrote:
I agree that it's a bit odd, but I think it has to be like this. Consider that you request a rate of 100Hz, but the clock can only produce 99.5Hz, so due to rounding clk_round_rate() returns 99Hz. Now when you request 99Hz from clk_set_rate() the 99.5Hz value can't be used because it's too high.
Math rounding rules normally state that anything of .5 and greater should be rounded up, not rounded down. So, for 99.5Hz, you really ought to be returning 100Hz, not 99Hz.
However, you do have a point for 99.4Hz, which would be returned as 99Hz, and when set, it would result in something which isn't 99.4Hz.
More practically, this again raises the issue of whether or not unsigned long rate should be in millihertz or something other than hertz.
And then that question again raises the issue of making rate 64-bit...
Regards, Mike
-- FTTC broadband for 0.8mile line: currently at 10.5Mbps down 400kbps up according to speedtest.net.
On Fri, Feb 20, 2015 at 11:13:06AM -0800, Mike Turquette wrote:
Quoting Russell King - ARM Linux (2015-02-16 03:27:24)
On Fri, Feb 13, 2015 at 07:57:13PM +0100, Sascha Hauer wrote:
I agree that it's a bit odd, but I think it has to be like this. Consider that you request a rate of 100Hz, but the clock can only produce 99.5Hz, so due to rounding clk_round_rate() returns 99Hz. Now when you request 99Hz from clk_set_rate() the 99.5Hz value can't be used because it's too high.
Math rounding rules normally state that anything of .5 and greater should be rounded up, not rounded down. So, for 99.5Hz, you really ought to be returning 100Hz, not 99Hz.
However, you do have a point for 99.4Hz, which would be returned as 99Hz, and when set, it would result in something which isn't 99.4Hz.
More practically, this again raises the issue of whether or not unsigned long rate should be in millihertz or something other than hertz.
You still get the same issue if it's millihertz. Take 10 2/3rds Hz. Is 10.666 Hz less than 10 2/3rds Hz?
Quoting Russell King - ARM Linux (2015-02-20 11:20:43)
On Fri, Feb 20, 2015 at 11:13:06AM -0800, Mike Turquette wrote:
Quoting Russell King - ARM Linux (2015-02-16 03:27:24)
On Fri, Feb 13, 2015 at 07:57:13PM +0100, Sascha Hauer wrote:
I agree that it's a bit odd, but I think it has to be like this. Consider that you request a rate of 100Hz, but the clock can only produce 99.5Hz, so due to rounding clk_round_rate() returns 99Hz. Now when you request 99Hz from clk_set_rate() the 99.5Hz value can't be used because it's too high.
Math rounding rules normally state that anything of .5 and greater should be rounded up, not rounded down. So, for 99.5Hz, you really ought to be returning 100Hz, not 99Hz.
However, you do have a point for 99.4Hz, which would be returned as 99Hz, and when set, it would result in something which isn't 99.4Hz.
More practically, this again raises the issue of whether or not unsigned long rate should be in millihertz or something other than hertz.
You still get the same issue if it's millihertz. Take 10 2/3rds Hz. Is 10.666 Hz less than 10 2/3rds Hz?
Millihertz won't solve rounding problems, I agree. But we currently do not support any fractional rates and we should.
Regards, Mike
-- FTTC broadband for 0.8mile line: currently at 10.5Mbps down 400kbps up according to speedtest.net.
Hello,
On Thu, Feb 12, 2015 at 01:24:05PM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 06:39:45PM +0800, Liu Ying wrote:
On Thu, Feb 12, 2015 at 10:33:56AM +0100, Sascha Hauer wrote:
On Thu, Feb 12, 2015 at 02:01:24PM +0800, Liu Ying wrote:
If no best divider is normally found, we will try to use the maximum divider. We should not set the parent clock rate to be 1Hz by force for being rounded. Instead, we should take the maximum divider as a base and calculate a correct parent clock rate for being rounded.
Please add an explanation why you think the current code is wrong and what this actually fixes, maybe an example?
The MIPI DSI panel's pixel clock rate is 26.4MHz and it's derived from PLL5 on the MX6DL SabreSD board.
These are the clock tree summaries with or without the patch applied:
- With the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 844800048 0 0 pll5_bypass 1 1 844800048 0 0 pll5_video 1 1 844800048 0 0 pll5_post_div 1 1 211200012 0 0 pll5_video_div 1 1 211200012 0 0 ipu1_di0_pre_sel 1 1 211200012 0 0 ipu1_di0_pre 1 1 26400002 0 0 ipu1_di0_sel 1 1 26400002 0 0 ipu1_di0 1 1 26400002 0 0
- Without the patch applied:
pll5_bypass_src 1 1 24000000 0 0 pll5 1 1 648000000 0 0 pll5_bypass 1 1 648000000 0 0 pll5_video 1 1 648000000 0 0 pll5_post_div 1 1 162000000 0 0 pll5_video_div 1 1 40500000 0 0 ipu1_di0_pre_sel 1 1 40500000 0 0 ipu1_di0_pre 1 1 20250000 0 0 ipu1_di0_sel 1 1 20250000 0 0 ipu1_di0 1 1 20250000 0 0
This seems to be broken since:
| commit b11d282dbea27db1788893115dfca8a7856bf205 | Author: Tomi Valkeinen tomi.valkeinen@ti.com | Date: Thu Feb 13 12:03:59 2014 +0200 | | clk: divider: fix rate calculation for fractional rates
This patch fixed a case when clk_set_rate(clk_round_rate(rate)) resulted in a lower frequency than clk_round_rate(rate) returned.
Since then the MULT_ROUND_UP in clk_divider_bestdiv() is inconsistent to the rest of the divider. Maybe this should be a simple rate * i now, but I'm unsure what side effects this has.
I think this is the right fix that I found, too, while fixing all problems that your test code with the three dividers shows (at least the subset of all problem I was able to identify).
I think your patch only fixes the behaviour in your case by accident, it's not a correct fix for this issue.
Right.
I will follow up with a patch (and two more that fix different things).
Best regards Uwe
This patch adds a macro to define the GPR3 MIPI muxing control register field shift bits.
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v4->v5: * None.
v3->v4: * None.
v2->v3: * None.
v1->v2: * None.
include/linux/mfd/syscon/imx6q-iomuxc-gpr.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h b/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h index c877cad..d16f4c8 100644 --- a/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h +++ b/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h @@ -207,6 +207,7 @@ #define IMX6Q_GPR3_LVDS0_MUX_CTL_IPU1_DI1 (0x1 << 6) #define IMX6Q_GPR3_LVDS0_MUX_CTL_IPU2_DI0 (0x2 << 6) #define IMX6Q_GPR3_LVDS0_MUX_CTL_IPU2_DI1 (0x3 << 6) +#define IMX6Q_GPR3_MIPI_MUX_CTL_SHIFT 4 #define IMX6Q_GPR3_MIPI_MUX_CTL_MASK (0x3 << 4) #define IMX6Q_GPR3_MIPI_MUX_CTL_IPU1_DI0 (0x0 << 4) #define IMX6Q_GPR3_MIPI_MUX_CTL_IPU1_DI1 (0x1 << 4)
This patch supports the video_27m clock which is a fixed factor clock of the pll3_pfd1_540m clock.
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v4->v5: * None.
v3->v4: * None.
v2->v3: * None.
v1->v2: * None.
arch/arm/mach-imx/clk-imx6q.c | 1 + include/dt-bindings/clock/imx6qdl-clock.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/arm/mach-imx/clk-imx6q.c b/arch/arm/mach-imx/clk-imx6q.c index 2daef61..2b7beb8 100644 --- a/arch/arm/mach-imx/clk-imx6q.c +++ b/arch/arm/mach-imx/clk-imx6q.c @@ -246,6 +246,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_PLL3_60M] = imx_clk_fixed_factor("pll3_60m", "pll3_usb_otg", 1, 8); clk[IMX6QDL_CLK_TWD] = imx_clk_fixed_factor("twd", "arm", 1, 2); clk[IMX6QDL_CLK_GPT_3M] = imx_clk_fixed_factor("gpt_3m", "osc", 1, 8); + clk[IMX6QDL_CLK_VIDEO_27M] = imx_clk_fixed_factor("video_27m", "pll3_pfd1_540m", 1, 20); if (cpu_is_imx6dl()) { clk[IMX6QDL_CLK_GPU2D_AXI] = imx_clk_fixed_factor("gpu2d_axi", "mmdc_ch0_axi_podf", 1, 1); clk[IMX6QDL_CLK_GPU3D_AXI] = imx_clk_fixed_factor("gpu3d_axi", "mmdc_ch0_axi_podf", 1, 1); diff --git a/include/dt-bindings/clock/imx6qdl-clock.h b/include/dt-bindings/clock/imx6qdl-clock.h index b690cdb..25625bf 100644 --- a/include/dt-bindings/clock/imx6qdl-clock.h +++ b/include/dt-bindings/clock/imx6qdl-clock.h @@ -248,6 +248,7 @@ #define IMX6QDL_PLL6_BYPASS 235 #define IMX6QDL_PLL7_BYPASS 236 #define IMX6QDL_CLK_GPT_3M 237 -#define IMX6QDL_CLK_END 238 +#define IMX6QDL_CLK_VIDEO_27M 238 +#define IMX6QDL_CLK_END 239
#endif /* __DT_BINDINGS_CLOCK_IMX6QDL_H */
According to the table 33-1 in the i.MX6Q reference manual, the hdmi_isfr clock's parent should be the video_27m clock. The i.MX6DL reference manual has the same statement. This patch changes the hdmi_isfr clock's parent from the pll3_pfd1_540m clock to the video_27m clock.
Suggested-by: Philipp Zabel p.zabel@pengutronix.de Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v4->v5: * None.
v3->v4: * None.
v2->v3: * Newly introduced in v3.
arch/arm/mach-imx/clk-imx6q.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm/mach-imx/clk-imx6q.c b/arch/arm/mach-imx/clk-imx6q.c index 2b7beb8..0dbc79a 100644 --- a/arch/arm/mach-imx/clk-imx6q.c +++ b/arch/arm/mach-imx/clk-imx6q.c @@ -401,7 +401,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_GPU2D_CORE] = imx_clk_gate2("gpu2d_core", "gpu2d_core_podf", base + 0x6c, 24); clk[IMX6QDL_CLK_GPU3D_CORE] = imx_clk_gate2("gpu3d_core", "gpu3d_core_podf", base + 0x6c, 26); clk[IMX6QDL_CLK_HDMI_IAHB] = imx_clk_gate2("hdmi_iahb", "ahb", base + 0x70, 0); - clk[IMX6QDL_CLK_HDMI_ISFR] = imx_clk_gate2("hdmi_isfr", "pll3_pfd1_540m", base + 0x70, 4); + clk[IMX6QDL_CLK_HDMI_ISFR] = imx_clk_gate2("hdmi_isfr", "video_27m", base + 0x70, 4); clk[IMX6QDL_CLK_I2C1] = imx_clk_gate2("i2c1", "ipg_per", base + 0x70, 6); clk[IMX6QDL_CLK_I2C2] = imx_clk_gate2("i2c2", "ipg_per", base + 0x70, 8); clk[IMX6QDL_CLK_I2C3] = imx_clk_gate2("i2c3", "ipg_per", base + 0x70, 10);
The CG8 field of the CCM CCGR3 register is named as 'mipi_core_cfg' clock, according to the i.MX6q/sdl reference manuals. This clock is actually the gate for several clocks, including the hsi_tx_sel clock's output and the video_27m clock's output. So, this patch changes the hsi_tx clock to be a shared clock gate.
Suggested-by: Philipp Zabel p.zabel@pengutronix.de Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v4->v5: * None.
v3->v4: * None.
v2->v3: * Newly introduced in v3.
arch/arm/mach-imx/clk-imx6q.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/arch/arm/mach-imx/clk-imx6q.c b/arch/arm/mach-imx/clk-imx6q.c index 0dbc79a..049e922 100644 --- a/arch/arm/mach-imx/clk-imx6q.c +++ b/arch/arm/mach-imx/clk-imx6q.c @@ -119,6 +119,7 @@ static unsigned int share_count_asrc; static unsigned int share_count_ssi1; static unsigned int share_count_ssi2; static unsigned int share_count_ssi3; +static unsigned int share_count_mipi_core_cfg;
static void __init imx6q_clocks_init(struct device_node *ccm_node) { @@ -416,7 +417,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_LDB_DI0] = imx_clk_gate2("ldb_di0", "ldb_di0_podf", base + 0x74, 12); clk[IMX6QDL_CLK_LDB_DI1] = imx_clk_gate2("ldb_di1", "ldb_di1_podf", base + 0x74, 14); clk[IMX6QDL_CLK_IPU2_DI1] = imx_clk_gate2("ipu2_di1", "ipu2_di1_sel", base + 0x74, 10); - clk[IMX6QDL_CLK_HSI_TX] = imx_clk_gate2("hsi_tx", "hsi_tx_podf", base + 0x74, 16); + clk[IMX6QDL_CLK_HSI_TX] = imx_clk_gate2_shared("hsi_tx", "hsi_tx_podf", base + 0x74, 16, &share_count_mipi_core_cfg); if (cpu_is_imx6dl()) /* * The multiplexer and divider of the imx6q clock gpu2d get
The CG8 field of the CCM CCGR3 register is named as 'mipi_core_cfg' clock, according to the i.MX6q/sdl reference manuals. This clock is actually the gate for several clocks, including the hsi_tx_sel clock's output and the video_27m clock's output. The MIPI DSI host controller embedded in the i.MX6q/sdl SoCs uses the video_27m clock to generate PLL reference clock and MIPI core configuration clock. In order to gate/ungate the two MIPI DSI host controller relevant clocks, this patch adds the mipi_core_cfg clock as a shared clock gate.
Suggested-by: Philipp Zabel p.zabel@pengutronix.de Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * Add two spaces in the clock driver to eliminate two errors reported by the checkpatch.pl script.
v4->v5: * None.
v3->v4: * None.
v2->v3: * Newly introduced in v3.
arch/arm/mach-imx/clk-imx6q.c | 1 + include/dt-bindings/clock/imx6qdl-clock.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/arm/mach-imx/clk-imx6q.c b/arch/arm/mach-imx/clk-imx6q.c index 049e922..cbdbe2a 100644 --- a/arch/arm/mach-imx/clk-imx6q.c +++ b/arch/arm/mach-imx/clk-imx6q.c @@ -418,6 +418,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_LDB_DI1] = imx_clk_gate2("ldb_di1", "ldb_di1_podf", base + 0x74, 14); clk[IMX6QDL_CLK_IPU2_DI1] = imx_clk_gate2("ipu2_di1", "ipu2_di1_sel", base + 0x74, 10); clk[IMX6QDL_CLK_HSI_TX] = imx_clk_gate2_shared("hsi_tx", "hsi_tx_podf", base + 0x74, 16, &share_count_mipi_core_cfg); + clk[IMX6QDL_CLK_MIPI_CORE_CFG] = imx_clk_gate2_shared("mipi_core_cfg", "video_27m", base + 0x74, 16, &share_count_mipi_core_cfg); if (cpu_is_imx6dl()) /* * The multiplexer and divider of the imx6q clock gpu2d get diff --git a/include/dt-bindings/clock/imx6qdl-clock.h b/include/dt-bindings/clock/imx6qdl-clock.h index 25625bf..dbc828c 100644 --- a/include/dt-bindings/clock/imx6qdl-clock.h +++ b/include/dt-bindings/clock/imx6qdl-clock.h @@ -249,6 +249,7 @@ #define IMX6QDL_PLL7_BYPASS 236 #define IMX6QDL_CLK_GPT_3M 237 #define IMX6QDL_CLK_VIDEO_27M 238 -#define IMX6QDL_CLK_END 239 +#define IMX6QDL_CLK_MIPI_CORE_CFG 239 +#define IMX6QDL_CLK_END 240
#endif /* __DT_BINDINGS_CLOCK_IMX6QDL_H */
The CG8 field of the CCM CCGR3 register is the 'mipi_core_cfg' gate clock, according to the i.MX6q/sdl reference manuals. This clock is actually the gate for several clocks, including the ipg clock's output. The MIPI DSI host controller embedded in the i.MX6q/sdl SoCs takes the ipg clock as the pclk - the APB clock signal . In order to gate/ungate the ipg clock, this patch adds a new shared clock gate named as "mipi_ipg".
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Newly introduced in v9.
arch/arm/mach-imx/clk-imx6q.c | 1 + include/dt-bindings/clock/imx6qdl-clock.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/arm/mach-imx/clk-imx6q.c b/arch/arm/mach-imx/clk-imx6q.c index cbdbe2a..909828d 100644 --- a/arch/arm/mach-imx/clk-imx6q.c +++ b/arch/arm/mach-imx/clk-imx6q.c @@ -419,6 +419,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_IPU2_DI1] = imx_clk_gate2("ipu2_di1", "ipu2_di1_sel", base + 0x74, 10); clk[IMX6QDL_CLK_HSI_TX] = imx_clk_gate2_shared("hsi_tx", "hsi_tx_podf", base + 0x74, 16, &share_count_mipi_core_cfg); clk[IMX6QDL_CLK_MIPI_CORE_CFG] = imx_clk_gate2_shared("mipi_core_cfg", "video_27m", base + 0x74, 16, &share_count_mipi_core_cfg); + clk[IMX6QDL_CLK_MIPI_IPG] = imx_clk_gate2_shared("mipi_ipg", "ipg", base + 0x74, 16, &share_count_mipi_core_cfg); if (cpu_is_imx6dl()) /* * The multiplexer and divider of the imx6q clock gpu2d get diff --git a/include/dt-bindings/clock/imx6qdl-clock.h b/include/dt-bindings/clock/imx6qdl-clock.h index dbc828c..8780868 100644 --- a/include/dt-bindings/clock/imx6qdl-clock.h +++ b/include/dt-bindings/clock/imx6qdl-clock.h @@ -250,6 +250,7 @@ #define IMX6QDL_CLK_GPT_3M 237 #define IMX6QDL_CLK_VIDEO_27M 238 #define IMX6QDL_CLK_MIPI_CORE_CFG 239 -#define IMX6QDL_CLK_END 240 +#define IMX6QDL_CLK_MIPI_IPG 240 +#define IMX6QDL_CLK_END 241
#endif /* __DT_BINDINGS_CLOCK_IMX6QDL_H */
The MIPI DSI node contains some ports which represent possible DRM CRTCs it can connect with. Each port has a 'reg' property embedded. This property will be wrongly interpretted by the MIPI DSI bus driver, because the driver will take each subnode which contains a 'reg' property as a DSI peripheral device. This patch moves the existing MIPI DSI ports into a new 'ports' node so that the MIPI DSI bus driver may distinguish its DSI peripheral device(s) from the existing ports.
Acked-by: Philipp Zabel p.zabel@pengutronix.de Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v5->v6: * None.
v4->v5: * None.
v3->v4: * None.
v2->v3: * Add Philipp's Ack.
v1->v2: * Newly added, as suggested by Thierry Reding.
arch/arm/boot/dts/imx6q.dtsi | 20 +++++++++++--------- arch/arm/boot/dts/imx6qdl.dtsi | 23 ++++++++++++++--------- 2 files changed, 25 insertions(+), 18 deletions(-)
diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi index 85f72e6..e152e6e 100644 --- a/arch/arm/boot/dts/imx6q.dtsi +++ b/arch/arm/boot/dts/imx6q.dtsi @@ -292,19 +292,21 @@ };
&mipi_dsi { - port@2 { - reg = <2>; + ports { + port@2 { + reg = <2>;
- mipi_mux_2: endpoint { - remote-endpoint = <&ipu2_di0_mipi>; + mipi_mux_2: endpoint { + remote-endpoint = <&ipu2_di0_mipi>; + }; }; - };
- port@3 { - reg = <3>; + port@3 { + reg = <3>;
- mipi_mux_3: endpoint { - remote-endpoint = <&ipu2_di1_mipi>; + mipi_mux_3: endpoint { + remote-endpoint = <&ipu2_di1_mipi>; + }; }; }; }; diff --git a/arch/arm/boot/dts/imx6qdl.dtsi b/arch/arm/boot/dts/imx6qdl.dtsi index 2109d07..55aced8 100644 --- a/arch/arm/boot/dts/imx6qdl.dtsi +++ b/arch/arm/boot/dts/imx6qdl.dtsi @@ -1023,19 +1023,24 @@ reg = <0x021e0000 0x4000>; status = "disabled";
- port@0 { - reg = <0>; + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>;
- mipi_mux_0: endpoint { - remote-endpoint = <&ipu1_di0_mipi>; + mipi_mux_0: endpoint { + remote-endpoint = <&ipu1_di0_mipi>; + }; }; - };
- port@1 { - reg = <1>; + port@1 { + reg = <1>;
- mipi_mux_1: endpoint { - remote-endpoint = <&ipu1_di1_mipi>; + mipi_mux_1: endpoint { + remote-endpoint = <&ipu1_di1_mipi>; + }; }; }; };
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * Address the over 80 characters in one line warning reported by the checkpatch.pl script.
v4->v5: * None.
v3->v4: * None.
v2->v3: * None.
v1->v2: * Thierry Reding suggested that the mipi_dsi_pixel_format_to_bpp() function could be placed at the common DRM MIPI DSI driver. This patch is newly added.
include/drm/drm_mipi_dsi.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index f1d8d0d..3662021 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -163,6 +163,20 @@ static inline struct mipi_dsi_device *to_mipi_dsi_device(struct device *dev) return container_of(dev, struct mipi_dsi_device, dev); }
+static inline int mipi_dsi_pixel_format_to_bpp(enum mipi_dsi_pixel_format fmt) +{ + switch (fmt) { + case MIPI_DSI_FMT_RGB888: + case MIPI_DSI_FMT_RGB666: + return 24; + case MIPI_DSI_FMT_RGB666_PACKED: + return 18; + case MIPI_DSI_FMT_RGB565: + return 16; + } + return -EINVAL; +} + struct mipi_dsi_device *of_find_mipi_dsi_device_by_node(struct device_node *np); int mipi_dsi_attach(struct mipi_dsi_device *dsi); int mipi_dsi_detach(struct mipi_dsi_device *dsi);
On Thu, Feb 12, 2015 at 02:01:32PM +0800, Liu Ying wrote:
Signed-off-by: Liu Ying Ying.Liu@freescale.com
v8->v9:
- Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8:
- None.
v6->v7:
- None.
v5->v6:
- Address the over 80 characters in one line warning reported by the checkpatch.pl script.
v4->v5:
- None.
v3->v4:
- None.
v2->v3:
- None.
v1->v2:
- Thierry Reding suggested that the mipi_dsi_pixel_format_to_bpp() function could be placed at the common DRM MIPI DSI driver. This patch is newly added.
include/drm/drm_mipi_dsi.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index f1d8d0d..3662021 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -163,6 +163,20 @@ static inline struct mipi_dsi_device *to_mipi_dsi_device(struct device *dev) return container_of(dev, struct mipi_dsi_device, dev); }
+static inline int mipi_dsi_pixel_format_to_bpp(enum mipi_dsi_pixel_format fmt)
Kerneldoc seems to be missing for this one. -Daniel
+{
- switch (fmt) {
- case MIPI_DSI_FMT_RGB888:
- case MIPI_DSI_FMT_RGB666:
return 24;
- case MIPI_DSI_FMT_RGB666_PACKED:
return 18;
- case MIPI_DSI_FMT_RGB565:
return 16;
- }
- return -EINVAL;
+}
struct mipi_dsi_device *of_find_mipi_dsi_device_by_node(struct device_node *np); int mipi_dsi_attach(struct mipi_dsi_device *dsi); int mipi_dsi_detach(struct mipi_dsi_device *dsi); -- 2.1.0
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, Feb 12, 2015 at 10:26:42AM +0100, Daniel Vetter wrote:
On Thu, Feb 12, 2015 at 02:01:32PM +0800, Liu Ying wrote:
Signed-off-by: Liu Ying Ying.Liu@freescale.com
v8->v9:
- Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8:
- None.
v6->v7:
- None.
v5->v6:
- Address the over 80 characters in one line warning reported by the checkpatch.pl script.
v4->v5:
- None.
v3->v4:
- None.
v2->v3:
- None.
v1->v2:
- Thierry Reding suggested that the mipi_dsi_pixel_format_to_bpp() function could be placed at the common DRM MIPI DSI driver. This patch is newly added.
include/drm/drm_mipi_dsi.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index f1d8d0d..3662021 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -163,6 +163,20 @@ static inline struct mipi_dsi_device *to_mipi_dsi_device(struct device *dev) return container_of(dev, struct mipi_dsi_device, dev); }
+static inline int mipi_dsi_pixel_format_to_bpp(enum mipi_dsi_pixel_format fmt)
Kerneldoc seems to be missing for this one.
I'll add it. Thanks for pointing out this.
Regards, Liu Ying
-Daniel
+{
- switch (fmt) {
- case MIPI_DSI_FMT_RGB888:
- case MIPI_DSI_FMT_RGB666:
return 24;
- case MIPI_DSI_FMT_RGB666_PACKED:
return 18;
- case MIPI_DSI_FMT_RGB565:
return 16;
- }
- return -EINVAL;
+}
struct mipi_dsi_device *of_find_mipi_dsi_device_by_node(struct device_node *np); int mipi_dsi_attach(struct mipi_dsi_device *dsi); int mipi_dsi_detach(struct mipi_dsi_device *dsi); -- 2.1.0
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
-- Daniel Vetter Software Engineer, Intel Corporation +41 (0) 79 365 57 48 - http://blog.ffwll.ch
Hi,
Am Donnerstag, den 12.02.2015, 14:01 +0800 schrieb Liu Ying:
Signed-off-by: Liu Ying Ying.Liu@freescale.com
v8->v9:
- Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
I can't test this myself for lack of hardware, but I see no further issues with patches 09 - 13 except for the use of imx_drm_encoder_get_mux_id. I'll either rebase my patches that remove it or fix it up when applying.
Thierry, may I take these patches through imx-drm, or would you rather I waited for you to pick up the drm/dsi and drm/bridge patches?
regards Philipp
Hi Thierry,
2015-03-03 19:07 GMT+08:00 Philipp Zabel p.zabel@pengutronix.de:
Hi,
Am Donnerstag, den 12.02.2015, 14:01 +0800 schrieb Liu Ying:
Signed-off-by: Liu Ying Ying.Liu@freescale.com
v8->v9:
- Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
I can't test this myself for lack of hardware, but I see no further issues with patches 09 - 13 except for the use of imx_drm_encoder_get_mux_id. I'll either rebase my patches that remove it or fix it up when applying.
Thierry, may I take these patches through imx-drm, or would you rather I waited for you to pick up the drm/dsi and drm/bridge patches?
Gentle ping. What's your opinion on the patches Philipp mentioned?
Regards, Liu Ying
regards Philipp
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, Feb 12, 2015 at 02:01:32PM +0800, Liu Ying wrote:
Signed-off-by: Liu Ying Ying.Liu@freescale.com
v8->v9:
- Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8:
- None.
v6->v7:
- None.
v5->v6:
- Address the over 80 characters in one line warning reported by the checkpatch.pl script.
v4->v5:
- None.
v3->v4:
- None.
v2->v3:
- None.
v1->v2:
- Thierry Reding suggested that the mipi_dsi_pixel_format_to_bpp() function could be placed at the common DRM MIPI DSI driver. This patch is newly added.
include/drm/drm_mipi_dsi.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
Acked-by: Thierry Reding treding@nvidia.com
This patch adds device tree bindings for Synopsys DesignWare MIPI DSI host controller DRM bridge driver.
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository. * To address Philipp's comment, mention that a common compatible string "snps,dw-mipi-dsi" should be appended for all SoCs. * To address Philipp's comment, add a new required clock pclk and clean up clock-names.
v7->v8: * None.
v6->v7: * None.
v5->v6: * Add the #address-cells and #size-cells properties in the example 'ports' node. * Remove the useless input-port properties from the example port@0 and port@1 nodes.
v4->v5: * None.
v3->v4: * Newly introduced in v4. This is separated from the relevant driver patch in v3 to address Stefan Wahren's comment.
.../devicetree/bindings/drm/bridge/dw_mipi_dsi.txt | 76 ++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/bridge/dw_mipi_dsi.txt
diff --git a/Documentation/devicetree/bindings/drm/bridge/dw_mipi_dsi.txt b/Documentation/devicetree/bindings/drm/bridge/dw_mipi_dsi.txt new file mode 100644 index 0000000..bb87466 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/bridge/dw_mipi_dsi.txt @@ -0,0 +1,76 @@ +Device-Tree bindings for Synopsys DesignWare MIPI DSI host controller + +The controller is a digital core that implements all protocol functions +defined in the MIPI DSI specification, providing an interface between +the system and the MIPI DPHY, and allowing communication with a MIPI DSI +compliant display. + +Required properties: + - #address-cells: Should be <1>. + - #size-cells: Should be <0>. + - compatible: The first compatible string should be "fsl,imx6q-mipi-dsi" + for i.MX6q/sdl SoCs. For other SoCs, please refer to their specific + device tree binding documentations. A common compatible string + "snps,dw-mipi-dsi" should be appended for all SoCs. + - reg: Represent the physical address range of the controller. + - interrupts: Represent the controller's interrupt to the CPU(s). + - clocks, clock-names: Phandles to the controller's pll reference + clock(ref), configuration clock(cfg) and APB clock(pclk), as + described in [1]. + +For more required properties, please refer to relevant device tree binding +documentations which describe the controller embedded in specific SoCs. + +Required sub-nodes: + - A node to represent a DSI peripheral as described in [2]. + +For more required sub-nodes, please refer to relevant device tree binding +documentations which describe the controller embedded in specific SoCs. + +[1] Documentation/devicetree/bindings/clock/clock-bindings.txt +[2] Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt + +example: + gpr: iomuxc-gpr@020e0000 { + /* ... */ + }; + + mipi_dsi: mipi@021e0000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "fsl,imx6q-mipi-dsi", "snps,dw-mipi-dsi"; + reg = <0x021e0000 0x4000>; + interrupts = <0 102 IRQ_TYPE_LEVEL_HIGH>; + gpr = <&gpr>; + clocks = <&clks IMX6QDL_CLK_MIPI_CORE_CFG>, + <&clks IMX6QDL_CLK_MIPI_CORE_CFG>, + <&clks IMX6QDL_CLK_MIPI_IPG>; + clock-names = "ref", "cfg", "pclk"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + mipi_mux_0: endpoint { + remote-endpoint = <&ipu1_di0_mipi>; + }; + }; + + port@1 { + reg = <1>; + + mipi_mux_1: endpoint { + remote-endpoint = <&ipu1_di1_mipi>; + }; + }; + }; + + panel { + compatible = "truly,tft480800-16-e-dsi"; + reg = <0>; + /* ... */ + }; + };
This patch adds Synopsys DesignWare MIPI DSI host controller driver support. Currently, the driver supports the burst with sync pulses mode only.
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository and adapt to bridge API change as the dw-hdmi driver did. * To address Philipp's comment, add a new required clock pclk and clean up clock-names. * Add driver copyright for 2015.
v7->v8: * Fix the driver's Kconfig so that we may pass the allmodconfig for ARM.
v6->v7: * None.
v5->v6: * Make the checkpatch.pl script be happier.
v4->v5: * Remove 'dsi->panel = NULL;' in dw_mipi_dsi_host_detach() to address Andrzej Hajda's comment.
v3->v4: * Move the relevant dt-bindings to a separate patch to address Stefan Wahren's comment.
v2->v3: * Newly introduced in v3 to address Andy Yan's comment. This is based on the i.MX MIPI DSI driver in v2. To make the Synopsys DesignWare MIPI DSI host controller driver less platform-dependant, this patch places it at the drm/bridge directory as a DRM bridge driver.
drivers/gpu/drm/bridge/Kconfig | 10 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/dw_mipi_dsi.c | 1006 ++++++++++++++++++++++++++++++++++ include/drm/bridge/dw_mipi_dsi.h | 27 + 4 files changed, 1044 insertions(+) create mode 100644 drivers/gpu/drm/bridge/dw_mipi_dsi.c create mode 100644 include/drm/bridge/dw_mipi_dsi.h
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index f38bbcd..de151f2 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -3,6 +3,16 @@ config DRM_DW_HDMI depends on DRM select DRM_KMS_HELPER
+config DRM_DW_MIPI_DSI + tristate "Synopsys DesignWare MIPI DSI host controller bridge" + depends on DRM + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + help + Choose this if you want to use the Synopsys DesignWare MIPI DSI host + controller bridge. + config DRM_PTN3460 tristate "PTN3460 DP/LVDS bridge" depends on DRM diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index d8a8cfd..5f8e9b3 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -2,3 +2,4 @@ ccflags-y := -Iinclude/drm
obj-$(CONFIG_DRM_PTN3460) += ptn3460.o obj-$(CONFIG_DRM_DW_HDMI) += dw_hdmi.o +obj-$(CONFIG_DRM_DW_MIPI_DSI) += dw_mipi_dsi.o diff --git a/drivers/gpu/drm/bridge/dw_mipi_dsi.c b/drivers/gpu/drm/bridge/dw_mipi_dsi.c new file mode 100644 index 0000000..0ff241e --- /dev/null +++ b/drivers/gpu/drm/bridge/dw_mipi_dsi.c @@ -0,0 +1,1006 @@ +/* + * Synopsys DesignWare(DW) MIPI DSI Host Controller + * + * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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/math64.h> +#include <linux/module.h> +#include <drm/bridge/dw_mipi_dsi.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <video/mipi_display.h> + +#define DSI_VERSION 0x00 + +#define DSI_PWR_UP 0x04 +#define RESET 0 +#define POWERUP BIT(0) + +#define DSI_CLKMGR_CFG 0x08 +#define TO_CLK_DIVIDSION(div) (((div) & 0xff) << 8) +#define TX_ESC_CLK_DIVIDSION(div) (((div) & 0xff) << 0) + +#define DSI_DPI_CFG 0x0c +#define EN18_LOOSELY BIT(10) +#define COLORM_ACTIVE_LOW BIT(9) +#define SHUTD_ACTIVE_LOW BIT(8) +#define HSYNC_ACTIVE_LOW BIT(7) +#define VSYNC_ACTIVE_LOW BIT(6) +#define DATAEN_ACTIVE_LOW BIT(5) +#define DPI_COLOR_CODING_16BIT_1 (0x0 << 2) +#define DPI_COLOR_CODING_16BIT_2 (0x1 << 2) +#define DPI_COLOR_CODING_16BIT_3 (0x2 << 2) +#define DPI_COLOR_CODING_18BIT_1 (0x3 << 2) +#define DPI_COLOR_CODING_18BIT_2 (0x4 << 2) +#define DPI_COLOR_CODING_24BIT (0x5 << 2) +#define DPI_VID(vid) (((vid) & 0x3) << 0) + +#define DSI_DBI_CFG 0x10 +#define DSI_DBIS_CMDSIZE 0x14 + +#define DSI_PCKHDL_CFG 0x18 +#define GEN_VID_RX(vid) (((vid) & 0x3) << 5) +#define EN_CRC_RX BIT(4) +#define EN_ECC_RX BIT(3) +#define EN_BTA BIT(2) +#define EN_EOTN_RX BIT(1) +#define EN_EOTP_TX BIT(0) + +#define DSI_VID_MODE_CFG 0x1c +#define FRAME_BTA_ACK BIT(11) +#define EN_NULL_PKT BIT(10) +#define EN_NULL_PKT_MASK BIT(10) +#define EN_MULTI_PKT BIT(9) +#define ENABLE_LOW_POWER (0x3f << 3) +#define ENABLE_LOW_POWER_MASK (0x3f << 3) +#define VID_MODE_TYPE_NONBURST_SYNC_PULSES (0x0 << 1) +#define VID_MODE_TYPE_NONBURST_SYNC_EVENTS (0x1 << 1) +#define VID_MODE_TYPE_BURST_SYNC_PULSES (0x3 << 1) +#define VID_MODE_TYPE_MASK (0x3 << 1) +#define ENABLE_VIDEO_MODE BIT(0) +#define DISABLE_VIDEO_MODE 0 +#define ENABLE_VIDEO_MODE_MASK BIT(0) + +#define DSI_VID_PKT_CFG 0x20 +#define NULL_PKT_SIZE(b) (((b) & 0x3f) << 21) +#define NUM_CHUNKS(n) (((n) & 0x3f) << 11) +#define VID_PKT_SIZE(p) (((p) & 0x7ff) << 0) +#define VID_PKT_MAX_SIZE 0x7ff + +#define DSI_CMD_MODE_CFG 0x24 +#define EN_TEAR_FX BIT(14) +#define EN_ACK_RQST BIT(13) +#define DCS_LW_TX_LP BIT(12) +#define GEN_LW_TX_LP BIT(11) +#define MAX_RD_PKT_SIZE_LP BIT(10) +#define DCS_SW_2P_TX_LP BIT(9) +#define DCS_SW_1P_TX_LP BIT(8) +#define DCS_SW_0P_TX_LP BIT(7) +#define GEN_SR_2P_TX_LP BIT(6) +#define GEN_SR_1P_TX_LP BIT(5) +#define GEN_SR_0P_TX_LP BIT(4) +#define GEN_SW_2P_TX_LP BIT(3) +#define GEN_SW_1P_TX_LP BIT(2) +#define GEN_SW_0P_TX_LP BIT(1) +#define ENABLE_CMD_MODE BIT(0) +#define DISABLE_CMD_MODE 0 +#define ENABLE_CMD_MODE_MASK BIT(0) +#define CMD_MODE_ALL_LP (DCS_LW_TX_LP | \ + GEN_LW_TX_LP | \ + MAX_RD_PKT_SIZE_LP | \ + DCS_SW_2P_TX_LP | \ + DCS_SW_1P_TX_LP | \ + DCS_SW_0P_TX_LP | \ + GEN_SR_2P_TX_LP | \ + GEN_SR_1P_TX_LP | \ + GEN_SR_0P_TX_LP | \ + GEN_SW_2P_TX_LP | \ + GEN_SW_1P_TX_LP | \ + GEN_SW_0P_TX_LP) + +#define DSI_TMR_LINE_CFG 0x28 +#define HLINE_TIME(lbcc) (((lbcc) & 0x3fff) << 18) +#define HBP_TIME(lbcc) (((lbcc) & 0x1ff) << 9) +#define HSA_TIME(lbcc) (((lbcc) & 0x1ff) << 0) + +#define DSI_VTIMING_CFG 0x2c +#define V_ACTIVE_LINES(line) (((line) & 0x7ff) << 16) +#define VFP_LINES(line) (((line) & 0x3f) << 10) +#define VBP_LINES(line) (((line) & 0x3f) << 4) +#define VSA_LINES(line) (((line) & 0xf) << 0) + +#define DSI_PHY_TMR_CFG 0x30 +#define PHY_HS2LP_TIME(lbcc) (((lbcc) & 0xff) << 20) +#define PHY_LP2HS_TIME(lbcc) (((lbcc) & 0xff) << 12) +#define BTA_TIME(lbcc) (((lbcc) & 0xfff) << 0) + +#define DSI_GEN_HDR 0x34 +#define GEN_HDATA(data) (((data) & 0xffff) << 8) +#define GEN_HDATA_MASK (0xffff << 8) +#define GEN_HTYPE(type) (((type) & 0xff) << 0) +#define GEN_HTYPE_MASK 0xff + +#define DSI_GEN_PLD_DATA 0x38 + +#define DSI_CMD_PKT_STATUS 0x3c +#define GEN_CMD_EMPTY BIT(0) +#define GEN_CMD_FULL BIT(1) +#define GEN_PLD_W_EMPTY BIT(2) +#define GEN_PLD_W_FULL BIT(3) +#define GEN_PLD_R_EMPTY BIT(4) +#define GEN_RD_CMD_BUSY BIT(6) + +#define DSI_TO_CNT_CFG 0x40 +#define DSI_ERROR_ST0 0x44 +#define DSI_ERROR_ST1 0x48 +#define DSI_ERROR_MSK0 0x4c +#define DSI_ERROR_MSK1 0x50 + +#define DSI_PHY_RSTZ 0x54 +#define PHY_DISABLECLK 0 +#define PHY_ENABLECLK BIT(2) +#define PHY_RSTZ 0 +#define PHY_UNRSTZ BIT(1) +#define PHY_SHUTDOWNZ 0 +#define PHY_UNSHUTDOWNZ BIT(0) + +#define DSI_PHY_IF_CFG 0x58 +#define N_LANES(n) ((((n) - 1) & 0x3) << 0) +#define PHY_STOP_WAIT_TIME(cycle) (((cycle) & 0x3ff) << 2) + +#define DSI_PHY_IF_CTRL 0x5c +#define PHY_IF_CTRL_RESET 0x0 +#define TX_REQ_CLK_HS BIT(0) + +#define DSI_PHY_STATUS 0x60 +#define LOCK BIT(0) +#define STOP_STATE_CLK_LANE BIT(2) + +#define DSI_PHY_TST_CTRL0 0x64 +#define PHY_TESTCLK BIT(1) +#define PHY_UNTESTCLK 0 +#define PHY_TESTCLR BIT(0) +#define PHY_UNTESTCLR 0 + +#define DSI_PHY_TST_CTRL1 0x68 +#define PHY_TESTEN BIT(16) +#define PHY_UNTESTEN 0 +#define PHY_TESTDOUT(n) (((n) & 0xff) << 8) +#define PHY_TESTDIN(n) (((n) & 0xff) << 0) + +#define PHY_STATUS_TIMEOUT 10 +#define CMD_PKT_STATUS_TIMEOUT 20 + +struct dw_mipi_dsi { + struct mipi_dsi_host dsi_host; + struct drm_connector connector; + struct drm_encoder *encoder; + struct drm_bridge *bridge; + struct drm_panel *panel; + struct device *dev; + + void __iomem *base; + + struct clk *pllref_clk; + struct clk *cfg_clk; + struct clk *pclk; + + unsigned int lane_mbps; /* per lane */ + u32 channel; + u32 lanes; + u32 format; + struct drm_display_mode *mode; + + const struct dw_mipi_dsi_plat_data *pdata; + + bool enabled; +}; + +enum { + STATUS_TO_CLEAR, + STATUS_TO_SET, +}; + +enum dw_mipi_dsi_mode { + DW_MIPI_DSI_CMD_MODE, + DW_MIPI_DSI_VID_MODE, +}; + +struct dphy_pll_testdin_map { + unsigned int max_mbps; + u8 testdin; +}; + +/* The table is based on 27MHz DPHY pll reference clock. */ +static const struct dphy_pll_testdin_map dptdin_map[] = { + {160, 0x04}, {180, 0x24}, {200, 0x44}, {210, 0x06}, + {240, 0x26}, {250, 0x46}, {270, 0x08}, {300, 0x28}, + {330, 0x48}, {360, 0x2a}, {400, 0x4a}, {450, 0x0c}, + {500, 0x2c}, {550, 0x0e}, {600, 0x2e}, {650, 0x10}, + {700, 0x30}, {750, 0x12}, {800, 0x32}, {850, 0x14}, + {900, 0x34}, {950, 0x54}, {1000, 0x74} +}; + +static inline struct dw_mipi_dsi *host_to_dsi(struct mipi_dsi_host *host) +{ + return container_of(host, struct dw_mipi_dsi, dsi_host); +} + +static inline struct dw_mipi_dsi *con_to_dsi(struct drm_connector *con) +{ + return container_of(con, struct dw_mipi_dsi, connector); +} + +int dw_mipi_dsi_get_encoder_pixel_format(struct drm_encoder *encoder) +{ + struct dw_mipi_dsi *dsi = encoder->bridge->driver_private; + + return dsi->format; +} +EXPORT_SYMBOL_GPL(dw_mipi_dsi_get_encoder_pixel_format); + +static int max_mbps_to_testdin(unsigned int max_mbps) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dptdin_map); i++) + if (dptdin_map[i].max_mbps == max_mbps) + return dptdin_map[i].testdin; + + return -EINVAL; +} + +static inline void dsi_write(struct dw_mipi_dsi *dsi, u32 reg, u32 val) +{ + writel(val, dsi->base + reg); +} + +static inline u32 dsi_read(struct dw_mipi_dsi *dsi, u32 reg) +{ + return readl(dsi->base + reg); +} + +static inline void dsi_modify(struct dw_mipi_dsi *dsi, u32 reg, + u32 mask, u32 val) +{ + u32 v; + + v = readl(dsi->base + reg); + v &= ~mask; + v |= val; + writel(v, dsi->base + reg); +} + +static int check_status(struct dw_mipi_dsi *dsi, u32 reg, u32 status, + unsigned int timeout, bool to_set) +{ + unsigned long expire; + bool out; + u32 val; + + expire = jiffies + msecs_to_jiffies(timeout); + for (;;) { + val = dsi_read(dsi, reg); + out = to_set ? ((val & status) == status) : !(val & status); + if (out) + break; + + if (time_after(jiffies, expire)) + return -ETIMEDOUT; + + cpu_relax(); + } + + return 0; +} + +/* + * The controller should generate 2 frames before + * preparing the peripheral. + */ +static void dw_mipi_dsi_wait_for_two_frames(struct dw_mipi_dsi *dsi) +{ + unsigned long expire; + int refresh, two_frames; + + refresh = drm_mode_vrefresh(dsi->mode); + two_frames = DIV_ROUND_UP(MSEC_PER_SEC, refresh) * 2; + + expire = jiffies + msecs_to_jiffies(two_frames); + while (time_before(jiffies, expire)) + cpu_relax(); +} + +static int dw_mipi_dsi_config_testdin(struct dw_mipi_dsi *dsi) +{ + int ret, testdin; + + testdin = max_mbps_to_testdin(dsi->lane_mbps); + if (testdin < 0) { + dev_err(dsi->dev, + "failed to get testdin for %dmbps lane clock\n", + dsi->lane_mbps); + return testdin; + } + + dsi_write(dsi, DSI_PHY_IF_CTRL, PHY_IF_CTRL_RESET); + dsi_write(dsi, DSI_PWR_UP, POWERUP); + + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR); + dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_TESTEN | PHY_TESTDOUT(0) | + PHY_TESTDIN(0x44)); + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR); + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR); + dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_UNTESTEN | PHY_TESTDOUT(0) | + PHY_TESTDIN(testdin)); + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR); + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR); + dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENABLECLK | PHY_UNRSTZ | + PHY_UNSHUTDOWNZ); + ret = check_status(dsi, DSI_PHY_STATUS, LOCK, + PHY_STATUS_TIMEOUT, STATUS_TO_SET); + if (ret < 0) { + dev_err(dsi->dev, "failed to wait for phy lock state\n"); + return ret; + } + ret = check_status(dsi, DSI_PHY_STATUS, STOP_STATE_CLK_LANE, + PHY_STATUS_TIMEOUT, STATUS_TO_SET); + if (ret < 0) { + dev_err(dsi->dev, + "failed to wait for phy clk lane stop state\n"); + return ret; + } + + return ret; +} + +static int dw_mipi_dsi_get_lane_bps(struct dw_mipi_dsi *dsi, + unsigned int *final_mbps) +{ + int bpp, i; + unsigned int target_mbps, mpclk; + unsigned long pllref; + + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + if (bpp < 0) { + dev_err(dsi->dev, "failed to get bpp for pixel format %d\n", + dsi->format); + return bpp; + } + + pllref = clk_get_rate(dsi->pllref_clk); + if (pllref != 27000000) + dev_warn(dsi->dev, "expect 27MHz DPHY pll reference clock\n"); + + mpclk = DIV_ROUND_UP(dsi->mode->clock, MSEC_PER_SEC); + if (mpclk) { + /* take 1/0.7 blanking overhead into consideration */ + target_mbps = (mpclk * (bpp / dsi->lanes) * 10) / 7; + } else { + dev_dbg(dsi->dev, "use default 1Gbps DPHY pll clock\n"); + target_mbps = 1000; + } + + dev_dbg(dsi->dev, "target DPHY pll clock frequency is %uMbps\n", + target_mbps); + + for (i = 0; i < ARRAY_SIZE(dptdin_map); i++) { + if (target_mbps < dptdin_map[i].max_mbps) { + *final_mbps = dptdin_map[i].max_mbps; + dev_dbg(dsi->dev, + "real DPHY pll clock frequency is %uMbps\n", + *final_mbps); + return 0; + } + } + + dev_err(dsi->dev, "DPHY clock frequency %uMbps is out of range\n", + target_mbps); + + return -EINVAL; +} + +static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct dw_mipi_dsi *dsi = host_to_dsi(host); + + if (device->lanes > dsi->pdata->max_data_lanes) { + dev_err(dsi->dev, "the number of data lanes(%d) is too many\n", + device->lanes); + return -EINVAL; + } + + if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) || + !(device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) { + dev_err(dsi->dev, "device mode is unsupported\n"); + return -EINVAL; + } + + if (device->format != MIPI_DSI_FMT_RGB888 && + device->format != MIPI_DSI_FMT_RGB565) { + dev_err(dsi->dev, "device pixel format is unsupported\n"); + return -EINVAL; + } + + dsi->lanes = device->lanes; + dsi->channel = device->channel; + dsi->format = device->format; + dsi->panel = of_drm_find_panel(device->dev.of_node); + drm_panel_attach(dsi->panel, &dsi->connector); + + return 0; +} + +static int dw_mipi_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct dw_mipi_dsi *dsi = host_to_dsi(host); + + drm_panel_detach(dsi->panel); + + return 0; +} + +static int dw_mipi_dsi_gen_pkt_hdr_write(struct dw_mipi_dsi *dsi, u32 val) +{ + int ret; + + ret = check_status(dsi, DSI_CMD_PKT_STATUS, GEN_CMD_FULL, + CMD_PKT_STATUS_TIMEOUT, STATUS_TO_CLEAR); + if (ret < 0) { + dev_err(dsi->dev, "failed to get available command FIFO\n"); + return ret; + } + + dsi_write(dsi, DSI_GEN_HDR, val); + + ret = check_status(dsi, DSI_CMD_PKT_STATUS, + GEN_CMD_EMPTY | GEN_PLD_W_EMPTY, + CMD_PKT_STATUS_TIMEOUT, STATUS_TO_SET); + if (ret < 0) { + dev_err(dsi->dev, "failed to write command FIFO\n"); + return ret; + } + + return 0; +} + +static int dw_mipi_dsi_dcs_short_write(struct dw_mipi_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + const u16 *tx_buf = msg->tx_buf; + u32 val = GEN_HDATA(*tx_buf) | GEN_HTYPE(msg->type); + + if (msg->tx_len > 2) { + dev_err(dsi->dev, "too long tx buf length %d for short write\n", + msg->tx_len); + return -EINVAL; + } + + return dw_mipi_dsi_gen_pkt_hdr_write(dsi, val); +} + +static int dw_mipi_dsi_dcs_long_write(struct dw_mipi_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + const u32 *tx_buf = msg->tx_buf; + int len = msg->tx_len, pld_data_bytes = sizeof(*tx_buf), ret; + u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type); + u32 remainder = 0; + + if (msg->tx_len < 3) { + dev_err(dsi->dev, "wrong tx buf length %d for long write\n", + msg->tx_len); + return -EINVAL; + } + + while (DIV_ROUND_UP(len, pld_data_bytes)) { + if (len < pld_data_bytes) { + memcpy(&remainder, tx_buf, len); + dsi_write(dsi, DSI_GEN_PLD_DATA, remainder); + len = 0; + } else { + dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf); + tx_buf++; + len -= pld_data_bytes; + } + ret = check_status(dsi, DSI_CMD_PKT_STATUS, GEN_PLD_W_FULL, + CMD_PKT_STATUS_TIMEOUT, STATUS_TO_CLEAR); + if (ret < 0) { + dev_err(dsi->dev, + "failed to get available write payload FIFO\n"); + return ret; + } + } + + return dw_mipi_dsi_gen_pkt_hdr_write(dsi, val); +} + +static ssize_t dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct dw_mipi_dsi *dsi = host_to_dsi(host); + int ret; + + switch (msg->type) { + case MIPI_DSI_DCS_SHORT_WRITE: + case MIPI_DSI_DCS_SHORT_WRITE_PARAM: + case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE: + ret = dw_mipi_dsi_dcs_short_write(dsi, msg); + break; + case MIPI_DSI_DCS_LONG_WRITE: + ret = dw_mipi_dsi_dcs_long_write(dsi, msg); + break; + default: + dev_err(dsi->dev, "unsupported message type\n"); + ret = -EINVAL; + } + + return ret; +} + +static const struct mipi_dsi_host_ops dw_mipi_dsi_host_ops = { + .attach = dw_mipi_dsi_host_attach, + .detach = dw_mipi_dsi_host_detach, + .transfer = dw_mipi_dsi_host_transfer, +}; + +static enum drm_connector_status +dw_mipi_dsi_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void dw_mipi_dsi_drm_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static struct drm_connector_funcs dw_mipi_dsi_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = dw_mipi_dsi_detect, + .destroy = dw_mipi_dsi_drm_connector_destroy, +}; + +static int dw_mipi_dsi_connector_get_modes(struct drm_connector *connector) +{ + struct dw_mipi_dsi *dsi = con_to_dsi(connector); + + return drm_panel_get_modes(dsi->panel); +} + +static enum drm_mode_status dw_mipi_dsi_mode_valid( + struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct dw_mipi_dsi *dsi = con_to_dsi(connector); + + enum drm_mode_status mode_status = MODE_OK; + + if (dsi->pdata->mode_valid) + mode_status = dsi->pdata->mode_valid(connector, mode); + + return mode_status; +} + +static struct drm_encoder *dw_mipi_dsi_connector_best_encoder( + struct drm_connector *connector) +{ + struct dw_mipi_dsi *dsi = con_to_dsi(connector); + + return dsi->encoder; +} + +static struct drm_connector_helper_funcs dw_mipi_dsi_connector_helper_funcs = { + .get_modes = dw_mipi_dsi_connector_get_modes, + .mode_valid = dw_mipi_dsi_mode_valid, + .best_encoder = dw_mipi_dsi_connector_best_encoder, +}; + +static void dw_mipi_dsi_video_mode_config(struct dw_mipi_dsi *dsi) +{ + u32 val; + + val = VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER; + + dsi_write(dsi, DSI_VID_MODE_CFG, val); +} + +static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi, + enum dw_mipi_dsi_mode mode) +{ + if (mode == DW_MIPI_DSI_CMD_MODE) { + dsi_write(dsi, DSI_PWR_UP, RESET); + dsi_modify(dsi, DSI_CMD_MODE_CFG, + ENABLE_CMD_MODE_MASK, ENABLE_CMD_MODE); + dsi_modify(dsi, DSI_VID_MODE_CFG, + ENABLE_VIDEO_MODE_MASK, DISABLE_VIDEO_MODE); + dsi_write(dsi, DSI_PWR_UP, POWERUP); + } else { + dsi_write(dsi, DSI_PWR_UP, RESET); + dsi_modify(dsi, DSI_CMD_MODE_CFG, + ENABLE_CMD_MODE_MASK, DISABLE_CMD_MODE); + + dw_mipi_dsi_video_mode_config(dsi); + + dsi_modify(dsi, DSI_VID_MODE_CFG, + ENABLE_VIDEO_MODE_MASK, ENABLE_VIDEO_MODE); + dsi_write(dsi, DSI_PWR_UP, POWERUP); + dsi_write(dsi, DSI_PHY_IF_CTRL, TX_REQ_CLK_HS); + } +} + +static void dw_mipi_dsi_disable(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PHY_IF_CTRL, PHY_IF_CTRL_RESET); + dsi_write(dsi, DSI_PWR_UP, RESET); + dsi_write(dsi, DSI_PHY_RSTZ, PHY_RSTZ); +} + +static void dw_mipi_dsi_bridge_enable(struct drm_bridge *bridge) +{ + struct dw_mipi_dsi *dsi = bridge->driver_private; + + if (dsi->enabled) + return; + + clk_prepare_enable(dsi->cfg_clk); + clk_prepare_enable(dsi->pclk); + dw_mipi_dsi_config_testdin(dsi); + dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE); + dw_mipi_dsi_wait_for_two_frames(dsi); + dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE); + drm_panel_prepare(dsi->panel); + dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE); + clk_disable_unprepare(dsi->pclk); + clk_disable_unprepare(dsi->cfg_clk); + + drm_panel_enable(dsi->panel); + + dsi->enabled = true; +} + +static void dw_mipi_dsi_bridge_disable(struct drm_bridge *bridge) +{ + struct dw_mipi_dsi *dsi = bridge->driver_private; + unsigned long expire; + + if (!dsi->enabled) + return; + + drm_panel_disable(dsi->panel); + + clk_prepare_enable(dsi->cfg_clk); + clk_prepare_enable(dsi->pclk); + dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE); + drm_panel_unprepare(dsi->panel); + dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE); + + /* + * This is necessary to make sure the peripheral + * will be driven normally when the display is + * enabled again later. + */ + expire = jiffies + msecs_to_jiffies(120); + while (time_before(jiffies, expire)) + cpu_relax(); + + dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE); + dw_mipi_dsi_disable(dsi); + clk_disable_unprepare(dsi->pclk); + clk_disable_unprepare(dsi->cfg_clk); + + dsi->enabled = false; +} + +static void dw_mipi_dsi_bridge_nope(struct drm_bridge *bridge) +{ + /* do nothing */ +} + +static void dw_mipi_dsi_init(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PWR_UP, RESET); + dsi_write(dsi, DSI_PHY_RSTZ, PHY_DISABLECLK | PHY_RSTZ | PHY_SHUTDOWNZ); + dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVIDSION(1) | + TX_ESC_CLK_DIVIDSION(7)); +} + +static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi, + struct drm_display_mode *mode) +{ + u32 val = 0; + + if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + val |= VSYNC_ACTIVE_LOW; + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + val |= HSYNC_ACTIVE_LOW; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + val |= DPI_COLOR_CODING_24BIT; + break; + case MIPI_DSI_FMT_RGB666: + val |= DPI_COLOR_CODING_18BIT_2; + val |= EN18_LOOSELY; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + val |= DPI_COLOR_CODING_18BIT_1; + break; + case MIPI_DSI_FMT_RGB565: + val |= DPI_COLOR_CODING_16BIT_1; + break; + } + + val |= DPI_VID(dsi->channel); + + dsi_write(dsi, DSI_DPI_CFG, val); +} + +static void dw_mipi_dsi_packet_handler_config(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PCKHDL_CFG, EN_CRC_RX | EN_ECC_RX | EN_BTA); +} + +static void dw_mipi_dsi_video_packet_config(struct dw_mipi_dsi *dsi, + struct drm_display_mode *mode) +{ + dsi_write(dsi, DSI_VID_PKT_CFG, VID_PKT_SIZE(mode->hdisplay)); +} + +static void dw_mipi_dsi_command_mode_config(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_CMD_MODE_CFG, CMD_MODE_ALL_LP | ENABLE_CMD_MODE); +} + +/* Get lane byte clock cycles. */ +static u64 dw_mipi_dsi_get_hcomponent_lbcc(struct dw_mipi_dsi *dsi, + u64 hcomponent) +{ + u64 frac, lbcc; + + lbcc = hcomponent * dsi->lane_mbps * USEC_PER_SEC / 8; + frac = do_div(lbcc, dsi->mode->clock * MSEC_PER_SEC); + if (frac) + lbcc++; + + return lbcc; +} + +static void dw_mipi_dsi_line_timer_config(struct dw_mipi_dsi *dsi) +{ + u32 val = 0, htotal, hsa, hbp, lbcc; + struct drm_display_mode *mode = dsi->mode; + + htotal = mode->htotal; + hsa = mode->hsync_end - mode->hsync_start; + hbp = mode->htotal - mode->hsync_end; + + lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, htotal); + val |= HLINE_TIME(lbcc); + + lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, hsa); + val |= HSA_TIME(lbcc); + + lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, hbp); + val |= HBP_TIME(lbcc); + + dsi_write(dsi, DSI_TMR_LINE_CFG, val); +} + +static void dw_mipi_dsi_vertical_timing_config(struct dw_mipi_dsi *dsi) +{ + u32 val, vactive, vsa, vfp, vbp; + struct drm_display_mode *mode = dsi->mode; + + vactive = mode->vdisplay; + vsa = mode->vsync_end - mode->vsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + val = V_ACTIVE_LINES(vactive) | VSA_LINES(vsa) | + VFP_LINES(vfp) | VBP_LINES(vbp); + + dsi_write(dsi, DSI_VTIMING_CFG, val); +} + +static void dw_mipi_dsi_dphy_timing_config(struct dw_mipi_dsi *dsi) +{ + u32 val; + + val = PHY_HS2LP_TIME(0x40) | PHY_LP2HS_TIME(0x40) | + BTA_TIME(0xd00); + + dsi_write(dsi, DSI_PHY_TMR_CFG, val); +} + +static void dw_mipi_dsi_dphy_interface_config(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) | + N_LANES(dsi->lanes)); +} + +static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi) +{ + dsi_read(dsi, DSI_ERROR_ST0); + dsi_read(dsi, DSI_ERROR_ST1); + dsi_write(dsi, DSI_ERROR_MSK0, 0); + dsi_write(dsi, DSI_ERROR_MSK1, 0); +} + +static void dw_mipi_dsi_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct dw_mipi_dsi *dsi = bridge->driver_private; + int ret; + + dsi->mode = mode; + + ret = dw_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps); + if (ret < 0) + return; + + clk_prepare_enable(dsi->cfg_clk); + clk_prepare_enable(dsi->pclk); + dw_mipi_dsi_init(dsi); + dw_mipi_dsi_dpi_config(dsi, mode); + dw_mipi_dsi_packet_handler_config(dsi); + dw_mipi_dsi_video_mode_config(dsi); + dw_mipi_dsi_video_packet_config(dsi, mode); + dw_mipi_dsi_command_mode_config(dsi); + dw_mipi_dsi_line_timer_config(dsi); + dw_mipi_dsi_vertical_timing_config(dsi); + dw_mipi_dsi_dphy_timing_config(dsi); + dw_mipi_dsi_dphy_interface_config(dsi); + dw_mipi_dsi_clear_err(dsi); + dw_mipi_dsi_config_testdin(dsi); + dw_mipi_dsi_wait_for_two_frames(dsi); + drm_panel_prepare(dsi->panel); + clk_disable_unprepare(dsi->pclk); + clk_disable_unprepare(dsi->cfg_clk); +} + +static bool dw_mipi_dsi_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = { + .enable = dw_mipi_dsi_bridge_enable, + .disable = dw_mipi_dsi_bridge_disable, + .pre_enable = dw_mipi_dsi_bridge_nope, + .post_disable = dw_mipi_dsi_bridge_nope, + .mode_set = dw_mipi_dsi_bridge_mode_set, + .mode_fixup = dw_mipi_dsi_bridge_mode_fixup, +}; + +static int dw_mipi_dsi_register(struct drm_device *drm, struct dw_mipi_dsi *dsi) +{ + struct drm_encoder *encoder = dsi->encoder; + struct drm_bridge *bridge; + int ret; + + bridge = devm_kzalloc(drm->dev, sizeof(*bridge), GFP_KERNEL); + if (!bridge) + return -ENOMEM; + + dsi->bridge = bridge; + bridge->driver_private = dsi; + + bridge->funcs = &dw_mipi_dsi_bridge_funcs; + ret = drm_bridge_attach(drm, bridge); + if (ret) { + dev_err(drm->dev, "failed to initialize bridge with drm\n"); + return ret; + } + + encoder->bridge = bridge; + + drm_connector_helper_add(&dsi->connector, + &dw_mipi_dsi_connector_helper_funcs); + drm_connector_init(drm, &dsi->connector, &dw_mipi_dsi_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + + drm_mode_connector_attach_encoder(&dsi->connector, dsi->encoder); + + return 0; +} + +int dw_mipi_dsi_bind(struct device *dev, struct device *master, void *data, + struct drm_encoder *encoder, struct resource *res, + const struct dw_mipi_dsi_plat_data *pdata) +{ + struct drm_device *drm = data; + struct dw_mipi_dsi *dsi; + u32 val; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + dsi->pdata = pdata; + dsi->dev = dev; + dsi->dsi_host.ops = &dw_mipi_dsi_host_ops; + dsi->dsi_host.dev = dev; + dsi->encoder = encoder; + + dsi->base = devm_ioremap_resource(dev, res); + if (IS_ERR(dsi->base)) + return PTR_ERR(dsi->base); + + dsi->pllref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(dsi->pllref_clk)) { + ret = PTR_ERR(dsi->pllref_clk); + dev_err(dev, "Unable to get pll reference clock: %d\n", ret); + return ret; + } + clk_prepare_enable(dsi->pllref_clk); + + dsi->cfg_clk = devm_clk_get(dev, "cfg"); + if (IS_ERR(dsi->cfg_clk)) { + ret = PTR_ERR(dsi->cfg_clk); + dev_err(dev, "Unable to get configuration clock: %d\n", ret); + goto err_pllref; + } + + dsi->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(dsi->cfg_clk)) { + ret = PTR_ERR(dsi->cfg_clk); + dev_err(dev, "Unable to get configuration clock: %d\n", ret); + goto err_pllref; + } + + clk_prepare_enable(dsi->cfg_clk); + clk_prepare_enable(dsi->pclk); + val = dsi_read(dsi, DSI_VERSION); + clk_disable_unprepare(dsi->pclk); + clk_disable_unprepare(dsi->cfg_clk); + + dev_info(dev, "version number is 0x%08x\n", val); + + ret = dw_mipi_dsi_register(drm, dsi); + if (ret) + goto err_pllref; + + dev_set_drvdata(dev, dsi); + + return mipi_dsi_host_register(&dsi->dsi_host); + +err_pllref: + clk_disable_unprepare(dsi->pllref_clk); + return ret; +} +EXPORT_SYMBOL_GPL(dw_mipi_dsi_bind); + +void dw_mipi_dsi_unbind(struct device *dev, struct device *master, void *data) +{ + struct dw_mipi_dsi *dsi = dev_get_drvdata(dev); + + mipi_dsi_host_unregister(&dsi->dsi_host); + clk_disable_unprepare(dsi->pllref_clk); +} +EXPORT_SYMBOL_GPL(dw_mipi_dsi_unbind); + +MODULE_DESCRIPTION("Synopsys DesignWare MIPI DSI host controller driver"); +MODULE_AUTHOR("Liu Ying Ying.Liu@freescale.com"); +MODULE_LICENSE("GPL"); diff --git a/include/drm/bridge/dw_mipi_dsi.h b/include/drm/bridge/dw_mipi_dsi.h new file mode 100644 index 0000000..17630aa --- /dev/null +++ b/include/drm/bridge/dw_mipi_dsi.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + */ + +#ifndef __DW_MIPI_DSI__ +#define __DW_MIPI_DSI__ + +#include <drm/drmP.h> + +struct dw_mipi_dsi_plat_data { + unsigned int max_data_lanes; + enum drm_mode_status (*mode_valid)(struct drm_connector *connector, + struct drm_display_mode *mode); +}; + +int dw_mipi_dsi_get_encoder_pixel_format(struct drm_encoder *encoder); + +int dw_mipi_dsi_bind(struct device *dev, struct device *master, void *data, + struct drm_encoder *encoder, struct resource *res, + const struct dw_mipi_dsi_plat_data *pdata); +void dw_mipi_dsi_unbind(struct device *dev, struct device *master, void *data); +#endif /* __DW_MIPI_DSI__ */
On Thu, Feb 12, 2015 at 02:01:34PM +0800, Liu Ying wrote: [...]
diff --git a/drivers/gpu/drm/bridge/dw_mipi_dsi.c b/drivers/gpu/drm/bridge/dw_mipi_dsi.c
[...]
+struct dw_mipi_dsi {
- struct mipi_dsi_host dsi_host;
- struct drm_connector connector;
- struct drm_encoder *encoder;
- struct drm_bridge *bridge;
- struct drm_panel *panel;
- struct device *dev;
- void __iomem *base;
- struct clk *pllref_clk;
- struct clk *cfg_clk;
- struct clk *pclk;
- unsigned int lane_mbps; /* per lane */
- u32 channel;
- u32 lanes;
- u32 format;
- struct drm_display_mode *mode;
- const struct dw_mipi_dsi_plat_data *pdata;
- bool enabled;
+};
While reviewing this I kept thinking whether this is really the right architectural design. This driver is a MIPI DSI host, a connector and a bridge, all in one. But it seems to me like it should really be an encoder/connector and a MIPI DSI host. Why the need for a bridge? The bridge abstraction targets blocks outside of the SoC, but it is my understanding that these DesignWare IP blocks are designed into SoCs.
Thierry
On 04/09/2015 02:13 PM, Thierry Reding wrote:
On Thu, Feb 12, 2015 at 02:01:34PM +0800, Liu Ying wrote: [...]
diff --git a/drivers/gpu/drm/bridge/dw_mipi_dsi.c b/drivers/gpu/drm/bridge/dw_mipi_dsi.c
[...]
+struct dw_mipi_dsi {
- struct mipi_dsi_host dsi_host;
- struct drm_connector connector;
- struct drm_encoder *encoder;
- struct drm_bridge *bridge;
- struct drm_panel *panel;
- struct device *dev;
- void __iomem *base;
- struct clk *pllref_clk;
- struct clk *cfg_clk;
- struct clk *pclk;
- unsigned int lane_mbps; /* per lane */
- u32 channel;
- u32 lanes;
- u32 format;
- struct drm_display_mode *mode;
- const struct dw_mipi_dsi_plat_data *pdata;
- bool enabled;
+};
While reviewing this I kept thinking whether this is really the right architectural design. This driver is a MIPI DSI host, a connector and a bridge, all in one. But it seems to me like it should really be an encoder/connector and a MIPI DSI host. Why the need for a bridge? The bridge abstraction targets blocks outside of the SoC, but it is my understanding that these DesignWare IP blocks are designed into SoCs.
The msm driver uses bridges for blocks within the SoC too. We have too many sub blocks in the display controller that use up crtcs and encoder entities. A bridge is the only option one has if an encoder in the display chain is already taken.
In the above designware configuration, if some one created a board that used an external chip to further convert DSI to some other output format, then we would be completely exhausted of all entities.
I posted a patch that allows us to create a chain of bridges for such cases. It seems to work well as an interim solution. Ideally, it would be better if we could make bridge a special case of an encoder, and let one encoder connect to another encoder.
Such a thing would also help us unify i2c slave encoders and bridge drivers too. A chip designed as an i2c slave encoder would work well with a drm driver that doesn't have an encoder, but won't work for SoCs SoCs that already have an encoder and were expecting a bridge entity instead.
Archit
Am Donnerstag, 16. April 2015, 11:09:58 schrieb Archit Taneja:
On 04/09/2015 02:13 PM, Thierry Reding wrote:
On Thu, Feb 12, 2015 at 02:01:34PM +0800, Liu Ying wrote: [...]
diff --git a/drivers/gpu/drm/bridge/dw_mipi_dsi.c b/drivers/gpu/drm/bridge/dw_mipi_dsi.c>
[...]
+struct dw_mipi_dsi {
- struct mipi_dsi_host dsi_host;
- struct drm_connector connector;
- struct drm_encoder *encoder;
- struct drm_bridge *bridge;
- struct drm_panel *panel;
- struct device *dev;
- void __iomem *base;
- struct clk *pllref_clk;
- struct clk *cfg_clk;
- struct clk *pclk;
- unsigned int lane_mbps; /* per lane */
- u32 channel;
- u32 lanes;
- u32 format;
- struct drm_display_mode *mode;
- const struct dw_mipi_dsi_plat_data *pdata;
- bool enabled;
+};
While reviewing this I kept thinking whether this is really the right architectural design. This driver is a MIPI DSI host, a connector and a bridge, all in one. But it seems to me like it should really be an encoder/connector and a MIPI DSI host. Why the need for a bridge? The bridge abstraction targets blocks outside of the SoC, but it is my understanding that these DesignWare IP blocks are designed into SoCs.
The msm driver uses bridges for blocks within the SoC too. We have too many sub blocks in the display controller that use up crtcs and encoder entities. A bridge is the only option one has if an encoder in the display chain is already taken.
In the above designware configuration, if some one created a board that used an external chip to further convert DSI to some other output format, then we would be completely exhausted of all entities.
I posted a patch that allows us to create a chain of bridges for such cases. It seems to work well as an interim solution. Ideally, it would be better if we could make bridge a special case of an encoder, and let one encoder connect to another encoder.
Such a thing would also help us unify i2c slave encoders and bridge drivers too. A chip designed as an i2c slave encoder would work well with a drm driver that doesn't have an encoder, but won't work for SoCs SoCs that already have an encoder and were expecting a bridge entity instead.
Yeah, having encoder-after-encoder chains would be great. I have the same issue on Rockchip where on the rk3288 the lvds-IP hogs the lcd-pins of the soc which are used both for panels as well as external encoders, while on the older Rockchip SoCs these pins are used by external encoders directly.
So in my current (pending review) patchset the lvds acts as encoder for panels and as bridge if external encoders are in use.
This patch adds device tree bindings for i.MX specific Synopsys DW MIPI DSI driver.
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository. * To address Philipp's comment, mention that a common compatible string "snps,dw-mipi-dsi" should be appended. * To address Philipp's comment, add a new required clock pclk and clean up clock-names.
v7->v8: * None.
v6->v7: * None.
v5->v6: * Add the #address-cells and #size-cells properties in the example 'ports' node. * Remove the useless pllref_gate clock from the required clocks, clock-names property.
v4->v5: * None.
v3->v4: * Newly introduced in v4. This is separated from the relevant driver patch in v3 to address Stefan Wahren's comment.
.../devicetree/bindings/drm/imx/mipi_dsi.txt | 81 ++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
diff --git a/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt new file mode 100644 index 0000000..4bd8451 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt @@ -0,0 +1,81 @@ +i.MX specific Device-Tree bindings for Synopsys DesignWare MIPI DSI host controller + +MIPI DSI host controller +======================== + +The MIPI DSI host controller is a Synopsys DesignWare IP. +The common device tree documentation for this controller can be found +at [1]. + +Required properties: + - #address-cells: Should be <1>. + - #size-cells: Should be <0>. + - compatible: The first compatible string should be "fsl,imx6q-mipi-dsi" + for i.MX6q/sdl SoCs. And, a common compatible string "snps,dw-mipi-dsi" + should be appended. + - reg: Physical base address of the controller and length of memory + mapped region. + - interrupts: The controller's interrupt number to the CPU(s). + - gpr: Should be <&gpr>. + The phandle points to the iomuxc-gpr region containing the + multiplexer control register for the controller. + - clocks, clock-names: Phandles to the controller's pll reference + clock(ref), configuration clock(cfg) and APB clock(pclk), as described + in [2] and [3]. + +Required sub-nodes: + - ports: This node may contain up to four port nodes with endpoint + definitions as defined in [4], corresponding to the four inputs to + the controller multiplexer. + - A node to represent a DSI peripheral as described in [5]. + +[1] Documentation/devicetree/bindings/drm/bridge/dw_mipi_dsi.txt. +[2] Documentation/devicetree/bindings/clock/clock-bindings.txt +[3] Documentation/devicetree/bindings/clock/imx6q-clock.txt +[4] Documentation/devicetree/bindings/media/video-interfaces.txt +[5] Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt + +example: + gpr: iomuxc-gpr@020e0000 { + /* ... */ + }; + + mipi_dsi: mipi@021e0000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "fsl,imx6q-mipi-dsi", "snps,dw-mipi-dsi"; + reg = <0x021e0000 0x4000>; + interrupts = <0 102 IRQ_TYPE_LEVEL_HIGH>; + gpr = <&gpr>; + clocks = <&clks IMX6QDL_CLK_MIPI_CORE_CFG>, + <&clks IMX6QDL_CLK_MIPI_CORE_CFG>, + <&clks IMX6QDL_CLK_MIPI_IPG>; + clock-names = "ref", "cfg", "pclk"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + mipi_mux_0: endpoint { + remote-endpoint = <&ipu1_di0_mipi>; + }; + }; + + port@1 { + reg = <1>; + + mipi_mux_1: endpoint { + remote-endpoint = <&ipu1_di1_mipi>; + }; + }; + }; + + panel { + compatible = "truly,tft480800-16-e-dsi"; + reg = <0>; + /* ... */ + }; + };
This patch adds support for Synopsys DesignWare MIPI DSI host controller which is embedded in the i.MX6q/sdl SoCs.
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository. * Add driver copyright for 2015.
v7->v8: * None.
v6->v7: * None.
v5->v6: * Make the checkpatch.pl script be happier.
v4->v5: * None.
v3->v4: * Move the relevant dt-bindings to a separate patch to address Stefan Wahren's comment.
v2->v3: * To address Andy Yan's comments, move the common Synopsys DesignWare MIPI DSI host controller logic into it's drm/bridge driver and leave the i.MX specific logic only.
v1->v2: * Address almost all comments from Thierry Reding and Russell. * Update the DT documentation to remove the display-timings node in the panel node. * Update the DT documentation to state that the nodes which represent the possible DRM CRTCs the controller may connect with should be placed in the node "ports". * Remove the flag 'enabled' from the struct imx_mipi_dsi. * Move the format_to_bpp() function in v1 to the common DRM MIPI DSI driver. * Improve the way we wait for check status for DPHY and command packet transfer. * Improve the DPMS support for the encoder. * Split the functions of ->host_attach() and ->mode_valid() clearly as suggested by Thierry Reding. * Improve the logics in imx_mipi_dsi_dcs_long_write(). * Enable/disable the pllref_clk and pllref_gate_clk at the component binding/unbinding stages to help remove the flag 'enabled'. * Update the module license to be "GPL". * Other minor changes, such as coding style issues and macro naming issues.
drivers/gpu/drm/imx/Kconfig | 7 ++ drivers/gpu/drm/imx/Makefile | 1 + drivers/gpu/drm/imx/dw_mipi_dsi-imx.c | 230 ++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 drivers/gpu/drm/imx/dw_mipi_dsi-imx.c
diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig index 33cdddf..7faeb49 100644 --- a/drivers/gpu/drm/imx/Kconfig +++ b/drivers/gpu/drm/imx/Kconfig @@ -53,3 +53,10 @@ config DRM_IMX_HDMI depends on DRM_IMX help Choose this if you want to use HDMI on i.MX6. + +config DRM_IMX_MIPI_DSI + tristate "Freescale i.MX DRM MIPI DSI" + select DRM_DW_MIPI_DSI + depends on DRM_IMX + help + Choose this if you want to use MIPI DSI on i.MX6. diff --git a/drivers/gpu/drm/imx/Makefile b/drivers/gpu/drm/imx/Makefile index f3ecd89..93919b4 100644 --- a/drivers/gpu/drm/imx/Makefile +++ b/drivers/gpu/drm/imx/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_DRM_IMX_LDB) += imx-ldb.o imx-ipuv3-crtc-objs := ipuv3-crtc.o ipuv3-plane.o obj-$(CONFIG_DRM_IMX_IPUV3) += imx-ipuv3-crtc.o obj-$(CONFIG_DRM_IMX_HDMI) += dw_hdmi-imx.o +obj-$(CONFIG_DRM_IMX_MIPI_DSI) += dw_mipi_dsi-imx.o diff --git a/drivers/gpu/drm/imx/dw_mipi_dsi-imx.c b/drivers/gpu/drm/imx/dw_mipi_dsi-imx.c new file mode 100644 index 0000000..5e6f62d --- /dev/null +++ b/drivers/gpu/drm/imx/dw_mipi_dsi-imx.c @@ -0,0 +1,230 @@ +/* + * i.MX drm driver - MIPI DSI Host Controller + * + * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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/component.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/videodev2.h> +#include <drm/bridge/dw_mipi_dsi.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h> + +#include "imx-drm.h" + +#define DRIVER_NAME "imx-mipi-dsi" + +struct imx_mipi_dsi { + struct drm_encoder encoder; + struct device *dev; + struct regmap *regmap; +}; + +static inline struct imx_mipi_dsi *enc_to_dsi(struct drm_encoder *enc) +{ + return container_of(enc, struct imx_mipi_dsi, encoder); +} + +static void imx_mipi_dsi_set_ipu_di_mux(struct imx_mipi_dsi *dsi, int ipu_di) +{ + regmap_update_bits(dsi->regmap, IOMUXC_GPR3, + IMX6Q_GPR3_MIPI_MUX_CTL_MASK, + ipu_di << IMX6Q_GPR3_MIPI_MUX_CTL_SHIFT); +} + +static struct drm_encoder_funcs imx_mipi_dsi_encoder_funcs = { + .destroy = imx_drm_encoder_destroy, +}; + +static bool imx_mipi_dsi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void imx_mipi_dsi_encoder_prepare(struct drm_encoder *encoder) +{ + u32 encoder_pix_fmt, interface_pix_fmt; + + encoder_pix_fmt = dw_mipi_dsi_get_encoder_pixel_format(encoder); + + switch (encoder_pix_fmt) { + case MIPI_DSI_FMT_RGB888: + interface_pix_fmt = V4L2_PIX_FMT_RGB24; + break; + case MIPI_DSI_FMT_RGB565: + interface_pix_fmt = V4L2_PIX_FMT_RGB565; + break; + default: + BUG(); + return; + } + + imx_drm_panel_format(encoder, interface_pix_fmt); +} + +static void imx_mipi_dsi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ +} + +static void imx_mipi_dsi_encoder_commit(struct drm_encoder *encoder) +{ + struct imx_mipi_dsi *dsi = enc_to_dsi(encoder); + int mux = imx_drm_encoder_get_mux_id(dsi->dev->of_node, encoder); + + imx_mipi_dsi_set_ipu_di_mux(dsi, mux); +} + +static void imx_mipi_dsi_encoder_disable(struct drm_encoder *encoder) +{ +} + +static struct drm_encoder_helper_funcs imx_mipi_dsi_encoder_helper_funcs = { + .mode_fixup = imx_mipi_dsi_encoder_mode_fixup, + .prepare = imx_mipi_dsi_encoder_prepare, + .mode_set = imx_mipi_dsi_encoder_mode_set, + .commit = imx_mipi_dsi_encoder_commit, + .disable = imx_mipi_dsi_encoder_disable, +}; + +static int imx_mipi_dsi_register(struct drm_device *drm, + struct imx_mipi_dsi *dsi) +{ + int ret; + + ret = imx_drm_encoder_parse_of(drm, &dsi->encoder, dsi->dev->of_node); + if (ret) + return ret; + + drm_encoder_helper_add(&dsi->encoder, + &imx_mipi_dsi_encoder_helper_funcs); + drm_encoder_init(drm, &dsi->encoder, &imx_mipi_dsi_encoder_funcs, + DRM_MODE_ENCODER_DSI); + return 0; +} + +static enum drm_mode_status imx_mipi_dsi_mode_valid( + struct drm_connector *connector, + struct drm_display_mode *mode) +{ + /* + * The VID_PKT_SIZE field in the DSI_VID_PKT_CFG + * register is 11-bit. + */ + if (mode->hdisplay > 0x7ff) + return MODE_BAD_HVALUE; + + /* + * The V_ACTIVE_LINES field in the DSI_VTIMING_CFG + * register is 11-bit. + */ + if (mode->vdisplay > 0x7ff) + return MODE_BAD_VVALUE; + + return MODE_OK; +} + +static struct dw_mipi_dsi_plat_data imx6q_mipi_dsi_drv_data = { + .max_data_lanes = 2, + .mode_valid = imx_mipi_dsi_mode_valid, +}; + +static const struct of_device_id imx_mipi_dsi_dt_ids[] = { + { + .compatible = "fsl,imx6q-mipi-dsi", + .data = &imx6q_mipi_dsi_drv_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_mipi_dsi_dt_ids); + +static int imx_mipi_dsi_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + const struct of_device_id *of_id = + of_match_device(imx_mipi_dsi_dt_ids, dev); + const struct dw_mipi_dsi_plat_data *pdata = of_id->data; + struct drm_device *drm = data; + struct device_node *np = dev->of_node; + struct imx_mipi_dsi *dsi; + struct resource *res; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + dsi->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + dsi->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); + if (IS_ERR(dsi->regmap)) + return PTR_ERR(dsi->regmap); + + ret = imx_mipi_dsi_register(drm, dsi); + if (ret) + return ret; + + dev_set_drvdata(dev, dsi); + + return dw_mipi_dsi_bind(dev, master, data, &dsi->encoder, res, pdata); +} + +static void imx_mipi_dsi_unbind(struct device *dev, struct device *master, + void *data) +{ + return dw_mipi_dsi_unbind(dev, master, data); +} + +static const struct component_ops imx_mipi_dsi_ops = { + .bind = imx_mipi_dsi_bind, + .unbind = imx_mipi_dsi_unbind, +}; + +static int imx_mipi_dsi_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &imx_mipi_dsi_ops); +} + +static int imx_mipi_dsi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &imx_mipi_dsi_ops); + return 0; +} + +static struct platform_driver imx_mipi_dsi_driver = { + .probe = imx_mipi_dsi_probe, + .remove = imx_mipi_dsi_remove, + .driver = { + .of_match_table = imx_mipi_dsi_dt_ids, + .name = DRIVER_NAME, + }, +}; +module_platform_driver(imx_mipi_dsi_driver); + +MODULE_DESCRIPTION("i.MX MIPI DSI host controller driver"); +MODULE_AUTHOR("Liu Ying Ying.Liu@freescale.com"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME);
This patch adds device tree bindings for Himax HX8369A DRM panel driver.
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v4->v5: * Merge the bs[3:0]-gpios properties into one property - bs-gpios. This addresses Andrzej Hajda's comment.
v3->v4: * Newly introduced in v4. This is separated from the relevant driver patch in v3 to address Stefan Wahren's comment.
.../devicetree/bindings/panel/himax,hx8369a.txt | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt new file mode 100644 index 0000000..3a44b70 --- /dev/null +++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt @@ -0,0 +1,39 @@ +Himax HX8369A WVGA 16.7M color TFT single chip driver with internal GRAM + +Himax HX8369A is a WVGA resolution driving controller. +It is designed to provide a single chip solution that combines a source +driver and power supply circuits to drive a TFT dot matrix LCD with +480RGBx864 dots at the maximum. + +The HX8369A supports several interface modes, including MPU MIPI DBI Type +A/B mode, MIPI DPI/DBI Type C mode, MIPI DSI video mode, MIPI DSI command +mode and MDDI mode. The interface mode is selected by the external hardware +pins BS[3:0]. + +Currently, only the MIPI DSI video mode is supported. + +Required properties: + - compatible: should be a panel's compatible string + - reg: the virtual channel number of a DSI peripheral, as described in [1] + - reset-gpios: a GPIO spec for the reset pin, as described in [2] + +Optional properties: + - vdd1-supply: I/O and interface power supply + - vdd2-supply: analog power supply + - vdd3-supply: logic power supply + - dsi-vcc-supply: DSI and MDDI power supply + - vpp-supply: OTP programming voltage + - bs-gpios: a GPIO spec for the pins BS[3:0], as described in [2] + +[1] Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt +[2] Documentation/devicetree/bindings/gpio/gpio.txt + +Example: + panel { + compatible = "truly,tft480800-16-e-dsi"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_mipi_panel>; + reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>; + bs-gpios = <0>, <0>, <&gpio6 14 GPIO_ACTIVE_HIGH>, <0>; + };
On Thu, Feb 12, 2015 at 02:01:37PM +0800, Liu Ying wrote:
This patch adds device tree bindings for Himax HX8369A DRM panel driver.
Signed-off-by: Liu Ying Ying.Liu@freescale.com
v8->v9:
- Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8:
- None.
v6->v7:
- None.
v5->v6:
- None.
v4->v5:
- Merge the bs[3:0]-gpios properties into one property - bs-gpios. This addresses Andrzej Hajda's comment.
v3->v4:
- Newly introduced in v4. This is separated from the relevant driver patch in v3 to address Stefan Wahren's comment.
.../devicetree/bindings/panel/himax,hx8369a.txt | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt new file mode 100644 index 0000000..3a44b70 --- /dev/null +++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt @@ -0,0 +1,39 @@ +Himax HX8369A WVGA 16.7M color TFT single chip driver with internal GRAM
+Himax HX8369A is a WVGA resolution driving controller. +It is designed to provide a single chip solution that combines a source +driver and power supply circuits to drive a TFT dot matrix LCD with +480RGBx864 dots at the maximum.
+The HX8369A supports several interface modes, including MPU MIPI DBI Type +A/B mode, MIPI DPI/DBI Type C mode, MIPI DSI video mode, MIPI DSI command +mode and MDDI mode. The interface mode is selected by the external hardware +pins BS[3:0].
+Currently, only the MIPI DSI video mode is supported.
This doesn't make much sense. The binding is supposed to describe the hardware, so saying "only video mode is supported" is weird. Perhaps if you have no way to test other modes you could reword as:
This version of the device tree binding only specifies MIPI DSI video mode.
Then again, would the device tree content be different based on the video mode?
+Required properties:
- compatible: should be a panel's compatible string
I don't understand this. If this chip isn't a panel, why should the compatible string contain the panel's compatible string? Is this some kind of bridge chip that can drive different panels? I guess I'll see when I look at the driver.
Anyway, if it isn't a panel it shouldn't have the panel's compatible string.
- reg: the virtual channel number of a DSI peripheral, as described in [1]
- reset-gpios: a GPIO spec for the reset pin, as described in [2]
+Optional properties:
- vdd1-supply: I/O and interface power supply
- vdd2-supply: analog power supply
- vdd3-supply: logic power supply
- dsi-vcc-supply: DSI and MDDI power supply
- vpp-supply: OTP programming voltage
- bs-gpios: a GPIO spec for the pins BS[3:0], as described in [2]
+[1] Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt +[2] Documentation/devicetree/bindings/gpio/gpio.txt
+Example:
- panel {
compatible = "truly,tft480800-16-e-dsi";
reg = <0>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_mipi_panel>;
reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
bs-gpios = <0>, <0>, <&gpio6 14 GPIO_ACTIVE_HIGH>, <0>;
- };
Again this doesn't make sense. You're mixing two things here: the HIMAX chip that is presumably a bridge and the panel connected to it.
Thierry
This patch adds support for Himax HX8369A MIPI DSI panel.
Reviewed-by: Andrzej Hajda a.hajda@samsung.com Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository. * Add driver copyright for 2015.
v7->v8: * Remove several unnecessary headers included in the driver.
v6->v7: * Address Andrzej Hajda's following comments. * Simplify the return logic in hx8369a_dcs_write(). * Replace the macro hx8369a_dsi_init_helper() with a function array to improve the code quality. * Handle error cases during getting gpios in probe(). * Add 'Reviewed-by: Andrzej Hajda a.hajda@samsung.com'.
v5->v6: * Make the checkpatch.pl script be happier. * Do not set the dsi channel number to be zero in probe(), because the MIPI DSI bus driver would set it.
v4->v5: * Address Andrzej Hajda's comments. * Get the bs-gpios property instead of the bs[3:0]-gpios properties. * Implement error propagation for panel register configurations. * Other minor changes to improve the code quality.
v3->v4: * Move the relevant dt-bindings to a separate patch to address Stefan Wahren's comment.
v2->v3: * Sort the included header files alphabetically.
v1->v2: * Address almost all comments from Thierry Reding. * Remove several DT properties as they can be implied by the compatible string. * Add the HIMAX/himax prefixes to the driver's Kconfig name and driver name. * Move the driver's Makefile entry place to sort the entries alphabetically. * Reuse several standard DCS functions instead of inventing wheels. * Move the panel resetting and power logics to the driver probe/remove stages. This may simplify panel prepare/unprepare hooks. The power consumption should not change a lot at DPMS since the panel enters sleep mode at that time. * Add the module author. * Other minor changes, such as coding style issues.
drivers/gpu/drm/panel/Kconfig | 5 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-himax-hx8369a.c | 610 ++++++++++++++++++++++++++++ 3 files changed, 616 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-himax-hx8369a.c
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index d845837..cd6fbb7 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -17,6 +17,11 @@ config DRM_PANEL_SIMPLE that it can be automatically turned off when the panel goes into a low power state.
+config DRM_PANEL_HIMAX_HX8369A + tristate "Himax HX8369A panel" + depends on OF + select DRM_MIPI_DSI + config DRM_PANEL_LD9040 tristate "LD9040 RGB/SPI panel" depends on OF && SPI diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 4b2a043..d5dbe06 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o +obj-$(CONFIG_DRM_PANEL_HIMAX_HX8369A) += panel-himax-hx8369a.o obj-$(CONFIG_DRM_PANEL_LD9040) += panel-ld9040.o obj-$(CONFIG_DRM_PANEL_S6E8AA0) += panel-s6e8aa0.o obj-$(CONFIG_DRM_PANEL_SHARP_LQ101R1SX01) += panel-sharp-lq101r1sx01.o diff --git a/drivers/gpu/drm/panel/panel-himax-hx8369a.c b/drivers/gpu/drm/panel/panel-himax-hx8369a.c new file mode 100644 index 0000000..649e395 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-himax-hx8369a.c @@ -0,0 +1,610 @@ +/* + * Himax HX8369A panel driver. + * + * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This driver is based on Samsung s6e8aa0 panel driver. + */ + +#include <drm/drmP.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <linux/regulator/consumer.h> + +#define WRDISBV 0x51 +#define WRCTRLD 0x53 +#define WRCABC 0x55 +#define SETPOWER 0xb1 +#define SETDISP 0xb2 +#define SETCYC 0xb4 +#define SETVCOM 0xb6 +#define SETEXTC 0xb9 +#define SETMIPI 0xba +#define SETPANEL 0xcc +#define SETGIP 0xd5 +#define SETGAMMA 0xe0 + +#define HX8369A_MIN_BRIGHTNESS 0x00 +#define HX8369A_MAX_BRIGHTNESS 0xff + +enum hx8369a_mpu_interface { + HX8369A_DBI_TYPE_A_8BIT, + HX8369A_DBI_TYPE_A_9BIT, + HX8369A_DBI_TYPE_A_16BIT, + HX8369A_DBI_TYPE_A_18BIT, + HX8369A_DBI_TYPE_B_8BIT, + HX8369A_DBI_TYPE_B_9BIT, + HX8369A_DBI_TYPE_B_16BIT, + HX8369A_DBI_TYPE_B_18BIT, + HX8369A_DSI_CMD_MODE, + HX8369A_DBI_TYPE_B_24BIT, + HX8369A_DSI_VIDEO_MODE, + HX8369A_MDDI, + HX8369A_DPI_DBI_TYPE_C_OPT1, + HX8369A_DPI_DBI_TYPE_C_OPT2, + HX8369A_DPI_DBI_TYPE_C_OPT3 +}; + +enum hx8369a_resolution { + HX8369A_RES_480_864, + HX8369A_RES_480_854, + HX8369A_RES_480_800, + HX8369A_RES_480_640, + HX8369A_RES_360_640, + HX8369A_RES_480_720, +}; + +struct hx8369a_panel_desc { + const struct drm_display_mode *mode; + + /* ms */ + unsigned int power_on_delay; + unsigned int reset_delay; + + unsigned int dsi_lanes; +}; + +struct hx8369a { + struct device *dev; + struct drm_panel panel; + + const struct hx8369a_panel_desc *pd; + + struct regulator_bulk_data supplies[5]; + struct gpio_desc *reset_gpio; + struct gpio_desc *bs_gpio[4]; + u8 res_sel; +}; + +static inline struct hx8369a *panel_to_hx8369a(struct drm_panel *panel) +{ + return container_of(panel, struct hx8369a, panel); +} + +static int hx8369a_dcs_write(struct hx8369a *ctx, const char *func, + const void *data, size_t len) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + ssize_t ret; + + ret = mipi_dsi_dcs_write_buffer(dsi, data, len); + if (ret < 0) + dev_err(ctx->dev, "%s failed: %d\n", func, ret); + return ret; +} + +#define hx8369a_dcs_write_seq(ctx, seq...) \ +({ \ + const u8 d[] = { seq }; \ + BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, \ + "DCS sequence too big for stack"); \ + hx8369a_dcs_write(ctx, __func__, d, ARRAY_SIZE(d)); \ +}) + +#define hx8369a_dcs_write_seq_static(ctx, seq...) \ +({ \ + static const u8 d[] = { seq }; \ + hx8369a_dcs_write(ctx, __func__, d, ARRAY_SIZE(d)); \ +}) + +static int hx8369a_dsi_set_display_related_register(struct hx8369a *ctx) +{ + u8 sec_p = (ctx->res_sel << 4) | 0x03; + + return hx8369a_dcs_write_seq(ctx, SETDISP, 0x00, sec_p, 0x03, + 0x03, 0x70, 0x00, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x03, 0x00, 0x01); +} + +static int hx8369a_dsi_set_display_waveform_cycle(struct hx8369a *ctx) +{ + return hx8369a_dcs_write_seq_static(ctx, SETCYC, 0x00, 0x1d, + 0x5f, 0x0e, 0x06); +} + +static int hx8369a_dsi_set_gip(struct hx8369a *ctx) +{ + return hx8369a_dcs_write_seq_static(ctx, SETGIP, 0x00, 0x04, + 0x03, 0x00, 0x01, 0x05, 0x1c, 0x70, + 0x01, 0x03, 0x00, 0x00, 0x40, 0x06, + 0x51, 0x07, 0x00, 0x00, 0x41, 0x06, + 0x50, 0x07, 0x07, 0x0f, 0x04); +} + +static int hx8369a_dsi_set_power(struct hx8369a *ctx) +{ + return hx8369a_dcs_write_seq_static(ctx, SETPOWER, 0x01, 0x00, + 0x34, 0x06, 0x00, 0x0f, 0x0f, 0x2a, + 0x32, 0x3f, 0x3f, 0x07, 0x3a, 0x01, + 0xe6, 0xe6, 0xe6, 0xe6, 0xe6); +} + +static int hx8369a_dsi_set_vcom_voltage(struct hx8369a *ctx) +{ + return hx8369a_dcs_write_seq_static(ctx, SETVCOM, 0x56, 0x56); +} + +static int hx8369a_dsi_set_panel(struct hx8369a *ctx) +{ + return hx8369a_dcs_write_seq_static(ctx, SETPANEL, 0x02); +} + +static int hx8369a_dsi_set_gamma_curve(struct hx8369a *ctx) +{ + return hx8369a_dcs_write_seq_static(ctx, SETGAMMA, 0x00, 0x1d, + 0x22, 0x38, 0x3d, 0x3f, 0x2e, 0x4a, + 0x06, 0x0d, 0x0f, 0x13, 0x15, 0x13, + 0x16, 0x10, 0x19, 0x00, 0x1d, 0x22, + 0x38, 0x3d, 0x3f, 0x2e, 0x4a, 0x06, + 0x0d, 0x0f, 0x13, 0x15, 0x13, 0x16, + 0x10, 0x19); +} + +static int hx8369a_dsi_set_mipi(struct hx8369a *ctx) +{ + u8 eleventh_p = ctx->pd->dsi_lanes == 2 ? 0x11 : 0x10; + + return hx8369a_dcs_write_seq(ctx, SETMIPI, 0x00, 0xa0, 0xc6, + 0x00, 0x0a, 0x00, 0x10, 0x30, 0x6f, + 0x02, eleventh_p, 0x18, 0x40); +} + +static int hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + u8 format; + int ret; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + case MIPI_DSI_FMT_RGB666: + format = 0x77; + break; + case MIPI_DSI_FMT_RGB565: + format = 0x55; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + format = 0x66; + break; + default: + dev_err(ctx->dev, "unsupported DSI pixel format\n"); + return -EINVAL; + } + + ret = mipi_dsi_dcs_set_pixel_format(dsi, format); + if (ret < 0) + dev_err(ctx->dev, "%s failed: %d\n", __func__, ret); + return ret; +} + +static int hx8369a_dsi_set_column_address(struct hx8369a *ctx) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + const struct drm_display_mode *mode = ctx->pd->mode; + int ret; + + ret = mipi_dsi_dcs_set_column_address(dsi, 0, mode->hdisplay - 1); + if (ret < 0) + dev_err(ctx->dev, "%s failed: %d\n", __func__, ret); + return ret; +} + +static int hx8369a_dsi_set_page_address(struct hx8369a *ctx) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + const struct drm_display_mode *mode = ctx->pd->mode; + int ret; + + ret = mipi_dsi_dcs_set_page_address(dsi, 0, mode->vdisplay - 1); + if (ret < 0) + dev_err(ctx->dev, "%s failed: %d\n", __func__, ret); + return ret; +} + +static int hx8369a_dsi_write_display_brightness(struct hx8369a *ctx, + u8 brightness) +{ + return hx8369a_dcs_write_seq(ctx, WRDISBV, brightness); +} + +static int hx8369a_dsi_write_cabc(struct hx8369a *ctx) +{ + return hx8369a_dcs_write_seq_static(ctx, WRCABC, 0x01); +} + +static int hx8369a_dsi_write_control_display(struct hx8369a *ctx) +{ + return hx8369a_dcs_write_seq_static(ctx, WRCTRLD, 0x24); +} + +static int hx8369a_dsi_panel_init(struct hx8369a *ctx) +{ + int (*funcs[])(struct hx8369a *ctx) = { + hx8369a_dsi_set_display_related_register, + hx8369a_dsi_set_display_waveform_cycle, + hx8369a_dsi_set_gip, + hx8369a_dsi_set_power, + hx8369a_dsi_set_vcom_voltage, + hx8369a_dsi_set_panel, + hx8369a_dsi_set_gamma_curve, + hx8369a_dsi_set_mipi, + hx8369a_dsi_set_interface_pixel_fomat, + hx8369a_dsi_set_column_address, + hx8369a_dsi_set_page_address, + hx8369a_dsi_write_cabc, + hx8369a_dsi_write_control_display, + }; + int ret, i; + + for (i = 0; i < ARRAY_SIZE(funcs); i++) { + ret = funcs[i](ctx); + if (ret) + return ret; + } + return 0; +} + +static int hx8369a_dsi_set_extension_command(struct hx8369a *ctx) +{ + return hx8369a_dcs_write_seq_static(ctx, SETEXTC, 0xff, 0x83, 0x69); +} + +static int hx8369a_dsi_set_maximum_return_packet_size(struct hx8369a *ctx, + u16 size) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + ret = mipi_dsi_set_maximum_return_packet_size(dsi, size); + if (ret < 0) + dev_err(ctx->dev, + "error %d setting maximum return packet size to %d\n", + ret, size); + return ret; +} + +static int hx8369a_dsi_set_sequence(struct hx8369a *ctx) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + ret = hx8369a_dsi_set_extension_command(ctx); + if (ret < 0) + goto out; + + ret = hx8369a_dsi_set_maximum_return_packet_size(ctx, 4); + if (ret < 0) + goto out; + + ret = hx8369a_dsi_panel_init(ctx); + if (ret < 0) + goto out; + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + dev_err(ctx->dev, "failed to exit sleep mode: %d\n", ret); + goto out; + } + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + dev_err(ctx->dev, "failed to set display on: %d\n", ret); + goto out; + } + + /* + * It's necessary to wait 120msec after sending the Sleep Out + * command before Sleep In command can be sent, according to + * the HX8369A data sheet. + */ + msleep(120); +out: + return ret; +} + +static int hx8369a_dsi_disable(struct drm_panel *panel) +{ + struct hx8369a *ctx = panel_to_hx8369a(panel); + + return hx8369a_dsi_write_display_brightness(ctx, + HX8369A_MIN_BRIGHTNESS); +} + +static int hx8369a_dsi_unprepare(struct drm_panel *panel) +{ + struct hx8369a *ctx = panel_to_hx8369a(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + dev_err(ctx->dev, "failed to enter sleep mode: %d\n", ret); + goto out; + } + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) { + dev_err(ctx->dev, "failed to set display off: %d\n", ret); + goto out; + } + + /* + * This is to allow time for the supply voltages and clock + * circuits to stablize, according to the HX8369A data sheet. + */ + msleep(5); +out: + return ret; +} + +static int hx8369a_dsi_prepare(struct drm_panel *panel) +{ + struct hx8369a *ctx = panel_to_hx8369a(panel); + + return hx8369a_dsi_set_sequence(ctx); +} + +static int hx8369a_dsi_enable(struct drm_panel *panel) +{ + struct hx8369a *ctx = panel_to_hx8369a(panel); + + return hx8369a_dsi_write_display_brightness(ctx, + HX8369A_MAX_BRIGHTNESS); +} + +static int hx8369a_get_modes(struct drm_panel *panel) +{ + struct drm_connector *connector = panel->connector; + struct drm_device *drm = panel->drm; + struct hx8369a *ctx = panel_to_hx8369a(panel); + struct drm_display_mode *mode; + const struct drm_display_mode *m = ctx->pd->mode; + + mode = drm_mode_duplicate(drm, m); + if (!mode) { + dev_err(drm->dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, m->vrefresh); + return 0; + } + + drm_mode_set_name(mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + connector->display_info.bpc = 8; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs hx8369a_dsi_drm_funcs = { + .disable = hx8369a_dsi_disable, + .unprepare = hx8369a_dsi_unprepare, + .prepare = hx8369a_dsi_prepare, + .enable = hx8369a_dsi_enable, + .get_modes = hx8369a_get_modes, +}; + +static int hx8369a_power_on(struct hx8369a *ctx) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + msleep(ctx->pd->power_on_delay); + + gpiod_set_value(ctx->reset_gpio, 1); + usleep_range(50, 60); + gpiod_set_value(ctx->reset_gpio, 0); + + msleep(ctx->pd->reset_delay); + + return 0; +} + +static int hx8369a_power_off(struct hx8369a *ctx) +{ + return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); +} + +static void hx8369a_vm_to_res_sel(struct hx8369a *ctx) +{ + const struct drm_display_mode *mode = ctx->pd->mode; + + switch (mode->hdisplay) { + case 480: + switch (mode->vdisplay) { + case 864: + ctx->res_sel = HX8369A_RES_480_864; + break; + case 854: + ctx->res_sel = HX8369A_RES_480_854; + break; + case 800: + ctx->res_sel = HX8369A_RES_480_800; + break; + case 640: + ctx->res_sel = HX8369A_RES_480_640; + break; + case 720: + ctx->res_sel = HX8369A_RES_480_720; + break; + default: + break; + } + break; + case 360: + if (mode->vdisplay == 640) + ctx->res_sel = HX8369A_RES_360_640; + break; + default: + break; + } +} + +static const struct drm_display_mode truly_tft480800_16_e_mode = { + .clock = 26400, + .hdisplay = 480, + .hsync_start = 480 + 8, + .hsync_end = 480 + 8 + 8, + .htotal = 480 + 8 + 8 + 8, + .vdisplay = 800, + .vsync_start = 800 + 6, + .vsync_end = 800 + 6 + 6, + .vtotal = 800 + 6 + 6 + 6, + .vrefresh = 60, + .width_mm = 45, + .height_mm = 76, +}; + +static const struct hx8369a_panel_desc truly_tft480800_16_e_dsi = { + .mode = &truly_tft480800_16_e_mode, + .power_on_delay = 10, + .reset_delay = 10, + .dsi_lanes = 2, +}; + +static const struct of_device_id hx8369a_dsi_of_match[] = { + { + .compatible = "truly,tft480800-16-e-dsi", + .data = &truly_tft480800_16_e_dsi, + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, hx8369a_dsi_of_match); + +static int hx8369a_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + const struct of_device_id *of_id = + of_match_device(hx8369a_dsi_of_match, dev); + struct hx8369a *ctx; + int ret, i; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->dev = dev; + + if (of_id) { + ctx->pd = of_id->data; + } else { + dev_err(dev, "cannot find compatible device\n"); + return -ENODEV; + } + + hx8369a_vm_to_res_sel(ctx); + + ctx->supplies[0].supply = "vdd1"; + ctx->supplies[1].supply = "vdd2"; + ctx->supplies[2].supply = "vdd3"; + ctx->supplies[3].supply = "dsi-vcc"; + ctx->supplies[4].supply = "vpp"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) { + dev_info(dev, "failed to get regulators: %d\n", ret); + return ret; + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + dev_info(dev, "cannot get reset-gpios %ld\n", + PTR_ERR(ctx->reset_gpio)); + return PTR_ERR(ctx->reset_gpio); + } + + for (i = 0; i < 4; i++) { + ctx->bs_gpio[i] = devm_gpiod_get_index_optional(dev, "bs", i, + GPIOD_OUT_HIGH); + if (!IS_ERR_OR_NULL(ctx->bs_gpio[i])) { + dev_dbg(dev, "bs%d-gpio is configured\n", i); + } else if (IS_ERR(ctx->bs_gpio[i])) { + dev_info(dev, "failed to get bs%d-gpio\n", i); + return PTR_ERR(ctx->bs_gpio[i]); + } + } + + ret = hx8369a_power_on(ctx); + if (ret < 0) { + dev_err(dev, "cannot power on\n"); + return ret; + } + + drm_panel_init(&ctx->panel); + ctx->panel.dev = dev; + ctx->panel.funcs = &hx8369a_dsi_drm_funcs; + + ret = drm_panel_add(&ctx->panel); + if (ret < 0) + return ret; + + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = ctx->pd->dsi_lanes; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + ret = mipi_dsi_attach(dsi); + if (ret < 0) + drm_panel_remove(&ctx->panel); + + return ret; +} + +static int hx8369a_dsi_remove(struct mipi_dsi_device *dsi) +{ + struct hx8369a *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&ctx->panel); + return hx8369a_power_off(ctx); +} + +static struct mipi_dsi_driver hx8369a_dsi_driver = { + .probe = hx8369a_dsi_probe, + .remove = hx8369a_dsi_remove, + .driver = { + .name = "panel-hx8369a-dsi", + .of_match_table = hx8369a_dsi_of_match, + }, +}; +module_mipi_dsi_driver(hx8369a_dsi_driver); + +MODULE_DESCRIPTION("Himax HX8369A panel driver"); +MODULE_AUTHOR("Liu Ying Ying.Liu@freescale.com"); +MODULE_LICENSE("GPL v2");
On Thu, Feb 12, 2015 at 02:01:38PM +0800, Liu Ying wrote:
This patch adds support for Himax HX8369A MIPI DSI panel.
If we're going to go ahead with this solution, this should read something like:
Add support for panels driven by the Himax HX8369A MIPI DSI bridge.
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index d845837..cd6fbb7 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -17,6 +17,11 @@ config DRM_PANEL_SIMPLE that it can be automatically turned off when the panel goes into a low power state.
+config DRM_PANEL_HIMAX_HX8369A
- tristate "Himax HX8369A panel"
Same here.
diff --git a/drivers/gpu/drm/panel/panel-himax-hx8369a.c b/drivers/gpu/drm/panel/panel-himax-hx8369a.c
[...]
+struct hx8369a {
- struct device *dev;
- struct drm_panel panel;
Maybe put this first in the structure so that the container_of() further below can be optimized away in most cases? Also, can you not reuse the dev field of struct drm_panel instead of adding another reference to it in the driver-private structure?
- const struct hx8369a_panel_desc *pd;
- struct regulator_bulk_data supplies[5];
- struct gpio_desc *reset_gpio;
- struct gpio_desc *bs_gpio[4];
- u8 res_sel;
The only purpose of this field seems to be to temporarily store a value that you could just as well simply return. See below.
+static int hx8369a_dcs_write(struct hx8369a *ctx, const char *func,
const void *data, size_t len)
+{
- struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
- ssize_t ret;
- ret = mipi_dsi_dcs_write_buffer(dsi, data, len);
- if (ret < 0)
dev_err(ctx->dev, "%s failed: %d\n", func, ret);
I think I'd put this error message into the caller. See below.
+#define hx8369a_dcs_write_seq(ctx, seq...) \ +({ \
- const u8 d[] = { seq }; \
- BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, \
"DCS sequence too big for stack"); \
That's not necessarily true. The stack is usually much larger than 64 bytes. But this seems to be common practice with MIPI DSI drivers, so you get to keep it if you really must have it. Note that the compiler will usually complain itself if you exceed the stack size, so this is somewhat redundant.
+static int hx8369a_dsi_set_display_related_register(struct hx8369a *ctx) +{
- u8 sec_p = (ctx->res_sel << 4) | 0x03;
You could call hx8369a_vm_to_res_sel() here and return the proper value rather than taking it from the driver-private data.
+static int hx8369a_dsi_set_mipi(struct hx8369a *ctx) +{
- u8 eleventh_p = ctx->pd->dsi_lanes == 2 ? 0x11 : 0x10;
I wish datasheets would be more verbose so that we could avoid this kind of meaningless names. Is there really no more documentation for any of these commands than just a fixed set of values with maybe one or two variable parameters?
If we ever need to support more than one panel with this driver this could get tricky.
+static int hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx) +{
- struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
- u8 format;
- int ret;
- switch (dsi->format) {
- case MIPI_DSI_FMT_RGB888:
- case MIPI_DSI_FMT_RGB666:
format = 0x77;
break;
- case MIPI_DSI_FMT_RGB565:
format = 0x55;
break;
- case MIPI_DSI_FMT_RGB666_PACKED:
format = 0x66;
break;
- default:
dev_err(ctx->dev, "unsupported DSI pixel format\n");
return -EINVAL;
- }
- ret = mipi_dsi_dcs_set_pixel_format(dsi, format);
- if (ret < 0)
dev_err(ctx->dev, "%s failed: %d\n", __func__, ret);
- return ret;
+}
This looks like something that could be extracted into a common helper. But that can be done separately and when a clear pattern emerges.
+static int hx8369a_dsi_panel_init(struct hx8369a *ctx) +{
- int (*funcs[])(struct hx8369a *ctx) = {
hx8369a_dsi_set_display_related_register,
hx8369a_dsi_set_display_waveform_cycle,
hx8369a_dsi_set_gip,
hx8369a_dsi_set_power,
hx8369a_dsi_set_vcom_voltage,
hx8369a_dsi_set_panel,
hx8369a_dsi_set_gamma_curve,
hx8369a_dsi_set_mipi,
hx8369a_dsi_set_interface_pixel_fomat,
hx8369a_dsi_set_column_address,
hx8369a_dsi_set_page_address,
hx8369a_dsi_write_cabc,
hx8369a_dsi_write_control_display,
- };
- int ret, i;
- for (i = 0; i < ARRAY_SIZE(funcs); i++) {
ret = funcs[i](ctx);
if (ret)
return ret;
I think you should be able to output an error message here in the form of:
dev_err(ctx->panel.dev, "%ps() failed: %d\n", funcs[i], ret);
%ps should print the name associated with the funcs[i] symbol. That way you can remove error reporting from the low-level macro/function.
+static int hx8369a_dsi_set_maximum_return_packet_size(struct hx8369a *ctx,
u16 size)
+{
- struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
- int ret;
- ret = mipi_dsi_set_maximum_return_packet_size(dsi, size);
- if (ret < 0)
dev_err(ctx->dev,
"error %d setting maximum return packet size to %d\n",
ret, size);
- return ret;
+}
+static int hx8369a_dsi_set_sequence(struct hx8369a *ctx) +{
- struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
- int ret;
- ret = hx8369a_dsi_set_extension_command(ctx);
- if (ret < 0)
goto out;
- ret = hx8369a_dsi_set_maximum_return_packet_size(ctx, 4);
- if (ret < 0)
goto out;
Why this wrapper? Since you already have the struct mipi_dsi_device *, can't you just do:
ret = mipi_dsi_set_maximum_return_packet_size(dsi, 4);
instead?
+static int hx8369a_dsi_disable(struct drm_panel *panel) +{
- struct hx8369a *ctx = panel_to_hx8369a(panel);
- return hx8369a_dsi_write_display_brightness(ctx,
HX8369A_MIN_BRIGHTNESS);
+}
Is brightness control something that you'd like to export to userspace using the backlight class perhaps?
+static int hx8369a_dsi_prepare(struct drm_panel *panel) +{
- struct hx8369a *ctx = panel_to_hx8369a(panel);
- return hx8369a_dsi_set_sequence(ctx);
+}
Why the indirection? Can't you just expand this function here? It isn't called anywhere else, so you gain nothing by putting it into a separate function.
+static int hx8369a_power_on(struct hx8369a *ctx) +{
- int ret;
- ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
- if (ret < 0)
return ret;
- msleep(ctx->pd->power_on_delay);
- gpiod_set_value(ctx->reset_gpio, 1);
- usleep_range(50, 60);
- gpiod_set_value(ctx->reset_gpio, 0);
Perhaps use gpiod_set_value_cansleep() to make this work with I2C GPIO expanders as well? You're sleeping in this function already, so it shouldn't be called from interrupt context anyway.
+static void hx8369a_vm_to_res_sel(struct hx8369a *ctx) +{
- const struct drm_display_mode *mode = ctx->pd->mode;
- switch (mode->hdisplay) {
- case 480:
switch (mode->vdisplay) {
case 864:
ctx->res_sel = HX8369A_RES_480_864;
break;
case 854:
ctx->res_sel = HX8369A_RES_480_854;
break;
case 800:
ctx->res_sel = HX8369A_RES_480_800;
break;
case 640:
ctx->res_sel = HX8369A_RES_480_640;
break;
case 720:
ctx->res_sel = HX8369A_RES_480_720;
break;
default:
break;
}
break;
- case 360:
if (mode->vdisplay == 640)
ctx->res_sel = HX8369A_RES_360_640;
break;
- default:
break;
- }
+}
Why not make this return an integer and remove the need for the res_sel field? Also, perhaps you'll want to error-check to make sure somebody isn't passing in an unsupported mode?
+static int hx8369a_dsi_probe(struct mipi_dsi_device *dsi) +{
- struct device *dev = &dsi->dev;
- const struct of_device_id *of_id =
of_match_device(hx8369a_dsi_of_match, dev);
- struct hx8369a *ctx;
- int ret, i;
i should be unsigned.
- ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
- if (!ctx)
return -ENOMEM;
- ctx->dev = dev;
- if (of_id) {
ctx->pd = of_id->data;
- } else {
dev_err(dev, "cannot find compatible device\n");
return -ENODEV;
- }
You should move this check up, before the allocation so that you can avoid it if not needed. Then again, I don't see a case where of_id would actually be NULL, so you might as well just skip the check. If somebody really added an entry with NULL data, they'll realize their mistake soon enough.
- ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
- if (IS_ERR(ctx->reset_gpio)) {
dev_info(dev, "cannot get reset-gpios %ld\n",
PTR_ERR(ctx->reset_gpio));
This should be dev_err(). Also the message is confusing because it could produce something like:
"cannot get reset-gpios -517"
and it isn't immediately obvious if -517 is a bogus GPIO number or an error code. A better error message would be, in my opinion:
"cannot get reset GPIO: %ld\n"
- for (i = 0; i < 4; i++) {
ctx->bs_gpio[i] = devm_gpiod_get_index_optional(dev, "bs", i,
GPIOD_OUT_HIGH);
if (!IS_ERR_OR_NULL(ctx->bs_gpio[i])) {
dev_dbg(dev, "bs%d-gpio is configured\n", i);
} else if (IS_ERR(ctx->bs_gpio[i])) {
dev_info(dev, "failed to get bs%d-gpio\n", i);
Should be dev_err() here, too. Also the names are somewhat confusing because they refer to a non-existing DT property. Perhaps it'd be better to name them after what the datasheet has. If the datasheet names them BS[0..3] for example, maybe make the error message:
dev_err(dev, "failed to get BS[%u] GPIO: %ld\n", i, PTR_ERR(ctx->bs_gpio[i]));
return PTR_ERR(ctx->bs_gpio[i]);
}
- }
You don't seem to be using these GPIOs either. I understand that you're only supporting a single mode, but maybe it'd be better to check that the chip is properly connected by matching the BS to the MIPI DSI video mode enum value.
- ret = hx8369a_power_on(ctx);
- if (ret < 0) {
dev_err(dev, "cannot power on\n");
return ret;
- }
Why power on here? Can't you postpone that until the panel is actually used (for example in ->prepare())?
+static struct mipi_dsi_driver hx8369a_dsi_driver = {
- .probe = hx8369a_dsi_probe,
- .remove = hx8369a_dsi_remove,
- .driver = {
.name = "panel-hx8369a-dsi",
Are there variants of hx8369a that don't use DSI as their control interface? If not, I'd simply omit the -dsi suffix.
Thierry
This patch adds support for MIPI DSI host controller.
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository. * To address Philipp's comment, mention that a common compatible string "snps,dw-mipi-dsi" should be appended. * To address Philipp's comment, add a new required clock pclk and clean up clock-names.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v3->v4: * None.
v2->v3: * As suggested by Phillip Zabel, change the clocks and the clock-names properties to use the pllref and core_cfg clocks only.
v1->v2: * None.
arch/arm/boot/dts/imx6qdl.dtsi | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/arch/arm/boot/dts/imx6qdl.dtsi b/arch/arm/boot/dts/imx6qdl.dtsi index 55aced8..0a4d7f7 100644 --- a/arch/arm/boot/dts/imx6qdl.dtsi +++ b/arch/arm/boot/dts/imx6qdl.dtsi @@ -1020,7 +1020,14 @@ mipi_dsi: mipi@021e0000 { #address-cells = <1>; #size-cells = <0>; + compatible = "fsl,imx6q-mipi-dsi", "snps,dw-mipi-dsi"; reg = <0x021e0000 0x4000>; + interrupts = <0 102 IRQ_TYPE_LEVEL_HIGH>; + gpr = <&gpr>; + clocks = <&clks IMX6QDL_CLK_MIPI_CORE_CFG>, + <&clks IMX6QDL_CLK_MIPI_CORE_CFG>, + <&clks IMX6QDL_CLK_MIPI_IPG>; + clock-names = "ref", "cfg", "pclk"; status = "disabled";
ports {
The TRULY TFT480800-16-E panel is driven by the Himax HX8369A driver IC. The driver IC supports several display/control interface modes, including the MIPI DSI video mode and command mode.
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v4->v5: * Replace the bs[3:0]-gpios properties with the bs-gpios property. This addresses Andrzej Hajda's comment.
v3->v4: * None.
v2->v3: * None.
v1->v2: * To address Thierry Reding's comments, remove several unnecessary properties as they can be implied by the compatible string. * Fix the compatible string. * Remove the display-timings node from the panel node as it can be implied by the compatible string as well. * Remove the status property as it is unneeded.
arch/arm/boot/dts/imx6qdl-sabresd.dtsi | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
diff --git a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi index f1cd214..9ff4ba5 100644 --- a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi +++ b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi @@ -480,6 +480,13 @@ MX6QDL_PAD_SD4_DAT7__SD4_DATA7 0x17059 >; }; + + pinctrl_mipi_panel: mipipanelgrp { + fsl,pins = < + MX6QDL_PAD_NANDF_CS0__GPIO6_IO11 0x1b0b0 + MX6QDL_PAD_NANDF_CS1__GPIO6_IO14 0x1b0b0 + >; + }; };
gpio_leds { @@ -516,6 +523,19 @@ }; };
+&mipi_dsi { + status = "okay"; + + panel { + compatible = "truly,tft480800-16-e-dsi"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_mipi_panel>; + reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>; + bs-gpios = <0>, <0>, <&gpio6 14 GPIO_ACTIVE_HIGH>, <0>; + }; +}; + &pcie { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_pcie>;
The new imx_v6_v7_defconfig is generated in this way: * make ARCH=arm imx_v6_v7_defconfig * make ARCH=arm savedefconfig * cp defconfig arch/arm/configs/imx_v6_v7_defconfig
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository, so the patch content is different from v8.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v4->v5: * None.
v3->v4: * None.
v2->v3: * None.
v1->v2: * None.
arch/arm/configs/imx_v6_v7_defconfig | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-)
diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig index 7c2075a..ec4b255 100644 --- a/arch/arm/configs/imx_v6_v7_defconfig +++ b/arch/arm/configs/imx_v6_v7_defconfig @@ -54,7 +54,6 @@ CONFIG_ARM_IMX6Q_CPUFREQ=y CONFIG_VFP=y CONFIG_NEON=y CONFIG_BINFMT_MISC=m -CONFIG_PM=y CONFIG_PM_DEBUG=y CONFIG_PM_TEST_SUSPEND=y CONFIG_NET=y @@ -163,13 +162,13 @@ CONFIG_SPI_IMX=y CONFIG_GPIO_SYSFS=y CONFIG_GPIO_MC9S08DZ60=y CONFIG_GPIO_STMPE=y +CONFIG_POWER_SUPPLY=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_IMX=y CONFIG_SENSORS_GPIO_FAN=y CONFIG_THERMAL=y CONFIG_CPU_THERMAL=y CONFIG_IMX_THERMAL=y -CONFIG_POWER_SUPPLY=y -CONFIG_POWER_RESET=y -CONFIG_POWER_RESET_IMX=y CONFIG_WATCHDOG=y CONFIG_IMX2_WDT=y CONFIG_MFD_DA9052_I2C=y @@ -198,7 +197,12 @@ CONFIG_SOC_CAMERA_OV2640=y CONFIG_IMX_IPUV3_CORE=y CONFIG_DRM=y CONFIG_DRM_PANEL_SIMPLE=y -CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_DRM_IMX=y +CONFIG_DRM_IMX_FB_HELPER=y +CONFIG_DRM_IMX_PARALLEL_DISPLAY=y +CONFIG_DRM_IMX_TVE=y +CONFIG_DRM_IMX_LDB=y +CONFIG_DRM_IMX_HDMI=y CONFIG_LCD_CLASS_DEVICE=y CONFIG_LCD_L4F00242T03=y CONFIG_LCD_PLATFORM=y @@ -257,13 +261,6 @@ CONFIG_IMX_SDMA=y CONFIG_MXS_DMA=y CONFIG_FSL_EDMA=y CONFIG_STAGING=y -CONFIG_DRM_IMX=y -CONFIG_DRM_IMX_FB_HELPER=y -CONFIG_DRM_IMX_PARALLEL_DISPLAY=y -CONFIG_DRM_IMX_TVE=y -CONFIG_DRM_IMX_LDB=y -CONFIG_DRM_IMX_IPUV3=y -CONFIG_DRM_IMX_HDMI=y # CONFIG_IOMMU_SUPPORT is not set CONFIG_PWM=y CONFIG_PWM_IMX=y
This patch adds support for MIPI DSI host controller.
The new imx_v6_v7_defconfig is generated in this way: * make ARCH=arm imx_v6_v7_defconfig * make ARCH=arm menuconfig and manually choose to build in the MIPI DSI host controller driver * make ARCH=arm savedefconfig * cp defconfig arch/arm/configs/imx_v6_v7_defconfig
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v4->v5: * None.
v3->v4: * None.
v2->v3: * None.
v1->v2: * None.
arch/arm/configs/imx_v6_v7_defconfig | 1 + 1 file changed, 1 insertion(+)
diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig index ec4b255..9e650e8f 100644 --- a/arch/arm/configs/imx_v6_v7_defconfig +++ b/arch/arm/configs/imx_v6_v7_defconfig @@ -203,6 +203,7 @@ CONFIG_DRM_IMX_PARALLEL_DISPLAY=y CONFIG_DRM_IMX_TVE=y CONFIG_DRM_IMX_LDB=y CONFIG_DRM_IMX_HDMI=y +CONFIG_DRM_IMX_MIPI_DSI=y CONFIG_LCD_CLASS_DEVICE=y CONFIG_LCD_L4F00242T03=y CONFIG_LCD_PLATFORM=y
This patch adds support for Himax HX8369A panel.
The new imx_v6_v7_defconfig is generated in this way: * make ARCH=arm imx_v6_v7_defconfig * make ARCH=arm menuconfig and manually choose to build in the Himax HX8369A panel driver * make ARCH=arm savedefconfig * cp defconfig arch/arm/configs/imx_v6_v7_defconfig
Signed-off-by: Liu Ying Ying.Liu@freescale.com --- v8->v9: * Rebase onto the imx-drm/next branch of Philipp Zabel's open git repository.
v7->v8: * None.
v6->v7: * None.
v5->v6: * None.
v4->v5: * None.
v3->v4: * None.
v2->v3: * None.
v1->v2: * Add the HIMAX prefix in the Kconfig name.
arch/arm/configs/imx_v6_v7_defconfig | 1 + 1 file changed, 1 insertion(+)
diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig index 9e650e8f..52d70a1 100644 --- a/arch/arm/configs/imx_v6_v7_defconfig +++ b/arch/arm/configs/imx_v6_v7_defconfig @@ -197,6 +197,7 @@ CONFIG_SOC_CAMERA_OV2640=y CONFIG_IMX_IPUV3_CORE=y CONFIG_DRM=y CONFIG_DRM_PANEL_SIMPLE=y +CONFIG_DRM_PANEL_HIMAX_HX8369A=y CONFIG_DRM_IMX=y CONFIG_DRM_IMX_FB_HELPER=y CONFIG_DRM_IMX_PARALLEL_DISPLAY=y
On Thu, Feb 12, 2015 at 02:01:23PM +0800, Liu Ying wrote:
Liu Ying (20): clk: divider: Correct parent clk round rate if no bestdiv is normally found
ARM: imx6q: Add GPR3 MIPI muxing control register field shift bits definition ARM: imx6q: clk: Add the video_27m clock ARM: imx6q: clk: Change hdmi_isfr clock's parent to be video_27m clock ARM: imx6q: clk: Change hsi_tx clock to be a shared clock gate ARM: imx6q: clk: Add support for mipi_core_cfg clock as a shared clock gate ARM: imx6q: clk: Add support for mipi_ipg clock as a shared clock gate ARM: dts: imx6qdl: Move existing MIPI DSI ports into a new 'ports' node
Applied above 7 i.MX patches (#2 ~ #8).
Shawn
drm/dsi: Add a helper to get bits per pixel of MIPI DSI pixel format Documentation: dt-bindings: Add bindings for Synopsys DW MIPI DSI DRM bridge driver drm/bridge: Add Synopsys DesignWare MIPI DSI host controller driver Documentation: dt-bindings: Add bindings for i.MX specific Synopsys DW MIPI DSI driver drm: imx: Support Synopsys DesignWare MIPI DSI host controller Documentation: dt-bindings: Add bindings for Himax HX8369A DRM panel driver drm: panel: Add support for Himax HX8369A MIPI DSI panel ARM: dtsi: imx6qdl: Add support for MIPI DSI host controller ARM: dts: imx6qdl-sabresd: Add support for TRULY TFT480800-16-E MIPI DSI panel ARM: imx_v6_v7_defconfig: Cleanup for imx drm being moved out of staging ARM: imx_v6_v7_defconfig: Add support for MIPI DSI host controller ARM: imx_v6_v7_defconfig: Add support for Himax HX8369A panel
dri-devel@lists.freedesktop.org