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