All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC 00/15] Add support for i.MX MIPI DSI DRM driver
@ 2014-12-10  8:37 ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

Hi,

This series adds support for i.MX MIPI DSI DRM driver.
Currently, the MIPI DSI driver only supports 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/15 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 drm-next branch.

[1] http://www.allshore.com/pdf/Himax_HX8369-A.pdf

Liu Ying (15):
  clk: divider: Correct parent clk round rate if no bestdiv is normally
    found
  of: Add vendor prefix for Himax Technologies Inc.
  of: Add vendor prefix for Truly Semiconductors Limited
  drm/dsi: Do not add DSI devices for the child nodes with input-port
    property
  ARM: dts: imx6qdl: Add input-port property to MIPI DSI node's CTRC
    child nodes
  ARM: dts: imx6q: Add MIPI DSI remote end points for IPU2 DI0/1 end
    points
  ARM: imx6q: Add GPR3 MIPI muxing control register field shift bits
    definition
  ARM: imx6q: clk: Add the video_27m clock
  drm: imx: Add MIPI DSI host controller 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/imx/mipi_dsi.txt       |   81 ++
 .../devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt  |    4 +
 .../devicetree/bindings/panel/himax,hx8369a.txt    |   86 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    2 +
 arch/arm/boot/dts/imx6q.dtsi                       |    4 +
 arch/arm/boot/dts/imx6qdl-sabresd.dtsi             |   41 +
 arch/arm/boot/dts/imx6qdl.dtsi                     |    9 +
 arch/arm/configs/imx_v6_v7_defconfig               |   17 +-
 arch/arm/mach-imx/clk-imx6q.c                      |    1 +
 drivers/clk/clk-divider.c                          |    3 +-
 drivers/gpu/drm/drm_mipi_dsi.c                     |    5 +-
 drivers/gpu/drm/imx/Kconfig                        |    6 +
 drivers/gpu/drm/imx/Makefile                       |    1 +
 drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
 drivers/gpu/drm/panel/Kconfig                      |    6 +
 drivers/gpu/drm/panel/Makefile                     |    1 +
 drivers/gpu/drm/panel/panel-hx8369a.c              |  627 ++++++++++++
 include/dt-bindings/clock/imx6qdl-clock.h          |    3 +-
 include/linux/mfd/syscon/imx6q-iomuxc-gpr.h        |    1 +
 19 files changed, 1903 insertions(+), 12 deletions(-)
 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/imx/imx-mipi-dsi.c
 create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c

-- 
2.1.0


^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 00/15] Add support for i.MX MIPI DSI DRM driver
@ 2014-12-10  8:37 ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

Hi,

This series adds support for i.MX MIPI DSI DRM driver.
Currently, the MIPI DSI driver only supports 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/15 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 drm-next branch.

[1] http://www.allshore.com/pdf/Himax_HX8369-A.pdf

Liu Ying (15):
  clk: divider: Correct parent clk round rate if no bestdiv is normally
    found
  of: Add vendor prefix for Himax Technologies Inc.
  of: Add vendor prefix for Truly Semiconductors Limited
  drm/dsi: Do not add DSI devices for the child nodes with input-port
    property
  ARM: dts: imx6qdl: Add input-port property to MIPI DSI node's CTRC
    child nodes
  ARM: dts: imx6q: Add MIPI DSI remote end points for IPU2 DI0/1 end
    points
  ARM: imx6q: Add GPR3 MIPI muxing control register field shift bits
    definition
  ARM: imx6q: clk: Add the video_27m clock
  drm: imx: Add MIPI DSI host controller 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/imx/mipi_dsi.txt       |   81 ++
 .../devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt  |    4 +
 .../devicetree/bindings/panel/himax,hx8369a.txt    |   86 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    2 +
 arch/arm/boot/dts/imx6q.dtsi                       |    4 +
 arch/arm/boot/dts/imx6qdl-sabresd.dtsi             |   41 +
 arch/arm/boot/dts/imx6qdl.dtsi                     |    9 +
 arch/arm/configs/imx_v6_v7_defconfig               |   17 +-
 arch/arm/mach-imx/clk-imx6q.c                      |    1 +
 drivers/clk/clk-divider.c                          |    3 +-
 drivers/gpu/drm/drm_mipi_dsi.c                     |    5 +-
 drivers/gpu/drm/imx/Kconfig                        |    6 +
 drivers/gpu/drm/imx/Makefile                       |    1 +
 drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
 drivers/gpu/drm/panel/Kconfig                      |    6 +
 drivers/gpu/drm/panel/Makefile                     |    1 +
 drivers/gpu/drm/panel/panel-hx8369a.c              |  627 ++++++++++++
 include/dt-bindings/clock/imx6qdl-clock.h          |    3 +-
 include/linux/mfd/syscon/imx6q-iomuxc-gpr.h        |    1 +
 19 files changed, 1903 insertions(+), 12 deletions(-)
 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/imx/imx-mipi-dsi.c
 create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c

-- 
2.1.0

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 00/15] Add support for i.MX MIPI DSI DRM driver
@ 2014-12-10  8:37 ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

This series adds support for i.MX MIPI DSI DRM driver.
Currently, the MIPI DSI driver only supports 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/15 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 drm-next branch.

[1] http://www.allshore.com/pdf/Himax_HX8369-A.pdf

Liu Ying (15):
  clk: divider: Correct parent clk round rate if no bestdiv is normally
    found
  of: Add vendor prefix for Himax Technologies Inc.
  of: Add vendor prefix for Truly Semiconductors Limited
  drm/dsi: Do not add DSI devices for the child nodes with input-port
    property
  ARM: dts: imx6qdl: Add input-port property to MIPI DSI node's CTRC
    child nodes
  ARM: dts: imx6q: Add MIPI DSI remote end points for IPU2 DI0/1 end
    points
  ARM: imx6q: Add GPR3 MIPI muxing control register field shift bits
    definition
  ARM: imx6q: clk: Add the video_27m clock
  drm: imx: Add MIPI DSI host controller 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/imx/mipi_dsi.txt       |   81 ++
 .../devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt  |    4 +
 .../devicetree/bindings/panel/himax,hx8369a.txt    |   86 ++
 .../devicetree/bindings/vendor-prefixes.txt        |    2 +
 arch/arm/boot/dts/imx6q.dtsi                       |    4 +
 arch/arm/boot/dts/imx6qdl-sabresd.dtsi             |   41 +
 arch/arm/boot/dts/imx6qdl.dtsi                     |    9 +
 arch/arm/configs/imx_v6_v7_defconfig               |   17 +-
 arch/arm/mach-imx/clk-imx6q.c                      |    1 +
 drivers/clk/clk-divider.c                          |    3 +-
 drivers/gpu/drm/drm_mipi_dsi.c                     |    5 +-
 drivers/gpu/drm/imx/Kconfig                        |    6 +
 drivers/gpu/drm/imx/Makefile                       |    1 +
 drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
 drivers/gpu/drm/panel/Kconfig                      |    6 +
 drivers/gpu/drm/panel/Makefile                     |    1 +
 drivers/gpu/drm/panel/panel-hx8369a.c              |  627 ++++++++++++
 include/dt-bindings/clock/imx6qdl-clock.h          |    3 +-
 include/linux/mfd/syscon/imx6q-iomuxc-gpr.h        |    1 +
 19 files changed, 1903 insertions(+), 12 deletions(-)
 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/imx/imx-mipi-dsi.c
 create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c

-- 
2.1.0

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 01/15] clk: divider: Correct parent clk round rate if no bestdiv is normally found
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

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>
---
 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;
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 01/15] clk: divider: Correct parent clk round rate if no bestdiv is normally found
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ,
	thierry.reding-Re5JQEeQqe8AvxtiuMwx3w,
	shawn.guo-QSEj5FYQhm4dnm+yROfE0A, kernel-bIcnvbaLZ9MEGnE8C9+IrQ,
	linux-lFZ/pmaqli7XmaaqVzeoHQ, mturquette-QSEj5FYQhm4dnm+yROfE0A,
	airlied-cv59FeDIM0c

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-KZfg59tc24xl57MIdRCFDg@public.gmane.org>
---
 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;
-- 
2.1.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 01/15] clk: divider: Correct parent clk round rate if no bestdiv is normally found
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

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>
---
 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;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 02/15] of: Add vendor prefix for Himax Technologies Inc.
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 78efebb..3cee528 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -69,6 +69,7 @@ hannstar	HannStar Display Corporation
 haoyu	Haoyu Microelectronic Co. Ltd.
 hisilicon	Hisilicon Limited.
 hit	Hitachi Ltd.
+himax	Himax Technologies, Inc.
 honeywell	Honeywell
 hp	Hewlett Packard
 i2se	I2SE GmbH
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 02/15] of: Add vendor prefix for Himax Technologies Inc.
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 78efebb..3cee528 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -69,6 +69,7 @@ hannstar	HannStar Display Corporation
 haoyu	Haoyu Microelectronic Co. Ltd.
 hisilicon	Hisilicon Limited.
 hit	Hitachi Ltd.
+himax	Himax Technologies, Inc.
 honeywell	Honeywell
 hp	Hewlett Packard
 i2se	I2SE GmbH
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 02/15] of: Add vendor prefix for Himax Technologies Inc.
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 78efebb..3cee528 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -69,6 +69,7 @@ hannstar	HannStar Display Corporation
 haoyu	Haoyu Microelectronic Co. Ltd.
 hisilicon	Hisilicon Limited.
 hit	Hitachi Ltd.
+himax	Himax Technologies, Inc.
 honeywell	Honeywell
 hp	Hewlett Packard
 i2se	I2SE GmbH
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 03/15] of: Add vendor prefix for Truly Semiconductors Limited
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 3cee528..8257f3a 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -158,6 +158,7 @@ tlm	Trusted Logic Mobility
 toradex	Toradex AG
 toshiba	Toshiba Corporation
 toumaz	Toumaz
+truly	Truly Semiconductors Limited
 usi	Universal Scientific Industrial Co., Ltd.
 v3	V3 Semiconductor
 variscite	Variscite Ltd.
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 03/15] of: Add vendor prefix for Truly Semiconductors Limited
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 3cee528..8257f3a 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -158,6 +158,7 @@ tlm	Trusted Logic Mobility
 toradex	Toradex AG
 toshiba	Toshiba Corporation
 toumaz	Toumaz
+truly	Truly Semiconductors Limited
 usi	Universal Scientific Industrial Co., Ltd.
 v3	V3 Semiconductor
 variscite	Variscite Ltd.
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 03/15] of: Add vendor prefix for Truly Semiconductors Limited
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 3cee528..8257f3a 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -158,6 +158,7 @@ tlm	Trusted Logic Mobility
 toradex	Toradex AG
 toshiba	Toshiba Corporation
 toumaz	Toumaz
+truly	Truly Semiconductors Limited
 usi	Universal Scientific Industrial Co., Ltd.
 v3	V3 Semiconductor
 variscite	Variscite Ltd.
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

The MIPI DSI bus driver would try to add a DSI device for a host's every
child node which contains the reg property.  Unfortunately, the existing
i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg
property, but the child nodes are ports pointing to dedicated CRTCs.
So, this patch phases out the child nodes with input-port property before
adding DSI devices for them and updates the MIPI DSI bus OF binding
documentation.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
 drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt b/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
index 973c272..1a1d3c1 100644
--- a/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
+++ b/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
@@ -36,6 +36,10 @@ Peripherals are represented as child nodes of the DSI host's node. Properties
 described here apply to all DSI peripherals, but individual bindings may want
 to define additional, device-specific properties.
 
+Please, do not add the input-port property to the child nodes which represent
+peripherals. Otherwise, the peripherals would be omitted by the MIPI DSI bus
+driver.
+
 Required properties:
 - reg: The virtual channel number of a DSI peripheral. Must be in the range
   from 0 to 3.
diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
index c0644bb..9adacfe 100644
--- a/drivers/gpu/drm/drm_mipi_dsi.c
+++ b/drivers/gpu/drm/drm_mipi_dsi.c
@@ -176,8 +176,9 @@ int mipi_dsi_host_register(struct mipi_dsi_host *host)
 	struct device_node *node;
 
 	for_each_available_child_of_node(host->dev->of_node, node) {
-		/* skip nodes without reg property */
-		if (!of_find_property(node, "reg", NULL))
+		/* skip nodes without reg property or with input-port property */
+		if (!of_find_property(node, "reg", NULL) ||
+		     of_find_property(node, "input-port", NULL))
 			continue;
 		of_mipi_dsi_device_add(host, node);
 	}
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

The MIPI DSI bus driver would try to add a DSI device for a host's every
child node which contains the reg property.  Unfortunately, the existing
i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg
property, but the child nodes are ports pointing to dedicated CRTCs.
So, this patch phases out the child nodes with input-port property before
adding DSI devices for them and updates the MIPI DSI bus OF binding
documentation.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
 drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt b/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
index 973c272..1a1d3c1 100644
--- a/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
+++ b/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
@@ -36,6 +36,10 @@ Peripherals are represented as child nodes of the DSI host's node. Properties
 described here apply to all DSI peripherals, but individual bindings may want
 to define additional, device-specific properties.
 
+Please, do not add the input-port property to the child nodes which represent
+peripherals. Otherwise, the peripherals would be omitted by the MIPI DSI bus
+driver.
+
 Required properties:
 - reg: The virtual channel number of a DSI peripheral. Must be in the range
   from 0 to 3.
diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
index c0644bb..9adacfe 100644
--- a/drivers/gpu/drm/drm_mipi_dsi.c
+++ b/drivers/gpu/drm/drm_mipi_dsi.c
@@ -176,8 +176,9 @@ int mipi_dsi_host_register(struct mipi_dsi_host *host)
 	struct device_node *node;
 
 	for_each_available_child_of_node(host->dev->of_node, node) {
-		/* skip nodes without reg property */
-		if (!of_find_property(node, "reg", NULL))
+		/* skip nodes without reg property or with input-port property */
+		if (!of_find_property(node, "reg", NULL) ||
+		     of_find_property(node, "input-port", NULL))
 			continue;
 		of_mipi_dsi_device_add(host, node);
 	}
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

The MIPI DSI bus driver would try to add a DSI device for a host's every
child node which contains the reg property.  Unfortunately, the existing
i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg
property, but the child nodes are ports pointing to dedicated CRTCs.
So, this patch phases out the child nodes with input-port property before
adding DSI devices for them and updates the MIPI DSI bus OF binding
documentation.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
 drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt b/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
index 973c272..1a1d3c1 100644
--- a/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
+++ b/Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
@@ -36,6 +36,10 @@ Peripherals are represented as child nodes of the DSI host's node. Properties
 described here apply to all DSI peripherals, but individual bindings may want
 to define additional, device-specific properties.
 
+Please, do not add the input-port property to the child nodes which represent
+peripherals. Otherwise, the peripherals would be omitted by the MIPI DSI bus
+driver.
+
 Required properties:
 - reg: The virtual channel number of a DSI peripheral. Must be in the range
   from 0 to 3.
diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
index c0644bb..9adacfe 100644
--- a/drivers/gpu/drm/drm_mipi_dsi.c
+++ b/drivers/gpu/drm/drm_mipi_dsi.c
@@ -176,8 +176,9 @@ int mipi_dsi_host_register(struct mipi_dsi_host *host)
 	struct device_node *node;
 
 	for_each_available_child_of_node(host->dev->of_node, node) {
-		/* skip nodes without reg property */
-		if (!of_find_property(node, "reg", NULL))
+		/* skip nodes without reg property or with input-port property */
+		if (!of_find_property(node, "reg", NULL) ||
+		     of_find_property(node, "input-port", NULL))
 			continue;
 		of_mipi_dsi_device_add(host, node);
 	}
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 05/15] ARM: dts: imx6qdl: Add input-port property to MIPI DSI node's CTRC child nodes
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

To phase out the MIPI DSI's child nodes which present DRM CRTCs from the
child nodes which represent MIPI DSI peripherals, we need to add input-port
property to the child nodes to be phased out.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 arch/arm/boot/dts/imx6q.dtsi   | 2 ++
 arch/arm/boot/dts/imx6qdl.dtsi | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
index e9f3646..e6a6d90 100644
--- a/arch/arm/boot/dts/imx6q.dtsi
+++ b/arch/arm/boot/dts/imx6q.dtsi
@@ -294,6 +294,7 @@
 &mipi_dsi {
 	port@2 {
 		reg = <2>;
+		input-port;
 
 		mipi_mux_2: endpoint {
 			remote-endpoint = <&ipu2_di0_mipi>;
@@ -302,6 +303,7 @@
 
 	port@3 {
 		reg = <3>;
+		input-port;
 
 		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 9596ed5..5d92ad7 100644
--- a/arch/arm/boot/dts/imx6qdl.dtsi
+++ b/arch/arm/boot/dts/imx6qdl.dtsi
@@ -1011,6 +1011,7 @@
 
 				port@0 {
 					reg = <0>;
+					input-port;
 
 					mipi_mux_0: endpoint {
 						remote-endpoint = <&ipu1_di0_mipi>;
@@ -1019,6 +1020,7 @@
 
 				port@1 {
 					reg = <1>;
+					input-port;
 
 					mipi_mux_1: endpoint {
 						remote-endpoint = <&ipu1_di1_mipi>;
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 05/15] ARM: dts: imx6qdl: Add input-port property to MIPI DSI node's CTRC child nodes
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

To phase out the MIPI DSI's child nodes which present DRM CRTCs from the
child nodes which represent MIPI DSI peripherals, we need to add input-port
property to the child nodes to be phased out.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 arch/arm/boot/dts/imx6q.dtsi   | 2 ++
 arch/arm/boot/dts/imx6qdl.dtsi | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
index e9f3646..e6a6d90 100644
--- a/arch/arm/boot/dts/imx6q.dtsi
+++ b/arch/arm/boot/dts/imx6q.dtsi
@@ -294,6 +294,7 @@
 &mipi_dsi {
 	port@2 {
 		reg = <2>;
+		input-port;
 
 		mipi_mux_2: endpoint {
 			remote-endpoint = <&ipu2_di0_mipi>;
@@ -302,6 +303,7 @@
 
 	port@3 {
 		reg = <3>;
+		input-port;
 
 		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 9596ed5..5d92ad7 100644
--- a/arch/arm/boot/dts/imx6qdl.dtsi
+++ b/arch/arm/boot/dts/imx6qdl.dtsi
@@ -1011,6 +1011,7 @@
 
 				port@0 {
 					reg = <0>;
+					input-port;
 
 					mipi_mux_0: endpoint {
 						remote-endpoint = <&ipu1_di0_mipi>;
@@ -1019,6 +1020,7 @@
 
 				port@1 {
 					reg = <1>;
+					input-port;
 
 					mipi_mux_1: endpoint {
 						remote-endpoint = <&ipu1_di1_mipi>;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 05/15] ARM: dts: imx6qdl: Add input-port property to MIPI DSI node's CTRC child nodes
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

To phase out the MIPI DSI's child nodes which present DRM CRTCs from the
child nodes which represent MIPI DSI peripherals, we need to add input-port
property to the child nodes to be phased out.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 arch/arm/boot/dts/imx6q.dtsi   | 2 ++
 arch/arm/boot/dts/imx6qdl.dtsi | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
index e9f3646..e6a6d90 100644
--- a/arch/arm/boot/dts/imx6q.dtsi
+++ b/arch/arm/boot/dts/imx6q.dtsi
@@ -294,6 +294,7 @@
 &mipi_dsi {
 	port at 2 {
 		reg = <2>;
+		input-port;
 
 		mipi_mux_2: endpoint {
 			remote-endpoint = <&ipu2_di0_mipi>;
@@ -302,6 +303,7 @@
 
 	port at 3 {
 		reg = <3>;
+		input-port;
 
 		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 9596ed5..5d92ad7 100644
--- a/arch/arm/boot/dts/imx6qdl.dtsi
+++ b/arch/arm/boot/dts/imx6qdl.dtsi
@@ -1011,6 +1011,7 @@
 
 				port at 0 {
 					reg = <0>;
+					input-port;
 
 					mipi_mux_0: endpoint {
 						remote-endpoint = <&ipu1_di0_mipi>;
@@ -1019,6 +1020,7 @@
 
 				port at 1 {
 					reg = <1>;
+					input-port;
 
 					mipi_mux_1: endpoint {
 						remote-endpoint = <&ipu1_di1_mipi>;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 06/15] ARM: dts: imx6q: Add MIPI DSI remote end points for IPU2 DI0/1 end points
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

This patch adds MIPI DSI remote end points for IPU2 DI0/1 end points.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 arch/arm/boot/dts/imx6q.dtsi | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
index e6a6d90..82507e7 100644
--- a/arch/arm/boot/dts/imx6q.dtsi
+++ b/arch/arm/boot/dts/imx6q.dtsi
@@ -185,6 +185,7 @@
 				};
 
 				ipu2_di0_mipi: endpoint@2 {
+					remote-endpoint = <&mipi_mux_2>;
 				};
 
 				ipu2_di0_lvds0: endpoint@3 {
@@ -206,6 +207,7 @@
 				};
 
 				ipu2_di1_mipi: endpoint@2 {
+					remote-endpoint = <&mipi_mux_3>;
 				};
 
 				ipu2_di1_lvds0: endpoint@3 {
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 06/15] ARM: dts: imx6q: Add MIPI DSI remote end points for IPU2 DI0/1 end points
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

This patch adds MIPI DSI remote end points for IPU2 DI0/1 end points.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 arch/arm/boot/dts/imx6q.dtsi | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
index e6a6d90..82507e7 100644
--- a/arch/arm/boot/dts/imx6q.dtsi
+++ b/arch/arm/boot/dts/imx6q.dtsi
@@ -185,6 +185,7 @@
 				};
 
 				ipu2_di0_mipi: endpoint@2 {
+					remote-endpoint = <&mipi_mux_2>;
 				};
 
 				ipu2_di0_lvds0: endpoint@3 {
@@ -206,6 +207,7 @@
 				};
 
 				ipu2_di1_mipi: endpoint@2 {
+					remote-endpoint = <&mipi_mux_3>;
 				};
 
 				ipu2_di1_lvds0: endpoint@3 {
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 06/15] ARM: dts: imx6q: Add MIPI DSI remote end points for IPU2 DI0/1 end points
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds MIPI DSI remote end points for IPU2 DI0/1 end points.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 arch/arm/boot/dts/imx6q.dtsi | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
index e6a6d90..82507e7 100644
--- a/arch/arm/boot/dts/imx6q.dtsi
+++ b/arch/arm/boot/dts/imx6q.dtsi
@@ -185,6 +185,7 @@
 				};
 
 				ipu2_di0_mipi: endpoint at 2 {
+					remote-endpoint = <&mipi_mux_2>;
 				};
 
 				ipu2_di0_lvds0: endpoint at 3 {
@@ -206,6 +207,7 @@
 				};
 
 				ipu2_di1_mipi: endpoint at 2 {
+					remote-endpoint = <&mipi_mux_3>;
 				};
 
 				ipu2_di1_lvds0: endpoint at 3 {
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 07/15] ARM: imx6q: Add GPR3 MIPI muxing control register field shift bits definition
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

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>
---
 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 ff44374..3b0bed4 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)
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 07/15] ARM: imx6q: Add GPR3 MIPI muxing control register field shift bits definition
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

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>
---
 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 ff44374..3b0bed4 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)
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 07/15] ARM: imx6q: Add GPR3 MIPI muxing control register field shift bits definition
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

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>
---
 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 ff44374..3b0bed4 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)
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 08/15] ARM: imx6q: clk: Add the video_27m clock
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

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>
---
 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 4e79da7..9470df3 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 */
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 08/15] ARM: imx6q: clk: Add the video_27m clock
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

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>
---
 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 4e79da7..9470df3 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 */
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 08/15] ARM: imx6q: clk: Add the video_27m clock
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

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>
---
 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 4e79da7..9470df3 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 */
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

This patch adds i.MX 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>
---
 .../devicetree/bindings/drm/imx/mipi_dsi.txt       |   81 ++
 drivers/gpu/drm/imx/Kconfig                        |    6 +
 drivers/gpu/drm/imx/Makefile                       |    1 +
 drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
 4 files changed, 1105 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
 create mode 100644 drivers/gpu/drm/imx/imx-mipi-dsi.c

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..3d07fd7
--- /dev/null
+++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
@@ -0,0 +1,81 @@
+Device-Tree bindings for MIPI DSI host controller
+
+MIPI DSI host controller
+========================
+
+The MIPI DSI host controller is a Synopsys DesignWare IP.
+It 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 : Should be "fsl,imx6q-mipi-dsi" for i.MX6q/sdl SoCs.
+ - 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 pllref, pllref_gate
+           and core_cfg clocks, as described in [1] and [2].
+ - panel@0 : A panel node which contains a display-timings child node as
+             defined in [3].
+ - port@[0-4] : Up to four port nodes with endpoint definitions as defined
+   in [4], corresponding to the four inputs to the controller multiplexer.
+   Note that each port node should contain the input-port property to
+   distinguish it from the panel node, as described in [5].
+
+[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+[2] Documentation/devicetree/bindings/clock/imx6q-clock.txt
+[3] Documentation/devicetree/bindings/video/display-timing.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";
+		reg = <0x021e0000 0x4000>;
+		interrupts = <0 102 IRQ_TYPE_LEVEL_HIGH>;
+		gpr = <&gpr>;
+		clocks = <&clks IMX6QDL_CLK_VIDEO_27M>,
+			 <&clks IMX6QDL_CLK_HSI_TX>,
+			 <&clks IMX6QDL_CLK_HSI_TX>;
+		clock-names = "pllref", "pllref_gate", "core_cfg";
+
+		port@0 {
+			reg = <0>;
+			input-port;
+
+			mipi_mux_0: endpoint {
+				remote-endpoint = <&ipu1_di0_mipi>;
+			};
+		};
+
+		port@1 {
+			reg = <1>;
+			input-port;
+
+			mipi_mux_1: endpoint {
+				remote-endpoint = <&ipu1_di1_mipi>;
+			};
+		};
+
+		panel@0 {
+			compatible = "himax,hx8369a-dsi";
+			reg = <0>;
+			/* ... */
+
+			display-timings {
+				/* ... */
+			};
+		};
+	};
diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig
index 82fb758..03f04fb 100644
--- a/drivers/gpu/drm/imx/Kconfig
+++ b/drivers/gpu/drm/imx/Kconfig
@@ -51,3 +51,9 @@ 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"
+	depends on DRM_IMX && MFD_SYSCON
+	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 582c438..4571d52 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) += imx-hdmi.o
+obj-$(CONFIG_DRM_IMX_MIPI_DSI) += imx-mipi-dsi.o
diff --git a/drivers/gpu/drm/imx/imx-mipi-dsi.c b/drivers/gpu/drm/imx/imx-mipi-dsi.c
new file mode 100644
index 0000000..c8ebeca
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx-mipi-dsi.c
@@ -0,0 +1,1017 @@
+/*
+ * i.MX drm driver - MIPI DSI Host Controller
+ *
+ * Copyright (C) 2011-2014 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/component.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/videodev2.h>
+#include <asm/div64.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include "imx-drm.h"
+
+#define DRIVER_NAME 			"imx-mipi-dsi"
+
+#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 IMX_MIPI_DSI_MAX_DATA_LANES	2
+
+#define PHY_STATUS_TIMEOUT		10
+#define CMD_PKT_STATUS_TIMEOUT		20
+
+#define IMX_MIPI_DSI_PLD_DATA_BUF_SIZE	4
+
+#define MHZ				1000000
+
+#define host_to_dsi(host) container_of(host, struct imx_mipi_dsi, dsi_host)
+#define con_to_dsi(x) container_of(x, struct imx_mipi_dsi, connector)
+#define enc_to_dsi(x) container_of(x, struct imx_mipi_dsi, encoder)
+
+struct imx_mipi_dsi {
+	struct mipi_dsi_host dsi_host;
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+	struct device_node *panel_node;
+	struct drm_panel *panel;
+	struct device *dev;
+
+	struct regmap *regmap;
+	void __iomem *base;
+
+	struct clk *pllref_clk;
+	struct clk *pllref_gate_clk;
+	struct clk *cfg_clk;
+
+	unsigned int lane_mbps; /* per lane */
+	u32 channel;
+	u32 lanes;
+	u32 format;
+	struct videomode vm;
+
+	bool enabled;
+};
+
+enum {
+	STATUS_TO_CLEAR,
+	STATUS_TO_SET,
+};
+
+struct dphy_pll_testdin_map {
+	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 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 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 inline void dsi_write(struct imx_mipi_dsi *dsi, u32 reg, u32 val)
+{
+	writel(val, dsi->base + reg);
+}
+
+static inline u32 dsi_read(struct imx_mipi_dsi *dsi, u32 reg)
+{
+	return readl(dsi->base + reg);
+}
+
+static inline void dsi_modify(struct imx_mipi_dsi *dsi, u32 reg,
+				       u32 mask, u32 val)
+{
+	u32 v = readl(dsi->base + reg);
+	v &= ~mask;
+	v |= val;
+	writel(v, dsi->base + reg);
+}
+
+static int format_to_bpp(struct imx_mipi_dsi *dsi)
+{
+	switch (dsi->format) {
+	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;
+	default:
+		dev_err(dsi->dev, "unsupported pixel format\n");
+		return -EINVAL;
+	}
+}
+
+static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
+			int timeout, bool to_set)
+{
+	u32 val;
+	bool out = false;
+
+	val = dsi_read(dsi, reg);
+	for (;;) {
+		out = to_set ? (val & status) : !(val & status);
+		if (out)
+			break;
+
+		if (!timeout--)
+			return -EFAULT;
+
+		msleep(1);
+		val = dsi_read(dsi, reg);
+	}
+	return 0;
+}
+
+static int imx_mipi_dsi_config_testdin(struct imx_mipi_dsi *dsi)
+{
+	int ret = 0;
+	int 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;
+	}
+
+	clk_prepare_enable(dsi->pllref_clk);
+	clk_prepare_enable(dsi->pllref_gate_clk);
+	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 imx_mipi_dsi_get_lane_bps(struct imx_mipi_dsi *dsi,
+				      unsigned int *final_mbps)
+{
+	int target_mbps, mpclk, bpp, i;
+	unsigned long pllref;
+
+	bpp = format_to_bpp(dsi);
+	if (bpp < 0)
+		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->vm.pixelclock, MHZ);
+	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 %dMbps\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_info(dsi->dev, "real DPHY pll clock frequency "
+				 "is %dMbps\n", *final_mbps);
+			return 0;
+		}
+	}
+
+	dev_err(dsi->dev, "DPHY clock frequency %dMbps is out of range\n",
+						target_mbps);
+
+	return -EINVAL;
+}
+
+static int imx_mipi_dsi_host_attach(struct mipi_dsi_host *host,
+				    struct mipi_dsi_device *device)
+{
+	struct imx_mipi_dsi *dsi = host_to_dsi(host);
+	int ret;
+
+	if (device->lanes > IMX_MIPI_DSI_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;
+	}
+
+	dsi->lanes = device->lanes;
+	dsi->channel = device->channel;
+	dsi->format = device->format;
+	dsi->panel_node = device->dev.of_node;
+	of_get_videomode(dsi->panel_node, &dsi->vm, 0);
+
+	ret = imx_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int imx_mipi_dsi_host_detach(struct mipi_dsi_host *host,
+				    struct mipi_dsi_device *device)
+{
+	struct imx_mipi_dsi *dsi = host_to_dsi(host);
+
+	dsi->panel_node = NULL;
+	dsi->panel = NULL;
+
+	return 0;
+}
+
+static int imx_mipi_dsi_gen_pkt_hdr_write(struct imx_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 avaliable 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 imx_mipi_dsi_dcs_short_write(struct imx_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 imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
+}
+
+static int imx_mipi_dsi_dcs_long_write(struct imx_mipi_dsi *dsi,
+				       const struct mipi_dsi_msg *msg)
+{
+	const u32 *tx_buf = msg->tx_buf;
+	int len = msg->tx_len, ret;
+	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
+
+	if (msg->tx_len < 3) {
+		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
+			msg->tx_len);
+		return -EINVAL;
+	}
+
+	/* write the bulks */
+	while (len / IMX_MIPI_DSI_PLD_DATA_BUF_SIZE) {
+		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
+		tx_buf++;
+		len -= IMX_MIPI_DSI_PLD_DATA_BUF_SIZE;
+		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 avaliable "
+					"write payload FIFO\n");
+			return ret;
+		}
+	}
+
+	/* write the remainder */
+	if (len > 0) {
+		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 avaliable "
+					"write payload FIFO\n");
+			return ret;
+		}
+		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
+	}
+
+	return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
+}
+
+static ssize_t imx_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
+					  const struct mipi_dsi_msg *msg)
+{
+	struct imx_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 = imx_mipi_dsi_dcs_short_write(dsi, msg);
+		break;
+	case MIPI_DSI_DCS_LONG_WRITE:
+		ret = imx_mipi_dsi_dcs_long_write(dsi, msg);
+		break;
+	default:
+		dev_err(dsi->dev, "unsupported message type\n");
+		ret = -EFAULT;
+	}
+
+	return ret;
+}
+
+static const struct mipi_dsi_host_ops imx_mipi_dsi_host_ops = {
+	.attach = imx_mipi_dsi_host_attach,
+	.detach = imx_mipi_dsi_host_detach,
+	.transfer = imx_mipi_dsi_host_transfer,
+};
+
+static enum drm_connector_status
+imx_mipi_dsi_detect(struct drm_connector *connector, bool force)
+{
+	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
+
+	if (!dsi->panel) {
+		dsi->panel = of_drm_find_panel(dsi->panel_node);
+		if (dsi->panel)
+			drm_panel_attach(dsi->panel, &dsi->connector);
+	}
+
+	if (dsi->panel)
+		return connector_status_connected;
+
+	return connector_status_disconnected;
+
+}
+
+static struct drm_connector_funcs imx_mipi_dsi_connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = imx_mipi_dsi_detect,
+	.destroy = imx_drm_connector_destroy,
+};
+
+static int imx_mipi_dsi_connector_get_modes(struct drm_connector *connector)
+{
+	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
+
+	return drm_panel_get_modes(dsi->panel);
+}
+
+static struct drm_encoder *imx_mipi_dsi_connector_best_encoder(
+					struct drm_connector *connector)
+{
+	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
+
+	return &dsi->encoder;
+}
+
+static struct drm_connector_helper_funcs imx_mipi_dsi_connector_helper_funcs = {
+	.get_modes = imx_mipi_dsi_connector_get_modes,
+	.best_encoder = imx_mipi_dsi_connector_best_encoder,
+};
+
+static struct drm_encoder_funcs imx_mipi_dsi_encoder_funcs = {
+	.destroy = imx_drm_encoder_destroy,
+};
+
+static void imx_mipi_dsi_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+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)
+{
+	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
+	u32 interface_pix_fmt;
+
+	switch (dsi->format) {
+	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:
+		dev_err(dsi->dev, "unsupported DSI pixel format\n");
+		return;
+	}
+
+	imx_drm_panel_format(encoder, interface_pix_fmt);
+}
+
+static void imx_mipi_dsi_init(struct imx_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 imx_mipi_dsi_dpi_config(struct imx_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 imx_mipi_dsi_packet_handler_config(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PCKHDL_CFG, EN_CRC_RX | EN_ECC_RX | EN_BTA);
+}
+
+static void imx_mipi_dsi_video_mode_config(struct imx_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 imx_mipi_dsi_video_packet_config(struct imx_mipi_dsi *dsi,
+					     struct drm_display_mode *mode)
+{
+	dsi_write(dsi, DSI_VID_PKT_CFG, VID_PKT_SIZE(mode->hdisplay));
+}
+
+static void imx_mipi_dsi_command_mode_config(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_CMD_MODE_CFG, CMD_MODE_ALL_LP | ENABLE_CMD_MODE);
+}
+
+/* Get lane byte clock cycles. */
+static u64 imx_mipi_dsi_get_hcomponent_lbcc(struct imx_mipi_dsi *dsi,
+						u64 hcomponent)
+{
+	u64 frac, lbcc;
+
+	lbcc = hcomponent * dsi->lane_mbps * MHZ / 8;
+	frac = do_div(lbcc, dsi->vm.pixelclock);
+	if (frac)
+		lbcc++;
+
+	return lbcc;
+}
+
+static void imx_mipi_dsi_line_timer_config(struct imx_mipi_dsi *dsi)
+{
+	u32 val = 0, htotal, hsa, hbp, lbcc;
+
+	htotal = dsi->vm.hactive + dsi->vm.hfront_porch +
+		 dsi->vm.hback_porch + dsi->vm.hsync_len;
+	hsa = dsi->vm.hsync_len;
+	hbp = dsi->vm.hback_porch;
+
+	lbcc = imx_mipi_dsi_get_hcomponent_lbcc(dsi, htotal);
+	val |= HLINE_TIME(lbcc);
+
+	lbcc = imx_mipi_dsi_get_hcomponent_lbcc(dsi, hsa);
+	val |= HSA_TIME(lbcc);
+
+	lbcc = imx_mipi_dsi_get_hcomponent_lbcc(dsi, hbp);
+	val |= HBP_TIME(lbcc);
+
+	dsi_write(dsi, DSI_TMR_LINE_CFG, val);
+}
+
+static void imx_mipi_dsi_vertical_timing_config(struct imx_mipi_dsi *dsi)
+{
+	u32 val;
+
+	val = V_ACTIVE_LINES(dsi->vm.vactive) | VSA_LINES(dsi->vm.vsync_len) |
+	      VFP_LINES(dsi->vm.vfront_porch) | VBP_LINES(dsi->vm.vback_porch);
+
+	dsi_write(dsi, DSI_VTIMING_CFG, val);
+}
+
+static void imx_mipi_dsi_dphy_timing_config(struct imx_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 imx_mipi_dsi_dphy_interface_config(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) |
+						N_LANES(dsi->lanes));
+}
+
+static void imx_mipi_dsi_clear_err(struct imx_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 imx_mipi_dsi_encoder_mode_set(struct drm_encoder *encoder,
+					  struct drm_display_mode *mode,
+					  struct drm_display_mode *adjusted_mode)
+{
+	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
+
+	clk_prepare_enable(dsi->cfg_clk);
+	imx_mipi_dsi_init(dsi);
+	imx_mipi_dsi_dpi_config(dsi, mode);
+	imx_mipi_dsi_packet_handler_config(dsi);
+	imx_mipi_dsi_video_mode_config(dsi);
+	imx_mipi_dsi_video_packet_config(dsi, mode);
+	imx_mipi_dsi_command_mode_config(dsi);
+	imx_mipi_dsi_line_timer_config(dsi);
+	imx_mipi_dsi_vertical_timing_config(dsi);
+	imx_mipi_dsi_dphy_timing_config(dsi);
+	imx_mipi_dsi_dphy_interface_config(dsi);
+	imx_mipi_dsi_clear_err(dsi);
+	imx_mipi_dsi_config_testdin(dsi);
+	drm_panel_prepare(dsi->panel);
+	clk_disable_unprepare(dsi->cfg_clk);
+}
+
+static void imx_mipi_dsi_disable_command_mode(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PWR_UP, RESET);
+	dsi_modify(dsi, DSI_CMD_MODE_CFG,
+		   ENABLE_CMD_MODE_MASK, DISABLE_CMD_MODE);
+}
+
+static void imx_mipi_dsi_enable_video_mode(struct imx_mipi_dsi *dsi)
+{
+	imx_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 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);
+
+	if (dsi->enabled)
+		return;
+
+	imx_mipi_dsi_set_ipu_di_mux(dsi, mux);
+
+	drm_panel_enable(dsi->panel);
+
+	imx_mipi_dsi_disable_command_mode(dsi);
+	imx_mipi_dsi_enable_video_mode(dsi);
+
+	dsi->enabled = true;
+}
+
+static void imx_mipi_dsi_enable_command_mode(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PWR_UP, RESET);
+	dsi_modify(dsi, DSI_CMD_MODE_CFG,
+		   ENABLE_CMD_MODE_MASK, ENABLE_CMD_MODE);
+}
+
+static void imx_mipi_dsi_disable_video_mode(struct imx_mipi_dsi *dsi)
+{
+	dsi_modify(dsi, DSI_VID_MODE_CFG,
+		   ENABLE_VIDEO_MODE_MASK, DISABLE_VIDEO_MODE);
+	dsi_write(dsi, DSI_PWR_UP, POWERUP);
+}
+
+static void imx_mipi_dsi_disable(struct imx_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 imx_mipi_dsi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
+
+	if (!dsi->enabled)
+		return;
+
+	clk_prepare_enable(dsi->cfg_clk);
+
+	drm_panel_disable(dsi->panel);
+	imx_mipi_dsi_enable_command_mode(dsi);
+	imx_mipi_dsi_disable_video_mode(dsi);
+	drm_panel_unprepare(dsi->panel);
+
+	imx_mipi_dsi_disable(dsi);
+
+	clk_disable_unprepare(dsi->cfg_clk);
+	clk_disable_unprepare(dsi->pllref_gate_clk);
+	clk_disable_unprepare(dsi->pllref_clk);
+
+	dsi->enabled = false;
+}
+
+static struct drm_encoder_helper_funcs imx_mipi_dsi_encoder_helper_funcs = {
+	.dpms = imx_mipi_dsi_encoder_dpms,
+	.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);
+
+	drm_connector_helper_add(&dsi->connector,
+			&imx_mipi_dsi_connector_helper_funcs);
+	drm_connector_init(drm, &dsi->connector, &imx_mipi_dsi_connector_funcs,
+			   DRM_MODE_CONNECTOR_DSI);
+
+	drm_mode_connector_attach_encoder(&dsi->connector, &dsi->encoder);
+	return 0;
+}
+
+static int imx_mipi_dsi_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct device_node *np = dev->of_node;
+	struct imx_mipi_dsi *dsi;
+	struct resource *res;
+	u32 val;
+	int ret;
+
+	dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+	if (!dsi)
+		return -ENOMEM;
+
+	dsi->dev = dev;
+	dsi->dsi_host.ops = &imx_mipi_dsi_host_ops;
+	dsi->dsi_host.dev = dev;
+	dsi->enabled = false;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dsi->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dsi->base))
+		return PTR_ERR(dsi->base);
+
+	dsi->regmap = syscon_regmap_lookup_by_phandle(np, "gpr");
+	if (IS_ERR(dsi->regmap))
+		return PTR_ERR(dsi->regmap);
+
+	dsi->pllref_clk = devm_clk_get(dev, "pllref");
+	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;
+	}
+
+	dsi->pllref_gate_clk = devm_clk_get(dev, "pllref_gate");
+	if (IS_ERR(dsi->pllref_gate_clk)) {
+		ret = PTR_ERR(dsi->pllref_gate_clk);
+		dev_err(dev, "Unable to get pll reference gate clock: %d\n", ret);
+		return ret;
+	}
+
+	dsi->cfg_clk = devm_clk_get(dev, "core_cfg");
+	if (IS_ERR(dsi->cfg_clk)) {
+		ret = PTR_ERR(dsi->cfg_clk);
+		dev_err(dev, "Unable to get configuration clock: %d\n", ret);
+		return ret;
+	}
+
+	clk_prepare_enable(dsi->cfg_clk);
+	val = dsi_read(dsi, DSI_VERSION);
+	clk_disable_unprepare(dsi->cfg_clk);
+
+	dev_info(dev, "version number is 0x%08x\n", val);
+
+	ret = imx_mipi_dsi_register(drm, dsi);
+	if (ret)
+		return ret;
+
+	dev_set_drvdata(dev, dsi);
+
+	return mipi_dsi_host_register(&dsi->dsi_host);
+}
+
+static void imx_mipi_dsi_unbind(struct device *dev, struct device *master,
+	void *data)
+{
+	struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
+
+	mipi_dsi_host_unregister(&dsi->dsi_host);
+}
+
+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 const struct of_device_id imx_mipi_dsi_dt_ids[] = {
+	{ .compatible = "fsl,imx6q-mipi-dsi", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_mipi_dsi_dt_ids);
+
+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 driver");
+MODULE_AUTHOR("Liu Ying <Ying.Liu@freescale.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

This patch adds i.MX 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>
---
 .../devicetree/bindings/drm/imx/mipi_dsi.txt       |   81 ++
 drivers/gpu/drm/imx/Kconfig                        |    6 +
 drivers/gpu/drm/imx/Makefile                       |    1 +
 drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
 4 files changed, 1105 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
 create mode 100644 drivers/gpu/drm/imx/imx-mipi-dsi.c

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..3d07fd7
--- /dev/null
+++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
@@ -0,0 +1,81 @@
+Device-Tree bindings for MIPI DSI host controller
+
+MIPI DSI host controller
+========================
+
+The MIPI DSI host controller is a Synopsys DesignWare IP.
+It 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 : Should be "fsl,imx6q-mipi-dsi" for i.MX6q/sdl SoCs.
+ - 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 pllref, pllref_gate
+           and core_cfg clocks, as described in [1] and [2].
+ - panel@0 : A panel node which contains a display-timings child node as
+             defined in [3].
+ - port@[0-4] : Up to four port nodes with endpoint definitions as defined
+   in [4], corresponding to the four inputs to the controller multiplexer.
+   Note that each port node should contain the input-port property to
+   distinguish it from the panel node, as described in [5].
+
+[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+[2] Documentation/devicetree/bindings/clock/imx6q-clock.txt
+[3] Documentation/devicetree/bindings/video/display-timing.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";
+		reg = <0x021e0000 0x4000>;
+		interrupts = <0 102 IRQ_TYPE_LEVEL_HIGH>;
+		gpr = <&gpr>;
+		clocks = <&clks IMX6QDL_CLK_VIDEO_27M>,
+			 <&clks IMX6QDL_CLK_HSI_TX>,
+			 <&clks IMX6QDL_CLK_HSI_TX>;
+		clock-names = "pllref", "pllref_gate", "core_cfg";
+
+		port@0 {
+			reg = <0>;
+			input-port;
+
+			mipi_mux_0: endpoint {
+				remote-endpoint = <&ipu1_di0_mipi>;
+			};
+		};
+
+		port@1 {
+			reg = <1>;
+			input-port;
+
+			mipi_mux_1: endpoint {
+				remote-endpoint = <&ipu1_di1_mipi>;
+			};
+		};
+
+		panel@0 {
+			compatible = "himax,hx8369a-dsi";
+			reg = <0>;
+			/* ... */
+
+			display-timings {
+				/* ... */
+			};
+		};
+	};
diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig
index 82fb758..03f04fb 100644
--- a/drivers/gpu/drm/imx/Kconfig
+++ b/drivers/gpu/drm/imx/Kconfig
@@ -51,3 +51,9 @@ 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"
+	depends on DRM_IMX && MFD_SYSCON
+	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 582c438..4571d52 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) += imx-hdmi.o
+obj-$(CONFIG_DRM_IMX_MIPI_DSI) += imx-mipi-dsi.o
diff --git a/drivers/gpu/drm/imx/imx-mipi-dsi.c b/drivers/gpu/drm/imx/imx-mipi-dsi.c
new file mode 100644
index 0000000..c8ebeca
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx-mipi-dsi.c
@@ -0,0 +1,1017 @@
+/*
+ * i.MX drm driver - MIPI DSI Host Controller
+ *
+ * Copyright (C) 2011-2014 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/component.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/videodev2.h>
+#include <asm/div64.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include "imx-drm.h"
+
+#define DRIVER_NAME 			"imx-mipi-dsi"
+
+#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 IMX_MIPI_DSI_MAX_DATA_LANES	2
+
+#define PHY_STATUS_TIMEOUT		10
+#define CMD_PKT_STATUS_TIMEOUT		20
+
+#define IMX_MIPI_DSI_PLD_DATA_BUF_SIZE	4
+
+#define MHZ				1000000
+
+#define host_to_dsi(host) container_of(host, struct imx_mipi_dsi, dsi_host)
+#define con_to_dsi(x) container_of(x, struct imx_mipi_dsi, connector)
+#define enc_to_dsi(x) container_of(x, struct imx_mipi_dsi, encoder)
+
+struct imx_mipi_dsi {
+	struct mipi_dsi_host dsi_host;
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+	struct device_node *panel_node;
+	struct drm_panel *panel;
+	struct device *dev;
+
+	struct regmap *regmap;
+	void __iomem *base;
+
+	struct clk *pllref_clk;
+	struct clk *pllref_gate_clk;
+	struct clk *cfg_clk;
+
+	unsigned int lane_mbps; /* per lane */
+	u32 channel;
+	u32 lanes;
+	u32 format;
+	struct videomode vm;
+
+	bool enabled;
+};
+
+enum {
+	STATUS_TO_CLEAR,
+	STATUS_TO_SET,
+};
+
+struct dphy_pll_testdin_map {
+	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 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 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 inline void dsi_write(struct imx_mipi_dsi *dsi, u32 reg, u32 val)
+{
+	writel(val, dsi->base + reg);
+}
+
+static inline u32 dsi_read(struct imx_mipi_dsi *dsi, u32 reg)
+{
+	return readl(dsi->base + reg);
+}
+
+static inline void dsi_modify(struct imx_mipi_dsi *dsi, u32 reg,
+				       u32 mask, u32 val)
+{
+	u32 v = readl(dsi->base + reg);
+	v &= ~mask;
+	v |= val;
+	writel(v, dsi->base + reg);
+}
+
+static int format_to_bpp(struct imx_mipi_dsi *dsi)
+{
+	switch (dsi->format) {
+	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;
+	default:
+		dev_err(dsi->dev, "unsupported pixel format\n");
+		return -EINVAL;
+	}
+}
+
+static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
+			int timeout, bool to_set)
+{
+	u32 val;
+	bool out = false;
+
+	val = dsi_read(dsi, reg);
+	for (;;) {
+		out = to_set ? (val & status) : !(val & status);
+		if (out)
+			break;
+
+		if (!timeout--)
+			return -EFAULT;
+
+		msleep(1);
+		val = dsi_read(dsi, reg);
+	}
+	return 0;
+}
+
+static int imx_mipi_dsi_config_testdin(struct imx_mipi_dsi *dsi)
+{
+	int ret = 0;
+	int 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;
+	}
+
+	clk_prepare_enable(dsi->pllref_clk);
+	clk_prepare_enable(dsi->pllref_gate_clk);
+	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 imx_mipi_dsi_get_lane_bps(struct imx_mipi_dsi *dsi,
+				      unsigned int *final_mbps)
+{
+	int target_mbps, mpclk, bpp, i;
+	unsigned long pllref;
+
+	bpp = format_to_bpp(dsi);
+	if (bpp < 0)
+		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->vm.pixelclock, MHZ);
+	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 %dMbps\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_info(dsi->dev, "real DPHY pll clock frequency "
+				 "is %dMbps\n", *final_mbps);
+			return 0;
+		}
+	}
+
+	dev_err(dsi->dev, "DPHY clock frequency %dMbps is out of range\n",
+						target_mbps);
+
+	return -EINVAL;
+}
+
+static int imx_mipi_dsi_host_attach(struct mipi_dsi_host *host,
+				    struct mipi_dsi_device *device)
+{
+	struct imx_mipi_dsi *dsi = host_to_dsi(host);
+	int ret;
+
+	if (device->lanes > IMX_MIPI_DSI_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;
+	}
+
+	dsi->lanes = device->lanes;
+	dsi->channel = device->channel;
+	dsi->format = device->format;
+	dsi->panel_node = device->dev.of_node;
+	of_get_videomode(dsi->panel_node, &dsi->vm, 0);
+
+	ret = imx_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int imx_mipi_dsi_host_detach(struct mipi_dsi_host *host,
+				    struct mipi_dsi_device *device)
+{
+	struct imx_mipi_dsi *dsi = host_to_dsi(host);
+
+	dsi->panel_node = NULL;
+	dsi->panel = NULL;
+
+	return 0;
+}
+
+static int imx_mipi_dsi_gen_pkt_hdr_write(struct imx_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 avaliable 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 imx_mipi_dsi_dcs_short_write(struct imx_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 imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
+}
+
+static int imx_mipi_dsi_dcs_long_write(struct imx_mipi_dsi *dsi,
+				       const struct mipi_dsi_msg *msg)
+{
+	const u32 *tx_buf = msg->tx_buf;
+	int len = msg->tx_len, ret;
+	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
+
+	if (msg->tx_len < 3) {
+		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
+			msg->tx_len);
+		return -EINVAL;
+	}
+
+	/* write the bulks */
+	while (len / IMX_MIPI_DSI_PLD_DATA_BUF_SIZE) {
+		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
+		tx_buf++;
+		len -= IMX_MIPI_DSI_PLD_DATA_BUF_SIZE;
+		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 avaliable "
+					"write payload FIFO\n");
+			return ret;
+		}
+	}
+
+	/* write the remainder */
+	if (len > 0) {
+		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 avaliable "
+					"write payload FIFO\n");
+			return ret;
+		}
+		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
+	}
+
+	return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
+}
+
+static ssize_t imx_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
+					  const struct mipi_dsi_msg *msg)
+{
+	struct imx_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 = imx_mipi_dsi_dcs_short_write(dsi, msg);
+		break;
+	case MIPI_DSI_DCS_LONG_WRITE:
+		ret = imx_mipi_dsi_dcs_long_write(dsi, msg);
+		break;
+	default:
+		dev_err(dsi->dev, "unsupported message type\n");
+		ret = -EFAULT;
+	}
+
+	return ret;
+}
+
+static const struct mipi_dsi_host_ops imx_mipi_dsi_host_ops = {
+	.attach = imx_mipi_dsi_host_attach,
+	.detach = imx_mipi_dsi_host_detach,
+	.transfer = imx_mipi_dsi_host_transfer,
+};
+
+static enum drm_connector_status
+imx_mipi_dsi_detect(struct drm_connector *connector, bool force)
+{
+	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
+
+	if (!dsi->panel) {
+		dsi->panel = of_drm_find_panel(dsi->panel_node);
+		if (dsi->panel)
+			drm_panel_attach(dsi->panel, &dsi->connector);
+	}
+
+	if (dsi->panel)
+		return connector_status_connected;
+
+	return connector_status_disconnected;
+
+}
+
+static struct drm_connector_funcs imx_mipi_dsi_connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = imx_mipi_dsi_detect,
+	.destroy = imx_drm_connector_destroy,
+};
+
+static int imx_mipi_dsi_connector_get_modes(struct drm_connector *connector)
+{
+	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
+
+	return drm_panel_get_modes(dsi->panel);
+}
+
+static struct drm_encoder *imx_mipi_dsi_connector_best_encoder(
+					struct drm_connector *connector)
+{
+	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
+
+	return &dsi->encoder;
+}
+
+static struct drm_connector_helper_funcs imx_mipi_dsi_connector_helper_funcs = {
+	.get_modes = imx_mipi_dsi_connector_get_modes,
+	.best_encoder = imx_mipi_dsi_connector_best_encoder,
+};
+
+static struct drm_encoder_funcs imx_mipi_dsi_encoder_funcs = {
+	.destroy = imx_drm_encoder_destroy,
+};
+
+static void imx_mipi_dsi_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+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)
+{
+	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
+	u32 interface_pix_fmt;
+
+	switch (dsi->format) {
+	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:
+		dev_err(dsi->dev, "unsupported DSI pixel format\n");
+		return;
+	}
+
+	imx_drm_panel_format(encoder, interface_pix_fmt);
+}
+
+static void imx_mipi_dsi_init(struct imx_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 imx_mipi_dsi_dpi_config(struct imx_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 imx_mipi_dsi_packet_handler_config(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PCKHDL_CFG, EN_CRC_RX | EN_ECC_RX | EN_BTA);
+}
+
+static void imx_mipi_dsi_video_mode_config(struct imx_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 imx_mipi_dsi_video_packet_config(struct imx_mipi_dsi *dsi,
+					     struct drm_display_mode *mode)
+{
+	dsi_write(dsi, DSI_VID_PKT_CFG, VID_PKT_SIZE(mode->hdisplay));
+}
+
+static void imx_mipi_dsi_command_mode_config(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_CMD_MODE_CFG, CMD_MODE_ALL_LP | ENABLE_CMD_MODE);
+}
+
+/* Get lane byte clock cycles. */
+static u64 imx_mipi_dsi_get_hcomponent_lbcc(struct imx_mipi_dsi *dsi,
+						u64 hcomponent)
+{
+	u64 frac, lbcc;
+
+	lbcc = hcomponent * dsi->lane_mbps * MHZ / 8;
+	frac = do_div(lbcc, dsi->vm.pixelclock);
+	if (frac)
+		lbcc++;
+
+	return lbcc;
+}
+
+static void imx_mipi_dsi_line_timer_config(struct imx_mipi_dsi *dsi)
+{
+	u32 val = 0, htotal, hsa, hbp, lbcc;
+
+	htotal = dsi->vm.hactive + dsi->vm.hfront_porch +
+		 dsi->vm.hback_porch + dsi->vm.hsync_len;
+	hsa = dsi->vm.hsync_len;
+	hbp = dsi->vm.hback_porch;
+
+	lbcc = imx_mipi_dsi_get_hcomponent_lbcc(dsi, htotal);
+	val |= HLINE_TIME(lbcc);
+
+	lbcc = imx_mipi_dsi_get_hcomponent_lbcc(dsi, hsa);
+	val |= HSA_TIME(lbcc);
+
+	lbcc = imx_mipi_dsi_get_hcomponent_lbcc(dsi, hbp);
+	val |= HBP_TIME(lbcc);
+
+	dsi_write(dsi, DSI_TMR_LINE_CFG, val);
+}
+
+static void imx_mipi_dsi_vertical_timing_config(struct imx_mipi_dsi *dsi)
+{
+	u32 val;
+
+	val = V_ACTIVE_LINES(dsi->vm.vactive) | VSA_LINES(dsi->vm.vsync_len) |
+	      VFP_LINES(dsi->vm.vfront_porch) | VBP_LINES(dsi->vm.vback_porch);
+
+	dsi_write(dsi, DSI_VTIMING_CFG, val);
+}
+
+static void imx_mipi_dsi_dphy_timing_config(struct imx_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 imx_mipi_dsi_dphy_interface_config(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) |
+						N_LANES(dsi->lanes));
+}
+
+static void imx_mipi_dsi_clear_err(struct imx_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 imx_mipi_dsi_encoder_mode_set(struct drm_encoder *encoder,
+					  struct drm_display_mode *mode,
+					  struct drm_display_mode *adjusted_mode)
+{
+	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
+
+	clk_prepare_enable(dsi->cfg_clk);
+	imx_mipi_dsi_init(dsi);
+	imx_mipi_dsi_dpi_config(dsi, mode);
+	imx_mipi_dsi_packet_handler_config(dsi);
+	imx_mipi_dsi_video_mode_config(dsi);
+	imx_mipi_dsi_video_packet_config(dsi, mode);
+	imx_mipi_dsi_command_mode_config(dsi);
+	imx_mipi_dsi_line_timer_config(dsi);
+	imx_mipi_dsi_vertical_timing_config(dsi);
+	imx_mipi_dsi_dphy_timing_config(dsi);
+	imx_mipi_dsi_dphy_interface_config(dsi);
+	imx_mipi_dsi_clear_err(dsi);
+	imx_mipi_dsi_config_testdin(dsi);
+	drm_panel_prepare(dsi->panel);
+	clk_disable_unprepare(dsi->cfg_clk);
+}
+
+static void imx_mipi_dsi_disable_command_mode(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PWR_UP, RESET);
+	dsi_modify(dsi, DSI_CMD_MODE_CFG,
+		   ENABLE_CMD_MODE_MASK, DISABLE_CMD_MODE);
+}
+
+static void imx_mipi_dsi_enable_video_mode(struct imx_mipi_dsi *dsi)
+{
+	imx_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 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);
+
+	if (dsi->enabled)
+		return;
+
+	imx_mipi_dsi_set_ipu_di_mux(dsi, mux);
+
+	drm_panel_enable(dsi->panel);
+
+	imx_mipi_dsi_disable_command_mode(dsi);
+	imx_mipi_dsi_enable_video_mode(dsi);
+
+	dsi->enabled = true;
+}
+
+static void imx_mipi_dsi_enable_command_mode(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PWR_UP, RESET);
+	dsi_modify(dsi, DSI_CMD_MODE_CFG,
+		   ENABLE_CMD_MODE_MASK, ENABLE_CMD_MODE);
+}
+
+static void imx_mipi_dsi_disable_video_mode(struct imx_mipi_dsi *dsi)
+{
+	dsi_modify(dsi, DSI_VID_MODE_CFG,
+		   ENABLE_VIDEO_MODE_MASK, DISABLE_VIDEO_MODE);
+	dsi_write(dsi, DSI_PWR_UP, POWERUP);
+}
+
+static void imx_mipi_dsi_disable(struct imx_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 imx_mipi_dsi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
+
+	if (!dsi->enabled)
+		return;
+
+	clk_prepare_enable(dsi->cfg_clk);
+
+	drm_panel_disable(dsi->panel);
+	imx_mipi_dsi_enable_command_mode(dsi);
+	imx_mipi_dsi_disable_video_mode(dsi);
+	drm_panel_unprepare(dsi->panel);
+
+	imx_mipi_dsi_disable(dsi);
+
+	clk_disable_unprepare(dsi->cfg_clk);
+	clk_disable_unprepare(dsi->pllref_gate_clk);
+	clk_disable_unprepare(dsi->pllref_clk);
+
+	dsi->enabled = false;
+}
+
+static struct drm_encoder_helper_funcs imx_mipi_dsi_encoder_helper_funcs = {
+	.dpms = imx_mipi_dsi_encoder_dpms,
+	.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);
+
+	drm_connector_helper_add(&dsi->connector,
+			&imx_mipi_dsi_connector_helper_funcs);
+	drm_connector_init(drm, &dsi->connector, &imx_mipi_dsi_connector_funcs,
+			   DRM_MODE_CONNECTOR_DSI);
+
+	drm_mode_connector_attach_encoder(&dsi->connector, &dsi->encoder);
+	return 0;
+}
+
+static int imx_mipi_dsi_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct device_node *np = dev->of_node;
+	struct imx_mipi_dsi *dsi;
+	struct resource *res;
+	u32 val;
+	int ret;
+
+	dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+	if (!dsi)
+		return -ENOMEM;
+
+	dsi->dev = dev;
+	dsi->dsi_host.ops = &imx_mipi_dsi_host_ops;
+	dsi->dsi_host.dev = dev;
+	dsi->enabled = false;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dsi->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dsi->base))
+		return PTR_ERR(dsi->base);
+
+	dsi->regmap = syscon_regmap_lookup_by_phandle(np, "gpr");
+	if (IS_ERR(dsi->regmap))
+		return PTR_ERR(dsi->regmap);
+
+	dsi->pllref_clk = devm_clk_get(dev, "pllref");
+	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;
+	}
+
+	dsi->pllref_gate_clk = devm_clk_get(dev, "pllref_gate");
+	if (IS_ERR(dsi->pllref_gate_clk)) {
+		ret = PTR_ERR(dsi->pllref_gate_clk);
+		dev_err(dev, "Unable to get pll reference gate clock: %d\n", ret);
+		return ret;
+	}
+
+	dsi->cfg_clk = devm_clk_get(dev, "core_cfg");
+	if (IS_ERR(dsi->cfg_clk)) {
+		ret = PTR_ERR(dsi->cfg_clk);
+		dev_err(dev, "Unable to get configuration clock: %d\n", ret);
+		return ret;
+	}
+
+	clk_prepare_enable(dsi->cfg_clk);
+	val = dsi_read(dsi, DSI_VERSION);
+	clk_disable_unprepare(dsi->cfg_clk);
+
+	dev_info(dev, "version number is 0x%08x\n", val);
+
+	ret = imx_mipi_dsi_register(drm, dsi);
+	if (ret)
+		return ret;
+
+	dev_set_drvdata(dev, dsi);
+
+	return mipi_dsi_host_register(&dsi->dsi_host);
+}
+
+static void imx_mipi_dsi_unbind(struct device *dev, struct device *master,
+	void *data)
+{
+	struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
+
+	mipi_dsi_host_unregister(&dsi->dsi_host);
+}
+
+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 const struct of_device_id imx_mipi_dsi_dt_ids[] = {
+	{ .compatible = "fsl,imx6q-mipi-dsi", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_mipi_dsi_dt_ids);
+
+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 driver");
+MODULE_AUTHOR("Liu Ying <Ying.Liu@freescale.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds i.MX 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>
---
 .../devicetree/bindings/drm/imx/mipi_dsi.txt       |   81 ++
 drivers/gpu/drm/imx/Kconfig                        |    6 +
 drivers/gpu/drm/imx/Makefile                       |    1 +
 drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
 4 files changed, 1105 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
 create mode 100644 drivers/gpu/drm/imx/imx-mipi-dsi.c

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..3d07fd7
--- /dev/null
+++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
@@ -0,0 +1,81 @@
+Device-Tree bindings for MIPI DSI host controller
+
+MIPI DSI host controller
+========================
+
+The MIPI DSI host controller is a Synopsys DesignWare IP.
+It 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 : Should be "fsl,imx6q-mipi-dsi" for i.MX6q/sdl SoCs.
+ - 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 pllref, pllref_gate
+           and core_cfg clocks, as described in [1] and [2].
+ - panel at 0 : A panel node which contains a display-timings child node as
+             defined in [3].
+ - port@[0-4] : Up to four port nodes with endpoint definitions as defined
+   in [4], corresponding to the four inputs to the controller multiplexer.
+   Note that each port node should contain the input-port property to
+   distinguish it from the panel node, as described in [5].
+
+[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+[2] Documentation/devicetree/bindings/clock/imx6q-clock.txt
+[3] Documentation/devicetree/bindings/video/display-timing.txt
+[4] Documentation/devicetree/bindings/media/video-interfaces.txt
+[5] Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
+
+example:
+	gpr: iomuxc-gpr at 020e0000 {
+		/* ... */
+	};
+
+	mipi_dsi: mipi at 021e0000 {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		compatible = "fsl,imx6q-mipi-dsi";
+		reg = <0x021e0000 0x4000>;
+		interrupts = <0 102 IRQ_TYPE_LEVEL_HIGH>;
+		gpr = <&gpr>;
+		clocks = <&clks IMX6QDL_CLK_VIDEO_27M>,
+			 <&clks IMX6QDL_CLK_HSI_TX>,
+			 <&clks IMX6QDL_CLK_HSI_TX>;
+		clock-names = "pllref", "pllref_gate", "core_cfg";
+
+		port at 0 {
+			reg = <0>;
+			input-port;
+
+			mipi_mux_0: endpoint {
+				remote-endpoint = <&ipu1_di0_mipi>;
+			};
+		};
+
+		port at 1 {
+			reg = <1>;
+			input-port;
+
+			mipi_mux_1: endpoint {
+				remote-endpoint = <&ipu1_di1_mipi>;
+			};
+		};
+
+		panel at 0 {
+			compatible = "himax,hx8369a-dsi";
+			reg = <0>;
+			/* ... */
+
+			display-timings {
+				/* ... */
+			};
+		};
+	};
diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig
index 82fb758..03f04fb 100644
--- a/drivers/gpu/drm/imx/Kconfig
+++ b/drivers/gpu/drm/imx/Kconfig
@@ -51,3 +51,9 @@ 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"
+	depends on DRM_IMX && MFD_SYSCON
+	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 582c438..4571d52 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) += imx-hdmi.o
+obj-$(CONFIG_DRM_IMX_MIPI_DSI) += imx-mipi-dsi.o
diff --git a/drivers/gpu/drm/imx/imx-mipi-dsi.c b/drivers/gpu/drm/imx/imx-mipi-dsi.c
new file mode 100644
index 0000000..c8ebeca
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx-mipi-dsi.c
@@ -0,0 +1,1017 @@
+/*
+ * i.MX drm driver - MIPI DSI Host Controller
+ *
+ * Copyright (C) 2011-2014 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/component.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/videodev2.h>
+#include <asm/div64.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include "imx-drm.h"
+
+#define DRIVER_NAME 			"imx-mipi-dsi"
+
+#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 IMX_MIPI_DSI_MAX_DATA_LANES	2
+
+#define PHY_STATUS_TIMEOUT		10
+#define CMD_PKT_STATUS_TIMEOUT		20
+
+#define IMX_MIPI_DSI_PLD_DATA_BUF_SIZE	4
+
+#define MHZ				1000000
+
+#define host_to_dsi(host) container_of(host, struct imx_mipi_dsi, dsi_host)
+#define con_to_dsi(x) container_of(x, struct imx_mipi_dsi, connector)
+#define enc_to_dsi(x) container_of(x, struct imx_mipi_dsi, encoder)
+
+struct imx_mipi_dsi {
+	struct mipi_dsi_host dsi_host;
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+	struct device_node *panel_node;
+	struct drm_panel *panel;
+	struct device *dev;
+
+	struct regmap *regmap;
+	void __iomem *base;
+
+	struct clk *pllref_clk;
+	struct clk *pllref_gate_clk;
+	struct clk *cfg_clk;
+
+	unsigned int lane_mbps; /* per lane */
+	u32 channel;
+	u32 lanes;
+	u32 format;
+	struct videomode vm;
+
+	bool enabled;
+};
+
+enum {
+	STATUS_TO_CLEAR,
+	STATUS_TO_SET,
+};
+
+struct dphy_pll_testdin_map {
+	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 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 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 inline void dsi_write(struct imx_mipi_dsi *dsi, u32 reg, u32 val)
+{
+	writel(val, dsi->base + reg);
+}
+
+static inline u32 dsi_read(struct imx_mipi_dsi *dsi, u32 reg)
+{
+	return readl(dsi->base + reg);
+}
+
+static inline void dsi_modify(struct imx_mipi_dsi *dsi, u32 reg,
+				       u32 mask, u32 val)
+{
+	u32 v = readl(dsi->base + reg);
+	v &= ~mask;
+	v |= val;
+	writel(v, dsi->base + reg);
+}
+
+static int format_to_bpp(struct imx_mipi_dsi *dsi)
+{
+	switch (dsi->format) {
+	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;
+	default:
+		dev_err(dsi->dev, "unsupported pixel format\n");
+		return -EINVAL;
+	}
+}
+
+static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
+			int timeout, bool to_set)
+{
+	u32 val;
+	bool out = false;
+
+	val = dsi_read(dsi, reg);
+	for (;;) {
+		out = to_set ? (val & status) : !(val & status);
+		if (out)
+			break;
+
+		if (!timeout--)
+			return -EFAULT;
+
+		msleep(1);
+		val = dsi_read(dsi, reg);
+	}
+	return 0;
+}
+
+static int imx_mipi_dsi_config_testdin(struct imx_mipi_dsi *dsi)
+{
+	int ret = 0;
+	int 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;
+	}
+
+	clk_prepare_enable(dsi->pllref_clk);
+	clk_prepare_enable(dsi->pllref_gate_clk);
+	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 imx_mipi_dsi_get_lane_bps(struct imx_mipi_dsi *dsi,
+				      unsigned int *final_mbps)
+{
+	int target_mbps, mpclk, bpp, i;
+	unsigned long pllref;
+
+	bpp = format_to_bpp(dsi);
+	if (bpp < 0)
+		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->vm.pixelclock, MHZ);
+	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 %dMbps\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_info(dsi->dev, "real DPHY pll clock frequency "
+				 "is %dMbps\n", *final_mbps);
+			return 0;
+		}
+	}
+
+	dev_err(dsi->dev, "DPHY clock frequency %dMbps is out of range\n",
+						target_mbps);
+
+	return -EINVAL;
+}
+
+static int imx_mipi_dsi_host_attach(struct mipi_dsi_host *host,
+				    struct mipi_dsi_device *device)
+{
+	struct imx_mipi_dsi *dsi = host_to_dsi(host);
+	int ret;
+
+	if (device->lanes > IMX_MIPI_DSI_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;
+	}
+
+	dsi->lanes = device->lanes;
+	dsi->channel = device->channel;
+	dsi->format = device->format;
+	dsi->panel_node = device->dev.of_node;
+	of_get_videomode(dsi->panel_node, &dsi->vm, 0);
+
+	ret = imx_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int imx_mipi_dsi_host_detach(struct mipi_dsi_host *host,
+				    struct mipi_dsi_device *device)
+{
+	struct imx_mipi_dsi *dsi = host_to_dsi(host);
+
+	dsi->panel_node = NULL;
+	dsi->panel = NULL;
+
+	return 0;
+}
+
+static int imx_mipi_dsi_gen_pkt_hdr_write(struct imx_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 avaliable 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 imx_mipi_dsi_dcs_short_write(struct imx_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 imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
+}
+
+static int imx_mipi_dsi_dcs_long_write(struct imx_mipi_dsi *dsi,
+				       const struct mipi_dsi_msg *msg)
+{
+	const u32 *tx_buf = msg->tx_buf;
+	int len = msg->tx_len, ret;
+	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
+
+	if (msg->tx_len < 3) {
+		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
+			msg->tx_len);
+		return -EINVAL;
+	}
+
+	/* write the bulks */
+	while (len / IMX_MIPI_DSI_PLD_DATA_BUF_SIZE) {
+		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
+		tx_buf++;
+		len -= IMX_MIPI_DSI_PLD_DATA_BUF_SIZE;
+		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 avaliable "
+					"write payload FIFO\n");
+			return ret;
+		}
+	}
+
+	/* write the remainder */
+	if (len > 0) {
+		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 avaliable "
+					"write payload FIFO\n");
+			return ret;
+		}
+		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
+	}
+
+	return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
+}
+
+static ssize_t imx_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
+					  const struct mipi_dsi_msg *msg)
+{
+	struct imx_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 = imx_mipi_dsi_dcs_short_write(dsi, msg);
+		break;
+	case MIPI_DSI_DCS_LONG_WRITE:
+		ret = imx_mipi_dsi_dcs_long_write(dsi, msg);
+		break;
+	default:
+		dev_err(dsi->dev, "unsupported message type\n");
+		ret = -EFAULT;
+	}
+
+	return ret;
+}
+
+static const struct mipi_dsi_host_ops imx_mipi_dsi_host_ops = {
+	.attach = imx_mipi_dsi_host_attach,
+	.detach = imx_mipi_dsi_host_detach,
+	.transfer = imx_mipi_dsi_host_transfer,
+};
+
+static enum drm_connector_status
+imx_mipi_dsi_detect(struct drm_connector *connector, bool force)
+{
+	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
+
+	if (!dsi->panel) {
+		dsi->panel = of_drm_find_panel(dsi->panel_node);
+		if (dsi->panel)
+			drm_panel_attach(dsi->panel, &dsi->connector);
+	}
+
+	if (dsi->panel)
+		return connector_status_connected;
+
+	return connector_status_disconnected;
+
+}
+
+static struct drm_connector_funcs imx_mipi_dsi_connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = imx_mipi_dsi_detect,
+	.destroy = imx_drm_connector_destroy,
+};
+
+static int imx_mipi_dsi_connector_get_modes(struct drm_connector *connector)
+{
+	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
+
+	return drm_panel_get_modes(dsi->panel);
+}
+
+static struct drm_encoder *imx_mipi_dsi_connector_best_encoder(
+					struct drm_connector *connector)
+{
+	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
+
+	return &dsi->encoder;
+}
+
+static struct drm_connector_helper_funcs imx_mipi_dsi_connector_helper_funcs = {
+	.get_modes = imx_mipi_dsi_connector_get_modes,
+	.best_encoder = imx_mipi_dsi_connector_best_encoder,
+};
+
+static struct drm_encoder_funcs imx_mipi_dsi_encoder_funcs = {
+	.destroy = imx_drm_encoder_destroy,
+};
+
+static void imx_mipi_dsi_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+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)
+{
+	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
+	u32 interface_pix_fmt;
+
+	switch (dsi->format) {
+	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:
+		dev_err(dsi->dev, "unsupported DSI pixel format\n");
+		return;
+	}
+
+	imx_drm_panel_format(encoder, interface_pix_fmt);
+}
+
+static void imx_mipi_dsi_init(struct imx_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 imx_mipi_dsi_dpi_config(struct imx_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 imx_mipi_dsi_packet_handler_config(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PCKHDL_CFG, EN_CRC_RX | EN_ECC_RX | EN_BTA);
+}
+
+static void imx_mipi_dsi_video_mode_config(struct imx_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 imx_mipi_dsi_video_packet_config(struct imx_mipi_dsi *dsi,
+					     struct drm_display_mode *mode)
+{
+	dsi_write(dsi, DSI_VID_PKT_CFG, VID_PKT_SIZE(mode->hdisplay));
+}
+
+static void imx_mipi_dsi_command_mode_config(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_CMD_MODE_CFG, CMD_MODE_ALL_LP | ENABLE_CMD_MODE);
+}
+
+/* Get lane byte clock cycles. */
+static u64 imx_mipi_dsi_get_hcomponent_lbcc(struct imx_mipi_dsi *dsi,
+						u64 hcomponent)
+{
+	u64 frac, lbcc;
+
+	lbcc = hcomponent * dsi->lane_mbps * MHZ / 8;
+	frac = do_div(lbcc, dsi->vm.pixelclock);
+	if (frac)
+		lbcc++;
+
+	return lbcc;
+}
+
+static void imx_mipi_dsi_line_timer_config(struct imx_mipi_dsi *dsi)
+{
+	u32 val = 0, htotal, hsa, hbp, lbcc;
+
+	htotal = dsi->vm.hactive + dsi->vm.hfront_porch +
+		 dsi->vm.hback_porch + dsi->vm.hsync_len;
+	hsa = dsi->vm.hsync_len;
+	hbp = dsi->vm.hback_porch;
+
+	lbcc = imx_mipi_dsi_get_hcomponent_lbcc(dsi, htotal);
+	val |= HLINE_TIME(lbcc);
+
+	lbcc = imx_mipi_dsi_get_hcomponent_lbcc(dsi, hsa);
+	val |= HSA_TIME(lbcc);
+
+	lbcc = imx_mipi_dsi_get_hcomponent_lbcc(dsi, hbp);
+	val |= HBP_TIME(lbcc);
+
+	dsi_write(dsi, DSI_TMR_LINE_CFG, val);
+}
+
+static void imx_mipi_dsi_vertical_timing_config(struct imx_mipi_dsi *dsi)
+{
+	u32 val;
+
+	val = V_ACTIVE_LINES(dsi->vm.vactive) | VSA_LINES(dsi->vm.vsync_len) |
+	      VFP_LINES(dsi->vm.vfront_porch) | VBP_LINES(dsi->vm.vback_porch);
+
+	dsi_write(dsi, DSI_VTIMING_CFG, val);
+}
+
+static void imx_mipi_dsi_dphy_timing_config(struct imx_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 imx_mipi_dsi_dphy_interface_config(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) |
+						N_LANES(dsi->lanes));
+}
+
+static void imx_mipi_dsi_clear_err(struct imx_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 imx_mipi_dsi_encoder_mode_set(struct drm_encoder *encoder,
+					  struct drm_display_mode *mode,
+					  struct drm_display_mode *adjusted_mode)
+{
+	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
+
+	clk_prepare_enable(dsi->cfg_clk);
+	imx_mipi_dsi_init(dsi);
+	imx_mipi_dsi_dpi_config(dsi, mode);
+	imx_mipi_dsi_packet_handler_config(dsi);
+	imx_mipi_dsi_video_mode_config(dsi);
+	imx_mipi_dsi_video_packet_config(dsi, mode);
+	imx_mipi_dsi_command_mode_config(dsi);
+	imx_mipi_dsi_line_timer_config(dsi);
+	imx_mipi_dsi_vertical_timing_config(dsi);
+	imx_mipi_dsi_dphy_timing_config(dsi);
+	imx_mipi_dsi_dphy_interface_config(dsi);
+	imx_mipi_dsi_clear_err(dsi);
+	imx_mipi_dsi_config_testdin(dsi);
+	drm_panel_prepare(dsi->panel);
+	clk_disable_unprepare(dsi->cfg_clk);
+}
+
+static void imx_mipi_dsi_disable_command_mode(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PWR_UP, RESET);
+	dsi_modify(dsi, DSI_CMD_MODE_CFG,
+		   ENABLE_CMD_MODE_MASK, DISABLE_CMD_MODE);
+}
+
+static void imx_mipi_dsi_enable_video_mode(struct imx_mipi_dsi *dsi)
+{
+	imx_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 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);
+
+	if (dsi->enabled)
+		return;
+
+	imx_mipi_dsi_set_ipu_di_mux(dsi, mux);
+
+	drm_panel_enable(dsi->panel);
+
+	imx_mipi_dsi_disable_command_mode(dsi);
+	imx_mipi_dsi_enable_video_mode(dsi);
+
+	dsi->enabled = true;
+}
+
+static void imx_mipi_dsi_enable_command_mode(struct imx_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PWR_UP, RESET);
+	dsi_modify(dsi, DSI_CMD_MODE_CFG,
+		   ENABLE_CMD_MODE_MASK, ENABLE_CMD_MODE);
+}
+
+static void imx_mipi_dsi_disable_video_mode(struct imx_mipi_dsi *dsi)
+{
+	dsi_modify(dsi, DSI_VID_MODE_CFG,
+		   ENABLE_VIDEO_MODE_MASK, DISABLE_VIDEO_MODE);
+	dsi_write(dsi, DSI_PWR_UP, POWERUP);
+}
+
+static void imx_mipi_dsi_disable(struct imx_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 imx_mipi_dsi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
+
+	if (!dsi->enabled)
+		return;
+
+	clk_prepare_enable(dsi->cfg_clk);
+
+	drm_panel_disable(dsi->panel);
+	imx_mipi_dsi_enable_command_mode(dsi);
+	imx_mipi_dsi_disable_video_mode(dsi);
+	drm_panel_unprepare(dsi->panel);
+
+	imx_mipi_dsi_disable(dsi);
+
+	clk_disable_unprepare(dsi->cfg_clk);
+	clk_disable_unprepare(dsi->pllref_gate_clk);
+	clk_disable_unprepare(dsi->pllref_clk);
+
+	dsi->enabled = false;
+}
+
+static struct drm_encoder_helper_funcs imx_mipi_dsi_encoder_helper_funcs = {
+	.dpms = imx_mipi_dsi_encoder_dpms,
+	.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);
+
+	drm_connector_helper_add(&dsi->connector,
+			&imx_mipi_dsi_connector_helper_funcs);
+	drm_connector_init(drm, &dsi->connector, &imx_mipi_dsi_connector_funcs,
+			   DRM_MODE_CONNECTOR_DSI);
+
+	drm_mode_connector_attach_encoder(&dsi->connector, &dsi->encoder);
+	return 0;
+}
+
+static int imx_mipi_dsi_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct device_node *np = dev->of_node;
+	struct imx_mipi_dsi *dsi;
+	struct resource *res;
+	u32 val;
+	int ret;
+
+	dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+	if (!dsi)
+		return -ENOMEM;
+
+	dsi->dev = dev;
+	dsi->dsi_host.ops = &imx_mipi_dsi_host_ops;
+	dsi->dsi_host.dev = dev;
+	dsi->enabled = false;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dsi->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dsi->base))
+		return PTR_ERR(dsi->base);
+
+	dsi->regmap = syscon_regmap_lookup_by_phandle(np, "gpr");
+	if (IS_ERR(dsi->regmap))
+		return PTR_ERR(dsi->regmap);
+
+	dsi->pllref_clk = devm_clk_get(dev, "pllref");
+	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;
+	}
+
+	dsi->pllref_gate_clk = devm_clk_get(dev, "pllref_gate");
+	if (IS_ERR(dsi->pllref_gate_clk)) {
+		ret = PTR_ERR(dsi->pllref_gate_clk);
+		dev_err(dev, "Unable to get pll reference gate clock: %d\n", ret);
+		return ret;
+	}
+
+	dsi->cfg_clk = devm_clk_get(dev, "core_cfg");
+	if (IS_ERR(dsi->cfg_clk)) {
+		ret = PTR_ERR(dsi->cfg_clk);
+		dev_err(dev, "Unable to get configuration clock: %d\n", ret);
+		return ret;
+	}
+
+	clk_prepare_enable(dsi->cfg_clk);
+	val = dsi_read(dsi, DSI_VERSION);
+	clk_disable_unprepare(dsi->cfg_clk);
+
+	dev_info(dev, "version number is 0x%08x\n", val);
+
+	ret = imx_mipi_dsi_register(drm, dsi);
+	if (ret)
+		return ret;
+
+	dev_set_drvdata(dev, dsi);
+
+	return mipi_dsi_host_register(&dsi->dsi_host);
+}
+
+static void imx_mipi_dsi_unbind(struct device *dev, struct device *master,
+	void *data)
+{
+	struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
+
+	mipi_dsi_host_unregister(&dsi->dsi_host);
+}
+
+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 const struct of_device_id imx_mipi_dsi_dt_ids[] = {
+	{ .compatible = "fsl,imx6q-mipi-dsi", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_mipi_dsi_dt_ids);
+
+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 driver");
+MODULE_AUTHOR("Liu Ying <Ying.Liu@freescale.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 10/15] drm: panel: Add support for Himax HX8369A MIPI DSI panel
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

This patch adds support for Himax HX8369A MIPI DSI panel.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 .../devicetree/bindings/panel/himax,hx8369a.txt    |  86 +++
 drivers/gpu/drm/panel/Kconfig                      |   6 +
 drivers/gpu/drm/panel/Makefile                     |   1 +
 drivers/gpu/drm/panel/panel-hx8369a.c              | 627 +++++++++++++++++++++
 4 files changed, 720 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
 create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c

diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
new file mode 100644
index 0000000..6fe251e
--- /dev/null
+++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
@@ -0,0 +1,86 @@
+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: "himax,hx8369a-dsi"
+  - reg: the virtual channel number of a DSI peripheral
+  - reset-gpios: a GPIO spec for the reset pin
+  - data-lanes: the data lane number of a DSI peripheral
+  - display-timings: timings for the connected panel as described by [1]
+  - bs: the interface mode number described by the following table
+        --------------------------
+       | DBI_TYPE_A_8BIT     |  0 |
+       | DBI_TYPE_A_9BIT     |  1 |
+       | DBI_TYPE_A_16BIT    |  2 |
+       | DBI_TYPE_A_18BIT    |  3 |
+       | DBI_TYPE_B_8BIT     |  4 |
+       | DBI_TYPE_B_9BIT     |  5 |
+       | DBI_TYPE_B_16BIT    |  6 |
+       | DBI_TYPE_B_18BIT    |  7 |
+       | DSI_CMD_MODE        |  8 |
+       | DBI_TYPE_B_24BIT    |  9 |
+       | DSI_VIDEO_MODE      | 10 |
+       | MDDI                | 11 |
+       | DPI_DBI_TYPE_C_OPT1 | 12 |
+       | DPI_DBI_TYPE_C_OPT2 | 13 |
+       | DPI_DBI_TYPE_C_OPT3 | 14 |
+        --------------------------
+
+Optional properties:
+  - power-on-delay: delay after turning regulators on [ms]
+  - reset-delay: delay after reset sequence [ms]
+  - 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
+  - bs0-gpios: a GPIO spec for the pin BS0
+  - bs1-gpios: a GPIO spec for the pin BS1
+  - bs2-gpios: a GPIO spec for the pin BS2
+  - bs3-gpios: a GPIO spec for the pin BS3
+  - panel-width-mm: physical panel width [mm]
+  - panel-height-mm: physical panel height [mm]
+
+[1]: Documentation/devicetree/bindings/video/display-timing.txt
+
+Example:
+	panel@0 {
+		compatible = "himax,hx8369a-dsi";
+		reg = <0>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_mipi_panel>;
+		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
+		reset-delay = <120>;
+		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
+		data-lanes = <2>;
+		panel-width-mm = <45>;
+		panel-height-mm = <76>;
+		bs = <10>;
+		status = "okay";
+
+		display-timings {
+			native-mode = <&timing1>;
+			timing1: truly-tft480800-16-e {
+			clock-frequency = <26400000>;
+			hactive = <480>;
+			vactive = <800>;
+			hfront-porch = <8>;
+			hback-porch = <8>;
+			hsync-len = <8>;
+			vfront-porch = <6>;
+			vback-porch = <6>;
+			vsync-len = <6>;
+		};
+	};
+};
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 024e98e..f1a5b58 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -40,4 +40,10 @@ config DRM_PANEL_SHARP_LQ101R1SX01
 	  To compile this driver as a module, choose M here: the module
 	  will be called panel-sharp-lq101r1sx01.
 
+config DRM_PANEL_HX8369A
+	tristate "HX8369A panel"
+	depends on OF
+	select DRM_MIPI_DSI
+	select VIDEOMODE_HELPERS
+
 endmenu
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 4b2a043..d6768ca 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.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
+obj-$(CONFIG_DRM_PANEL_HX8369A) += panel-hx8369a.o
diff --git a/drivers/gpu/drm/panel/panel-hx8369a.c b/drivers/gpu/drm/panel/panel-hx8369a.c
new file mode 100644
index 0000000..a8cdaea
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-hx8369a.c
@@ -0,0 +1,627 @@
+/*
+ * Himax HX8369A panel driver.
+ *
+ * Copyright (C) 2011-2014 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/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#define SETCLUMN_ADDR	0x2a
+#define SETPAGE_ADDR 	0x2b
+#define SETPIXEL_FMT 	0x3a
+#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
+
+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,
+	HX8369A_RES_DISABLE
+};
+
+struct hx8369a {
+	struct device *dev;
+	struct drm_panel panel;
+
+	struct regulator_bulk_data supplies[5];
+	struct gpio_desc *reset_gpio;
+	u32 power_on_delay;
+	u32 reset_delay;
+	struct gpio_desc *bs_gpio[4];
+	u32 data_lanes;
+	u32 mpu_interface;
+	struct videomode vm;
+	u32 width_mm;
+	u32 height_mm;
+	u8 res_sel;
+};
+
+static inline struct hx8369a *panel_to_hx8369a(struct drm_panel *panel)
+{
+	return container_of(panel, struct hx8369a, panel);
+}
+
+static bool hx8369a_is_dsi_interface(struct hx8369a *ctx) {
+	if (ctx->mpu_interface == HX8369A_DSI_VIDEO_MODE ||
+	    ctx->mpu_interface == HX8369A_DSI_CMD_MODE)
+		return true;
+
+	return false;
+}
+
+static void hx8369a_dcs_write(struct hx8369a *ctx, 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, "error %zd writing dcs seq: %*ph\n", ret, len,
+			data);
+}
+
+#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, d, ARRAY_SIZE(d));\
+})
+
+#define hx8369a_dcs_write_seq_static(ctx, seq...) \
+({\
+	static const u8 d[] = { seq };\
+	hx8369a_dcs_write(ctx, d, ARRAY_SIZE(d));\
+})
+
+static void hx8369a_dsi_set_display_related_register(struct hx8369a *ctx)
+{
+	u8 sec_p = (ctx->res_sel << 4) | 0x03;
+
+	hx8369a_dcs_write_seq(ctx, SETDISP, 0x00, sec_p, 0x03,
+		0x03, 0x70, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
+		0x03, 0x03, 0x00, 0x01);
+}
+
+static void hx8369a_dsi_set_display_waveform_cycle(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETCYC, 0x00, 0x1d, 0x5f, 0x0e, 0x06);
+}
+
+static void hx8369a_dsi_set_gip(struct hx8369a *ctx)
+{
+	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 void hx8369a_dsi_set_power(struct hx8369a *ctx)
+{
+	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 void hx8369a_dsi_set_vcom_voltage(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETVCOM, 0x56, 0x56);
+}
+
+static void hx8369a_dsi_set_panel(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETPANEL, 0x02);
+}
+
+static void hx8369a_dsi_set_gamma_curve(struct hx8369a *ctx)
+{
+	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 void hx8369a_dsi_set_mipi(struct hx8369a *ctx)
+{
+	u8 eleventh_p = ctx->data_lanes == 2 ? 0x11 : 0x10;
+
+	hx8369a_dcs_write_seq(ctx, SETMIPI, 0x00, 0xa0, 0xc6, 0x00, 0x0a, 0x00,
+			0x10, 0x30, 0x6f, 0x02, eleventh_p, 0x18, 0x40);
+}
+
+static void hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx)
+{
+	struct mipi_dsi_device * dsi = to_mipi_dsi_device(ctx->dev);
+	u8 bpp;
+
+	if (dsi->format == MIPI_DSI_FMT_RGB888)
+		bpp = 0x77;
+	else if (dsi->format == MIPI_DSI_FMT_RGB565)
+		bpp = 0x55;
+	else
+		bpp = 0x66;
+
+	hx8369a_dcs_write_seq(ctx, SETPIXEL_FMT, bpp);
+}
+
+static void hx8369a_dsi_set_column_address(struct hx8369a *ctx)
+{
+	u8 ecl = 0;
+
+	switch (ctx->res_sel) {
+	case HX8369A_RES_480_864:
+	case HX8369A_RES_480_854:
+	case HX8369A_RES_480_800:
+	case HX8369A_RES_480_640:
+	case HX8369A_RES_480_720:
+		ecl = 0xdf;
+		break;
+	case HX8369A_RES_360_640:
+		ecl = 0x67;
+		break;
+	}
+
+	hx8369a_dcs_write_seq(ctx, SETCLUMN_ADDR, 0x00, 0x00, 0x01, ecl);
+}
+
+static void hx8369a_dsi_set_page_address(struct hx8369a *ctx)
+{
+	u8 epl = 0, eph = 0;
+
+	switch (ctx->res_sel) {
+	case HX8369A_RES_480_864:
+		epl = 0x5f;
+		eph = 0x03;
+		break;
+	case HX8369A_RES_480_854:
+		epl = 0x55;
+		eph = 0x03;
+		break;
+	case HX8369A_RES_480_800:
+		epl = 0x1f;
+		eph = 0x03;
+		break;
+	case HX8369A_RES_480_640:
+	case HX8369A_RES_360_640:
+		epl = 0x7f;
+		eph = 0x02;
+		break;
+	case HX8369A_RES_480_720:
+		epl = 0xcf;
+		eph = 0x02;
+		break;
+	}
+
+	hx8369a_dcs_write_seq(ctx, SETPAGE_ADDR, 0x00, 0x00, eph, epl);
+}
+
+static void hx8369a_dsi_write_display_brightness(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, WRDISBV, 0xff);
+}
+
+static void hx8369a_dsi_write_cabc(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, WRCABC, 0x01);
+}
+
+static void hx8369a_dsi_write_control_display(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, WRCTRLD, 0x24);
+}
+
+static void hx8369a_dsi_panel_init(struct hx8369a *ctx)
+{
+	hx8369a_dsi_set_display_related_register(ctx);
+	hx8369a_dsi_set_display_waveform_cycle(ctx);
+	hx8369a_dsi_set_gip(ctx);
+	hx8369a_dsi_set_power(ctx);
+	hx8369a_dsi_set_vcom_voltage(ctx);
+	hx8369a_dsi_set_panel(ctx);
+	hx8369a_dsi_set_gamma_curve(ctx);
+	hx8369a_dsi_set_mipi(ctx);
+	hx8369a_dsi_set_interface_pixel_fomat(ctx);
+	hx8369a_dsi_set_column_address(ctx);
+	hx8369a_dsi_set_page_address(ctx);
+	hx8369a_dsi_write_display_brightness(ctx);
+	hx8369a_dsi_write_cabc(ctx);
+	hx8369a_dsi_write_control_display(ctx);
+}
+
+static void hx8369a_dsi_set_extension_command(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETEXTC, 0xff, 0x83, 0x69);
+}
+
+static void 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);
+}
+
+static int hx8369a_dsi_set_sequence(struct hx8369a *ctx)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	int ret;
+
+	hx8369a_dsi_set_extension_command(ctx);
+	hx8369a_dsi_set_maximum_return_packet_size(ctx, 4);
+	hx8369a_dsi_panel_init(ctx);
+
+	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+	if (ret < 0) {
+		dev_err(ctx->dev, "failed to exit sleep mode: %d\n", ret);
+		return ret;
+	}
+
+	ret = mipi_dsi_dcs_set_display_on(dsi);
+	if (ret < 0) {
+		dev_err(ctx->dev, "failed to set display on: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+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->power_on_delay);
+
+	gpiod_set_value(ctx->reset_gpio, 1);
+	usleep_range(50, 60);
+	gpiod_set_value(ctx->reset_gpio, 0);
+
+	msleep(ctx->reset_delay);
+
+	return 0;
+}
+
+static int hx8369a_power_off(struct hx8369a *ctx)
+{
+	return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);;
+}
+
+static int hx8369a_dsi_disable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+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);
+		return ret;
+	}
+
+	ret = mipi_dsi_dcs_set_display_off(dsi);
+	if (ret < 0) {
+		dev_err(ctx->dev, "failed to set display off: %d\n", ret);
+		return ret;
+	}
+
+	msleep(40);
+
+	return hx8369a_power_off(ctx);
+}
+
+static int hx8369a_dsi_prepare(struct drm_panel *panel)
+{
+	struct hx8369a *ctx = panel_to_hx8369a(panel);
+	int ret;
+
+	ret = hx8369a_power_on(ctx);
+	if (ret < 0)
+		return ret;
+
+	ret = hx8369a_dsi_set_sequence(ctx);
+
+	return ret;
+}
+
+static int hx8369a_dsi_enable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int hx8369a_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct hx8369a *ctx = panel_to_hx8369a(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_create(connector->dev);
+	if (!mode) {
+		DRM_ERROR("failed to create a new display mode\n");
+		return 0;
+	}
+
+	drm_display_mode_from_videomode(&ctx->vm, mode);
+	mode->width_mm = ctx->width_mm;
+	mode->height_mm = ctx->height_mm;
+	connector->display_info.width_mm = mode->width_mm;
+	connector->display_info.height_mm = mode->height_mm;
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	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_parse_dt(struct hx8369a *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct device_node *np = dev->of_node;
+	int ret;
+
+	ret = of_get_videomode(np, &ctx->vm, 0);
+	if (ret < 0)
+		return ret;
+
+	of_property_read_u32(np, "bs", &ctx->mpu_interface);
+	if (ctx->mpu_interface != HX8369A_DSI_VIDEO_MODE &&
+	    ctx->mpu_interface != HX8369A_DSI_CMD_MODE) {
+		dev_err(dev, "unsupported MPU interface 0x%01x\n",
+			ctx->mpu_interface);
+		return -EINVAL;
+	}
+
+	if (hx8369a_is_dsi_interface(ctx)) {
+		ret = of_property_read_u32(np, "data-lanes", &ctx->data_lanes);
+		if (ret || ctx->data_lanes == 0 || ctx->data_lanes > 2) {
+			dev_err(dev, "cannot get correct data lanes\n");
+			return -EINVAL;
+		}
+	}
+
+	of_property_read_u32(np, "power-on-delay", &ctx->power_on_delay);
+	of_property_read_u32(np, "reset-delay", &ctx->reset_delay);
+	of_property_read_u32(np, "panel-width-mm", &ctx->width_mm);
+	of_property_read_u32(np, "panel-height-mm", &ctx->height_mm);
+
+	return 0;
+}
+
+static int hx8369a_vm_to_res_sel(struct hx8369a *ctx)
+{
+	switch (ctx->vm.hactive) {
+	case 480:
+		switch (ctx->vm.vactive) {
+		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:
+			ctx->res_sel = HX8369A_RES_DISABLE;
+			break;
+		}
+		break;
+	case 360:
+		if (ctx->vm.vactive == 640)
+			ctx->res_sel = HX8369A_RES_360_640;
+		else
+			ctx->res_sel = HX8369A_RES_DISABLE;
+		break;
+	default:
+		ctx->res_sel = HX8369A_RES_DISABLE;
+		break;
+	}
+
+	if (ctx->res_sel == HX8369A_RES_DISABLE) {
+		dev_err(ctx->dev, "unsupported resolution %dx%d\n",
+			ctx->vm.hactive, ctx->vm.vactive);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int hx8369a_dsi_probe(struct mipi_dsi_device *dsi)
+{
+	struct device *dev = &dsi->dev;
+	struct hx8369a *ctx;
+	int ret, i;
+	char bs[4];
+
+	ctx = devm_kzalloc(dev, sizeof(struct hx8369a), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	ctx->dev = dev;
+
+	ret = hx8369a_parse_dt(ctx);
+	if (ret < 0)
+		return ret;
+
+	ret = hx8369a_vm_to_res_sel(ctx);
+	if (ret < 0)
+		return ret;
+
+	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_err(dev, "failed to get regulators: %d\n", ret);
+		return ret;
+	}
+
+	ctx->reset_gpio = devm_gpiod_get(dev, "reset");
+	if (IS_ERR(ctx->reset_gpio)) {
+		dev_err(dev, "cannot get reset-gpios %ld\n",
+			PTR_ERR(ctx->reset_gpio));
+		return PTR_ERR(ctx->reset_gpio);
+	}
+	ret = gpiod_direction_output(ctx->reset_gpio, 0);
+	if (ret < 0) {
+		dev_err(dev, "cannot configure reset-gpios %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < 4; i++) {
+		snprintf(bs, sizeof(bs), "bs%d", i);
+		ctx->bs_gpio[i] = devm_gpiod_get(dev, bs);
+		if (IS_ERR(ctx->bs_gpio[i]))
+			continue;
+
+		ret = gpiod_direction_output(ctx->bs_gpio[i], 1);
+		if (ret < 0) {
+			dev_err(dev, "cannot configure bs%d-gpio %d\n", i, ret);
+			return ret;
+		}
+		dev_dbg(dev, "bs%d-gpio is configured\n", i);
+	}
+
+	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->channel = 0;
+	dsi->lanes = ctx->data_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 0;
+}
+
+static const struct of_device_id hx8369a_dsi_of_match[] = {
+	{ .compatible = "himax,hx8369a-dsi" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, hx8369a_dsi_of_match);
+
+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,
+	},
+};
+
+static int __init hx8369a_init(void)
+{
+	int err;
+
+	err = mipi_dsi_driver_register(&hx8369a_dsi_driver);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+module_init(hx8369a_init);
+
+static void __exit hx8369a_exit(void)
+{
+	mipi_dsi_driver_unregister(&hx8369a_dsi_driver);
+}
+module_exit(hx8369a_exit);
+
+MODULE_DESCRIPTION("Himax HX8369A panel driver");
+MODULE_LICENSE("GPL v2");
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 10/15] drm: panel: Add support for Himax HX8369A MIPI DSI panel
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

This patch adds support for Himax HX8369A MIPI DSI panel.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 .../devicetree/bindings/panel/himax,hx8369a.txt    |  86 +++
 drivers/gpu/drm/panel/Kconfig                      |   6 +
 drivers/gpu/drm/panel/Makefile                     |   1 +
 drivers/gpu/drm/panel/panel-hx8369a.c              | 627 +++++++++++++++++++++
 4 files changed, 720 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
 create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c

diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
new file mode 100644
index 0000000..6fe251e
--- /dev/null
+++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
@@ -0,0 +1,86 @@
+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: "himax,hx8369a-dsi"
+  - reg: the virtual channel number of a DSI peripheral
+  - reset-gpios: a GPIO spec for the reset pin
+  - data-lanes: the data lane number of a DSI peripheral
+  - display-timings: timings for the connected panel as described by [1]
+  - bs: the interface mode number described by the following table
+        --------------------------
+       | DBI_TYPE_A_8BIT     |  0 |
+       | DBI_TYPE_A_9BIT     |  1 |
+       | DBI_TYPE_A_16BIT    |  2 |
+       | DBI_TYPE_A_18BIT    |  3 |
+       | DBI_TYPE_B_8BIT     |  4 |
+       | DBI_TYPE_B_9BIT     |  5 |
+       | DBI_TYPE_B_16BIT    |  6 |
+       | DBI_TYPE_B_18BIT    |  7 |
+       | DSI_CMD_MODE        |  8 |
+       | DBI_TYPE_B_24BIT    |  9 |
+       | DSI_VIDEO_MODE      | 10 |
+       | MDDI                | 11 |
+       | DPI_DBI_TYPE_C_OPT1 | 12 |
+       | DPI_DBI_TYPE_C_OPT2 | 13 |
+       | DPI_DBI_TYPE_C_OPT3 | 14 |
+        --------------------------
+
+Optional properties:
+  - power-on-delay: delay after turning regulators on [ms]
+  - reset-delay: delay after reset sequence [ms]
+  - 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
+  - bs0-gpios: a GPIO spec for the pin BS0
+  - bs1-gpios: a GPIO spec for the pin BS1
+  - bs2-gpios: a GPIO spec for the pin BS2
+  - bs3-gpios: a GPIO spec for the pin BS3
+  - panel-width-mm: physical panel width [mm]
+  - panel-height-mm: physical panel height [mm]
+
+[1]: Documentation/devicetree/bindings/video/display-timing.txt
+
+Example:
+	panel@0 {
+		compatible = "himax,hx8369a-dsi";
+		reg = <0>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_mipi_panel>;
+		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
+		reset-delay = <120>;
+		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
+		data-lanes = <2>;
+		panel-width-mm = <45>;
+		panel-height-mm = <76>;
+		bs = <10>;
+		status = "okay";
+
+		display-timings {
+			native-mode = <&timing1>;
+			timing1: truly-tft480800-16-e {
+			clock-frequency = <26400000>;
+			hactive = <480>;
+			vactive = <800>;
+			hfront-porch = <8>;
+			hback-porch = <8>;
+			hsync-len = <8>;
+			vfront-porch = <6>;
+			vback-porch = <6>;
+			vsync-len = <6>;
+		};
+	};
+};
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 024e98e..f1a5b58 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -40,4 +40,10 @@ config DRM_PANEL_SHARP_LQ101R1SX01
 	  To compile this driver as a module, choose M here: the module
 	  will be called panel-sharp-lq101r1sx01.
 
+config DRM_PANEL_HX8369A
+	tristate "HX8369A panel"
+	depends on OF
+	select DRM_MIPI_DSI
+	select VIDEOMODE_HELPERS
+
 endmenu
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 4b2a043..d6768ca 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.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
+obj-$(CONFIG_DRM_PANEL_HX8369A) += panel-hx8369a.o
diff --git a/drivers/gpu/drm/panel/panel-hx8369a.c b/drivers/gpu/drm/panel/panel-hx8369a.c
new file mode 100644
index 0000000..a8cdaea
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-hx8369a.c
@@ -0,0 +1,627 @@
+/*
+ * Himax HX8369A panel driver.
+ *
+ * Copyright (C) 2011-2014 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/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#define SETCLUMN_ADDR	0x2a
+#define SETPAGE_ADDR 	0x2b
+#define SETPIXEL_FMT 	0x3a
+#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
+
+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,
+	HX8369A_RES_DISABLE
+};
+
+struct hx8369a {
+	struct device *dev;
+	struct drm_panel panel;
+
+	struct regulator_bulk_data supplies[5];
+	struct gpio_desc *reset_gpio;
+	u32 power_on_delay;
+	u32 reset_delay;
+	struct gpio_desc *bs_gpio[4];
+	u32 data_lanes;
+	u32 mpu_interface;
+	struct videomode vm;
+	u32 width_mm;
+	u32 height_mm;
+	u8 res_sel;
+};
+
+static inline struct hx8369a *panel_to_hx8369a(struct drm_panel *panel)
+{
+	return container_of(panel, struct hx8369a, panel);
+}
+
+static bool hx8369a_is_dsi_interface(struct hx8369a *ctx) {
+	if (ctx->mpu_interface == HX8369A_DSI_VIDEO_MODE ||
+	    ctx->mpu_interface == HX8369A_DSI_CMD_MODE)
+		return true;
+
+	return false;
+}
+
+static void hx8369a_dcs_write(struct hx8369a *ctx, 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, "error %zd writing dcs seq: %*ph\n", ret, len,
+			data);
+}
+
+#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, d, ARRAY_SIZE(d));\
+})
+
+#define hx8369a_dcs_write_seq_static(ctx, seq...) \
+({\
+	static const u8 d[] = { seq };\
+	hx8369a_dcs_write(ctx, d, ARRAY_SIZE(d));\
+})
+
+static void hx8369a_dsi_set_display_related_register(struct hx8369a *ctx)
+{
+	u8 sec_p = (ctx->res_sel << 4) | 0x03;
+
+	hx8369a_dcs_write_seq(ctx, SETDISP, 0x00, sec_p, 0x03,
+		0x03, 0x70, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
+		0x03, 0x03, 0x00, 0x01);
+}
+
+static void hx8369a_dsi_set_display_waveform_cycle(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETCYC, 0x00, 0x1d, 0x5f, 0x0e, 0x06);
+}
+
+static void hx8369a_dsi_set_gip(struct hx8369a *ctx)
+{
+	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 void hx8369a_dsi_set_power(struct hx8369a *ctx)
+{
+	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 void hx8369a_dsi_set_vcom_voltage(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETVCOM, 0x56, 0x56);
+}
+
+static void hx8369a_dsi_set_panel(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETPANEL, 0x02);
+}
+
+static void hx8369a_dsi_set_gamma_curve(struct hx8369a *ctx)
+{
+	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 void hx8369a_dsi_set_mipi(struct hx8369a *ctx)
+{
+	u8 eleventh_p = ctx->data_lanes == 2 ? 0x11 : 0x10;
+
+	hx8369a_dcs_write_seq(ctx, SETMIPI, 0x00, 0xa0, 0xc6, 0x00, 0x0a, 0x00,
+			0x10, 0x30, 0x6f, 0x02, eleventh_p, 0x18, 0x40);
+}
+
+static void hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx)
+{
+	struct mipi_dsi_device * dsi = to_mipi_dsi_device(ctx->dev);
+	u8 bpp;
+
+	if (dsi->format == MIPI_DSI_FMT_RGB888)
+		bpp = 0x77;
+	else if (dsi->format == MIPI_DSI_FMT_RGB565)
+		bpp = 0x55;
+	else
+		bpp = 0x66;
+
+	hx8369a_dcs_write_seq(ctx, SETPIXEL_FMT, bpp);
+}
+
+static void hx8369a_dsi_set_column_address(struct hx8369a *ctx)
+{
+	u8 ecl = 0;
+
+	switch (ctx->res_sel) {
+	case HX8369A_RES_480_864:
+	case HX8369A_RES_480_854:
+	case HX8369A_RES_480_800:
+	case HX8369A_RES_480_640:
+	case HX8369A_RES_480_720:
+		ecl = 0xdf;
+		break;
+	case HX8369A_RES_360_640:
+		ecl = 0x67;
+		break;
+	}
+
+	hx8369a_dcs_write_seq(ctx, SETCLUMN_ADDR, 0x00, 0x00, 0x01, ecl);
+}
+
+static void hx8369a_dsi_set_page_address(struct hx8369a *ctx)
+{
+	u8 epl = 0, eph = 0;
+
+	switch (ctx->res_sel) {
+	case HX8369A_RES_480_864:
+		epl = 0x5f;
+		eph = 0x03;
+		break;
+	case HX8369A_RES_480_854:
+		epl = 0x55;
+		eph = 0x03;
+		break;
+	case HX8369A_RES_480_800:
+		epl = 0x1f;
+		eph = 0x03;
+		break;
+	case HX8369A_RES_480_640:
+	case HX8369A_RES_360_640:
+		epl = 0x7f;
+		eph = 0x02;
+		break;
+	case HX8369A_RES_480_720:
+		epl = 0xcf;
+		eph = 0x02;
+		break;
+	}
+
+	hx8369a_dcs_write_seq(ctx, SETPAGE_ADDR, 0x00, 0x00, eph, epl);
+}
+
+static void hx8369a_dsi_write_display_brightness(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, WRDISBV, 0xff);
+}
+
+static void hx8369a_dsi_write_cabc(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, WRCABC, 0x01);
+}
+
+static void hx8369a_dsi_write_control_display(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, WRCTRLD, 0x24);
+}
+
+static void hx8369a_dsi_panel_init(struct hx8369a *ctx)
+{
+	hx8369a_dsi_set_display_related_register(ctx);
+	hx8369a_dsi_set_display_waveform_cycle(ctx);
+	hx8369a_dsi_set_gip(ctx);
+	hx8369a_dsi_set_power(ctx);
+	hx8369a_dsi_set_vcom_voltage(ctx);
+	hx8369a_dsi_set_panel(ctx);
+	hx8369a_dsi_set_gamma_curve(ctx);
+	hx8369a_dsi_set_mipi(ctx);
+	hx8369a_dsi_set_interface_pixel_fomat(ctx);
+	hx8369a_dsi_set_column_address(ctx);
+	hx8369a_dsi_set_page_address(ctx);
+	hx8369a_dsi_write_display_brightness(ctx);
+	hx8369a_dsi_write_cabc(ctx);
+	hx8369a_dsi_write_control_display(ctx);
+}
+
+static void hx8369a_dsi_set_extension_command(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETEXTC, 0xff, 0x83, 0x69);
+}
+
+static void 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);
+}
+
+static int hx8369a_dsi_set_sequence(struct hx8369a *ctx)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	int ret;
+
+	hx8369a_dsi_set_extension_command(ctx);
+	hx8369a_dsi_set_maximum_return_packet_size(ctx, 4);
+	hx8369a_dsi_panel_init(ctx);
+
+	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+	if (ret < 0) {
+		dev_err(ctx->dev, "failed to exit sleep mode: %d\n", ret);
+		return ret;
+	}
+
+	ret = mipi_dsi_dcs_set_display_on(dsi);
+	if (ret < 0) {
+		dev_err(ctx->dev, "failed to set display on: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+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->power_on_delay);
+
+	gpiod_set_value(ctx->reset_gpio, 1);
+	usleep_range(50, 60);
+	gpiod_set_value(ctx->reset_gpio, 0);
+
+	msleep(ctx->reset_delay);
+
+	return 0;
+}
+
+static int hx8369a_power_off(struct hx8369a *ctx)
+{
+	return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);;
+}
+
+static int hx8369a_dsi_disable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+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);
+		return ret;
+	}
+
+	ret = mipi_dsi_dcs_set_display_off(dsi);
+	if (ret < 0) {
+		dev_err(ctx->dev, "failed to set display off: %d\n", ret);
+		return ret;
+	}
+
+	msleep(40);
+
+	return hx8369a_power_off(ctx);
+}
+
+static int hx8369a_dsi_prepare(struct drm_panel *panel)
+{
+	struct hx8369a *ctx = panel_to_hx8369a(panel);
+	int ret;
+
+	ret = hx8369a_power_on(ctx);
+	if (ret < 0)
+		return ret;
+
+	ret = hx8369a_dsi_set_sequence(ctx);
+
+	return ret;
+}
+
+static int hx8369a_dsi_enable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int hx8369a_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct hx8369a *ctx = panel_to_hx8369a(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_create(connector->dev);
+	if (!mode) {
+		DRM_ERROR("failed to create a new display mode\n");
+		return 0;
+	}
+
+	drm_display_mode_from_videomode(&ctx->vm, mode);
+	mode->width_mm = ctx->width_mm;
+	mode->height_mm = ctx->height_mm;
+	connector->display_info.width_mm = mode->width_mm;
+	connector->display_info.height_mm = mode->height_mm;
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	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_parse_dt(struct hx8369a *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct device_node *np = dev->of_node;
+	int ret;
+
+	ret = of_get_videomode(np, &ctx->vm, 0);
+	if (ret < 0)
+		return ret;
+
+	of_property_read_u32(np, "bs", &ctx->mpu_interface);
+	if (ctx->mpu_interface != HX8369A_DSI_VIDEO_MODE &&
+	    ctx->mpu_interface != HX8369A_DSI_CMD_MODE) {
+		dev_err(dev, "unsupported MPU interface 0x%01x\n",
+			ctx->mpu_interface);
+		return -EINVAL;
+	}
+
+	if (hx8369a_is_dsi_interface(ctx)) {
+		ret = of_property_read_u32(np, "data-lanes", &ctx->data_lanes);
+		if (ret || ctx->data_lanes == 0 || ctx->data_lanes > 2) {
+			dev_err(dev, "cannot get correct data lanes\n");
+			return -EINVAL;
+		}
+	}
+
+	of_property_read_u32(np, "power-on-delay", &ctx->power_on_delay);
+	of_property_read_u32(np, "reset-delay", &ctx->reset_delay);
+	of_property_read_u32(np, "panel-width-mm", &ctx->width_mm);
+	of_property_read_u32(np, "panel-height-mm", &ctx->height_mm);
+
+	return 0;
+}
+
+static int hx8369a_vm_to_res_sel(struct hx8369a *ctx)
+{
+	switch (ctx->vm.hactive) {
+	case 480:
+		switch (ctx->vm.vactive) {
+		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:
+			ctx->res_sel = HX8369A_RES_DISABLE;
+			break;
+		}
+		break;
+	case 360:
+		if (ctx->vm.vactive == 640)
+			ctx->res_sel = HX8369A_RES_360_640;
+		else
+			ctx->res_sel = HX8369A_RES_DISABLE;
+		break;
+	default:
+		ctx->res_sel = HX8369A_RES_DISABLE;
+		break;
+	}
+
+	if (ctx->res_sel == HX8369A_RES_DISABLE) {
+		dev_err(ctx->dev, "unsupported resolution %dx%d\n",
+			ctx->vm.hactive, ctx->vm.vactive);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int hx8369a_dsi_probe(struct mipi_dsi_device *dsi)
+{
+	struct device *dev = &dsi->dev;
+	struct hx8369a *ctx;
+	int ret, i;
+	char bs[4];
+
+	ctx = devm_kzalloc(dev, sizeof(struct hx8369a), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	ctx->dev = dev;
+
+	ret = hx8369a_parse_dt(ctx);
+	if (ret < 0)
+		return ret;
+
+	ret = hx8369a_vm_to_res_sel(ctx);
+	if (ret < 0)
+		return ret;
+
+	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_err(dev, "failed to get regulators: %d\n", ret);
+		return ret;
+	}
+
+	ctx->reset_gpio = devm_gpiod_get(dev, "reset");
+	if (IS_ERR(ctx->reset_gpio)) {
+		dev_err(dev, "cannot get reset-gpios %ld\n",
+			PTR_ERR(ctx->reset_gpio));
+		return PTR_ERR(ctx->reset_gpio);
+	}
+	ret = gpiod_direction_output(ctx->reset_gpio, 0);
+	if (ret < 0) {
+		dev_err(dev, "cannot configure reset-gpios %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < 4; i++) {
+		snprintf(bs, sizeof(bs), "bs%d", i);
+		ctx->bs_gpio[i] = devm_gpiod_get(dev, bs);
+		if (IS_ERR(ctx->bs_gpio[i]))
+			continue;
+
+		ret = gpiod_direction_output(ctx->bs_gpio[i], 1);
+		if (ret < 0) {
+			dev_err(dev, "cannot configure bs%d-gpio %d\n", i, ret);
+			return ret;
+		}
+		dev_dbg(dev, "bs%d-gpio is configured\n", i);
+	}
+
+	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->channel = 0;
+	dsi->lanes = ctx->data_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 0;
+}
+
+static const struct of_device_id hx8369a_dsi_of_match[] = {
+	{ .compatible = "himax,hx8369a-dsi" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, hx8369a_dsi_of_match);
+
+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,
+	},
+};
+
+static int __init hx8369a_init(void)
+{
+	int err;
+
+	err = mipi_dsi_driver_register(&hx8369a_dsi_driver);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+module_init(hx8369a_init);
+
+static void __exit hx8369a_exit(void)
+{
+	mipi_dsi_driver_unregister(&hx8369a_dsi_driver);
+}
+module_exit(hx8369a_exit);
+
+MODULE_DESCRIPTION("Himax HX8369A panel driver");
+MODULE_LICENSE("GPL v2");
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 10/15] drm: panel: Add support for Himax HX8369A MIPI DSI panel
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds support for Himax HX8369A MIPI DSI panel.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 .../devicetree/bindings/panel/himax,hx8369a.txt    |  86 +++
 drivers/gpu/drm/panel/Kconfig                      |   6 +
 drivers/gpu/drm/panel/Makefile                     |   1 +
 drivers/gpu/drm/panel/panel-hx8369a.c              | 627 +++++++++++++++++++++
 4 files changed, 720 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
 create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c

diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
new file mode 100644
index 0000000..6fe251e
--- /dev/null
+++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
@@ -0,0 +1,86 @@
+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: "himax,hx8369a-dsi"
+  - reg: the virtual channel number of a DSI peripheral
+  - reset-gpios: a GPIO spec for the reset pin
+  - data-lanes: the data lane number of a DSI peripheral
+  - display-timings: timings for the connected panel as described by [1]
+  - bs: the interface mode number described by the following table
+        --------------------------
+       | DBI_TYPE_A_8BIT     |  0 |
+       | DBI_TYPE_A_9BIT     |  1 |
+       | DBI_TYPE_A_16BIT    |  2 |
+       | DBI_TYPE_A_18BIT    |  3 |
+       | DBI_TYPE_B_8BIT     |  4 |
+       | DBI_TYPE_B_9BIT     |  5 |
+       | DBI_TYPE_B_16BIT    |  6 |
+       | DBI_TYPE_B_18BIT    |  7 |
+       | DSI_CMD_MODE        |  8 |
+       | DBI_TYPE_B_24BIT    |  9 |
+       | DSI_VIDEO_MODE      | 10 |
+       | MDDI                | 11 |
+       | DPI_DBI_TYPE_C_OPT1 | 12 |
+       | DPI_DBI_TYPE_C_OPT2 | 13 |
+       | DPI_DBI_TYPE_C_OPT3 | 14 |
+        --------------------------
+
+Optional properties:
+  - power-on-delay: delay after turning regulators on [ms]
+  - reset-delay: delay after reset sequence [ms]
+  - 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
+  - bs0-gpios: a GPIO spec for the pin BS0
+  - bs1-gpios: a GPIO spec for the pin BS1
+  - bs2-gpios: a GPIO spec for the pin BS2
+  - bs3-gpios: a GPIO spec for the pin BS3
+  - panel-width-mm: physical panel width [mm]
+  - panel-height-mm: physical panel height [mm]
+
+[1]: Documentation/devicetree/bindings/video/display-timing.txt
+
+Example:
+	panel at 0 {
+		compatible = "himax,hx8369a-dsi";
+		reg = <0>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_mipi_panel>;
+		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
+		reset-delay = <120>;
+		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
+		data-lanes = <2>;
+		panel-width-mm = <45>;
+		panel-height-mm = <76>;
+		bs = <10>;
+		status = "okay";
+
+		display-timings {
+			native-mode = <&timing1>;
+			timing1: truly-tft480800-16-e {
+			clock-frequency = <26400000>;
+			hactive = <480>;
+			vactive = <800>;
+			hfront-porch = <8>;
+			hback-porch = <8>;
+			hsync-len = <8>;
+			vfront-porch = <6>;
+			vback-porch = <6>;
+			vsync-len = <6>;
+		};
+	};
+};
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 024e98e..f1a5b58 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -40,4 +40,10 @@ config DRM_PANEL_SHARP_LQ101R1SX01
 	  To compile this driver as a module, choose M here: the module
 	  will be called panel-sharp-lq101r1sx01.
 
+config DRM_PANEL_HX8369A
+	tristate "HX8369A panel"
+	depends on OF
+	select DRM_MIPI_DSI
+	select VIDEOMODE_HELPERS
+
 endmenu
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 4b2a043..d6768ca 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.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
+obj-$(CONFIG_DRM_PANEL_HX8369A) += panel-hx8369a.o
diff --git a/drivers/gpu/drm/panel/panel-hx8369a.c b/drivers/gpu/drm/panel/panel-hx8369a.c
new file mode 100644
index 0000000..a8cdaea
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-hx8369a.c
@@ -0,0 +1,627 @@
+/*
+ * Himax HX8369A panel driver.
+ *
+ * Copyright (C) 2011-2014 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/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#define SETCLUMN_ADDR	0x2a
+#define SETPAGE_ADDR 	0x2b
+#define SETPIXEL_FMT 	0x3a
+#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
+
+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,
+	HX8369A_RES_DISABLE
+};
+
+struct hx8369a {
+	struct device *dev;
+	struct drm_panel panel;
+
+	struct regulator_bulk_data supplies[5];
+	struct gpio_desc *reset_gpio;
+	u32 power_on_delay;
+	u32 reset_delay;
+	struct gpio_desc *bs_gpio[4];
+	u32 data_lanes;
+	u32 mpu_interface;
+	struct videomode vm;
+	u32 width_mm;
+	u32 height_mm;
+	u8 res_sel;
+};
+
+static inline struct hx8369a *panel_to_hx8369a(struct drm_panel *panel)
+{
+	return container_of(panel, struct hx8369a, panel);
+}
+
+static bool hx8369a_is_dsi_interface(struct hx8369a *ctx) {
+	if (ctx->mpu_interface == HX8369A_DSI_VIDEO_MODE ||
+	    ctx->mpu_interface == HX8369A_DSI_CMD_MODE)
+		return true;
+
+	return false;
+}
+
+static void hx8369a_dcs_write(struct hx8369a *ctx, 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, "error %zd writing dcs seq: %*ph\n", ret, len,
+			data);
+}
+
+#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, d, ARRAY_SIZE(d));\
+})
+
+#define hx8369a_dcs_write_seq_static(ctx, seq...) \
+({\
+	static const u8 d[] = { seq };\
+	hx8369a_dcs_write(ctx, d, ARRAY_SIZE(d));\
+})
+
+static void hx8369a_dsi_set_display_related_register(struct hx8369a *ctx)
+{
+	u8 sec_p = (ctx->res_sel << 4) | 0x03;
+
+	hx8369a_dcs_write_seq(ctx, SETDISP, 0x00, sec_p, 0x03,
+		0x03, 0x70, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
+		0x03, 0x03, 0x00, 0x01);
+}
+
+static void hx8369a_dsi_set_display_waveform_cycle(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETCYC, 0x00, 0x1d, 0x5f, 0x0e, 0x06);
+}
+
+static void hx8369a_dsi_set_gip(struct hx8369a *ctx)
+{
+	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 void hx8369a_dsi_set_power(struct hx8369a *ctx)
+{
+	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 void hx8369a_dsi_set_vcom_voltage(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETVCOM, 0x56, 0x56);
+}
+
+static void hx8369a_dsi_set_panel(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETPANEL, 0x02);
+}
+
+static void hx8369a_dsi_set_gamma_curve(struct hx8369a *ctx)
+{
+	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 void hx8369a_dsi_set_mipi(struct hx8369a *ctx)
+{
+	u8 eleventh_p = ctx->data_lanes == 2 ? 0x11 : 0x10;
+
+	hx8369a_dcs_write_seq(ctx, SETMIPI, 0x00, 0xa0, 0xc6, 0x00, 0x0a, 0x00,
+			0x10, 0x30, 0x6f, 0x02, eleventh_p, 0x18, 0x40);
+}
+
+static void hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx)
+{
+	struct mipi_dsi_device * dsi = to_mipi_dsi_device(ctx->dev);
+	u8 bpp;
+
+	if (dsi->format == MIPI_DSI_FMT_RGB888)
+		bpp = 0x77;
+	else if (dsi->format == MIPI_DSI_FMT_RGB565)
+		bpp = 0x55;
+	else
+		bpp = 0x66;
+
+	hx8369a_dcs_write_seq(ctx, SETPIXEL_FMT, bpp);
+}
+
+static void hx8369a_dsi_set_column_address(struct hx8369a *ctx)
+{
+	u8 ecl = 0;
+
+	switch (ctx->res_sel) {
+	case HX8369A_RES_480_864:
+	case HX8369A_RES_480_854:
+	case HX8369A_RES_480_800:
+	case HX8369A_RES_480_640:
+	case HX8369A_RES_480_720:
+		ecl = 0xdf;
+		break;
+	case HX8369A_RES_360_640:
+		ecl = 0x67;
+		break;
+	}
+
+	hx8369a_dcs_write_seq(ctx, SETCLUMN_ADDR, 0x00, 0x00, 0x01, ecl);
+}
+
+static void hx8369a_dsi_set_page_address(struct hx8369a *ctx)
+{
+	u8 epl = 0, eph = 0;
+
+	switch (ctx->res_sel) {
+	case HX8369A_RES_480_864:
+		epl = 0x5f;
+		eph = 0x03;
+		break;
+	case HX8369A_RES_480_854:
+		epl = 0x55;
+		eph = 0x03;
+		break;
+	case HX8369A_RES_480_800:
+		epl = 0x1f;
+		eph = 0x03;
+		break;
+	case HX8369A_RES_480_640:
+	case HX8369A_RES_360_640:
+		epl = 0x7f;
+		eph = 0x02;
+		break;
+	case HX8369A_RES_480_720:
+		epl = 0xcf;
+		eph = 0x02;
+		break;
+	}
+
+	hx8369a_dcs_write_seq(ctx, SETPAGE_ADDR, 0x00, 0x00, eph, epl);
+}
+
+static void hx8369a_dsi_write_display_brightness(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, WRDISBV, 0xff);
+}
+
+static void hx8369a_dsi_write_cabc(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, WRCABC, 0x01);
+}
+
+static void hx8369a_dsi_write_control_display(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, WRCTRLD, 0x24);
+}
+
+static void hx8369a_dsi_panel_init(struct hx8369a *ctx)
+{
+	hx8369a_dsi_set_display_related_register(ctx);
+	hx8369a_dsi_set_display_waveform_cycle(ctx);
+	hx8369a_dsi_set_gip(ctx);
+	hx8369a_dsi_set_power(ctx);
+	hx8369a_dsi_set_vcom_voltage(ctx);
+	hx8369a_dsi_set_panel(ctx);
+	hx8369a_dsi_set_gamma_curve(ctx);
+	hx8369a_dsi_set_mipi(ctx);
+	hx8369a_dsi_set_interface_pixel_fomat(ctx);
+	hx8369a_dsi_set_column_address(ctx);
+	hx8369a_dsi_set_page_address(ctx);
+	hx8369a_dsi_write_display_brightness(ctx);
+	hx8369a_dsi_write_cabc(ctx);
+	hx8369a_dsi_write_control_display(ctx);
+}
+
+static void hx8369a_dsi_set_extension_command(struct hx8369a *ctx)
+{
+	hx8369a_dcs_write_seq_static(ctx, SETEXTC, 0xff, 0x83, 0x69);
+}
+
+static void 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);
+}
+
+static int hx8369a_dsi_set_sequence(struct hx8369a *ctx)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	int ret;
+
+	hx8369a_dsi_set_extension_command(ctx);
+	hx8369a_dsi_set_maximum_return_packet_size(ctx, 4);
+	hx8369a_dsi_panel_init(ctx);
+
+	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+	if (ret < 0) {
+		dev_err(ctx->dev, "failed to exit sleep mode: %d\n", ret);
+		return ret;
+	}
+
+	ret = mipi_dsi_dcs_set_display_on(dsi);
+	if (ret < 0) {
+		dev_err(ctx->dev, "failed to set display on: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+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->power_on_delay);
+
+	gpiod_set_value(ctx->reset_gpio, 1);
+	usleep_range(50, 60);
+	gpiod_set_value(ctx->reset_gpio, 0);
+
+	msleep(ctx->reset_delay);
+
+	return 0;
+}
+
+static int hx8369a_power_off(struct hx8369a *ctx)
+{
+	return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);;
+}
+
+static int hx8369a_dsi_disable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+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);
+		return ret;
+	}
+
+	ret = mipi_dsi_dcs_set_display_off(dsi);
+	if (ret < 0) {
+		dev_err(ctx->dev, "failed to set display off: %d\n", ret);
+		return ret;
+	}
+
+	msleep(40);
+
+	return hx8369a_power_off(ctx);
+}
+
+static int hx8369a_dsi_prepare(struct drm_panel *panel)
+{
+	struct hx8369a *ctx = panel_to_hx8369a(panel);
+	int ret;
+
+	ret = hx8369a_power_on(ctx);
+	if (ret < 0)
+		return ret;
+
+	ret = hx8369a_dsi_set_sequence(ctx);
+
+	return ret;
+}
+
+static int hx8369a_dsi_enable(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int hx8369a_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct hx8369a *ctx = panel_to_hx8369a(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_create(connector->dev);
+	if (!mode) {
+		DRM_ERROR("failed to create a new display mode\n");
+		return 0;
+	}
+
+	drm_display_mode_from_videomode(&ctx->vm, mode);
+	mode->width_mm = ctx->width_mm;
+	mode->height_mm = ctx->height_mm;
+	connector->display_info.width_mm = mode->width_mm;
+	connector->display_info.height_mm = mode->height_mm;
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	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_parse_dt(struct hx8369a *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct device_node *np = dev->of_node;
+	int ret;
+
+	ret = of_get_videomode(np, &ctx->vm, 0);
+	if (ret < 0)
+		return ret;
+
+	of_property_read_u32(np, "bs", &ctx->mpu_interface);
+	if (ctx->mpu_interface != HX8369A_DSI_VIDEO_MODE &&
+	    ctx->mpu_interface != HX8369A_DSI_CMD_MODE) {
+		dev_err(dev, "unsupported MPU interface 0x%01x\n",
+			ctx->mpu_interface);
+		return -EINVAL;
+	}
+
+	if (hx8369a_is_dsi_interface(ctx)) {
+		ret = of_property_read_u32(np, "data-lanes", &ctx->data_lanes);
+		if (ret || ctx->data_lanes == 0 || ctx->data_lanes > 2) {
+			dev_err(dev, "cannot get correct data lanes\n");
+			return -EINVAL;
+		}
+	}
+
+	of_property_read_u32(np, "power-on-delay", &ctx->power_on_delay);
+	of_property_read_u32(np, "reset-delay", &ctx->reset_delay);
+	of_property_read_u32(np, "panel-width-mm", &ctx->width_mm);
+	of_property_read_u32(np, "panel-height-mm", &ctx->height_mm);
+
+	return 0;
+}
+
+static int hx8369a_vm_to_res_sel(struct hx8369a *ctx)
+{
+	switch (ctx->vm.hactive) {
+	case 480:
+		switch (ctx->vm.vactive) {
+		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:
+			ctx->res_sel = HX8369A_RES_DISABLE;
+			break;
+		}
+		break;
+	case 360:
+		if (ctx->vm.vactive == 640)
+			ctx->res_sel = HX8369A_RES_360_640;
+		else
+			ctx->res_sel = HX8369A_RES_DISABLE;
+		break;
+	default:
+		ctx->res_sel = HX8369A_RES_DISABLE;
+		break;
+	}
+
+	if (ctx->res_sel == HX8369A_RES_DISABLE) {
+		dev_err(ctx->dev, "unsupported resolution %dx%d\n",
+			ctx->vm.hactive, ctx->vm.vactive);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int hx8369a_dsi_probe(struct mipi_dsi_device *dsi)
+{
+	struct device *dev = &dsi->dev;
+	struct hx8369a *ctx;
+	int ret, i;
+	char bs[4];
+
+	ctx = devm_kzalloc(dev, sizeof(struct hx8369a), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	ctx->dev = dev;
+
+	ret = hx8369a_parse_dt(ctx);
+	if (ret < 0)
+		return ret;
+
+	ret = hx8369a_vm_to_res_sel(ctx);
+	if (ret < 0)
+		return ret;
+
+	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_err(dev, "failed to get regulators: %d\n", ret);
+		return ret;
+	}
+
+	ctx->reset_gpio = devm_gpiod_get(dev, "reset");
+	if (IS_ERR(ctx->reset_gpio)) {
+		dev_err(dev, "cannot get reset-gpios %ld\n",
+			PTR_ERR(ctx->reset_gpio));
+		return PTR_ERR(ctx->reset_gpio);
+	}
+	ret = gpiod_direction_output(ctx->reset_gpio, 0);
+	if (ret < 0) {
+		dev_err(dev, "cannot configure reset-gpios %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < 4; i++) {
+		snprintf(bs, sizeof(bs), "bs%d", i);
+		ctx->bs_gpio[i] = devm_gpiod_get(dev, bs);
+		if (IS_ERR(ctx->bs_gpio[i]))
+			continue;
+
+		ret = gpiod_direction_output(ctx->bs_gpio[i], 1);
+		if (ret < 0) {
+			dev_err(dev, "cannot configure bs%d-gpio %d\n", i, ret);
+			return ret;
+		}
+		dev_dbg(dev, "bs%d-gpio is configured\n", i);
+	}
+
+	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->channel = 0;
+	dsi->lanes = ctx->data_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 0;
+}
+
+static const struct of_device_id hx8369a_dsi_of_match[] = {
+	{ .compatible = "himax,hx8369a-dsi" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, hx8369a_dsi_of_match);
+
+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,
+	},
+};
+
+static int __init hx8369a_init(void)
+{
+	int err;
+
+	err = mipi_dsi_driver_register(&hx8369a_dsi_driver);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+module_init(hx8369a_init);
+
+static void __exit hx8369a_exit(void)
+{
+	mipi_dsi_driver_unregister(&hx8369a_dsi_driver);
+}
+module_exit(hx8369a_exit);
+
+MODULE_DESCRIPTION("Himax HX8369A panel driver");
+MODULE_LICENSE("GPL v2");
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 11/15] ARM: dtsi: imx6qdl: Add support for MIPI DSI host controller
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

This patch adds support for MIPI DSI host controller.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 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 5d92ad7..4769767 100644
--- a/arch/arm/boot/dts/imx6qdl.dtsi
+++ b/arch/arm/boot/dts/imx6qdl.dtsi
@@ -1006,7 +1006,14 @@
 			mipi_dsi: mipi@021e0000 {
 				#address-cells = <1>;
 				#size-cells = <0>;
+				compatible = "fsl,imx6q-mipi-dsi";
 				reg = <0x021e0000 0x4000>;
+				interrupts = <0 102 IRQ_TYPE_LEVEL_HIGH>;
+				gpr = <&gpr>;
+				clocks = <&clks IMX6QDL_CLK_VIDEO_27M>,
+					 <&clks IMX6QDL_CLK_HSI_TX>,
+					 <&clks IMX6QDL_CLK_HSI_TX>;
+				clock-names = "pllref", "pllref_gate", "core_cfg";
 				status = "disabled";
 
 				port@0 {
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 11/15] ARM: dtsi: imx6qdl: Add support for MIPI DSI host controller
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

This patch adds support for MIPI DSI host controller.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 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 5d92ad7..4769767 100644
--- a/arch/arm/boot/dts/imx6qdl.dtsi
+++ b/arch/arm/boot/dts/imx6qdl.dtsi
@@ -1006,7 +1006,14 @@
 			mipi_dsi: mipi@021e0000 {
 				#address-cells = <1>;
 				#size-cells = <0>;
+				compatible = "fsl,imx6q-mipi-dsi";
 				reg = <0x021e0000 0x4000>;
+				interrupts = <0 102 IRQ_TYPE_LEVEL_HIGH>;
+				gpr = <&gpr>;
+				clocks = <&clks IMX6QDL_CLK_VIDEO_27M>,
+					 <&clks IMX6QDL_CLK_HSI_TX>,
+					 <&clks IMX6QDL_CLK_HSI_TX>;
+				clock-names = "pllref", "pllref_gate", "core_cfg";
 				status = "disabled";
 
 				port@0 {
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 11/15] ARM: dtsi: imx6qdl: Add support for MIPI DSI host controller
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

This patch adds support for MIPI DSI host controller.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
---
 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 5d92ad7..4769767 100644
--- a/arch/arm/boot/dts/imx6qdl.dtsi
+++ b/arch/arm/boot/dts/imx6qdl.dtsi
@@ -1006,7 +1006,14 @@
 			mipi_dsi: mipi at 021e0000 {
 				#address-cells = <1>;
 				#size-cells = <0>;
+				compatible = "fsl,imx6q-mipi-dsi";
 				reg = <0x021e0000 0x4000>;
+				interrupts = <0 102 IRQ_TYPE_LEVEL_HIGH>;
+				gpr = <&gpr>;
+				clocks = <&clks IMX6QDL_CLK_VIDEO_27M>,
+					 <&clks IMX6QDL_CLK_HSI_TX>,
+					 <&clks IMX6QDL_CLK_HSI_TX>;
+				clock-names = "pllref", "pllref_gate", "core_cfg";
 				status = "disabled";
 
 				port at 0 {
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 12/15] ARM: dts: imx6qdl-sabresd: Add support for TRULY TFT480800-16-E MIPI DSI panel
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

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>
---
 arch/arm/boot/dts/imx6qdl-sabresd.dtsi | 41 ++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
index baf2f00..483aa5f 100644
--- a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
+++ b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
@@ -482,6 +482,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 {
@@ -518,6 +525,40 @@
 	};
 };
 
+&mipi_dsi {
+	status = "okay";
+
+	panel@0 {
+		compatible = "himax,hx8369a-dsi";
+		reg = <0>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_mipi_panel>;
+		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
+		reset-delay = <120>;
+		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
+		data-lanes = <2>;
+		panel-width-mm = <45>;
+		panel-height-mm = <76>;
+		bs = <10>;
+		status = "okay";
+
+		display-timings {
+			native-mode = <&timing1>;
+			timing1: truly-tft480800-16-e {
+				clock-frequency = <26400000>;
+				hactive = <480>;
+				vactive = <800>;
+				hfront-porch = <8>;
+				hback-porch = <8>;
+				hsync-len = <8>;
+				vfront-porch = <6>;
+				vback-porch = <6>;
+				vsync-len = <6>;
+			};
+		};
+	};
+};
+
 &pcie {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_pcie>;
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 12/15] ARM: dts: imx6qdl-sabresd: Add support for TRULY TFT480800-16-E MIPI DSI panel
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

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>
---
 arch/arm/boot/dts/imx6qdl-sabresd.dtsi | 41 ++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
index baf2f00..483aa5f 100644
--- a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
+++ b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
@@ -482,6 +482,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 {
@@ -518,6 +525,40 @@
 	};
 };
 
+&mipi_dsi {
+	status = "okay";
+
+	panel@0 {
+		compatible = "himax,hx8369a-dsi";
+		reg = <0>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_mipi_panel>;
+		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
+		reset-delay = <120>;
+		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
+		data-lanes = <2>;
+		panel-width-mm = <45>;
+		panel-height-mm = <76>;
+		bs = <10>;
+		status = "okay";
+
+		display-timings {
+			native-mode = <&timing1>;
+			timing1: truly-tft480800-16-e {
+				clock-frequency = <26400000>;
+				hactive = <480>;
+				vactive = <800>;
+				hfront-porch = <8>;
+				hback-porch = <8>;
+				hsync-len = <8>;
+				vfront-porch = <6>;
+				vback-porch = <6>;
+				vsync-len = <6>;
+			};
+		};
+	};
+};
+
 &pcie {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_pcie>;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 12/15] ARM: dts: imx6qdl-sabresd: Add support for TRULY TFT480800-16-E MIPI DSI panel
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

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>
---
 arch/arm/boot/dts/imx6qdl-sabresd.dtsi | 41 ++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
index baf2f00..483aa5f 100644
--- a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
+++ b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
@@ -482,6 +482,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 {
@@ -518,6 +525,40 @@
 	};
 };
 
+&mipi_dsi {
+	status = "okay";
+
+	panel at 0 {
+		compatible = "himax,hx8369a-dsi";
+		reg = <0>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pinctrl_mipi_panel>;
+		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
+		reset-delay = <120>;
+		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
+		data-lanes = <2>;
+		panel-width-mm = <45>;
+		panel-height-mm = <76>;
+		bs = <10>;
+		status = "okay";
+
+		display-timings {
+			native-mode = <&timing1>;
+			timing1: truly-tft480800-16-e {
+				clock-frequency = <26400000>;
+				hactive = <480>;
+				vactive = <800>;
+				hfront-porch = <8>;
+				hback-porch = <8>;
+				hsync-len = <8>;
+				vfront-porch = <6>;
+				vback-porch = <6>;
+				vsync-len = <6>;
+			};
+		};
+	};
+};
+
 &pcie {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_pcie>;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 13/15] ARM: imx_v6_v7_defconfig: Cleanup for imx drm being moved out of staging
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

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>
---
 arch/arm/configs/imx_v6_v7_defconfig | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 6790f1b..0dbd0c3 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -192,7 +192,13 @@ 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_IPUV3=y
+CONFIG_DRM_IMX_HDMI=y
 CONFIG_LCD_CLASS_DEVICE=y
 CONFIG_LCD_L4F00242T03=y
 CONFIG_LCD_PLATFORM=y
@@ -249,13 +255,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
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 13/15] ARM: imx_v6_v7_defconfig: Cleanup for imx drm being moved out of staging
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

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>
---
 arch/arm/configs/imx_v6_v7_defconfig | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 6790f1b..0dbd0c3 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -192,7 +192,13 @@ 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_IPUV3=y
+CONFIG_DRM_IMX_HDMI=y
 CONFIG_LCD_CLASS_DEVICE=y
 CONFIG_LCD_L4F00242T03=y
 CONFIG_LCD_PLATFORM=y
@@ -249,13 +255,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
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 13/15] ARM: imx_v6_v7_defconfig: Cleanup for imx drm being moved out of staging
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

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>
---
 arch/arm/configs/imx_v6_v7_defconfig | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 6790f1b..0dbd0c3 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -192,7 +192,13 @@ 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_IPUV3=y
+CONFIG_DRM_IMX_HDMI=y
 CONFIG_LCD_CLASS_DEVICE=y
 CONFIG_LCD_L4F00242T03=y
 CONFIG_LCD_PLATFORM=y
@@ -249,13 +255,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
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 14/15] ARM: imx_v6_v7_defconfig: Add support for MIPI DSI host controller
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

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>
---
 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 0dbd0c3..3e0e589 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -199,6 +199,7 @@ CONFIG_DRM_IMX_TVE=y
 CONFIG_DRM_IMX_LDB=y
 CONFIG_DRM_IMX_IPUV3=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
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 14/15] ARM: imx_v6_v7_defconfig: Add support for MIPI DSI host controller
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux, kernel, airlied, linux-kernel, thierry.reding,
	p.zabel, mturquette, shawn.guo, linux-arm-kernel

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>
---
 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 0dbd0c3..3e0e589 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -199,6 +199,7 @@ CONFIG_DRM_IMX_TVE=y
 CONFIG_DRM_IMX_LDB=y
 CONFIG_DRM_IMX_IPUV3=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
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 14/15] ARM: imx_v6_v7_defconfig: Add support for MIPI DSI host controller
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

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>
---
 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 0dbd0c3..3e0e589 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -199,6 +199,7 @@ CONFIG_DRM_IMX_TVE=y
 CONFIG_DRM_IMX_LDB=y
 CONFIG_DRM_IMX_IPUV3=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
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 15/15] ARM: imx_v6_v7_defconfig: Add support for Himax HX8369A panel
  2014-12-10  8:37 ` Liu Ying
  (?)
@ 2014-12-10  8:37   ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

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>
---
 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 3e0e589..01b2b89 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -192,6 +192,7 @@ CONFIG_SOC_CAMERA_OV2640=y
 CONFIG_IMX_IPUV3_CORE=y
 CONFIG_DRM=y
 CONFIG_DRM_PANEL_SIMPLE=y
+CONFIG_DRM_PANEL_HX8369A=y
 CONFIG_DRM_IMX=y
 CONFIG_DRM_IMX_FB_HELPER=y
 CONFIG_DRM_IMX_PARALLEL_DISPLAY=y
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 15/15] ARM: imx_v6_v7_defconfig: Add support for Himax HX8369A panel
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: dri-devel
  Cc: devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	thierry.reding, shawn.guo, kernel, linux, mturquette, airlied

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>
---
 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 3e0e589..01b2b89 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -192,6 +192,7 @@ CONFIG_SOC_CAMERA_OV2640=y
 CONFIG_IMX_IPUV3_CORE=y
 CONFIG_DRM=y
 CONFIG_DRM_PANEL_SIMPLE=y
+CONFIG_DRM_PANEL_HX8369A=y
 CONFIG_DRM_IMX=y
 CONFIG_DRM_IMX_FB_HELPER=y
 CONFIG_DRM_IMX_PARALLEL_DISPLAY=y
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* [PATCH RFC 15/15] ARM: imx_v6_v7_defconfig: Add support for Himax HX8369A panel
@ 2014-12-10  8:37   ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-10  8:37 UTC (permalink / raw)
  To: linux-arm-kernel

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>
---
 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 3e0e589..01b2b89 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -192,6 +192,7 @@ CONFIG_SOC_CAMERA_OV2640=y
 CONFIG_IMX_IPUV3_CORE=y
 CONFIG_DRM=y
 CONFIG_DRM_PANEL_SIMPLE=y
+CONFIG_DRM_PANEL_HX8369A=y
 CONFIG_DRM_IMX=y
 CONFIG_DRM_IMX_FB_HELPER=y
 CONFIG_DRM_IMX_PARALLEL_DISPLAY=y
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
  2014-12-10  8:37   ` Liu Ying
  (?)
@ 2014-12-10 12:21     ` Thierry Reding
  -1 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 12:21 UTC (permalink / raw)
  To: Liu Ying
  Cc: dri-devel, devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	shawn.guo, kernel, linux, mturquette, airlied

[-- Attachment #1: Type: text/plain, Size: 1015 bytes --]

On Wed, Dec 10, 2014 at 04:37:17PM +0800, Liu Ying wrote:
> The MIPI DSI bus driver would try to add a DSI device for a host's every
> child node which contains the reg property.  Unfortunately, the existing
> i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg

There aren't any existing nodes for the DSI host on i.MX. This patch
series adds support for them.

> property, but the child nodes are ports pointing to dedicated CRTCs.
> So, this patch phases out the child nodes with input-port property before
> adding DSI devices for them and updates the MIPI DSI bus OF binding
> documentation.
> 
> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
> ---
>  Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
>  drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
>  2 files changed, 7 insertions(+), 2 deletions(-)

Sorry, but NAK. There's no need for this special-case. I'll go into more
detail in response to patch 09/15.

Thierry

[-- Attachment #2: Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
@ 2014-12-10 12:21     ` Thierry Reding
  0 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 12:21 UTC (permalink / raw)
  To: Liu Ying
  Cc: devicetree, linux, kernel, linux-kernel, dri-devel, mturquette,
	linux-arm-kernel


[-- Attachment #1.1: Type: text/plain, Size: 1015 bytes --]

On Wed, Dec 10, 2014 at 04:37:17PM +0800, Liu Ying wrote:
> The MIPI DSI bus driver would try to add a DSI device for a host's every
> child node which contains the reg property.  Unfortunately, the existing
> i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg

There aren't any existing nodes for the DSI host on i.MX. This patch
series adds support for them.

> property, but the child nodes are ports pointing to dedicated CRTCs.
> So, this patch phases out the child nodes with input-port property before
> adding DSI devices for them and updates the MIPI DSI bus OF binding
> documentation.
> 
> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
> ---
>  Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
>  drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
>  2 files changed, 7 insertions(+), 2 deletions(-)

Sorry, but NAK. There's no need for this special-case. I'll go into more
detail in response to patch 09/15.

Thierry

[-- Attachment #1.2: Type: application/pgp-signature, Size: 819 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
@ 2014-12-10 12:21     ` Thierry Reding
  0 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 12:21 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Dec 10, 2014 at 04:37:17PM +0800, Liu Ying wrote:
> The MIPI DSI bus driver would try to add a DSI device for a host's every
> child node which contains the reg property.  Unfortunately, the existing
> i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg

There aren't any existing nodes for the DSI host on i.MX. This patch
series adds support for them.

> property, but the child nodes are ports pointing to dedicated CRTCs.
> So, this patch phases out the child nodes with input-port property before
> adding DSI devices for them and updates the MIPI DSI bus OF binding
> documentation.
> 
> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
> ---
>  Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
>  drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
>  2 files changed, 7 insertions(+), 2 deletions(-)

Sorry, but NAK. There's no need for this special-case. I'll go into more
detail in response to patch 09/15.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20141210/6e6f1051/attachment.sig>

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
  2014-12-10  8:37   ` Liu Ying
  (?)
@ 2014-12-10 13:16     ` Thierry Reding
  -1 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 13:16 UTC (permalink / raw)
  To: Liu Ying
  Cc: dri-devel, devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	shawn.guo, kernel, linux, mturquette, airlied

[-- Attachment #1: Type: text/plain, Size: 16100 bytes --]

On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
> This patch adds i.MX 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>
> ---
>  .../devicetree/bindings/drm/imx/mipi_dsi.txt       |   81 ++
>  drivers/gpu/drm/imx/Kconfig                        |    6 +
>  drivers/gpu/drm/imx/Makefile                       |    1 +
>  drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
>  4 files changed, 1105 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>  create mode 100644 drivers/gpu/drm/imx/imx-mipi-dsi.c
> 
> 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..3d07fd7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
> @@ -0,0 +1,81 @@
> +Device-Tree bindings for MIPI DSI host controller
> +
> +MIPI DSI host controller
> +========================
> +
> +The MIPI DSI host controller is a Synopsys DesignWare IP.
> +It 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 : Should be "fsl,imx6q-mipi-dsi" for i.MX6q/sdl SoCs.
> + - 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.

Side-note: Shouldn't this really be a pinmux, then?

> + - clocks, clock-names : Phandles to the controller pllref, pllref_gate
> +           and core_cfg clocks, as described in [1] and [2].
> + - panel@0 : A panel node which contains a display-timings child node as
> +             defined in [3].

There's no need for these to be named panel@*. They could be bridges for
example. And no, they shouldn't contain a display-timings child node
either. Panels should have a proper driver and the driver being device
specific it should have the timings embedded.

> + - port@[0-4] : Up to four port nodes with endpoint definitions as defined
> +   in [4], corresponding to the four inputs to the controller multiplexer.
> +   Note that each port node should contain the input-port property to
> +   distinguish it from the panel node, as described in [5].

[4] says that you can group all port nodes under a ports parent node. I
think this is really what you want to do here to make it clear that the
ports aren't part of the DSI host binding part of the device.

> diff --git a/drivers/gpu/drm/imx/imx-mipi-dsi.c b/drivers/gpu/drm/imx/imx-mipi-dsi.c
[...]
> +/*
> + * i.MX drm driver - MIPI DSI Host Controller
> + *
> + * Copyright (C) 2011-2014 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/component.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/regmap.h>
> +#include <linux/videodev2.h>
> +#include <asm/div64.h>

Don't you want the more generic linux/math64.h here?

> +#include <drm/drmP.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_helper.h>

I don't see any of the functions defined in that header used here.

> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +#include <video/mipi_display.h>
> +#include <video/of_videomode.h>
> +#include <video/videomode.h>
> +
> +#include "imx-drm.h"
> +
> +#define DRIVER_NAME 			"imx-mipi-dsi"
> +
> +#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)

Your indentation here is... weird.

> +#define IMX_MIPI_DSI_MAX_DATA_LANES	2
> +
> +#define PHY_STATUS_TIMEOUT		10
> +#define CMD_PKT_STATUS_TIMEOUT		20
> +
> +#define IMX_MIPI_DSI_PLD_DATA_BUF_SIZE	4
> +
> +#define MHZ				1000000

Why not reuse the existing USEC_PER_SEC?

> +#define host_to_dsi(host) container_of(host, struct imx_mipi_dsi, dsi_host)
> +#define con_to_dsi(x) container_of(x, struct imx_mipi_dsi, connector)
> +#define enc_to_dsi(x) container_of(x, struct imx_mipi_dsi, encoder)

These should really be static inline functions for proper type safety.

> +struct imx_mipi_dsi {
> +	struct mipi_dsi_host dsi_host;
> +	struct drm_connector connector;
> +	struct drm_encoder encoder;
> +	struct device_node *panel_node;
> +	struct drm_panel *panel;
> +	struct device *dev;
> +
> +	struct regmap *regmap;
> +	void __iomem *base;
> +
> +	struct clk *pllref_clk;
> +	struct clk *pllref_gate_clk;
> +	struct clk *cfg_clk;
> +
> +	unsigned int lane_mbps; /* per lane */
> +	u32 channel;
> +	u32 lanes;
> +	u32 format;
> +	struct videomode vm;
> +
> +	bool enabled;

Do you really need this flag?

> +};
> +
> +enum {
> +	STATUS_TO_CLEAR,
> +	STATUS_TO_SET,
> +};
> +
> +struct dphy_pll_testdin_map {
> +	int max_mbps;

unsigned?

> +	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 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 int format_to_bpp(struct imx_mipi_dsi *dsi)
> +{
> +	switch (dsi->format) {
> +	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;
> +	default:
> +		dev_err(dsi->dev, "unsupported pixel format\n");
> +		return -EINVAL;
> +	}
> +}

Is there a reason why this can't be a standard MIPI DSI function?

> +
> +static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
> +			int timeout, bool to_set)
> +{
> +	u32 val;
> +	bool out = false;
> +
> +	val = dsi_read(dsi, reg);
> +	for (;;) {
> +		out = to_set ? (val & status) : !(val & status);
> +		if (out)
> +			break;
> +
> +		if (!timeout--)
> +			return -EFAULT;
> +
> +		msleep(1);
> +		val = dsi_read(dsi, reg);
> +	}
> +	return 0;
> +}

You should probably use a properly timed loop here. msleep() isn't
guaranteed to return after exactly one millisecond, so your timeout is
never going to be accurate. Something like the following would be better
in my opinion:

	timeout = jiffies + msecs_to_jiffies(timeout);

	while (time_before(jiffies, timeout)) {
		...
	}

Also timeout should be unsigned long in that case.

> +static int imx_mipi_dsi_host_attach(struct mipi_dsi_host *host,
> +				    struct mipi_dsi_device *device)
> +{
> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
> +	int ret;
> +
> +	if (device->lanes > IMX_MIPI_DSI_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;
> +	}
> +
> +	dsi->lanes = device->lanes;
> +	dsi->channel = device->channel;
> +	dsi->format = device->format;
> +	dsi->panel_node = device->dev.of_node;
> +	of_get_videomode(dsi->panel_node, &dsi->vm, 0);

No, you shouldn't use this. Query the mode from the panel instead. Or
rather, encode this requirement in ->mode_valid() since that'll give you
direct access to the mode.

That way, ->host_attach() becomes a filter for compatible devices, yet
devices may support multiple modes, therefore ->mode_valid() can be used
to filter out those that don't work with your DSI host controller. There
is a subtle difference here between devices that are compatible with the
controller and modes that the controller doesn't support.

> +
> +	ret = imx_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int imx_mipi_dsi_host_detach(struct mipi_dsi_host *host,
> +				    struct mipi_dsi_device *device)
> +{
> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
> +
> +	dsi->panel_node = NULL;
> +	dsi->panel = NULL;
> +
> +	return 0;
> +}

You'll want to detach from the panel here as well.

> +
> +static int imx_mipi_dsi_gen_pkt_hdr_write(struct imx_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 avaliable 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 imx_mipi_dsi_dcs_short_write(struct imx_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 imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
> +}

It seems like you would profit from converting to the newly introduced
mipi_dsi_create_packet() helper which does most of the packing along
with some sanity checking for you.

> +
> +static int imx_mipi_dsi_dcs_long_write(struct imx_mipi_dsi *dsi,
> +				       const struct mipi_dsi_msg *msg)
> +{
> +	const u32 *tx_buf = msg->tx_buf;
> +	int len = msg->tx_len, ret;
> +	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
> +
> +	if (msg->tx_len < 3) {
> +		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
> +			msg->tx_len);
> +		return -EINVAL;
> +	}
> +
> +	/* write the bulks */
> +	while (len / IMX_MIPI_DSI_PLD_DATA_BUF_SIZE) {
> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
> +		tx_buf++;
> +		len -= IMX_MIPI_DSI_PLD_DATA_BUF_SIZE;

This is confusing. Maybe a better option would be to use sizeof(*tx_buf)
instead of this macro. You're effectively consuming one u32 with each
write, irrespective of what the value of the macro is.

> +		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 avaliable "
> +					"write payload FIFO\n");
> +			return ret;
> +		}
> +	}
> +
> +	/* write the remainder */
> +	if (len > 0) {
> +		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 avaliable "
> +					"write payload FIFO\n");
> +			return ret;
> +		}
> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
> +	}

This is really duplicating what the above loop does. Why can't you do it
in a single loop? Also the transmission buffer isn't guaranteed to be a
multiple of 4 bytes, so you could have the situation where len > 0 and
len < 4 and yet you'll be reading 4 bytes by doing *tx_buf and accessing
memory that you shouldn't.

> +	return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
> +}
> +
> +static ssize_t imx_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
> +					  const struct mipi_dsi_msg *msg)
> +{
> +	struct imx_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 = imx_mipi_dsi_dcs_short_write(dsi, msg);
> +		break;
> +	case MIPI_DSI_DCS_LONG_WRITE:
> +		ret = imx_mipi_dsi_dcs_long_write(dsi, msg);
> +		break;
> +	default:
> +		dev_err(dsi->dev, "unsupported message type\n");
> +		ret = -EFAULT;

EFAULT really isn't appropriate here.

> +	}
> +
> +	return ret;
> +}
> +
> +static const struct mipi_dsi_host_ops imx_mipi_dsi_host_ops = {
> +	.attach = imx_mipi_dsi_host_attach,
> +	.detach = imx_mipi_dsi_host_detach,
> +	.transfer = imx_mipi_dsi_host_transfer,
> +};
> +
> +static enum drm_connector_status
> +imx_mipi_dsi_detect(struct drm_connector *connector, bool force)
> +{
> +	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
> +
> +	if (!dsi->panel) {
> +		dsi->panel = of_drm_find_panel(dsi->panel_node);
> +		if (dsi->panel)
> +			drm_panel_attach(dsi->panel, &dsi->connector);
> +	}
> +
> +	if (dsi->panel)
> +		return connector_status_connected;
> +
> +	return connector_status_disconnected;
> +
> +}

You really shouldn't be doing that here. of_drm_find_panel() returning
NULL should be considered cause for deferring probe. I suspect that the
driver doesn't really handle that very well at all, though, since if it
did you would've run into the same issue that Benjamin ran into this
morning.

> +static void imx_mipi_dsi_encoder_prepare(struct drm_encoder *encoder)
> +{
> +	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
> +	u32 interface_pix_fmt;
> +
> +	switch (dsi->format) {
> +	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:
> +		dev_err(dsi->dev, "unsupported DSI pixel format\n");
> +		return;

Why even try doing this if you know upfront that it can't be done. You
know much earlier than this that you can't drive the pixel format, why
not abort then?

People are much more likely to notice that the panel isn't supported if
the DSI output doesn't even show up (or doesn't expose any modes) rather
than if they have to find this error message in dmesg.

> +static void imx_mipi_dsi_video_mode_config(struct imx_mipi_dsi *dsi)
> +{
> +	u32 val;
> +
> +	val = VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER;
> +
> +	dsi_write(dsi, DSI_VID_MODE_CFG, val);
> +}

You probably want to parameterize based on the DSI peripheral's flags. I
see that you do reject devices with any other modes, so this may not be
necessary now. Out of curiosity, the hardware supports other modes,
right?

[...]
> +MODULE_LICENSE("GPL v2");

Sigh... according to your header comment this needs to be "GPL".

Thierry

[-- Attachment #2: Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
@ 2014-12-10 13:16     ` Thierry Reding
  0 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 13:16 UTC (permalink / raw)
  To: Liu Ying
  Cc: devicetree, linux, kernel, linux-kernel, dri-devel, mturquette,
	linux-arm-kernel


[-- Attachment #1.1: Type: text/plain, Size: 16100 bytes --]

On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
> This patch adds i.MX 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>
> ---
>  .../devicetree/bindings/drm/imx/mipi_dsi.txt       |   81 ++
>  drivers/gpu/drm/imx/Kconfig                        |    6 +
>  drivers/gpu/drm/imx/Makefile                       |    1 +
>  drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
>  4 files changed, 1105 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>  create mode 100644 drivers/gpu/drm/imx/imx-mipi-dsi.c
> 
> 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..3d07fd7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
> @@ -0,0 +1,81 @@
> +Device-Tree bindings for MIPI DSI host controller
> +
> +MIPI DSI host controller
> +========================
> +
> +The MIPI DSI host controller is a Synopsys DesignWare IP.
> +It 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 : Should be "fsl,imx6q-mipi-dsi" for i.MX6q/sdl SoCs.
> + - 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.

Side-note: Shouldn't this really be a pinmux, then?

> + - clocks, clock-names : Phandles to the controller pllref, pllref_gate
> +           and core_cfg clocks, as described in [1] and [2].
> + - panel@0 : A panel node which contains a display-timings child node as
> +             defined in [3].

There's no need for these to be named panel@*. They could be bridges for
example. And no, they shouldn't contain a display-timings child node
either. Panels should have a proper driver and the driver being device
specific it should have the timings embedded.

> + - port@[0-4] : Up to four port nodes with endpoint definitions as defined
> +   in [4], corresponding to the four inputs to the controller multiplexer.
> +   Note that each port node should contain the input-port property to
> +   distinguish it from the panel node, as described in [5].

[4] says that you can group all port nodes under a ports parent node. I
think this is really what you want to do here to make it clear that the
ports aren't part of the DSI host binding part of the device.

> diff --git a/drivers/gpu/drm/imx/imx-mipi-dsi.c b/drivers/gpu/drm/imx/imx-mipi-dsi.c
[...]
> +/*
> + * i.MX drm driver - MIPI DSI Host Controller
> + *
> + * Copyright (C) 2011-2014 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/component.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/regmap.h>
> +#include <linux/videodev2.h>
> +#include <asm/div64.h>

Don't you want the more generic linux/math64.h here?

> +#include <drm/drmP.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_helper.h>

I don't see any of the functions defined in that header used here.

> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +#include <video/mipi_display.h>
> +#include <video/of_videomode.h>
> +#include <video/videomode.h>
> +
> +#include "imx-drm.h"
> +
> +#define DRIVER_NAME 			"imx-mipi-dsi"
> +
> +#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)

Your indentation here is... weird.

> +#define IMX_MIPI_DSI_MAX_DATA_LANES	2
> +
> +#define PHY_STATUS_TIMEOUT		10
> +#define CMD_PKT_STATUS_TIMEOUT		20
> +
> +#define IMX_MIPI_DSI_PLD_DATA_BUF_SIZE	4
> +
> +#define MHZ				1000000

Why not reuse the existing USEC_PER_SEC?

> +#define host_to_dsi(host) container_of(host, struct imx_mipi_dsi, dsi_host)
> +#define con_to_dsi(x) container_of(x, struct imx_mipi_dsi, connector)
> +#define enc_to_dsi(x) container_of(x, struct imx_mipi_dsi, encoder)

These should really be static inline functions for proper type safety.

> +struct imx_mipi_dsi {
> +	struct mipi_dsi_host dsi_host;
> +	struct drm_connector connector;
> +	struct drm_encoder encoder;
> +	struct device_node *panel_node;
> +	struct drm_panel *panel;
> +	struct device *dev;
> +
> +	struct regmap *regmap;
> +	void __iomem *base;
> +
> +	struct clk *pllref_clk;
> +	struct clk *pllref_gate_clk;
> +	struct clk *cfg_clk;
> +
> +	unsigned int lane_mbps; /* per lane */
> +	u32 channel;
> +	u32 lanes;
> +	u32 format;
> +	struct videomode vm;
> +
> +	bool enabled;

Do you really need this flag?

> +};
> +
> +enum {
> +	STATUS_TO_CLEAR,
> +	STATUS_TO_SET,
> +};
> +
> +struct dphy_pll_testdin_map {
> +	int max_mbps;

unsigned?

> +	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 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 int format_to_bpp(struct imx_mipi_dsi *dsi)
> +{
> +	switch (dsi->format) {
> +	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;
> +	default:
> +		dev_err(dsi->dev, "unsupported pixel format\n");
> +		return -EINVAL;
> +	}
> +}

Is there a reason why this can't be a standard MIPI DSI function?

> +
> +static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
> +			int timeout, bool to_set)
> +{
> +	u32 val;
> +	bool out = false;
> +
> +	val = dsi_read(dsi, reg);
> +	for (;;) {
> +		out = to_set ? (val & status) : !(val & status);
> +		if (out)
> +			break;
> +
> +		if (!timeout--)
> +			return -EFAULT;
> +
> +		msleep(1);
> +		val = dsi_read(dsi, reg);
> +	}
> +	return 0;
> +}

You should probably use a properly timed loop here. msleep() isn't
guaranteed to return after exactly one millisecond, so your timeout is
never going to be accurate. Something like the following would be better
in my opinion:

	timeout = jiffies + msecs_to_jiffies(timeout);

	while (time_before(jiffies, timeout)) {
		...
	}

Also timeout should be unsigned long in that case.

> +static int imx_mipi_dsi_host_attach(struct mipi_dsi_host *host,
> +				    struct mipi_dsi_device *device)
> +{
> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
> +	int ret;
> +
> +	if (device->lanes > IMX_MIPI_DSI_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;
> +	}
> +
> +	dsi->lanes = device->lanes;
> +	dsi->channel = device->channel;
> +	dsi->format = device->format;
> +	dsi->panel_node = device->dev.of_node;
> +	of_get_videomode(dsi->panel_node, &dsi->vm, 0);

No, you shouldn't use this. Query the mode from the panel instead. Or
rather, encode this requirement in ->mode_valid() since that'll give you
direct access to the mode.

That way, ->host_attach() becomes a filter for compatible devices, yet
devices may support multiple modes, therefore ->mode_valid() can be used
to filter out those that don't work with your DSI host controller. There
is a subtle difference here between devices that are compatible with the
controller and modes that the controller doesn't support.

> +
> +	ret = imx_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int imx_mipi_dsi_host_detach(struct mipi_dsi_host *host,
> +				    struct mipi_dsi_device *device)
> +{
> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
> +
> +	dsi->panel_node = NULL;
> +	dsi->panel = NULL;
> +
> +	return 0;
> +}

You'll want to detach from the panel here as well.

> +
> +static int imx_mipi_dsi_gen_pkt_hdr_write(struct imx_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 avaliable 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 imx_mipi_dsi_dcs_short_write(struct imx_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 imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
> +}

It seems like you would profit from converting to the newly introduced
mipi_dsi_create_packet() helper which does most of the packing along
with some sanity checking for you.

> +
> +static int imx_mipi_dsi_dcs_long_write(struct imx_mipi_dsi *dsi,
> +				       const struct mipi_dsi_msg *msg)
> +{
> +	const u32 *tx_buf = msg->tx_buf;
> +	int len = msg->tx_len, ret;
> +	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
> +
> +	if (msg->tx_len < 3) {
> +		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
> +			msg->tx_len);
> +		return -EINVAL;
> +	}
> +
> +	/* write the bulks */
> +	while (len / IMX_MIPI_DSI_PLD_DATA_BUF_SIZE) {
> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
> +		tx_buf++;
> +		len -= IMX_MIPI_DSI_PLD_DATA_BUF_SIZE;

This is confusing. Maybe a better option would be to use sizeof(*tx_buf)
instead of this macro. You're effectively consuming one u32 with each
write, irrespective of what the value of the macro is.

> +		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 avaliable "
> +					"write payload FIFO\n");
> +			return ret;
> +		}
> +	}
> +
> +	/* write the remainder */
> +	if (len > 0) {
> +		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 avaliable "
> +					"write payload FIFO\n");
> +			return ret;
> +		}
> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
> +	}

This is really duplicating what the above loop does. Why can't you do it
in a single loop? Also the transmission buffer isn't guaranteed to be a
multiple of 4 bytes, so you could have the situation where len > 0 and
len < 4 and yet you'll be reading 4 bytes by doing *tx_buf and accessing
memory that you shouldn't.

> +	return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
> +}
> +
> +static ssize_t imx_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
> +					  const struct mipi_dsi_msg *msg)
> +{
> +	struct imx_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 = imx_mipi_dsi_dcs_short_write(dsi, msg);
> +		break;
> +	case MIPI_DSI_DCS_LONG_WRITE:
> +		ret = imx_mipi_dsi_dcs_long_write(dsi, msg);
> +		break;
> +	default:
> +		dev_err(dsi->dev, "unsupported message type\n");
> +		ret = -EFAULT;

EFAULT really isn't appropriate here.

> +	}
> +
> +	return ret;
> +}
> +
> +static const struct mipi_dsi_host_ops imx_mipi_dsi_host_ops = {
> +	.attach = imx_mipi_dsi_host_attach,
> +	.detach = imx_mipi_dsi_host_detach,
> +	.transfer = imx_mipi_dsi_host_transfer,
> +};
> +
> +static enum drm_connector_status
> +imx_mipi_dsi_detect(struct drm_connector *connector, bool force)
> +{
> +	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
> +
> +	if (!dsi->panel) {
> +		dsi->panel = of_drm_find_panel(dsi->panel_node);
> +		if (dsi->panel)
> +			drm_panel_attach(dsi->panel, &dsi->connector);
> +	}
> +
> +	if (dsi->panel)
> +		return connector_status_connected;
> +
> +	return connector_status_disconnected;
> +
> +}

You really shouldn't be doing that here. of_drm_find_panel() returning
NULL should be considered cause for deferring probe. I suspect that the
driver doesn't really handle that very well at all, though, since if it
did you would've run into the same issue that Benjamin ran into this
morning.

> +static void imx_mipi_dsi_encoder_prepare(struct drm_encoder *encoder)
> +{
> +	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
> +	u32 interface_pix_fmt;
> +
> +	switch (dsi->format) {
> +	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:
> +		dev_err(dsi->dev, "unsupported DSI pixel format\n");
> +		return;

Why even try doing this if you know upfront that it can't be done. You
know much earlier than this that you can't drive the pixel format, why
not abort then?

People are much more likely to notice that the panel isn't supported if
the DSI output doesn't even show up (or doesn't expose any modes) rather
than if they have to find this error message in dmesg.

> +static void imx_mipi_dsi_video_mode_config(struct imx_mipi_dsi *dsi)
> +{
> +	u32 val;
> +
> +	val = VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER;
> +
> +	dsi_write(dsi, DSI_VID_MODE_CFG, val);
> +}

You probably want to parameterize based on the DSI peripheral's flags. I
see that you do reject devices with any other modes, so this may not be
necessary now. Out of curiosity, the hardware supports other modes,
right?

[...]
> +MODULE_LICENSE("GPL v2");

Sigh... according to your header comment this needs to be "GPL".

Thierry

[-- Attachment #1.2: Type: application/pgp-signature, Size: 819 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
@ 2014-12-10 13:16     ` Thierry Reding
  0 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 13:16 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
> This patch adds i.MX 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>
> ---
>  .../devicetree/bindings/drm/imx/mipi_dsi.txt       |   81 ++
>  drivers/gpu/drm/imx/Kconfig                        |    6 +
>  drivers/gpu/drm/imx/Makefile                       |    1 +
>  drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
>  4 files changed, 1105 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>  create mode 100644 drivers/gpu/drm/imx/imx-mipi-dsi.c
> 
> 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..3d07fd7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
> @@ -0,0 +1,81 @@
> +Device-Tree bindings for MIPI DSI host controller
> +
> +MIPI DSI host controller
> +========================
> +
> +The MIPI DSI host controller is a Synopsys DesignWare IP.
> +It 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 : Should be "fsl,imx6q-mipi-dsi" for i.MX6q/sdl SoCs.
> + - 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.

Side-note: Shouldn't this really be a pinmux, then?

> + - clocks, clock-names : Phandles to the controller pllref, pllref_gate
> +           and core_cfg clocks, as described in [1] and [2].
> + - panel at 0 : A panel node which contains a display-timings child node as
> +             defined in [3].

There's no need for these to be named panel@*. They could be bridges for
example. And no, they shouldn't contain a display-timings child node
either. Panels should have a proper driver and the driver being device
specific it should have the timings embedded.

> + - port@[0-4] : Up to four port nodes with endpoint definitions as defined
> +   in [4], corresponding to the four inputs to the controller multiplexer.
> +   Note that each port node should contain the input-port property to
> +   distinguish it from the panel node, as described in [5].

[4] says that you can group all port nodes under a ports parent node. I
think this is really what you want to do here to make it clear that the
ports aren't part of the DSI host binding part of the device.

> diff --git a/drivers/gpu/drm/imx/imx-mipi-dsi.c b/drivers/gpu/drm/imx/imx-mipi-dsi.c
[...]
> +/*
> + * i.MX drm driver - MIPI DSI Host Controller
> + *
> + * Copyright (C) 2011-2014 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/component.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/regmap.h>
> +#include <linux/videodev2.h>
> +#include <asm/div64.h>

Don't you want the more generic linux/math64.h here?

> +#include <drm/drmP.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_helper.h>

I don't see any of the functions defined in that header used here.

> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +#include <video/mipi_display.h>
> +#include <video/of_videomode.h>
> +#include <video/videomode.h>
> +
> +#include "imx-drm.h"
> +
> +#define DRIVER_NAME 			"imx-mipi-dsi"
> +
> +#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)

Your indentation here is... weird.

> +#define IMX_MIPI_DSI_MAX_DATA_LANES	2
> +
> +#define PHY_STATUS_TIMEOUT		10
> +#define CMD_PKT_STATUS_TIMEOUT		20
> +
> +#define IMX_MIPI_DSI_PLD_DATA_BUF_SIZE	4
> +
> +#define MHZ				1000000

Why not reuse the existing USEC_PER_SEC?

> +#define host_to_dsi(host) container_of(host, struct imx_mipi_dsi, dsi_host)
> +#define con_to_dsi(x) container_of(x, struct imx_mipi_dsi, connector)
> +#define enc_to_dsi(x) container_of(x, struct imx_mipi_dsi, encoder)

These should really be static inline functions for proper type safety.

> +struct imx_mipi_dsi {
> +	struct mipi_dsi_host dsi_host;
> +	struct drm_connector connector;
> +	struct drm_encoder encoder;
> +	struct device_node *panel_node;
> +	struct drm_panel *panel;
> +	struct device *dev;
> +
> +	struct regmap *regmap;
> +	void __iomem *base;
> +
> +	struct clk *pllref_clk;
> +	struct clk *pllref_gate_clk;
> +	struct clk *cfg_clk;
> +
> +	unsigned int lane_mbps; /* per lane */
> +	u32 channel;
> +	u32 lanes;
> +	u32 format;
> +	struct videomode vm;
> +
> +	bool enabled;

Do you really need this flag?

> +};
> +
> +enum {
> +	STATUS_TO_CLEAR,
> +	STATUS_TO_SET,
> +};
> +
> +struct dphy_pll_testdin_map {
> +	int max_mbps;

unsigned?

> +	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 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 int format_to_bpp(struct imx_mipi_dsi *dsi)
> +{
> +	switch (dsi->format) {
> +	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;
> +	default:
> +		dev_err(dsi->dev, "unsupported pixel format\n");
> +		return -EINVAL;
> +	}
> +}

Is there a reason why this can't be a standard MIPI DSI function?

> +
> +static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
> +			int timeout, bool to_set)
> +{
> +	u32 val;
> +	bool out = false;
> +
> +	val = dsi_read(dsi, reg);
> +	for (;;) {
> +		out = to_set ? (val & status) : !(val & status);
> +		if (out)
> +			break;
> +
> +		if (!timeout--)
> +			return -EFAULT;
> +
> +		msleep(1);
> +		val = dsi_read(dsi, reg);
> +	}
> +	return 0;
> +}

You should probably use a properly timed loop here. msleep() isn't
guaranteed to return after exactly one millisecond, so your timeout is
never going to be accurate. Something like the following would be better
in my opinion:

	timeout = jiffies + msecs_to_jiffies(timeout);

	while (time_before(jiffies, timeout)) {
		...
	}

Also timeout should be unsigned long in that case.

> +static int imx_mipi_dsi_host_attach(struct mipi_dsi_host *host,
> +				    struct mipi_dsi_device *device)
> +{
> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
> +	int ret;
> +
> +	if (device->lanes > IMX_MIPI_DSI_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;
> +	}
> +
> +	dsi->lanes = device->lanes;
> +	dsi->channel = device->channel;
> +	dsi->format = device->format;
> +	dsi->panel_node = device->dev.of_node;
> +	of_get_videomode(dsi->panel_node, &dsi->vm, 0);

No, you shouldn't use this. Query the mode from the panel instead. Or
rather, encode this requirement in ->mode_valid() since that'll give you
direct access to the mode.

That way, ->host_attach() becomes a filter for compatible devices, yet
devices may support multiple modes, therefore ->mode_valid() can be used
to filter out those that don't work with your DSI host controller. There
is a subtle difference here between devices that are compatible with the
controller and modes that the controller doesn't support.

> +
> +	ret = imx_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int imx_mipi_dsi_host_detach(struct mipi_dsi_host *host,
> +				    struct mipi_dsi_device *device)
> +{
> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
> +
> +	dsi->panel_node = NULL;
> +	dsi->panel = NULL;
> +
> +	return 0;
> +}

You'll want to detach from the panel here as well.

> +
> +static int imx_mipi_dsi_gen_pkt_hdr_write(struct imx_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 avaliable 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 imx_mipi_dsi_dcs_short_write(struct imx_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 imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
> +}

It seems like you would profit from converting to the newly introduced
mipi_dsi_create_packet() helper which does most of the packing along
with some sanity checking for you.

> +
> +static int imx_mipi_dsi_dcs_long_write(struct imx_mipi_dsi *dsi,
> +				       const struct mipi_dsi_msg *msg)
> +{
> +	const u32 *tx_buf = msg->tx_buf;
> +	int len = msg->tx_len, ret;
> +	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
> +
> +	if (msg->tx_len < 3) {
> +		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
> +			msg->tx_len);
> +		return -EINVAL;
> +	}
> +
> +	/* write the bulks */
> +	while (len / IMX_MIPI_DSI_PLD_DATA_BUF_SIZE) {
> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
> +		tx_buf++;
> +		len -= IMX_MIPI_DSI_PLD_DATA_BUF_SIZE;

This is confusing. Maybe a better option would be to use sizeof(*tx_buf)
instead of this macro. You're effectively consuming one u32 with each
write, irrespective of what the value of the macro is.

> +		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 avaliable "
> +					"write payload FIFO\n");
> +			return ret;
> +		}
> +	}
> +
> +	/* write the remainder */
> +	if (len > 0) {
> +		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 avaliable "
> +					"write payload FIFO\n");
> +			return ret;
> +		}
> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
> +	}

This is really duplicating what the above loop does. Why can't you do it
in a single loop? Also the transmission buffer isn't guaranteed to be a
multiple of 4 bytes, so you could have the situation where len > 0 and
len < 4 and yet you'll be reading 4 bytes by doing *tx_buf and accessing
memory that you shouldn't.

> +	return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
> +}
> +
> +static ssize_t imx_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
> +					  const struct mipi_dsi_msg *msg)
> +{
> +	struct imx_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 = imx_mipi_dsi_dcs_short_write(dsi, msg);
> +		break;
> +	case MIPI_DSI_DCS_LONG_WRITE:
> +		ret = imx_mipi_dsi_dcs_long_write(dsi, msg);
> +		break;
> +	default:
> +		dev_err(dsi->dev, "unsupported message type\n");
> +		ret = -EFAULT;

EFAULT really isn't appropriate here.

> +	}
> +
> +	return ret;
> +}
> +
> +static const struct mipi_dsi_host_ops imx_mipi_dsi_host_ops = {
> +	.attach = imx_mipi_dsi_host_attach,
> +	.detach = imx_mipi_dsi_host_detach,
> +	.transfer = imx_mipi_dsi_host_transfer,
> +};
> +
> +static enum drm_connector_status
> +imx_mipi_dsi_detect(struct drm_connector *connector, bool force)
> +{
> +	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
> +
> +	if (!dsi->panel) {
> +		dsi->panel = of_drm_find_panel(dsi->panel_node);
> +		if (dsi->panel)
> +			drm_panel_attach(dsi->panel, &dsi->connector);
> +	}
> +
> +	if (dsi->panel)
> +		return connector_status_connected;
> +
> +	return connector_status_disconnected;
> +
> +}

You really shouldn't be doing that here. of_drm_find_panel() returning
NULL should be considered cause for deferring probe. I suspect that the
driver doesn't really handle that very well at all, though, since if it
did you would've run into the same issue that Benjamin ran into this
morning.

> +static void imx_mipi_dsi_encoder_prepare(struct drm_encoder *encoder)
> +{
> +	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
> +	u32 interface_pix_fmt;
> +
> +	switch (dsi->format) {
> +	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:
> +		dev_err(dsi->dev, "unsupported DSI pixel format\n");
> +		return;

Why even try doing this if you know upfront that it can't be done. You
know much earlier than this that you can't drive the pixel format, why
not abort then?

People are much more likely to notice that the panel isn't supported if
the DSI output doesn't even show up (or doesn't expose any modes) rather
than if they have to find this error message in dmesg.

> +static void imx_mipi_dsi_video_mode_config(struct imx_mipi_dsi *dsi)
> +{
> +	u32 val;
> +
> +	val = VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER;
> +
> +	dsi_write(dsi, DSI_VID_MODE_CFG, val);
> +}

You probably want to parameterize based on the DSI peripheral's flags. I
see that you do reject devices with any other modes, so this may not be
necessary now. Out of curiosity, the hardware supports other modes,
right?

[...]
> +MODULE_LICENSE("GPL v2");

Sigh... according to your header comment this needs to be "GPL".

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20141210/10d152b5/attachment-0001.sig>

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 10/15] drm: panel: Add support for Himax HX8369A MIPI DSI panel
  2014-12-10  8:37   ` Liu Ying
  (?)
@ 2014-12-10 14:03     ` Thierry Reding
  -1 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 14:03 UTC (permalink / raw)
  To: Liu Ying
  Cc: dri-devel, devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	shawn.guo, kernel, linux, mturquette, airlied

[-- Attachment #1: Type: text/plain, Size: 12760 bytes --]

On Wed, Dec 10, 2014 at 04:37:23PM +0800, Liu Ying wrote:
> This patch adds support for Himax HX8369A MIPI DSI panel.
> 
> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
> ---
>  .../devicetree/bindings/panel/himax,hx8369a.txt    |  86 +++
>  drivers/gpu/drm/panel/Kconfig                      |   6 +
>  drivers/gpu/drm/panel/Makefile                     |   1 +
>  drivers/gpu/drm/panel/panel-hx8369a.c              | 627 +++++++++++++++++++++
>  4 files changed, 720 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>  create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c
> 
> diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
> new file mode 100644
> index 0000000..6fe251e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
> @@ -0,0 +1,86 @@
> +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: "himax,hx8369a-dsi"
> +  - reg: the virtual channel number of a DSI peripheral
> +  - reset-gpios: a GPIO spec for the reset pin
> +  - data-lanes: the data lane number of a DSI peripheral

This is implied by the compatible already.

> +  - display-timings: timings for the connected panel as described by [1]

Also implied by the compatible value.

> +  - bs: the interface mode number described by the following table
> +        --------------------------
> +       | DBI_TYPE_A_8BIT     |  0 |
> +       | DBI_TYPE_A_9BIT     |  1 |
> +       | DBI_TYPE_A_16BIT    |  2 |
> +       | DBI_TYPE_A_18BIT    |  3 |
> +       | DBI_TYPE_B_8BIT     |  4 |
> +       | DBI_TYPE_B_9BIT     |  5 |
> +       | DBI_TYPE_B_16BIT    |  6 |
> +       | DBI_TYPE_B_18BIT    |  7 |
> +       | DSI_CMD_MODE        |  8 |
> +       | DBI_TYPE_B_24BIT    |  9 |
> +       | DSI_VIDEO_MODE      | 10 |
> +       | MDDI                | 11 |
> +       | DPI_DBI_TYPE_C_OPT1 | 12 |
> +       | DPI_DBI_TYPE_C_OPT2 | 13 |
> +       | DPI_DBI_TYPE_C_OPT3 | 14 |
> +        --------------------------

Can this not be inferred by the driver? If it's a DSI driver can't it
select between DSI_VIDEO_MODE or DSI_CMD_MODE based on its capabilities?
That is, if the panel driver can setup command mode, shouldn't it be
using command mode in that case? And use DSI_VIDEO_MODE otherwise?

> +Optional properties:
> +  - power-on-delay: delay after turning regulators on [ms]
> +  - reset-delay: delay after reset sequence [ms]

Surely these are constant for this panel?

> +  - panel-width-mm: physical panel width [mm]
> +  - panel-height-mm: physical panel height [mm]

These are also implied by compatible.

> +Example:
> +	panel@0 {
> +		compatible = "himax,hx8369a-dsi";
> +		reg = <0>;
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&pinctrl_mipi_panel>;
> +		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
> +		reset-delay = <120>;
> +		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
> +		data-lanes = <2>;
> +		panel-width-mm = <45>;
> +		panel-height-mm = <76>;
> +		bs = <10>;
> +		status = "okay";

status = "okay" is the default, so it probably shouldn't be in this
example.

> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> index 024e98e..f1a5b58 100644
> --- a/drivers/gpu/drm/panel/Kconfig
> +++ b/drivers/gpu/drm/panel/Kconfig
> @@ -40,4 +40,10 @@ config DRM_PANEL_SHARP_LQ101R1SX01
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called panel-sharp-lq101r1sx01.
>  
> +config DRM_PANEL_HX8369A
> +	tristate "HX8369A panel"
> +	depends on OF
> +	select DRM_MIPI_DSI
> +	select VIDEOMODE_HELPERS
> +

This should be sorted alphabetically. I think it would also be a good
idea to use a HIMAX prefix here, just to reduce the potential for name
clashes.

> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> index 4b2a043..d6768ca 100644
> --- a/drivers/gpu/drm/panel/Makefile
> +++ b/drivers/gpu/drm/panel/Makefile
> @@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.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
> +obj-$(CONFIG_DRM_PANEL_HX8369A) += panel-hx8369a.o

Needs to be sorted alphabetically, too.

> diff --git a/drivers/gpu/drm/panel/panel-hx8369a.c b/drivers/gpu/drm/panel/panel-hx8369a.c
[...]
> +#include <drm/drmP.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +
> +#include <linux/gpio/consumer.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include <video/mipi_display.h>
> +#include <video/of_videomode.h>
> +#include <video/videomode.h>
> +
> +#define SETCLUMN_ADDR	0x2a
> +#define SETPAGE_ADDR 	0x2b
> +#define SETPIXEL_FMT 	0x3a

There are standard DCS functions for these now.

> +#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

Watch your indentation here and above. Use tabs and spaces more
consistently.

> +static bool hx8369a_is_dsi_interface(struct hx8369a *ctx) {

Coding style: opening { goes on a line by itself.

> +	if (ctx->mpu_interface == HX8369A_DSI_VIDEO_MODE ||
> +	    ctx->mpu_interface == HX8369A_DSI_CMD_MODE)
> +		return true;
> +
> +	return false;
> +}
> +
> +static void hx8369a_dcs_write(struct hx8369a *ctx, 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, "error %zd writing dcs seq: %*ph\n", ret, len,
> +			data);
> +}

You really shouldn't have based this on the Samsung drivers. You really
should do proper error handling here. These error messages are going to
be completely cryptic and very hard for people to look up in the driver
as opposed to a simple specific error message like:

	dev_err(ctx->dev, "failed to do XYZ: %d\n", err);

which will immediately let you look up the right location without a need
to decode the hexdump and look for the correct array.

> +#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, d, ARRAY_SIZE(d));\
> +})
> +
> +#define hx8369a_dcs_write_seq_static(ctx, seq...) \
> +({\
> +	static const u8 d[] = { seq };\
> +	hx8369a_dcs_write(ctx, d, ARRAY_SIZE(d));\
> +})
> +
> +static void hx8369a_dsi_set_display_related_register(struct hx8369a *ctx)
> +{
> +	u8 sec_p = (ctx->res_sel << 4) | 0x03;
> +
> +	hx8369a_dcs_write_seq(ctx, SETDISP, 0x00, sec_p, 0x03,
> +		0x03, 0x70, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
> +		0x03, 0x03, 0x00, 0x01);
> +}

This and all of the other initialization functions below completely
ignore any errors. Other than spamming the kernel log the user won't get
any indication of anything going wrong.

> +static void hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx)
> +{
[...]
> +}
> +
> +static void hx8369a_dsi_set_column_address(struct hx8369a *ctx)
> +{
[...]
> +}
> +
> +static void hx8369a_dsi_set_page_address(struct hx8369a *ctx)
> +{
[...]
> +}

We have standard functions for these, please use them.

> +static void hx8369a_dsi_set_extension_command(struct hx8369a *ctx)
> +{
> +	hx8369a_dcs_write_seq_static(ctx, SETEXTC, 0xff, 0x83, 0x69);
> +}

What does this do exactly? It seems to be more than setting an extension
command.

> +static void 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);
> +}
> +
> +static int hx8369a_dsi_set_sequence(struct hx8369a *ctx)
> +{
> +	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
> +	int ret;
> +
> +	hx8369a_dsi_set_extension_command(ctx);
> +	hx8369a_dsi_set_maximum_return_packet_size(ctx, 4);
> +	hx8369a_dsi_panel_init(ctx);
> +
> +	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
> +	if (ret < 0) {
> +		dev_err(ctx->dev, "failed to exit sleep mode: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = mipi_dsi_dcs_set_display_on(dsi);
> +	if (ret < 0) {
> +		dev_err(ctx->dev, "failed to set display on: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +

> +static int hx8369a_dsi_disable(struct drm_panel *panel)
> +{
> +	return 0;
> +}
[...]
> +static int hx8369a_dsi_enable(struct drm_panel *panel)
> +{
> +	return 0;
> +}

Doesn't this usually come with a backlight attached that you want to
control here?

> +static int hx8369a_get_modes(struct drm_panel *panel)
> +{
> +	struct drm_connector *connector = panel->connector;
> +	struct hx8369a *ctx = panel_to_hx8369a(panel);
> +	struct drm_display_mode *mode;
> +
> +	mode = drm_mode_create(connector->dev);
> +	if (!mode) {
> +		DRM_ERROR("failed to create a new display mode\n");
> +		return 0;
> +	}
> +
> +	drm_display_mode_from_videomode(&ctx->vm, mode);

Like I've said before, the driver should hardcode the mode because it is
implied by the compatible value.

> +	mode->width_mm = ctx->width_mm;
> +	mode->height_mm = ctx->height_mm;
> +	connector->display_info.width_mm = mode->width_mm;
> +	connector->display_info.height_mm = mode->height_mm;
> +
> +	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
> +	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_dsi_probe(struct mipi_dsi_device *dsi)
> +{
> +	struct device *dev = &dsi->dev;
> +	struct hx8369a *ctx;
> +	int ret, i;
> +	char bs[4];
> +
> +	ctx = devm_kzalloc(dev, sizeof(struct hx8369a), GFP_KERNEL);

I'd prefer sizeof(*ctx).

> +	ctx->reset_gpio = devm_gpiod_get(dev, "reset");
> +	if (IS_ERR(ctx->reset_gpio)) {
> +		dev_err(dev, "cannot get reset-gpios %ld\n",
> +			PTR_ERR(ctx->reset_gpio));
> +		return PTR_ERR(ctx->reset_gpio);
> +	}
> +	ret = gpiod_direction_output(ctx->reset_gpio, 0);
> +	if (ret < 0) {
> +		dev_err(dev, "cannot configure reset-gpios %d\n", ret);
> +		return ret;
> +	}

I think you're supposed to combine this into something like:

	ret = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);

nowadays.

> +
> +	for (i = 0; i < 4; i++) {
> +		snprintf(bs, sizeof(bs), "bs%d", i);
> +		ctx->bs_gpio[i] = devm_gpiod_get(dev, bs);
> +		if (IS_ERR(ctx->bs_gpio[i]))
> +			continue;
> +
> +		ret = gpiod_direction_output(ctx->bs_gpio[i], 1);
> +		if (ret < 0) {
> +			dev_err(dev, "cannot configure bs%d-gpio %d\n", i, ret);
> +			return ret;
> +		}

Similarly to the above:

	ret = devm_gpiod_get(dev, bs, GPIOD_OUT_HIGH);

> +static int __init hx8369a_init(void)
> +{
> +	int err;
> +
> +	err = mipi_dsi_driver_register(&hx8369a_dsi_driver);
> +	if (err < 0)
> +		return err;
> +
> +	return 0;
> +}
> +module_init(hx8369a_init);
> +
> +static void __exit hx8369a_exit(void)
> +{
> +	mipi_dsi_driver_unregister(&hx8369a_dsi_driver);
> +}
> +module_exit(hx8369a_exit);

Why can't this be module_mipi_dsi_driver(&hx8369a_dsi_driver)?

> +
> +MODULE_DESCRIPTION("Himax HX8369A panel driver");
> +MODULE_LICENSE("GPL v2");

Missing MODULE_AUTHOR()?

Thierry

[-- Attachment #2: Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 10/15] drm: panel: Add support for Himax HX8369A MIPI DSI panel
@ 2014-12-10 14:03     ` Thierry Reding
  0 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 14:03 UTC (permalink / raw)
  To: Liu Ying
  Cc: devicetree, linux, kernel, linux-kernel, dri-devel, mturquette,
	linux-arm-kernel


[-- Attachment #1.1: Type: text/plain, Size: 12760 bytes --]

On Wed, Dec 10, 2014 at 04:37:23PM +0800, Liu Ying wrote:
> This patch adds support for Himax HX8369A MIPI DSI panel.
> 
> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
> ---
>  .../devicetree/bindings/panel/himax,hx8369a.txt    |  86 +++
>  drivers/gpu/drm/panel/Kconfig                      |   6 +
>  drivers/gpu/drm/panel/Makefile                     |   1 +
>  drivers/gpu/drm/panel/panel-hx8369a.c              | 627 +++++++++++++++++++++
>  4 files changed, 720 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>  create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c
> 
> diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
> new file mode 100644
> index 0000000..6fe251e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
> @@ -0,0 +1,86 @@
> +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: "himax,hx8369a-dsi"
> +  - reg: the virtual channel number of a DSI peripheral
> +  - reset-gpios: a GPIO spec for the reset pin
> +  - data-lanes: the data lane number of a DSI peripheral

This is implied by the compatible already.

> +  - display-timings: timings for the connected panel as described by [1]

Also implied by the compatible value.

> +  - bs: the interface mode number described by the following table
> +        --------------------------
> +       | DBI_TYPE_A_8BIT     |  0 |
> +       | DBI_TYPE_A_9BIT     |  1 |
> +       | DBI_TYPE_A_16BIT    |  2 |
> +       | DBI_TYPE_A_18BIT    |  3 |
> +       | DBI_TYPE_B_8BIT     |  4 |
> +       | DBI_TYPE_B_9BIT     |  5 |
> +       | DBI_TYPE_B_16BIT    |  6 |
> +       | DBI_TYPE_B_18BIT    |  7 |
> +       | DSI_CMD_MODE        |  8 |
> +       | DBI_TYPE_B_24BIT    |  9 |
> +       | DSI_VIDEO_MODE      | 10 |
> +       | MDDI                | 11 |
> +       | DPI_DBI_TYPE_C_OPT1 | 12 |
> +       | DPI_DBI_TYPE_C_OPT2 | 13 |
> +       | DPI_DBI_TYPE_C_OPT3 | 14 |
> +        --------------------------

Can this not be inferred by the driver? If it's a DSI driver can't it
select between DSI_VIDEO_MODE or DSI_CMD_MODE based on its capabilities?
That is, if the panel driver can setup command mode, shouldn't it be
using command mode in that case? And use DSI_VIDEO_MODE otherwise?

> +Optional properties:
> +  - power-on-delay: delay after turning regulators on [ms]
> +  - reset-delay: delay after reset sequence [ms]

Surely these are constant for this panel?

> +  - panel-width-mm: physical panel width [mm]
> +  - panel-height-mm: physical panel height [mm]

These are also implied by compatible.

> +Example:
> +	panel@0 {
> +		compatible = "himax,hx8369a-dsi";
> +		reg = <0>;
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&pinctrl_mipi_panel>;
> +		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
> +		reset-delay = <120>;
> +		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
> +		data-lanes = <2>;
> +		panel-width-mm = <45>;
> +		panel-height-mm = <76>;
> +		bs = <10>;
> +		status = "okay";

status = "okay" is the default, so it probably shouldn't be in this
example.

> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> index 024e98e..f1a5b58 100644
> --- a/drivers/gpu/drm/panel/Kconfig
> +++ b/drivers/gpu/drm/panel/Kconfig
> @@ -40,4 +40,10 @@ config DRM_PANEL_SHARP_LQ101R1SX01
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called panel-sharp-lq101r1sx01.
>  
> +config DRM_PANEL_HX8369A
> +	tristate "HX8369A panel"
> +	depends on OF
> +	select DRM_MIPI_DSI
> +	select VIDEOMODE_HELPERS
> +

This should be sorted alphabetically. I think it would also be a good
idea to use a HIMAX prefix here, just to reduce the potential for name
clashes.

> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> index 4b2a043..d6768ca 100644
> --- a/drivers/gpu/drm/panel/Makefile
> +++ b/drivers/gpu/drm/panel/Makefile
> @@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.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
> +obj-$(CONFIG_DRM_PANEL_HX8369A) += panel-hx8369a.o

Needs to be sorted alphabetically, too.

> diff --git a/drivers/gpu/drm/panel/panel-hx8369a.c b/drivers/gpu/drm/panel/panel-hx8369a.c
[...]
> +#include <drm/drmP.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +
> +#include <linux/gpio/consumer.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include <video/mipi_display.h>
> +#include <video/of_videomode.h>
> +#include <video/videomode.h>
> +
> +#define SETCLUMN_ADDR	0x2a
> +#define SETPAGE_ADDR 	0x2b
> +#define SETPIXEL_FMT 	0x3a

There are standard DCS functions for these now.

> +#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

Watch your indentation here and above. Use tabs and spaces more
consistently.

> +static bool hx8369a_is_dsi_interface(struct hx8369a *ctx) {

Coding style: opening { goes on a line by itself.

> +	if (ctx->mpu_interface == HX8369A_DSI_VIDEO_MODE ||
> +	    ctx->mpu_interface == HX8369A_DSI_CMD_MODE)
> +		return true;
> +
> +	return false;
> +}
> +
> +static void hx8369a_dcs_write(struct hx8369a *ctx, 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, "error %zd writing dcs seq: %*ph\n", ret, len,
> +			data);
> +}

You really shouldn't have based this on the Samsung drivers. You really
should do proper error handling here. These error messages are going to
be completely cryptic and very hard for people to look up in the driver
as opposed to a simple specific error message like:

	dev_err(ctx->dev, "failed to do XYZ: %d\n", err);

which will immediately let you look up the right location without a need
to decode the hexdump and look for the correct array.

> +#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, d, ARRAY_SIZE(d));\
> +})
> +
> +#define hx8369a_dcs_write_seq_static(ctx, seq...) \
> +({\
> +	static const u8 d[] = { seq };\
> +	hx8369a_dcs_write(ctx, d, ARRAY_SIZE(d));\
> +})
> +
> +static void hx8369a_dsi_set_display_related_register(struct hx8369a *ctx)
> +{
> +	u8 sec_p = (ctx->res_sel << 4) | 0x03;
> +
> +	hx8369a_dcs_write_seq(ctx, SETDISP, 0x00, sec_p, 0x03,
> +		0x03, 0x70, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
> +		0x03, 0x03, 0x00, 0x01);
> +}

This and all of the other initialization functions below completely
ignore any errors. Other than spamming the kernel log the user won't get
any indication of anything going wrong.

> +static void hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx)
> +{
[...]
> +}
> +
> +static void hx8369a_dsi_set_column_address(struct hx8369a *ctx)
> +{
[...]
> +}
> +
> +static void hx8369a_dsi_set_page_address(struct hx8369a *ctx)
> +{
[...]
> +}

We have standard functions for these, please use them.

> +static void hx8369a_dsi_set_extension_command(struct hx8369a *ctx)
> +{
> +	hx8369a_dcs_write_seq_static(ctx, SETEXTC, 0xff, 0x83, 0x69);
> +}

What does this do exactly? It seems to be more than setting an extension
command.

> +static void 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);
> +}
> +
> +static int hx8369a_dsi_set_sequence(struct hx8369a *ctx)
> +{
> +	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
> +	int ret;
> +
> +	hx8369a_dsi_set_extension_command(ctx);
> +	hx8369a_dsi_set_maximum_return_packet_size(ctx, 4);
> +	hx8369a_dsi_panel_init(ctx);
> +
> +	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
> +	if (ret < 0) {
> +		dev_err(ctx->dev, "failed to exit sleep mode: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = mipi_dsi_dcs_set_display_on(dsi);
> +	if (ret < 0) {
> +		dev_err(ctx->dev, "failed to set display on: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +

> +static int hx8369a_dsi_disable(struct drm_panel *panel)
> +{
> +	return 0;
> +}
[...]
> +static int hx8369a_dsi_enable(struct drm_panel *panel)
> +{
> +	return 0;
> +}

Doesn't this usually come with a backlight attached that you want to
control here?

> +static int hx8369a_get_modes(struct drm_panel *panel)
> +{
> +	struct drm_connector *connector = panel->connector;
> +	struct hx8369a *ctx = panel_to_hx8369a(panel);
> +	struct drm_display_mode *mode;
> +
> +	mode = drm_mode_create(connector->dev);
> +	if (!mode) {
> +		DRM_ERROR("failed to create a new display mode\n");
> +		return 0;
> +	}
> +
> +	drm_display_mode_from_videomode(&ctx->vm, mode);

Like I've said before, the driver should hardcode the mode because it is
implied by the compatible value.

> +	mode->width_mm = ctx->width_mm;
> +	mode->height_mm = ctx->height_mm;
> +	connector->display_info.width_mm = mode->width_mm;
> +	connector->display_info.height_mm = mode->height_mm;
> +
> +	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
> +	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_dsi_probe(struct mipi_dsi_device *dsi)
> +{
> +	struct device *dev = &dsi->dev;
> +	struct hx8369a *ctx;
> +	int ret, i;
> +	char bs[4];
> +
> +	ctx = devm_kzalloc(dev, sizeof(struct hx8369a), GFP_KERNEL);

I'd prefer sizeof(*ctx).

> +	ctx->reset_gpio = devm_gpiod_get(dev, "reset");
> +	if (IS_ERR(ctx->reset_gpio)) {
> +		dev_err(dev, "cannot get reset-gpios %ld\n",
> +			PTR_ERR(ctx->reset_gpio));
> +		return PTR_ERR(ctx->reset_gpio);
> +	}
> +	ret = gpiod_direction_output(ctx->reset_gpio, 0);
> +	if (ret < 0) {
> +		dev_err(dev, "cannot configure reset-gpios %d\n", ret);
> +		return ret;
> +	}

I think you're supposed to combine this into something like:

	ret = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);

nowadays.

> +
> +	for (i = 0; i < 4; i++) {
> +		snprintf(bs, sizeof(bs), "bs%d", i);
> +		ctx->bs_gpio[i] = devm_gpiod_get(dev, bs);
> +		if (IS_ERR(ctx->bs_gpio[i]))
> +			continue;
> +
> +		ret = gpiod_direction_output(ctx->bs_gpio[i], 1);
> +		if (ret < 0) {
> +			dev_err(dev, "cannot configure bs%d-gpio %d\n", i, ret);
> +			return ret;
> +		}

Similarly to the above:

	ret = devm_gpiod_get(dev, bs, GPIOD_OUT_HIGH);

> +static int __init hx8369a_init(void)
> +{
> +	int err;
> +
> +	err = mipi_dsi_driver_register(&hx8369a_dsi_driver);
> +	if (err < 0)
> +		return err;
> +
> +	return 0;
> +}
> +module_init(hx8369a_init);
> +
> +static void __exit hx8369a_exit(void)
> +{
> +	mipi_dsi_driver_unregister(&hx8369a_dsi_driver);
> +}
> +module_exit(hx8369a_exit);

Why can't this be module_mipi_dsi_driver(&hx8369a_dsi_driver)?

> +
> +MODULE_DESCRIPTION("Himax HX8369A panel driver");
> +MODULE_LICENSE("GPL v2");

Missing MODULE_AUTHOR()?

Thierry

[-- Attachment #1.2: Type: application/pgp-signature, Size: 819 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 10/15] drm: panel: Add support for Himax HX8369A MIPI DSI panel
@ 2014-12-10 14:03     ` Thierry Reding
  0 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 14:03 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Dec 10, 2014 at 04:37:23PM +0800, Liu Ying wrote:
> This patch adds support for Himax HX8369A MIPI DSI panel.
> 
> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
> ---
>  .../devicetree/bindings/panel/himax,hx8369a.txt    |  86 +++
>  drivers/gpu/drm/panel/Kconfig                      |   6 +
>  drivers/gpu/drm/panel/Makefile                     |   1 +
>  drivers/gpu/drm/panel/panel-hx8369a.c              | 627 +++++++++++++++++++++
>  4 files changed, 720 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>  create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c
> 
> diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
> new file mode 100644
> index 0000000..6fe251e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
> @@ -0,0 +1,86 @@
> +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: "himax,hx8369a-dsi"
> +  - reg: the virtual channel number of a DSI peripheral
> +  - reset-gpios: a GPIO spec for the reset pin
> +  - data-lanes: the data lane number of a DSI peripheral

This is implied by the compatible already.

> +  - display-timings: timings for the connected panel as described by [1]

Also implied by the compatible value.

> +  - bs: the interface mode number described by the following table
> +        --------------------------
> +       | DBI_TYPE_A_8BIT     |  0 |
> +       | DBI_TYPE_A_9BIT     |  1 |
> +       | DBI_TYPE_A_16BIT    |  2 |
> +       | DBI_TYPE_A_18BIT    |  3 |
> +       | DBI_TYPE_B_8BIT     |  4 |
> +       | DBI_TYPE_B_9BIT     |  5 |
> +       | DBI_TYPE_B_16BIT    |  6 |
> +       | DBI_TYPE_B_18BIT    |  7 |
> +       | DSI_CMD_MODE        |  8 |
> +       | DBI_TYPE_B_24BIT    |  9 |
> +       | DSI_VIDEO_MODE      | 10 |
> +       | MDDI                | 11 |
> +       | DPI_DBI_TYPE_C_OPT1 | 12 |
> +       | DPI_DBI_TYPE_C_OPT2 | 13 |
> +       | DPI_DBI_TYPE_C_OPT3 | 14 |
> +        --------------------------

Can this not be inferred by the driver? If it's a DSI driver can't it
select between DSI_VIDEO_MODE or DSI_CMD_MODE based on its capabilities?
That is, if the panel driver can setup command mode, shouldn't it be
using command mode in that case? And use DSI_VIDEO_MODE otherwise?

> +Optional properties:
> +  - power-on-delay: delay after turning regulators on [ms]
> +  - reset-delay: delay after reset sequence [ms]

Surely these are constant for this panel?

> +  - panel-width-mm: physical panel width [mm]
> +  - panel-height-mm: physical panel height [mm]

These are also implied by compatible.

> +Example:
> +	panel at 0 {
> +		compatible = "himax,hx8369a-dsi";
> +		reg = <0>;
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&pinctrl_mipi_panel>;
> +		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
> +		reset-delay = <120>;
> +		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
> +		data-lanes = <2>;
> +		panel-width-mm = <45>;
> +		panel-height-mm = <76>;
> +		bs = <10>;
> +		status = "okay";

status = "okay" is the default, so it probably shouldn't be in this
example.

> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> index 024e98e..f1a5b58 100644
> --- a/drivers/gpu/drm/panel/Kconfig
> +++ b/drivers/gpu/drm/panel/Kconfig
> @@ -40,4 +40,10 @@ config DRM_PANEL_SHARP_LQ101R1SX01
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called panel-sharp-lq101r1sx01.
>  
> +config DRM_PANEL_HX8369A
> +	tristate "HX8369A panel"
> +	depends on OF
> +	select DRM_MIPI_DSI
> +	select VIDEOMODE_HELPERS
> +

This should be sorted alphabetically. I think it would also be a good
idea to use a HIMAX prefix here, just to reduce the potential for name
clashes.

> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> index 4b2a043..d6768ca 100644
> --- a/drivers/gpu/drm/panel/Makefile
> +++ b/drivers/gpu/drm/panel/Makefile
> @@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.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
> +obj-$(CONFIG_DRM_PANEL_HX8369A) += panel-hx8369a.o

Needs to be sorted alphabetically, too.

> diff --git a/drivers/gpu/drm/panel/panel-hx8369a.c b/drivers/gpu/drm/panel/panel-hx8369a.c
[...]
> +#include <drm/drmP.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +
> +#include <linux/gpio/consumer.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include <video/mipi_display.h>
> +#include <video/of_videomode.h>
> +#include <video/videomode.h>
> +
> +#define SETCLUMN_ADDR	0x2a
> +#define SETPAGE_ADDR 	0x2b
> +#define SETPIXEL_FMT 	0x3a

There are standard DCS functions for these now.

> +#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

Watch your indentation here and above. Use tabs and spaces more
consistently.

> +static bool hx8369a_is_dsi_interface(struct hx8369a *ctx) {

Coding style: opening { goes on a line by itself.

> +	if (ctx->mpu_interface == HX8369A_DSI_VIDEO_MODE ||
> +	    ctx->mpu_interface == HX8369A_DSI_CMD_MODE)
> +		return true;
> +
> +	return false;
> +}
> +
> +static void hx8369a_dcs_write(struct hx8369a *ctx, 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, "error %zd writing dcs seq: %*ph\n", ret, len,
> +			data);
> +}

You really shouldn't have based this on the Samsung drivers. You really
should do proper error handling here. These error messages are going to
be completely cryptic and very hard for people to look up in the driver
as opposed to a simple specific error message like:

	dev_err(ctx->dev, "failed to do XYZ: %d\n", err);

which will immediately let you look up the right location without a need
to decode the hexdump and look for the correct array.

> +#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, d, ARRAY_SIZE(d));\
> +})
> +
> +#define hx8369a_dcs_write_seq_static(ctx, seq...) \
> +({\
> +	static const u8 d[] = { seq };\
> +	hx8369a_dcs_write(ctx, d, ARRAY_SIZE(d));\
> +})
> +
> +static void hx8369a_dsi_set_display_related_register(struct hx8369a *ctx)
> +{
> +	u8 sec_p = (ctx->res_sel << 4) | 0x03;
> +
> +	hx8369a_dcs_write_seq(ctx, SETDISP, 0x00, sec_p, 0x03,
> +		0x03, 0x70, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
> +		0x03, 0x03, 0x00, 0x01);
> +}

This and all of the other initialization functions below completely
ignore any errors. Other than spamming the kernel log the user won't get
any indication of anything going wrong.

> +static void hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx)
> +{
[...]
> +}
> +
> +static void hx8369a_dsi_set_column_address(struct hx8369a *ctx)
> +{
[...]
> +}
> +
> +static void hx8369a_dsi_set_page_address(struct hx8369a *ctx)
> +{
[...]
> +}

We have standard functions for these, please use them.

> +static void hx8369a_dsi_set_extension_command(struct hx8369a *ctx)
> +{
> +	hx8369a_dcs_write_seq_static(ctx, SETEXTC, 0xff, 0x83, 0x69);
> +}

What does this do exactly? It seems to be more than setting an extension
command.

> +static void 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);
> +}
> +
> +static int hx8369a_dsi_set_sequence(struct hx8369a *ctx)
> +{
> +	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
> +	int ret;
> +
> +	hx8369a_dsi_set_extension_command(ctx);
> +	hx8369a_dsi_set_maximum_return_packet_size(ctx, 4);
> +	hx8369a_dsi_panel_init(ctx);
> +
> +	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
> +	if (ret < 0) {
> +		dev_err(ctx->dev, "failed to exit sleep mode: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = mipi_dsi_dcs_set_display_on(dsi);
> +	if (ret < 0) {
> +		dev_err(ctx->dev, "failed to set display on: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +

> +static int hx8369a_dsi_disable(struct drm_panel *panel)
> +{
> +	return 0;
> +}
[...]
> +static int hx8369a_dsi_enable(struct drm_panel *panel)
> +{
> +	return 0;
> +}

Doesn't this usually come with a backlight attached that you want to
control here?

> +static int hx8369a_get_modes(struct drm_panel *panel)
> +{
> +	struct drm_connector *connector = panel->connector;
> +	struct hx8369a *ctx = panel_to_hx8369a(panel);
> +	struct drm_display_mode *mode;
> +
> +	mode = drm_mode_create(connector->dev);
> +	if (!mode) {
> +		DRM_ERROR("failed to create a new display mode\n");
> +		return 0;
> +	}
> +
> +	drm_display_mode_from_videomode(&ctx->vm, mode);

Like I've said before, the driver should hardcode the mode because it is
implied by the compatible value.

> +	mode->width_mm = ctx->width_mm;
> +	mode->height_mm = ctx->height_mm;
> +	connector->display_info.width_mm = mode->width_mm;
> +	connector->display_info.height_mm = mode->height_mm;
> +
> +	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
> +	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_dsi_probe(struct mipi_dsi_device *dsi)
> +{
> +	struct device *dev = &dsi->dev;
> +	struct hx8369a *ctx;
> +	int ret, i;
> +	char bs[4];
> +
> +	ctx = devm_kzalloc(dev, sizeof(struct hx8369a), GFP_KERNEL);

I'd prefer sizeof(*ctx).

> +	ctx->reset_gpio = devm_gpiod_get(dev, "reset");
> +	if (IS_ERR(ctx->reset_gpio)) {
> +		dev_err(dev, "cannot get reset-gpios %ld\n",
> +			PTR_ERR(ctx->reset_gpio));
> +		return PTR_ERR(ctx->reset_gpio);
> +	}
> +	ret = gpiod_direction_output(ctx->reset_gpio, 0);
> +	if (ret < 0) {
> +		dev_err(dev, "cannot configure reset-gpios %d\n", ret);
> +		return ret;
> +	}

I think you're supposed to combine this into something like:

	ret = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);

nowadays.

> +
> +	for (i = 0; i < 4; i++) {
> +		snprintf(bs, sizeof(bs), "bs%d", i);
> +		ctx->bs_gpio[i] = devm_gpiod_get(dev, bs);
> +		if (IS_ERR(ctx->bs_gpio[i]))
> +			continue;
> +
> +		ret = gpiod_direction_output(ctx->bs_gpio[i], 1);
> +		if (ret < 0) {
> +			dev_err(dev, "cannot configure bs%d-gpio %d\n", i, ret);
> +			return ret;
> +		}

Similarly to the above:

	ret = devm_gpiod_get(dev, bs, GPIOD_OUT_HIGH);

> +static int __init hx8369a_init(void)
> +{
> +	int err;
> +
> +	err = mipi_dsi_driver_register(&hx8369a_dsi_driver);
> +	if (err < 0)
> +		return err;
> +
> +	return 0;
> +}
> +module_init(hx8369a_init);
> +
> +static void __exit hx8369a_exit(void)
> +{
> +	mipi_dsi_driver_unregister(&hx8369a_dsi_driver);
> +}
> +module_exit(hx8369a_exit);

Why can't this be module_mipi_dsi_driver(&hx8369a_dsi_driver)?

> +
> +MODULE_DESCRIPTION("Himax HX8369A panel driver");
> +MODULE_LICENSE("GPL v2");

Missing MODULE_AUTHOR()?

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20141210/9aaf3496/attachment.sig>

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 12/15] ARM: dts: imx6qdl-sabresd: Add support for TRULY TFT480800-16-E MIPI DSI panel
  2014-12-10  8:37   ` Liu Ying
  (?)
@ 2014-12-10 14:07     ` Thierry Reding
  -1 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 14:07 UTC (permalink / raw)
  To: Liu Ying
  Cc: dri-devel, devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	shawn.guo, kernel, linux, mturquette, airlied

[-- Attachment #1: Type: text/plain, Size: 1279 bytes --]

On Wed, Dec 10, 2014 at 04:37:25PM +0800, Liu Ying wrote:
[...]
> diff --git a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
[...]
> +&mipi_dsi {
> +	status = "okay";
> +
> +	panel@0 {
> +		compatible = "himax,hx8369a-dsi";
> +		reg = <0>;
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&pinctrl_mipi_panel>;
> +		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
> +		reset-delay = <120>;
> +		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
> +		data-lanes = <2>;
> +		panel-width-mm = <45>;
> +		panel-height-mm = <76>;
> +		bs = <10>;
> +		status = "okay";
> +
> +		display-timings {
> +			native-mode = <&timing1>;
> +			timing1: truly-tft480800-16-e {

This is the only place where Truly is mentioned. The panel vendor is
either Truly or it is Himax, it can't be a mix. From this example and
your patch description it seems like Truly is the manufacturer of the
panel, hence the panel compatible should really be:

	compatible = "truly,tft480800-16-e";

That it uses an HX8369A chip internally is really secondary, at least
regarding the binding. If we ever get need to support multiple panels
with the same driver IC the proper solution would be to abstract that
code out into a helper library that is used from the various panel
drivers.

Thierry

[-- Attachment #2: Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 12/15] ARM: dts: imx6qdl-sabresd: Add support for TRULY TFT480800-16-E MIPI DSI panel
@ 2014-12-10 14:07     ` Thierry Reding
  0 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 14:07 UTC (permalink / raw)
  To: Liu Ying
  Cc: devicetree, linux, kernel, linux-kernel, dri-devel, mturquette,
	linux-arm-kernel


[-- Attachment #1.1: Type: text/plain, Size: 1279 bytes --]

On Wed, Dec 10, 2014 at 04:37:25PM +0800, Liu Ying wrote:
[...]
> diff --git a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
[...]
> +&mipi_dsi {
> +	status = "okay";
> +
> +	panel@0 {
> +		compatible = "himax,hx8369a-dsi";
> +		reg = <0>;
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&pinctrl_mipi_panel>;
> +		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
> +		reset-delay = <120>;
> +		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
> +		data-lanes = <2>;
> +		panel-width-mm = <45>;
> +		panel-height-mm = <76>;
> +		bs = <10>;
> +		status = "okay";
> +
> +		display-timings {
> +			native-mode = <&timing1>;
> +			timing1: truly-tft480800-16-e {

This is the only place where Truly is mentioned. The panel vendor is
either Truly or it is Himax, it can't be a mix. From this example and
your patch description it seems like Truly is the manufacturer of the
panel, hence the panel compatible should really be:

	compatible = "truly,tft480800-16-e";

That it uses an HX8369A chip internally is really secondary, at least
regarding the binding. If we ever get need to support multiple panels
with the same driver IC the proper solution would be to abstract that
code out into a helper library that is used from the various panel
drivers.

Thierry

[-- Attachment #1.2: Type: application/pgp-signature, Size: 819 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 12/15] ARM: dts: imx6qdl-sabresd: Add support for TRULY TFT480800-16-E MIPI DSI panel
@ 2014-12-10 14:07     ` Thierry Reding
  0 siblings, 0 replies; 79+ messages in thread
From: Thierry Reding @ 2014-12-10 14:07 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Dec 10, 2014 at 04:37:25PM +0800, Liu Ying wrote:
[...]
> diff --git a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
[...]
> +&mipi_dsi {
> +	status = "okay";
> +
> +	panel at 0 {
> +		compatible = "himax,hx8369a-dsi";
> +		reg = <0>;
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&pinctrl_mipi_panel>;
> +		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
> +		reset-delay = <120>;
> +		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
> +		data-lanes = <2>;
> +		panel-width-mm = <45>;
> +		panel-height-mm = <76>;
> +		bs = <10>;
> +		status = "okay";
> +
> +		display-timings {
> +			native-mode = <&timing1>;
> +			timing1: truly-tft480800-16-e {

This is the only place where Truly is mentioned. The panel vendor is
either Truly or it is Himax, it can't be a mix. From this example and
your patch description it seems like Truly is the manufacturer of the
panel, hence the panel compatible should really be:

	compatible = "truly,tft480800-16-e";

That it uses an HX8369A chip internally is really secondary, at least
regarding the binding. If we ever get need to support multiple panels
with the same driver IC the proper solution would be to abstract that
code out into a helper library that is used from the various panel
drivers.

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20141210/ee9b2376/attachment.sig>

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
  2014-12-10 12:21     ` Thierry Reding
  (?)
@ 2014-12-11  2:52       ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-11  2:52 UTC (permalink / raw)
  To: Thierry Reding
  Cc: dri-devel, devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	shawn.guo, kernel, linux, mturquette, airlied

Hi Thierry,

On 12/10/2014 08:21 PM, Thierry Reding wrote:
> On Wed, Dec 10, 2014 at 04:37:17PM +0800, Liu Ying wrote:
>> The MIPI DSI bus driver would try to add a DSI device for a host's every
>> child node which contains the reg property.  Unfortunately, the existing
>> i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg
>
> There aren't any existing nodes for the DSI host on i.MX. This patch
> series adds support for them.

There are existing nodes for the DSI host node. Please find them in 
arch/arm/boot/dts/imx6qdl.dtsi and arch/arm/boot/dts/imx6q.dts. The DSI 
host node contains two child nodes for i.MX6DL and another two for 
i.MX6Q.  Each child node has a reg property embedded.

>
>> property, but the child nodes are ports pointing to dedicated CRTCs.
>> So, this patch phases out the child nodes with input-port property before
>> adding DSI devices for them and updates the MIPI DSI bus OF binding
>> documentation.
>>
>> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
>> ---
>>   Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
>>   drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
>>   2 files changed, 7 insertions(+), 2 deletions(-)
>
> Sorry, but NAK. There's no need for this special-case. I'll go into more
> detail in response to patch 09/15.

Thanks for reviewing the patches.

Any idea how to handle the special case?  The existing child nodes for 
the DSI host node do have the reg properties embedded.

Regards,

Liu Ying

>
> Thierry
>

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
@ 2014-12-11  2:52       ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-11  2:52 UTC (permalink / raw)
  To: Thierry Reding
  Cc: devicetree, linux, kernel, linux-kernel, dri-devel, mturquette,
	linux-arm-kernel

Hi Thierry,

On 12/10/2014 08:21 PM, Thierry Reding wrote:
> On Wed, Dec 10, 2014 at 04:37:17PM +0800, Liu Ying wrote:
>> The MIPI DSI bus driver would try to add a DSI device for a host's every
>> child node which contains the reg property.  Unfortunately, the existing
>> i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg
>
> There aren't any existing nodes for the DSI host on i.MX. This patch
> series adds support for them.

There are existing nodes for the DSI host node. Please find them in 
arch/arm/boot/dts/imx6qdl.dtsi and arch/arm/boot/dts/imx6q.dts. The DSI 
host node contains two child nodes for i.MX6DL and another two for 
i.MX6Q.  Each child node has a reg property embedded.

>
>> property, but the child nodes are ports pointing to dedicated CRTCs.
>> So, this patch phases out the child nodes with input-port property before
>> adding DSI devices for them and updates the MIPI DSI bus OF binding
>> documentation.
>>
>> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
>> ---
>>   Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
>>   drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
>>   2 files changed, 7 insertions(+), 2 deletions(-)
>
> Sorry, but NAK. There's no need for this special-case. I'll go into more
> detail in response to patch 09/15.

Thanks for reviewing the patches.

Any idea how to handle the special case?  The existing child nodes for 
the DSI host node do have the reg properties embedded.

Regards,

Liu Ying

>
> Thierry
>
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
@ 2014-12-11  2:52       ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-11  2:52 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Thierry,

On 12/10/2014 08:21 PM, Thierry Reding wrote:
> On Wed, Dec 10, 2014 at 04:37:17PM +0800, Liu Ying wrote:
>> The MIPI DSI bus driver would try to add a DSI device for a host's every
>> child node which contains the reg property.  Unfortunately, the existing
>> i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg
>
> There aren't any existing nodes for the DSI host on i.MX. This patch
> series adds support for them.

There are existing nodes for the DSI host node. Please find them in 
arch/arm/boot/dts/imx6qdl.dtsi and arch/arm/boot/dts/imx6q.dts. The DSI 
host node contains two child nodes for i.MX6DL and another two for 
i.MX6Q.  Each child node has a reg property embedded.

>
>> property, but the child nodes are ports pointing to dedicated CRTCs.
>> So, this patch phases out the child nodes with input-port property before
>> adding DSI devices for them and updates the MIPI DSI bus OF binding
>> documentation.
>>
>> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
>> ---
>>   Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
>>   drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
>>   2 files changed, 7 insertions(+), 2 deletions(-)
>
> Sorry, but NAK. There's no need for this special-case. I'll go into more
> detail in response to patch 09/15.

Thanks for reviewing the patches.

Any idea how to handle the special case?  The existing child nodes for 
the DSI host node do have the reg properties embedded.

Regards,

Liu Ying

>
> Thierry
>

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
  2014-12-11  2:52       ` Liu Ying
  (?)
@ 2014-12-11  5:50         ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-11  5:50 UTC (permalink / raw)
  To: Thierry Reding
  Cc: devicetree, linux, kernel, linux-kernel, dri-devel, mturquette,
	linux-arm-kernel

Hi Thierry,

On 12/11/2014 10:52 AM, Liu Ying wrote:
> Hi Thierry,
>
> On 12/10/2014 08:21 PM, Thierry Reding wrote:
>> On Wed, Dec 10, 2014 at 04:37:17PM +0800, Liu Ying wrote:
>>> The MIPI DSI bus driver would try to add a DSI device for a host's every
>>> child node which contains the reg property.  Unfortunately, the existing
>>> i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg
>>
>> There aren't any existing nodes for the DSI host on i.MX. This patch
>> series adds support for them.
>
> There are existing nodes for the DSI host node. Please find them in
> arch/arm/boot/dts/imx6qdl.dtsi and arch/arm/boot/dts/imx6q.dts. The DSI
> host node contains two child nodes for i.MX6DL and another two for
> i.MX6Q.  Each child node has a reg property embedded.
>
>>
>>> property, but the child nodes are ports pointing to dedicated CRTCs.
>>> So, this patch phases out the child nodes with input-port property
>>> before
>>> adding DSI devices for them and updates the MIPI DSI bus OF binding
>>> documentation.
>>>
>>> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
>>> ---
>>>   Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
>>>   drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
>>>   2 files changed, 7 insertions(+), 2 deletions(-)
>>
>> Sorry, but NAK. There's no need for this special-case. I'll go into more
>> detail in response to patch 09/15.
>
> Thanks for reviewing the patches.
>
> Any idea how to handle the special case?  The existing child nodes for
> the DSI host node do have the reg properties embedded.
>

I see your suggestions on this topic in the comments for patch 09/15.
I will try to move the existing child nodes into a parent port node.

Liu Ying

>
>>
>> Thierry
>>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
@ 2014-12-11  5:50         ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-11  5:50 UTC (permalink / raw)
  To: Thierry Reding
  Cc: devicetree, linux, linux-kernel, dri-devel, kernel, mturquette,
	linux-arm-kernel

Hi Thierry,

On 12/11/2014 10:52 AM, Liu Ying wrote:
> Hi Thierry,
>
> On 12/10/2014 08:21 PM, Thierry Reding wrote:
>> On Wed, Dec 10, 2014 at 04:37:17PM +0800, Liu Ying wrote:
>>> The MIPI DSI bus driver would try to add a DSI device for a host's every
>>> child node which contains the reg property.  Unfortunately, the existing
>>> i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg
>>
>> There aren't any existing nodes for the DSI host on i.MX. This patch
>> series adds support for them.
>
> There are existing nodes for the DSI host node. Please find them in
> arch/arm/boot/dts/imx6qdl.dtsi and arch/arm/boot/dts/imx6q.dts. The DSI
> host node contains two child nodes for i.MX6DL and another two for
> i.MX6Q.  Each child node has a reg property embedded.
>
>>
>>> property, but the child nodes are ports pointing to dedicated CRTCs.
>>> So, this patch phases out the child nodes with input-port property
>>> before
>>> adding DSI devices for them and updates the MIPI DSI bus OF binding
>>> documentation.
>>>
>>> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
>>> ---
>>>   Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
>>>   drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
>>>   2 files changed, 7 insertions(+), 2 deletions(-)
>>
>> Sorry, but NAK. There's no need for this special-case. I'll go into more
>> detail in response to patch 09/15.
>
> Thanks for reviewing the patches.
>
> Any idea how to handle the special case?  The existing child nodes for
> the DSI host node do have the reg properties embedded.
>

I see your suggestions on this topic in the comments for patch 09/15.
I will try to move the existing child nodes into a parent port node.

Liu Ying

>
>>
>> Thierry
>>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property
@ 2014-12-11  5:50         ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-11  5:50 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Thierry,

On 12/11/2014 10:52 AM, Liu Ying wrote:
> Hi Thierry,
>
> On 12/10/2014 08:21 PM, Thierry Reding wrote:
>> On Wed, Dec 10, 2014 at 04:37:17PM +0800, Liu Ying wrote:
>>> The MIPI DSI bus driver would try to add a DSI device for a host's every
>>> child node which contains the reg property.  Unfortunately, the existing
>>> i.MX6Q/SDL MIPI DSI host device tree node's child nodes contain the reg
>>
>> There aren't any existing nodes for the DSI host on i.MX. This patch
>> series adds support for them.
>
> There are existing nodes for the DSI host node. Please find them in
> arch/arm/boot/dts/imx6qdl.dtsi and arch/arm/boot/dts/imx6q.dts. The DSI
> host node contains two child nodes for i.MX6DL and another two for
> i.MX6Q.  Each child node has a reg property embedded.
>
>>
>>> property, but the child nodes are ports pointing to dedicated CRTCs.
>>> So, this patch phases out the child nodes with input-port property
>>> before
>>> adding DSI devices for them and updates the MIPI DSI bus OF binding
>>> documentation.
>>>
>>> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
>>> ---
>>>   Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt | 4 ++++
>>>   drivers/gpu/drm/drm_mipi_dsi.c                              | 5 +++--
>>>   2 files changed, 7 insertions(+), 2 deletions(-)
>>
>> Sorry, but NAK. There's no need for this special-case. I'll go into more
>> detail in response to patch 09/15.
>
> Thanks for reviewing the patches.
>
> Any idea how to handle the special case?  The existing child nodes for
> the DSI host node do have the reg properties embedded.
>

I see your suggestions on this topic in the comments for patch 09/15.
I will try to move the existing child nodes into a parent port node.

Liu Ying

>
>>
>> Thierry
>>
> _______________________________________________
> dri-devel mailing list
> dri-devel at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
  2014-12-10 13:16     ` Thierry Reding
  (?)
@ 2014-12-17  9:44       ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-17  9:44 UTC (permalink / raw)
  To: Thierry Reding
  Cc: dri-devel, devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	shawn.guo, kernel, linux, mturquette, airlied

Hi Thierry,

Sorry for the late response.
I tried to address almost all your comments locally first.
More feedback below.

On 12/10/2014 09:16 PM, Thierry Reding wrote:
> On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
>> This patch adds i.MX 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>
>> ---
>>   .../devicetree/bindings/drm/imx/mipi_dsi.txt       |   81 ++
>>   drivers/gpu/drm/imx/Kconfig                        |    6 +
>>   drivers/gpu/drm/imx/Makefile                       |    1 +
>>   drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
>>   4 files changed, 1105 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>>   create mode 100644 drivers/gpu/drm/imx/imx-mipi-dsi.c
>>
>> 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..3d07fd7
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>> @@ -0,0 +1,81 @@
>> +Device-Tree bindings for MIPI DSI host controller
>> +
>> +MIPI DSI host controller
>> +========================
>> +
>> +The MIPI DSI host controller is a Synopsys DesignWare IP.
>> +It 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 : Should be "fsl,imx6q-mipi-dsi" for i.MX6q/sdl SoCs.
>> + - 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.
>
> Side-note: Shouldn't this really be a pinmux, then?

No. The muxing is inside the i.MX SoC.
There is a DT binding documentation for the system controller node(gpr)
at [1]. And, for i.MX DT sources, there are several existing use cases 
in which the gpr node is referred by other nodes.

[1] Documentation/devicetree/bindings/mfd/syscon.txt.

>
>> + - clocks, clock-names : Phandles to the controller pllref, pllref_gate
>> +           and core_cfg clocks, as described in [1] and [2].
>> + - panel@0 : A panel node which contains a display-timings child node as
>> +             defined in [3].
>
> There's no need for these to be named panel@*. They could be bridges for
> example. And no, they shouldn't contain a display-timings child node
> either. Panels should have a proper driver and the driver being device
> specific it should have the timings embedded.

Ok, I'll move the timing to the panel driver.

>
>> + - port@[0-4] : Up to four port nodes with endpoint definitions as defined
>> +   in [4], corresponding to the four inputs to the controller multiplexer.
>> +   Note that each port node should contain the input-port property to
>> +   distinguish it from the panel node, as described in [5].
>
> [4] says that you can group all port nodes under a ports parent node. I
> think this is really what you want to do here to make it clear that the
> ports aren't part of the DSI host binding part of the device.
>

Accepted.

>> diff --git a/drivers/gpu/drm/imx/imx-mipi-dsi.c b/drivers/gpu/drm/imx/imx-mipi-dsi.c
> [...]
>> +/*
>> + * i.MX drm driver - MIPI DSI Host Controller
>> + *
>> + * Copyright (C) 2011-2014 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/component.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
>> +#include <linux/module.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_device.h>
>> +#include <linux/regmap.h>
>> +#include <linux/videodev2.h>
>> +#include <asm/div64.h>
>
> Don't you want the more generic linux/math64.h here?

I'll use linux/math64.h.

>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_fb_helper.h>
>
> I don't see any of the functions defined in that header used here.

I'll remove this.

>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
>> +#include <video/mipi_display.h>
>> +#include <video/of_videomode.h>
>> +#include <video/videomode.h>
>> +
>> +#include "imx-drm.h"
>> +
>> +#define DRIVER_NAME 			"imx-mipi-dsi"
>> +
>> +#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)
>
> Your indentation here is... weird.

I'll fix this.

>
>> +#define IMX_MIPI_DSI_MAX_DATA_LANES	2
>> +
>> +#define PHY_STATUS_TIMEOUT		10
>> +#define CMD_PKT_STATUS_TIMEOUT		20
>> +
>> +#define IMX_MIPI_DSI_PLD_DATA_BUF_SIZE	4
>> +
>> +#define MHZ				1000000
>
> Why not reuse the existing USEC_PER_SEC?

Accepted.

>
>> +#define host_to_dsi(host) container_of(host, struct imx_mipi_dsi, dsi_host)
>> +#define con_to_dsi(x) container_of(x, struct imx_mipi_dsi, connector)
>> +#define enc_to_dsi(x) container_of(x, struct imx_mipi_dsi, encoder)
>
> These should really be static inline functions for proper type safety.

Ok, I'll change to use functions.

>
>> +struct imx_mipi_dsi {
>> +	struct mipi_dsi_host dsi_host;
>> +	struct drm_connector connector;
>> +	struct drm_encoder encoder;
>> +	struct device_node *panel_node;
>> +	struct drm_panel *panel;
>> +	struct device *dev;
>> +
>> +	struct regmap *regmap;
>> +	void __iomem *base;
>> +
>> +	struct clk *pllref_clk;
>> +	struct clk *pllref_gate_clk;
>> +	struct clk *cfg_clk;
>> +
>> +	unsigned int lane_mbps; /* per lane */
>> +	u32 channel;
>> +	u32 lanes;
>> +	u32 format;
>> +	struct videomode vm;
>> +
>> +	bool enabled;
>
> Do you really need this flag?

I'll remove this flag.

>
>> +};
>> +
>> +enum {
>> +	STATUS_TO_CLEAR,
>> +	STATUS_TO_SET,
>> +};
>> +
>> +struct dphy_pll_testdin_map {
>> +	int max_mbps;
>
> unsigned?

Accepted.

>
>> +	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 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 int format_to_bpp(struct imx_mipi_dsi *dsi)
>> +{
>> +	switch (dsi->format) {
>> +	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;
>> +	default:
>> +		dev_err(dsi->dev, "unsupported pixel format\n");
>> +		return -EINVAL;
>> +	}
>> +}
>
> Is there a reason why this can't be a standard MIPI DSI function?

How about moving this to include/drm/drm_mipi_dsi.h as an inline
function?

>
>> +
>> +static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
>> +			int timeout, bool to_set)
>> +{
>> +	u32 val;
>> +	bool out = false;
>> +
>> +	val = dsi_read(dsi, reg);
>> +	for (;;) {
>> +		out = to_set ? (val & status) : !(val & status);
>> +		if (out)
>> +			break;
>> +
>> +		if (!timeout--)
>> +			return -EFAULT;
>> +
>> +		msleep(1);
>> +		val = dsi_read(dsi, reg);
>> +	}
>> +	return 0;
>> +}
>
> You should probably use a properly timed loop here. msleep() isn't
> guaranteed to return after exactly one millisecond, so your timeout is
> never going to be accurate. Something like the following would be better
> in my opinion:
>
> 	timeout = jiffies + msecs_to_jiffies(timeout);
>
> 	while (time_before(jiffies, timeout)) {
> 		...
> 	}
>
> Also timeout should be unsigned long in that case.

Accepted.

>
>> +static int imx_mipi_dsi_host_attach(struct mipi_dsi_host *host,
>> +				    struct mipi_dsi_device *device)
>> +{
>> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
>> +	int ret;
>> +
>> +	if (device->lanes > IMX_MIPI_DSI_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;
>> +	}
>> +
>> +	dsi->lanes = device->lanes;
>> +	dsi->channel = device->channel;
>> +	dsi->format = device->format;
>> +	dsi->panel_node = device->dev.of_node;
>> +	of_get_videomode(dsi->panel_node, &dsi->vm, 0);
>
> No, you shouldn't use this. Query the mode from the panel instead. Or
> rather, encode this requirement in ->mode_valid() since that'll give you
> direct access to the mode.
>
> That way, ->host_attach() becomes a filter for compatible devices, yet
> devices may support multiple modes, therefore ->mode_valid() can be used
> to filter out those that don't work with your DSI host controller. There
> is a subtle difference here between devices that are compatible with the
> controller and modes that the controller doesn't support.

Accepted.

In the next version ->host_attach(), I will check the peripheral
compatibility.  Also, I'll get the panel by calling of_drm_find_panel(), 
and then call the drm_panel_attach() function.
Do you think this is okay?

>
>> +
>> +	ret = imx_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +static int imx_mipi_dsi_host_detach(struct mipi_dsi_host *host,
>> +				    struct mipi_dsi_device *device)
>> +{
>> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
>> +
>> +	dsi->panel_node = NULL;
>> +	dsi->panel = NULL;
>> +
>> +	return 0;
>> +}
>
> You'll want to detach from the panel here as well.

Ok. I'll call drm_panel_detach() here.

>
>> +
>> +static int imx_mipi_dsi_gen_pkt_hdr_write(struct imx_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 avaliable 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 imx_mipi_dsi_dcs_short_write(struct imx_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 imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
>> +}
>
> It seems like you would profit from converting to the newly introduced
> mipi_dsi_create_packet() helper which does most of the packing along
> with some sanity checking for you.

I looked at the helper. It seems it's not quite straightforward for me
to use it here.  The message type here defined by GEN_HTYPE() is 8bit
long, while the helper seems to take it as 6bit long?

>
>> +
>> +static int imx_mipi_dsi_dcs_long_write(struct imx_mipi_dsi *dsi,
>> +				       const struct mipi_dsi_msg *msg)
>> +{
>> +	const u32 *tx_buf = msg->tx_buf;
>> +	int len = msg->tx_len, ret;
>> +	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
>> +
>> +	if (msg->tx_len < 3) {
>> +		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
>> +			msg->tx_len);
>> +		return -EINVAL;
>> +	}
>> +
>> +	/* write the bulks */
>> +	while (len / IMX_MIPI_DSI_PLD_DATA_BUF_SIZE) {
>> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
>> +		tx_buf++;
>> +		len -= IMX_MIPI_DSI_PLD_DATA_BUF_SIZE;
>
> This is confusing. Maybe a better option would be to use sizeof(*tx_buf)
> instead of this macro. You're effectively consuming one u32 with each
> write, irrespective of what the value of the macro is.

Ok. I'll use sizeof(*tx_buf).

>
>> +		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 avaliable "
>> +					"write payload FIFO\n");
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	/* write the remainder */
>> +	if (len > 0) {
>> +		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 avaliable "
>> +					"write payload FIFO\n");
>> +			return ret;
>> +		}
>> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
>> +	}
>
> This is really duplicating what the above loop does. Why can't you do it
> in a single loop? Also the transmission buffer isn't guaranteed to be a
> multiple of 4 bytes, so you could have the situation where len > 0 and
> len < 4 and yet you'll be reading 4 bytes by doing *tx_buf and accessing
> memory that you shouldn't.

Good catch.  I'll make it to be a single loop and avoid accessing memory
that I shouldn't in the next version.

>
>> +	return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
>> +}
>> +
>> +static ssize_t imx_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
>> +					  const struct mipi_dsi_msg *msg)
>> +{
>> +	struct imx_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 = imx_mipi_dsi_dcs_short_write(dsi, msg);
>> +		break;
>> +	case MIPI_DSI_DCS_LONG_WRITE:
>> +		ret = imx_mipi_dsi_dcs_long_write(dsi, msg);
>> +		break;
>> +	default:
>> +		dev_err(dsi->dev, "unsupported message type\n");
>> +		ret = -EFAULT;
>
> EFAULT really isn't appropriate here.

I'll change it to EINVAL.

>
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct mipi_dsi_host_ops imx_mipi_dsi_host_ops = {
>> +	.attach = imx_mipi_dsi_host_attach,
>> +	.detach = imx_mipi_dsi_host_detach,
>> +	.transfer = imx_mipi_dsi_host_transfer,
>> +};
>> +
>> +static enum drm_connector_status
>> +imx_mipi_dsi_detect(struct drm_connector *connector, bool force)
>> +{
>> +	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
>> +
>> +	if (!dsi->panel) {
>> +		dsi->panel = of_drm_find_panel(dsi->panel_node);
>> +		if (dsi->panel)
>> +			drm_panel_attach(dsi->panel, &dsi->connector);
>> +	}
>> +
>> +	if (dsi->panel)
>> +		return connector_status_connected;
>> +
>> +	return connector_status_disconnected;
>> +
>> +}
>
> You really shouldn't be doing that here. of_drm_find_panel() returning
> NULL should be considered cause for deferring probe. I suspect that the
> driver doesn't really handle that very well at all, though, since if it
> did you would've run into the same issue that Benjamin ran into this
> morning.

I'll return connector_status_disconnected directly here. Is it okay?

>
>> +static void imx_mipi_dsi_encoder_prepare(struct drm_encoder *encoder)
>> +{
>> +	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
>> +	u32 interface_pix_fmt;
>> +
>> +	switch (dsi->format) {
>> +	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:
>> +		dev_err(dsi->dev, "unsupported DSI pixel format\n");
>> +		return;
>
> Why even try doing this if you know upfront that it can't be done. You
> know much earlier than this that you can't drive the pixel format, why
> not abort then?
>
> People are much more likely to notice that the panel isn't supported if
> the DSI output doesn't even show up (or doesn't expose any modes) rather
> than if they have to find this error message in dmesg.

Accepted. I'll check the dsi format compatibility in ->host_attach().
And, how about changing the default patch to be this?

	default:
		BUG();
		return;
>
>> +static void imx_mipi_dsi_video_mode_config(struct imx_mipi_dsi *dsi)
>> +{
>> +	u32 val;
>> +
>> +	val = VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER;
>> +
>> +	dsi_write(dsi, DSI_VID_MODE_CFG, val);
>> +}
>
> You probably want to parameterize based on the DSI peripheral's flags. I
> see that you do reject devices with any other modes, so this may not be
> necessary now. Out of curiosity, the hardware supports other modes,
> right?

Yes, the hardware supports other modes according to it's register
definition.
I prefer not to parameterize at present as I do reject devices with 
other modes now.

>
> [...]
>> +MODULE_LICENSE("GPL v2");
>
> Sigh... according to your header comment this needs to be "GPL".

I'll change it to be "GPL".

Thanks,

Liu Ying

>
> Thierry
>

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
@ 2014-12-17  9:44       ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-17  9:44 UTC (permalink / raw)
  To: Thierry Reding
  Cc: devicetree, linux, kernel, linux-kernel, dri-devel, mturquette,
	linux-arm-kernel

Hi Thierry,

Sorry for the late response.
I tried to address almost all your comments locally first.
More feedback below.

On 12/10/2014 09:16 PM, Thierry Reding wrote:
> On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
>> This patch adds i.MX 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>
>> ---
>>   .../devicetree/bindings/drm/imx/mipi_dsi.txt       |   81 ++
>>   drivers/gpu/drm/imx/Kconfig                        |    6 +
>>   drivers/gpu/drm/imx/Makefile                       |    1 +
>>   drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
>>   4 files changed, 1105 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>>   create mode 100644 drivers/gpu/drm/imx/imx-mipi-dsi.c
>>
>> 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..3d07fd7
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>> @@ -0,0 +1,81 @@
>> +Device-Tree bindings for MIPI DSI host controller
>> +
>> +MIPI DSI host controller
>> +========================
>> +
>> +The MIPI DSI host controller is a Synopsys DesignWare IP.
>> +It 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 : Should be "fsl,imx6q-mipi-dsi" for i.MX6q/sdl SoCs.
>> + - 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.
>
> Side-note: Shouldn't this really be a pinmux, then?

No. The muxing is inside the i.MX SoC.
There is a DT binding documentation for the system controller node(gpr)
at [1]. And, for i.MX DT sources, there are several existing use cases 
in which the gpr node is referred by other nodes.

[1] Documentation/devicetree/bindings/mfd/syscon.txt.

>
>> + - clocks, clock-names : Phandles to the controller pllref, pllref_gate
>> +           and core_cfg clocks, as described in [1] and [2].
>> + - panel@0 : A panel node which contains a display-timings child node as
>> +             defined in [3].
>
> There's no need for these to be named panel@*. They could be bridges for
> example. And no, they shouldn't contain a display-timings child node
> either. Panels should have a proper driver and the driver being device
> specific it should have the timings embedded.

Ok, I'll move the timing to the panel driver.

>
>> + - port@[0-4] : Up to four port nodes with endpoint definitions as defined
>> +   in [4], corresponding to the four inputs to the controller multiplexer.
>> +   Note that each port node should contain the input-port property to
>> +   distinguish it from the panel node, as described in [5].
>
> [4] says that you can group all port nodes under a ports parent node. I
> think this is really what you want to do here to make it clear that the
> ports aren't part of the DSI host binding part of the device.
>

Accepted.

>> diff --git a/drivers/gpu/drm/imx/imx-mipi-dsi.c b/drivers/gpu/drm/imx/imx-mipi-dsi.c
> [...]
>> +/*
>> + * i.MX drm driver - MIPI DSI Host Controller
>> + *
>> + * Copyright (C) 2011-2014 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/component.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
>> +#include <linux/module.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_device.h>
>> +#include <linux/regmap.h>
>> +#include <linux/videodev2.h>
>> +#include <asm/div64.h>
>
> Don't you want the more generic linux/math64.h here?

I'll use linux/math64.h.

>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_fb_helper.h>
>
> I don't see any of the functions defined in that header used here.

I'll remove this.

>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
>> +#include <video/mipi_display.h>
>> +#include <video/of_videomode.h>
>> +#include <video/videomode.h>
>> +
>> +#include "imx-drm.h"
>> +
>> +#define DRIVER_NAME 			"imx-mipi-dsi"
>> +
>> +#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)
>
> Your indentation here is... weird.

I'll fix this.

>
>> +#define IMX_MIPI_DSI_MAX_DATA_LANES	2
>> +
>> +#define PHY_STATUS_TIMEOUT		10
>> +#define CMD_PKT_STATUS_TIMEOUT		20
>> +
>> +#define IMX_MIPI_DSI_PLD_DATA_BUF_SIZE	4
>> +
>> +#define MHZ				1000000
>
> Why not reuse the existing USEC_PER_SEC?

Accepted.

>
>> +#define host_to_dsi(host) container_of(host, struct imx_mipi_dsi, dsi_host)
>> +#define con_to_dsi(x) container_of(x, struct imx_mipi_dsi, connector)
>> +#define enc_to_dsi(x) container_of(x, struct imx_mipi_dsi, encoder)
>
> These should really be static inline functions for proper type safety.

Ok, I'll change to use functions.

>
>> +struct imx_mipi_dsi {
>> +	struct mipi_dsi_host dsi_host;
>> +	struct drm_connector connector;
>> +	struct drm_encoder encoder;
>> +	struct device_node *panel_node;
>> +	struct drm_panel *panel;
>> +	struct device *dev;
>> +
>> +	struct regmap *regmap;
>> +	void __iomem *base;
>> +
>> +	struct clk *pllref_clk;
>> +	struct clk *pllref_gate_clk;
>> +	struct clk *cfg_clk;
>> +
>> +	unsigned int lane_mbps; /* per lane */
>> +	u32 channel;
>> +	u32 lanes;
>> +	u32 format;
>> +	struct videomode vm;
>> +
>> +	bool enabled;
>
> Do you really need this flag?

I'll remove this flag.

>
>> +};
>> +
>> +enum {
>> +	STATUS_TO_CLEAR,
>> +	STATUS_TO_SET,
>> +};
>> +
>> +struct dphy_pll_testdin_map {
>> +	int max_mbps;
>
> unsigned?

Accepted.

>
>> +	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 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 int format_to_bpp(struct imx_mipi_dsi *dsi)
>> +{
>> +	switch (dsi->format) {
>> +	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;
>> +	default:
>> +		dev_err(dsi->dev, "unsupported pixel format\n");
>> +		return -EINVAL;
>> +	}
>> +}
>
> Is there a reason why this can't be a standard MIPI DSI function?

How about moving this to include/drm/drm_mipi_dsi.h as an inline
function?

>
>> +
>> +static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
>> +			int timeout, bool to_set)
>> +{
>> +	u32 val;
>> +	bool out = false;
>> +
>> +	val = dsi_read(dsi, reg);
>> +	for (;;) {
>> +		out = to_set ? (val & status) : !(val & status);
>> +		if (out)
>> +			break;
>> +
>> +		if (!timeout--)
>> +			return -EFAULT;
>> +
>> +		msleep(1);
>> +		val = dsi_read(dsi, reg);
>> +	}
>> +	return 0;
>> +}
>
> You should probably use a properly timed loop here. msleep() isn't
> guaranteed to return after exactly one millisecond, so your timeout is
> never going to be accurate. Something like the following would be better
> in my opinion:
>
> 	timeout = jiffies + msecs_to_jiffies(timeout);
>
> 	while (time_before(jiffies, timeout)) {
> 		...
> 	}
>
> Also timeout should be unsigned long in that case.

Accepted.

>
>> +static int imx_mipi_dsi_host_attach(struct mipi_dsi_host *host,
>> +				    struct mipi_dsi_device *device)
>> +{
>> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
>> +	int ret;
>> +
>> +	if (device->lanes > IMX_MIPI_DSI_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;
>> +	}
>> +
>> +	dsi->lanes = device->lanes;
>> +	dsi->channel = device->channel;
>> +	dsi->format = device->format;
>> +	dsi->panel_node = device->dev.of_node;
>> +	of_get_videomode(dsi->panel_node, &dsi->vm, 0);
>
> No, you shouldn't use this. Query the mode from the panel instead. Or
> rather, encode this requirement in ->mode_valid() since that'll give you
> direct access to the mode.
>
> That way, ->host_attach() becomes a filter for compatible devices, yet
> devices may support multiple modes, therefore ->mode_valid() can be used
> to filter out those that don't work with your DSI host controller. There
> is a subtle difference here between devices that are compatible with the
> controller and modes that the controller doesn't support.

Accepted.

In the next version ->host_attach(), I will check the peripheral
compatibility.  Also, I'll get the panel by calling of_drm_find_panel(), 
and then call the drm_panel_attach() function.
Do you think this is okay?

>
>> +
>> +	ret = imx_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +static int imx_mipi_dsi_host_detach(struct mipi_dsi_host *host,
>> +				    struct mipi_dsi_device *device)
>> +{
>> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
>> +
>> +	dsi->panel_node = NULL;
>> +	dsi->panel = NULL;
>> +
>> +	return 0;
>> +}
>
> You'll want to detach from the panel here as well.

Ok. I'll call drm_panel_detach() here.

>
>> +
>> +static int imx_mipi_dsi_gen_pkt_hdr_write(struct imx_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 avaliable 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 imx_mipi_dsi_dcs_short_write(struct imx_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 imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
>> +}
>
> It seems like you would profit from converting to the newly introduced
> mipi_dsi_create_packet() helper which does most of the packing along
> with some sanity checking for you.

I looked at the helper. It seems it's not quite straightforward for me
to use it here.  The message type here defined by GEN_HTYPE() is 8bit
long, while the helper seems to take it as 6bit long?

>
>> +
>> +static int imx_mipi_dsi_dcs_long_write(struct imx_mipi_dsi *dsi,
>> +				       const struct mipi_dsi_msg *msg)
>> +{
>> +	const u32 *tx_buf = msg->tx_buf;
>> +	int len = msg->tx_len, ret;
>> +	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
>> +
>> +	if (msg->tx_len < 3) {
>> +		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
>> +			msg->tx_len);
>> +		return -EINVAL;
>> +	}
>> +
>> +	/* write the bulks */
>> +	while (len / IMX_MIPI_DSI_PLD_DATA_BUF_SIZE) {
>> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
>> +		tx_buf++;
>> +		len -= IMX_MIPI_DSI_PLD_DATA_BUF_SIZE;
>
> This is confusing. Maybe a better option would be to use sizeof(*tx_buf)
> instead of this macro. You're effectively consuming one u32 with each
> write, irrespective of what the value of the macro is.

Ok. I'll use sizeof(*tx_buf).

>
>> +		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 avaliable "
>> +					"write payload FIFO\n");
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	/* write the remainder */
>> +	if (len > 0) {
>> +		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 avaliable "
>> +					"write payload FIFO\n");
>> +			return ret;
>> +		}
>> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
>> +	}
>
> This is really duplicating what the above loop does. Why can't you do it
> in a single loop? Also the transmission buffer isn't guaranteed to be a
> multiple of 4 bytes, so you could have the situation where len > 0 and
> len < 4 and yet you'll be reading 4 bytes by doing *tx_buf and accessing
> memory that you shouldn't.

Good catch.  I'll make it to be a single loop and avoid accessing memory
that I shouldn't in the next version.

>
>> +	return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
>> +}
>> +
>> +static ssize_t imx_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
>> +					  const struct mipi_dsi_msg *msg)
>> +{
>> +	struct imx_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 = imx_mipi_dsi_dcs_short_write(dsi, msg);
>> +		break;
>> +	case MIPI_DSI_DCS_LONG_WRITE:
>> +		ret = imx_mipi_dsi_dcs_long_write(dsi, msg);
>> +		break;
>> +	default:
>> +		dev_err(dsi->dev, "unsupported message type\n");
>> +		ret = -EFAULT;
>
> EFAULT really isn't appropriate here.

I'll change it to EINVAL.

>
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct mipi_dsi_host_ops imx_mipi_dsi_host_ops = {
>> +	.attach = imx_mipi_dsi_host_attach,
>> +	.detach = imx_mipi_dsi_host_detach,
>> +	.transfer = imx_mipi_dsi_host_transfer,
>> +};
>> +
>> +static enum drm_connector_status
>> +imx_mipi_dsi_detect(struct drm_connector *connector, bool force)
>> +{
>> +	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
>> +
>> +	if (!dsi->panel) {
>> +		dsi->panel = of_drm_find_panel(dsi->panel_node);
>> +		if (dsi->panel)
>> +			drm_panel_attach(dsi->panel, &dsi->connector);
>> +	}
>> +
>> +	if (dsi->panel)
>> +		return connector_status_connected;
>> +
>> +	return connector_status_disconnected;
>> +
>> +}
>
> You really shouldn't be doing that here. of_drm_find_panel() returning
> NULL should be considered cause for deferring probe. I suspect that the
> driver doesn't really handle that very well at all, though, since if it
> did you would've run into the same issue that Benjamin ran into this
> morning.

I'll return connector_status_disconnected directly here. Is it okay?

>
>> +static void imx_mipi_dsi_encoder_prepare(struct drm_encoder *encoder)
>> +{
>> +	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
>> +	u32 interface_pix_fmt;
>> +
>> +	switch (dsi->format) {
>> +	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:
>> +		dev_err(dsi->dev, "unsupported DSI pixel format\n");
>> +		return;
>
> Why even try doing this if you know upfront that it can't be done. You
> know much earlier than this that you can't drive the pixel format, why
> not abort then?
>
> People are much more likely to notice that the panel isn't supported if
> the DSI output doesn't even show up (or doesn't expose any modes) rather
> than if they have to find this error message in dmesg.

Accepted. I'll check the dsi format compatibility in ->host_attach().
And, how about changing the default patch to be this?

	default:
		BUG();
		return;
>
>> +static void imx_mipi_dsi_video_mode_config(struct imx_mipi_dsi *dsi)
>> +{
>> +	u32 val;
>> +
>> +	val = VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER;
>> +
>> +	dsi_write(dsi, DSI_VID_MODE_CFG, val);
>> +}
>
> You probably want to parameterize based on the DSI peripheral's flags. I
> see that you do reject devices with any other modes, so this may not be
> necessary now. Out of curiosity, the hardware supports other modes,
> right?

Yes, the hardware supports other modes according to it's register
definition.
I prefer not to parameterize at present as I do reject devices with 
other modes now.

>
> [...]
>> +MODULE_LICENSE("GPL v2");
>
> Sigh... according to your header comment this needs to be "GPL".

I'll change it to be "GPL".

Thanks,

Liu Ying

>
> Thierry
>
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
@ 2014-12-17  9:44       ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-17  9:44 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Thierry,

Sorry for the late response.
I tried to address almost all your comments locally first.
More feedback below.

On 12/10/2014 09:16 PM, Thierry Reding wrote:
> On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
>> This patch adds i.MX 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>
>> ---
>>   .../devicetree/bindings/drm/imx/mipi_dsi.txt       |   81 ++
>>   drivers/gpu/drm/imx/Kconfig                        |    6 +
>>   drivers/gpu/drm/imx/Makefile                       |    1 +
>>   drivers/gpu/drm/imx/imx-mipi-dsi.c                 | 1017 ++++++++++++++++++++
>>   4 files changed, 1105 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>>   create mode 100644 drivers/gpu/drm/imx/imx-mipi-dsi.c
>>
>> 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..3d07fd7
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>> @@ -0,0 +1,81 @@
>> +Device-Tree bindings for MIPI DSI host controller
>> +
>> +MIPI DSI host controller
>> +========================
>> +
>> +The MIPI DSI host controller is a Synopsys DesignWare IP.
>> +It 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 : Should be "fsl,imx6q-mipi-dsi" for i.MX6q/sdl SoCs.
>> + - 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.
>
> Side-note: Shouldn't this really be a pinmux, then?

No. The muxing is inside the i.MX SoC.
There is a DT binding documentation for the system controller node(gpr)
at [1]. And, for i.MX DT sources, there are several existing use cases 
in which the gpr node is referred by other nodes.

[1] Documentation/devicetree/bindings/mfd/syscon.txt.

>
>> + - clocks, clock-names : Phandles to the controller pllref, pllref_gate
>> +           and core_cfg clocks, as described in [1] and [2].
>> + - panel at 0 : A panel node which contains a display-timings child node as
>> +             defined in [3].
>
> There's no need for these to be named panel@*. They could be bridges for
> example. And no, they shouldn't contain a display-timings child node
> either. Panels should have a proper driver and the driver being device
> specific it should have the timings embedded.

Ok, I'll move the timing to the panel driver.

>
>> + - port@[0-4] : Up to four port nodes with endpoint definitions as defined
>> +   in [4], corresponding to the four inputs to the controller multiplexer.
>> +   Note that each port node should contain the input-port property to
>> +   distinguish it from the panel node, as described in [5].
>
> [4] says that you can group all port nodes under a ports parent node. I
> think this is really what you want to do here to make it clear that the
> ports aren't part of the DSI host binding part of the device.
>

Accepted.

>> diff --git a/drivers/gpu/drm/imx/imx-mipi-dsi.c b/drivers/gpu/drm/imx/imx-mipi-dsi.c
> [...]
>> +/*
>> + * i.MX drm driver - MIPI DSI Host Controller
>> + *
>> + * Copyright (C) 2011-2014 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/component.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
>> +#include <linux/module.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_device.h>
>> +#include <linux/regmap.h>
>> +#include <linux/videodev2.h>
>> +#include <asm/div64.h>
>
> Don't you want the more generic linux/math64.h here?

I'll use linux/math64.h.

>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_fb_helper.h>
>
> I don't see any of the functions defined in that header used here.

I'll remove this.

>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
>> +#include <video/mipi_display.h>
>> +#include <video/of_videomode.h>
>> +#include <video/videomode.h>
>> +
>> +#include "imx-drm.h"
>> +
>> +#define DRIVER_NAME 			"imx-mipi-dsi"
>> +
>> +#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)
>
> Your indentation here is... weird.

I'll fix this.

>
>> +#define IMX_MIPI_DSI_MAX_DATA_LANES	2
>> +
>> +#define PHY_STATUS_TIMEOUT		10
>> +#define CMD_PKT_STATUS_TIMEOUT		20
>> +
>> +#define IMX_MIPI_DSI_PLD_DATA_BUF_SIZE	4
>> +
>> +#define MHZ				1000000
>
> Why not reuse the existing USEC_PER_SEC?

Accepted.

>
>> +#define host_to_dsi(host) container_of(host, struct imx_mipi_dsi, dsi_host)
>> +#define con_to_dsi(x) container_of(x, struct imx_mipi_dsi, connector)
>> +#define enc_to_dsi(x) container_of(x, struct imx_mipi_dsi, encoder)
>
> These should really be static inline functions for proper type safety.

Ok, I'll change to use functions.

>
>> +struct imx_mipi_dsi {
>> +	struct mipi_dsi_host dsi_host;
>> +	struct drm_connector connector;
>> +	struct drm_encoder encoder;
>> +	struct device_node *panel_node;
>> +	struct drm_panel *panel;
>> +	struct device *dev;
>> +
>> +	struct regmap *regmap;
>> +	void __iomem *base;
>> +
>> +	struct clk *pllref_clk;
>> +	struct clk *pllref_gate_clk;
>> +	struct clk *cfg_clk;
>> +
>> +	unsigned int lane_mbps; /* per lane */
>> +	u32 channel;
>> +	u32 lanes;
>> +	u32 format;
>> +	struct videomode vm;
>> +
>> +	bool enabled;
>
> Do you really need this flag?

I'll remove this flag.

>
>> +};
>> +
>> +enum {
>> +	STATUS_TO_CLEAR,
>> +	STATUS_TO_SET,
>> +};
>> +
>> +struct dphy_pll_testdin_map {
>> +	int max_mbps;
>
> unsigned?

Accepted.

>
>> +	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 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 int format_to_bpp(struct imx_mipi_dsi *dsi)
>> +{
>> +	switch (dsi->format) {
>> +	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;
>> +	default:
>> +		dev_err(dsi->dev, "unsupported pixel format\n");
>> +		return -EINVAL;
>> +	}
>> +}
>
> Is there a reason why this can't be a standard MIPI DSI function?

How about moving this to include/drm/drm_mipi_dsi.h as an inline
function?

>
>> +
>> +static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
>> +			int timeout, bool to_set)
>> +{
>> +	u32 val;
>> +	bool out = false;
>> +
>> +	val = dsi_read(dsi, reg);
>> +	for (;;) {
>> +		out = to_set ? (val & status) : !(val & status);
>> +		if (out)
>> +			break;
>> +
>> +		if (!timeout--)
>> +			return -EFAULT;
>> +
>> +		msleep(1);
>> +		val = dsi_read(dsi, reg);
>> +	}
>> +	return 0;
>> +}
>
> You should probably use a properly timed loop here. msleep() isn't
> guaranteed to return after exactly one millisecond, so your timeout is
> never going to be accurate. Something like the following would be better
> in my opinion:
>
> 	timeout = jiffies + msecs_to_jiffies(timeout);
>
> 	while (time_before(jiffies, timeout)) {
> 		...
> 	}
>
> Also timeout should be unsigned long in that case.

Accepted.

>
>> +static int imx_mipi_dsi_host_attach(struct mipi_dsi_host *host,
>> +				    struct mipi_dsi_device *device)
>> +{
>> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
>> +	int ret;
>> +
>> +	if (device->lanes > IMX_MIPI_DSI_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;
>> +	}
>> +
>> +	dsi->lanes = device->lanes;
>> +	dsi->channel = device->channel;
>> +	dsi->format = device->format;
>> +	dsi->panel_node = device->dev.of_node;
>> +	of_get_videomode(dsi->panel_node, &dsi->vm, 0);
>
> No, you shouldn't use this. Query the mode from the panel instead. Or
> rather, encode this requirement in ->mode_valid() since that'll give you
> direct access to the mode.
>
> That way, ->host_attach() becomes a filter for compatible devices, yet
> devices may support multiple modes, therefore ->mode_valid() can be used
> to filter out those that don't work with your DSI host controller. There
> is a subtle difference here between devices that are compatible with the
> controller and modes that the controller doesn't support.

Accepted.

In the next version ->host_attach(), I will check the peripheral
compatibility.  Also, I'll get the panel by calling of_drm_find_panel(), 
and then call the drm_panel_attach() function.
Do you think this is okay?

>
>> +
>> +	ret = imx_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +static int imx_mipi_dsi_host_detach(struct mipi_dsi_host *host,
>> +				    struct mipi_dsi_device *device)
>> +{
>> +	struct imx_mipi_dsi *dsi = host_to_dsi(host);
>> +
>> +	dsi->panel_node = NULL;
>> +	dsi->panel = NULL;
>> +
>> +	return 0;
>> +}
>
> You'll want to detach from the panel here as well.

Ok. I'll call drm_panel_detach() here.

>
>> +
>> +static int imx_mipi_dsi_gen_pkt_hdr_write(struct imx_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 avaliable 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 imx_mipi_dsi_dcs_short_write(struct imx_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 imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
>> +}
>
> It seems like you would profit from converting to the newly introduced
> mipi_dsi_create_packet() helper which does most of the packing along
> with some sanity checking for you.

I looked at the helper. It seems it's not quite straightforward for me
to use it here.  The message type here defined by GEN_HTYPE() is 8bit
long, while the helper seems to take it as 6bit long?

>
>> +
>> +static int imx_mipi_dsi_dcs_long_write(struct imx_mipi_dsi *dsi,
>> +				       const struct mipi_dsi_msg *msg)
>> +{
>> +	const u32 *tx_buf = msg->tx_buf;
>> +	int len = msg->tx_len, ret;
>> +	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
>> +
>> +	if (msg->tx_len < 3) {
>> +		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
>> +			msg->tx_len);
>> +		return -EINVAL;
>> +	}
>> +
>> +	/* write the bulks */
>> +	while (len / IMX_MIPI_DSI_PLD_DATA_BUF_SIZE) {
>> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
>> +		tx_buf++;
>> +		len -= IMX_MIPI_DSI_PLD_DATA_BUF_SIZE;
>
> This is confusing. Maybe a better option would be to use sizeof(*tx_buf)
> instead of this macro. You're effectively consuming one u32 with each
> write, irrespective of what the value of the macro is.

Ok. I'll use sizeof(*tx_buf).

>
>> +		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 avaliable "
>> +					"write payload FIFO\n");
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	/* write the remainder */
>> +	if (len > 0) {
>> +		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 avaliable "
>> +					"write payload FIFO\n");
>> +			return ret;
>> +		}
>> +		dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
>> +	}
>
> This is really duplicating what the above loop does. Why can't you do it
> in a single loop? Also the transmission buffer isn't guaranteed to be a
> multiple of 4 bytes, so you could have the situation where len > 0 and
> len < 4 and yet you'll be reading 4 bytes by doing *tx_buf and accessing
> memory that you shouldn't.

Good catch.  I'll make it to be a single loop and avoid accessing memory
that I shouldn't in the next version.

>
>> +	return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
>> +}
>> +
>> +static ssize_t imx_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
>> +					  const struct mipi_dsi_msg *msg)
>> +{
>> +	struct imx_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 = imx_mipi_dsi_dcs_short_write(dsi, msg);
>> +		break;
>> +	case MIPI_DSI_DCS_LONG_WRITE:
>> +		ret = imx_mipi_dsi_dcs_long_write(dsi, msg);
>> +		break;
>> +	default:
>> +		dev_err(dsi->dev, "unsupported message type\n");
>> +		ret = -EFAULT;
>
> EFAULT really isn't appropriate here.

I'll change it to EINVAL.

>
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct mipi_dsi_host_ops imx_mipi_dsi_host_ops = {
>> +	.attach = imx_mipi_dsi_host_attach,
>> +	.detach = imx_mipi_dsi_host_detach,
>> +	.transfer = imx_mipi_dsi_host_transfer,
>> +};
>> +
>> +static enum drm_connector_status
>> +imx_mipi_dsi_detect(struct drm_connector *connector, bool force)
>> +{
>> +	struct imx_mipi_dsi *dsi = con_to_dsi(connector);
>> +
>> +	if (!dsi->panel) {
>> +		dsi->panel = of_drm_find_panel(dsi->panel_node);
>> +		if (dsi->panel)
>> +			drm_panel_attach(dsi->panel, &dsi->connector);
>> +	}
>> +
>> +	if (dsi->panel)
>> +		return connector_status_connected;
>> +
>> +	return connector_status_disconnected;
>> +
>> +}
>
> You really shouldn't be doing that here. of_drm_find_panel() returning
> NULL should be considered cause for deferring probe. I suspect that the
> driver doesn't really handle that very well at all, though, since if it
> did you would've run into the same issue that Benjamin ran into this
> morning.

I'll return connector_status_disconnected directly here. Is it okay?

>
>> +static void imx_mipi_dsi_encoder_prepare(struct drm_encoder *encoder)
>> +{
>> +	struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
>> +	u32 interface_pix_fmt;
>> +
>> +	switch (dsi->format) {
>> +	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:
>> +		dev_err(dsi->dev, "unsupported DSI pixel format\n");
>> +		return;
>
> Why even try doing this if you know upfront that it can't be done. You
> know much earlier than this that you can't drive the pixel format, why
> not abort then?
>
> People are much more likely to notice that the panel isn't supported if
> the DSI output doesn't even show up (or doesn't expose any modes) rather
> than if they have to find this error message in dmesg.

Accepted. I'll check the dsi format compatibility in ->host_attach().
And, how about changing the default patch to be this?

	default:
		BUG();
		return;
>
>> +static void imx_mipi_dsi_video_mode_config(struct imx_mipi_dsi *dsi)
>> +{
>> +	u32 val;
>> +
>> +	val = VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER;
>> +
>> +	dsi_write(dsi, DSI_VID_MODE_CFG, val);
>> +}
>
> You probably want to parameterize based on the DSI peripheral's flags. I
> see that you do reject devices with any other modes, so this may not be
> necessary now. Out of curiosity, the hardware supports other modes,
> right?

Yes, the hardware supports other modes according to it's register
definition.
I prefer not to parameterize at present as I do reject devices with 
other modes now.

>
> [...]
>> +MODULE_LICENSE("GPL v2");
>
> Sigh... according to your header comment this needs to be "GPL".

I'll change it to be "GPL".

Thanks,

Liu Ying

>
> Thierry
>

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 10/15] drm: panel: Add support for Himax HX8369A MIPI DSI panel
  2014-12-10 14:03     ` Thierry Reding
  (?)
@ 2014-12-17 10:27       ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-17 10:27 UTC (permalink / raw)
  To: Thierry Reding
  Cc: dri-devel, devicetree, linux-kernel, linux-arm-kernel, p.zabel,
	shawn.guo, kernel, linux, mturquette, airlied



On 12/10/2014 10:03 PM, Thierry Reding wrote:
> On Wed, Dec 10, 2014 at 04:37:23PM +0800, Liu Ying wrote:
>> This patch adds support for Himax HX8369A MIPI DSI panel.
>>
>> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
>> ---
>>   .../devicetree/bindings/panel/himax,hx8369a.txt    |  86 +++
>>   drivers/gpu/drm/panel/Kconfig                      |   6 +
>>   drivers/gpu/drm/panel/Makefile                     |   1 +
>>   drivers/gpu/drm/panel/panel-hx8369a.c              | 627 +++++++++++++++++++++
>>   4 files changed, 720 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>>   create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c
>>
>> diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>> new file mode 100644
>> index 0000000..6fe251e
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>> @@ -0,0 +1,86 @@
>> +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: "himax,hx8369a-dsi"
>> +  - reg: the virtual channel number of a DSI peripheral
>> +  - reset-gpios: a GPIO spec for the reset pin
>> +  - data-lanes: the data lane number of a DSI peripheral
>
> This is implied by the compatible already.

Accepted.

>
>> +  - display-timings: timings for the connected panel as described by [1]
>
> Also implied by the compatible value.

Accepted.

>
>> +  - bs: the interface mode number described by the following table
>> +        --------------------------
>> +       | DBI_TYPE_A_8BIT     |  0 |
>> +       | DBI_TYPE_A_9BIT     |  1 |
>> +       | DBI_TYPE_A_16BIT    |  2 |
>> +       | DBI_TYPE_A_18BIT    |  3 |
>> +       | DBI_TYPE_B_8BIT     |  4 |
>> +       | DBI_TYPE_B_9BIT     |  5 |
>> +       | DBI_TYPE_B_16BIT    |  6 |
>> +       | DBI_TYPE_B_18BIT    |  7 |
>> +       | DSI_CMD_MODE        |  8 |
>> +       | DBI_TYPE_B_24BIT    |  9 |
>> +       | DSI_VIDEO_MODE      | 10 |
>> +       | MDDI                | 11 |
>> +       | DPI_DBI_TYPE_C_OPT1 | 12 |
>> +       | DPI_DBI_TYPE_C_OPT2 | 13 |
>> +       | DPI_DBI_TYPE_C_OPT3 | 14 |
>> +        --------------------------
>
> Can this not be inferred by the driver? If it's a DSI driver can't it
> select between DSI_VIDEO_MODE or DSI_CMD_MODE based on its capabilities?
> That is, if the panel driver can setup command mode, shouldn't it be
> using command mode in that case? And use DSI_VIDEO_MODE otherwise?

I may remove this property.
But, I choose not to add any logic in the host and slave drivers to
handle the interface mode selection at present, since I only support
the DSI_VIDEO_MODE now. Is this acceptable?

>
>> +Optional properties:
>> +  - power-on-delay: delay after turning regulators on [ms]
>> +  - reset-delay: delay after reset sequence [ms]
>
> Surely these are constant for this panel?

Accepted.

>
>> +  - panel-width-mm: physical panel width [mm]
>> +  - panel-height-mm: physical panel height [mm]
>
> These are also implied by compatible.
>

Accepted.

>> +Example:
>> +	panel@0 {
>> +		compatible = "himax,hx8369a-dsi";
>> +		reg = <0>;
>> +		pinctrl-names = "default";
>> +		pinctrl-0 = <&pinctrl_mipi_panel>;
>> +		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
>> +		reset-delay = <120>;
>> +		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
>> +		data-lanes = <2>;
>> +		panel-width-mm = <45>;
>> +		panel-height-mm = <76>;
>> +		bs = <10>;
>> +		status = "okay";
>
> status = "okay" is the default, so it probably shouldn't be in this
> example.
>

Accepted.

>> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
>> index 024e98e..f1a5b58 100644
>> --- a/drivers/gpu/drm/panel/Kconfig
>> +++ b/drivers/gpu/drm/panel/Kconfig
>> @@ -40,4 +40,10 @@ config DRM_PANEL_SHARP_LQ101R1SX01
>>   	  To compile this driver as a module, choose M here: the module
>>   	  will be called panel-sharp-lq101r1sx01.
>>
>> +config DRM_PANEL_HX8369A
>> +	tristate "HX8369A panel"
>> +	depends on OF
>> +	select DRM_MIPI_DSI
>> +	select VIDEOMODE_HELPERS
>> +
>
> This should be sorted alphabetically. I think it would also be a good
> idea to use a HIMAX prefix here, just to reduce the potential for name
> clashes.

Accepted.

>
>> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
>> index 4b2a043..d6768ca 100644
>> --- a/drivers/gpu/drm/panel/Makefile
>> +++ b/drivers/gpu/drm/panel/Makefile
>> @@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.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
>> +obj-$(CONFIG_DRM_PANEL_HX8369A) += panel-hx8369a.o
>
> Needs to be sorted alphabetically, too.

Accepted.

>
>> diff --git a/drivers/gpu/drm/panel/panel-hx8369a.c b/drivers/gpu/drm/panel/panel-hx8369a.c
> [...]
>> +#include <drm/drmP.h>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
>> +
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/regulator/consumer.h>
>> +
>> +#include <video/mipi_display.h>
>> +#include <video/of_videomode.h>
>> +#include <video/videomode.h>
>> +
>> +#define SETCLUMN_ADDR	0x2a
>> +#define SETPAGE_ADDR 	0x2b
>> +#define SETPIXEL_FMT 	0x3a
>
> There are standard DCS functions for these now.

Accepted.

>
>> +#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
>
> Watch your indentation here and above. Use tabs and spaces more
> consistently.

Accepted.

>
>> +static bool hx8369a_is_dsi_interface(struct hx8369a *ctx) {
>
> Coding style: opening { goes on a line by itself.

Accepted.

>
>> +	if (ctx->mpu_interface == HX8369A_DSI_VIDEO_MODE ||
>> +	    ctx->mpu_interface == HX8369A_DSI_CMD_MODE)
>> +		return true;
>> +
>> +	return false;
>> +}
>> +
>> +static void hx8369a_dcs_write(struct hx8369a *ctx, 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, "error %zd writing dcs seq: %*ph\n", ret, len,
>> +			data);
>> +}
>
> You really shouldn't have based this on the Samsung drivers. You really
> should do proper error handling here. These error messages are going to
> be completely cryptic and very hard for people to look up in the driver
> as opposed to a simple specific error message like:
>
> 	dev_err(ctx->dev, "failed to do XYZ: %d\n", err);
>
> which will immediately let you look up the right location without a need
> to decode the hexdump and look for the correct array.

Accepted.

>
>> +#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, d, ARRAY_SIZE(d));\
>> +})
>> +
>> +#define hx8369a_dcs_write_seq_static(ctx, seq...) \
>> +({\
>> +	static const u8 d[] = { seq };\
>> +	hx8369a_dcs_write(ctx, d, ARRAY_SIZE(d));\
>> +})
>> +
>> +static void hx8369a_dsi_set_display_related_register(struct hx8369a *ctx)
>> +{
>> +	u8 sec_p = (ctx->res_sel << 4) | 0x03;
>> +
>> +	hx8369a_dcs_write_seq(ctx, SETDISP, 0x00, sec_p, 0x03,
>> +		0x03, 0x70, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
>> +		0x03, 0x03, 0x00, 0x01);
>> +}
>
> This and all of the other initialization functions below completely
> ignore any errors. Other than spamming the kernel log the user won't get
> any indication of anything going wrong.
>
>> +static void hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx)
>> +{
> [...]
>> +}
>> +
>> +static void hx8369a_dsi_set_column_address(struct hx8369a *ctx)
>> +{
> [...]
>> +}
>> +
>> +static void hx8369a_dsi_set_page_address(struct hx8369a *ctx)
>> +{
> [...]
>> +}
>
> We have standard functions for these, please use them.
>
>> +static void hx8369a_dsi_set_extension_command(struct hx8369a *ctx)
>> +{
>> +	hx8369a_dcs_write_seq_static(ctx, SETEXTC, 0xff, 0x83, 0x69);
>> +}
>
> What does this do exactly? It seems to be more than setting an extension
> command.

The panel data sheet tells that several commands rely on the SETEXTC,
such as SETMIPI, SETPANEL and SETCYC ..., which are used in this driver.

>
>> +static void 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);
>> +}
>> +
>> +static int hx8369a_dsi_set_sequence(struct hx8369a *ctx)
>> +{
>> +	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
>> +	int ret;
>> +
>> +	hx8369a_dsi_set_extension_command(ctx);
>> +	hx8369a_dsi_set_maximum_return_packet_size(ctx, 4);
>> +	hx8369a_dsi_panel_init(ctx);
>> +
>> +	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>> +	if (ret < 0) {
>> +		dev_err(ctx->dev, "failed to exit sleep mode: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = mipi_dsi_dcs_set_display_on(dsi);
>> +	if (ret < 0) {
>> +		dev_err(ctx->dev, "failed to set display on: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>
>> +static int hx8369a_dsi_disable(struct drm_panel *panel)
>> +{
>> +	return 0;
>> +}
> [...]
>> +static int hx8369a_dsi_enable(struct drm_panel *panel)
>> +{
>> +	return 0;
>> +}
>
> Doesn't this usually come with a backlight attached that you want to
> control here?

Accepted.

>
>> +static int hx8369a_get_modes(struct drm_panel *panel)
>> +{
>> +	struct drm_connector *connector = panel->connector;
>> +	struct hx8369a *ctx = panel_to_hx8369a(panel);
>> +	struct drm_display_mode *mode;
>> +
>> +	mode = drm_mode_create(connector->dev);
>> +	if (!mode) {
>> +		DRM_ERROR("failed to create a new display mode\n");
>> +		return 0;
>> +	}
>> +
>> +	drm_display_mode_from_videomode(&ctx->vm, mode);
>
> Like I've said before, the driver should hardcode the mode because it is
> implied by the compatible value.

Accepted.

>
>> +	mode->width_mm = ctx->width_mm;
>> +	mode->height_mm = ctx->height_mm;
>> +	connector->display_info.width_mm = mode->width_mm;
>> +	connector->display_info.height_mm = mode->height_mm;
>> +
>> +	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>> +	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_dsi_probe(struct mipi_dsi_device *dsi)
>> +{
>> +	struct device *dev = &dsi->dev;
>> +	struct hx8369a *ctx;
>> +	int ret, i;
>> +	char bs[4];
>> +
>> +	ctx = devm_kzalloc(dev, sizeof(struct hx8369a), GFP_KERNEL);
>
> I'd prefer sizeof(*ctx).

Accepted.

>
>> +	ctx->reset_gpio = devm_gpiod_get(dev, "reset");
>> +	if (IS_ERR(ctx->reset_gpio)) {
>> +		dev_err(dev, "cannot get reset-gpios %ld\n",
>> +			PTR_ERR(ctx->reset_gpio));
>> +		return PTR_ERR(ctx->reset_gpio);
>> +	}
>> +	ret = gpiod_direction_output(ctx->reset_gpio, 0);
>> +	if (ret < 0) {
>> +		dev_err(dev, "cannot configure reset-gpios %d\n", ret);
>> +		return ret;
>> +	}
>
> I think you're supposed to combine this into something like:
>
> 	ret = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
>
> nowadays.

Accepted.

>
>> +
>> +	for (i = 0; i < 4; i++) {
>> +		snprintf(bs, sizeof(bs), "bs%d", i);
>> +		ctx->bs_gpio[i] = devm_gpiod_get(dev, bs);
>> +		if (IS_ERR(ctx->bs_gpio[i]))
>> +			continue;
>> +
>> +		ret = gpiod_direction_output(ctx->bs_gpio[i], 1);
>> +		if (ret < 0) {
>> +			dev_err(dev, "cannot configure bs%d-gpio %d\n", i, ret);
>> +			return ret;
>> +		}
>
> Similarly to the above:
>
> 	ret = devm_gpiod_get(dev, bs, GPIOD_OUT_HIGH);
>
>> +static int __init hx8369a_init(void)
>> +{
>> +	int err;
>> +
>> +	err = mipi_dsi_driver_register(&hx8369a_dsi_driver);
>> +	if (err < 0)
>> +		return err;
>> +
>> +	return 0;
>> +}
>> +module_init(hx8369a_init);
>> +
>> +static void __exit hx8369a_exit(void)
>> +{
>> +	mipi_dsi_driver_unregister(&hx8369a_dsi_driver);
>> +}
>> +module_exit(hx8369a_exit);
>
> Why can't this be module_mipi_dsi_driver(&hx8369a_dsi_driver)?

Like the panel-simple driver. I thought that we probably may add other
driver registers here. But, since the driver only supports DSI now,
I may change to use module_mipi_dsi_driver().

>
>> +
>> +MODULE_DESCRIPTION("Himax HX8369A panel driver");
>> +MODULE_LICENSE("GPL v2");
>
> Missing MODULE_AUTHOR()?

I'll add it.


Thanks,

Liu Ying

>
> Thierry
>

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 10/15] drm: panel: Add support for Himax HX8369A MIPI DSI panel
@ 2014-12-17 10:27       ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-17 10:27 UTC (permalink / raw)
  To: Thierry Reding
  Cc: devicetree, linux, kernel, linux-kernel, dri-devel, mturquette,
	linux-arm-kernel



On 12/10/2014 10:03 PM, Thierry Reding wrote:
> On Wed, Dec 10, 2014 at 04:37:23PM +0800, Liu Ying wrote:
>> This patch adds support for Himax HX8369A MIPI DSI panel.
>>
>> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
>> ---
>>   .../devicetree/bindings/panel/himax,hx8369a.txt    |  86 +++
>>   drivers/gpu/drm/panel/Kconfig                      |   6 +
>>   drivers/gpu/drm/panel/Makefile                     |   1 +
>>   drivers/gpu/drm/panel/panel-hx8369a.c              | 627 +++++++++++++++++++++
>>   4 files changed, 720 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>>   create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c
>>
>> diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>> new file mode 100644
>> index 0000000..6fe251e
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>> @@ -0,0 +1,86 @@
>> +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: "himax,hx8369a-dsi"
>> +  - reg: the virtual channel number of a DSI peripheral
>> +  - reset-gpios: a GPIO spec for the reset pin
>> +  - data-lanes: the data lane number of a DSI peripheral
>
> This is implied by the compatible already.

Accepted.

>
>> +  - display-timings: timings for the connected panel as described by [1]
>
> Also implied by the compatible value.

Accepted.

>
>> +  - bs: the interface mode number described by the following table
>> +        --------------------------
>> +       | DBI_TYPE_A_8BIT     |  0 |
>> +       | DBI_TYPE_A_9BIT     |  1 |
>> +       | DBI_TYPE_A_16BIT    |  2 |
>> +       | DBI_TYPE_A_18BIT    |  3 |
>> +       | DBI_TYPE_B_8BIT     |  4 |
>> +       | DBI_TYPE_B_9BIT     |  5 |
>> +       | DBI_TYPE_B_16BIT    |  6 |
>> +       | DBI_TYPE_B_18BIT    |  7 |
>> +       | DSI_CMD_MODE        |  8 |
>> +       | DBI_TYPE_B_24BIT    |  9 |
>> +       | DSI_VIDEO_MODE      | 10 |
>> +       | MDDI                | 11 |
>> +       | DPI_DBI_TYPE_C_OPT1 | 12 |
>> +       | DPI_DBI_TYPE_C_OPT2 | 13 |
>> +       | DPI_DBI_TYPE_C_OPT3 | 14 |
>> +        --------------------------
>
> Can this not be inferred by the driver? If it's a DSI driver can't it
> select between DSI_VIDEO_MODE or DSI_CMD_MODE based on its capabilities?
> That is, if the panel driver can setup command mode, shouldn't it be
> using command mode in that case? And use DSI_VIDEO_MODE otherwise?

I may remove this property.
But, I choose not to add any logic in the host and slave drivers to
handle the interface mode selection at present, since I only support
the DSI_VIDEO_MODE now. Is this acceptable?

>
>> +Optional properties:
>> +  - power-on-delay: delay after turning regulators on [ms]
>> +  - reset-delay: delay after reset sequence [ms]
>
> Surely these are constant for this panel?

Accepted.

>
>> +  - panel-width-mm: physical panel width [mm]
>> +  - panel-height-mm: physical panel height [mm]
>
> These are also implied by compatible.
>

Accepted.

>> +Example:
>> +	panel@0 {
>> +		compatible = "himax,hx8369a-dsi";
>> +		reg = <0>;
>> +		pinctrl-names = "default";
>> +		pinctrl-0 = <&pinctrl_mipi_panel>;
>> +		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
>> +		reset-delay = <120>;
>> +		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
>> +		data-lanes = <2>;
>> +		panel-width-mm = <45>;
>> +		panel-height-mm = <76>;
>> +		bs = <10>;
>> +		status = "okay";
>
> status = "okay" is the default, so it probably shouldn't be in this
> example.
>

Accepted.

>> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
>> index 024e98e..f1a5b58 100644
>> --- a/drivers/gpu/drm/panel/Kconfig
>> +++ b/drivers/gpu/drm/panel/Kconfig
>> @@ -40,4 +40,10 @@ config DRM_PANEL_SHARP_LQ101R1SX01
>>   	  To compile this driver as a module, choose M here: the module
>>   	  will be called panel-sharp-lq101r1sx01.
>>
>> +config DRM_PANEL_HX8369A
>> +	tristate "HX8369A panel"
>> +	depends on OF
>> +	select DRM_MIPI_DSI
>> +	select VIDEOMODE_HELPERS
>> +
>
> This should be sorted alphabetically. I think it would also be a good
> idea to use a HIMAX prefix here, just to reduce the potential for name
> clashes.

Accepted.

>
>> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
>> index 4b2a043..d6768ca 100644
>> --- a/drivers/gpu/drm/panel/Makefile
>> +++ b/drivers/gpu/drm/panel/Makefile
>> @@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.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
>> +obj-$(CONFIG_DRM_PANEL_HX8369A) += panel-hx8369a.o
>
> Needs to be sorted alphabetically, too.

Accepted.

>
>> diff --git a/drivers/gpu/drm/panel/panel-hx8369a.c b/drivers/gpu/drm/panel/panel-hx8369a.c
> [...]
>> +#include <drm/drmP.h>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
>> +
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/regulator/consumer.h>
>> +
>> +#include <video/mipi_display.h>
>> +#include <video/of_videomode.h>
>> +#include <video/videomode.h>
>> +
>> +#define SETCLUMN_ADDR	0x2a
>> +#define SETPAGE_ADDR 	0x2b
>> +#define SETPIXEL_FMT 	0x3a
>
> There are standard DCS functions for these now.

Accepted.

>
>> +#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
>
> Watch your indentation here and above. Use tabs and spaces more
> consistently.

Accepted.

>
>> +static bool hx8369a_is_dsi_interface(struct hx8369a *ctx) {
>
> Coding style: opening { goes on a line by itself.

Accepted.

>
>> +	if (ctx->mpu_interface == HX8369A_DSI_VIDEO_MODE ||
>> +	    ctx->mpu_interface == HX8369A_DSI_CMD_MODE)
>> +		return true;
>> +
>> +	return false;
>> +}
>> +
>> +static void hx8369a_dcs_write(struct hx8369a *ctx, 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, "error %zd writing dcs seq: %*ph\n", ret, len,
>> +			data);
>> +}
>
> You really shouldn't have based this on the Samsung drivers. You really
> should do proper error handling here. These error messages are going to
> be completely cryptic and very hard for people to look up in the driver
> as opposed to a simple specific error message like:
>
> 	dev_err(ctx->dev, "failed to do XYZ: %d\n", err);
>
> which will immediately let you look up the right location without a need
> to decode the hexdump and look for the correct array.

Accepted.

>
>> +#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, d, ARRAY_SIZE(d));\
>> +})
>> +
>> +#define hx8369a_dcs_write_seq_static(ctx, seq...) \
>> +({\
>> +	static const u8 d[] = { seq };\
>> +	hx8369a_dcs_write(ctx, d, ARRAY_SIZE(d));\
>> +})
>> +
>> +static void hx8369a_dsi_set_display_related_register(struct hx8369a *ctx)
>> +{
>> +	u8 sec_p = (ctx->res_sel << 4) | 0x03;
>> +
>> +	hx8369a_dcs_write_seq(ctx, SETDISP, 0x00, sec_p, 0x03,
>> +		0x03, 0x70, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
>> +		0x03, 0x03, 0x00, 0x01);
>> +}
>
> This and all of the other initialization functions below completely
> ignore any errors. Other than spamming the kernel log the user won't get
> any indication of anything going wrong.
>
>> +static void hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx)
>> +{
> [...]
>> +}
>> +
>> +static void hx8369a_dsi_set_column_address(struct hx8369a *ctx)
>> +{
> [...]
>> +}
>> +
>> +static void hx8369a_dsi_set_page_address(struct hx8369a *ctx)
>> +{
> [...]
>> +}
>
> We have standard functions for these, please use them.
>
>> +static void hx8369a_dsi_set_extension_command(struct hx8369a *ctx)
>> +{
>> +	hx8369a_dcs_write_seq_static(ctx, SETEXTC, 0xff, 0x83, 0x69);
>> +}
>
> What does this do exactly? It seems to be more than setting an extension
> command.

The panel data sheet tells that several commands rely on the SETEXTC,
such as SETMIPI, SETPANEL and SETCYC ..., which are used in this driver.

>
>> +static void 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);
>> +}
>> +
>> +static int hx8369a_dsi_set_sequence(struct hx8369a *ctx)
>> +{
>> +	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
>> +	int ret;
>> +
>> +	hx8369a_dsi_set_extension_command(ctx);
>> +	hx8369a_dsi_set_maximum_return_packet_size(ctx, 4);
>> +	hx8369a_dsi_panel_init(ctx);
>> +
>> +	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>> +	if (ret < 0) {
>> +		dev_err(ctx->dev, "failed to exit sleep mode: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = mipi_dsi_dcs_set_display_on(dsi);
>> +	if (ret < 0) {
>> +		dev_err(ctx->dev, "failed to set display on: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>
>> +static int hx8369a_dsi_disable(struct drm_panel *panel)
>> +{
>> +	return 0;
>> +}
> [...]
>> +static int hx8369a_dsi_enable(struct drm_panel *panel)
>> +{
>> +	return 0;
>> +}
>
> Doesn't this usually come with a backlight attached that you want to
> control here?

Accepted.

>
>> +static int hx8369a_get_modes(struct drm_panel *panel)
>> +{
>> +	struct drm_connector *connector = panel->connector;
>> +	struct hx8369a *ctx = panel_to_hx8369a(panel);
>> +	struct drm_display_mode *mode;
>> +
>> +	mode = drm_mode_create(connector->dev);
>> +	if (!mode) {
>> +		DRM_ERROR("failed to create a new display mode\n");
>> +		return 0;
>> +	}
>> +
>> +	drm_display_mode_from_videomode(&ctx->vm, mode);
>
> Like I've said before, the driver should hardcode the mode because it is
> implied by the compatible value.

Accepted.

>
>> +	mode->width_mm = ctx->width_mm;
>> +	mode->height_mm = ctx->height_mm;
>> +	connector->display_info.width_mm = mode->width_mm;
>> +	connector->display_info.height_mm = mode->height_mm;
>> +
>> +	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>> +	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_dsi_probe(struct mipi_dsi_device *dsi)
>> +{
>> +	struct device *dev = &dsi->dev;
>> +	struct hx8369a *ctx;
>> +	int ret, i;
>> +	char bs[4];
>> +
>> +	ctx = devm_kzalloc(dev, sizeof(struct hx8369a), GFP_KERNEL);
>
> I'd prefer sizeof(*ctx).

Accepted.

>
>> +	ctx->reset_gpio = devm_gpiod_get(dev, "reset");
>> +	if (IS_ERR(ctx->reset_gpio)) {
>> +		dev_err(dev, "cannot get reset-gpios %ld\n",
>> +			PTR_ERR(ctx->reset_gpio));
>> +		return PTR_ERR(ctx->reset_gpio);
>> +	}
>> +	ret = gpiod_direction_output(ctx->reset_gpio, 0);
>> +	if (ret < 0) {
>> +		dev_err(dev, "cannot configure reset-gpios %d\n", ret);
>> +		return ret;
>> +	}
>
> I think you're supposed to combine this into something like:
>
> 	ret = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
>
> nowadays.

Accepted.

>
>> +
>> +	for (i = 0; i < 4; i++) {
>> +		snprintf(bs, sizeof(bs), "bs%d", i);
>> +		ctx->bs_gpio[i] = devm_gpiod_get(dev, bs);
>> +		if (IS_ERR(ctx->bs_gpio[i]))
>> +			continue;
>> +
>> +		ret = gpiod_direction_output(ctx->bs_gpio[i], 1);
>> +		if (ret < 0) {
>> +			dev_err(dev, "cannot configure bs%d-gpio %d\n", i, ret);
>> +			return ret;
>> +		}
>
> Similarly to the above:
>
> 	ret = devm_gpiod_get(dev, bs, GPIOD_OUT_HIGH);
>
>> +static int __init hx8369a_init(void)
>> +{
>> +	int err;
>> +
>> +	err = mipi_dsi_driver_register(&hx8369a_dsi_driver);
>> +	if (err < 0)
>> +		return err;
>> +
>> +	return 0;
>> +}
>> +module_init(hx8369a_init);
>> +
>> +static void __exit hx8369a_exit(void)
>> +{
>> +	mipi_dsi_driver_unregister(&hx8369a_dsi_driver);
>> +}
>> +module_exit(hx8369a_exit);
>
> Why can't this be module_mipi_dsi_driver(&hx8369a_dsi_driver)?

Like the panel-simple driver. I thought that we probably may add other
driver registers here. But, since the driver only supports DSI now,
I may change to use module_mipi_dsi_driver().

>
>> +
>> +MODULE_DESCRIPTION("Himax HX8369A panel driver");
>> +MODULE_LICENSE("GPL v2");
>
> Missing MODULE_AUTHOR()?

I'll add it.


Thanks,

Liu Ying

>
> Thierry
>
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 10/15] drm: panel: Add support for Himax HX8369A MIPI DSI panel
@ 2014-12-17 10:27       ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-17 10:27 UTC (permalink / raw)
  To: linux-arm-kernel



On 12/10/2014 10:03 PM, Thierry Reding wrote:
> On Wed, Dec 10, 2014 at 04:37:23PM +0800, Liu Ying wrote:
>> This patch adds support for Himax HX8369A MIPI DSI panel.
>>
>> Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
>> ---
>>   .../devicetree/bindings/panel/himax,hx8369a.txt    |  86 +++
>>   drivers/gpu/drm/panel/Kconfig                      |   6 +
>>   drivers/gpu/drm/panel/Makefile                     |   1 +
>>   drivers/gpu/drm/panel/panel-hx8369a.c              | 627 +++++++++++++++++++++
>>   4 files changed, 720 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>>   create mode 100644 drivers/gpu/drm/panel/panel-hx8369a.c
>>
>> diff --git a/Documentation/devicetree/bindings/panel/himax,hx8369a.txt b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>> new file mode 100644
>> index 0000000..6fe251e
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/panel/himax,hx8369a.txt
>> @@ -0,0 +1,86 @@
>> +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: "himax,hx8369a-dsi"
>> +  - reg: the virtual channel number of a DSI peripheral
>> +  - reset-gpios: a GPIO spec for the reset pin
>> +  - data-lanes: the data lane number of a DSI peripheral
>
> This is implied by the compatible already.

Accepted.

>
>> +  - display-timings: timings for the connected panel as described by [1]
>
> Also implied by the compatible value.

Accepted.

>
>> +  - bs: the interface mode number described by the following table
>> +        --------------------------
>> +       | DBI_TYPE_A_8BIT     |  0 |
>> +       | DBI_TYPE_A_9BIT     |  1 |
>> +       | DBI_TYPE_A_16BIT    |  2 |
>> +       | DBI_TYPE_A_18BIT    |  3 |
>> +       | DBI_TYPE_B_8BIT     |  4 |
>> +       | DBI_TYPE_B_9BIT     |  5 |
>> +       | DBI_TYPE_B_16BIT    |  6 |
>> +       | DBI_TYPE_B_18BIT    |  7 |
>> +       | DSI_CMD_MODE        |  8 |
>> +       | DBI_TYPE_B_24BIT    |  9 |
>> +       | DSI_VIDEO_MODE      | 10 |
>> +       | MDDI                | 11 |
>> +       | DPI_DBI_TYPE_C_OPT1 | 12 |
>> +       | DPI_DBI_TYPE_C_OPT2 | 13 |
>> +       | DPI_DBI_TYPE_C_OPT3 | 14 |
>> +        --------------------------
>
> Can this not be inferred by the driver? If it's a DSI driver can't it
> select between DSI_VIDEO_MODE or DSI_CMD_MODE based on its capabilities?
> That is, if the panel driver can setup command mode, shouldn't it be
> using command mode in that case? And use DSI_VIDEO_MODE otherwise?

I may remove this property.
But, I choose not to add any logic in the host and slave drivers to
handle the interface mode selection at present, since I only support
the DSI_VIDEO_MODE now. Is this acceptable?

>
>> +Optional properties:
>> +  - power-on-delay: delay after turning regulators on [ms]
>> +  - reset-delay: delay after reset sequence [ms]
>
> Surely these are constant for this panel?

Accepted.

>
>> +  - panel-width-mm: physical panel width [mm]
>> +  - panel-height-mm: physical panel height [mm]
>
> These are also implied by compatible.
>

Accepted.

>> +Example:
>> +	panel at 0 {
>> +		compatible = "himax,hx8369a-dsi";
>> +		reg = <0>;
>> +		pinctrl-names = "default";
>> +		pinctrl-0 = <&pinctrl_mipi_panel>;
>> +		reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
>> +		reset-delay = <120>;
>> +		bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
>> +		data-lanes = <2>;
>> +		panel-width-mm = <45>;
>> +		panel-height-mm = <76>;
>> +		bs = <10>;
>> +		status = "okay";
>
> status = "okay" is the default, so it probably shouldn't be in this
> example.
>

Accepted.

>> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
>> index 024e98e..f1a5b58 100644
>> --- a/drivers/gpu/drm/panel/Kconfig
>> +++ b/drivers/gpu/drm/panel/Kconfig
>> @@ -40,4 +40,10 @@ config DRM_PANEL_SHARP_LQ101R1SX01
>>   	  To compile this driver as a module, choose M here: the module
>>   	  will be called panel-sharp-lq101r1sx01.
>>
>> +config DRM_PANEL_HX8369A
>> +	tristate "HX8369A panel"
>> +	depends on OF
>> +	select DRM_MIPI_DSI
>> +	select VIDEOMODE_HELPERS
>> +
>
> This should be sorted alphabetically. I think it would also be a good
> idea to use a HIMAX prefix here, just to reduce the potential for name
> clashes.

Accepted.

>
>> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
>> index 4b2a043..d6768ca 100644
>> --- a/drivers/gpu/drm/panel/Makefile
>> +++ b/drivers/gpu/drm/panel/Makefile
>> @@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.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
>> +obj-$(CONFIG_DRM_PANEL_HX8369A) += panel-hx8369a.o
>
> Needs to be sorted alphabetically, too.

Accepted.

>
>> diff --git a/drivers/gpu/drm/panel/panel-hx8369a.c b/drivers/gpu/drm/panel/panel-hx8369a.c
> [...]
>> +#include <drm/drmP.h>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
>> +
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/regulator/consumer.h>
>> +
>> +#include <video/mipi_display.h>
>> +#include <video/of_videomode.h>
>> +#include <video/videomode.h>
>> +
>> +#define SETCLUMN_ADDR	0x2a
>> +#define SETPAGE_ADDR 	0x2b
>> +#define SETPIXEL_FMT 	0x3a
>
> There are standard DCS functions for these now.

Accepted.

>
>> +#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
>
> Watch your indentation here and above. Use tabs and spaces more
> consistently.

Accepted.

>
>> +static bool hx8369a_is_dsi_interface(struct hx8369a *ctx) {
>
> Coding style: opening { goes on a line by itself.

Accepted.

>
>> +	if (ctx->mpu_interface == HX8369A_DSI_VIDEO_MODE ||
>> +	    ctx->mpu_interface == HX8369A_DSI_CMD_MODE)
>> +		return true;
>> +
>> +	return false;
>> +}
>> +
>> +static void hx8369a_dcs_write(struct hx8369a *ctx, 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, "error %zd writing dcs seq: %*ph\n", ret, len,
>> +			data);
>> +}
>
> You really shouldn't have based this on the Samsung drivers. You really
> should do proper error handling here. These error messages are going to
> be completely cryptic and very hard for people to look up in the driver
> as opposed to a simple specific error message like:
>
> 	dev_err(ctx->dev, "failed to do XYZ: %d\n", err);
>
> which will immediately let you look up the right location without a need
> to decode the hexdump and look for the correct array.

Accepted.

>
>> +#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, d, ARRAY_SIZE(d));\
>> +})
>> +
>> +#define hx8369a_dcs_write_seq_static(ctx, seq...) \
>> +({\
>> +	static const u8 d[] = { seq };\
>> +	hx8369a_dcs_write(ctx, d, ARRAY_SIZE(d));\
>> +})
>> +
>> +static void hx8369a_dsi_set_display_related_register(struct hx8369a *ctx)
>> +{
>> +	u8 sec_p = (ctx->res_sel << 4) | 0x03;
>> +
>> +	hx8369a_dcs_write_seq(ctx, SETDISP, 0x00, sec_p, 0x03,
>> +		0x03, 0x70, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
>> +		0x03, 0x03, 0x00, 0x01);
>> +}
>
> This and all of the other initialization functions below completely
> ignore any errors. Other than spamming the kernel log the user won't get
> any indication of anything going wrong.
>
>> +static void hx8369a_dsi_set_interface_pixel_fomat(struct hx8369a *ctx)
>> +{
> [...]
>> +}
>> +
>> +static void hx8369a_dsi_set_column_address(struct hx8369a *ctx)
>> +{
> [...]
>> +}
>> +
>> +static void hx8369a_dsi_set_page_address(struct hx8369a *ctx)
>> +{
> [...]
>> +}
>
> We have standard functions for these, please use them.
>
>> +static void hx8369a_dsi_set_extension_command(struct hx8369a *ctx)
>> +{
>> +	hx8369a_dcs_write_seq_static(ctx, SETEXTC, 0xff, 0x83, 0x69);
>> +}
>
> What does this do exactly? It seems to be more than setting an extension
> command.

The panel data sheet tells that several commands rely on the SETEXTC,
such as SETMIPI, SETPANEL and SETCYC ..., which are used in this driver.

>
>> +static void 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);
>> +}
>> +
>> +static int hx8369a_dsi_set_sequence(struct hx8369a *ctx)
>> +{
>> +	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
>> +	int ret;
>> +
>> +	hx8369a_dsi_set_extension_command(ctx);
>> +	hx8369a_dsi_set_maximum_return_packet_size(ctx, 4);
>> +	hx8369a_dsi_panel_init(ctx);
>> +
>> +	ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
>> +	if (ret < 0) {
>> +		dev_err(ctx->dev, "failed to exit sleep mode: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = mipi_dsi_dcs_set_display_on(dsi);
>> +	if (ret < 0) {
>> +		dev_err(ctx->dev, "failed to set display on: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>
>> +static int hx8369a_dsi_disable(struct drm_panel *panel)
>> +{
>> +	return 0;
>> +}
> [...]
>> +static int hx8369a_dsi_enable(struct drm_panel *panel)
>> +{
>> +	return 0;
>> +}
>
> Doesn't this usually come with a backlight attached that you want to
> control here?

Accepted.

>
>> +static int hx8369a_get_modes(struct drm_panel *panel)
>> +{
>> +	struct drm_connector *connector = panel->connector;
>> +	struct hx8369a *ctx = panel_to_hx8369a(panel);
>> +	struct drm_display_mode *mode;
>> +
>> +	mode = drm_mode_create(connector->dev);
>> +	if (!mode) {
>> +		DRM_ERROR("failed to create a new display mode\n");
>> +		return 0;
>> +	}
>> +
>> +	drm_display_mode_from_videomode(&ctx->vm, mode);
>
> Like I've said before, the driver should hardcode the mode because it is
> implied by the compatible value.

Accepted.

>
>> +	mode->width_mm = ctx->width_mm;
>> +	mode->height_mm = ctx->height_mm;
>> +	connector->display_info.width_mm = mode->width_mm;
>> +	connector->display_info.height_mm = mode->height_mm;
>> +
>> +	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
>> +	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_dsi_probe(struct mipi_dsi_device *dsi)
>> +{
>> +	struct device *dev = &dsi->dev;
>> +	struct hx8369a *ctx;
>> +	int ret, i;
>> +	char bs[4];
>> +
>> +	ctx = devm_kzalloc(dev, sizeof(struct hx8369a), GFP_KERNEL);
>
> I'd prefer sizeof(*ctx).

Accepted.

>
>> +	ctx->reset_gpio = devm_gpiod_get(dev, "reset");
>> +	if (IS_ERR(ctx->reset_gpio)) {
>> +		dev_err(dev, "cannot get reset-gpios %ld\n",
>> +			PTR_ERR(ctx->reset_gpio));
>> +		return PTR_ERR(ctx->reset_gpio);
>> +	}
>> +	ret = gpiod_direction_output(ctx->reset_gpio, 0);
>> +	if (ret < 0) {
>> +		dev_err(dev, "cannot configure reset-gpios %d\n", ret);
>> +		return ret;
>> +	}
>
> I think you're supposed to combine this into something like:
>
> 	ret = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
>
> nowadays.

Accepted.

>
>> +
>> +	for (i = 0; i < 4; i++) {
>> +		snprintf(bs, sizeof(bs), "bs%d", i);
>> +		ctx->bs_gpio[i] = devm_gpiod_get(dev, bs);
>> +		if (IS_ERR(ctx->bs_gpio[i]))
>> +			continue;
>> +
>> +		ret = gpiod_direction_output(ctx->bs_gpio[i], 1);
>> +		if (ret < 0) {
>> +			dev_err(dev, "cannot configure bs%d-gpio %d\n", i, ret);
>> +			return ret;
>> +		}
>
> Similarly to the above:
>
> 	ret = devm_gpiod_get(dev, bs, GPIOD_OUT_HIGH);
>
>> +static int __init hx8369a_init(void)
>> +{
>> +	int err;
>> +
>> +	err = mipi_dsi_driver_register(&hx8369a_dsi_driver);
>> +	if (err < 0)
>> +		return err;
>> +
>> +	return 0;
>> +}
>> +module_init(hx8369a_init);
>> +
>> +static void __exit hx8369a_exit(void)
>> +{
>> +	mipi_dsi_driver_unregister(&hx8369a_dsi_driver);
>> +}
>> +module_exit(hx8369a_exit);
>
> Why can't this be module_mipi_dsi_driver(&hx8369a_dsi_driver)?

Like the panel-simple driver. I thought that we probably may add other
driver registers here. But, since the driver only supports DSI now,
I may change to use module_mipi_dsi_driver().

>
>> +
>> +MODULE_DESCRIPTION("Himax HX8369A panel driver");
>> +MODULE_LICENSE("GPL v2");
>
> Missing MODULE_AUTHOR()?

I'll add it.


Thanks,

Liu Ying

>
> Thierry
>

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 12/15] ARM: dts: imx6qdl-sabresd: Add support for TRULY TFT480800-16-E MIPI DSI panel
  2014-12-10 14:07     ` Thierry Reding
  (?)
  (?)
@ 2014-12-17 10:35     ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-17 10:35 UTC (permalink / raw)
  To: Thierry Reding
  Cc: devicetree, Russell King - ARM Linux, linux-kernel, dri-devel,
	kernel, mturquette, linux-arm-kernel


[-- Attachment #1.1: Type: text/plain, Size: 1918 bytes --]

2014-12-10 22:07 GMT+08:00 Thierry Reding <thierry.reding@gmail.com>:
>
> On Wed, Dec 10, 2014 at 04:37:25PM +0800, Liu Ying wrote:
> [...]
> > diff --git a/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
> b/arch/arm/boot/dts/imx6qdl-sabresd.dtsi
> [...]
> > +&mipi_dsi {
> > +     status = "okay";
> > +
> > +     panel@0 {
> > +             compatible = "himax,hx8369a-dsi";
> > +             reg = <0>;
> > +             pinctrl-names = "default";
> > +             pinctrl-0 = <&pinctrl_mipi_panel>;
> > +             reset-gpios = <&gpio6 11 GPIO_ACTIVE_LOW>;
> > +             reset-delay = <120>;
> > +             bs2-gpios = <&gpio6 14 GPIO_ACTIVE_HIGH>;
> > +             data-lanes = <2>;
> > +             panel-width-mm = <45>;
> > +             panel-height-mm = <76>;
> > +             bs = <10>;
> > +             status = "okay";
> > +
> > +             display-timings {
> > +                     native-mode = <&timing1>;
> > +                     timing1: truly-tft480800-16-e {
>
> This is the only place where Truly is mentioned. The panel vendor is
> either Truly or it is Himax, it can't be a mix. From this example and
> your patch description it seems like Truly is the manufacturer of the
> panel, hence the panel compatible should really be:
>
>         compatible = "truly,tft480800-16-e";
>

May I change the compatible string to be '"truly,tft480800-16-e-dsi"?
This way, the dsi driver may match the dsi device.


>
> That it uses an HX8369A chip internally is really secondary, at least
> regarding the binding. If we ever get need to support multiple panels
> with the same driver IC the proper solution would be to abstract that
> code out into a helper library that is used from the various panel
> drivers.
>
> Thierry
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
>

[-- Attachment #1.2: Type: text/html, Size: 3055 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
  2014-12-17  9:44       ` Liu Ying
  (?)
@ 2014-12-17 10:40         ` Russell King - ARM Linux
  -1 siblings, 0 replies; 79+ messages in thread
From: Russell King - ARM Linux @ 2014-12-17 10:40 UTC (permalink / raw)
  To: Liu Ying
  Cc: Thierry Reding, dri-devel, devicetree, linux-kernel,
	linux-arm-kernel, p.zabel, shawn.guo, kernel, mturquette,
	airlied

On Wed, Dec 17, 2014 at 05:44:33PM +0800, Liu Ying wrote:
> Hi Thierry,
> 
> Sorry for the late response.
> I tried to address almost all your comments locally first.
> More feedback below.
> 
> On 12/10/2014 09:16 PM, Thierry Reding wrote:
> >On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
> >>+static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
> >>+			int timeout, bool to_set)
> >>+{
> >>+	u32 val;
> >>+	bool out = false;
> >>+
> >>+	val = dsi_read(dsi, reg);
> >>+	for (;;) {
> >>+		out = to_set ? (val & status) : !(val & status);
> >>+		if (out)
> >>+			break;
> >>+
> >>+		if (!timeout--)
> >>+			return -EFAULT;
> >>+
> >>+		msleep(1);
> >>+		val = dsi_read(dsi, reg);
> >>+	}
> >>+	return 0;
> >>+}
> >
> >You should probably use a properly timed loop here. msleep() isn't
> >guaranteed to return after exactly one millisecond, so your timeout is
> >never going to be accurate. Something like the following would be better
> >in my opinion:
> >
> >	timeout = jiffies + msecs_to_jiffies(timeout);
> >
> >	while (time_before(jiffies, timeout)) {
> >		...
> >	}
> >
> >Also timeout should be unsigned long in that case.
> 
> Accepted.

Actually, that's a bad example: what we want to do is to assess success
after we wait, before we decide that something has failed.  In other
words, we don't want to wait, and decide that we failed without first
checking for success.

In any case, returning -EFAULT is not sane: EFAULT doesn't mean "fault"
it means "Bad address", and it is returned to userspace to mean that
userspace passed the kernel a bad address.  That definition does /not/
fit what's going on here.

	timeout = jiffies + msecs_to_jiffies(timeout);

	do {
		val = dsi_read(dsi, reg);
		out = to_set ? (val & status) : !(val & status);
		if (out)
			break;

		if (time_is_after_jiffies(timeout))
			return -ETIMEDOUT;

		msleep(1);
	} while (1);

	return 0;

would be better: we only fail immediately after we have checked whether
we succeeded, and we also do the first check immediately.

-- 
FTTC broadband for 0.8mile line: currently at 9.5Mbps down 400kbps up
according to speedtest.net.

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
@ 2014-12-17 10:40         ` Russell King - ARM Linux
  0 siblings, 0 replies; 79+ messages in thread
From: Russell King - ARM Linux @ 2014-12-17 10:40 UTC (permalink / raw)
  To: Liu Ying
  Cc: devicetree, mturquette, kernel, linux-kernel, dri-devel,
	linux-arm-kernel

On Wed, Dec 17, 2014 at 05:44:33PM +0800, Liu Ying wrote:
> Hi Thierry,
> 
> Sorry for the late response.
> I tried to address almost all your comments locally first.
> More feedback below.
> 
> On 12/10/2014 09:16 PM, Thierry Reding wrote:
> >On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
> >>+static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
> >>+			int timeout, bool to_set)
> >>+{
> >>+	u32 val;
> >>+	bool out = false;
> >>+
> >>+	val = dsi_read(dsi, reg);
> >>+	for (;;) {
> >>+		out = to_set ? (val & status) : !(val & status);
> >>+		if (out)
> >>+			break;
> >>+
> >>+		if (!timeout--)
> >>+			return -EFAULT;
> >>+
> >>+		msleep(1);
> >>+		val = dsi_read(dsi, reg);
> >>+	}
> >>+	return 0;
> >>+}
> >
> >You should probably use a properly timed loop here. msleep() isn't
> >guaranteed to return after exactly one millisecond, so your timeout is
> >never going to be accurate. Something like the following would be better
> >in my opinion:
> >
> >	timeout = jiffies + msecs_to_jiffies(timeout);
> >
> >	while (time_before(jiffies, timeout)) {
> >		...
> >	}
> >
> >Also timeout should be unsigned long in that case.
> 
> Accepted.

Actually, that's a bad example: what we want to do is to assess success
after we wait, before we decide that something has failed.  In other
words, we don't want to wait, and decide that we failed without first
checking for success.

In any case, returning -EFAULT is not sane: EFAULT doesn't mean "fault"
it means "Bad address", and it is returned to userspace to mean that
userspace passed the kernel a bad address.  That definition does /not/
fit what's going on here.

	timeout = jiffies + msecs_to_jiffies(timeout);

	do {
		val = dsi_read(dsi, reg);
		out = to_set ? (val & status) : !(val & status);
		if (out)
			break;

		if (time_is_after_jiffies(timeout))
			return -ETIMEDOUT;

		msleep(1);
	} while (1);

	return 0;

would be better: we only fail immediately after we have checked whether
we succeeded, and we also do the first check immediately.

-- 
FTTC broadband for 0.8mile line: currently at 9.5Mbps down 400kbps up
according to speedtest.net.
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
@ 2014-12-17 10:40         ` Russell King - ARM Linux
  0 siblings, 0 replies; 79+ messages in thread
From: Russell King - ARM Linux @ 2014-12-17 10:40 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Dec 17, 2014 at 05:44:33PM +0800, Liu Ying wrote:
> Hi Thierry,
> 
> Sorry for the late response.
> I tried to address almost all your comments locally first.
> More feedback below.
> 
> On 12/10/2014 09:16 PM, Thierry Reding wrote:
> >On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
> >>+static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
> >>+			int timeout, bool to_set)
> >>+{
> >>+	u32 val;
> >>+	bool out = false;
> >>+
> >>+	val = dsi_read(dsi, reg);
> >>+	for (;;) {
> >>+		out = to_set ? (val & status) : !(val & status);
> >>+		if (out)
> >>+			break;
> >>+
> >>+		if (!timeout--)
> >>+			return -EFAULT;
> >>+
> >>+		msleep(1);
> >>+		val = dsi_read(dsi, reg);
> >>+	}
> >>+	return 0;
> >>+}
> >
> >You should probably use a properly timed loop here. msleep() isn't
> >guaranteed to return after exactly one millisecond, so your timeout is
> >never going to be accurate. Something like the following would be better
> >in my opinion:
> >
> >	timeout = jiffies + msecs_to_jiffies(timeout);
> >
> >	while (time_before(jiffies, timeout)) {
> >		...
> >	}
> >
> >Also timeout should be unsigned long in that case.
> 
> Accepted.

Actually, that's a bad example: what we want to do is to assess success
after we wait, before we decide that something has failed.  In other
words, we don't want to wait, and decide that we failed without first
checking for success.

In any case, returning -EFAULT is not sane: EFAULT doesn't mean "fault"
it means "Bad address", and it is returned to userspace to mean that
userspace passed the kernel a bad address.  That definition does /not/
fit what's going on here.

	timeout = jiffies + msecs_to_jiffies(timeout);

	do {
		val = dsi_read(dsi, reg);
		out = to_set ? (val & status) : !(val & status);
		if (out)
			break;

		if (time_is_after_jiffies(timeout))
			return -ETIMEDOUT;

		msleep(1);
	} while (1);

	return 0;

would be better: we only fail immediately after we have checked whether
we succeeded, and we also do the first check immediately.

-- 
FTTC broadband for 0.8mile line: currently at 9.5Mbps down 400kbps up
according to speedtest.net.

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
  2014-12-17 10:40         ` Russell King - ARM Linux
  (?)
@ 2014-12-18  2:46           ` Liu Ying
  -1 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-18  2:46 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Thierry Reding, dri-devel, devicetree, linux-kernel,
	linux-arm-kernel, p.zabel, shawn.guo, kernel, mturquette,
	airlied

Hi Russell,

On 12/17/2014 06:40 PM, Russell King - ARM Linux wrote:
> On Wed, Dec 17, 2014 at 05:44:33PM +0800, Liu Ying wrote:
>> Hi Thierry,
>>
>> Sorry for the late response.
>> I tried to address almost all your comments locally first.
>> More feedback below.
>>
>> On 12/10/2014 09:16 PM, Thierry Reding wrote:
>>> On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
>>>> +static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
>>>> +			int timeout, bool to_set)
>>>> +{
>>>> +	u32 val;
>>>> +	bool out = false;
>>>> +
>>>> +	val = dsi_read(dsi, reg);
>>>> +	for (;;) {
>>>> +		out = to_set ? (val & status) : !(val & status);
>>>> +		if (out)
>>>> +			break;
>>>> +
>>>> +		if (!timeout--)
>>>> +			return -EFAULT;
>>>> +
>>>> +		msleep(1);
>>>> +		val = dsi_read(dsi, reg);
>>>> +	}
>>>> +	return 0;
>>>> +}
>>>
>>> You should probably use a properly timed loop here. msleep() isn't
>>> guaranteed to return after exactly one millisecond, so your timeout is
>>> never going to be accurate. Something like the following would be better
>>> in my opinion:
>>>
>>> 	timeout = jiffies + msecs_to_jiffies(timeout);
>>>
>>> 	while (time_before(jiffies, timeout)) {
>>> 		...
>>> 	}
>>>
>>> Also timeout should be unsigned long in that case.
>>
>> Accepted.
>
> Actually, that's a bad example: what we want to do is to assess success
> after we wait, before we decide that something has failed.  In other
> words, we don't want to wait, and decide that we failed without first
> checking for success.
>
> In any case, returning -EFAULT is not sane: EFAULT doesn't mean "fault"
> it means "Bad address", and it is returned to userspace to mean that
> userspace passed the kernel a bad address.  That definition does /not/
> fit what's going on here.
>
> 	timeout = jiffies + msecs_to_jiffies(timeout);
>
> 	do {
> 		val = dsi_read(dsi, reg);
> 		out = to_set ? (val & status) : !(val & status);
> 		if (out)
> 			break;
>
> 		if (time_is_after_jiffies(timeout))

time_is_after_jiffies(a) is defined as time_before(jiffies, a).

So, this line should be changed to

	if (time_after(jiffies, timeout))

Right?

> 			return -ETIMEDOUT;
>
> 		msleep(1);
> 	} while (1);
>
> 	return 0;
>
> would be better: we only fail immediately after we have checked whether
> we succeeded, and we also do the first check immediately.
>

Does this one look better?  I use cpu_relax() instead of msleep(1).

         expire = jiffies + msecs_to_jiffies(timeout);
         for (;;) {
                 val = dsi_read(dsi, reg);
                 out = to_set ? (val & status) : !(val & status);
                 if (out)
                         break;

                 if (time_after(jiffies, expire))
                         return -ETIMEDOUT;

                 cpu_relax();
         }

	return 0;

Regards,

Liu Ying

^ permalink raw reply	[flat|nested] 79+ messages in thread

* Re: [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
@ 2014-12-18  2:46           ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-18  2:46 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: devicetree, mturquette, kernel, linux-kernel, dri-devel,
	linux-arm-kernel

Hi Russell,

On 12/17/2014 06:40 PM, Russell King - ARM Linux wrote:
> On Wed, Dec 17, 2014 at 05:44:33PM +0800, Liu Ying wrote:
>> Hi Thierry,
>>
>> Sorry for the late response.
>> I tried to address almost all your comments locally first.
>> More feedback below.
>>
>> On 12/10/2014 09:16 PM, Thierry Reding wrote:
>>> On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
>>>> +static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
>>>> +			int timeout, bool to_set)
>>>> +{
>>>> +	u32 val;
>>>> +	bool out = false;
>>>> +
>>>> +	val = dsi_read(dsi, reg);
>>>> +	for (;;) {
>>>> +		out = to_set ? (val & status) : !(val & status);
>>>> +		if (out)
>>>> +			break;
>>>> +
>>>> +		if (!timeout--)
>>>> +			return -EFAULT;
>>>> +
>>>> +		msleep(1);
>>>> +		val = dsi_read(dsi, reg);
>>>> +	}
>>>> +	return 0;
>>>> +}
>>>
>>> You should probably use a properly timed loop here. msleep() isn't
>>> guaranteed to return after exactly one millisecond, so your timeout is
>>> never going to be accurate. Something like the following would be better
>>> in my opinion:
>>>
>>> 	timeout = jiffies + msecs_to_jiffies(timeout);
>>>
>>> 	while (time_before(jiffies, timeout)) {
>>> 		...
>>> 	}
>>>
>>> Also timeout should be unsigned long in that case.
>>
>> Accepted.
>
> Actually, that's a bad example: what we want to do is to assess success
> after we wait, before we decide that something has failed.  In other
> words, we don't want to wait, and decide that we failed without first
> checking for success.
>
> In any case, returning -EFAULT is not sane: EFAULT doesn't mean "fault"
> it means "Bad address", and it is returned to userspace to mean that
> userspace passed the kernel a bad address.  That definition does /not/
> fit what's going on here.
>
> 	timeout = jiffies + msecs_to_jiffies(timeout);
>
> 	do {
> 		val = dsi_read(dsi, reg);
> 		out = to_set ? (val & status) : !(val & status);
> 		if (out)
> 			break;
>
> 		if (time_is_after_jiffies(timeout))

time_is_after_jiffies(a) is defined as time_before(jiffies, a).

So, this line should be changed to

	if (time_after(jiffies, timeout))

Right?

> 			return -ETIMEDOUT;
>
> 		msleep(1);
> 	} while (1);
>
> 	return 0;
>
> would be better: we only fail immediately after we have checked whether
> we succeeded, and we also do the first check immediately.
>

Does this one look better?  I use cpu_relax() instead of msleep(1).

         expire = jiffies + msecs_to_jiffies(timeout);
         for (;;) {
                 val = dsi_read(dsi, reg);
                 out = to_set ? (val & status) : !(val & status);
                 if (out)
                         break;

                 if (time_after(jiffies, expire))
                         return -ETIMEDOUT;

                 cpu_relax();
         }

	return 0;

Regards,

Liu Ying
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 79+ messages in thread

* [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
@ 2014-12-18  2:46           ` Liu Ying
  0 siblings, 0 replies; 79+ messages in thread
From: Liu Ying @ 2014-12-18  2:46 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Russell,

On 12/17/2014 06:40 PM, Russell King - ARM Linux wrote:
> On Wed, Dec 17, 2014 at 05:44:33PM +0800, Liu Ying wrote:
>> Hi Thierry,
>>
>> Sorry for the late response.
>> I tried to address almost all your comments locally first.
>> More feedback below.
>>
>> On 12/10/2014 09:16 PM, Thierry Reding wrote:
>>> On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
>>>> +static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
>>>> +			int timeout, bool to_set)
>>>> +{
>>>> +	u32 val;
>>>> +	bool out = false;
>>>> +
>>>> +	val = dsi_read(dsi, reg);
>>>> +	for (;;) {
>>>> +		out = to_set ? (val & status) : !(val & status);
>>>> +		if (out)
>>>> +			break;
>>>> +
>>>> +		if (!timeout--)
>>>> +			return -EFAULT;
>>>> +
>>>> +		msleep(1);
>>>> +		val = dsi_read(dsi, reg);
>>>> +	}
>>>> +	return 0;
>>>> +}
>>>
>>> You should probably use a properly timed loop here. msleep() isn't
>>> guaranteed to return after exactly one millisecond, so your timeout is
>>> never going to be accurate. Something like the following would be better
>>> in my opinion:
>>>
>>> 	timeout = jiffies + msecs_to_jiffies(timeout);
>>>
>>> 	while (time_before(jiffies, timeout)) {
>>> 		...
>>> 	}
>>>
>>> Also timeout should be unsigned long in that case.
>>
>> Accepted.
>
> Actually, that's a bad example: what we want to do is to assess success
> after we wait, before we decide that something has failed.  In other
> words, we don't want to wait, and decide that we failed without first
> checking for success.
>
> In any case, returning -EFAULT is not sane: EFAULT doesn't mean "fault"
> it means "Bad address", and it is returned to userspace to mean that
> userspace passed the kernel a bad address.  That definition does /not/
> fit what's going on here.
>
> 	timeout = jiffies + msecs_to_jiffies(timeout);
>
> 	do {
> 		val = dsi_read(dsi, reg);
> 		out = to_set ? (val & status) : !(val & status);
> 		if (out)
> 			break;
>
> 		if (time_is_after_jiffies(timeout))

time_is_after_jiffies(a) is defined as time_before(jiffies, a).

So, this line should be changed to

	if (time_after(jiffies, timeout))

Right?

> 			return -ETIMEDOUT;
>
> 		msleep(1);
> 	} while (1);
>
> 	return 0;
>
> would be better: we only fail immediately after we have checked whether
> we succeeded, and we also do the first check immediately.
>

Does this one look better?  I use cpu_relax() instead of msleep(1).

         expire = jiffies + msecs_to_jiffies(timeout);
         for (;;) {
                 val = dsi_read(dsi, reg);
                 out = to_set ? (val & status) : !(val & status);
                 if (out)
                         break;

                 if (time_after(jiffies, expire))
                         return -ETIMEDOUT;

                 cpu_relax();
         }

	return 0;

Regards,

Liu Ying

^ permalink raw reply	[flat|nested] 79+ messages in thread

end of thread, other threads:[~2014-12-18  2:46 UTC | newest]

Thread overview: 79+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-12-10  8:37 [PATCH RFC 00/15] Add support for i.MX MIPI DSI DRM driver Liu Ying
2014-12-10  8:37 ` Liu Ying
2014-12-10  8:37 ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 01/15] clk: divider: Correct parent clk round rate if no bestdiv is normally found Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 02/15] of: Add vendor prefix for Himax Technologies Inc Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 03/15] of: Add vendor prefix for Truly Semiconductors Limited Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 04/15] drm/dsi: Do not add DSI devices for the child nodes with input-port property Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10 12:21   ` Thierry Reding
2014-12-10 12:21     ` Thierry Reding
2014-12-10 12:21     ` Thierry Reding
2014-12-11  2:52     ` Liu Ying
2014-12-11  2:52       ` Liu Ying
2014-12-11  2:52       ` Liu Ying
2014-12-11  5:50       ` Liu Ying
2014-12-11  5:50         ` Liu Ying
2014-12-11  5:50         ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 05/15] ARM: dts: imx6qdl: Add input-port property to MIPI DSI node's CTRC child nodes Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 06/15] ARM: dts: imx6q: Add MIPI DSI remote end points for IPU2 DI0/1 end points Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 07/15] ARM: imx6q: Add GPR3 MIPI muxing control register field shift bits definition Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 08/15] ARM: imx6q: clk: Add the video_27m clock Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10 13:16   ` Thierry Reding
2014-12-10 13:16     ` Thierry Reding
2014-12-10 13:16     ` Thierry Reding
2014-12-17  9:44     ` Liu Ying
2014-12-17  9:44       ` Liu Ying
2014-12-17  9:44       ` Liu Ying
2014-12-17 10:40       ` Russell King - ARM Linux
2014-12-17 10:40         ` Russell King - ARM Linux
2014-12-17 10:40         ` Russell King - ARM Linux
2014-12-18  2:46         ` Liu Ying
2014-12-18  2:46           ` Liu Ying
2014-12-18  2:46           ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 10/15] drm: panel: Add support for Himax HX8369A MIPI DSI panel Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10 14:03   ` Thierry Reding
2014-12-10 14:03     ` Thierry Reding
2014-12-10 14:03     ` Thierry Reding
2014-12-17 10:27     ` Liu Ying
2014-12-17 10:27       ` Liu Ying
2014-12-17 10:27       ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 11/15] ARM: dtsi: imx6qdl: Add support for MIPI DSI host controller Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 12/15] ARM: dts: imx6qdl-sabresd: Add support for TRULY TFT480800-16-E MIPI DSI panel Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10 14:07   ` Thierry Reding
2014-12-10 14:07     ` Thierry Reding
2014-12-10 14:07     ` Thierry Reding
2014-12-17 10:35     ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 13/15] ARM: imx_v6_v7_defconfig: Cleanup for imx drm being moved out of staging Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 14/15] ARM: imx_v6_v7_defconfig: Add support for MIPI DSI host controller Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37 ` [PATCH RFC 15/15] ARM: imx_v6_v7_defconfig: Add support for Himax HX8369A panel Liu Ying
2014-12-10  8:37   ` Liu Ying
2014-12-10  8:37   ` Liu Ying

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.