This patch set adds a new drm driver for HiSilicon hi6220 SoC. Current testing and support board is Hikey board which is one of Linaro 96boards. It is an arm64 open source board. For more information about this board, please access https://www.96boards.org.
Hardware Detail --------------- The display subsystem of Hi6220 SoC is shown as bellow: +-----+ +----------+ +-----+ +---------+ | | | | | | | | | FB |------>| ADE |---->| DSI |---->| External| | | | | | | | HDMI | +-----+ +----------+ +-----+ +---------+
- ADE(Advanced Display Engine) is the display controller. It contains 7 channels, 3 overlay compositors and a LDI. - A channel looks like: DMA-->clip-->scale-->ctrans(or called csc). - Overlay compositor is response to compose planes which come from 7 channels and pass composed image to LDI. - LDI is response to generate timings and RGB data stream. - DSI converts the RGB data stream from ADE to DSI packets. - External HDMI module is connected with DSI bus. Now Hikey use a ADI's ADV7533 external HDMI chip.
Change History ------------- Changes in v2: - Remove abtraction layer of plane/crtc/encoder/connector. - Refactor atomic implementation according to Daniel Vetter's guides: http://blog.ffwll.ch/2014/11/atomic-modeset-support-for-kms-drivers.html http://blog.ffwll.ch/2015/09/xdc-2015-atomic-modesetting-for-drivers.html http://blog.ffwll.ch/2015/08/atomic-modesetting-design-overview.html - Use bridge instead of slave encoder to connect external HDMI. - Move dt binding docs to bindings/display/hisilicon directory.
Xinliang Liu (10): arm64: dts: hisilicon: Add display subsystem DT nodes for hi6220. drm/hisilicon: Add device tree binding for hi6220 display subsystem drm/hisilicon: Add hisilicon DRM master driver drm/hisilicon: Add crtc funcs for ADE drm/hisilicon: Add plane funcs for ADE drm/hisilicon: Add vblank feature drm/hisilicon: Add cma fbdev and hotplug drm/hisilicon: Add dsi encoder driver drm/hisilicon: Add dsi host driver drm/hisilicon: Add support for external bridge
.../bindings/display/hisilicon/hisi-ade.txt | 42 + .../bindings/display/hisilicon/hisi-drm.txt | 66 ++ .../bindings/display/hisilicon/hisi-dsi.txt | 53 + arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts | 21 + arch/arm64/boot/dts/hisilicon/hi6220.dtsi | 44 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/hisilicon/Kconfig | 10 + drivers/gpu/drm/hisilicon/Makefile | 5 + drivers/gpu/drm/hisilicon/hisi_ade_reg.h | 490 +++++++++ drivers/gpu/drm/hisilicon/hisi_drm_ade.c | 1068 ++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_drm_ade.h | 16 + drivers/gpu/drm/hisilicon/hisi_drm_drv.c | 280 +++++ drivers/gpu/drm/hisilicon/hisi_drm_drv.h | 19 + drivers/gpu/drm/hisilicon/hisi_drm_dsi.c | 829 +++++++++++++++ drivers/gpu/drm/hisilicon/hisi_dsi_reg.h | 89 ++ 16 files changed, 3035 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-dsi.txt create mode 100644 drivers/gpu/drm/hisilicon/Kconfig create mode 100644 drivers/gpu/drm/hisilicon/Makefile create mode 100644 drivers/gpu/drm/hisilicon/hisi_ade_reg.h create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_ade.c create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_ade.h create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_drv.c create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_drv.h create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_dsi.c create mode 100644 drivers/gpu/drm/hisilicon/hisi_dsi_reg.h
Add ade, dsi and adv7533 DT nodes for hikey board.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org --- arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts | 21 ++++++++++++ arch/arm64/boot/dts/hisilicon/hi6220.dtsi | 44 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+)
diff --git a/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts b/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts index 8d43a0f..81236b3 100644 --- a/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts +++ b/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts @@ -31,4 +31,25 @@ device_type = "memory"; reg = <0x0 0x0 0x0 0x40000000>; }; + + soc { + i2c2: i2c@f7102000 { + status = "ok"; + + adv7533: adv7533@39 { + compatible = "adi,adv7533"; + reg = <0x39>; + interrupt-parent = <&gpio1>; + interrupts = <1 2>; + pd-gpio = <&gpio0 4 0>; + adi,dsi-lanes = <4>; + + port { + adv_in: endpoint { + remote-endpoint = <&dsi_out>; + }; + }; + }; + }; + }; }; diff --git a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi index 82d2488..2d6cf03 100644 --- a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi +++ b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi @@ -208,5 +208,49 @@ clock-names = "uartclk", "apb_pclk"; status = "disabled"; }; + + display-subsystem { + compatible = "hisilicon,hi6220-dss"; + #address-cells = <2>; + #size-cells = <2>; + ranges; + + ade: ade@f4100000 { + compatible = "hisilicon,hi6220-ade"; + reg = <0x0 0xf4100000 0x0 0x7800>, + <0x0 0xf4410000 0x0 0x1000>; + reg-names = "ade_base", + "media_base"; + interrupts = <0 115 4>; /* ldi interrupt */ + + clocks = <&media_ctrl HI6220_ADE_CORE>, + <&media_ctrl HI6220_CODEC_JPEG>, + <&media_ctrl HI6220_ADE_PIX_SRC>, + <&media_ctrl HI6220_PLL_SYS>, + <&media_ctrl HI6220_PLL_SYS_MEDIA>; + /*clock name*/ + clock-names = "clk_ade_core", + "aclk_codec_jpeg_src", + "clk_ade_pix", + "clk_syspll_src", + "clk_medpll_src"; + ade_core_clk_rate = <360000000>; + media_noc_clk_rate = <288000000>; + }; + + dsi: dsi@0xf4107800 { + compatible = "hisilicon,hi6220-dsi"; + reg = <0x0 0xf4107800 0x0 0x100>; + clocks = <&media_ctrl HI6220_DSI_PCLK>; + clock-names = "pclk_dsi"; + + port { + dsi_out: endpoint { + remote-endpoint = <&adv_in>; + }; + }; + + }; + }; }; };
Add the device tree binding documentation for hi6220 SoC display subsystem. drm master device binding doc. ADE display controller binding doc. DSI controller binding doc.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org --- .../bindings/display/hisilicon/hisi-ade.txt | 42 ++++++++++++++ .../bindings/display/hisilicon/hisi-drm.txt | 66 ++++++++++++++++++++++ .../bindings/display/hisilicon/hisi-dsi.txt | 53 +++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-dsi.txt
diff --git a/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt b/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt new file mode 100644 index 0000000..2777a2c --- /dev/null +++ b/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt @@ -0,0 +1,42 @@ +Device-Tree bindings for hisilicon ADE display controller driver + +ADE (Advanced Display Engine) is the display controller which grab image +data from memory, do composition, do post image processing, generate RGB +timing stream and transfer to DSI. + +Required properties: +- compatible: value should be one of the following + "hisilicon,hi6220-ade". +- reg: physical base address and length of the controller's registers. +- reg-names: name of physical base. +- interrupt: the interrupt number. +- clocks: the clocks needed. +- clock-names: the name of the clocks. +- ade_core_clk_rate: ADE core clock rate. +- media_noc_clk_rate: media noc module clock rate. + + +A example of HiKey board hi6220 SoC specific DT entry: +Example: + + ade: ade@f4100000 { + compatible = "hisilicon,hi6220-ade"; + reg = <0x0 0xf4100000 0x0 0x7800>, + <0x0 0xf4410000 0x0 0x1000>; + reg-names = "ade_base", + "media_base"; + interrupts = <0 115 4>; + + clocks = <&media_ctrl HI6220_ADE_CORE>, + <&media_ctrl HI6220_CODEC_JPEG>, + <&media_ctrl HI6220_ADE_PIX_SRC>, + <&media_ctrl HI6220_PLL_SYS>, + <&media_ctrl HI6220_PLL_SYS_MEDIA>; + clock-names = "clk_ade_core", + "aclk_codec_jpeg_src", + "clk_ade_pix", + "clk_syspll_src", + "clk_medpll_src"; + ade_core_clk_rate = <360000000>; + media_noc_clk_rate = <288000000>; + }; diff --git a/Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt b/Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt new file mode 100644 index 0000000..fd93026 --- /dev/null +++ b/Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt @@ -0,0 +1,66 @@ +Hisilicon DRM master device + +The Hisilicon DRM master device is a virtual device needed to list all +the other display relevant nodes that comprise the display subsystem. + + +Required properties: +- compatible: Should be "hisilicon,<chip>-dss" +- #address-cells: should be set to 2. +- #size-cells: should be set to 2. +- range: to allow probing of subdevices. + +Optional properties: +- dma-coherent: Present if dma operations are coherent. + +Required sub nodes: +All the device nodes of display subsystem of SoC should be the sub nodes. +Such as display controller node, DSI node and so on. + +A example of HiKey board hi6220 SoC specific DT entry: +Example: + + display-subsystem { + compatible = "hisilicon,hi6220-dss"; + #address-cells = <2>; + #size-cells = <2>; + ranges; + dma-coherent; + + ade: ade@f4100000 { + compatible = "hisilicon,hi6220-ade"; + reg = <0x0 0xf4100000 0x0 0x7800>, + <0x0 0xf4410000 0x0 0x1000>; + reg-names = "ade_base", + "media_base"; + interrupts = <0 115 4>; /* ldi interrupt */ + + clocks = <&media_ctrl HI6220_ADE_CORE>, + <&media_ctrl HI6220_CODEC_JPEG>, + <&media_ctrl HI6220_ADE_PIX_SRC>, + <&media_ctrl HI6220_PLL_SYS>, + <&media_ctrl HI6220_PLL_SYS_MEDIA>; + /*clock name*/ + clock-names = "clk_ade_core", + "aclk_codec_jpeg_src", + "clk_ade_pix", + "clk_syspll_src", + "clk_medpll_src"; + ade_core_clk_rate = <360000000>; + media_noc_clk_rate = <288000000>; + }; + + dsi: dsi@0xf4107800 { + compatible = "hisilicon,hi6220-dsi"; + reg = <0x0 0xf4107800 0x0 0x100>; + clocks = <&media_ctrl HI6220_DSI_PCLK>; + clock-names = "pclk_dsi"; + + port { + dsi_out: endpoint { + remote-endpoint = <&adv_in>; + }; + }; + + }; + }; diff --git a/Documentation/devicetree/bindings/display/hisilicon/hisi-dsi.txt b/Documentation/devicetree/bindings/display/hisilicon/hisi-dsi.txt new file mode 100644 index 0000000..30abaa85 --- /dev/null +++ b/Documentation/devicetree/bindings/display/hisilicon/hisi-dsi.txt @@ -0,0 +1,53 @@ +Device-Tree bindings for hisilicon DSI controller driver + +A DSI controller resides in the middle of display controller and external +HDMI converter. + +Required properties: +- compatible: value should be one of the following + "hisilicon,hi6220-dsi". +- reg: physical base address and length of the controller's registers. +- clocks: the clocks needed. +- clock-names: the name of the clocks. +- port: DSI controller output port. This contains one endpoint subnode, with its + remote-endpoint set to the phandle of the connected external HDMI endpoint. + See Documentation/devicetree/bindings/graph.txt for device graph info. + +A example of HiKey board hi6220 SoC and board specific DT entry: +Example: + +SoC specific: + dsi: dsi@0xf4107800 { + compatible = "hisilicon,hi6220-dsi"; + reg = <0x0 0xf4107800 0x0 0x100>; + clocks = <&media_ctrl HI6220_DSI_PCLK>; + clock-names = "pclk_dsi"; + + port { + dsi_out: endpoint { + remote-endpoint = <&adv_in>; + }; + }; + + }; + +Board specific: + i2c2: i2c@f7102000 { + status = "ok"; + + adv7533: adv7533@39 { + compatible = "adi,adv7533"; + reg = <0x39>; + interrupt-parent = <&gpio1>; + interrupts = <1 2>; + pd-gpio = <&gpio0 4 0>; + adi,dsi-lanes = <4>; + + port { + adv_in: endpoint { + remote-endpoint = <&dsi_out>; + }; + }; + }; + }; +
On Sat, Nov 28, 2015 at 06:38:57PM +0800, Xinliang Liu wrote:
Add the device tree binding documentation for hi6220 SoC display subsystem. drm master device binding doc. ADE display controller binding doc. DSI controller binding doc.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
.../bindings/display/hisilicon/hisi-ade.txt | 42 ++++++++++++++ .../bindings/display/hisilicon/hisi-drm.txt | 66 ++++++++++++++++++++++ .../bindings/display/hisilicon/hisi-dsi.txt | 53 +++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-dsi.txt
diff --git a/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt b/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt new file mode 100644 index 0000000..2777a2c --- /dev/null +++ b/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt @@ -0,0 +1,42 @@ +Device-Tree bindings for hisilicon ADE display controller driver
+ADE (Advanced Display Engine) is the display controller which grab image +data from memory, do composition, do post image processing, generate RGB +timing stream and transfer to DSI.
+Required properties: +- compatible: value should be one of the following
- "hisilicon,hi6220-ade".
+- reg: physical base address and length of the controller's registers. +- reg-names: name of physical base. +- interrupt: the interrupt number. +- clocks: the clocks needed. +- clock-names: the name of the clocks. +- ade_core_clk_rate: ADE core clock rate. +- media_noc_clk_rate: media noc module clock rate.
I think you can use assigned clock properties instead:
assigned-clocks = <&media_ctrl HI6220_ADE_CORE>; assigned-clock-rates = <360000000>;
I'm not sure about what media_noc corresponds to in clocks list though.
+A example of HiKey board hi6220 SoC specific DT entry: +Example:
- ade: ade@f4100000 {
compatible = "hisilicon,hi6220-ade";
reg = <0x0 0xf4100000 0x0 0x7800>,
<0x0 0xf4410000 0x0 0x1000>;
reg-names = "ade_base",
"media_base";
interrupts = <0 115 4>;
clocks = <&media_ctrl HI6220_ADE_CORE>,
<&media_ctrl HI6220_CODEC_JPEG>,
<&media_ctrl HI6220_ADE_PIX_SRC>,
<&media_ctrl HI6220_PLL_SYS>,
<&media_ctrl HI6220_PLL_SYS_MEDIA>;
clock-names = "clk_ade_core",
"aclk_codec_jpeg_src",
"clk_ade_pix",
"clk_syspll_src",
"clk_medpll_src";
ade_core_clk_rate = <360000000>;
media_noc_clk_rate = <288000000>;
- };
diff --git a/Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt b/Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt new file mode 100644 index 0000000..fd93026 --- /dev/null +++ b/Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt @@ -0,0 +1,66 @@ +Hisilicon DRM master device
+The Hisilicon DRM master device is a virtual device needed to list all +the other display relevant nodes that comprise the display subsystem.
There is no need for this in DT. The ADE can be the master device and of-graph can link to the DSI node. I have a similar example here[1] with a LCD controller block, DSI block and adv7533.
+Required properties: +- compatible: Should be "hisilicon,<chip>-dss" +- #address-cells: should be set to 2. +- #size-cells: should be set to 2. +- range: to allow probing of subdevices.
+Optional properties: +- dma-coherent: Present if dma operations are coherent.
Put this on the actually DMA master.
Rob
[1] https://git.linaro.org/people/rob.herring/linux.git pxa1928-drm-v2
On 1 December 2015 at 03:31, Rob Herring robh@kernel.org wrote:
Hi Rob, thank you for review.
On Sat, Nov 28, 2015 at 06:38:57PM +0800, Xinliang Liu wrote:
Add the device tree binding documentation for hi6220 SoC display subsystem. drm master device binding doc. ADE display controller binding doc. DSI controller binding doc.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
.../bindings/display/hisilicon/hisi-ade.txt | 42 ++++++++++++++ .../bindings/display/hisilicon/hisi-drm.txt | 66 ++++++++++++++++++++++ .../bindings/display/hisilicon/hisi-dsi.txt | 53 +++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt create mode 100644 Documentation/devicetree/bindings/display/hisilicon/hisi-dsi.txt
diff --git a/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt b/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt new file mode 100644 index 0000000..2777a2c --- /dev/null +++ b/Documentation/devicetree/bindings/display/hisilicon/hisi-ade.txt @@ -0,0 +1,42 @@ +Device-Tree bindings for hisilicon ADE display controller driver
+ADE (Advanced Display Engine) is the display controller which grab image +data from memory, do composition, do post image processing, generate RGB +timing stream and transfer to DSI.
+Required properties: +- compatible: value should be one of the following
"hisilicon,hi6220-ade".
+- reg: physical base address and length of the controller's registers. +- reg-names: name of physical base. +- interrupt: the interrupt number. +- clocks: the clocks needed. +- clock-names: the name of the clocks. +- ade_core_clk_rate: ADE core clock rate. +- media_noc_clk_rate: media noc module clock rate.
I think you can use assigned clock properties instead:
assigned-clocks = <&media_ctrl HI6220_ADE_CORE>; assigned-clock-rates = <360000000>;
I'm not sure about what media_noc corresponds to in clocks list though.
will use assigned-clocks in v3.
+A example of HiKey board hi6220 SoC specific DT entry: +Example:
ade: ade@f4100000 {
compatible = "hisilicon,hi6220-ade";
reg = <0x0 0xf4100000 0x0 0x7800>,
<0x0 0xf4410000 0x0 0x1000>;
reg-names = "ade_base",
"media_base";
interrupts = <0 115 4>;
clocks = <&media_ctrl HI6220_ADE_CORE>,
<&media_ctrl HI6220_CODEC_JPEG>,
<&media_ctrl HI6220_ADE_PIX_SRC>,
<&media_ctrl HI6220_PLL_SYS>,
<&media_ctrl HI6220_PLL_SYS_MEDIA>;
clock-names = "clk_ade_core",
"aclk_codec_jpeg_src",
"clk_ade_pix",
"clk_syspll_src",
"clk_medpll_src";
ade_core_clk_rate = <360000000>;
media_noc_clk_rate = <288000000>;
};
diff --git a/Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt b/Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt new file mode 100644 index 0000000..fd93026 --- /dev/null +++ b/Documentation/devicetree/bindings/display/hisilicon/hisi-drm.txt @@ -0,0 +1,66 @@ +Hisilicon DRM master device
+The Hisilicon DRM master device is a virtual device needed to list all +the other display relevant nodes that comprise the display subsystem.
There is no need for this in DT. The ADE can be the master device and of-graph can link to the DSI node. I have a similar example here[1] with a LCD controller block, DSI block and adv7533.
This sounds good, then I can remove the virtual master device. I will refer to this example and make ADE as the master device in v3.
+Required properties: +- compatible: Should be "hisilicon,<chip>-dss" +- #address-cells: should be set to 2. +- #size-cells: should be set to 2. +- range: to allow probing of subdevices.
+Optional properties: +- dma-coherent: Present if dma operations are coherent.
Put this on the actually DMA master.
The DMA modules reside in the begining of each channel(or plane) of ADE. So I need to put this dma-coherent property to ADE device node, right?
Thanks, -xinliang
Rob
[1] https://git.linaro.org/people/rob.herring/linux.git pxa1928-drm-v2
On Tue, Dec 1, 2015 at 1:17 AM, Xinliang Liu xinliang.liu@linaro.org wrote:
On 1 December 2015 at 03:31, Rob Herring robh@kernel.org wrote:
Hi Rob, thank you for review.
On Sat, Nov 28, 2015 at 06:38:57PM +0800, Xinliang Liu wrote:
Add the device tree binding documentation for hi6220 SoC display subsystem. drm master device binding doc. ADE display controller binding doc. DSI controller binding doc.
+Required properties: +- compatible: Should be "hisilicon,<chip>-dss" +- #address-cells: should be set to 2. +- #size-cells: should be set to 2. +- range: to allow probing of subdevices.
+Optional properties: +- dma-coherent: Present if dma operations are coherent.
Put this on the actually DMA master.
The DMA modules reside in the begining of each channel(or plane) of ADE. So I need to put this dma-coherent property to ADE device node, right?
Right.
Rob
Add DRM master driver for hi6220 SoC which used in HiKey board. Add dumb buffer feature. Add prime dmabuf feature.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/hisilicon/Kconfig | 9 ++ drivers/gpu/drm/hisilicon/Makefile | 3 + drivers/gpu/drm/hisilicon/hisi_drm_drv.c | 214 +++++++++++++++++++++++++++++++ 5 files changed, 229 insertions(+) create mode 100644 drivers/gpu/drm/hisilicon/Kconfig create mode 100644 drivers/gpu/drm/hisilicon/Makefile create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_drv.c
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 8773fad..038aae8 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -274,3 +274,5 @@ source "drivers/gpu/drm/amd/amdkfd/Kconfig" source "drivers/gpu/drm/imx/Kconfig"
source "drivers/gpu/drm/vc4/Kconfig" + +source "drivers/gpu/drm/hisilicon/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 1e9ff4c..e7efcb7 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -75,3 +75,4 @@ obj-y += i2c/ obj-y += panel/ obj-y += bridge/ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/ +obj-$(CONFIG_DRM_HISI) += hisilicon/ diff --git a/drivers/gpu/drm/hisilicon/Kconfig b/drivers/gpu/drm/hisilicon/Kconfig new file mode 100644 index 0000000..70aa8d1 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/Kconfig @@ -0,0 +1,9 @@ +config DRM_HISI + tristate "DRM Support for Hisilicon SoCs Platform" + depends on DRM + select DRM_KMS_HELPER + select DRM_GEM_CMA_HELPER + select DRM_KMS_CMA_HELPER + help + Choose this option if you have a hisilicon chipsets(hi6220). + If M is selected the module will be called hisi-drm. diff --git a/drivers/gpu/drm/hisilicon/Makefile b/drivers/gpu/drm/hisilicon/Makefile new file mode 100644 index 0000000..7375456 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/Makefile @@ -0,0 +1,3 @@ +hisi-drm-y := hisi_drm_drv.o + +obj-$(CONFIG_DRM_HISI) += hisi-drm.o diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c new file mode 100644 index 0000000..445e2ec --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c @@ -0,0 +1,214 @@ +/* + * Hisilicon SoCs drm master driver + * + * Copyright (c) 2014-2015 Hisilicon Limited. + * Author: + * Xinliang Liu xinliang.liu@linaro.org + * Xinliang Liu z.liuxinliang@hisilicon.com + * Xinwei Kong kong.kongxinwei@hisilicon.com + * + * 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. + * + */ + +#include <linux/of_platform.h> +#include <linux/component.h> + +#include <drm/drmP.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_atomic_helper.h> + +#define DRIVER_NAME "hisi-drm" + +static int hisi_drm_unload(struct drm_device *dev) +{ + drm_mode_config_cleanup(dev); + return 0; +} + +static const struct drm_mode_config_funcs hisi_drm_mode_config_funcs = { + .fb_create = drm_fb_cma_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void hisi_drm_mode_config_init(struct drm_device *dev) +{ + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + + dev->mode_config.max_width = 2048; + dev->mode_config.max_height = 2048; + + dev->mode_config.funcs = &hisi_drm_mode_config_funcs; +} + +static int hisi_drm_load(struct drm_device *dev, unsigned long flags) +{ + int ret; + + dev_set_drvdata(dev->dev, dev); + + /* dev->mode_config initialization */ + drm_mode_config_init(dev); + hisi_drm_mode_config_init(dev); + + /* bind and init sub drivers */ + ret = component_bind_all(dev->dev, dev); + if (ret) { + DRM_ERROR("failed to bind all component.\n"); + goto err_mode_config_cleanup; + } + + /* reset all the states of crtc/plane/encoder/connector */ + drm_mode_config_reset(dev); + + return 0; + +err_mode_config_cleanup: + drm_mode_config_cleanup(dev); + + return ret; +} + +static const struct file_operations hisi_drm_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .mmap = drm_gem_cma_mmap, +}; + +static struct dma_buf *hisi_gem_prime_export(struct drm_device *dev, + struct drm_gem_object *obj, + int flags) +{ + /* we want to be able to write in mmapped buffer */ + flags |= O_RDWR; + return drm_gem_prime_export(dev, obj, flags); +} + +static int hisi_gem_cma_dumb_create(struct drm_file *file, + struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8); + + /* mali gpu need pitch 8 bytes alignment for 32bpp */ + args->pitch = roundup(min_pitch, 8); + + return drm_gem_cma_dumb_create_internal(file, dev, args); +} + +static struct drm_driver hisi_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | + DRIVER_ATOMIC, + .load = hisi_drm_load, + .unload = hisi_drm_unload, + .fops = &hisi_drm_fops, + .set_busid = drm_platform_set_busid, + + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = hisi_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = hisi_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, + + .name = "hisi", + .desc = "Hisilicon SoCs' DRM Driver", + .date = "20150718", + .major = 1, + .minor = 0, +}; + +static int compare_of(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static int hisi_drm_bind(struct device *dev) +{ + dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); + return drm_platform_init(&hisi_drm_driver, to_platform_device(dev)); +} + +static void hisi_drm_unbind(struct device *dev) +{ + drm_put_dev(dev_get_drvdata(dev)); +} + +static const struct component_master_ops hisi_drm_ops = { + .bind = hisi_drm_bind, + .unbind = hisi_drm_unbind, +}; + +static int hisi_drm_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct device_node *child_np; + struct component_match *match = NULL; + + of_platform_populate(node, NULL, NULL, dev); + + child_np = of_get_next_available_child(node, NULL); + while (child_np) { + component_match_add(dev, &match, compare_of, child_np); + of_node_put(child_np); + child_np = of_get_next_available_child(node, child_np); + } + + return component_master_add_with_match(dev, &hisi_drm_ops, match); + + return 0; +} + +static int hisi_drm_platform_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &hisi_drm_ops); + of_platform_depopulate(&pdev->dev); + return 0; +} + +static const struct of_device_id hisi_drm_dt_ids[] = { + { .compatible = "hisilicon,hi6220-dss", }, + { /* end node */ }, +}; +MODULE_DEVICE_TABLE(of, hisi_drm_dt_ids); + +static struct platform_driver hisi_drm_platform_driver = { + .probe = hisi_drm_platform_probe, + .remove = hisi_drm_platform_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .of_match_table = hisi_drm_dt_ids, + }, +}; + +module_platform_driver(hisi_drm_platform_driver); + +MODULE_AUTHOR("Xinliang Liu xinliang.liu@linaro.org"); +MODULE_AUTHOR("Xinliang Liu z.liuxinliang@hisilicon.com"); +MODULE_AUTHOR("Xinwei Kong kong.kongxinwei@hisilicon.com"); +MODULE_DESCRIPTION("hisilicon SoCs' DRM master driver"); +MODULE_LICENSE("GPL v2");
On Sat, Nov 28, 2015 at 4:38 AM, Xinliang Liu xinliang.liu@linaro.org wrote:
Add DRM master driver for hi6220 SoC which used in HiKey board. Add dumb buffer feature. Add prime dmabuf feature.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
+static int hisi_gem_cma_dumb_create(struct drm_file *file,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
+{
int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
BTW, is args->bpp supposed to be bits or bytes? I'm using your dumb bo support in drm_gralloc which does bytes as that is what gralloc_drm_get_bpp returns. The virtio-gpu driver does bits. I'm guessing bits is correct.
Rob
On 4 December 2015 at 00:21, Rob Herring robh@kernel.org wrote:
On Sat, Nov 28, 2015 at 4:38 AM, Xinliang Liu xinliang.liu@linaro.org wrote:
Add DRM master driver for hi6220 SoC which used in HiKey board. Add dumb buffer feature. Add prime dmabuf feature.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
+static int hisi_gem_cma_dumb_create(struct drm_file *file,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
+{
int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
BTW, is args->bpp supposed to be bits or bytes? I'm using your dumb bo support in drm_gralloc which does bytes as that is what gralloc_drm_get_bpp returns. The virtio-gpu driver does bits. I'm guessing bits is correct.
yes, it is bits. And pitch is bytes.
Rob
Add crtc funcs and helper funcs for ADE.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org --- drivers/gpu/drm/hisilicon/Makefile | 3 +- drivers/gpu/drm/hisilicon/hisi_ade_reg.h | 490 +++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_drm_ade.c | 511 +++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_drm_drv.c | 15 + drivers/gpu/drm/hisilicon/hisi_drm_drv.h | 16 + 5 files changed, 1034 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hisi_ade_reg.h create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_ade.c create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_drv.h
diff --git a/drivers/gpu/drm/hisilicon/Makefile b/drivers/gpu/drm/hisilicon/Makefile index 7375456..3433c8b 100644 --- a/drivers/gpu/drm/hisilicon/Makefile +++ b/drivers/gpu/drm/hisilicon/Makefile @@ -1,3 +1,4 @@ -hisi-drm-y := hisi_drm_drv.o +hisi-drm-y := hisi_drm_drv.o \ + hisi_drm_ade.o
obj-$(CONFIG_DRM_HISI) += hisi-drm.o diff --git a/drivers/gpu/drm/hisilicon/hisi_ade_reg.h b/drivers/gpu/drm/hisilicon/hisi_ade_reg.h new file mode 100644 index 0000000..6a7bc46 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_ade_reg.h @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2014-2015 Hisilicon Limited. + * + * 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. + * + */ + +#ifndef __HISI_ADE_REG_H__ +#define __HISI_ADE_REG_H__ + +/* + * ADE Registers Offset + */ +#define ADE_CTRL (0x4) +#define ADE_CTRL1 (0x8C) +#define ADE_ROT_SRC_CFG (0x10) +#define ADE_DISP_SRC_CFG (0x18) +#define ADE_WDMA2_SRC_CFG (0x1C) +#define ADE_SEC_OVLY_SRC_CFG (0x20) +#define ADE_WDMA3_SRC_CFG (0x24) +#define ADE_OVLY1_TRANS_CFG (0x2C) +#define ADE_EN (0x100) +#define INTR_MASK_CPU_0 (0xC10) +#define INTR_MASK_CPU_1 (0xC14) +#define ADE_FRM_DISGARD_CTRL (0xA4) +/* reset and reload regs */ +#define ADE_SOFT_RST_SEL0 (0x78) +#define ADE_SOFT_RST_SEL1 (0x7C) +#define ADE_RELOAD_DIS0 (0xAC) +#define ADE_RELOAD_DIS1 (0xB0) +#define ADE_CH_RDMA_BIT_OFST (0) +#define ADE_CLIP_BIT_OFST (15) +#define ADE_SCL_BIT_OFST (21) +#define ADE_CTRAN_BIT_OFST (24) +#define ADE_OVLY_BIT_OFST (37) /* 32+5 */ +/* channel regs */ +#define RD_CH_PE(x) (0x1000 + (x) * 0x80) +#define RD_CH_CTRL(x) (0x1004 + (x) * 0x80) +#define RD_CH_ADDR(x) (0x1008 + (x) * 0x80) +#define RD_CH_SIZE(x) (0x100C + (x) * 0x80) +#define RD_CH_STRIDE(x) (0x1010 + (x) * 0x80) +#define RD_CH_SPACE(x) (0x1014 + (x) * 0x80) +#define RD_CH_PARTIAL_SIZE(x) (0x1018 + (x) * 0x80) +#define RD_CH_PARTIAL_SPACE(x) (0x101C + (x) * 0x80) +#define RD_CH_EN(x) (0x1020 + (x) * 0x80) +#define RD_CH_STATUS(x) (0x1024 + (x) * 0x80) +#define RD_CH_DISP_CTRL (0x1404) +#define RD_CH_DISP_ADDR (0x1408) +#define RD_CH_DISP_SIZE (0x140C) +#define RD_CH_DISP_STRIDE (0x1410) +#define RD_CH_DISP_SPACE (0x1414) +#define RD_CH_DISP_EN (0x142C) +/* clip regs */ +#define ADE_CLIP_DISABLE(x) (0x6800 + (x) * 0x100) +#define ADE_CLIP_SIZE0(x) (0x6804 + (x) * 0x100) +#define ADE_CLIP_SIZE1(x) (0x6808 + (x) * 0x100) +#define ADE_CLIP_SIZE2(x) (0x680C + (x) * 0x100) +#define ADE_CLIP_CFG_OK(x) (0x6810 + (x) * 0x100) +/* scale regs */ +#define ADE_SCL1_MUX_CFG (0xC) +#define ADE_SCL2_SRC_CFG (0x14) +#define ADE_SCL3_MUX_CFG (0x8) +#define ADE_SCL_CTRL(x) (0x3000 + (x) * 0x800) +#define ADE_SCL_HSP(x) (0x3004 + (x) * 0x800) +#define ADE_SCL_UV_HSP(x) (0x3008 + (x) * 0x800) +#define ADE_SCL_VSP(x) (0x300C + (x) * 0x800) +#define ADE_SCL_UV_VSP(x) (0x3010 + (x) * 0x800) +#define ADE_SCL_ORES(x) (0x3014 + (x) * 0x800) +#define ADE_SCL_IRES(x) (0x3018 + (x) * 0x800) +#define ADE_SCL_START(x) (0x301C + (x) * 0x800) +#define ADE_SCL_ERR(x) (0x3020 + (x) * 0x800) +#define ADE_SCL_PIX_OFST(x) (0x3024 + (x) * 0x800) +#define ADE_SCL_UV_PIX_OFST(x) (0x3028 + (x) * 0x800) +#define ADE_SCL_COEF_CLR(x) (0x3030 + (x) * 0x800) +#define ADE_SCL_HCOEF(x, m, n) (0x3100 + (x) * 0x800 + \ + 12 * (m) + 4 * (n)) +#define ADE_SCL_VCOEF(x, i, j) (0x340C + (x) * 0x800 + \ + 12 * (i) + 4 * (j)) +/* ctran regs */ +#define ADE_CTRAN5_TRANS_CFG (0x40) +#define ADE_CTRAN_DIS(x) (0x5004 + (x) * 0x100) +#define ADE_CTRAN_MODE_CHOOSE(x) (0x5008 + (x) * 0x100) +#define ADE_CTRAN_STAT(x) (0x500C + (x) * 0x100) +#define ADE_CTRAN_CHDC0(x) (0x5010 + (x) * 0x100) +#define ADE_CTRAN_CHDC1(x) (0x5014 + (x) * 0x100) +#define ADE_CTRAN_CHDC2(x) (0x5018 + (x) * 0x100) +#define ADE_CTRAN_CHDC3(x) (0x501C + (x) * 0x100) +#define ADE_CTRAN_CHDC4(x) (0x5020 + (x) * 0x100) +#define ADE_CTRAN_CHDC5(x) (0x5024 + (x) * 0x100) +#define ADE_CTRAN_CSC0(x) (0x5028 + (x) * 0x100) +#define ADE_CTRAN_CSC1(x) (0x502C + (x) * 0x100) +#define ADE_CTRAN_CSC2(x) (0x5030 + (x) * 0x100) +#define ADE_CTRAN_CSC3(x) (0x5034 + (x) * 0x100) +#define ADE_CTRAN_CSC4(x) (0x5038 + (x) * 0x100) +#define ADE_CTRAN_IMAGE_SIZE(x) (0x503C + (x) * 0x100) +#define ADE_CTRAN_CFG_OK(x) (0x5040 + (x) * 0x100) +/* overlay regs */ +#define ADE_OVLY_ALPHA_ST (0x2000) +#define ADE_OVLY_CH_XY0(x) (0x2004 + (x) * 4) +#define ADE_OVLY_CH_XY1(x) (0x2024 + (x) * 4) +#define ADE_OVLY_CH_CTL(x) (0x204C + (x) * 4) +#define ADE_OVLY_OUTPUT_SIZE(x) (0x2070 + (x) * 8) +#define ADE_OVLY_BASE_COLOR(x) (0x2074 + (x) * 8) +#define ADE_OVLYX_CTL(x) (0x209C + (x) * 4) +#define ADE_OVLY_CTL (0x98) +#define ADE_OVLY_CH_ALP_MODE_OFST (0) +#define ADE_OVLY_CH_ALP_SEL_OFST (2) +#define ADE_OVLY_CH_UNDER_ALP_SEL_OFST (4) +#define ADE_OVLY_CH_EN_OFST (6) +#define ADE_OVLY_CH_ALP_GBL_OFST (15) +#define ADE_OVLY_CH_SEL_OFST (28) + +/* + * media regs + */ +#define SC_MEDIA_RSTDIS (0x530) +#define SC_MEDIA_RSTEN (0x52C) + +/* + * regs relevant enum + */ +enum { + LDI_TEST = 0, + LDI_WORK +}; + +enum { + LDI_ISR_FRAME_END_INT = 0x2, + LDI_ISR_UNDER_FLOW_INT = 0x4 +}; + +enum { + ADE_ISR1_RES_SWITCH_CMPL = 0x80000000 +}; + +enum { + LDI_DISP_MODE_NOT_3D_FBF = 0, + LDI_DISP_MODE_3D_FBF +}; + +enum { + ADE_RGB = 0, + ADE_BGR +}; + +enum { + ADE_DISABLE = 0, + ADE_ENABLE +}; + +enum { + ADE_OUT_RGB_565 = 0, + ADE_OUT_RGB_666, + ADE_OUT_RGB_888 +}; + +/* + * ADE read as big-endian, so revert the + * rgb order described in the SoC datasheet + */ +enum ADE_FORMAT { + ADE_RGB_565 = 0, + ADE_BGR_565, + ADE_XRGB_8888, + ADE_XBGR_8888, + ADE_ARGB_8888, + ADE_ABGR_8888, + ADE_RGBA_8888, + ADE_BGRA_8888, + ADE_RGB_888, + ADE_BGR_888 = 9, + ADE_FORMAT_NOT_SUPPORT = 800 +}; + +/* ldi src cfg */ +enum { + TOP_DISP_SRC_NONE = 0, + TOP_DISP_SRC_OVLY2, + TOP_DISP_SRC_DISP, + TOP_DISP_SRC_ROT, + TOP_DISP_SRC_SCL2 +}; + +enum { + ADE_ISR_DMA_ERROR = 0x2000000 +}; + +enum ade_channel { + ADE_CH1 = 0, /* channel 1 for primary plane */ + ADE_CH_NUM +}; + +enum ade_scale { + ADE_SCL1 = 0, + ADE_SCL2, + ADE_SCL3, + ADE_SCL_NUM +}; + +enum ade_ctran { + ADE_CTRAN1 = 0, + ADE_CTRAN2, + ADE_CTRAN3, + ADE_CTRAN4, + ADE_CTRAN5, + ADE_CTRAN6, + ADE_CTRAN_NUM +}; + +enum ade_overlay { + ADE_OVLY1 = 0, + ADE_OVLY2, + ADE_OVLY3, + ADE_OVLY_NUM +}; + +enum { + ADE_ALP_GLOBAL = 0, + ADE_ALP_PIXEL, + ADE_ALP_PIXEL_AND_GLB +}; + +enum { + ADE_ALP_MUL_COEFF_0 = 0, /* alpha */ + ADE_ALP_MUL_COEFF_1, /* 1-alpha */ + ADE_ALP_MUL_COEFF_2, /* 0 */ + ADE_ALP_MUL_COEFF_3 /* 1 */ +}; + +/* + * ADE Register Union Struct + */ +union U_ADE_CTRL1 { +struct { + unsigned int auto_clk_gate_en :1; + unsigned int rot_buf_shr_out :1; + unsigned int reserved_44 :30; + } bits; + unsigned int u32; +}; + +union U_ADE_SOFT_RST_SEL0 { +struct { + unsigned int ch1_rdma_srst_sel :1; + unsigned int ch2_rdma_srst_sel :1; + unsigned int ch3_rdma_srst_sel :1; + unsigned int ch4_rdma_srst_sel :1; + unsigned int ch5_rdma_srst_sel :1; + unsigned int ch6_rdma_srst_sel :1; + unsigned int disp_rdma_srst_sel :1; + unsigned int cmdq1_rdma_srst_sel :1; + unsigned int cmdq2_rdma_srst_sel :1; + unsigned int reserved_29 :1; + unsigned int ch1_wdma_srst_sel :1; + unsigned int ch2_wdma_srst_sel :1; + unsigned int ch3_wdma_srst_sel :1; + unsigned int reserved_28 :1; + unsigned int cmdq_wdma_srst_sel :1; + unsigned int clip1_srst_sel :1; + unsigned int clip2_srst_sel :1; + unsigned int clip3_srst_sel :1; + unsigned int clip4_srst_sel :1; + unsigned int clip5_srst_sel :1; + unsigned int clip6_srst_sel :1; + unsigned int scl1_srst_sel :1; + unsigned int scl2_srst_sel :1; + unsigned int scl3_srst_sel :1; + unsigned int ctran1_srst_sel :1; + unsigned int ctran2_srst_sel :1; + unsigned int ctran3_srst_sel :1; + unsigned int ctran4_srst_sel :1; + unsigned int ctran5_srst_sel :1; + unsigned int ctran6_srst_sel :1; + unsigned int rot_srst_sel :1; + unsigned int reserved_27 :1; + } bits; + unsigned int u32; +}; + +union U_ADE_CTRL { +struct { + unsigned int frm_end_start :2; + unsigned int dfs_buf_cfg :1; + unsigned int rot_buf_cfg :3; + unsigned int rd_ch5_nv :1; + unsigned int rd_ch6_nv :1; + unsigned int dfs_buf_unflow_lev1 :13; + unsigned int dfs_buf_unflow_lev2 :11; + } bits; + unsigned int u32; +}; + +/* + * ADE Register Write/Read functions + */ +static inline void set_TOP_CTL_clk_gate_en(u8 *base, u32 val) +{ + union U_ADE_CTRL1 ade_ctrl1; + u8 *reg_addr = base + ADE_CTRL1; + + ade_ctrl1.u32 = readl(reg_addr); + ade_ctrl1.bits.auto_clk_gate_en = val; + writel(ade_ctrl1.u32, reg_addr); +} + +static inline void set_TOP_SOFT_RST_SEL0_disp_rdma(u8 *base, u32 val) +{ + union U_ADE_SOFT_RST_SEL0 ade_soft_rst; + u8 *addr = base + ADE_SOFT_RST_SEL0; + + ade_soft_rst.u32 = readl(addr); + ade_soft_rst.bits.disp_rdma_srst_sel = val; + writel(ade_soft_rst.u32, addr); +} + +static inline void set_TOP_SOFT_RST_SEL0_ctran5(u8 *base, u32 val) +{ + union U_ADE_SOFT_RST_SEL0 ade_soft_rst; + u8 *addr = base + ADE_SOFT_RST_SEL0; + + ade_soft_rst.u32 = readl(addr); + ade_soft_rst.bits.ctran5_srst_sel = val; + writel(ade_soft_rst.u32, addr); +} + +static inline void set_TOP_SOFT_RST_SEL0_ctran6(u8 *base, u32 val) +{ + union U_ADE_SOFT_RST_SEL0 ade_soft_rst; + u8 *addr = base + ADE_SOFT_RST_SEL0; + + ade_soft_rst.u32 = readl(addr); + ade_soft_rst.bits.ctran6_srst_sel = val; + writel(ade_soft_rst.u32, addr); +} + +static inline void set_TOP_CTL_frm_end_start(u8 *base, u32 val) +{ + union U_ADE_CTRL ade_ctrl; + u8 *reg_addr = base + ADE_CTRL; + + ade_ctrl.u32 = readl(reg_addr); + ade_ctrl.bits.frm_end_start = val; + writel(ade_ctrl.u32, reg_addr); +} + +/* + * LDI Registers Offset + */ +#define LDI_HRZ_CTRL0 (0x7400) +#define LDI_HRZ_CTRL1 (0x7404) +#define LDI_VRT_CTRL0 (0x7408) +#define LDI_VRT_CTRL1 (0x740C) +#define LDI_PLR_CTRL (0x7410) +#define LDI_DSP_SIZE (0x7414) +#define LDI_INT_EN (0x741C) +#define LDI_CTRL (0x7420) +#define LDI_ORG_INT (0x7424) +#define LDI_MSK_INT (0x7428) +#define LDI_INT_CLR (0x742C) +#define LDI_WORK_MODE (0x7430) +#define LDI_DE_SPACE_LOW (0x7438) +#define LDI_MCU_INTS (0x7450) +#define LDI_MCU_INTE (0x7454) +#define LDI_MCU_INTC (0x7458) +#define LDI_HDMI_DSI_GT (0x7434) + +/* + * LDI Timing Polarity defines + */ +#define HISI_LDI_FLAG_NVSYNC BIT(0) +#define HISI_LDI_FLAG_NHSYNC BIT(1) +#define HISI_LDI_FLAG_NPIXCLK BIT(2) +#define HISI_LDI_FLAG_NDE BIT(3) + +/* + * LDI Register Union Struct + */ +union U_LDI_CTRL { +struct { + unsigned int ldi_en :1; + unsigned int disp_mode_buf :1; + unsigned int date_gate_en :1; + unsigned int bpp :2; + unsigned int wait_vsync_en :1; + unsigned int corlorbar_width :7; + unsigned int bgr :1; + unsigned int color_mode :1; + unsigned int shutdown :1; + unsigned int vactive_line :12; + unsigned int ldi_en_self_clr :1; + unsigned int reserved_573 :3; + } bits; + unsigned int u32; +}; + +union U_LDI_WORK_MODE { +struct { + unsigned int work_mode :1; + unsigned int wback_en :1; + unsigned int colorbar_en :1; + unsigned int reserved_577 :29; + } bits; + unsigned int u32; +}; + +/* + * LDI Register Write/Read Helper functions + */ +static inline void set_reg(u8 *addr, u32 val, u32 bw, u32 bs) +{ + u32 mask = (1 << bw) - 1; + u32 tmp = readl(addr); + + tmp &= ~(mask << bs); + writel(tmp | ((val & mask) << bs), addr); +} + +static inline void set_LDI_CTRL_ldi_en(u8 *base, u32 val) +{ + union U_LDI_CTRL ldi_ctrl; + u8 *addr = base + LDI_CTRL; + + ldi_ctrl.u32 = readl(addr); + ldi_ctrl.bits.ldi_en = val; + writel(ldi_ctrl.u32, addr); +} + +static inline void set_LDI_CTRL_disp_mode(u8 *base, u32 val) +{ + union U_LDI_CTRL ldi_ctrl; + u8 *addr = base + LDI_CTRL; + + ldi_ctrl.u32 = readl(addr); + ldi_ctrl.bits.disp_mode_buf = val; + writel(ldi_ctrl.u32, addr); +} + +static inline void set_LDI_CTRL_bpp(u8 *base, u32 val) +{ + union U_LDI_CTRL ldi_ctrl; + u8 *addr = base + LDI_CTRL; + + ldi_ctrl.u32 = readl(addr); + ldi_ctrl.bits.bpp = val; + writel(ldi_ctrl.u32, addr); +} + +static inline void set_LDI_CTRL_corlorbar_width(u8 *base, u32 val) +{ + union U_LDI_CTRL ldi_ctrl; + u8 *addr = base + LDI_CTRL; + + ldi_ctrl.u32 = readl(addr); + ldi_ctrl.bits.corlorbar_width = (val > 0) ? val - 1 : 0; + writel(ldi_ctrl.u32, addr); +} + +static inline void set_LDI_CTRL_bgr(u8 *base, u32 val) +{ + union U_LDI_CTRL ldi_ctrl; + u8 *addr = base + LDI_CTRL; + + ldi_ctrl.u32 = readl(addr); + ldi_ctrl.bits.bgr = val; + writel(ldi_ctrl.u32, addr); +} + +static inline void set_LDI_WORK_MODE_work_mode(u8 *base, u32 val) +{ + union U_LDI_WORK_MODE ldi_work_mode; + u8 *addr = base + LDI_WORK_MODE; + + ldi_work_mode.u32 = readl(addr); + ldi_work_mode.bits.work_mode = val; + writel(ldi_work_mode.u32, addr); +} + +static inline void set_LDI_WORK_MODE_colorbar_en(u8 *base, u32 val) +{ + union U_LDI_WORK_MODE ldi_work_mode; + u8 *addr = base + LDI_WORK_MODE; + + ldi_work_mode.u32 = readl(addr); + ldi_work_mode.bits.colorbar_en = val; + writel(ldi_work_mode.u32, addr); +} + +#endif diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c new file mode 100644 index 0000000..d157879 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c @@ -0,0 +1,511 @@ +/* + * Hisilicon Hi6220 SoC ADE(Advanced Display Engine)'s crtc&plane driver + * + * Copyright (c) 2014-2015 Hisilicon Limited. + * Author: + * Xinliang Liu xinliang.liu@linaro.org + * Xinliang Liu z.liuxinliang@hisilicon.com + * Xinwei Kong kong.kongxinwei@hisilicon.com + * + * 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. + * + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <video/display_timing.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> + +#include "hisi_drm_drv.h" +#include "hisi_ade_reg.h" + +#define FORCE_PIXEL_CLOCK_SAME_OR_HIGHER 0 + +#define to_ade_crtc(crtc) \ + container_of(crtc, struct ade_crtc, base) + +struct ade_hw_ctx { + void __iomem *base; + void __iomem *media_base; + + int irq; + u32 ade_core_rate; + u32 media_noc_rate; + + struct clk *ade_core_clk; + struct clk *media_noc_clk; + struct clk *ade_pix_clk; + bool power_on; +}; + +struct ade_crtc { + struct drm_crtc base; + struct ade_hw_ctx *ctx; + bool enable; + u64 use_mask; +}; + +struct ade_data { + struct ade_crtc acrtc; + struct ade_hw_ctx ctx; +}; + +static void ade_init(struct ade_hw_ctx *ctx) +{ + void __iomem *base = ctx->base; + + /* enable clk gate */ + set_TOP_CTL_clk_gate_en(base, 1); + /* clear overlay */ + writel(0, base + ADE_OVLY1_TRANS_CFG); + writel(0, base + ADE_OVLY_CTL); + writel(0, base + ADE_OVLYX_CTL(ADE_OVLY2)); + /* clear reset and reload regs */ + writel(0, base + ADE_SOFT_RST_SEL0); + writel(0, base + ADE_SOFT_RST_SEL1); + writel(0xFFFFFFFF, base + ADE_RELOAD_DIS0); + writel(0xFFFFFFFF, base + ADE_RELOAD_DIS1); + /* for video set to 1, means that ade registers + * became effective at frame end + */ + set_TOP_CTL_frm_end_start(base, 1); +} + +static void ade_ldi_set_mode(struct ade_crtc *acrtc, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + u32 out_w = mode->hdisplay; + u32 out_h = mode->vdisplay; + u32 hfp, hbp, hsw, vfp, vbp, vsw; + u32 plr_flags; + int ret; + + plr_flags = (mode->flags & DRM_MODE_FLAG_NVSYNC) + ? HISI_LDI_FLAG_NVSYNC : 0; + plr_flags |= (mode->flags & DRM_MODE_FLAG_NHSYNC) + ? HISI_LDI_FLAG_NHSYNC : 0; + hfp = mode->hsync_start - mode->hdisplay; + hbp = mode->htotal - mode->hsync_end; + hsw = mode->hsync_end - mode->hsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + vsw = mode->vsync_end - mode->vsync_start; + if (vsw > 15) { + DRM_INFO("vsw exceeded 15\n"); + vsw = 15; + } + + writel((hbp << 20) | (hfp << 0), base + LDI_HRZ_CTRL0); + /* p3-73 6220V100 pdf: + * "The configured value is the actual width - 1" + */ + writel(hsw - 1, base + LDI_HRZ_CTRL1); + writel((vbp << 20) | (vfp << 0), base + LDI_VRT_CTRL0); + /* p3-74 6220V100 pdf: + * "The configured value is the actual width - 1" + */ + writel(vsw - 1, base + LDI_VRT_CTRL1); + + /* p3-75 6220V100 pdf: + * "The configured value is the actual width - 1" + */ + writel(((out_h - 1) << 20) | ((out_w - 1) << 0), + base + LDI_DSP_SIZE); + writel(plr_flags, base + LDI_PLR_CTRL); + + ret = clk_set_rate(ctx->ade_pix_clk, mode->clock * 1000); + /* Success should be guaranteed in aotomic_check + * failer shouldn't happen here + */ + if (ret) + DRM_ERROR("set ade_pixel_clk_rate fail\n"); + adj_mode->clock = clk_get_rate(ctx->ade_pix_clk) / 1000; + + /* ctran6 setting */ + writel(1, base + ADE_CTRAN_DIS(ADE_CTRAN6)); + writel(out_w * out_h - 1, base + ADE_CTRAN_IMAGE_SIZE(ADE_CTRAN6)); + acrtc->use_mask |= BIT(ADE_CTRAN_BIT_OFST + ADE_CTRAN6); + DRM_INFO("set mode: %dx%d\n", out_w, out_h); + + /* + * other parameters setting + */ + writel(BIT(0), base + LDI_WORK_MODE); + writel((0x3c << 6) | (ADE_OUT_RGB_888 << 3) | BIT(2) | BIT(0), + base + LDI_CTRL); + set_reg(base + LDI_DE_SPACE_LOW, 0x1, 1, 1); +} + +static int ade_power_up(struct ade_hw_ctx *ctx) +{ + void __iomem *media_base = ctx->media_base; + int ret; + + ret = clk_set_rate(ctx->ade_core_clk, ctx->ade_core_rate); + if (ret) { + DRM_ERROR("clk_set_rate ade_core_rate error\n"); + return ret; + } + ret = clk_set_rate(ctx->media_noc_clk, ctx->media_noc_rate); + if (ret) { + DRM_ERROR("media_noc_clk media_noc_rate error\n"); + return ret; + } + ret = clk_prepare_enable(ctx->media_noc_clk); + if (ret) { + DRM_ERROR("fail to clk_prepare_enable media_noc_clk\n"); + return ret; + } + + writel(0x20, media_base + SC_MEDIA_RSTDIS); + + ret = clk_prepare_enable(ctx->ade_core_clk); + if (ret) { + DRM_ERROR("fail to clk_prepare_enable ade_core_clk\n"); + return ret; + } + + ade_init(ctx); + ctx->power_on = true; + return 0; +} + +static void ade_power_down(struct ade_hw_ctx *ctx) +{ + void __iomem *base = ctx->base; + void __iomem *media_base = ctx->media_base; + + set_LDI_CTRL_ldi_en(base, ADE_DISABLE); + /* dsi pixel off */ + set_reg(base + LDI_HDMI_DSI_GT, 0x1, 1, 0); + + clk_disable_unprepare(ctx->ade_core_clk); + writel(0x20, media_base + SC_MEDIA_RSTEN); + clk_disable_unprepare(ctx->media_noc_clk); + ctx->power_on = false; +} + + + +/* + * set modules' reset mode: by software or hardware + * set modules' reload enable/disable + */ +static void ade_set_reset_and_reload(struct ade_crtc *acrtc) +{ + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + u32 mask0 = (u32)acrtc->use_mask; + u32 mask1 = (u32)(acrtc->use_mask >> 32); + + DRM_DEBUG_DRIVER("mask=0x%llX, mask0=0x%X, mask1=0x%X\n", + acrtc->use_mask, mask0, mask1); + + writel(mask0, base + ADE_SOFT_RST_SEL0); + writel(mask1, base + ADE_SOFT_RST_SEL1); + writel(~mask0, base + ADE_RELOAD_DIS0); + writel(~mask1, base + ADE_RELOAD_DIS1); +} + +/* + * commit to ldi to display + */ +static void ade_display_commit(struct ade_crtc *acrtc) +{ + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + + /* TODO: set rotator after overlay */ + + /* TODO: set scale after overlay */ + + /* display source setting */ + writel(TOP_DISP_SRC_OVLY2, base + ADE_DISP_SRC_CFG); + + /* set reset mode:soft or hw, and reload modules */ + ade_set_reset_and_reload(acrtc); + + DRM_INFO("ADE GO\n"); + /* enable ade */ + wmb(); + writel(ADE_ENABLE, base + ADE_EN); + /* enable ldi */ + wmb(); + set_LDI_CTRL_ldi_en(base, ADE_ENABLE); + /* dsi pixel on */ + set_reg(base + LDI_HDMI_DSI_GT, 0x0, 1, 0); +} + +static void ade_crtc_enable(struct drm_crtc *crtc) +{ + struct ade_crtc *acrtc = to_ade_crtc(crtc); + struct ade_hw_ctx *ctx = acrtc->ctx; + int ret; + + DRM_DEBUG_DRIVER("enter.\n"); + if (acrtc->enable) + return; + + if (!ctx->power_on) { + ret = ade_power_up(ctx); + if (ret) { + DRM_ERROR("failed to initialize ade clk\n"); + return; + } + } + + ade_display_commit(acrtc); + acrtc->enable = true; + + DRM_DEBUG_DRIVER("exit success.\n"); +} + +static void ade_crtc_disable(struct drm_crtc *crtc) +{ + struct ade_crtc *acrtc = to_ade_crtc(crtc); + struct ade_hw_ctx *ctx = acrtc->ctx; + + DRM_DEBUG_DRIVER("enter.\n"); + + if (!acrtc->enable) + return; + + ade_power_down(ctx); + acrtc->use_mask = 0; + acrtc->enable = false; + DRM_DEBUG_DRIVER("exit success.\n"); +} + +int ade_crtc_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state) +{ + DRM_DEBUG_DRIVER("enter.\n"); + DRM_DEBUG_DRIVER("exit success.\n"); + /* do nothing */ + return 0; +} + +static void ade_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct ade_crtc *acrtc = to_ade_crtc(crtc); + struct ade_hw_ctx *ctx = acrtc->ctx; + struct drm_display_mode *mode = &crtc->state->mode; + struct drm_display_mode *adj_mode = &crtc->state->adjusted_mode; + + DRM_DEBUG_DRIVER("enter.\n"); + if (!ctx->power_on) + (void)ade_power_up(ctx); + ade_ldi_set_mode(acrtc, mode, adj_mode); + DRM_DEBUG_DRIVER("exit success.\n"); +} + +static void ade_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct ade_crtc *acrtc = to_ade_crtc(crtc); + struct ade_hw_ctx *ctx = acrtc->ctx; + + DRM_DEBUG_DRIVER("enter.\n"); + if (!ctx->power_on) + (void)ade_power_up(ctx); + DRM_DEBUG_DRIVER("exit success.\n"); +} + +static void ade_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) + +{ + struct ade_crtc *acrtc = to_ade_crtc(crtc); + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + + DRM_DEBUG_DRIVER("enter.\n"); + /* commit to display: LDI input setting */ + if (acrtc->enable) { + /* set reset and reload */ + ade_set_reset_and_reload(acrtc); + /* flush ade regitsters */ + wmb(); + writel(ADE_ENABLE, base + ADE_EN); + } + DRM_DEBUG_DRIVER("exit success.\n"); +} + +static const struct drm_crtc_helper_funcs ade_crtc_helper_funcs = { + .enable = ade_crtc_enable, + .disable = ade_crtc_disable, + .atomic_check = ade_crtc_atomic_check, + .mode_set_nofb = ade_crtc_mode_set_nofb, + .atomic_begin = ade_crtc_atomic_begin, + .atomic_flush = ade_crtc_atomic_flush, +}; + +static const struct drm_crtc_funcs ade_crtc_funcs = { + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_property = drm_atomic_helper_crtc_set_property, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +static int ade_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, + struct drm_plane *plane) +{ + int ret; + + ret = drm_crtc_init_with_planes(dev, crtc, plane, + NULL, &ade_crtc_funcs); + if (ret) { + DRM_ERROR("failed to init crtc.\n"); + return ret; + } + + drm_crtc_helper_add(crtc, &ade_crtc_helper_funcs); + + return 0; +} + +static int ade_bind(struct device *dev, struct device *master, void *data) +{ + return 0; +} + +static void ade_unbind(struct device *dev, struct device *master, void *data) +{ + /* do nothing */ +} + +static const struct component_ops ade_ops = { + .bind = ade_bind, + .unbind = ade_unbind, +}; + +static int ade_dts_parse(struct platform_device *pdev, struct ade_hw_ctx *ctx) +{ + struct resource *res; + struct device *dev; + struct device_node *np; + int ret; + + dev = &pdev->dev; + np = dev->of_node; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ade_base"); + ctx->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ctx->base)) { + DRM_ERROR("failed to remap ade io base\n"); + return PTR_ERR(ctx->base); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "media_base"); + ctx->media_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ctx->media_base)) { + DRM_ERROR("failed to remap media io base\n"); + return PTR_ERR(ctx->media_base); + } + + ctx->irq = platform_get_irq(pdev, 0); + if (ctx->irq < 0) { + DRM_ERROR("failed to parse the irq\n"); + return -ENODEV; + } + + ctx->ade_core_clk = devm_clk_get(&pdev->dev, "clk_ade_core"); + if (!ctx->ade_core_clk) { + DRM_ERROR("failed to parse the ADE_CORE\n"); + return -ENODEV; + } + ctx->media_noc_clk = devm_clk_get(&pdev->dev, + "aclk_codec_jpeg_src"); + if (!ctx->media_noc_clk) { + DRM_ERROR("failed to parse the CODEC_JPEG\n"); + return -ENODEV; + } + ctx->ade_pix_clk = devm_clk_get(&pdev->dev, "clk_ade_pix"); + if (!ctx->ade_pix_clk) { + DRM_ERROR("failed to parse the ADE_PIX_SRC\n"); + return -ENODEV; + } + + ret = of_property_read_u32(np, "ade_core_clk_rate", + &ctx->ade_core_rate); + if (ret) { + DRM_ERROR("failed to parse the ade_core_clk_rate\n"); + return -ENODEV; + } + ret = of_property_read_u32(np, "media_noc_clk_rate", + &ctx->media_noc_rate); + if (ret) { + DRM_ERROR("failed to parse the media_noc_clk_rate\n"); + return -ENODEV; + } + + return 0; +} + +static int ade_probe(struct platform_device *pdev) +{ + struct ade_data *ade; + int ret; + + DRM_DEBUG_DRIVER("enter.\n"); + + ade = devm_kzalloc(&pdev->dev, sizeof(*ade), GFP_KERNEL); + if (!ade) { + DRM_ERROR("failed to alloc ade_data\n"); + return -ENOMEM; + } + + ret = ade_dts_parse(pdev, &ade->ctx); + if (ret) { + DRM_ERROR("failed to parse dts!!\n"); + return ret; + } + + platform_set_drvdata(pdev, ade); + + return component_add(&pdev->dev, &ade_ops); +} + +static int ade_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &ade_ops); + + return 0; +} + +static const struct of_device_id ade_of_match[] = { + { .compatible = "hisilicon,hi6220-ade" }, + { } +}; +MODULE_DEVICE_TABLE(of, ade_of_match); + +static struct platform_driver ade_driver = { + .probe = ade_probe, + .remove = ade_remove, + .driver = { + .name = "hisi-ade", + .owner = THIS_MODULE, + .of_match_table = ade_of_match, + }, +}; + +module_platform_driver(ade_driver); + +MODULE_AUTHOR("Xinliang Liu xinliang.liu@linaro.org"); +MODULE_AUTHOR("Xinliang Liu z.liuxinliang@hisilicon.com"); +MODULE_AUTHOR("Xinwei Kong kong.kongxinwei@hisilicon.com"); +MODULE_DESCRIPTION("Hisilicon DRM ADE(crtc/plane) Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c index 445e2ec..d0eca80 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c @@ -21,11 +21,18 @@ #include <drm/drm_fb_cma_helper.h> #include <drm/drm_atomic_helper.h>
+#include "hisi_drm_drv.h" + #define DRIVER_NAME "hisi-drm"
static int hisi_drm_unload(struct drm_device *dev) { + struct hisi_drm_private *priv = dev->dev_private; + drm_mode_config_cleanup(dev); + devm_kfree(dev->dev, priv); + dev->dev_private = NULL; + return 0; }
@@ -48,8 +55,14 @@ static void hisi_drm_mode_config_init(struct drm_device *dev)
static int hisi_drm_load(struct drm_device *dev, unsigned long flags) { + struct hisi_drm_private *priv; int ret;
+ priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev->dev_private = priv; dev_set_drvdata(dev->dev, dev);
/* dev->mode_config initialization */ @@ -70,6 +83,8 @@ static int hisi_drm_load(struct drm_device *dev, unsigned long flags)
err_mode_config_cleanup: drm_mode_config_cleanup(dev); + devm_kfree(dev->dev, priv); + dev->dev_private = NULL;
return ret; } diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_drv.h b/drivers/gpu/drm/hisilicon/hisi_drm_drv.h new file mode 100644 index 0000000..a10229e --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_drv.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2014-2015 Hisilicon Limited. + * + * 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. + * + */ + +#ifndef __HISI_DRM_DRV_H__ +#define __HISI_DRM_DRV_H__ + +struct hisi_drm_private { +}; + +#endif /* __HISI_DRM_DRV_H__ */
use_maskOn 28 November 2015 at 10:38, Xinliang Liu xinliang.liu@linaro.org wrote:
Add crtc funcs and helper funcs for ADE.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
--- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_ade_reg.h
+#define ADE_CTRL (0x4) +#define ADE_CTRL1 (0x8C) +#define ADE_ROT_SRC_CFG (0x10) +#define ADE_DISP_SRC_CFG (0x18) +#define ADE_WDMA2_SRC_CFG (0x1C) +#define ADE_SEC_OVLY_SRC_CFG (0x20) +#define ADE_WDMA3_SRC_CFG (0x24) +#define ADE_OVLY1_TRANS_CFG (0x2C) +#define ADE_EN (0x100) +#define INTR_MASK_CPU_0 (0xC10) +#define INTR_MASK_CPU_1 (0xC14) +#define ADE_FRM_DISGARD_CTRL (0xA4) +/* reset and reload regs */ +#define ADE_SOFT_RST_SEL0 (0x78) +#define ADE_SOFT_RST_SEL1 (0x7C) +#define ADE_RELOAD_DIS0 (0xAC) +#define ADE_RELOAD_DIS1 (0xB0) +#define ADE_CH_RDMA_BIT_OFST (0) +#define ADE_CLIP_BIT_OFST (15) +#define ADE_SCL_BIT_OFST (21) +#define ADE_CTRAN_BIT_OFST (24) +#define ADE_OVLY_BIT_OFST (37) /* 32+5 */
Don't think we have any cases in drm where constants are wrapped in brackets. Is there any benefit of doing that here ?
+/* channel regs */ +#define RD_CH_PE(x) (0x1000 + (x) * 0x80)
... and I'm not talking about cases where the macros such as this one.
+union U_LDI_CTRL { +struct {
unsigned int ldi_en :1;
unsigned int disp_mode_buf :1;
unsigned int date_gate_en :1;
unsigned int bpp :2;
unsigned int wait_vsync_en :1;
unsigned int corlorbar_width :7;
unsigned int bgr :1;
unsigned int color_mode :1;
unsigned int shutdown :1;
unsigned int vactive_line :12;
unsigned int ldi_en_self_clr :1;
unsigned int reserved_573 :3;
} bits;
unsigned int u32;
+};
+union U_LDI_WORK_MODE { +struct {
unsigned int work_mode :1;
unsigned int wback_en :1;
unsigned int colorbar_en :1;
unsigned int reserved_577 :29;
} bits;
unsigned int u32;
+};
The struct in the above two unions is missing a level of indentation.
--- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c
+static void ade_ldi_set_mode(struct ade_crtc *acrtc,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
+{
struct ade_hw_ctx *ctx = acrtc->ctx;
void __iomem *base = ctx->base;
u32 out_w = mode->hdisplay;
u32 out_h = mode->vdisplay;
u32 hfp, hbp, hsw, vfp, vbp, vsw;
u32 plr_flags;
int ret;
plr_flags = (mode->flags & DRM_MODE_FLAG_NVSYNC)
? HISI_LDI_FLAG_NVSYNC : 0;
plr_flags |= (mode->flags & DRM_MODE_FLAG_NHSYNC)
? HISI_LDI_FLAG_NHSYNC : 0;
hfp = mode->hsync_start - mode->hdisplay;
hbp = mode->htotal - mode->hsync_end;
hsw = mode->hsync_end - mode->hsync_start;
vfp = mode->vsync_start - mode->vdisplay;
vbp = mode->vtotal - mode->vsync_end;
vsw = mode->vsync_end - mode->vsync_start;
if (vsw > 15) {
DRM_INFO("vsw exceeded 15\n");
DRM_ERROR or DRM_DEBUG_xx perhaps ?
vsw = 15;
}
writel((hbp << 20) | (hfp << 0), base + LDI_HRZ_CTRL0);
/* p3-73 6220V100 pdf:
* "The configured value is the actual width - 1"
*/
writel(hsw - 1, base + LDI_HRZ_CTRL1);
writel((vbp << 20) | (vfp << 0), base + LDI_VRT_CTRL0);
/* p3-74 6220V100 pdf:
* "The configured value is the actual width - 1"
*/
writel(vsw - 1, base + LDI_VRT_CTRL1);
/* p3-75 6220V100 pdf:
* "The configured value is the actual width - 1"
*/
writel(((out_h - 1) << 20) | ((out_w - 1) << 0),
base + LDI_DSP_SIZE);
writel(plr_flags, base + LDI_PLR_CTRL);
ret = clk_set_rate(ctx->ade_pix_clk, mode->clock * 1000);
/* Success should be guaranteed in aotomic_check
* failer shouldn't happen here
*/
if (ret)
DRM_ERROR("set ade_pixel_clk_rate fail\n");
DItto
adj_mode->clock = clk_get_rate(ctx->ade_pix_clk) / 1000;
/* ctran6 setting */
writel(1, base + ADE_CTRAN_DIS(ADE_CTRAN6));
writel(out_w * out_h - 1, base + ADE_CTRAN_IMAGE_SIZE(ADE_CTRAN6));
acrtc->use_mask |= BIT(ADE_CTRAN_BIT_OFST + ADE_CTRAN6);
DRM_INFO("set mode: %dx%d\n", out_w, out_h);
DRM_DEBUG_DRIVER ?
/*
* other parameters setting
*/
writel(BIT(0), base + LDI_WORK_MODE);
writel((0x3c << 6) | (ADE_OUT_RGB_888 << 3) | BIT(2) | BIT(0),
base + LDI_CTRL);
set_reg(base + LDI_DE_SPACE_LOW, 0x1, 1, 1);
+}
+static int ade_power_up(struct ade_hw_ctx *ctx) +{
void __iomem *media_base = ctx->media_base;
int ret;
ret = clk_set_rate(ctx->ade_core_clk, ctx->ade_core_rate);
if (ret) {
DRM_ERROR("clk_set_rate ade_core_rate error\n");
How about the following (or alike) less cryptic and more informative message. Other places could use a similar treatment.
"Failed to set rate X clk (%d)\n", ret ?
+static void ade_crtc_enable(struct drm_crtc *crtc) +{
struct ade_crtc *acrtc = to_ade_crtc(crtc);
struct ade_hw_ctx *ctx = acrtc->ctx;
int ret;
DRM_DEBUG_DRIVER("enter.\n");
Does this and the remaining DEBUG_DRIVER(enter|exit) messages provide any meaningful input, past the driver development stage ?
if (acrtc->enable)
return;
Esp. since we have cases like this where no message is available.
Regards, Emil
On 28 November 2015 at 23:56, Emil Velikov emil.l.velikov@gmail.com wrote: ​Hi Emil, thank you for review.​
use_maskOn 28 November 2015 at 10:38, Xinliang Liu
xinliang.liu@linaro.org wrote:
Add crtc funcs and helper funcs for ADE.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
--- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_ade_reg.h
+#define ADE_CTRL (0x4) +#define ADE_CTRL1 (0x8C) +#define ADE_ROT_SRC_CFG (0x10) +#define ADE_DISP_SRC_CFG (0x18) +#define ADE_WDMA2_SRC_CFG (0x1C) +#define ADE_SEC_OVLY_SRC_CFG (0x20) +#define ADE_WDMA3_SRC_CFG (0x24) +#define ADE_OVLY1_TRANS_CFG (0x2C) +#define ADE_EN (0x100) +#define INTR_MASK_CPU_0 (0xC10) +#define INTR_MASK_CPU_1 (0xC14) +#define ADE_FRM_DISGARD_CTRL (0xA4) +/* reset and reload regs */ +#define ADE_SOFT_RST_SEL0 (0x78) +#define ADE_SOFT_RST_SEL1 (0x7C) +#define ADE_RELOAD_DIS0 (0xAC) +#define ADE_RELOAD_DIS1 (0xB0) +#define ADE_CH_RDMA_BIT_OFST (0) +#define ADE_CLIP_BIT_OFST (15) +#define ADE_SCL_BIT_OFST (21) +#define ADE_CTRAN_BIT_OFST (24) +#define ADE_OVLY_BIT_OFST (37) /* 32+5 */
Don't think we have any cases in drm where constants are wrapped in brackets. Is there any benefit of doing that here ?
Seems no any benefit​, will remove these brackets in v3.
+/* channel regs */ +#define RD_CH_PE(x) (0x1000 + (x) * 0x80)
... and I'm not talking about cases where the macros such as this one.
+union U_LDI_CTRL { +struct {
unsigned int ldi_en :1;
unsigned int disp_mode_buf :1;
unsigned int date_gate_en :1;
unsigned int bpp :2;
unsigned int wait_vsync_en :1;
unsigned int corlorbar_width :7;
unsigned int bgr :1;
unsigned int color_mode :1;
unsigned int shutdown :1;
unsigned int vactive_line :12;
unsigned int ldi_en_self_clr :1;
unsigned int reserved_573 :3;
} bits;
unsigned int u32;
+};
+union U_LDI_WORK_MODE { +struct {
unsigned int work_mode :1;
unsigned int wback_en :1;
unsigned int colorbar_en :1;
unsigned int reserved_577 :29;
} bits;
unsigned int u32;
+};
The struct in the above two unions is missing a level of indentation.
​yes, will be fixed in v3.​
--- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c
+static void ade_ldi_set_mode(struct ade_crtc *acrtc,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
+{
struct ade_hw_ctx *ctx = acrtc->ctx;
void __iomem *base = ctx->base;
u32 out_w = mode->hdisplay;
u32 out_h = mode->vdisplay;
u32 hfp, hbp, hsw, vfp, vbp, vsw;
u32 plr_flags;
int ret;
plr_flags = (mode->flags & DRM_MODE_FLAG_NVSYNC)
? HISI_LDI_FLAG_NVSYNC : 0;
plr_flags |= (mode->flags & DRM_MODE_FLAG_NHSYNC)
? HISI_LDI_FLAG_NHSYNC : 0;
hfp = mode->hsync_start - mode->hdisplay;
hbp = mode->htotal - mode->hsync_end;
hsw = mode->hsync_end - mode->hsync_start;
vfp = mode->vsync_start - mode->vdisplay;
vbp = mode->vtotal - mode->vsync_end;
vsw = mode->vsync_end - mode->vsync_start;
if (vsw > 15) {
DRM_INFO("vsw exceeded 15\n");
DRM_ERROR or DRM_DEBUG_xx perhaps ?
This is not an error hardware still can handle if vsw exceed 15. You are right maybe ​ ​ DRM_DEBUG_DRIVER is better. ​
vsw = 15;
}
writel((hbp << 20) | (hfp << 0), base + LDI_HRZ_CTRL0);
/* p3-73 6220V100 pdf:
* "The configured value is the actual width - 1"
*/
writel(hsw - 1, base + LDI_HRZ_CTRL1);
writel((vbp << 20) | (vfp << 0), base + LDI_VRT_CTRL0);
/* p3-74 6220V100 pdf:
* "The configured value is the actual width - 1"
*/
writel(vsw - 1, base + LDI_VRT_CTRL1);
/* p3-75 6220V100 pdf:
* "The configured value is the actual width - 1"
*/
writel(((out_h - 1) << 20) | ((out_w - 1) << 0),
base + LDI_DSP_SIZE);
writel(plr_flags, base + LDI_PLR_CTRL);
ret = clk_set_rate(ctx->ade_pix_clk, mode->clock * 1000);
/* Success should be guaranteed in aotomic_check
* failer shouldn't happen here
*/
if (ret)
DRM_ERROR("set ade_pixel_clk_rate fail\n");
DItto
​will use ​ DRM_DEBUG_DRIVER ​ in v3.​ ​
adj_mode->clock = clk_get_rate(ctx->ade_pix_clk) / 1000;
/* ctran6 setting */
writel(1, base + ADE_CTRAN_DIS(ADE_CTRAN6));
writel(out_w * out_h - 1, base +
ADE_CTRAN_IMAGE_SIZE(ADE_CTRAN6));
acrtc->use_mask |= BIT(ADE_CTRAN_BIT_OFST + ADE_CTRAN6);
DRM_INFO("set mode: %dx%d\n", out_w, out_h);
​​ ​​ DRM_DEBUG_DRIVER ?
​ will use ​ DRM_DEBUG_DRIVER ​ in v3. ​
/*
* other parameters setting
*/
writel(BIT(0), base + LDI_WORK_MODE);
writel((0x3c << 6) | (ADE_OUT_RGB_888 << 3) | BIT(2) | BIT(0),
base + LDI_CTRL);
set_reg(base + LDI_DE_SPACE_LOW, 0x1, 1, 1);
+}
+static int ade_power_up(struct ade_hw_ctx *ctx) +{
void __iomem *media_base = ctx->media_base;
int ret;
ret = clk_set_rate(ctx->ade_core_clk, ctx->ade_core_rate);
if (ret) {
DRM_ERROR("clk_set_rate ade_core_rate error\n");
How about the following (or alike) less cryptic and more informative message. Other places could use a similar treatment.
"Failed to set rate X clk (%d)\n", ret ?
​yes, good advice. will be fixed in v3. ​
+static void ade_crtc_enable(struct drm_crtc *crtc) +{
struct ade_crtc *acrtc = to_ade_crtc(crtc);
struct ade_hw_ctx *ctx = acrtc->ctx;
int ret;
DRM_DEBUG_DRIVER("enter.\n");
Does this and the remaining ​​ DEBUG_DRIVER(enter|exit) messages provide any meaningful input, past the driver ​​ development stage ?
if (acrtc->enable)
return;
Esp. since we have cases like this where no message is available.
yeap, these ​ DEBUG_DRIVER(enter|exit) messages​ are for ​ development stage. Which forgot to be removed. will be removed in v3.
Thanks, -xinliang
Regards, Emil
Add plane funcs and helper funcs for ADE.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org --- drivers/gpu/drm/hisilicon/hisi_drm_ade.c | 479 +++++++++++++++++++++++++++++++ 1 file changed, 479 insertions(+)
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c index d157879..b0976c3 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c @@ -23,15 +23,22 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h>
#include "hisi_drm_drv.h" #include "hisi_ade_reg.h"
#define FORCE_PIXEL_CLOCK_SAME_OR_HIGHER 0 +#define PRIMARY_CH (ADE_CH1)
#define to_ade_crtc(crtc) \ container_of(crtc, struct ade_crtc, base)
+#define to_ade_plane(plane) \ + container_of(plane, struct ade_plane, base) + struct ade_hw_ctx { void __iomem *base; void __iomem *media_base; @@ -53,11 +60,75 @@ struct ade_crtc { u64 use_mask; };
+struct ade_plane { + struct drm_plane base; + void *ctx; + u8 ch; /* channel */ +}; + struct ade_data { struct ade_crtc acrtc; + struct ade_plane aplane[ADE_CH_NUM]; struct ade_hw_ctx ctx; };
+/* ade-format info: */ +struct ade_format { + u32 pixel_format; + enum ADE_FORMAT ade_format; +}; + +static const struct ade_format ade_formats[] = { + /* 16bpp RGB: */ + { DRM_FORMAT_RGB565, ADE_RGB_565 }, + { DRM_FORMAT_BGR565, ADE_BGR_565 }, + /* 24bpp RGB: */ + { DRM_FORMAT_RGB888, ADE_RGB_888 }, + { DRM_FORMAT_BGR888, ADE_BGR_888 }, + /* 32bpp [A]RGB: */ + { DRM_FORMAT_XRGB8888, ADE_XRGB_8888 }, + { DRM_FORMAT_XBGR8888, ADE_XBGR_8888 }, + { DRM_FORMAT_RGBA8888, ADE_RGBA_8888 }, + { DRM_FORMAT_BGRA8888, ADE_BGRA_8888 }, + { DRM_FORMAT_ARGB8888, ADE_ARGB_8888 }, + { DRM_FORMAT_ABGR8888, ADE_ABGR_8888 }, +}; + +static const u32 channel_formats1[] = { + /* channel 1,2,3,4 */ + DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888, DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888 +}; + +u32 ade_get_channel_formats(u8 ch, const u32 **formats) +{ + switch (ch) { + case ADE_CH1: + *formats = channel_formats1; + return ARRAY_SIZE(channel_formats1); + default: + DRM_ERROR("no this channel %d\n", ch); + *formats = NULL; + return 0; + } +} + +/* convert from fourcc format to ade format */ +static u32 ade_get_format(u32 pixel_format) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ade_formats); i++) + if (ade_formats[i].pixel_format == pixel_format) + return ade_formats[i].ade_format; + + /* not found */ + DRM_ERROR("Not found pixel format!!fourcc_format= %d\n", pixel_format); + return ADE_FORMAT_NOT_SUPPORT; +} + static void ade_init(struct ade_hw_ctx *ctx) { void __iomem *base = ctx->base; @@ -377,8 +448,416 @@ static int ade_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, return 0; }
+static void ade_rdma_set(struct ade_crtc *acrtc, struct drm_framebuffer *fb, + u32 ch, u32 y, u32 in_h, u32 fmt) +{ + u32 reg_ctrl, reg_addr, reg_size, reg_stride, reg_space, reg_en; + struct drm_gem_cma_object *obj = drm_fb_cma_get_gem_obj(fb, 0); + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + u32 stride = fb->pitches[0]; + u32 addr = (u32)obj->paddr + y * stride; + + DRM_DEBUG_DRIVER("rdma%d: (y=%d, height=%d), stride=%d, paddr=0x%x,", + "addr=0x%x, fb:%dx%d, pixel_format=%d(%s)\n", + ch + 1, y, in_h, stride, (u32)obj->paddr, + addr, fb->width, fb->height, + fmt, drm_get_format_name(fb->pixel_format)); + + /* get reg offset */ + reg_ctrl = RD_CH_CTRL(ch); + reg_addr = RD_CH_ADDR(ch); + reg_size = RD_CH_SIZE(ch); + reg_stride = RD_CH_STRIDE(ch); + reg_space = RD_CH_SPACE(ch); + reg_en = RD_CH_EN(ch); + + /* + * TODO: set rotation + */ + writel((fmt << 16) & 0x1f0000, base + reg_ctrl); + writel(addr, base + reg_addr); + writel((in_h << 16) | stride, base + reg_size); + writel(stride, base + reg_stride); + writel(in_h * stride, base + reg_space); + writel(1, base + reg_en); + + acrtc->use_mask |= BIT(ADE_CH_RDMA_BIT_OFST + ch); +} + +static void ade_rdma_disable(struct ade_crtc *acrtc, u32 ch) +{ + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + u32 reg_en; + + /* get reg offset */ + reg_en = RD_CH_EN(ch); + + writel(0, base + reg_en); + acrtc->use_mask &= ~BIT(ADE_CH_RDMA_BIT_OFST + ch); +} + +static void ade_clip_set(struct ade_crtc *acrtc, u32 ch, u32 fb_w, u32 x, + u32 in_w, u32 in_h) +{ + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + u32 disable_val; + u32 clip_left; + u32 clip_right; + + /* + * clip width, no need to clip height + */ + if (fb_w == in_w) { /* bypass */ + disable_val = 1; + clip_left = 0; + clip_right = 0; + } else { + disable_val = 0; + clip_left = x; + clip_right = fb_w - (x + in_w) - 1; + } + + DRM_DEBUG_DRIVER("clip%d: clip_left=%d, clip_right=%d\n", + ch + 1, clip_left, clip_right); + + writel(disable_val, base + ADE_CLIP_DISABLE(ch)); + writel((fb_w - 1) << 16 | (in_h - 1), base + ADE_CLIP_SIZE0(ch)); + writel(clip_left << 16 | clip_right, base + ADE_CLIP_SIZE1(ch)); + + acrtc->use_mask |= BIT(ADE_CLIP_BIT_OFST + ch); +} + +static void ade_clip_disable(struct ade_crtc *acrtc, u32 ch) +{ + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + + writel(1, base + ADE_CLIP_DISABLE(ch)); + acrtc->use_mask &= ~BIT(ADE_CLIP_BIT_OFST + ch); +} + +static bool has_Alpha_channel(int format) +{ + switch (format) { + case ADE_ARGB_8888: + case ADE_ABGR_8888: + case ADE_RGBA_8888: + case ADE_BGRA_8888: + return true; + default: + return false; + } +} + +static void ade_get_blending_params(u32 fmt, u8 glb_alpha, u8 *alp_mode, + u8 *alp_sel, u8 *under_alp_sel) +{ + bool has_alpha = has_Alpha_channel(fmt); + + /* + * get alp_mode + */ + if (has_alpha && glb_alpha < 255) + *alp_mode = ADE_ALP_PIXEL_AND_GLB; + else if (has_alpha) + *alp_mode = ADE_ALP_PIXEL; + else + *alp_mode = ADE_ALP_GLOBAL; + + /* + * get alp sel + */ + *alp_sel = ADE_ALP_MUL_COEFF_3; /* 1 */ + *under_alp_sel = ADE_ALP_MUL_COEFF_2; /* 0 */ +} + +static void ade_overlay_set(struct ade_crtc *acrtc, u8 ch, u32 x0, u32 y0, + u32 in_w, u32 in_h, u32 fmt) +{ + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + u8 ovly_ch = 0; + u8 x = ADE_OVLY2; + u8 glb_alpha = 255; + u32 x1 = x0 + in_w - 1; + u32 y1 = y0 + in_h - 1; + u32 val; + u8 alp_sel; + u8 under_alp_sel; + u8 alp_mode; + + ade_get_blending_params(fmt, glb_alpha, &alp_mode, &alp_sel, + &under_alp_sel); + + /* overlay routing setting */ + writel(x0 << 16 | y0, base + ADE_OVLY_CH_XY0(ovly_ch)); + writel(x1 << 16 | y1, base + ADE_OVLY_CH_XY1(ovly_ch)); + val = (ch + 1) << ADE_OVLY_CH_SEL_OFST | BIT(ADE_OVLY_CH_EN_OFST) | + alp_sel << ADE_OVLY_CH_ALP_SEL_OFST | + under_alp_sel << ADE_OVLY_CH_UNDER_ALP_SEL_OFST | + glb_alpha << ADE_OVLY_CH_ALP_GBL_OFST | + alp_mode << ADE_OVLY_CH_ALP_MODE_OFST; + DRM_DEBUG_DRIVER("ch%d_ctl=0x%X\n", ovly_ch + 1, val); + writel(val, base + ADE_OVLY_CH_CTL(ovly_ch)); + val = (x + 1) << (ovly_ch * 4) | readl(base + ADE_OVLY_CTL); + DRM_DEBUG_DRIVER("ovly_ctl=0x%X\n", val); + writel(val, base + ADE_OVLY_CTL); + + /* when primary is enable, indicate that it's ready to output. */ + if (ch == PRIMARY_CH) { + val = (in_w - 1) << 16 | (in_h - 1); + writel(val, base + ADE_OVLY_OUTPUT_SIZE(x)); + writel(1, base + ADE_OVLYX_CTL(x)); + acrtc->use_mask |= BIT(ADE_OVLY_BIT_OFST + x); + } +} + +static void ade_overlay_disable(struct ade_crtc *acrtc, u32 ch) +{ + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + u8 ovly_ch = 0; + u32 val; + + val = ~BIT(6) & readl(base + ADE_OVLY_CH_CTL(ovly_ch)); + DRM_DEBUG_DRIVER("ch%d_ctl=0x%X\n", ovly_ch + 1, val); + writel(val, base + ADE_OVLY_CH_CTL(ovly_ch)); + val = ~(0x3 << (ovly_ch * 4)) & readl(base + ADE_OVLY_CTL); + + DRM_DEBUG_DRIVER("ovly_ctl=0x%X\n", val); + writel(val, base + ADE_OVLY_CTL); +} + +/* + * Typicaly, a channel looks like: DMA-->clip-->scale-->ctrans-->overlay + */ +static void ade_update_channel(struct ade_plane *aplane, struct ade_crtc *acrtc, + struct drm_framebuffer *fb, int crtc_x, + int crtc_y, unsigned int crtc_w, + unsigned int crtc_h, u32 src_x, + u32 src_y, u32 src_w, u32 src_h) +{ + u8 ch = aplane->ch; + u32 fmt = ade_get_format(fb->pixel_format); + u32 in_w; + u32 in_h; + + DRM_DEBUG_DRIVER("channel%d: src:(%d, %d)-%dx%d, crtc:(%d, %d)-%dx%d", + ch + 1, src_x, src_y, src_w, src_h, + crtc_x, crtc_y, crtc_w, crtc_h); + + /* 1) DMA setting */ + in_w = src_w; + in_h = src_h; + ade_rdma_set(acrtc, fb, ch, src_y, in_h, fmt); + + /* 2) clip setting */ + ade_clip_set(acrtc, ch, fb->width, src_x, in_w, in_h); + + /* 3) TODO: scale setting for overlay planes */ + + /* 4) TODO: ctran/csc setting for overlay planes */ + + /* 5) overlay/compositor routing setting */ + ade_overlay_set(acrtc, ch, crtc_x, crtc_y, in_w, in_h, fmt); + + DRM_DEBUG_DRIVER("exit success.\n"); +} + +static void ade_disable_channel(struct ade_plane *aplane, + struct ade_crtc *acrtc) +{ + u32 ch = aplane->ch; + + DRM_DEBUG_DRIVER("disable channel%d\n", ch + 1); + + /* + * when primary is disable, power is down + * so no need to disable this channel. + */ + if (ch == PRIMARY_CH) + return; + + /* disable read DMA */ + ade_rdma_disable(acrtc, ch); + + /* disable clip */ + ade_clip_disable(acrtc, ch); + + /* disable overlay routing */ + ade_overlay_disable(acrtc, ch); + + DRM_DEBUG_DRIVER("exit success.\n"); +} + +static int ade_plane_prepare_fb(struct drm_plane *p, + struct drm_framebuffer *fb, + const struct drm_plane_state *new_state) +{ + DRM_DEBUG_DRIVER("enter.\n"); + DRM_DEBUG_DRIVER("exit success.\n"); + return 0; +} + +static void ade_plane_cleanup_fb(struct drm_plane *plane, + struct drm_framebuffer *fb, + const struct drm_plane_state *old_state) +{ + DRM_DEBUG_DRIVER("enter.\n"); + DRM_DEBUG_DRIVER("exit success.\n"); +} + +static int ade_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_framebuffer *fb = state->fb; + struct drm_crtc *crtc = state->crtc; + struct drm_crtc_state *crtc_state; + u32 src_x = state->src_x >> 16; + u32 src_y = state->src_y >> 16; + u32 src_w = state->src_w >> 16; + u32 src_h = state->src_h >> 16; + int crtc_x = state->crtc_x; + int crtc_y = state->crtc_y; + u32 crtc_w = state->crtc_w; + u32 crtc_h = state->crtc_h; + + if (!crtc || !fb) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state->state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (src_w != crtc_w || src_h != crtc_h) { + DRM_ERROR("Scale not support!!!\n"); + return -EINVAL; + } + + if (src_x + src_w > fb->width || + src_y + src_h > fb->height) + return -EINVAL; + + if (crtc_x < 0 || crtc_y < 0) + return -EINVAL; + + if (crtc_x + crtc_w > crtc_state->adjusted_mode.hdisplay || + crtc_y + crtc_h > crtc_state->adjusted_mode.vdisplay) + return -EINVAL; + + return 0; +} + +static void ade_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct drm_plane_state *state = plane->state; + struct ade_plane *aplane = to_ade_plane(plane); + struct ade_crtc *acrtc; + + if (!state->crtc) + return; + + acrtc = to_ade_crtc(state->crtc); + ade_update_channel(aplane, acrtc, state->fb, + state->crtc_x, state->crtc_y, + state->crtc_w, state->crtc_h, + state->src_x >> 16, state->src_y >> 16, + state->src_w >> 16, state->src_h >> 16); +} + +static void ade_plane_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct ade_plane *aplane = to_ade_plane(plane); + struct ade_crtc *acrtc; + + if (!old_state->crtc) + return; + acrtc = to_ade_crtc(old_state->crtc); + ade_disable_channel(aplane, acrtc); +} + +static const struct drm_plane_helper_funcs ade_plane_helper_funcs = { + .prepare_fb = ade_plane_prepare_fb, + .cleanup_fb = ade_plane_cleanup_fb, + .atomic_check = ade_plane_atomic_check, + .atomic_update = ade_plane_atomic_update, + .atomic_disable = ade_plane_atomic_disable, +}; + +static struct drm_plane_funcs ade_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .set_property = drm_atomic_helper_plane_set_property, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static int ade_plane_init(struct drm_device *dev, struct ade_plane *aplane, + enum drm_plane_type type) +{ + const u32 *fmts; + u32 fmts_cnt; + int ret = 0; + + /* get properties */ + fmts_cnt = ade_get_channel_formats(aplane->ch, &fmts); + if (ret) + return ret; + + ret = drm_universal_plane_init(dev, &aplane->base, 1, &ade_plane_funcs, + fmts, fmts_cnt, type); + if (ret) { + DRM_ERROR("fail to init plane, ch=%d\n", aplane->ch); + return ret; + } + + drm_plane_helper_add(&aplane->base, &ade_plane_helper_funcs); + + return 0; +} + static int ade_bind(struct device *dev, struct device *master, void *data) { + struct ade_data *ade = dev_get_drvdata(dev); + struct ade_hw_ctx *ctx = &ade->ctx; + struct ade_crtc *acrtc = &ade->acrtc; + struct drm_device *drm_dev = (struct drm_device *)data; + struct ade_plane *aplane; + enum drm_plane_type type; + int ret; + int i; + + /* + * plane init + * TODO: Now only support primary plane, overlay planes + * need to do. + */ + for (i = 0; i < ADE_CH_NUM; i++) { + aplane = &ade->aplane[i]; + aplane->ch = i; + aplane->ctx = ctx; + type = i == PRIMARY_CH ? DRM_PLANE_TYPE_PRIMARY : + DRM_PLANE_TYPE_OVERLAY; + + ret = ade_plane_init(drm_dev, aplane, type); + if (ret) + return ret; + } + + /* crtc init */ + acrtc->ctx = ctx; + ret = ade_crtc_init(drm_dev, &acrtc->base, + &ade->aplane[PRIMARY_CH].base); + if (ret) + return ret; + return 0; }
Add vblank handle for ADE.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org --- drivers/gpu/drm/hisilicon/hisi_drm_ade.c | 78 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_drm_ade.h | 16 +++++++ drivers/gpu/drm/hisilicon/hisi_drm_drv.c | 19 +++++++- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_ade.h
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c index b0976c3..acb11e7 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c @@ -267,7 +267,79 @@ static void ade_power_down(struct ade_hw_ctx *ctx) ctx->power_on = false; }
+static struct drm_crtc *hisi_get_crtc_from_index(struct drm_device *dev, + unsigned int index) +{ + unsigned int index_tmp = 0; + struct drm_crtc *crtc; + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + if (index_tmp == index) + return crtc; + + index_tmp++; + } + + WARN_ON(true); +} + +int ade_enable_vblank(struct drm_device *dev, int crtc_index) +{ + struct drm_crtc *crtc = hisi_get_crtc_from_index(dev, crtc_index); + struct ade_crtc *acrtc = to_ade_crtc(crtc); + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + u32 intr_en; + + DRM_INFO("enable_vblank enter.\n"); + if (!ctx->power_on) + (void)ade_power_up(ctx); + + intr_en = readl(base + LDI_INT_EN); + intr_en |= LDI_ISR_FRAME_END_INT; + writel(intr_en, base + LDI_INT_EN);
+ return 0; +} + +void ade_disable_vblank(struct drm_device *dev, int crtc_index) +{ + struct drm_crtc *crtc = hisi_get_crtc_from_index(dev, crtc_index); + struct ade_crtc *acrtc = to_ade_crtc(crtc); + struct ade_hw_ctx *ctx = acrtc->ctx; + void __iomem *base = ctx->base; + u32 intr_en; + + DRM_INFO("disable_vblank enter.\n"); + if (!ctx->power_on) { + DRM_ERROR("power is down! vblank disable fail\n"); + return; + } + intr_en = readl(base + LDI_INT_EN); + intr_en &= ~LDI_ISR_FRAME_END_INT; + writel(intr_en, base + LDI_INT_EN); +} + +static irqreturn_t ade_irq_handler(int irq, void *data) +{ + struct ade_crtc *acrtc = data; + struct ade_hw_ctx *ctx = acrtc->ctx; + struct drm_crtc *crtc = &acrtc->base; + struct drm_device *dev = crtc->dev; + void __iomem *base = ctx->base; + u32 status; + + status = readl(base + LDI_MSK_INT); + /* DRM_INFO("LDI IRQ: status=0x%X\n",status); */ + + /* vblank irq */ + if (status & LDI_ISR_FRAME_END_INT) { + writel(LDI_ISR_FRAME_END_INT, base + LDI_INT_CLR); + drm_handle_vblank(dev, drm_crtc_index(crtc)); + } + + return IRQ_HANDLED; +}
/* * set modules' reset mode: by software or hardware @@ -858,6 +930,12 @@ static int ade_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
+ /* vblank irq init */ + ret = request_irq(ctx->irq, ade_irq_handler, DRIVER_IRQ_SHARED, + drm_dev->driver->name, acrtc); + if (ret) + return ret; + return 0; }
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.h b/drivers/gpu/drm/hisilicon/hisi_drm_ade.h new file mode 100644 index 0000000..d1d7b5d --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2014-2015 Hisilicon Limited. + * + * 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. + * + */ + +#ifndef __HISI_DRM_ADE_H__ +#define __HISI_DRM_ADE_H__ + +int ade_enable_vblank(struct drm_device *dev, int crtc_index); +void ade_disable_vblank(struct drm_device *dev, int crtc_index); + +#endif diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c index d0eca80..13f59aa 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c @@ -21,6 +21,7 @@ #include <drm/drm_fb_cma_helper.h> #include <drm/drm_atomic_helper.h>
+#include "hisi_drm_ade.h" #include "hisi_drm_drv.h"
#define DRIVER_NAME "hisi-drm" @@ -29,6 +30,7 @@ static int hisi_drm_unload(struct drm_device *dev) { struct hisi_drm_private *priv = dev->dev_private;
+ drm_vblank_cleanup(dev); drm_mode_config_cleanup(dev); devm_kfree(dev->dev, priv); dev->dev_private = NULL; @@ -76,11 +78,22 @@ static int hisi_drm_load(struct drm_device *dev, unsigned long flags) goto err_mode_config_cleanup; }
+ /* vblank init */ + ret = drm_vblank_init(dev, dev->mode_config.num_crtc); + if (ret) { + DRM_ERROR("failed to initialize vblank.\n"); + goto err_unbind_all; + } + /* with irq_enabled = true, we can use the vblank feature. */ + dev->irq_enabled = true; + /* reset all the states of crtc/plane/encoder/connector */ drm_mode_config_reset(dev);
return 0;
+err_unbind_all: + component_unbind_all(dev->dev, dev); err_mode_config_cleanup: drm_mode_config_cleanup(dev); devm_kfree(dev->dev, priv); @@ -126,7 +139,7 @@ static int hisi_gem_cma_dumb_create(struct drm_file *file,
static struct drm_driver hisi_drm_driver = { .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | - DRIVER_ATOMIC, + DRIVER_ATOMIC | DRIVER_HAVE_IRQ, .load = hisi_drm_load, .unload = hisi_drm_unload, .fops = &hisi_drm_fops, @@ -148,6 +161,10 @@ static struct drm_driver hisi_drm_driver = { .gem_prime_vunmap = drm_gem_cma_prime_vunmap, .gem_prime_mmap = drm_gem_cma_prime_mmap,
+ .get_vblank_counter = drm_vblank_count, + .enable_vblank = ade_enable_vblank, + .disable_vblank = ade_disable_vblank, + .name = "hisi", .desc = "Hisilicon SoCs' DRM Driver", .date = "20150718",
On Sat, Nov 28, 2015 at 06:39:01PM +0800, Xinliang Liu wrote:
Add vblank handle for ADE.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/hisi_drm_ade.c | 78 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_drm_ade.h | 16 +++++++ drivers/gpu/drm/hisilicon/hisi_drm_drv.c | 19 +++++++- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_ade.h
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c index b0976c3..acb11e7 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c @@ -267,7 +267,79 @@ static void ade_power_down(struct ade_hw_ctx *ctx) ctx->power_on = false; }
+static struct drm_crtc *hisi_get_crtc_from_index(struct drm_device *dev,
unsigned int index)
Ugly that you had to add this, but unfortunately necessary :( Fixing up the drm vblank hooks so that they deal with struct drm_crtc directly is somewhere on my todo. But drm_irq.c is still a bit a mess, so this is some ways off still.
What might be possible as a follow-up cleanup though is to add vblank_enable and vblank_disable functions to struct drm_crtc_helper_funcs. And then provide this code here to map from int index to struct drm_crtc * as helpers in a new drm_vblank_helper.c file. That might be a good intermediate step.
But nothing that needs to be done before merging hisilicon, that's for sure.
One more comment below.
+{
- unsigned int index_tmp = 0;
- struct drm_crtc *crtc;
- list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
if (index_tmp == index)
return crtc;
index_tmp++;
- }
- WARN_ON(true);
+}
+int ade_enable_vblank(struct drm_device *dev, int crtc_index) +{
struct drm_crtc *crtc = hisi_get_crtc_from_index(dev, crtc_index);
struct ade_crtc *acrtc = to_ade_crtc(crtc);
struct ade_hw_ctx *ctx = acrtc->ctx;
void __iomem *base = ctx->base;
u32 intr_en;
DRM_INFO("enable_vblank enter.\n");
if (!ctx->power_on)
(void)ade_power_up(ctx);
intr_en = readl(base + LDI_INT_EN);
intr_en |= LDI_ISR_FRAME_END_INT;
writel(intr_en, base + LDI_INT_EN);
return 0;
+}
+void ade_disable_vblank(struct drm_device *dev, int crtc_index) +{
- struct drm_crtc *crtc = hisi_get_crtc_from_index(dev, crtc_index);
- struct ade_crtc *acrtc = to_ade_crtc(crtc);
- struct ade_hw_ctx *ctx = acrtc->ctx;
- void __iomem *base = ctx->base;
- u32 intr_en;
- DRM_INFO("disable_vblank enter.\n");
- if (!ctx->power_on) {
DRM_ERROR("power is down! vblank disable fail\n");
return;
- }
- intr_en = readl(base + LDI_INT_EN);
- intr_en &= ~LDI_ISR_FRAME_END_INT;
- writel(intr_en, base + LDI_INT_EN);
+}
+static irqreturn_t ade_irq_handler(int irq, void *data) +{
- struct ade_crtc *acrtc = data;
- struct ade_hw_ctx *ctx = acrtc->ctx;
- struct drm_crtc *crtc = &acrtc->base;
- struct drm_device *dev = crtc->dev;
- void __iomem *base = ctx->base;
- u32 status;
- status = readl(base + LDI_MSK_INT);
- /* DRM_INFO("LDI IRQ: status=0x%X\n",status); */
- /* vblank irq */
- if (status & LDI_ISR_FRAME_END_INT) {
writel(LDI_ISR_FRAME_END_INT, base + LDI_INT_CLR);
drm_handle_vblank(dev, drm_crtc_index(crtc));
drm_crtc_handle_vblank please. At least when calling into the vblank code drivers don't have to do the struct drm_crtc -> int index conversion any more. Please make sure you do that everywhere, in case I've missed one.
Cheers, Daniel
- }
- return IRQ_HANDLED;
+}
/*
- set modules' reset mode: by software or hardware
@@ -858,6 +930,12 @@ static int ade_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
- /* vblank irq init */
- ret = request_irq(ctx->irq, ade_irq_handler, DRIVER_IRQ_SHARED,
drm_dev->driver->name, acrtc);
- if (ret)
return ret;
- return 0;
}
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.h b/drivers/gpu/drm/hisilicon/hisi_drm_ade.h new file mode 100644 index 0000000..d1d7b5d --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.h @@ -0,0 +1,16 @@ +/*
- Copyright (c) 2014-2015 Hisilicon Limited.
- 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.
- */
+#ifndef __HISI_DRM_ADE_H__ +#define __HISI_DRM_ADE_H__
+int ade_enable_vblank(struct drm_device *dev, int crtc_index); +void ade_disable_vblank(struct drm_device *dev, int crtc_index);
+#endif diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c index d0eca80..13f59aa 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c @@ -21,6 +21,7 @@ #include <drm/drm_fb_cma_helper.h> #include <drm/drm_atomic_helper.h>
+#include "hisi_drm_ade.h" #include "hisi_drm_drv.h"
#define DRIVER_NAME "hisi-drm" @@ -29,6 +30,7 @@ static int hisi_drm_unload(struct drm_device *dev) { struct hisi_drm_private *priv = dev->dev_private;
- drm_vblank_cleanup(dev); drm_mode_config_cleanup(dev); devm_kfree(dev->dev, priv); dev->dev_private = NULL;
@@ -76,11 +78,22 @@ static int hisi_drm_load(struct drm_device *dev, unsigned long flags) goto err_mode_config_cleanup; }
/* vblank init */
ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
if (ret) {
DRM_ERROR("failed to initialize vblank.\n");
goto err_unbind_all;
}
/* with irq_enabled = true, we can use the vblank feature. */
dev->irq_enabled = true;
/* reset all the states of crtc/plane/encoder/connector */ drm_mode_config_reset(dev);
return 0;
+err_unbind_all:
- component_unbind_all(dev->dev, dev);
err_mode_config_cleanup: drm_mode_config_cleanup(dev); devm_kfree(dev->dev, priv); @@ -126,7 +139,7 @@ static int hisi_gem_cma_dumb_create(struct drm_file *file,
static struct drm_driver hisi_drm_driver = { .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
DRIVER_ATOMIC,
.load = hisi_drm_load, .unload = hisi_drm_unload, .fops = &hisi_drm_fops,DRIVER_ATOMIC | DRIVER_HAVE_IRQ,
@@ -148,6 +161,10 @@ static struct drm_driver hisi_drm_driver = { .gem_prime_vunmap = drm_gem_cma_prime_vunmap, .gem_prime_mmap = drm_gem_cma_prime_mmap,
- .get_vblank_counter = drm_vblank_count,
- .enable_vblank = ade_enable_vblank,
- .disable_vblank = ade_disable_vblank,
- .name = "hisi", .desc = "Hisilicon SoCs' DRM Driver", .date = "20150718",
-- 1.9.1
On 30 November 2015 at 15:54, Daniel Vetter daniel@ffwll.ch wrote:
On Sat, Nov 28, 2015 at 06:39:01PM +0800, Xinliang Liu wrote:
Add vblank handle for ADE.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/hisi_drm_ade.c | 78 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_drm_ade.h | 16 +++++++ drivers/gpu/drm/hisilicon/hisi_drm_drv.c | 19 +++++++- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_ade.h
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c index b0976c3..acb11e7 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c @@ -267,7 +267,79 @@ static void ade_power_down(struct ade_hw_ctx *ctx) ctx->power_on = false; }
+static struct drm_crtc *hisi_get_crtc_from_index(struct drm_device *dev,
unsigned int index)
Ugly that you had to add this, but unfortunately necessary :( Fixing up the drm vblank hooks so that they deal with struct drm_crtc directly is somewhere on my todo. But drm_irq.c is still a bit a mess, so this is some ways off still.
What might be possible as a follow-up cleanup though is to add vblank_enable and vblank_disable functions to struct drm_crtc_helper_funcs. And then provide this code here to map from int index to struct drm_crtc * as helpers in a new drm_vblank_helper.c file. That might be a good intermediate step.
I would like to have a try to make this intermediate step patch and send out for review soon.
But nothing that needs to be done before merging hisilicon, that's for sure.
One more comment below.
+{
unsigned int index_tmp = 0;
struct drm_crtc *crtc;
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
if (index_tmp == index)
return crtc;
index_tmp++;
}
WARN_ON(true);
+}
+int ade_enable_vblank(struct drm_device *dev, int crtc_index) +{
struct drm_crtc *crtc = hisi_get_crtc_from_index(dev, crtc_index);
struct ade_crtc *acrtc = to_ade_crtc(crtc);
struct ade_hw_ctx *ctx = acrtc->ctx;
void __iomem *base = ctx->base;
u32 intr_en;
DRM_INFO("enable_vblank enter.\n");
if (!ctx->power_on)
(void)ade_power_up(ctx);
intr_en = readl(base + LDI_INT_EN);
intr_en |= LDI_ISR_FRAME_END_INT;
writel(intr_en, base + LDI_INT_EN);
return 0;
+}
+void ade_disable_vblank(struct drm_device *dev, int crtc_index) +{
struct drm_crtc *crtc = hisi_get_crtc_from_index(dev, crtc_index);
struct ade_crtc *acrtc = to_ade_crtc(crtc);
struct ade_hw_ctx *ctx = acrtc->ctx;
void __iomem *base = ctx->base;
u32 intr_en;
DRM_INFO("disable_vblank enter.\n");
if (!ctx->power_on) {
DRM_ERROR("power is down! vblank disable fail\n");
return;
}
intr_en = readl(base + LDI_INT_EN);
intr_en &= ~LDI_ISR_FRAME_END_INT;
writel(intr_en, base + LDI_INT_EN);
+}
+static irqreturn_t ade_irq_handler(int irq, void *data) +{
struct ade_crtc *acrtc = data;
struct ade_hw_ctx *ctx = acrtc->ctx;
struct drm_crtc *crtc = &acrtc->base;
struct drm_device *dev = crtc->dev;
void __iomem *base = ctx->base;
u32 status;
status = readl(base + LDI_MSK_INT);
/* DRM_INFO("LDI IRQ: status=0x%X\n",status); */
/* vblank irq */
if (status & LDI_ISR_FRAME_END_INT) {
writel(LDI_ISR_FRAME_END_INT, base + LDI_INT_CLR);
drm_handle_vblank(dev, drm_crtc_index(crtc));
drm_crtc_handle_vblank please. At least when calling into the vblank code drivers don't have to do the struct drm_crtc -> int index conversion any more. Please make sure you do that everywhere, in case I've missed one.
will use drm_crtc_handle_vblank in v3.
Thanks, -xinliang
Cheers, Daniel
}
return IRQ_HANDLED;
+}
/*
- set modules' reset mode: by software or hardware
@@ -858,6 +930,12 @@ static int ade_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
/* vblank irq init */
ret = request_irq(ctx->irq, ade_irq_handler, DRIVER_IRQ_SHARED,
drm_dev->driver->name, acrtc);
if (ret)
return ret;
return 0;
}
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.h b/drivers/gpu/drm/hisilicon/hisi_drm_ade.h new file mode 100644 index 0000000..d1d7b5d --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.h @@ -0,0 +1,16 @@ +/*
- Copyright (c) 2014-2015 Hisilicon Limited.
- 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.
- */
+#ifndef __HISI_DRM_ADE_H__ +#define __HISI_DRM_ADE_H__
+int ade_enable_vblank(struct drm_device *dev, int crtc_index); +void ade_disable_vblank(struct drm_device *dev, int crtc_index);
+#endif diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c index d0eca80..13f59aa 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c @@ -21,6 +21,7 @@ #include <drm/drm_fb_cma_helper.h> #include <drm/drm_atomic_helper.h>
+#include "hisi_drm_ade.h" #include "hisi_drm_drv.h"
#define DRIVER_NAME "hisi-drm" @@ -29,6 +30,7 @@ static int hisi_drm_unload(struct drm_device *dev) { struct hisi_drm_private *priv = dev->dev_private;
drm_vblank_cleanup(dev); drm_mode_config_cleanup(dev); devm_kfree(dev->dev, priv); dev->dev_private = NULL;
@@ -76,11 +78,22 @@ static int hisi_drm_load(struct drm_device *dev, unsigned long flags) goto err_mode_config_cleanup; }
/* vblank init */
ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
if (ret) {
DRM_ERROR("failed to initialize vblank.\n");
goto err_unbind_all;
}
/* with irq_enabled = true, we can use the vblank feature. */
dev->irq_enabled = true;
/* reset all the states of crtc/plane/encoder/connector */ drm_mode_config_reset(dev); return 0;
+err_unbind_all:
component_unbind_all(dev->dev, dev);
err_mode_config_cleanup: drm_mode_config_cleanup(dev); devm_kfree(dev->dev, priv); @@ -126,7 +139,7 @@ static int hisi_gem_cma_dumb_create(struct drm_file *file,
static struct drm_driver hisi_drm_driver = { .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
DRIVER_ATOMIC,
DRIVER_ATOMIC | DRIVER_HAVE_IRQ, .load = hisi_drm_load, .unload = hisi_drm_unload, .fops = &hisi_drm_fops,
@@ -148,6 +161,10 @@ static struct drm_driver hisi_drm_driver = { .gem_prime_vunmap = drm_gem_cma_prime_vunmap, .gem_prime_mmap = drm_gem_cma_prime_mmap,
.get_vblank_counter = drm_vblank_count,
.enable_vblank = ade_enable_vblank,
.disable_vblank = ade_disable_vblank,
.name = "hisi", .desc = "Hisilicon SoCs' DRM Driver", .date = "20150718",
-- 1.9.1
-- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
On Tue, Dec 01, 2015 at 11:16:19AM +0800, Xinliang Liu wrote:
On 30 November 2015 at 15:54, Daniel Vetter daniel@ffwll.ch wrote:
On Sat, Nov 28, 2015 at 06:39:01PM +0800, Xinliang Liu wrote:
Add vblank handle for ADE.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/hisi_drm_ade.c | 78 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_drm_ade.h | 16 +++++++ drivers/gpu/drm/hisilicon/hisi_drm_drv.c | 19 +++++++- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_ade.h
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c index b0976c3..acb11e7 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c @@ -267,7 +267,79 @@ static void ade_power_down(struct ade_hw_ctx *ctx) ctx->power_on = false; }
+static struct drm_crtc *hisi_get_crtc_from_index(struct drm_device *dev,
unsigned int index)
Ugly that you had to add this, but unfortunately necessary :( Fixing up the drm vblank hooks so that they deal with struct drm_crtc directly is somewhere on my todo. But drm_irq.c is still a bit a mess, so this is some ways off still.
What might be possible as a follow-up cleanup though is to add vblank_enable and vblank_disable functions to struct drm_crtc_helper_funcs. And then provide this code here to map from int index to struct drm_crtc * as helpers in a new drm_vblank_helper.c file. That might be a good intermediate step.
I would like to have a try to make this intermediate step patch and send out for review soon.
Awesome, looking forward to reviewing it. If you have questions about details fastest to ping me on irc (nick: danvet, #dri-devel on freenode.net).
Cheers, Daniel
On 1 December 2015 at 15:13, Daniel Vetter daniel@ffwll.ch wrote:
On Tue, Dec 01, 2015 at 11:16:19AM +0800, Xinliang Liu wrote:
On 30 November 2015 at 15:54, Daniel Vetter daniel@ffwll.ch wrote:
On Sat, Nov 28, 2015 at 06:39:01PM +0800, Xinliang Liu wrote:
Add vblank handle for ADE.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/hisi_drm_ade.c | 78 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_drm_ade.h | 16 +++++++ drivers/gpu/drm/hisilicon/hisi_drm_drv.c | 19 +++++++- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_ade.h
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c index b0976c3..acb11e7 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c @@ -267,7 +267,79 @@ static void ade_power_down(struct ade_hw_ctx *ctx) ctx->power_on = false; }
+static struct drm_crtc *hisi_get_crtc_from_index(struct drm_device *dev,
unsigned int index)
Ugly that you had to add this, but unfortunately necessary :( Fixing up the drm vblank hooks so that they deal with struct drm_crtc directly is somewhere on my todo. But drm_irq.c is still a bit a mess, so this is some ways off still.
What might be possible as a follow-up cleanup though is to add vblank_enable and vblank_disable functions to struct drm_crtc_helper_funcs. And then provide this code here to map from int index to struct drm_crtc * as helpers in a new drm_vblank_helper.c file. That might be a good intermediate step.
I would like to have a try to make this intermediate step patch and send out for review soon.
Awesome, looking forward to reviewing it. If you have questions about details fastest to ping me on irc (nick: danvet, #dri-devel on freenode.net).
Great! What't your time zone. So that I can ping you in right time.
Thanks, -xinliang
Cheers, Daniel
Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
On Tue, Dec 01, 2015 at 06:34:43PM +0800, Xinliang Liu wrote:
On 1 December 2015 at 15:13, Daniel Vetter daniel@ffwll.ch wrote:
On Tue, Dec 01, 2015 at 11:16:19AM +0800, Xinliang Liu wrote:
On 30 November 2015 at 15:54, Daniel Vetter daniel@ffwll.ch wrote:
On Sat, Nov 28, 2015 at 06:39:01PM +0800, Xinliang Liu wrote:
Add vblank handle for ADE.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/hisi_drm_ade.c | 78 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_drm_ade.h | 16 +++++++ drivers/gpu/drm/hisilicon/hisi_drm_drv.c | 19 +++++++- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_ade.h
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c index b0976c3..acb11e7 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_ade.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_ade.c @@ -267,7 +267,79 @@ static void ade_power_down(struct ade_hw_ctx *ctx) ctx->power_on = false; }
+static struct drm_crtc *hisi_get_crtc_from_index(struct drm_device *dev,
unsigned int index)
Ugly that you had to add this, but unfortunately necessary :( Fixing up the drm vblank hooks so that they deal with struct drm_crtc directly is somewhere on my todo. But drm_irq.c is still a bit a mess, so this is some ways off still.
What might be possible as a follow-up cleanup though is to add vblank_enable and vblank_disable functions to struct drm_crtc_helper_funcs. And then provide this code here to map from int index to struct drm_crtc * as helpers in a new drm_vblank_helper.c file. That might be a good intermediate step.
I would like to have a try to make this intermediate step patch and send out for review soon.
Awesome, looking forward to reviewing it. If you have questions about details fastest to ping me on irc (nick: danvet, #dri-devel on freenode.net).
Great! What't your time zone. So that I can ping you in right time.
Europe, GMT+1.
Cheers, Daniel
Thanks, -xinliang
Cheers, Daniel
Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
Add cma Fbdev, Fbdev is legency and optional, you can enable/disable it by configuring DRM_FBDEV_EMULATION. Add hotplug.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org --- drivers/gpu/drm/hisilicon/hisi_drm_drv.c | 34 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_drm_drv.h | 3 +++ 2 files changed, 37 insertions(+)
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c index 13f59aa..76eb711 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_drv.c @@ -20,6 +20,7 @@ #include <drm/drm_gem_cma_helper.h> #include <drm/drm_fb_cma_helper.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h>
#include "hisi_drm_ade.h" #include "hisi_drm_drv.h" @@ -30,6 +31,13 @@ static int hisi_drm_unload(struct drm_device *dev) { struct hisi_drm_private *priv = dev->dev_private;
+#ifdef CONFIG_DRM_FBDEV_EMULATION + if (priv->fbdev) { + drm_fbdev_cma_fini(priv->fbdev); + priv->fbdev = NULL; + } +#endif + drm_kms_helper_poll_fini(dev); drm_vblank_cleanup(dev); drm_mode_config_cleanup(dev); devm_kfree(dev->dev, priv); @@ -38,8 +46,28 @@ static int hisi_drm_unload(struct drm_device *dev) return 0; }
+#ifdef CONFIG_DRM_FBDEV_EMULATION +static void hisi_fbdev_output_poll_changed(struct drm_device *dev) +{ + struct hisi_drm_private *priv = dev->dev_private; + + if (priv->fbdev) { + drm_fbdev_cma_hotplug_event(priv->fbdev); + } else { + priv->fbdev = drm_fbdev_cma_init(dev, 32, + dev->mode_config.num_crtc, + dev->mode_config.num_connector); + if (IS_ERR(priv->fbdev)) + priv->fbdev = NULL; + } +} +#endif + static const struct drm_mode_config_funcs hisi_drm_mode_config_funcs = { .fb_create = drm_fb_cma_create, +#ifdef CONFIG_DRM_FBDEV_EMULATION + .output_poll_changed = hisi_fbdev_output_poll_changed, +#endif .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, }; @@ -90,6 +118,12 @@ static int hisi_drm_load(struct drm_device *dev, unsigned long flags) /* reset all the states of crtc/plane/encoder/connector */ drm_mode_config_reset(dev);
+ /* init kms poll for handling hpd */ + drm_kms_helper_poll_init(dev); + + /* force detection after connectors init */ + (void)drm_helper_hpd_irq_event(dev); + return 0;
err_unbind_all: diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_drv.h b/drivers/gpu/drm/hisilicon/hisi_drm_drv.h index a10229e..984121f 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_drv.h +++ b/drivers/gpu/drm/hisilicon/hisi_drm_drv.h @@ -11,6 +11,9 @@ #define __HISI_DRM_DRV_H__
struct hisi_drm_private { +#ifdef CONFIG_DRM_FBDEV_EMULATION + struct drm_fbdev_cma *fbdev; +#endif };
#endif /* __HISI_DRM_DRV_H__ */
Add dsi encoder driver for hi6220 SoC.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org --- drivers/gpu/drm/hisilicon/Kconfig | 1 + drivers/gpu/drm/hisilicon/Makefile | 3 +- drivers/gpu/drm/hisilicon/hisi_drm_dsi.c | 728 +++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_dsi_reg.h | 89 ++++ 4 files changed, 820 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_dsi.c create mode 100644 drivers/gpu/drm/hisilicon/hisi_dsi_reg.h
diff --git a/drivers/gpu/drm/hisilicon/Kconfig b/drivers/gpu/drm/hisilicon/Kconfig index 70aa8d1..f1c33c2 100644 --- a/drivers/gpu/drm/hisilicon/Kconfig +++ b/drivers/gpu/drm/hisilicon/Kconfig @@ -4,6 +4,7 @@ config DRM_HISI select DRM_KMS_HELPER select DRM_GEM_CMA_HELPER select DRM_KMS_CMA_HELPER + select DRM_MIPI_DSI help Choose this option if you have a hisilicon chipsets(hi6220). If M is selected the module will be called hisi-drm. diff --git a/drivers/gpu/drm/hisilicon/Makefile b/drivers/gpu/drm/hisilicon/Makefile index 3433c8b..5083c1f 100644 --- a/drivers/gpu/drm/hisilicon/Makefile +++ b/drivers/gpu/drm/hisilicon/Makefile @@ -1,4 +1,5 @@ hisi-drm-y := hisi_drm_drv.o \ - hisi_drm_ade.o + hisi_drm_ade.o \ + hisi_drm_dsi.o
obj-$(CONFIG_DRM_HISI) += hisi-drm.o diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c new file mode 100644 index 0000000..7a6cf66 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c @@ -0,0 +1,728 @@ +/* + * Hisilicon hi6220 SoC dsi driver + * + * Copyright (c) 2014-2015 Hisilicon Limited. + * Author: + * Xinliang Liu xinliang.liu@linaro.org + * Xinliang Liu z.liuxinliang@hisilicon.com + * Xinwei Kong kong.kongxinwei@hisilicon.com + * + * 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. + * + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_graph.h> + +#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_atomic_helper.h> + +#include "hisi_dsi_reg.h" + +#define MAX_TX_ESC_CLK (10) +#define ROUND(x, y) ((x) / (y) + ((x) % (y) * 10 / (y) >= 5 ? 1 : 0)) +#define DEFAULT_MIPI_CLK_RATE 19200000 +#define DEFAULT_MIPI_CLK_PERIOD_PS (1000000000 / (DEFAULT_MIPI_CLK_RATE / 1000)) +#define R(x) ((u32)((((u64)(x) * (u64)1000 * (u64)mode->clock) / \ + phy->lane_byte_clk_kHz))) + +#define encoder_to_dsi(encoder) \ + container_of(encoder, struct hisi_dsi, encoder) +#define host_to_dsi(host) \ + container_of(host, struct hisi_dsi, host) + +struct mipi_phy_register { + u32 clk_t_lpx; + u32 clk_t_hs_prepare; + u32 clk_t_hs_zero; + u32 clk_t_hs_trial; + u32 clk_t_wakeup; + u32 data_t_lpx; + u32 data_t_hs_prepare; + u32 data_t_hs_zero; + u32 data_t_hs_trial; + u32 data_t_ta_go; + u32 data_t_ta_get; + u32 data_t_wakeup; + u32 hstx_ckg_sel; + u32 pll_fbd_div5f; + u32 pll_fbd_div1f; + u32 pll_fbd_2p; + u32 pll_enbwt; + u32 pll_fbd_p; + u32 pll_fbd_s; + u32 pll_pre_div1p; + u32 pll_pre_p; + u32 pll_vco_750M; + u32 pll_lpf_rs; + u32 pll_lpf_cs; + u32 clklp2hs_time; + u32 clkhs2lp_time; + u32 lp2hs_time; + u32 hs2lp_time; + u32 clk_to_data_delay; + u32 data_to_clk_delay; + u32 lane_byte_clk_kHz; + u32 clk_division; +}; + +struct dsi_hw_ctx { + void __iomem *base; + struct clk *dsi_cfg_clk; +}; + +struct hisi_dsi { + struct drm_encoder encoder; + struct drm_display_mode cur_mode; + struct dsi_hw_ctx *ctx; + struct mipi_phy_register phy; + + u32 lanes; + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; + bool enable; +}; + +struct dsi_data { + struct hisi_dsi dsi; + struct dsi_hw_ctx ctx; +}; + +struct dsi_phy_seq_info { + u32 min_range_kHz; + u32 max_range_kHz; + u32 pll_vco_750M; + u32 hstx_ckg_sel; +}; + +static const struct dsi_phy_seq_info dphy_seq_info[] = { + { 46000, 62000, 1, 7 }, + { 62000, 93000, 0, 7 }, + { 93000, 125000, 1, 6 }, + { 125000, 187000, 0, 6 }, + { 187000, 250000, 1, 5 }, + { 250000, 375000, 0, 5 }, + { 375000, 500000, 1, 4 }, + { 500000, 750000, 0, 4 }, + { 750000, 1000000, 1, 0 }, + { 1000000, 1500000, 0, 0 } +}; + +static void set_dsi_phy_rate_equal_or_faster(u32 phy_freq_kHz, + struct mipi_phy_register *phy) +{ + u32 ui = 0; + u32 cfg_clk_ps = DEFAULT_MIPI_CLK_PERIOD_PS; + u32 i = 0; + u32 q_pll = 1; + u32 m_pll = 0; + u32 n_pll = 0; + u32 r_pll = 1; + u32 m_n = 0; + u32 m_n_int = 0; + u64 f_kHz; + u64 temp; + u64 tmp_kHz = phy_freq_kHz; + + do { + f_kHz = tmp_kHz; + + /* Find the PLL clock range from the table */ + for (i = 0; i < ARRAY_SIZE(dphy_seq_info); i++) + if (f_kHz > dphy_seq_info[i].min_range_kHz && + f_kHz <= dphy_seq_info[i].max_range_kHz) + break; + + if (i == ARRAY_SIZE(dphy_seq_info)) { + DRM_ERROR("%lldkHz out of range\n", f_kHz); + return; + } + + phy->pll_vco_750M = dphy_seq_info[i].pll_vco_750M; + phy->hstx_ckg_sel = dphy_seq_info[i].hstx_ckg_sel; + + if (phy->hstx_ckg_sel <= 7 && + phy->hstx_ckg_sel >= 4) + q_pll = 0x10 >> (7 - phy->hstx_ckg_sel); + + temp = f_kHz * (u64)q_pll * (u64)cfg_clk_ps; + m_n_int = temp / (u64)1000000000; + m_n = (temp % (u64)1000000000) / (u64)100000000; + + if (m_n_int % 2 == 0) { + if (m_n * 6 >= 50) { + n_pll = 2; + m_pll = (m_n_int + 1) * n_pll; + } else if (m_n * 6 >= 30) { + n_pll = 3; + m_pll = m_n_int * n_pll + 2; + } else { + n_pll = 1; + m_pll = m_n_int * n_pll; + } + } else { + if (m_n * 6 >= 50) { + n_pll = 1; + m_pll = (m_n_int + 1) * n_pll; + } else if (m_n * 6 >= 30) { + n_pll = 1; + m_pll = (m_n_int + 1) * n_pll; + } else if (m_n * 6 >= 10) { + n_pll = 3; + m_pll = m_n_int * n_pll + 1; + } else { + n_pll = 2; + m_pll = m_n_int * n_pll; + } + } + + if (n_pll == 1) { + phy->pll_fbd_p = 0; + phy->pll_pre_div1p = 1; + } else { + phy->pll_fbd_p = n_pll; + phy->pll_pre_div1p = 0; + } + + if (phy->pll_fbd_2p <= 7 && phy->pll_fbd_2p >= 4) + r_pll = 0x10 >> (7 - phy->pll_fbd_2p); + + if (m_pll == 2) { + phy->pll_pre_p = 0; + phy->pll_fbd_s = 0; + phy->pll_fbd_div1f = 0; + phy->pll_fbd_div5f = 1; + } else if (m_pll >= 2 * 2 * r_pll && m_pll <= 2 * 4 * r_pll) { + phy->pll_pre_p = m_pll / (2 * r_pll); + phy->pll_fbd_s = 0; + phy->pll_fbd_div1f = 1; + phy->pll_fbd_div5f = 0; + } else if (m_pll >= 2 * 5 * r_pll && m_pll <= 2 * 150 * r_pll) { + if (((m_pll / (2 * r_pll)) % 2) == 0) { + phy->pll_pre_p = + (m_pll / (2 * r_pll)) / 2 - 1; + phy->pll_fbd_s = + (m_pll / (2 * r_pll)) % 2 + 2; + } else { + phy->pll_pre_p = + (m_pll / (2 * r_pll)) / 2; + phy->pll_fbd_s = + (m_pll / (2 * r_pll)) % 2; + } + phy->pll_fbd_div1f = 0; + phy->pll_fbd_div5f = 0; + } else { + phy->pll_pre_p = 0; + phy->pll_fbd_s = 0; + phy->pll_fbd_div1f = 0; + phy->pll_fbd_div5f = 1; + } + + f_kHz = (u64)1000000000 * (u64)m_pll / + ((u64)cfg_clk_ps * (u64)n_pll * (u64)q_pll); + + if (f_kHz >= phy_freq_kHz) + break; + + tmp_kHz += 10; + + } while (1); + + ui = 1000000 / f_kHz; + + phy->clk_t_lpx = ROUND(50, 8 * ui); + phy->clk_t_hs_prepare = ROUND(133, 16 * ui) - 1; + + phy->clk_t_hs_zero = ROUND(262, 8 * ui); + phy->clk_t_hs_trial = 2 * (ROUND(60, 8 * ui) - 1); + phy->clk_t_wakeup = ROUND(1000000, (cfg_clk_ps / 1000) - 1); + if (phy->clk_t_wakeup > 0xff) + phy->clk_t_wakeup = 0xff; + phy->data_t_wakeup = phy->clk_t_wakeup; + phy->data_t_lpx = phy->clk_t_lpx; + phy->data_t_hs_prepare = ROUND(125 + 10 * ui, 16 * ui) - 1; + phy->data_t_hs_zero = ROUND(105 + 6 * ui, 8 * ui); + phy->data_t_hs_trial = 2 * (ROUND(60 + 4 * ui, 8 * ui) - 1); + phy->data_t_ta_go = 3; + phy->data_t_ta_get = 4; + + phy->pll_enbwt = 1; + phy->clklp2hs_time = ROUND(407, 8 * ui) + 12; + phy->clkhs2lp_time = ROUND(105 + 12 * ui, 8 * ui); + phy->lp2hs_time = ROUND(240 + 12 * ui, 8 * ui) + 1; + phy->hs2lp_time = phy->clkhs2lp_time; + phy->clk_to_data_delay = 1 + phy->clklp2hs_time; + phy->data_to_clk_delay = ROUND(60 + 52 * ui, 8 * ui) + + phy->clkhs2lp_time; + + phy->lane_byte_clk_kHz = f_kHz / 8; + phy->clk_division = phy->lane_byte_clk_kHz / MAX_TX_ESC_CLK; + if (phy->lane_byte_clk_kHz % MAX_TX_ESC_CLK) + phy->clk_division++; +} + +static u32 dsi_get_dpi_color_coding(enum mipi_dsi_pixel_format format) +{ + u32 val; + + /* TODO: only support RGB888 now, to support more */ + switch (format) { + case MIPI_DSI_FMT_RGB888: + val = DSI_24BITS_1; + break; + default: + val = DSI_24BITS_1; + break; + } + + return val; +} + +static void dsi_mipi_phy_clks(void __iomem *base, + struct mipi_phy_register *phy, + u32 lanes) +{ + u32 delay_count; + bool is_ready; + u32 val; + u32 i; + + /* set lanes value */ + val = (lanes - 1) | (PHY_STOP_WAIT_TIME << 8); + writel(val, base + PHY_IF_CFG); + + /* set phy clk division */ + val = readl(base + CLKMGR_CFG) | phy->clk_division; + writel(val, base + CLKMGR_CFG); + + /* clean up phy set param */ + writel(0, base + PHY_RSTZ); + writel(0, base + PHY_TST_CTRL0); + writel(1, base + PHY_TST_CTRL0); + writel(0, base + PHY_TST_CTRL0); + + /* clock lane Timing control - TLPX */ + dsi_phy_tst_set(base, 0x10010, phy->clk_t_lpx); + + /* clock lane Timing control - THS-PREPARE */ + dsi_phy_tst_set(base, 0x10011, phy->clk_t_hs_prepare); + + /* clock lane Timing control - THS-ZERO */ + dsi_phy_tst_set(base, 0x10012, phy->clk_t_hs_zero); + + /* clock lane Timing control - THS-TRAIL */ + dsi_phy_tst_set(base, 0x10013, phy->clk_t_hs_trial); + + /* clock lane Timing control - TWAKEUP */ + dsi_phy_tst_set(base, 0x10014, phy->clk_t_wakeup); + + /* data lane */ + for (i = 0; i < lanes; i++) { + /* Timing control - TLPX*/ + dsi_phy_tst_set(base, 0x10020 + (i << 4), phy->data_t_lpx); + + /* Timing control - THS-PREPARE */ + dsi_phy_tst_set(base, 0x10021 + (i << 4), + phy->data_t_hs_prepare); + + /* Timing control - THS-ZERO */ + dsi_phy_tst_set(base, 0x10022 + (i << 4), phy->data_t_hs_zero); + + /* Timing control - THS-TRAIL */ + dsi_phy_tst_set(base, 0x10023 + (i << 4), phy->data_t_hs_trial); + + /* Timing control - TTA-GO */ + dsi_phy_tst_set(base, 0x10024 + (i << 4), phy->data_t_ta_go); + + /* Timing control - TTA-GET */ + dsi_phy_tst_set(base, 0x10025 + (i << 4), phy->data_t_ta_get); + + /* Timing control - TWAKEUP */ + dsi_phy_tst_set(base, 0x10026 + (i << 4), phy->data_t_wakeup); + } + + /* physical configuration I */ + dsi_phy_tst_set(base, 0x10060, phy->hstx_ckg_sel); + + /* physical configuration pll II */ + val = (phy->pll_fbd_div5f << 5) + (phy->pll_fbd_div1f << 4) + + (phy->pll_fbd_2p << 1) + phy->pll_enbwt; + dsi_phy_tst_set(base, 0x10063, val); + + /* physical configuration pll II */ + dsi_phy_tst_set(base, 0x10064, phy->pll_fbd_p); + + /* physical configuration pll III */ + dsi_phy_tst_set(base, 0x10065, phy->pll_fbd_s); + + /*physical configuration pll IV*/ + val = (phy->pll_pre_div1p << 7) + phy->pll_pre_p; + dsi_phy_tst_set(base, 0x10066, val); + + /*physical configuration pll V*/ + val = (phy->pll_vco_750M << 4) + (phy->pll_lpf_rs << 2) + + phy->pll_lpf_cs + BIT(5); + dsi_phy_tst_set(base, 0x10067, val); + + writel(BIT(2), base + PHY_RSTZ); + udelay(1); + writel(BIT(2) | BIT(0), base + PHY_RSTZ); + udelay(1); + writel(BIT(2) | BIT(1) | BIT(0), base + PHY_RSTZ); + usleep_range(1000, 1500); + + /* wait for phy's clock ready */ + delay_count = 0; + is_ready = false; + while (1) { + val = readl(base + PHY_STATUS); + if (((BIT(0) | BIT(2)) & val) || delay_count > 100) { + is_ready = (delay_count < 100) ? true : false; + delay_count = 0; + break; + } + + udelay(1); + ++delay_count; + } + + if (!is_ready) + DRM_INFO("phylock and phystopstateclklane is not ready.\n"); +} + +static void dsi_set_mode_timing(void __iomem *base, + struct mipi_phy_register *phy, + struct drm_display_mode *mode, + enum mipi_dsi_pixel_format format) +{ + u32 hfp, hbp, hsw, vfp, vbp, vsw; + u32 hline_time; + u32 hsa_time; + u32 hbp_time; + u32 pixel_clk_kHz; + int htot, vtot; + u32 val; + + /* DSI color coding setting */ + val = dsi_get_dpi_color_coding(format); + writel(val, base + DPI_COLOR_CODING); + + /* DSI format and pol setting */ + val = (mode->flags & DRM_MODE_FLAG_NHSYNC ? 1 : 0) << 2; + val |= (mode->flags & DRM_MODE_FLAG_NVSYNC ? 1 : 0) << 1; + writel(val, base + DPI_CFG_POL); + + /* + * The DSI IP accepts vertical timing using lines as normal, + * but horizontal timing is a mixture of pixel-clocks for the + * active region and byte-lane clocks for the blanking-related + * timings. hfp is specified as the total hline_time in byte- + * lane clocks minus hsa, hbp and active. + */ + pixel_clk_kHz = mode->clock; + htot = mode->htotal; + vtot = mode->vtotal; + hfp = mode->hsync_start - mode->hdisplay; + hbp = mode->htotal - mode->hsync_end; + hsw = mode->hsync_end - mode->hsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + vsw = mode->vsync_end - mode->vsync_start; + if (vsw > 15) { + DRM_INFO("vsw exceeded 15\n"); + vtot -= vsw - 15; + vsw = 15; + } + + hsa_time = (hsw * phy->lane_byte_clk_kHz) / pixel_clk_kHz; + hbp_time = (hbp * phy->lane_byte_clk_kHz) / pixel_clk_kHz; + hline_time = (((u64)htot * (u64)phy->lane_byte_clk_kHz)) / + pixel_clk_kHz; + + if ((R(hline_time) / 1000) > htot) { + DRM_INFO("--: hline_time=%d\n", hline_time); + hline_time--; + } + + if ((R(hline_time) / 1000) < htot) { + DRM_INFO("++: hline_time=%d\n", hline_time); + hline_time++; + } + + /* all specified in byte-lane clocks */ + writel(hsa_time, base + VID_HSA_TIME); + writel(hbp_time, base + VID_HBP_TIME); + writel(hline_time, base + VID_HLINE_TIME); + + writel(vsw, base + VID_VSA_LINES); + writel(vbp, base + VID_VBP_LINES); + writel(vfp, base + VID_VFP_LINES); + writel(mode->vdisplay, base + VID_VACTIVE_LINES); + writel(mode->hdisplay, base + VID_PKT_SIZE); +} + +static void dsi_set_video_mode_type(void __iomem *base, + struct mipi_phy_register *phy, + unsigned long flags) +{ + u32 val; + u32 mode_mask = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + u32 non_burst_sync_pulse = MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + u32 non_burst_sync_event = MIPI_DSI_MODE_VIDEO; + + /* + * choose video type + */ + if ((flags & mode_mask) == non_burst_sync_pulse) + val = DSI_NON_BURST_SYNC_PULSES; + else if ((flags & mode_mask) == non_burst_sync_event) + val = DSI_NON_BURST_SYNC_EVENTS; + else + val = DSI_BURST_SYNC_PULSES_1; + + writel(val, base + VID_MODE_CFG); + /* TODO: to support LCD panel need to set LP command transfer */ +} + +static void dsi_mipi_init(struct hisi_dsi *dsi) +{ + struct dsi_hw_ctx *ctx = dsi->ctx; + struct mipi_phy_register *phy = &dsi->phy; + struct drm_display_mode *mode = &dsi->cur_mode; + void __iomem *base = ctx->base; + u32 dphy_freq_kHz; + + /* count phy params */ + dphy_freq_kHz = mode->clock * 24 / dsi->lanes; + set_dsi_phy_rate_equal_or_faster(dphy_freq_kHz, phy); + + /* reset Core */ + writel(0, base + PWR_UP); + + /* set phy clocks */ + dsi_mipi_phy_clks(base, phy, dsi->lanes); + + /* set dsi mode */ + dsi_set_mode_timing(base, phy, mode, dsi->format); + + /* set video mode type and low power */ + dsi_set_video_mode_type(base, phy, dsi->mode_flags); + + /* DSI and D-PHY Initialization */ + writel(DSI_VIDEO_MODE, base + MODE_CFG); + writel(BIT(0), base + LPCLK_CTRL); + writel(BIT(0), base + PWR_UP); +} + +static void dsi_encoder_disable(struct drm_encoder *encoder) +{ + struct hisi_dsi *dsi = encoder_to_dsi(encoder); + struct dsi_hw_ctx *ctx = dsi->ctx; + void __iomem *base = ctx->base; + + DRM_DEBUG_DRIVER("enter\n"); + if (!dsi->enable) + return; + + writel(0, base + PWR_UP); + writel(0, base + LPCLK_CTRL); + writel(0, base + PHY_RSTZ); + clk_disable_unprepare(ctx->dsi_cfg_clk); + + dsi->enable = false; + DRM_DEBUG_DRIVER("exit success.\n"); +} + +static void dsi_encoder_enable(struct drm_encoder *encoder) +{ + struct hisi_dsi *dsi = encoder_to_dsi(encoder); + struct dsi_hw_ctx *ctx = dsi->ctx; + int ret; + + DRM_DEBUG_DRIVER("enter.\n"); + if (dsi->enable) + return; + + /* mipi dphy clock enable */ + ret = clk_prepare_enable(ctx->dsi_cfg_clk); + if (ret) { + DRM_ERROR("fail to enable dsi_cfg_clk: %d\n", ret); + return; + } + + dsi_mipi_init(dsi); + + dsi->enable = true; + DRM_DEBUG_DRIVER("exit success.\n"); +} + +static void dsi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct hisi_dsi *dsi = encoder_to_dsi(encoder); + + DRM_DEBUG_DRIVER("enter.\n"); + drm_mode_copy(&dsi->cur_mode, adj_mode); + DRM_DEBUG_DRIVER("exit success.\n"); +} + +static int dsi_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *mode = &crtc_state->mode; + + DRM_DEBUG_DRIVER("enter.\n"); + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + DRM_ERROR("not support INTERLACE mode\n"); + return MODE_NO_INTERLACE; + } + + /* pixel clock support range is (1190494208/64, 1190494208)Hz */ + if (mode->clock < 18602 || mode->clock > 1190494) { + DRM_ERROR("mode clock not support\n"); + return MODE_CLOCK_RANGE; + } + + DRM_DEBUG_DRIVER("exit success.\n"); + return 0; +} + +static const struct drm_encoder_helper_funcs hisi_encoder_helper_funcs = { + .atomic_check = dsi_encoder_atomic_check, + .mode_set = dsi_encoder_mode_set, + .enable = dsi_encoder_enable, + .disable = dsi_encoder_disable +}; + +static const struct drm_encoder_funcs hisi_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int hisi_drm_encoder_init(struct drm_device *dev, + struct drm_encoder *encoder) +{ + int ret; + + encoder->possible_crtcs = 1; + ret = drm_encoder_init(dev, encoder, &hisi_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (ret) { + DRM_ERROR("failed to init dsi encoder\n"); + return ret; + } + + drm_encoder_helper_add(encoder, &hisi_encoder_helper_funcs); + + return 0; +} + +static int dsi_bind(struct device *dev, struct device *master, void *data) +{ + struct dsi_data *ddata = dev_get_drvdata(dev); + struct hisi_dsi *dsi = &ddata->dsi; + struct drm_device *drm_dev = data; + int ret; + + ret = hisi_drm_encoder_init(drm_dev, &dsi->encoder); + if (ret) + return ret; + + return 0; +} + +static void dsi_unbind(struct device *dev, struct device *master, void *data) +{ + /* do nothing */ +} + +static const struct component_ops dsi_ops = { + .bind = dsi_bind, + .unbind = dsi_unbind, +}; + +static int dsi_parse_dt(struct platform_device *pdev, struct hisi_dsi *dsi) +{ + struct dsi_hw_ctx *ctx = dsi->ctx; + struct resource *res; + + + ctx->dsi_cfg_clk = devm_clk_get(&pdev->dev, "pclk_dsi"); + if (IS_ERR(ctx->dsi_cfg_clk)) { + DRM_ERROR("failed to get dsi plck clock\n"); + return PTR_ERR(ctx->dsi_cfg_clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ctx->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ctx->base)) { + DRM_ERROR("failed to remap dsi io region\n"); + return PTR_ERR(ctx->base); + } + + return 0; +} + +static int dsi_probe(struct platform_device *pdev) +{ + struct dsi_data *data; + struct hisi_dsi *dsi; + struct dsi_hw_ctx *ctx; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + DRM_ERROR("failed to allocate dsi data.\n"); + return -ENOMEM; + } + dsi = &data->dsi; + ctx = &data->ctx; + dsi->ctx = ctx; + + ret = dsi_parse_dt(pdev, dsi); + if (ret) + return ret; + + platform_set_drvdata(pdev, data); + + return component_add(&pdev->dev, &dsi_ops); +} + +static int dsi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dsi_ops); + + return 0; +} + +static const struct of_device_id dsi_of_match[] = { + {.compatible = "hisilicon,hi6220-dsi"}, + { } +}; +MODULE_DEVICE_TABLE(of, dsi_of_match); + +static struct platform_driver dsi_driver = { + .probe = dsi_probe, + .remove = dsi_remove, + .driver = { + .name = "hisi-dsi", + .owner = THIS_MODULE, + .of_match_table = dsi_of_match, + }, +}; + +module_platform_driver(dsi_driver); + +MODULE_AUTHOR("Xinliang Liu xinliang.liu@linaro.org"); +MODULE_AUTHOR("Xinliang Liu z.liuxinliang@hisilicon.com"); +MODULE_AUTHOR("Xinwei Kong kong.kongxinwei@hisilicon.com"); +MODULE_DESCRIPTION("hisilicon hi6220 SoC dsi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/hisilicon/hisi_dsi_reg.h b/drivers/gpu/drm/hisilicon/hisi_dsi_reg.h new file mode 100644 index 0000000..db8f9df --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_dsi_reg.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2014-2015 Hisilicon Limited. + * + * 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. + * + */ + +#ifndef __HISI_DSI_REG_H__ +#define __HISI_DSI_REG_H__ + +/* + * regs + */ +#define PWR_UP (0x4) /* Core power-up */ +#define PHY_IF_CFG (0xA4) /* D-PHY interface configuration */ +#define CLKMGR_CFG (0x8) /* the internal clock dividers */ +#define PHY_RSTZ (0xA0) /* D-PHY reset control */ +#define PHY_TST_CTRL0 (0xB4) /* D-PHY test interface control 0 */ +#define PHY_TST_CTRL1 (0xB8) /* D-PHY test interface control 1 */ +#define DPI_VCID (0xC) /* DPI virtual channel id */ +#define DPI_COLOR_CODING (0x10) /* DPI color coding */ +#define DPI_CFG_POL (0x14) /* DPI polarity configuration */ +#define VID_HSA_TIME (0x48) /* Horizontal Sync Active time */ +#define VID_HBP_TIME (0x4C) /* Horizontal Back Porch time */ +#define VID_HLINE_TIME (0x50) /* Line time */ +#define VID_VSA_LINES (0x54) /* Vertical Sync Active period */ +#define VID_VBP_LINES (0x58) /* Vertical Back Porch period */ +#define VID_VFP_LINES (0x5C) /* Vertical Front Porch period */ +#define VID_VACTIVE_LINES (0x60) /* Vertical resolution */ +#define VID_PKT_SIZE (0x3C) /* Video packet size */ +#define VID_MODE_CFG (0x38) /* Video mode configuration */ +#define DPI_LP_CMD_TIM (0x18) /* Low-power command timing config */ +#define PHY_TMR_CFG (0x9C) /* Data lanes timing configuration */ +#define BTA_TO_CNT (0x8C) /* Response timeout definition */ +#define PHY_TMR_LPCLK_CFG (0x98) /* clock lane timing configuration */ +#define CLK_DATA_TMR_CFG (0xCC) +#define LPCLK_CTRL (0x94) /* Low-power in clock lane */ +#define PCKHDL_CFG (0x2C) /* Packet handler configuration */ +#define EDPI_CMD_SIZE (0x64) /* Size for eDPI packets */ +#define MODE_CFG (0x34) /* Video or Command mode selection */ +#define PHY_STATUS (0xB0) /* D-PHY PPI status interface */ + +#define PHY_STOP_WAIT_TIME (0x30) + +/* + * regs relevant enum + */ +enum dpi_color_coding { + DSI_24BITS_1 = 5, +}; + +enum dsi_video_mode_type { + DSI_NON_BURST_SYNC_PULSES = 0, + DSI_NON_BURST_SYNC_EVENTS, + DSI_BURST_SYNC_PULSES_1, + DSI_BURST_SYNC_PULSES_2 +}; + +enum dsi_work_mode { + DSI_VIDEO_MODE = 0, + DSI_COMMAND_MODE +}; + +/* + * regs Write/Read functions + */ +static inline void dsi_phy_tst_set(void __iomem *base, u32 reg, u32 val) +{ + writel(reg, base + PHY_TST_CTRL1); + /* reg addr written at first */ + wmb(); + writel(0x02, base + PHY_TST_CTRL0); + /* cmd1 sent for write */ + wmb(); + writel(0x00, base + PHY_TST_CTRL0); + /* cmd2 sent for write */ + wmb(); + writel(val, base + PHY_TST_CTRL1); + /* Then write data */ + wmb(); + writel(0x02, base + PHY_TST_CTRL0); + /* cmd2 sent for write */ + wmb(); + writel(0x00, base + PHY_TST_CTRL0); +} + +#endif /* __HISI_DRM_DSI_H__ */
On 11/28/2015 04:09 PM, Xinliang Liu wrote:
Add dsi encoder driver for hi6220 SoC.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/Kconfig | 1 + drivers/gpu/drm/hisilicon/Makefile | 3 +- drivers/gpu/drm/hisilicon/hisi_drm_dsi.c | 728 +++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_dsi_reg.h | 89 ++++ 4 files changed, 820 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_dsi.c create mode 100644 drivers/gpu/drm/hisilicon/hisi_dsi_reg.h
diff --git a/drivers/gpu/drm/hisilicon/Kconfig b/drivers/gpu/drm/hisilicon/Kconfig index 70aa8d1..f1c33c2 100644 --- a/drivers/gpu/drm/hisilicon/Kconfig +++ b/drivers/gpu/drm/hisilicon/Kconfig @@ -4,6 +4,7 @@ config DRM_HISI select DRM_KMS_HELPER select DRM_GEM_CMA_HELPER select DRM_KMS_CMA_HELPER
- select DRM_MIPI_DSI help Choose this option if you have a hisilicon chipsets(hi6220). If M is selected the module will be called hisi-drm.
diff --git a/drivers/gpu/drm/hisilicon/Makefile b/drivers/gpu/drm/hisilicon/Makefile index 3433c8b..5083c1f 100644 --- a/drivers/gpu/drm/hisilicon/Makefile +++ b/drivers/gpu/drm/hisilicon/Makefile @@ -1,4 +1,5 @@ hisi-drm-y := hisi_drm_drv.o \
hisi_drm_ade.o
hisi_drm_ade.o \
hisi_drm_dsi.o
obj-$(CONFIG_DRM_HISI) += hisi-drm.o
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c new file mode 100644 index 0000000..7a6cf66 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c @@ -0,0 +1,728 @@ +/*
- Hisilicon hi6220 SoC dsi driver
- Copyright (c) 2014-2015 Hisilicon Limited.
- Author:
- Xinliang Liu xinliang.liu@linaro.org
- Xinliang Liu z.liuxinliang@hisilicon.com
- Xinwei Kong kong.kongxinwei@hisilicon.com
- 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.
- */
+#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_graph.h>
+#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_atomic_helper.h>
+#include "hisi_dsi_reg.h"
+#define MAX_TX_ESC_CLK (10) +#define ROUND(x, y) ((x) / (y) + ((x) % (y) * 10 / (y) >= 5 ? 1 : 0)) +#define DEFAULT_MIPI_CLK_RATE 19200000 +#define DEFAULT_MIPI_CLK_PERIOD_PS (1000000000 / (DEFAULT_MIPI_CLK_RATE / 1000)) +#define R(x) ((u32)((((u64)(x) * (u64)1000 * (u64)mode->clock) / \
phy->lane_byte_clk_kHz)))
+#define encoder_to_dsi(encoder) \
- container_of(encoder, struct hisi_dsi, encoder)
+#define host_to_dsi(host) \
- container_of(host, struct hisi_dsi, host)
+struct mipi_phy_register {
- u32 clk_t_lpx;
- u32 clk_t_hs_prepare;
- u32 clk_t_hs_zero;
- u32 clk_t_hs_trial;
- u32 clk_t_wakeup;
- u32 data_t_lpx;
- u32 data_t_hs_prepare;
- u32 data_t_hs_zero;
- u32 data_t_hs_trial;
- u32 data_t_ta_go;
- u32 data_t_ta_get;
- u32 data_t_wakeup;
- u32 hstx_ckg_sel;
- u32 pll_fbd_div5f;
- u32 pll_fbd_div1f;
- u32 pll_fbd_2p;
- u32 pll_enbwt;
- u32 pll_fbd_p;
- u32 pll_fbd_s;
- u32 pll_pre_div1p;
- u32 pll_pre_p;
- u32 pll_vco_750M;
- u32 pll_lpf_rs;
- u32 pll_lpf_cs;
- u32 clklp2hs_time;
- u32 clkhs2lp_time;
- u32 lp2hs_time;
- u32 hs2lp_time;
- u32 clk_to_data_delay;
- u32 data_to_clk_delay;
- u32 lane_byte_clk_kHz;
- u32 clk_division;
+};
+struct dsi_hw_ctx {
- void __iomem *base;
- struct clk *dsi_cfg_clk;
+};
+struct hisi_dsi {
- struct drm_encoder encoder;
- struct drm_display_mode cur_mode;
- struct dsi_hw_ctx *ctx;
- struct mipi_phy_register phy;
- u32 lanes;
- enum mipi_dsi_pixel_format format;
- unsigned long mode_flags;
- bool enable;
+};
+struct dsi_data {
- struct hisi_dsi dsi;
- struct dsi_hw_ctx ctx;
+};
+struct dsi_phy_seq_info {
- u32 min_range_kHz;
- u32 max_range_kHz;
- u32 pll_vco_750M;
- u32 hstx_ckg_sel;
+};
+static const struct dsi_phy_seq_info dphy_seq_info[] = {
- { 46000, 62000, 1, 7 },
- { 62000, 93000, 0, 7 },
- { 93000, 125000, 1, 6 },
- { 125000, 187000, 0, 6 },
- { 187000, 250000, 1, 5 },
- { 250000, 375000, 0, 5 },
- { 375000, 500000, 1, 4 },
- { 500000, 750000, 0, 4 },
- { 750000, 1000000, 1, 0 },
- { 1000000, 1500000, 0, 0 }
+};
+static void set_dsi_phy_rate_equal_or_faster(u32 phy_freq_kHz,
struct mipi_phy_register *phy)
+{
- u32 ui = 0;
- u32 cfg_clk_ps = DEFAULT_MIPI_CLK_PERIOD_PS;
- u32 i = 0;
- u32 q_pll = 1;
- u32 m_pll = 0;
- u32 n_pll = 0;
- u32 r_pll = 1;
- u32 m_n = 0;
- u32 m_n_int = 0;
- u64 f_kHz;
- u64 temp;
- u64 tmp_kHz = phy_freq_kHz;
- do {
f_kHz = tmp_kHz;
/* Find the PLL clock range from the table */
for (i = 0; i < ARRAY_SIZE(dphy_seq_info); i++)
if (f_kHz > dphy_seq_info[i].min_range_kHz &&
f_kHz <= dphy_seq_info[i].max_range_kHz)
break;
if (i == ARRAY_SIZE(dphy_seq_info)) {
DRM_ERROR("%lldkHz out of range\n", f_kHz);
return;
}
phy->pll_vco_750M = dphy_seq_info[i].pll_vco_750M;
phy->hstx_ckg_sel = dphy_seq_info[i].hstx_ckg_sel;
if (phy->hstx_ckg_sel <= 7 &&
phy->hstx_ckg_sel >= 4)
q_pll = 0x10 >> (7 - phy->hstx_ckg_sel);
temp = f_kHz * (u64)q_pll * (u64)cfg_clk_ps;
m_n_int = temp / (u64)1000000000;
m_n = (temp % (u64)1000000000) / (u64)100000000;
if (m_n_int % 2 == 0) {
if (m_n * 6 >= 50) {
n_pll = 2;
m_pll = (m_n_int + 1) * n_pll;
} else if (m_n * 6 >= 30) {
n_pll = 3;
m_pll = m_n_int * n_pll + 2;
} else {
n_pll = 1;
m_pll = m_n_int * n_pll;
}
} else {
if (m_n * 6 >= 50) {
n_pll = 1;
m_pll = (m_n_int + 1) * n_pll;
} else if (m_n * 6 >= 30) {
n_pll = 1;
m_pll = (m_n_int + 1) * n_pll;
} else if (m_n * 6 >= 10) {
n_pll = 3;
m_pll = m_n_int * n_pll + 1;
} else {
n_pll = 2;
m_pll = m_n_int * n_pll;
}
}
if (n_pll == 1) {
phy->pll_fbd_p = 0;
phy->pll_pre_div1p = 1;
} else {
phy->pll_fbd_p = n_pll;
phy->pll_pre_div1p = 0;
}
if (phy->pll_fbd_2p <= 7 && phy->pll_fbd_2p >= 4)
r_pll = 0x10 >> (7 - phy->pll_fbd_2p);
if (m_pll == 2) {
phy->pll_pre_p = 0;
phy->pll_fbd_s = 0;
phy->pll_fbd_div1f = 0;
phy->pll_fbd_div5f = 1;
} else if (m_pll >= 2 * 2 * r_pll && m_pll <= 2 * 4 * r_pll) {
phy->pll_pre_p = m_pll / (2 * r_pll);
phy->pll_fbd_s = 0;
phy->pll_fbd_div1f = 1;
phy->pll_fbd_div5f = 0;
} else if (m_pll >= 2 * 5 * r_pll && m_pll <= 2 * 150 * r_pll) {
if (((m_pll / (2 * r_pll)) % 2) == 0) {
phy->pll_pre_p =
(m_pll / (2 * r_pll)) / 2 - 1;
phy->pll_fbd_s =
(m_pll / (2 * r_pll)) % 2 + 2;
} else {
phy->pll_pre_p =
(m_pll / (2 * r_pll)) / 2;
phy->pll_fbd_s =
(m_pll / (2 * r_pll)) % 2;
}
phy->pll_fbd_div1f = 0;
phy->pll_fbd_div5f = 0;
} else {
phy->pll_pre_p = 0;
phy->pll_fbd_s = 0;
phy->pll_fbd_div1f = 0;
phy->pll_fbd_div5f = 1;
}
f_kHz = (u64)1000000000 * (u64)m_pll /
((u64)cfg_clk_ps * (u64)n_pll * (u64)q_pll);
if (f_kHz >= phy_freq_kHz)
break;
tmp_kHz += 10;
- } while (1);
- ui = 1000000 / f_kHz;
- phy->clk_t_lpx = ROUND(50, 8 * ui);
- phy->clk_t_hs_prepare = ROUND(133, 16 * ui) - 1;
- phy->clk_t_hs_zero = ROUND(262, 8 * ui);
- phy->clk_t_hs_trial = 2 * (ROUND(60, 8 * ui) - 1);
- phy->clk_t_wakeup = ROUND(1000000, (cfg_clk_ps / 1000) - 1);
- if (phy->clk_t_wakeup > 0xff)
phy->clk_t_wakeup = 0xff;
- phy->data_t_wakeup = phy->clk_t_wakeup;
- phy->data_t_lpx = phy->clk_t_lpx;
- phy->data_t_hs_prepare = ROUND(125 + 10 * ui, 16 * ui) - 1;
- phy->data_t_hs_zero = ROUND(105 + 6 * ui, 8 * ui);
- phy->data_t_hs_trial = 2 * (ROUND(60 + 4 * ui, 8 * ui) - 1);
- phy->data_t_ta_go = 3;
- phy->data_t_ta_get = 4;
- phy->pll_enbwt = 1;
- phy->clklp2hs_time = ROUND(407, 8 * ui) + 12;
- phy->clkhs2lp_time = ROUND(105 + 12 * ui, 8 * ui);
- phy->lp2hs_time = ROUND(240 + 12 * ui, 8 * ui) + 1;
- phy->hs2lp_time = phy->clkhs2lp_time;
- phy->clk_to_data_delay = 1 + phy->clklp2hs_time;
- phy->data_to_clk_delay = ROUND(60 + 52 * ui, 8 * ui) +
phy->clkhs2lp_time;
- phy->lane_byte_clk_kHz = f_kHz / 8;
- phy->clk_division = phy->lane_byte_clk_kHz / MAX_TX_ESC_CLK;
- if (phy->lane_byte_clk_kHz % MAX_TX_ESC_CLK)
phy->clk_division++;
+}
+static u32 dsi_get_dpi_color_coding(enum mipi_dsi_pixel_format format) +{
- u32 val;
- /* TODO: only support RGB888 now, to support more */
- switch (format) {
- case MIPI_DSI_FMT_RGB888:
val = DSI_24BITS_1;
break;
- default:
val = DSI_24BITS_1;
break;
- }
- return val;
+}
+static void dsi_mipi_phy_clks(void __iomem *base,
struct mipi_phy_register *phy,
u32 lanes)
+{
- u32 delay_count;
- bool is_ready;
- u32 val;
- u32 i;
- /* set lanes value */
- val = (lanes - 1) | (PHY_STOP_WAIT_TIME << 8);
- writel(val, base + PHY_IF_CFG);
- /* set phy clk division */
- val = readl(base + CLKMGR_CFG) | phy->clk_division;
- writel(val, base + CLKMGR_CFG);
- /* clean up phy set param */
- writel(0, base + PHY_RSTZ);
- writel(0, base + PHY_TST_CTRL0);
- writel(1, base + PHY_TST_CTRL0);
- writel(0, base + PHY_TST_CTRL0);
- /* clock lane Timing control - TLPX */
- dsi_phy_tst_set(base, 0x10010, phy->clk_t_lpx);
- /* clock lane Timing control - THS-PREPARE */
- dsi_phy_tst_set(base, 0x10011, phy->clk_t_hs_prepare);
- /* clock lane Timing control - THS-ZERO */
- dsi_phy_tst_set(base, 0x10012, phy->clk_t_hs_zero);
- /* clock lane Timing control - THS-TRAIL */
- dsi_phy_tst_set(base, 0x10013, phy->clk_t_hs_trial);
- /* clock lane Timing control - TWAKEUP */
- dsi_phy_tst_set(base, 0x10014, phy->clk_t_wakeup);
- /* data lane */
- for (i = 0; i < lanes; i++) {
/* Timing control - TLPX*/
dsi_phy_tst_set(base, 0x10020 + (i << 4), phy->data_t_lpx);
/* Timing control - THS-PREPARE */
dsi_phy_tst_set(base, 0x10021 + (i << 4),
phy->data_t_hs_prepare);
/* Timing control - THS-ZERO */
dsi_phy_tst_set(base, 0x10022 + (i << 4), phy->data_t_hs_zero);
/* Timing control - THS-TRAIL */
dsi_phy_tst_set(base, 0x10023 + (i << 4), phy->data_t_hs_trial);
/* Timing control - TTA-GO */
dsi_phy_tst_set(base, 0x10024 + (i << 4), phy->data_t_ta_go);
/* Timing control - TTA-GET */
dsi_phy_tst_set(base, 0x10025 + (i << 4), phy->data_t_ta_get);
/* Timing control - TWAKEUP */
dsi_phy_tst_set(base, 0x10026 + (i << 4), phy->data_t_wakeup);
- }
- /* physical configuration I */
- dsi_phy_tst_set(base, 0x10060, phy->hstx_ckg_sel);
- /* physical configuration pll II */
- val = (phy->pll_fbd_div5f << 5) + (phy->pll_fbd_div1f << 4) +
(phy->pll_fbd_2p << 1) + phy->pll_enbwt;
- dsi_phy_tst_set(base, 0x10063, val);
- /* physical configuration pll II */
- dsi_phy_tst_set(base, 0x10064, phy->pll_fbd_p);
- /* physical configuration pll III */
- dsi_phy_tst_set(base, 0x10065, phy->pll_fbd_s);
- /*physical configuration pll IV*/
- val = (phy->pll_pre_div1p << 7) + phy->pll_pre_p;
- dsi_phy_tst_set(base, 0x10066, val);
- /*physical configuration pll V*/
It would be nice to fix the comment spacing for uniformity.
- val = (phy->pll_vco_750M << 4) + (phy->pll_lpf_rs << 2) +
phy->pll_lpf_cs + BIT(5);
- dsi_phy_tst_set(base, 0x10067, val);
- writel(BIT(2), base + PHY_RSTZ);
- udelay(1);
- writel(BIT(2) | BIT(0), base + PHY_RSTZ);
- udelay(1);
- writel(BIT(2) | BIT(1) | BIT(0), base + PHY_RSTZ);
- usleep_range(1000, 1500);
- /* wait for phy's clock ready */
- delay_count = 0;
- is_ready = false;
- while (1) {
val = readl(base + PHY_STATUS);
if (((BIT(0) | BIT(2)) & val) || delay_count > 100) {
is_ready = (delay_count < 100) ? true : false;
delay_count = 0;
break;
}
udelay(1);
++delay_count;
- }
You could simplify this a bit:
delay_count = 100 while (delay_count) { ... --delay_count; }
if (!delay_count) DRM_INFO("...");
- if (!is_ready)
DRM_INFO("phylock and phystopstateclklane is not ready.\n");
+}
+static void dsi_set_mode_timing(void __iomem *base,
struct mipi_phy_register *phy,
struct drm_display_mode *mode,
enum mipi_dsi_pixel_format format)
+{
- u32 hfp, hbp, hsw, vfp, vbp, vsw;
- u32 hline_time;
- u32 hsa_time;
- u32 hbp_time;
- u32 pixel_clk_kHz;
- int htot, vtot;
- u32 val;
- /* DSI color coding setting */
- val = dsi_get_dpi_color_coding(format);
- writel(val, base + DPI_COLOR_CODING);
- /* DSI format and pol setting */
- val = (mode->flags & DRM_MODE_FLAG_NHSYNC ? 1 : 0) << 2;
- val |= (mode->flags & DRM_MODE_FLAG_NVSYNC ? 1 : 0) << 1;
- writel(val, base + DPI_CFG_POL);
- /*
* The DSI IP accepts vertical timing using lines as normal,
* but horizontal timing is a mixture of pixel-clocks for the
* active region and byte-lane clocks for the blanking-related
* timings. hfp is specified as the total hline_time in byte-
* lane clocks minus hsa, hbp and active.
*/
- pixel_clk_kHz = mode->clock;
- htot = mode->htotal;
- vtot = mode->vtotal;
- hfp = mode->hsync_start - mode->hdisplay;
- hbp = mode->htotal - mode->hsync_end;
- hsw = mode->hsync_end - mode->hsync_start;
- vfp = mode->vsync_start - mode->vdisplay;
- vbp = mode->vtotal - mode->vsync_end;
- vsw = mode->vsync_end - mode->vsync_start;
- if (vsw > 15) {
DRM_INFO("vsw exceeded 15\n");
vtot -= vsw - 15;
vsw = 15;
- }
- hsa_time = (hsw * phy->lane_byte_clk_kHz) / pixel_clk_kHz;
- hbp_time = (hbp * phy->lane_byte_clk_kHz) / pixel_clk_kHz;
- hline_time = (((u64)htot * (u64)phy->lane_byte_clk_kHz)) /
pixel_clk_kHz;
- if ((R(hline_time) / 1000) > htot) {
DRM_INFO("--: hline_time=%d\n", hline_time);
hline_time--;
- }
- if ((R(hline_time) / 1000) < htot) {
Could we use a better macro name here? Also, it would be nice to pass the phy argument to it too. Maybe even move the divide by 1000 in the macro.
DRM_INFO("++: hline_time=%d\n", hline_time);
hline_time++;
- }
- /* all specified in byte-lane clocks */
- writel(hsa_time, base + VID_HSA_TIME);
- writel(hbp_time, base + VID_HBP_TIME);
- writel(hline_time, base + VID_HLINE_TIME);
- writel(vsw, base + VID_VSA_LINES);
- writel(vbp, base + VID_VBP_LINES);
- writel(vfp, base + VID_VFP_LINES);
- writel(mode->vdisplay, base + VID_VACTIVE_LINES);
- writel(mode->hdisplay, base + VID_PKT_SIZE);
+}
+static void dsi_set_video_mode_type(void __iomem *base,
struct mipi_phy_register *phy,
unsigned long flags)
+{
- u32 val;
- u32 mode_mask = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
- u32 non_burst_sync_pulse = MIPI_DSI_MODE_VIDEO |
MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
- u32 non_burst_sync_event = MIPI_DSI_MODE_VIDEO;
- /*
* choose video type
*/
- if ((flags & mode_mask) == non_burst_sync_pulse)
val = DSI_NON_BURST_SYNC_PULSES;
- else if ((flags & mode_mask) == non_burst_sync_event)
val = DSI_NON_BURST_SYNC_EVENTS;
- else
val = DSI_BURST_SYNC_PULSES_1;
- writel(val, base + VID_MODE_CFG);
- /* TODO: to support LCD panel need to set LP command transfer */
+}
+static void dsi_mipi_init(struct hisi_dsi *dsi) +{
- struct dsi_hw_ctx *ctx = dsi->ctx;
- struct mipi_phy_register *phy = &dsi->phy;
- struct drm_display_mode *mode = &dsi->cur_mode;
- void __iomem *base = ctx->base;
- u32 dphy_freq_kHz;
- /* count phy params */
- dphy_freq_kHz = mode->clock * 24 / dsi->lanes;
Maybe replace the 24 above with the bpp in use for future compatibility?
- set_dsi_phy_rate_equal_or_faster(dphy_freq_kHz, phy);
- /* reset Core */
- writel(0, base + PWR_UP);
- /* set phy clocks */
- dsi_mipi_phy_clks(base, phy, dsi->lanes);
- /* set dsi mode */
- dsi_set_mode_timing(base, phy, mode, dsi->format);
- /* set video mode type and low power */
- dsi_set_video_mode_type(base, phy, dsi->mode_flags);
- /* DSI and D-PHY Initialization */
- writel(DSI_VIDEO_MODE, base + MODE_CFG);
- writel(BIT(0), base + LPCLK_CTRL);
- writel(BIT(0), base + PWR_UP);
+}
+static void dsi_encoder_disable(struct drm_encoder *encoder) +{
- struct hisi_dsi *dsi = encoder_to_dsi(encoder);
- struct dsi_hw_ctx *ctx = dsi->ctx;
- void __iomem *base = ctx->base;
- DRM_DEBUG_DRIVER("enter\n");
- if (!dsi->enable)
return;
- writel(0, base + PWR_UP);
- writel(0, base + LPCLK_CTRL);
- writel(0, base + PHY_RSTZ);
- clk_disable_unprepare(ctx->dsi_cfg_clk);
- dsi->enable = false;
- DRM_DEBUG_DRIVER("exit success.\n");
+}
+static void dsi_encoder_enable(struct drm_encoder *encoder) +{
- struct hisi_dsi *dsi = encoder_to_dsi(encoder);
- struct dsi_hw_ctx *ctx = dsi->ctx;
- int ret;
- DRM_DEBUG_DRIVER("enter.\n");
- if (dsi->enable)
return;
- /* mipi dphy clock enable */
- ret = clk_prepare_enable(ctx->dsi_cfg_clk);
- if (ret) {
DRM_ERROR("fail to enable dsi_cfg_clk: %d\n", ret);
return;
- }
- dsi_mipi_init(dsi);
- dsi->enable = true;
- DRM_DEBUG_DRIVER("exit success.\n");
+}
+static void dsi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
+{
- struct hisi_dsi *dsi = encoder_to_dsi(encoder);
- DRM_DEBUG_DRIVER("enter.\n");
- drm_mode_copy(&dsi->cur_mode, adj_mode);
- DRM_DEBUG_DRIVER("exit success.\n");
+}
+static int dsi_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
+{
- struct drm_display_mode *mode = &crtc_state->mode;
- DRM_DEBUG_DRIVER("enter.\n");
- if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
DRM_ERROR("not support INTERLACE mode\n");
return MODE_NO_INTERLACE;
- }
- /* pixel clock support range is (1190494208/64, 1190494208)Hz */
- if (mode->clock < 18602 || mode->clock > 1190494) {
I don't think we need to worry about the maximum limit if we support pixel clocks up to 1190 Mhz :)
DRM_ERROR("mode clock not support\n");
return MODE_CLOCK_RANGE;
- }
- DRM_DEBUG_DRIVER("exit success.\n");
- return 0;
+}
+static const struct drm_encoder_helper_funcs hisi_encoder_helper_funcs = {
- .atomic_check = dsi_encoder_atomic_check,
- .mode_set = dsi_encoder_mode_set,
- .enable = dsi_encoder_enable,
- .disable = dsi_encoder_disable
+};
+static const struct drm_encoder_funcs hisi_encoder_funcs = {
- .destroy = drm_encoder_cleanup,
+};
+static int hisi_drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder)
+{
- int ret;
- encoder->possible_crtcs = 1;
- ret = drm_encoder_init(dev, encoder, &hisi_encoder_funcs,
DRM_MODE_ENCODER_TMDS);
We should have the encoder as DRM_MODE_ENCODER_DSI here, even if the connector is finally hdmi.
- if (ret) {
DRM_ERROR("failed to init dsi encoder\n");
return ret;
- }
- drm_encoder_helper_add(encoder, &hisi_encoder_helper_funcs);
- return 0;
+}
+static int dsi_bind(struct device *dev, struct device *master, void *data) +{
- struct dsi_data *ddata = dev_get_drvdata(dev);
- struct hisi_dsi *dsi = &ddata->dsi;
- struct drm_device *drm_dev = data;
- int ret;
- ret = hisi_drm_encoder_init(drm_dev, &dsi->encoder);
- if (ret)
return ret;
- return 0;
+}
+static void dsi_unbind(struct device *dev, struct device *master, void *data) +{
- /* do nothing */
+}
+static const struct component_ops dsi_ops = {
- .bind = dsi_bind,
- .unbind = dsi_unbind,
+};
+static int dsi_parse_dt(struct platform_device *pdev, struct hisi_dsi *dsi) +{
- struct dsi_hw_ctx *ctx = dsi->ctx;
- struct resource *res;
- ctx->dsi_cfg_clk = devm_clk_get(&pdev->dev, "pclk_dsi");
- if (IS_ERR(ctx->dsi_cfg_clk)) {
DRM_ERROR("failed to get dsi plck clock\n");
return PTR_ERR(ctx->dsi_cfg_clk);
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- ctx->base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(ctx->base)) {
DRM_ERROR("failed to remap dsi io region\n");
return PTR_ERR(ctx->base);
- }
- return 0;
+}
+static int dsi_probe(struct platform_device *pdev) +{
- struct dsi_data *data;
- struct hisi_dsi *dsi;
- struct dsi_hw_ctx *ctx;
- int ret;
- data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
- if (!data) {
DRM_ERROR("failed to allocate dsi data.\n");
return -ENOMEM;
- }
- dsi = &data->dsi;
- ctx = &data->ctx;
- dsi->ctx = ctx;
- ret = dsi_parse_dt(pdev, dsi);
- if (ret)
return ret;
- platform_set_drvdata(pdev, data);
- return component_add(&pdev->dev, &dsi_ops);
+}
+static int dsi_remove(struct platform_device *pdev) +{
- component_del(&pdev->dev, &dsi_ops);
- return 0;
+}
+static const struct of_device_id dsi_of_match[] = {
- {.compatible = "hisilicon,hi6220-dsi"},
- { }
+}; +MODULE_DEVICE_TABLE(of, dsi_of_match);
+static struct platform_driver dsi_driver = {
- .probe = dsi_probe,
- .remove = dsi_remove,
- .driver = {
.name = "hisi-dsi",
.owner = THIS_MODULE,
.of_match_table = dsi_of_match,
- },
+};
+module_platform_driver(dsi_driver);
+MODULE_AUTHOR("Xinliang Liu xinliang.liu@linaro.org"); +MODULE_AUTHOR("Xinliang Liu z.liuxinliang@hisilicon.com"); +MODULE_AUTHOR("Xinwei Kong kong.kongxinwei@hisilicon.com"); +MODULE_DESCRIPTION("hisilicon hi6220 SoC dsi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/hisilicon/hisi_dsi_reg.h b/drivers/gpu/drm/hisilicon/hisi_dsi_reg.h new file mode 100644 index 0000000..db8f9df --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_dsi_reg.h @@ -0,0 +1,89 @@ +/*
- Copyright (c) 2014-2015 Hisilicon Limited.
- 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.
- */
+#ifndef __HISI_DSI_REG_H__ +#define __HISI_DSI_REG_H__
+/*
- regs
- */
+#define PWR_UP (0x4) /* Core power-up */ +#define PHY_IF_CFG (0xA4) /* D-PHY interface configuration */ +#define CLKMGR_CFG (0x8) /* the internal clock dividers */ +#define PHY_RSTZ (0xA0) /* D-PHY reset control */ +#define PHY_TST_CTRL0 (0xB4) /* D-PHY test interface control 0 */ +#define PHY_TST_CTRL1 (0xB8) /* D-PHY test interface control 1 */ +#define DPI_VCID (0xC) /* DPI virtual channel id */ +#define DPI_COLOR_CODING (0x10) /* DPI color coding */ +#define DPI_CFG_POL (0x14) /* DPI polarity configuration */ +#define VID_HSA_TIME (0x48) /* Horizontal Sync Active time */ +#define VID_HBP_TIME (0x4C) /* Horizontal Back Porch time */ +#define VID_HLINE_TIME (0x50) /* Line time */ +#define VID_VSA_LINES (0x54) /* Vertical Sync Active period */ +#define VID_VBP_LINES (0x58) /* Vertical Back Porch period */ +#define VID_VFP_LINES (0x5C) /* Vertical Front Porch period */ +#define VID_VACTIVE_LINES (0x60) /* Vertical resolution */ +#define VID_PKT_SIZE (0x3C) /* Video packet size */ +#define VID_MODE_CFG (0x38) /* Video mode configuration */ +#define DPI_LP_CMD_TIM (0x18) /* Low-power command timing config */ +#define PHY_TMR_CFG (0x9C) /* Data lanes timing configuration */ +#define BTA_TO_CNT (0x8C) /* Response timeout definition */ +#define PHY_TMR_LPCLK_CFG (0x98) /* clock lane timing configuration */ +#define CLK_DATA_TMR_CFG (0xCC) +#define LPCLK_CTRL (0x94) /* Low-power in clock lane */ +#define PCKHDL_CFG (0x2C) /* Packet handler configuration */ +#define EDPI_CMD_SIZE (0x64) /* Size for eDPI packets */ +#define MODE_CFG (0x34) /* Video or Command mode selection */ +#define PHY_STATUS (0xB0) /* D-PHY PPI status interface */
+#define PHY_STOP_WAIT_TIME (0x30)
+/*
- regs relevant enum
- */
+enum dpi_color_coding {
- DSI_24BITS_1 = 5,
+};
+enum dsi_video_mode_type {
- DSI_NON_BURST_SYNC_PULSES = 0,
- DSI_NON_BURST_SYNC_EVENTS,
- DSI_BURST_SYNC_PULSES_1,
- DSI_BURST_SYNC_PULSES_2
+};
+enum dsi_work_mode {
- DSI_VIDEO_MODE = 0,
- DSI_COMMAND_MODE
+};
+/*
- regs Write/Read functions
- */
+static inline void dsi_phy_tst_set(void __iomem *base, u32 reg, u32 val) +{
- writel(reg, base + PHY_TST_CTRL1);
- /* reg addr written at first */
- wmb();
- writel(0x02, base + PHY_TST_CTRL0);
- /* cmd1 sent for write */
- wmb();
- writel(0x00, base + PHY_TST_CTRL0);
- /* cmd2 sent for write */
- wmb();
- writel(val, base + PHY_TST_CTRL1);
- /* Then write data */
- wmb();
- writel(0x02, base + PHY_TST_CTRL0);
- /* cmd2 sent for write */
- wmb();
- writel(0x00, base + PHY_TST_CTRL0);
+}
This doesn't look like something that should be inlined. Maybe move it to hisi_drm_dsi.c?
+#endif /* __HISI_DRM_DSI_H__ */
On 1 December 2015 at 16:58, Archit Taneja architt@codeaurora.org wrote:
Hi Archit , thank you for review.
On 11/28/2015 04:09 PM, Xinliang Liu wrote:
Add dsi encoder driver for hi6220 SoC.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/Kconfig | 1 + drivers/gpu/drm/hisilicon/Makefile | 3 +- drivers/gpu/drm/hisilicon/hisi_drm_dsi.c | 728 +++++++++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hisi_dsi_reg.h | 89 ++++ 4 files changed, 820 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hisi_drm_dsi.c create mode 100644 drivers/gpu/drm/hisilicon/hisi_dsi_reg.h
diff --git a/drivers/gpu/drm/hisilicon/Kconfig b/drivers/gpu/drm/hisilicon/Kconfig index 70aa8d1..f1c33c2 100644 --- a/drivers/gpu/drm/hisilicon/Kconfig +++ b/drivers/gpu/drm/hisilicon/Kconfig @@ -4,6 +4,7 @@ config DRM_HISI select DRM_KMS_HELPER select DRM_GEM_CMA_HELPER select DRM_KMS_CMA_HELPER
select DRM_MIPI_DSI help Choose this option if you have a hisilicon chipsets(hi6220). If M is selected the module will be called hisi-drm.
diff --git a/drivers/gpu/drm/hisilicon/Makefile b/drivers/gpu/drm/hisilicon/Makefile index 3433c8b..5083c1f 100644 --- a/drivers/gpu/drm/hisilicon/Makefile +++ b/drivers/gpu/drm/hisilicon/Makefile @@ -1,4 +1,5 @@ hisi-drm-y := hisi_drm_drv.o \
hisi_drm_ade.o
hisi_drm_ade.o \
hisi_drm_dsi.o
obj-$(CONFIG_DRM_HISI) += hisi-drm.o
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c new file mode 100644 index 0000000..7a6cf66 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c @@ -0,0 +1,728 @@ +/*
- Hisilicon hi6220 SoC dsi driver
- Copyright (c) 2014-2015 Hisilicon Limited.
- Author:
Xinliang Liu <xinliang.liu@linaro.org>
Xinliang Liu <z.liuxinliang@hisilicon.com>
Xinwei Kong <kong.kongxinwei@hisilicon.com>
- 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.
- */
+#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_graph.h>
+#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_atomic_helper.h>
+#include "hisi_dsi_reg.h"
+#define MAX_TX_ESC_CLK (10) +#define ROUND(x, y) ((x) / (y) + ((x) % (y) * 10 / (y) >= 5 ? 1 : 0)) +#define DEFAULT_MIPI_CLK_RATE 19200000 +#define DEFAULT_MIPI_CLK_PERIOD_PS (1000000000 / (DEFAULT_MIPI_CLK_RATE / 1000)) +#define R(x) ((u32)((((u64)(x) * (u64)1000 * (u64)mode->clock) / \
phy->lane_byte_clk_kHz)))
+#define encoder_to_dsi(encoder) \
container_of(encoder, struct hisi_dsi, encoder)
+#define host_to_dsi(host) \
container_of(host, struct hisi_dsi, host)
+struct mipi_phy_register {
u32 clk_t_lpx;
u32 clk_t_hs_prepare;
u32 clk_t_hs_zero;
u32 clk_t_hs_trial;
u32 clk_t_wakeup;
u32 data_t_lpx;
u32 data_t_hs_prepare;
u32 data_t_hs_zero;
u32 data_t_hs_trial;
u32 data_t_ta_go;
u32 data_t_ta_get;
u32 data_t_wakeup;
u32 hstx_ckg_sel;
u32 pll_fbd_div5f;
u32 pll_fbd_div1f;
u32 pll_fbd_2p;
u32 pll_enbwt;
u32 pll_fbd_p;
u32 pll_fbd_s;
u32 pll_pre_div1p;
u32 pll_pre_p;
u32 pll_vco_750M;
u32 pll_lpf_rs;
u32 pll_lpf_cs;
u32 clklp2hs_time;
u32 clkhs2lp_time;
u32 lp2hs_time;
u32 hs2lp_time;
u32 clk_to_data_delay;
u32 data_to_clk_delay;
u32 lane_byte_clk_kHz;
u32 clk_division;
+};
+struct dsi_hw_ctx {
void __iomem *base;
struct clk *dsi_cfg_clk;
+};
+struct hisi_dsi {
struct drm_encoder encoder;
struct drm_display_mode cur_mode;
struct dsi_hw_ctx *ctx;
struct mipi_phy_register phy;
u32 lanes;
enum mipi_dsi_pixel_format format;
unsigned long mode_flags;
bool enable;
+};
+struct dsi_data {
struct hisi_dsi dsi;
struct dsi_hw_ctx ctx;
+};
+struct dsi_phy_seq_info {
u32 min_range_kHz;
u32 max_range_kHz;
u32 pll_vco_750M;
u32 hstx_ckg_sel;
+};
+static const struct dsi_phy_seq_info dphy_seq_info[] = {
{ 46000, 62000, 1, 7 },
{ 62000, 93000, 0, 7 },
{ 93000, 125000, 1, 6 },
{ 125000, 187000, 0, 6 },
{ 187000, 250000, 1, 5 },
{ 250000, 375000, 0, 5 },
{ 375000, 500000, 1, 4 },
{ 500000, 750000, 0, 4 },
{ 750000, 1000000, 1, 0 },
{ 1000000, 1500000, 0, 0 }
+};
+static void set_dsi_phy_rate_equal_or_faster(u32 phy_freq_kHz,
struct mipi_phy_register
*phy) +{
u32 ui = 0;
u32 cfg_clk_ps = DEFAULT_MIPI_CLK_PERIOD_PS;
u32 i = 0;
u32 q_pll = 1;
u32 m_pll = 0;
u32 n_pll = 0;
u32 r_pll = 1;
u32 m_n = 0;
u32 m_n_int = 0;
u64 f_kHz;
u64 temp;
u64 tmp_kHz = phy_freq_kHz;
do {
f_kHz = tmp_kHz;
/* Find the PLL clock range from the table */
for (i = 0; i < ARRAY_SIZE(dphy_seq_info); i++)
if (f_kHz > dphy_seq_info[i].min_range_kHz &&
f_kHz <= dphy_seq_info[i].max_range_kHz)
break;
if (i == ARRAY_SIZE(dphy_seq_info)) {
DRM_ERROR("%lldkHz out of range\n", f_kHz);
return;
}
phy->pll_vco_750M = dphy_seq_info[i].pll_vco_750M;
phy->hstx_ckg_sel = dphy_seq_info[i].hstx_ckg_sel;
if (phy->hstx_ckg_sel <= 7 &&
phy->hstx_ckg_sel >= 4)
q_pll = 0x10 >> (7 - phy->hstx_ckg_sel);
temp = f_kHz * (u64)q_pll * (u64)cfg_clk_ps;
m_n_int = temp / (u64)1000000000;
m_n = (temp % (u64)1000000000) / (u64)100000000;
if (m_n_int % 2 == 0) {
if (m_n * 6 >= 50) {
n_pll = 2;
m_pll = (m_n_int + 1) * n_pll;
} else if (m_n * 6 >= 30) {
n_pll = 3;
m_pll = m_n_int * n_pll + 2;
} else {
n_pll = 1;
m_pll = m_n_int * n_pll;
}
} else {
if (m_n * 6 >= 50) {
n_pll = 1;
m_pll = (m_n_int + 1) * n_pll;
} else if (m_n * 6 >= 30) {
n_pll = 1;
m_pll = (m_n_int + 1) * n_pll;
} else if (m_n * 6 >= 10) {
n_pll = 3;
m_pll = m_n_int * n_pll + 1;
} else {
n_pll = 2;
m_pll = m_n_int * n_pll;
}
}
if (n_pll == 1) {
phy->pll_fbd_p = 0;
phy->pll_pre_div1p = 1;
} else {
phy->pll_fbd_p = n_pll;
phy->pll_pre_div1p = 0;
}
if (phy->pll_fbd_2p <= 7 && phy->pll_fbd_2p >= 4)
r_pll = 0x10 >> (7 - phy->pll_fbd_2p);
if (m_pll == 2) {
phy->pll_pre_p = 0;
phy->pll_fbd_s = 0;
phy->pll_fbd_div1f = 0;
phy->pll_fbd_div5f = 1;
} else if (m_pll >= 2 * 2 * r_pll && m_pll <= 2 * 4 *
r_pll) {
phy->pll_pre_p = m_pll / (2 * r_pll);
phy->pll_fbd_s = 0;
phy->pll_fbd_div1f = 1;
phy->pll_fbd_div5f = 0;
} else if (m_pll >= 2 * 5 * r_pll && m_pll <= 2 * 150 *
r_pll) {
if (((m_pll / (2 * r_pll)) % 2) == 0) {
phy->pll_pre_p =
(m_pll / (2 * r_pll)) / 2 - 1;
phy->pll_fbd_s =
(m_pll / (2 * r_pll)) % 2 + 2;
} else {
phy->pll_pre_p =
(m_pll / (2 * r_pll)) / 2;
phy->pll_fbd_s =
(m_pll / (2 * r_pll)) % 2;
}
phy->pll_fbd_div1f = 0;
phy->pll_fbd_div5f = 0;
} else {
phy->pll_pre_p = 0;
phy->pll_fbd_s = 0;
phy->pll_fbd_div1f = 0;
phy->pll_fbd_div5f = 1;
}
f_kHz = (u64)1000000000 * (u64)m_pll /
((u64)cfg_clk_ps * (u64)n_pll * (u64)q_pll);
if (f_kHz >= phy_freq_kHz)
break;
tmp_kHz += 10;
} while (1);
ui = 1000000 / f_kHz;
phy->clk_t_lpx = ROUND(50, 8 * ui);
phy->clk_t_hs_prepare = ROUND(133, 16 * ui) - 1;
phy->clk_t_hs_zero = ROUND(262, 8 * ui);
phy->clk_t_hs_trial = 2 * (ROUND(60, 8 * ui) - 1);
phy->clk_t_wakeup = ROUND(1000000, (cfg_clk_ps / 1000) - 1);
if (phy->clk_t_wakeup > 0xff)
phy->clk_t_wakeup = 0xff;
phy->data_t_wakeup = phy->clk_t_wakeup;
phy->data_t_lpx = phy->clk_t_lpx;
phy->data_t_hs_prepare = ROUND(125 + 10 * ui, 16 * ui) - 1;
phy->data_t_hs_zero = ROUND(105 + 6 * ui, 8 * ui);
phy->data_t_hs_trial = 2 * (ROUND(60 + 4 * ui, 8 * ui) - 1);
phy->data_t_ta_go = 3;
phy->data_t_ta_get = 4;
phy->pll_enbwt = 1;
phy->clklp2hs_time = ROUND(407, 8 * ui) + 12;
phy->clkhs2lp_time = ROUND(105 + 12 * ui, 8 * ui);
phy->lp2hs_time = ROUND(240 + 12 * ui, 8 * ui) + 1;
phy->hs2lp_time = phy->clkhs2lp_time;
phy->clk_to_data_delay = 1 + phy->clklp2hs_time;
phy->data_to_clk_delay = ROUND(60 + 52 * ui, 8 * ui) +
phy->clkhs2lp_time;
phy->lane_byte_clk_kHz = f_kHz / 8;
phy->clk_division = phy->lane_byte_clk_kHz / MAX_TX_ESC_CLK;
if (phy->lane_byte_clk_kHz % MAX_TX_ESC_CLK)
phy->clk_division++;
+}
+static u32 dsi_get_dpi_color_coding(enum mipi_dsi_pixel_format format) +{
u32 val;
/* TODO: only support RGB888 now, to support more */
switch (format) {
case MIPI_DSI_FMT_RGB888:
val = DSI_24BITS_1;
break;
default:
val = DSI_24BITS_1;
break;
}
return val;
+}
+static void dsi_mipi_phy_clks(void __iomem *base,
struct mipi_phy_register *phy,
u32 lanes)
+{
u32 delay_count;
bool is_ready;
u32 val;
u32 i;
/* set lanes value */
val = (lanes - 1) | (PHY_STOP_WAIT_TIME << 8);
writel(val, base + PHY_IF_CFG);
/* set phy clk division */
val = readl(base + CLKMGR_CFG) | phy->clk_division;
writel(val, base + CLKMGR_CFG);
/* clean up phy set param */
writel(0, base + PHY_RSTZ);
writel(0, base + PHY_TST_CTRL0);
writel(1, base + PHY_TST_CTRL0);
writel(0, base + PHY_TST_CTRL0);
/* clock lane Timing control - TLPX */
dsi_phy_tst_set(base, 0x10010, phy->clk_t_lpx);
/* clock lane Timing control - THS-PREPARE */
dsi_phy_tst_set(base, 0x10011, phy->clk_t_hs_prepare);
/* clock lane Timing control - THS-ZERO */
dsi_phy_tst_set(base, 0x10012, phy->clk_t_hs_zero);
/* clock lane Timing control - THS-TRAIL */
dsi_phy_tst_set(base, 0x10013, phy->clk_t_hs_trial);
/* clock lane Timing control - TWAKEUP */
dsi_phy_tst_set(base, 0x10014, phy->clk_t_wakeup);
/* data lane */
for (i = 0; i < lanes; i++) {
/* Timing control - TLPX*/
dsi_phy_tst_set(base, 0x10020 + (i << 4),
phy->data_t_lpx);
/* Timing control - THS-PREPARE */
dsi_phy_tst_set(base, 0x10021 + (i << 4),
phy->data_t_hs_prepare);
/* Timing control - THS-ZERO */
dsi_phy_tst_set(base, 0x10022 + (i << 4),
phy->data_t_hs_zero);
/* Timing control - THS-TRAIL */
dsi_phy_tst_set(base, 0x10023 + (i << 4),
phy->data_t_hs_trial);
/* Timing control - TTA-GO */
dsi_phy_tst_set(base, 0x10024 + (i << 4),
phy->data_t_ta_go);
/* Timing control - TTA-GET */
dsi_phy_tst_set(base, 0x10025 + (i << 4),
phy->data_t_ta_get);
/* Timing control - TWAKEUP */
dsi_phy_tst_set(base, 0x10026 + (i << 4),
phy->data_t_wakeup);
}
/* physical configuration I */
dsi_phy_tst_set(base, 0x10060, phy->hstx_ckg_sel);
/* physical configuration pll II */
val = (phy->pll_fbd_div5f << 5) + (phy->pll_fbd_div1f << 4) +
(phy->pll_fbd_2p << 1) + phy->pll_enbwt;
dsi_phy_tst_set(base, 0x10063, val);
/* physical configuration pll II */
dsi_phy_tst_set(base, 0x10064, phy->pll_fbd_p);
/* physical configuration pll III */
dsi_phy_tst_set(base, 0x10065, phy->pll_fbd_s);
/*physical configuration pll IV*/
val = (phy->pll_pre_div1p << 7) + phy->pll_pre_p;
dsi_phy_tst_set(base, 0x10066, val);
/*physical configuration pll V*/
It would be nice to fix the comment spacing for uniformity.
will be fixed in v3.
val = (phy->pll_vco_750M << 4) + (phy->pll_lpf_rs << 2) +
phy->pll_lpf_cs + BIT(5);
dsi_phy_tst_set(base, 0x10067, val);
writel(BIT(2), base + PHY_RSTZ);
udelay(1);
writel(BIT(2) | BIT(0), base + PHY_RSTZ);
udelay(1);
writel(BIT(2) | BIT(1) | BIT(0), base + PHY_RSTZ);
usleep_range(1000, 1500);
/* wait for phy's clock ready */
delay_count = 0;
is_ready = false;
while (1) {
val = readl(base + PHY_STATUS);
if (((BIT(0) | BIT(2)) & val) || delay_count > 100) {
is_ready = (delay_count < 100) ? true : false;
delay_count = 0;
break;
}
udelay(1);
++delay_count;
}
You could simplify this a bit:
delay_count = 100 while (delay_count) { ... --delay_count; } if (!delay_count) DRM_INFO("...");
Great! This looks much more clearly. will be fixed in v3.
if (!is_ready)
DRM_INFO("phylock and phystopstateclklane is not
ready.\n"); +}
+static void dsi_set_mode_timing(void __iomem *base,
struct mipi_phy_register *phy,
struct drm_display_mode *mode,
enum mipi_dsi_pixel_format format)
+{
u32 hfp, hbp, hsw, vfp, vbp, vsw;
u32 hline_time;
u32 hsa_time;
u32 hbp_time;
u32 pixel_clk_kHz;
int htot, vtot;
u32 val;
/* DSI color coding setting */
val = dsi_get_dpi_color_coding(format);
writel(val, base + DPI_COLOR_CODING);
/* DSI format and pol setting */
val = (mode->flags & DRM_MODE_FLAG_NHSYNC ? 1 : 0) << 2;
val |= (mode->flags & DRM_MODE_FLAG_NVSYNC ? 1 : 0) << 1;
writel(val, base + DPI_CFG_POL);
/*
* The DSI IP accepts vertical timing using lines as normal,
* but horizontal timing is a mixture of pixel-clocks for the
* active region and byte-lane clocks for the blanking-related
* timings. hfp is specified as the total hline_time in byte-
* lane clocks minus hsa, hbp and active.
*/
pixel_clk_kHz = mode->clock;
htot = mode->htotal;
vtot = mode->vtotal;
hfp = mode->hsync_start - mode->hdisplay;
hbp = mode->htotal - mode->hsync_end;
hsw = mode->hsync_end - mode->hsync_start;
vfp = mode->vsync_start - mode->vdisplay;
vbp = mode->vtotal - mode->vsync_end;
vsw = mode->vsync_end - mode->vsync_start;
if (vsw > 15) {
DRM_INFO("vsw exceeded 15\n");
vtot -= vsw - 15;
vsw = 15;
}
hsa_time = (hsw * phy->lane_byte_clk_kHz) / pixel_clk_kHz;
hbp_time = (hbp * phy->lane_byte_clk_kHz) / pixel_clk_kHz;
hline_time = (((u64)htot * (u64)phy->lane_byte_clk_kHz)) /
pixel_clk_kHz;
if ((R(hline_time) / 1000) > htot) {
DRM_INFO("--: hline_time=%d\n", hline_time);
hline_time--;
}
if ((R(hline_time) / 1000) < htot) {
Could we use a better macro name here? Also, it would be nice to pass the phy argument to it too. Maybe even move the divide by 1000 in the macro.
Great idea! will be fixed in v3.
DRM_INFO("++: hline_time=%d\n", hline_time);
hline_time++;
}
/* all specified in byte-lane clocks */
writel(hsa_time, base + VID_HSA_TIME);
writel(hbp_time, base + VID_HBP_TIME);
writel(hline_time, base + VID_HLINE_TIME);
writel(vsw, base + VID_VSA_LINES);
writel(vbp, base + VID_VBP_LINES);
writel(vfp, base + VID_VFP_LINES);
writel(mode->vdisplay, base + VID_VACTIVE_LINES);
writel(mode->hdisplay, base + VID_PKT_SIZE);
+}
+static void dsi_set_video_mode_type(void __iomem *base,
struct mipi_phy_register *phy,
unsigned long flags)
+{
u32 val;
u32 mode_mask = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
u32 non_burst_sync_pulse = MIPI_DSI_MODE_VIDEO |
MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
u32 non_burst_sync_event = MIPI_DSI_MODE_VIDEO;
/*
* choose video type
*/
if ((flags & mode_mask) == non_burst_sync_pulse)
val = DSI_NON_BURST_SYNC_PULSES;
else if ((flags & mode_mask) == non_burst_sync_event)
val = DSI_NON_BURST_SYNC_EVENTS;
else
val = DSI_BURST_SYNC_PULSES_1;
writel(val, base + VID_MODE_CFG);
/* TODO: to support LCD panel need to set LP command transfer */
+}
+static void dsi_mipi_init(struct hisi_dsi *dsi) +{
struct dsi_hw_ctx *ctx = dsi->ctx;
struct mipi_phy_register *phy = &dsi->phy;
struct drm_display_mode *mode = &dsi->cur_mode;
void __iomem *base = ctx->base;
u32 dphy_freq_kHz;
/* count phy params */
dphy_freq_kHz = mode->clock * 24 / dsi->lanes;
Maybe replace the 24 above with the bpp in use for future compatibility?
Yes, it is bpp. will be replaced by bpp in v3.
set_dsi_phy_rate_equal_or_faster(dphy_freq_kHz, phy);
/* reset Core */
writel(0, base + PWR_UP);
/* set phy clocks */
dsi_mipi_phy_clks(base, phy, dsi->lanes);
/* set dsi mode */
dsi_set_mode_timing(base, phy, mode, dsi->format);
/* set video mode type and low power */
dsi_set_video_mode_type(base, phy, dsi->mode_flags);
/* DSI and D-PHY Initialization */
writel(DSI_VIDEO_MODE, base + MODE_CFG);
writel(BIT(0), base + LPCLK_CTRL);
writel(BIT(0), base + PWR_UP);
+}
+static void dsi_encoder_disable(struct drm_encoder *encoder) +{
struct hisi_dsi *dsi = encoder_to_dsi(encoder);
struct dsi_hw_ctx *ctx = dsi->ctx;
void __iomem *base = ctx->base;
DRM_DEBUG_DRIVER("enter\n");
if (!dsi->enable)
return;
writel(0, base + PWR_UP);
writel(0, base + LPCLK_CTRL);
writel(0, base + PHY_RSTZ);
clk_disable_unprepare(ctx->dsi_cfg_clk);
dsi->enable = false;
DRM_DEBUG_DRIVER("exit success.\n");
+}
+static void dsi_encoder_enable(struct drm_encoder *encoder) +{
struct hisi_dsi *dsi = encoder_to_dsi(encoder);
struct dsi_hw_ctx *ctx = dsi->ctx;
int ret;
DRM_DEBUG_DRIVER("enter.\n");
if (dsi->enable)
return;
/* mipi dphy clock enable */
ret = clk_prepare_enable(ctx->dsi_cfg_clk);
if (ret) {
DRM_ERROR("fail to enable dsi_cfg_clk: %d\n", ret);
return;
}
dsi_mipi_init(dsi);
dsi->enable = true;
DRM_DEBUG_DRIVER("exit success.\n");
+}
+static void dsi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
+{
struct hisi_dsi *dsi = encoder_to_dsi(encoder);
DRM_DEBUG_DRIVER("enter.\n");
drm_mode_copy(&dsi->cur_mode, adj_mode);
DRM_DEBUG_DRIVER("exit success.\n");
+}
+static int dsi_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state
*conn_state) +{
struct drm_display_mode *mode = &crtc_state->mode;
DRM_DEBUG_DRIVER("enter.\n");
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
DRM_ERROR("not support INTERLACE mode\n");
return MODE_NO_INTERLACE;
}
/* pixel clock support range is (1190494208/64, 1190494208)Hz */
if (mode->clock < 18602 || mode->clock > 1190494) {
I don't think we need to worry about the maximum limit if we support pixel clocks up to 1190 Mhz :)
yea, 1190Mhz is too larger. will be fixed in v3.
DRM_ERROR("mode clock not support\n");
return MODE_CLOCK_RANGE;
}
DRM_DEBUG_DRIVER("exit success.\n");
return 0;
+}
+static const struct drm_encoder_helper_funcs hisi_encoder_helper_funcs = {
.atomic_check = dsi_encoder_atomic_check,
.mode_set = dsi_encoder_mode_set,
.enable = dsi_encoder_enable,
.disable = dsi_encoder_disable
+};
+static const struct drm_encoder_funcs hisi_encoder_funcs = {
.destroy = drm_encoder_cleanup,
+};
+static int hisi_drm_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder)
+{
int ret;
encoder->possible_crtcs = 1;
ret = drm_encoder_init(dev, encoder, &hisi_encoder_funcs,
DRM_MODE_ENCODER_TMDS);
We should have the encoder as DRM_MODE_ENCODER_DSI here, even if the connector is finally hdmi.
Correct! will be fixed in v3.
if (ret) {
DRM_ERROR("failed to init dsi encoder\n");
return ret;
}
drm_encoder_helper_add(encoder, &hisi_encoder_helper_funcs);
return 0;
+}
+static int dsi_bind(struct device *dev, struct device *master, void *data) +{
struct dsi_data *ddata = dev_get_drvdata(dev);
struct hisi_dsi *dsi = &ddata->dsi;
struct drm_device *drm_dev = data;
int ret;
ret = hisi_drm_encoder_init(drm_dev, &dsi->encoder);
if (ret)
return ret;
return 0;
+}
+static void dsi_unbind(struct device *dev, struct device *master, void *data) +{
/* do nothing */
+}
+static const struct component_ops dsi_ops = {
.bind = dsi_bind,
.unbind = dsi_unbind,
+};
+static int dsi_parse_dt(struct platform_device *pdev, struct hisi_dsi *dsi) +{
struct dsi_hw_ctx *ctx = dsi->ctx;
struct resource *res;
ctx->dsi_cfg_clk = devm_clk_get(&pdev->dev, "pclk_dsi");
if (IS_ERR(ctx->dsi_cfg_clk)) {
DRM_ERROR("failed to get dsi plck clock\n");
return PTR_ERR(ctx->dsi_cfg_clk);
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ctx->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(ctx->base)) {
DRM_ERROR("failed to remap dsi io region\n");
return PTR_ERR(ctx->base);
}
return 0;
+}
+static int dsi_probe(struct platform_device *pdev) +{
struct dsi_data *data;
struct hisi_dsi *dsi;
struct dsi_hw_ctx *ctx;
int ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
DRM_ERROR("failed to allocate dsi data.\n");
return -ENOMEM;
}
dsi = &data->dsi;
ctx = &data->ctx;
dsi->ctx = ctx;
ret = dsi_parse_dt(pdev, dsi);
if (ret)
return ret;
platform_set_drvdata(pdev, data);
return component_add(&pdev->dev, &dsi_ops);
+}
+static int dsi_remove(struct platform_device *pdev) +{
component_del(&pdev->dev, &dsi_ops);
return 0;
+}
+static const struct of_device_id dsi_of_match[] = {
{.compatible = "hisilicon,hi6220-dsi"},
{ }
+}; +MODULE_DEVICE_TABLE(of, dsi_of_match);
+static struct platform_driver dsi_driver = {
.probe = dsi_probe,
.remove = dsi_remove,
.driver = {
.name = "hisi-dsi",
.owner = THIS_MODULE,
.of_match_table = dsi_of_match,
},
+};
+module_platform_driver(dsi_driver);
+MODULE_AUTHOR("Xinliang Liu xinliang.liu@linaro.org"); +MODULE_AUTHOR("Xinliang Liu z.liuxinliang@hisilicon.com"); +MODULE_AUTHOR("Xinwei Kong kong.kongxinwei@hisilicon.com"); +MODULE_DESCRIPTION("hisilicon hi6220 SoC dsi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/hisilicon/hisi_dsi_reg.h b/drivers/gpu/drm/hisilicon/hisi_dsi_reg.h new file mode 100644 index 0000000..db8f9df --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hisi_dsi_reg.h @@ -0,0 +1,89 @@ +/*
- Copyright (c) 2014-2015 Hisilicon Limited.
- 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.
- */
+#ifndef __HISI_DSI_REG_H__ +#define __HISI_DSI_REG_H__
+/*
- regs
- */
+#define PWR_UP (0x4) /* Core power-up */ +#define PHY_IF_CFG (0xA4) /* D-PHY interface configuration */ +#define CLKMGR_CFG (0x8) /* the internal clock dividers */ +#define PHY_RSTZ (0xA0) /* D-PHY reset control */ +#define PHY_TST_CTRL0 (0xB4) /* D-PHY test interface control 0 */ +#define PHY_TST_CTRL1 (0xB8) /* D-PHY test interface control 1 */ +#define DPI_VCID (0xC) /* DPI virtual channel id */ +#define DPI_COLOR_CODING (0x10) /* DPI color coding */ +#define DPI_CFG_POL (0x14) /* DPI polarity configuration */ +#define VID_HSA_TIME (0x48) /* Horizontal Sync Active time */ +#define VID_HBP_TIME (0x4C) /* Horizontal Back Porch time */ +#define VID_HLINE_TIME (0x50) /* Line time */ +#define VID_VSA_LINES (0x54) /* Vertical Sync Active period */ +#define VID_VBP_LINES (0x58) /* Vertical Back Porch period */ +#define VID_VFP_LINES (0x5C) /* Vertical Front Porch period */ +#define VID_VACTIVE_LINES (0x60) /* Vertical resolution */ +#define VID_PKT_SIZE (0x3C) /* Video packet size */ +#define VID_MODE_CFG (0x38) /* Video mode configuration */ +#define DPI_LP_CMD_TIM (0x18) /* Low-power command timing config */ +#define PHY_TMR_CFG (0x9C) /* Data lanes timing configuration */ +#define BTA_TO_CNT (0x8C) /* Response timeout definition */ +#define PHY_TMR_LPCLK_CFG (0x98) /* clock lane timing configuration */ +#define CLK_DATA_TMR_CFG (0xCC) +#define LPCLK_CTRL (0x94) /* Low-power in clock lane */ +#define PCKHDL_CFG (0x2C) /* Packet handler configuration */ +#define EDPI_CMD_SIZE (0x64) /* Size for eDPI packets */ +#define MODE_CFG (0x34) /* Video or Command mode selection */ +#define PHY_STATUS (0xB0) /* D-PHY PPI status interface */
+#define PHY_STOP_WAIT_TIME (0x30)
+/*
- regs relevant enum
- */
+enum dpi_color_coding {
DSI_24BITS_1 = 5,
+};
+enum dsi_video_mode_type {
DSI_NON_BURST_SYNC_PULSES = 0,
DSI_NON_BURST_SYNC_EVENTS,
DSI_BURST_SYNC_PULSES_1,
DSI_BURST_SYNC_PULSES_2
+};
+enum dsi_work_mode {
DSI_VIDEO_MODE = 0,
DSI_COMMAND_MODE
+};
+/*
- regs Write/Read functions
- */
+static inline void dsi_phy_tst_set(void __iomem *base, u32 reg, u32 val) +{
writel(reg, base + PHY_TST_CTRL1);
/* reg addr written at first */
wmb();
writel(0x02, base + PHY_TST_CTRL0);
/* cmd1 sent for write */
wmb();
writel(0x00, base + PHY_TST_CTRL0);
/* cmd2 sent for write */
wmb();
writel(val, base + PHY_TST_CTRL1);
/* Then write data */
wmb();
writel(0x02, base + PHY_TST_CTRL0);
/* cmd2 sent for write */
wmb();
writel(0x00, base + PHY_TST_CTRL0);
+}
This doesn't look like something that should be inlined. Maybe move it to hisi_drm_dsi.c?
yes, you are right. will be fixed in v3.
Thanks, -xinliang
+#endif /* __HISI_DRM_DSI_H__ */
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
Add dsi host driver for hi6220 SoC.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org --- drivers/gpu/drm/hisilicon/hisi_drm_dsi.c | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+)
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c index 7a6cf66..066e08d 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c @@ -78,6 +78,7 @@ struct dsi_hw_ctx {
struct hisi_dsi { struct drm_encoder encoder; + struct mipi_dsi_host host; struct drm_display_mode cur_mode; struct dsi_hw_ctx *ctx; struct mipi_phy_register phy; @@ -625,6 +626,51 @@ static int hisi_drm_encoder_init(struct drm_device *dev, return 0; }
+static int dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *mdsi) +{ + struct hisi_dsi *dsi = host_to_dsi(host); + + if (mdsi->lanes < 1 || mdsi->lanes > 4) { + DRM_ERROR("dsi device params invalid\n"); + return -EINVAL; + } + + dsi->lanes = mdsi->lanes; + dsi->format = mdsi->format; + dsi->mode_flags = mdsi->mode_flags; + + return 0; +} + +static int dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *mdsi) +{ + /* do nothing */ + return 0; +} + +static struct mipi_dsi_host_ops dsi_host_ops = { + .attach = dsi_host_attach, + .detach = dsi_host_detach, +}; + +static int dsi_host_init(struct device *dev, struct hisi_dsi *dsi) +{ + struct mipi_dsi_host *host = &dsi->host; + int ret; + + host->dev = dev; + host->ops = &dsi_host_ops; + ret = mipi_dsi_host_register(host); + if (ret) { + DRM_ERROR("failed to register dsi host\n"); + return ret; + } + + return 0; +} + static int dsi_bind(struct device *dev, struct device *master, void *data) { struct dsi_data *ddata = dev_get_drvdata(dev); @@ -636,6 +682,10 @@ static int dsi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
+ ret = dsi_host_init(dev, dsi); + if (ret) + return ret; + return 0; }
Add support for external HDMI bridge.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org --- drivers/gpu/drm/hisilicon/hisi_drm_dsi.c | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+)
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c index 066e08d..9e056db 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c @@ -78,6 +78,7 @@ struct dsi_hw_ctx {
struct hisi_dsi { struct drm_encoder encoder; + struct drm_bridge *bridge; struct mipi_dsi_host host; struct drm_display_mode cur_mode; struct dsi_hw_ctx *ctx; @@ -671,6 +672,25 @@ static int dsi_host_init(struct device *dev, struct hisi_dsi *dsi) return 0; }
+static int dsi_bridge_init(struct drm_device *dev, struct hisi_dsi *dsi) +{ + struct drm_encoder *encoder = &dsi->encoder; + struct drm_bridge *bridge = dsi->bridge; + int ret; + + /* associate the bridge to dsi encoder */ + encoder->bridge = bridge; + bridge->encoder = encoder; + + ret = drm_bridge_attach(dev, bridge); + if (ret) { + DRM_ERROR("failed to attach exteranl bridge\n"); + return ret; + } + + return 0; +} + static int dsi_bind(struct device *dev, struct device *master, void *data) { struct dsi_data *ddata = dev_get_drvdata(dev); @@ -686,6 +706,10 @@ static int dsi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
+ ret = dsi_bridge_init(drm_dev, dsi); + if (ret) + return ret; + return 0; }
@@ -702,8 +726,35 @@ static const struct component_ops dsi_ops = { static int dsi_parse_dt(struct platform_device *pdev, struct hisi_dsi *dsi) { struct dsi_hw_ctx *ctx = dsi->ctx; + struct device_node *np = pdev->dev.of_node; + struct device_node *endpoint, *bridge_node; + struct drm_bridge *bridge; struct resource *res;
+ /* + * Get the endpoint node. In our case, dsi has one output port + * to which the external HDMI bridge is connected. + */ + endpoint = of_graph_get_next_endpoint(np, NULL); + if (!endpoint) { + DRM_ERROR("no valid endpoint node\n"); + return -ENODEV; + } + of_node_put(endpoint); + + bridge_node = of_graph_get_remote_port_parent(endpoint); + if (!bridge_node) { + DRM_ERROR("no valid bridge node\n"); + return -ENODEV; + } + of_node_put(bridge_node); + + bridge = of_drm_find_bridge(bridge_node); + if (!bridge) { + DRM_INFO("wait for external HDMI bridge driver.\n"); + return -EPROBE_DEFER; + } + dsi->bridge = bridge;
ctx->dsi_cfg_clk = devm_clk_get(&pdev->dev, "pclk_dsi"); if (IS_ERR(ctx->dsi_cfg_clk)) {
On 11/28/2015 04:09 PM, Xinliang Liu wrote:
Add support for external HDMI bridge.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/hisi_drm_dsi.c | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+)
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c index 066e08d..9e056db 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c @@ -78,6 +78,7 @@ struct dsi_hw_ctx {
struct hisi_dsi { struct drm_encoder encoder;
- struct drm_bridge *bridge; struct mipi_dsi_host host; struct drm_display_mode cur_mode; struct dsi_hw_ctx *ctx;
@@ -671,6 +672,25 @@ static int dsi_host_init(struct device *dev, struct hisi_dsi *dsi) return 0; }
+static int dsi_bridge_init(struct drm_device *dev, struct hisi_dsi *dsi) +{
- struct drm_encoder *encoder = &dsi->encoder;
- struct drm_bridge *bridge = dsi->bridge;
- int ret;
- /* associate the bridge to dsi encoder */
- encoder->bridge = bridge;
- bridge->encoder = encoder;
- ret = drm_bridge_attach(dev, bridge);
- if (ret) {
DRM_ERROR("failed to attach exteranl bridge\n");
s/exteranl/external
return ret;
- }
- return 0;
+}
- static int dsi_bind(struct device *dev, struct device *master, void *data) { struct dsi_data *ddata = dev_get_drvdata(dev);
@@ -686,6 +706,10 @@ static int dsi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
- ret = dsi_bridge_init(drm_dev, dsi);
- if (ret)
return ret;
- return 0; }
@@ -702,8 +726,35 @@ static const struct component_ops dsi_ops = { static int dsi_parse_dt(struct platform_device *pdev, struct hisi_dsi *dsi) { struct dsi_hw_ctx *ctx = dsi->ctx;
struct device_node *np = pdev->dev.of_node;
struct device_node *endpoint, *bridge_node;
struct drm_bridge *bridge; struct resource *res;
/*
* Get the endpoint node. In our case, dsi has one output port
* to which the external HDMI bridge is connected.
*/
endpoint = of_graph_get_next_endpoint(np, NULL);
if (!endpoint) {
DRM_ERROR("no valid endpoint node\n");
return -ENODEV;
}
of_node_put(endpoint);
bridge_node = of_graph_get_remote_port_parent(endpoint);
if (!bridge_node) {
DRM_ERROR("no valid bridge node\n");
return -ENODEV;
}
of_node_put(bridge_node);
bridge = of_drm_find_bridge(bridge_node);
if (!bridge) {
DRM_INFO("wait for external HDMI bridge driver.\n");
return -EPROBE_DEFER;
}
dsi->bridge = bridge;
This could be left for later, but it would be better if the dsi driver registers even if the bridge driver module isn't inserted yet, or happens much later in boot.
We could achieve this by trying to attach the bridge (done in dsi_bridge_init) in the dsi_host_attach callback instead of having it in dsi_bind.
Thanks, Archit
ctx->dsi_cfg_clk = devm_clk_get(&pdev->dev, "pclk_dsi"); if (IS_ERR(ctx->dsi_cfg_clk)) {
On 1 December 2015 at 17:04, Archit Taneja architt@codeaurora.org wrote:
On 11/28/2015 04:09 PM, Xinliang Liu wrote:
Add support for external HDMI bridge.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/hisi_drm_dsi.c | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+)
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c index 066e08d..9e056db 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c @@ -78,6 +78,7 @@ struct dsi_hw_ctx {
struct hisi_dsi { struct drm_encoder encoder;
struct drm_bridge *bridge; struct mipi_dsi_host host; struct drm_display_mode cur_mode; struct dsi_hw_ctx *ctx;
@@ -671,6 +672,25 @@ static int dsi_host_init(struct device *dev, struct hisi_dsi *dsi) return 0; }
+static int dsi_bridge_init(struct drm_device *dev, struct hisi_dsi *dsi) +{
struct drm_encoder *encoder = &dsi->encoder;
struct drm_bridge *bridge = dsi->bridge;
int ret;
/* associate the bridge to dsi encoder */
encoder->bridge = bridge;
bridge->encoder = encoder;
ret = drm_bridge_attach(dev, bridge);
if (ret) {
DRM_ERROR("failed to attach exteranl bridge\n");
s/exteranl/external
will be fixed in v3.
return ret;
}
return 0;
+}
- static int dsi_bind(struct device *dev, struct device *master, void
*data) { struct dsi_data *ddata = dev_get_drvdata(dev); @@ -686,6 +706,10 @@ static int dsi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
ret = dsi_bridge_init(drm_dev, dsi);
if (ret)
return ret;
}return 0;
@@ -702,8 +726,35 @@ static const struct component_ops dsi_ops = { static int dsi_parse_dt(struct platform_device *pdev, struct hisi_dsi *dsi) { struct dsi_hw_ctx *ctx = dsi->ctx;
struct device_node *np = pdev->dev.of_node;
struct device_node *endpoint, *bridge_node;
struct drm_bridge *bridge; struct resource *res;
/*
* Get the endpoint node. In our case, dsi has one output port
* to which the external HDMI bridge is connected.
*/
endpoint = of_graph_get_next_endpoint(np, NULL);
if (!endpoint) {
DRM_ERROR("no valid endpoint node\n");
return -ENODEV;
}
of_node_put(endpoint);
bridge_node = of_graph_get_remote_port_parent(endpoint);
if (!bridge_node) {
DRM_ERROR("no valid bridge node\n");
return -ENODEV;
}
of_node_put(bridge_node);
bridge = of_drm_find_bridge(bridge_node);
if (!bridge) {
DRM_INFO("wait for external HDMI bridge driver.\n");
return -EPROBE_DEFER;
}
dsi->bridge = bridge;
This could be left for later, but it would be better if the dsi driver registers even if the bridge driver module isn't inserted yet, or happens much later in boot.
We could achieve this by trying to attach the bridge (done in dsi_bridge_init) in the dsi_host_attach callback instead of having it in dsi_bind.
Do you mean that it is the right time or place to attach the bridge in dsi_host_attach callback. Why? Because at this time bridge driver must be register?
Thanks, -xinliang
Thanks, Archit
ctx->dsi_cfg_clk = devm_clk_get(&pdev->dev, "pclk_dsi"); if (IS_ERR(ctx->dsi_cfg_clk)) {
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
On 12/01/2015 08:20 PM, Xinliang Liu wrote:
On 1 December 2015 at 17:04, Archit Taneja architt@codeaurora.org wrote:
On 11/28/2015 04:09 PM, Xinliang Liu wrote:
Add support for external HDMI bridge.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/hisi_drm_dsi.c | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+)
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c index 066e08d..9e056db 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c @@ -78,6 +78,7 @@ struct dsi_hw_ctx {
struct hisi_dsi { struct drm_encoder encoder;
struct drm_bridge *bridge; struct mipi_dsi_host host; struct drm_display_mode cur_mode; struct dsi_hw_ctx *ctx;
@@ -671,6 +672,25 @@ static int dsi_host_init(struct device *dev, struct hisi_dsi *dsi) return 0; }
+static int dsi_bridge_init(struct drm_device *dev, struct hisi_dsi *dsi) +{
struct drm_encoder *encoder = &dsi->encoder;
struct drm_bridge *bridge = dsi->bridge;
int ret;
/* associate the bridge to dsi encoder */
encoder->bridge = bridge;
bridge->encoder = encoder;
ret = drm_bridge_attach(dev, bridge);
if (ret) {
DRM_ERROR("failed to attach exteranl bridge\n");
s/exteranl/external
will be fixed in v3.
return ret;
}
return 0;
+}
- static int dsi_bind(struct device *dev, struct device *master, void
*data) { struct dsi_data *ddata = dev_get_drvdata(dev); @@ -686,6 +706,10 @@ static int dsi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
ret = dsi_bridge_init(drm_dev, dsi);
if (ret)
return ret;
}return 0;
@@ -702,8 +726,35 @@ static const struct component_ops dsi_ops = { static int dsi_parse_dt(struct platform_device *pdev, struct hisi_dsi *dsi) { struct dsi_hw_ctx *ctx = dsi->ctx;
struct device_node *np = pdev->dev.of_node;
struct device_node *endpoint, *bridge_node;
struct drm_bridge *bridge; struct resource *res;
/*
* Get the endpoint node. In our case, dsi has one output port
* to which the external HDMI bridge is connected.
*/
endpoint = of_graph_get_next_endpoint(np, NULL);
if (!endpoint) {
DRM_ERROR("no valid endpoint node\n");
return -ENODEV;
}
of_node_put(endpoint);
bridge_node = of_graph_get_remote_port_parent(endpoint);
if (!bridge_node) {
DRM_ERROR("no valid bridge node\n");
return -ENODEV;
}
of_node_put(bridge_node);
bridge = of_drm_find_bridge(bridge_node);
if (!bridge) {
DRM_INFO("wait for external HDMI bridge driver.\n");
return -EPROBE_DEFER;
}
dsi->bridge = bridge;
This could be left for later, but it would be better if the dsi driver registers even if the bridge driver module isn't inserted yet, or happens much later in boot.
We could achieve this by trying to attach the bridge (done in dsi_bridge_init) in the dsi_host_attach callback instead of having it in dsi_bind.
Do you mean that it is the right time or place to attach the bridge in dsi_host_attach callback. Why? Because at this time bridge driver must be register?
The bridge/panel drivers generally call drm_bridge_add/drm_panel_add and mipi_dsi_attach() during probe.
If the driver calls drm_bridge_add() before mipi_dsi_attach() in probe, then 'of_drm_find_bridge()' should succeed in the callback. This, however, is prone to all sorts of race conditions and hence not the best solution.
In the case of panels, a dsi host can always create a dummy connector, and search for a panel (of_drm_find_panel) in its 'detect' helper. Unfortunately, we don't have a free connector in the case of bridges. The connector are, in most cases, created by the bridge driver itself, and the dsi host driver has no say over the connector ops.
I don't have a better solution yet, hence I said it could be left for later :)
Archit
Thanks, -xinliang
Thanks, Archit
ctx->dsi_cfg_clk = devm_clk_get(&pdev->dev, "pclk_dsi"); if (IS_ERR(ctx->dsi_cfg_clk)) {
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
On 2 December 2015 at 16:20, Archit Taneja architt@codeaurora.org wrote:
On 12/01/2015 08:20 PM, Xinliang Liu wrote:
On 1 December 2015 at 17:04, Archit Taneja architt@codeaurora.org wrote:
On 11/28/2015 04:09 PM, Xinliang Liu wrote:
Add support for external HDMI bridge.
Signed-off-by: Xinliang Liu xinliang.liu@linaro.org Signed-off-by: Xinwei Kong kong.kongxinwei@hisilicon.com Signed-off-by: Andy Green andy.green@linaro.org
drivers/gpu/drm/hisilicon/hisi_drm_dsi.c | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+)
diff --git a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c index 066e08d..9e056db 100644 --- a/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c +++ b/drivers/gpu/drm/hisilicon/hisi_drm_dsi.c @@ -78,6 +78,7 @@ struct dsi_hw_ctx {
struct hisi_dsi { struct drm_encoder encoder;
struct drm_bridge *bridge; struct mipi_dsi_host host; struct drm_display_mode cur_mode; struct dsi_hw_ctx *ctx;
@@ -671,6 +672,25 @@ static int dsi_host_init(struct device *dev, struct hisi_dsi *dsi) return 0; }
+static int dsi_bridge_init(struct drm_device *dev, struct hisi_dsi *dsi) +{
struct drm_encoder *encoder = &dsi->encoder;
struct drm_bridge *bridge = dsi->bridge;
int ret;
/* associate the bridge to dsi encoder */
encoder->bridge = bridge;
bridge->encoder = encoder;
ret = drm_bridge_attach(dev, bridge);
if (ret) {
DRM_ERROR("failed to attach exteranl bridge\n");
s/exteranl/external
will be fixed in v3.
return ret;
}
return 0;
+}
- static int dsi_bind(struct device *dev, struct device *master, void
*data) { struct dsi_data *ddata = dev_get_drvdata(dev); @@ -686,6 +706,10 @@ static int dsi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
ret = dsi_bridge_init(drm_dev, dsi);
if (ret)
return ret;
}return 0;
@@ -702,8 +726,35 @@ static const struct component_ops dsi_ops = { static int dsi_parse_dt(struct platform_device *pdev, struct hisi_dsi *dsi) { struct dsi_hw_ctx *ctx = dsi->ctx;
struct device_node *np = pdev->dev.of_node;
struct device_node *endpoint, *bridge_node;
struct drm_bridge *bridge; struct resource *res;
/*
* Get the endpoint node. In our case, dsi has one output port
* to which the external HDMI bridge is connected.
*/
endpoint = of_graph_get_next_endpoint(np, NULL);
if (!endpoint) {
DRM_ERROR("no valid endpoint node\n");
return -ENODEV;
}
of_node_put(endpoint);
bridge_node = of_graph_get_remote_port_parent(endpoint);
if (!bridge_node) {
DRM_ERROR("no valid bridge node\n");
return -ENODEV;
}
of_node_put(bridge_node);
bridge = of_drm_find_bridge(bridge_node);
if (!bridge) {
DRM_INFO("wait for external HDMI bridge driver.\n");
return -EPROBE_DEFER;
}
dsi->bridge = bridge;
This could be left for later, but it would be better if the dsi driver registers even if the bridge driver module isn't inserted yet, or happens much later in boot.
We could achieve this by trying to attach the bridge (done in dsi_bridge_init) in the dsi_host_attach callback instead of having it in dsi_bind.
Do you mean that it is the right time or place to attach the bridge in dsi_host_attach callback. Why? Because at this time bridge driver must be register?
The bridge/panel drivers generally call drm_bridge_add/drm_panel_add and mipi_dsi_attach() during probe.
If the driver calls drm_bridge_add() before mipi_dsi_attach() in probe, then 'of_drm_find_bridge()' should succeed in the callback. This, however, is prone to all sorts of race conditions and hence not the best solution.
In the case of panels, a dsi host can always create a dummy connector, and search for a panel (of_drm_find_panel) in its 'detect' helper. Unfortunately, we don't have a free connector in the case of bridges. The connector are, in most cases, created by the bridge driver itself, and the dsi host driver has no say over the connector ops.
I don't have a better solution yet, hence I said it could be left for later :)
Understand, This is an Initialization dependence of two drivers. One driver need to do something after other driver init. I don't think there is a best solution. I prefer to keep waiting in probe stage.
Thanks, -xinliang
Archit
Thanks, -xinliang
Thanks, Archit
ctx->dsi_cfg_clk = devm_clk_get(&pdev->dev, "pclk_dsi"); if (IS_ERR(ctx->dsi_cfg_clk)) {
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
-- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation
dri-devel@lists.freedesktop.org