Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 03/11] ARM: shmobile: r8a7745: basic SoC support
From: Rob Herring @ 2016-10-31  6:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1638480.5pJxF7pUnz@wasted.cogentembedded.com>

On Sat, Oct 29, 2016 at 01:16:54AM +0300, Sergei Shtylyov wrote:
> Add minimal support for the RZ/G1E (R8A7745) SoC.
> 
> Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>
> 
> ---
>  Documentation/devicetree/bindings/arm/shmobile.txt |    2 ++

Acked-by: Rob Herring <robh@kernel.org>

>  arch/arm/mach-shmobile/Kconfig                     |    4 ++++
>  arch/arm/mach-shmobile/setup-rcar-gen2.c           |    1 +
>  3 files changed, 7 insertions(+)

^ permalink raw reply

* [PATCH 1/3] dt-bindings: firmware: scm: Add MSM8996 DT bindings
From: Rob Herring @ 2016-10-31  6:23 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1477699729-18451-2-git-send-email-spjoshi@codeaurora.org>

On Fri, Oct 28, 2016 at 05:08:47PM -0700, Sarangdhar Joshi wrote:
> Add SCM DT bindings for Qualcomm's MSM8996 platform.
> 
> Signed-off-by: Sarangdhar Joshi <spjoshi@codeaurora.org>
> ---
>  Documentation/devicetree/bindings/firmware/qcom,scm.txt | 2 ++
>  1 file changed, 2 insertions(+)

Acked-by: Rob Herring <robh@kernel.org>

^ permalink raw reply

* [PATCH v3 1/2] drm/bridge: dumb-vga-dac: Support a VDD regulator supply
From: Rob Herring @ 2016-10-31  6:28 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161029110611.28951-2-wens@csie.org>

On Sat, Oct 29, 2016 at 07:06:10PM +0800, Chen-Yu Tsai wrote:
> Some dumb VGA DACs are active components which require external power.
> Add support for specifying a regulator as its power supply.
> 
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
> ---
>  .../bindings/display/bridge/dumb-vga-dac.txt       |  2 ++

For the binding,

Acked-by: Rob Herring <robh@kernel.org>

One code comment below...

>  drivers/gpu/drm/bridge/dumb-vga-dac.c              | 35 ++++++++++++++++++++++
>  2 files changed, 37 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/display/bridge/dumb-vga-dac.txt b/Documentation/devicetree/bindings/display/bridge/dumb-vga-dac.txt
> index 003bc246a270..164cbb15f04c 100644
> --- a/Documentation/devicetree/bindings/display/bridge/dumb-vga-dac.txt
> +++ b/Documentation/devicetree/bindings/display/bridge/dumb-vga-dac.txt
> @@ -16,6 +16,8 @@ graph bindings specified in Documentation/devicetree/bindings/graph.txt.
>  - Video port 0 for RGB input
>  - Video port 1 for VGA output
>  
> +Optional properties:
> +- vdd-supply: Power supply for DAC
>  
>  Example
>  -------
> diff --git a/drivers/gpu/drm/bridge/dumb-vga-dac.c b/drivers/gpu/drm/bridge/dumb-vga-dac.c
> index afec232185a7..59781e031220 100644
> --- a/drivers/gpu/drm/bridge/dumb-vga-dac.c
> +++ b/drivers/gpu/drm/bridge/dumb-vga-dac.c
> @@ -12,6 +12,7 @@
>  
>  #include <linux/module.h>
>  #include <linux/of_graph.h>
> +#include <linux/regulator/consumer.h>
>  
>  #include <drm/drmP.h>
>  #include <drm/drm_atomic_helper.h>
> @@ -23,6 +24,7 @@ struct dumb_vga {
>  	struct drm_connector	connector;
>  
>  	struct i2c_adapter	*ddc;
> +	struct regulator	*vdd;
>  };
>  
>  static inline struct dumb_vga *
> @@ -124,8 +126,33 @@ static int dumb_vga_attach(struct drm_bridge *bridge)
>  	return 0;
>  }
>  
> +static void dumb_vga_enable(struct drm_bridge *bridge)
> +{
> +	struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
> +	int ret;
> +
> +	if (!IS_ERR(vga->vdd)) {

if (IS_ERR())
	return;

...will save some intentation.

> +		ret = regulator_enable(vga->vdd);
> +
> +		if (ret) {
> +			DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
> +			return;
> +		}
> +	}
> +}
> +
> +static void dumb_vga_disable(struct drm_bridge *bridge)
> +{
> +	struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
> +
> +	if (!IS_ERR(vga->vdd))
> +		regulator_disable(vga->vdd);
> +}
> +
>  static const struct drm_bridge_funcs dumb_vga_bridge_funcs = {
>  	.attach		= dumb_vga_attach,
> +	.enable		= dumb_vga_enable,
> +	.disable	= dumb_vga_disable,
>  };
>  
>  static struct i2c_adapter *dumb_vga_retrieve_ddc(struct device *dev)
> @@ -169,6 +196,14 @@ static int dumb_vga_probe(struct platform_device *pdev)
>  		return -ENOMEM;
>  	platform_set_drvdata(pdev, vga);
>  
> +	vga->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
> +	if (IS_ERR(vga->vdd)) {
> +		ret = PTR_ERR(vga->vdd);
> +		if (ret == -EPROBE_DEFER)
> +			return -EPROBE_DEFER;
> +		dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
> +	}
> +
>  	vga->ddc = dumb_vga_retrieve_ddc(&pdev->dev);
>  	if (IS_ERR(vga->ddc)) {
>  		if (PTR_ERR(vga->ddc) == -ENODEV) {
> -- 
> 2.9.3
> 

^ permalink raw reply

* [PATCH] ASoC: sun4i-codec: return error code instead of NULL when create_card fails
From: Chen-Yu Tsai @ 2016-10-31  6:42 UTC (permalink / raw)
  To: linux-arm-kernel

When sun4i_codec_create_card fails, we do not assign a proper error
code to the return value. The return value would be 0 from the previous
function call, or we would have bailed out sooner. This would confuse
the driver core into thinking the device probe succeeded, when in fact
it didn't, leaving various devres based resources lingering.

Make the create_card function pass back a meaningful error code, and
assign it to the return value.

Fixes: 45fb6b6f2aa3 ("ASoC: sunxi: add support for the on-chip codec on
		      early Allwinner SoCs")
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---

In practice this should be really hard to hit, as the only failures in
create_card are memory allocation failures.

Should we mark this for stable?

---
 sound/soc/sunxi/sun4i-codec.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c
index e047ec06d538..a60707761abf 100644
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -765,11 +765,11 @@ static struct snd_soc_card *sun4i_codec_create_card(struct device *dev)
 
 	card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
 	if (!card)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	card->dai_link = sun4i_codec_create_link(dev, &card->num_links);
 	if (!card->dai_link)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	card->dev		= dev;
 	card->name		= "sun4i-codec";
@@ -876,7 +876,8 @@ static int sun4i_codec_probe(struct platform_device *pdev)
 	}
 
 	card = sun4i_codec_create_card(&pdev->dev);
-	if (!card) {
+	if (IS_ERR(card)) {
+		ret = PTR_ERR(card);
 		dev_err(&pdev->dev, "Failed to create our card\n");
 		goto err_unregister_codec;
 	}
-- 
2.10.1

^ permalink raw reply related

* [PATCH v14 1/4] clk: mediatek: Add MT2701 clock support
From: James Liao @ 2016-10-31  6:49 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161028011753.GS26139@codeaurora.org>

Hi Stephen,

On Thu, 2016-10-27 at 18:17 -0700, Stephen Boyd wrote:
> On 10/21, Erin Lo wrote:
> > diff --git a/drivers/clk/mediatek/clk-mt2701-bdp.c b/drivers/clk/mediatek/clk-mt2701-bdp.c
> > new file mode 100644
> > index 0000000..dbf6ab2
> > --- /dev/null
> > +++ b/drivers/clk/mediatek/clk-mt2701-bdp.c
> > @@ -0,0 +1,148 @@
> > +
> > +static int mtk_bdpsys_init(struct platform_device *pdev)
> > +{
> > +	struct clk_onecell_data *clk_data;
> > +	int r;
> > +	struct device_node *node = pdev->dev.of_node;
> > +
> > +	clk_data = mtk_alloc_clk_data(CLK_BDP_NR);
> > +
> > +	mtk_clk_register_gates(node, bdp_clks, ARRAY_SIZE(bdp_clks),
> > +						clk_data);
> > +
> > +	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
> > +
> > +	return r;
> > +}
> > +
> > +static const struct of_device_id of_match_clk_mt2701_bdp[] = {
> > +	{ .compatible = "mediatek,mt2701-bdpsys", },
> > +	{}
> > +};
> > +
> > +static int clk_mt2701_bdp_probe(struct platform_device *pdev)
> > +{
> > +	int r;
> > +
> > +	r = mtk_bdpsys_init(pdev);
> 
> Why not just put the contents of that function here? I don't see
> the point of this.

Because of some historical reasons, we keep mtk_bdpsys_init() for
changing init points between CLK_OF_DECLARE() and probe() more easily. I
can move all contents from mtk_bdpsys_init() here in next patch.

> > +	if (r) {
> > +		dev_err(&pdev->dev,
> > +			"could not register clock provider: %s: %d\n",
> > +			pdev->name, r);
> > +	}
> > +
> > +	return r;
> > +}
> > +
> > +static struct platform_driver clk_mt2701_bdp_drv = {
> > +	.probe = clk_mt2701_bdp_probe,
> > +	.driver = {
> > +		.name = "clk-mt2701-bdp",
> > +		.of_match_table = of_match_clk_mt2701_bdp,
> > +	},
> > +};
> > +
> > +builtin_platform_driver(clk_mt2701_bdp_drv);
> > diff --git a/drivers/clk/mediatek/clk-mt2701-eth.c b/drivers/clk/mediatek/clk-mt2701-eth.c
> > new file mode 100644
> > index 0000000..b2a71a4
> > --- /dev/null
> > +++ b/drivers/clk/mediatek/clk-mt2701-eth.c
> > @@ -0,0 +1,90 @@
> > +/*
> > + * Copyright (c) 2014 MediaTek Inc.
> > + * Author: Shunli Wang <shunli.wang@mediatek.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.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + * GNU General Public License for more details.
> > + */
> > +
> > +#include <linux/clk-provider.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include "clk-mtk.h"
> > +#include "clk-gate.h"
> > +
> > +#include <dt-bindings/clock/mt2701-clk.h>
> > +
> > +static const struct mtk_gate_regs eth_cg_regs = {
> > +	.sta_ofs = 0x0030,
> > +};
> > +
> > +#define GATE_ETH(_id, _name, _parent, _shift) {		\
> > +		.id = _id,				\
> > +		.name = _name,				\
> > +		.parent_name = _parent,			\
> > +		.regs = &eth_cg_regs,			\
> > +		.shift = _shift,			\
> > +		.ops = &mtk_clk_gate_ops_no_setclr_inv,	\
> > +	}
> > +
> > +static const struct mtk_gate eth_clks[] = {
> > +	GATE_ETH(CLK_ETHSYS_HSDMA, "hsdma_clk", "ethif_sel", 5),
> > +	GATE_ETH(CLK_ETHSYS_ESW, "esw_clk", "ethpll_500m_ck", 6),
> > +	GATE_ETH(CLK_ETHSYS_GP2, "gp2_clk", "trgpll", 7),
> > +	GATE_ETH(CLK_ETHSYS_GP1, "gp1_clk", "ethpll_500m_ck", 8),
> > +	GATE_ETH(CLK_ETHSYS_PCM, "pcm_clk", "ethif_sel", 11),
> > +	GATE_ETH(CLK_ETHSYS_GDMA, "gdma_clk", "ethif_sel", 14),
> > +	GATE_ETH(CLK_ETHSYS_I2S, "i2s_clk", "ethif_sel", 17),
> > +	GATE_ETH(CLK_ETHSYS_CRYPTO, "crypto_clk", "ethif_sel", 29),
> > +};
> > +
> > +static int mtk_ethsys_init(struct platform_device *pdev)
> > +{
> > +	struct clk_onecell_data *clk_data;
> > +	int r;
> > +	struct device_node *node = pdev->dev.of_node;
> > +
> > +	clk_data = mtk_alloc_clk_data(CLK_ETHSYS_NR);
> > +
> > +	mtk_clk_register_gates(node, eth_clks, ARRAY_SIZE(eth_clks),
> > +						clk_data);
> > +
> > +	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
> > +
> > +	return r;
> 
> Just return of_clk_add_provider() please.

I'll change it in next patch.

> > +}
> > +
> > +static const struct of_device_id of_match_clk_mt2701_eth[] = {
> > +	{ .compatible = "mediatek,mt2701-ethsys", },
> > +	{}
> > +};
> > +
> > +static int clk_mt2701_eth_probe(struct platform_device *pdev)
> > +{
> > +	int r;
> > +
> > +	r = mtk_ethsys_init(pdev);
> 
> Same comment.

Okay.

> > +	if (r) {
> > +		dev_err(&pdev->dev,
> > +			"could not register clock provider: %s: %d\n",
> > +			pdev->name, r);
> > +	}
> > +
> > +	return r;
> > +}
> > +
> > +static struct platform_driver clk_mt2701_eth_drv = {
> > +	.probe = clk_mt2701_eth_probe,
> > +	.driver = {
> > +		.name = "clk-mt2701-eth",
> > +		.of_match_table = of_match_clk_mt2701_eth,
> > +	},
> > +};
> > +
> > +builtin_platform_driver(clk_mt2701_eth_drv);
> > diff --git a/drivers/clk/mediatek/clk-mt2701-hif.c b/drivers/clk/mediatek/clk-mt2701-hif.c
> > new file mode 100644
> > index 0000000..e2b0039
> > --- /dev/null
> > +++ b/drivers/clk/mediatek/clk-mt2701-hif.c
> > @@ -0,0 +1,87 @@
> > +/*
> > + * Copyright (c) 2014 MediaTek Inc.
> > + * Author: Shunli Wang <shunli.wang@mediatek.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.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + * GNU General Public License for more details.
> > + */
> > +
> > +#include <linux/clk-provider.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include "clk-mtk.h"
> > +#include "clk-gate.h"
> > +
> > +#include <dt-bindings/clock/mt2701-clk.h>
> > +
> > +static const struct mtk_gate_regs hif_cg_regs = {
> > +	.sta_ofs = 0x0030,
> > +};
> > +
> > +#define GATE_HIF(_id, _name, _parent, _shift) {		\
> > +		.id = _id,				\
> > +		.name = _name,				\
> > +		.parent_name = _parent,			\
> > +		.regs = &hif_cg_regs,			\
> > +		.shift = _shift,			\
> > +		.ops = &mtk_clk_gate_ops_no_setclr_inv,	\
> > +	}
> > +
> > +static const struct mtk_gate hif_clks[] = {
> > +	GATE_HIF(CLK_HIFSYS_USB0PHY, "usb0_phy_clk", "ethpll_500m_ck", 21),
> > +	GATE_HIF(CLK_HIFSYS_USB1PHY, "usb1_phy_clk", "ethpll_500m_ck", 22),
> > +	GATE_HIF(CLK_HIFSYS_PCIE0, "pcie0_clk", "ethpll_500m_ck", 24),
> > +	GATE_HIF(CLK_HIFSYS_PCIE1, "pcie1_clk", "ethpll_500m_ck", 25),
> > +	GATE_HIF(CLK_HIFSYS_PCIE2, "pcie2_clk", "ethpll_500m_ck", 26),
> > +};
> > +
> > +static int mtk_hifsys_init(struct platform_device *pdev)
> > +{
> > +	struct clk_onecell_data *clk_data;
> > +	int r;
> > +	struct device_node *node = pdev->dev.of_node;
> > +
> > +	clk_data = mtk_alloc_clk_data(CLK_HIFSYS_NR);
> > +
> > +	mtk_clk_register_gates(node, hif_clks, ARRAY_SIZE(hif_clks),
> > +						clk_data);
> > +
> > +	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
> > +
> > +	return r;
> 
> Just return of_clk_add_provider() please.

Okay.

> > +}
> > +
> > +static const struct of_device_id of_match_clk_mt2701_hif[] = {
> > +	{ .compatible = "mediatek,mt2701-hifsys", },
> > +	{}
> > +};
> > +
> > +static int clk_mt2701_hif_probe(struct platform_device *pdev)
> > +{
> > +	int r;
> > +
> > +	r = mtk_hifsys_init(pdev);
> 
> There's a pattern. Same comments apply for everything that uses
> this style.

I'll change them in next patch.

> > +	if (r) {
> > +		dev_err(&pdev->dev,
> > +			"could not register clock provider: %s: %d\n",
> > +			pdev->name, r);
> > +	}
> > +
> > +	return r;
> > +}
> > +
> > +static struct platform_driver clk_mt2701_hif_drv = {
> > +	.probe = clk_mt2701_hif_probe,
> > +	.driver = {
> > +		.name = "clk-mt2701-hif",
> > +		.of_match_table = of_match_clk_mt2701_hif,
> > +	},
> > +};
> > +
> > +builtin_platform_driver(clk_mt2701_hif_drv);
> [cut a bunch of same drivers]
> > diff --git a/drivers/clk/mediatek/clk-mt2701.c b/drivers/clk/mediatek/clk-mt2701.c
> > new file mode 100644
> > index 0000000..c225256
> > --- /dev/null
> > +++ b/drivers/clk/mediatek/clk-mt2701.c
> > @@ -0,0 +1,1046 @@
> > +/*
> > + * Copyright (c) 2014 MediaTek Inc.
> > + * Author: Shunli Wang <shunli.wang@mediatek.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.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + * GNU General Public License for more details.
> > + */
> > +
> > +#include <linux/clk-provider.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_device.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include "clk-mtk.h"
> > +#include "clk-gate.h"
> > +
> > +#include <dt-bindings/clock/mt2701-clk.h>
> > +
> > +/*
> > + * For some clocks, we don't care what their actual rates are. And these
> > + * clocks may change their rate on different products or different scenarios.
> > + * So we model these clocks' rate as 0, to denote it's not an actual rate.
> > + */
> > +#define DUMMY_RATE		0
> > +
> > +static DEFINE_SPINLOCK(lock);
> 
> Please name this something more mtk specific. mt2701_clk_lock?

Okay.

> > +
> > +static const struct mtk_fixed_clk top_fixed_clks[] = {
> > +	FIXED_CLK(CLK_TOP_DPI, "dpi_ck", "clk26m",
> > +		108 * MHZ),
> > +	FIXED_CLK(CLK_TOP_DMPLL, "dmpll_ck", "clk26m",
> > +		400 * MHZ),
> > +	FIXED_CLK(CLK_TOP_VENCPLL, "vencpll_ck", "clk26m",
> > +		295750000),
> > +	FIXED_CLK(CLK_TOP_HDMI_0_PIX340M, "hdmi_0_pix340m", "clk26m",
> > +		340 * MHZ),
> > +	FIXED_CLK(CLK_TOP_HDMI_0_DEEP340M, "hdmi_0_deep340m", "clk26m",
> > +		340 * MHZ),
> > +	FIXED_CLK(CLK_TOP_HDMI_0_PLL340M, "hdmi_0_pll340m", "clk26m",
> > +		340 * MHZ),
> > +	FIXED_CLK(CLK_TOP_HDMITX_CLKDIG_CTS, "hdmitx_dig_cts", "clk26m",
> > +		300 * MHZ),
> > +	FIXED_CLK(CLK_TOP_HADDS2_FB, "hadds2_fbclk", "clk26m",
> > +		27 * MHZ),
> > +	FIXED_CLK(CLK_TOP_WBG_DIG_416M, "wbg_dig_ck_416m", "clk26m",
> > +		416 * MHZ),
> > +	FIXED_CLK(CLK_TOP_DSI0_LNTC_DSI, "dsi0_lntc_dsi", "clk26m",
> > +		143 * MHZ),
> > +	FIXED_CLK(CLK_TOP_HDMI_SCL_RX, "hdmi_scl_rx", "clk26m",
> > +		27 * MHZ),
> > +	FIXED_CLK(CLK_TOP_AUD_EXT1, "aud_ext1", "clk26m",
> > +		DUMMY_RATE),
> > +	FIXED_CLK(CLK_TOP_AUD_EXT2, "aud_ext2", "clk26m",
> > +		DUMMY_RATE),
> > +	FIXED_CLK(CLK_TOP_NFI1X_PAD, "nfi1x_pad", "clk26m",
> > +		DUMMY_RATE),
> > +};
> > +
> > +static const struct mtk_fixed_factor top_fixed_divs[] = {
> > +	FACTOR(CLK_TOP_SYSPLL, "syspll_ck", "mainpll", 1, 1),
> > +	FACTOR(CLK_TOP_SYSPLL_D2, "syspll_d2", "mainpll", 1, 2),
> > +	FACTOR(CLK_TOP_SYSPLL_D3, "syspll_d3", "mainpll", 1, 3),
> > +	FACTOR(CLK_TOP_SYSPLL_D5, "syspll_d5", "mainpll", 1, 5),
> > +	FACTOR(CLK_TOP_SYSPLL_D7, "syspll_d7", "mainpll", 1, 7),
> > +	FACTOR(CLK_TOP_SYSPLL1_D2, "syspll1_d2", "syspll_d2", 1, 2),
> > +	FACTOR(CLK_TOP_SYSPLL1_D4, "syspll1_d4", "syspll_d2", 1, 4),
> > +	FACTOR(CLK_TOP_SYSPLL1_D8, "syspll1_d8", "syspll_d2", 1, 8),
> > +	FACTOR(CLK_TOP_SYSPLL1_D16, "syspll1_d16", "syspll_d2", 1, 16),
> > +	FACTOR(CLK_TOP_SYSPLL2_D2, "syspll2_d2", "syspll_d3", 1, 2),
> > +	FACTOR(CLK_TOP_SYSPLL2_D4, "syspll2_d4", "syspll_d3", 1, 4),
> > +	FACTOR(CLK_TOP_SYSPLL2_D8, "syspll2_d8", "syspll_d3", 1, 8),
> > +	FACTOR(CLK_TOP_SYSPLL3_D2, "syspll3_d2", "syspll_d5", 1, 2),
> > +	FACTOR(CLK_TOP_SYSPLL3_D4, "syspll3_d4", "syspll_d5", 1, 4),
> > +	FACTOR(CLK_TOP_SYSPLL4_D2, "syspll4_d2", "syspll_d7", 1, 2),
> > +	FACTOR(CLK_TOP_SYSPLL4_D4, "syspll4_d4", "syspll_d7", 1, 4),
> > +
> > +	FACTOR(CLK_TOP_UNIVPLL, "univpll_ck", "univpll", 1, 1),
> > +	FACTOR(CLK_TOP_UNIVPLL_D2, "univpll_d2", "univpll", 1, 2),
> > +	FACTOR(CLK_TOP_UNIVPLL_D3, "univpll_d3", "univpll", 1, 3),
> > +	FACTOR(CLK_TOP_UNIVPLL_D5, "univpll_d5", "univpll", 1, 5),
> > +	FACTOR(CLK_TOP_UNIVPLL_D7, "univpll_d7", "univpll", 1, 7),
> > +	FACTOR(CLK_TOP_UNIVPLL_D26, "univpll_d26", "univpll", 1, 26),
> > +	FACTOR(CLK_TOP_UNIVPLL_D52, "univpll_d52", "univpll", 1, 52),
> > +	FACTOR(CLK_TOP_UNIVPLL_D108, "univpll_d108", "univpll", 1, 108),
> > +	FACTOR(CLK_TOP_USB_PHY48M, "usb_phy48m_ck", "univpll", 1, 26),
> > +	FACTOR(CLK_TOP_UNIVPLL1_D2, "univpll1_d2", "univpll_d2", 1, 2),
> > +	FACTOR(CLK_TOP_UNIVPLL1_D4, "univpll1_d4", "univpll_d2", 1, 4),
> > +	FACTOR(CLK_TOP_UNIVPLL1_D8, "univpll1_d8", "univpll_d2", 1, 8),
> > +	FACTOR(CLK_TOP_8BDAC, "8bdac_ck", "univpll_d2", 1, 1),
> > +	FACTOR(CLK_TOP_UNIVPLL2_D2, "univpll2_d2", "univpll_d3", 1, 2),
> > +	FACTOR(CLK_TOP_UNIVPLL2_D4, "univpll2_d4", "univpll_d3", 1, 4),
> > +	FACTOR(CLK_TOP_UNIVPLL2_D8, "univpll2_d8", "univpll_d3", 1, 8),
> > +	FACTOR(CLK_TOP_UNIVPLL2_D16, "univpll2_d16", "univpll_d3", 1, 16),
> > +	FACTOR(CLK_TOP_UNIVPLL2_D32, "univpll2_d32", "univpll_d3", 1, 32),
> > +	FACTOR(CLK_TOP_UNIVPLL3_D2, "univpll3_d2", "univpll_d5", 1, 2),
> > +	FACTOR(CLK_TOP_UNIVPLL3_D4, "univpll3_d4", "univpll_d5", 1, 4),
> > +	FACTOR(CLK_TOP_UNIVPLL3_D8, "univpll3_d8", "univpll_d5", 1, 8),
> > +
> > +	FACTOR(CLK_TOP_MSDCPLL, "msdcpll_ck", "msdcpll", 1, 1),
> > +	FACTOR(CLK_TOP_MSDCPLL_D2, "msdcpll_d2", "msdcpll", 1, 2),
> > +	FACTOR(CLK_TOP_MSDCPLL_D4, "msdcpll_d4", "msdcpll", 1, 4),
> > +	FACTOR(CLK_TOP_MSDCPLL_D8, "msdcpll_d8", "msdcpll", 1, 8),
> > +
> > +	FACTOR(CLK_TOP_MMPLL, "mmpll_ck", "mmpll", 1, 1),
> > +	FACTOR(CLK_TOP_MMPLL_D2, "mmpll_d2", "mmpll", 1, 2),
> > +
> > +	FACTOR(CLK_TOP_DMPLL_D2, "dmpll_d2", "dmpll_ck", 1, 2),
> > +	FACTOR(CLK_TOP_DMPLL_D4, "dmpll_d4", "dmpll_ck", 1, 4),
> > +	FACTOR(CLK_TOP_DMPLL_X2, "dmpll_x2", "dmpll_ck", 1, 1),
> > +
> > +	FACTOR(CLK_TOP_TVDPLL, "tvdpll_ck", "tvdpll", 1, 1),
> > +	FACTOR(CLK_TOP_TVDPLL_D2, "tvdpll_d2", "tvdpll", 1, 2),
> > +	FACTOR(CLK_TOP_TVDPLL_D4, "tvdpll_d4", "tvdpll", 1, 4),
> > +
> > +	FACTOR(CLK_TOP_VDECPLL, "vdecpll_ck", "vdecpll", 1, 1),
> > +	FACTOR(CLK_TOP_TVD2PLL, "tvd2pll_ck", "tvd2pll", 1, 1),
> > +	FACTOR(CLK_TOP_TVD2PLL_D2, "tvd2pll_d2", "tvd2pll", 1, 2),
> > +
> > +	FACTOR(CLK_TOP_MIPIPLL, "mipipll", "dpi_ck", 1, 1),
> > +	FACTOR(CLK_TOP_MIPIPLL_D2, "mipipll_d2", "dpi_ck", 1, 2),
> > +	FACTOR(CLK_TOP_MIPIPLL_D4, "mipipll_d4", "dpi_ck", 1, 4),
> > +
> > +	FACTOR(CLK_TOP_HDMIPLL, "hdmipll_ck", "hdmitx_dig_cts", 1, 1),
> > +	FACTOR(CLK_TOP_HDMIPLL_D2, "hdmipll_d2", "hdmitx_dig_cts", 1, 2),
> > +	FACTOR(CLK_TOP_HDMIPLL_D3, "hdmipll_d3", "hdmitx_dig_cts", 1, 3),
> > +
> > +	FACTOR(CLK_TOP_ARMPLL_1P3G, "armpll_1p3g_ck", "armpll", 1, 1),
> > +
> > +	FACTOR(CLK_TOP_AUDPLL, "audpll", "audpll_sel", 1, 1),
> > +	FACTOR(CLK_TOP_AUDPLL_D4, "audpll_d4", "audpll_sel", 1, 4),
> > +	FACTOR(CLK_TOP_AUDPLL_D8, "audpll_d8", "audpll_sel", 1, 8),
> > +	FACTOR(CLK_TOP_AUDPLL_D16, "audpll_d16", "audpll_sel", 1, 16),
> > +	FACTOR(CLK_TOP_AUDPLL_D24, "audpll_d24", "audpll_sel", 1, 24),
> > +
> > +	FACTOR(CLK_TOP_AUD1PLL_98M, "aud1pll_98m_ck", "aud1pll", 1, 3),
> > +	FACTOR(CLK_TOP_AUD2PLL_90M, "aud2pll_90m_ck", "aud2pll", 1, 3),
> > +	FACTOR(CLK_TOP_HADDS2PLL_98M, "hadds2pll_98m", "hadds2pll", 1, 3),
> > +	FACTOR(CLK_TOP_HADDS2PLL_294M, "hadds2pll_294m", "hadds2pll", 1, 1),
> > +	FACTOR(CLK_TOP_ETHPLL_500M, "ethpll_500m_ck", "ethpll", 1, 1),
> > +	FACTOR(CLK_TOP_CLK26M_D8, "clk26m_d8", "clk26m", 1, 8),
> > +	FACTOR(CLK_TOP_32K_INTERNAL, "32k_internal", "clk26m", 1, 793),
> > +	FACTOR(CLK_TOP_32K_EXTERNAL, "32k_external", "rtc32k", 1, 1),
> > +};
> > +
> > +static const char * const axi_parents[] = {
> > +	"clk26m",
> > +	"syspll1_d2",
> > +	"syspll_d5",
> > +	"syspll1_d4",
> > +	"univpll_d5",
> > +	"univpll2_d2",
> > +	"mmpll_d2",
> > +	"dmpll_d2"
> > +};
> > +
> > +static const char * const mem_parents[] = {
> > +	"clk26m",
> > +	"dmpll_ck"
> > +};
> > +
> > +static const char * const ddrphycfg_parents[] = {
> > +	"clk26m",
> > +	"syspll1_d8"
> > +};
> > +
> > +static const char * const mm_parents[] = {
> > +	"clk26m",
> > +	"vencpll_ck",
> > +	"syspll1_d2",
> > +	"syspll1_d4",
> > +	"univpll_d5",
> > +	"univpll1_d2",
> > +	"univpll2_d2",
> > +	"dmpll_ck"
> > +};
> > +
> > +static const char * const pwm_parents[] = {
> > +	"clk26m",
> > +	"univpll2_d4",
> > +	"univpll3_d2",
> > +	"univpll1_d4",
> > +};
> > +
> > +static const char * const vdec_parents[] = {
> > +	"clk26m",
> > +	"vdecpll_ck",
> > +	"syspll_d5",
> > +	"syspll1_d4",
> > +	"univpll_d5",
> > +	"univpll2_d2",
> > +	"vencpll_ck",
> > +	"msdcpll_d2",
> > +	"mmpll_d2"
> > +};
> > +
> > +static const char * const mfg_parents[] = {
> > +	"clk26m",
> > +	"mmpll_ck",
> > +	"dmpll_x2_ck",
> > +	"msdcpll_ck",
> > +	"clk26m",
> > +	"syspll_d3",
> > +	"univpll_d3",
> > +	"univpll1_d2"
> > +};
> > +
> > +static const char * const camtg_parents[] = {
> > +	"clk26m",
> > +	"univpll_d26",
> > +	"univpll2_d2",
> > +	"syspll3_d2",
> > +	"syspll3_d4",
> > +	"msdcpll_d2",
> > +	"mmpll_d2"
> > +};
> > +
> > +static const char * const uart_parents[] = {
> > +	"clk26m",
> > +	"univpll2_d8"
> > +};
> > +
> > +static const char * const spi_parents[] = {
> > +	"clk26m",
> > +	"syspll3_d2",
> > +	"syspll4_d2",
> > +	"univpll2_d4",
> > +	"univpll1_d8"
> > +};
> > +
> > +static const char * const usb20_parents[] = {
> > +	"clk26m",
> > +	"univpll1_d8",
> > +	"univpll3_d4"
> > +};
> > +
> > +static const char * const msdc30_parents[] = {
> > +	"clk26m",
> > +	"msdcpll_d2",
> > +	"syspll2_d2",
> > +	"syspll1_d4",
> > +	"univpll1_d4",
> > +	"univpll2_d4"
> > +};
> > +
> > +static const char * const audio_parents[] = {
> > +	"clk26m",
> > +	"syspll1_d16"
> > +};
> > +
> > +static const char * const aud_intbus_parents[] = {
> > +	"clk26m",
> > +	"syspll1_d4",
> > +	"syspll3_d2",
> > +	"syspll4_d2",
> > +	"univpll3_d2",
> > +	"univpll2_d4"
> > +};
> > +
> > +static const char * const pmicspi_parents[] = {
> > +	"clk26m",
> > +	"syspll1_d8",
> > +	"syspll2_d4",
> > +	"syspll4_d2",
> > +	"syspll3_d4",
> > +	"syspll2_d8",
> > +	"syspll1_d16",
> > +	"univpll3_d4",
> > +	"univpll_d26",
> > +	"dmpll_d2",
> > +	"dmpll_d4"
> > +};
> > +
> > +static const char * const scp_parents[] = {
> > +	"clk26m",
> > +	"syspll1_d8",
> > +	"dmpll_d2",
> > +	"dmpll_d4"
> > +};
> > +
> > +static const char * const dpi0_parents[] = {
> > +	"clk26m",
> > +	"mipipll",
> > +	"mipipll_d2",
> > +	"mipipll_d4",
> > +	"clk26m",
> > +	"tvdpll_ck",
> > +	"tvdpll_d2",
> > +	"tvdpll_d4"
> > +};
> > +
> > +static const char * const dpi1_parents[] = {
> > +	"clk26m",
> > +	"tvdpll_ck",
> > +	"tvdpll_d2",
> > +	"tvdpll_d4"
> > +};
> > +
> > +static const char * const tve_parents[] = {
> > +	"clk26m",
> > +	"mipipll",
> > +	"mipipll_d2",
> > +	"mipipll_d4",
> > +	"clk26m",
> > +	"tvdpll_ck",
> > +	"tvdpll_d2",
> > +	"tvdpll_d4"
> > +};
> > +
> > +static const char * const hdmi_parents[] = {
> > +	"clk26m",
> > +	"hdmipll_ck",
> > +	"hdmipll_d2",
> > +	"hdmipll_d3"
> > +};
> > +
> > +static const char * const apll_parents[] = {
> > +	"clk26m",
> > +	"audpll",
> > +	"audpll_d4",
> > +	"audpll_d8",
> > +	"audpll_d16",
> > +	"audpll_d24",
> > +	"clk26m",
> > +	"clk26m"
> > +};
> > +
> > +static const char * const rtc_parents[] = {
> > +	"32k_internal",
> > +	"32k_external",
> > +	"clk26m",
> > +	"univpll3_d8"
> > +};
> > +
> > +static const char * const nfi2x_parents[] = {
> > +	"clk26m",
> > +	"syspll2_d2",
> > +	"syspll_d7",
> > +	"univpll3_d2",
> > +	"syspll2_d4",
> > +	"univpll3_d4",
> > +	"syspll4_d4",
> > +	"clk26m"
> > +};
> > +
> > +static const char * const emmc_hclk_parents[] = {
> > +	"clk26m",
> > +	"syspll1_d2",
> > +	"syspll1_d4",
> > +	"syspll2_d2"
> > +};
> > +
> > +static const char * const flash_parents[] = {
> > +	"clk26m_d8",
> > +	"clk26m",
> > +	"syspll2_d8",
> > +	"syspll3_d4",
> > +	"univpll3_d4",
> > +	"syspll4_d2",
> > +	"syspll2_d4",
> > +	"univpll2_d4"
> > +};
> > +
> > +static const char * const di_parents[] = {
> > +	"clk26m",
> > +	"tvd2pll_ck",
> > +	"tvd2pll_d2",
> > +	"clk26m"
> > +};
> > +
> > +static const char * const nr_osd_parents[] = {
> > +	"clk26m",
> > +	"vencpll_ck",
> > +	"syspll1_d2",
> > +	"syspll1_d4",
> > +	"univpll_d5",
> > +	"univpll1_d2",
> > +	"univpll2_d2",
> > +	"dmpll_ck"
> > +};
> > +
> > +static const char * const hdmirx_bist_parents[] = {
> > +	"clk26m",
> > +	"syspll_d3",
> > +	"clk26m",
> > +	"syspll1_d16",
> > +	"syspll4_d2",
> > +	"syspll1_d4",
> > +	"vencpll_ck",
> > +	"clk26m"
> > +};
> > +
> > +static const char * const intdir_parents[] = {
> > +	"clk26m",
> > +	"mmpll_ck",
> > +	"syspll_d2",
> > +	"univpll_d2"
> > +};
> > +
> > +static const char * const asm_parents[] = {
> > +	"clk26m",
> > +	"univpll2_d4",
> > +	"univpll2_d2",
> > +	"syspll_d5"
> > +};
> > +
> > +static const char * const ms_card_parents[] = {
> > +	"clk26m",
> > +	"univpll3_d8",
> > +	"syspll4_d4"
> > +};
> > +
> > +static const char * const ethif_parents[] = {
> > +	"clk26m",
> > +	"syspll1_d2",
> > +	"syspll_d5",
> > +	"syspll1_d4",
> > +	"univpll_d5",
> > +	"univpll1_d2",
> > +	"dmpll_ck",
> > +	"dmpll_d2"
> > +};
> > +
> > +static const char * const hdmirx_parents[] = {
> > +	"clk26m",
> > +	"univpll_d52"
> > +};
> > +
> > +static const char * const cmsys_parents[] = {
> > +	"clk26m",
> > +	"syspll1_d2",
> > +	"univpll1_d2",
> > +	"univpll_d5",
> > +	"syspll_d5",
> > +	"syspll2_d2",
> > +	"syspll1_d4",
> > +	"syspll3_d2",
> > +	"syspll2_d4",
> > +	"syspll1_d8",
> > +	"clk26m",
> > +	"clk26m",
> > +	"clk26m",
> > +	"clk26m",
> > +	"clk26m"
> > +};
> > +
> > +static const char * const clk_8bdac_parents[] = {
> > +	"32k_internal",
> > +	"8bdac_ck",
> > +	"clk26m",
> > +	"clk26m"
> > +};
> > +
> > +static const char * const aud2dvd_parents[] = {
> > +	"a1sys_hp_ck",
> > +	"a2sys_hp_ck"
> > +};
> > +
> > +static const char * const padmclk_parents[] = {
> > +	"clk26m",
> > +	"univpll_d26",
> > +	"univpll_d52",
> > +	"univpll_d108",
> > +	"univpll2_d8",
> > +	"univpll2_d16",
> > +	"univpll2_d32"
> > +};
> > +
> > +static const char * const aud_mux_parents[] = {
> > +	"clk26m",
> > +	"aud1pll_98m_ck",
> > +	"aud2pll_90m_ck",
> > +	"hadds2pll_98m",
> > +	"audio_ext1_ck",
> > +	"audio_ext2_ck"
> > +};
> > +
> > +static const char * const aud_src_parents[] = {
> > +	"aud_mux1_sel",
> > +	"aud_mux2_sel"
> > +};
> > +
> > +static const char * const cpu_parents[] = {
> > +	"clk26m",
> > +	"armpll",
> > +	"mainpll",
> > +	"mmpll"
> > +};
> > +
> > +static const struct mtk_composite top_muxes[] = {
> > +	MUX_GATE_FLAGS(CLK_TOP_AXI_SEL, "axi_sel", axi_parents,
> > +		0x0040, 0, 3, 7, CLK_IS_CRITICAL),
> > +	MUX_GATE_FLAGS(CLK_TOP_MEM_SEL, "mem_sel", mem_parents,
> > +		0x0040, 8, 1, 15, CLK_IS_CRITICAL),
> > +	MUX_GATE_FLAGS(CLK_TOP_DDRPHYCFG_SEL, "ddrphycfg_sel",
> > +		ddrphycfg_parents, 0x0040, 16, 1, 23, CLK_IS_CRITICAL),
> > +	MUX_GATE(CLK_TOP_MM_SEL, "mm_sel", mm_parents,
> > +		0x0040, 24, 3, 31),
> > +
> > +	MUX_GATE(CLK_TOP_PWM_SEL, "pwm_sel", pwm_parents,
> > +		0x0050, 0, 2, 7),
> > +	MUX_GATE(CLK_TOP_VDEC_SEL, "vdec_sel", vdec_parents,
> > +		0x0050, 8, 4, 15),
> > +	MUX_GATE(CLK_TOP_MFG_SEL, "mfg_sel", mfg_parents,
> > +		0x0050, 16, 3, 23),
> > +	MUX_GATE(CLK_TOP_CAMTG_SEL, "camtg_sel", camtg_parents,
> > +		0x0050, 24, 3, 31),
> > +	MUX_GATE(CLK_TOP_UART_SEL, "uart_sel", uart_parents,
> > +		0x0060, 0, 1, 7),
> > +
> > +	MUX_GATE(CLK_TOP_SPI0_SEL, "spi0_sel", spi_parents,
> > +		0x0060, 8, 3, 15),
> > +	MUX_GATE(CLK_TOP_USB20_SEL, "usb20_sel", usb20_parents,
> > +		0x0060, 16, 2, 23),
> > +	MUX_GATE(CLK_TOP_MSDC30_0_SEL, "msdc30_0_sel", msdc30_parents,
> > +		0x0060, 24, 3, 31),
> > +
> > +	MUX_GATE(CLK_TOP_MSDC30_1_SEL, "msdc30_1_sel", msdc30_parents,
> > +		0x0070, 0, 3, 7),
> > +	MUX_GATE(CLK_TOP_MSDC30_2_SEL, "msdc30_2_sel", msdc30_parents,
> > +		0x0070, 8, 3, 15),
> > +	MUX_GATE(CLK_TOP_AUDIO_SEL, "audio_sel", msdc30_parents,
> > +		0x0070, 16, 1, 23),
> > +	MUX_GATE(CLK_TOP_AUDINTBUS_SEL, "aud_intbus_sel", aud_intbus_parents,
> > +		0x0070, 24, 3, 31),
> > +
> > +	MUX_GATE(CLK_TOP_PMICSPI_SEL, "pmicspi_sel", pmicspi_parents,
> > +		0x0080, 0, 4, 7),
> > +	MUX_GATE(CLK_TOP_SCP_SEL, "scp_sel", scp_parents,
> > +		0x0080, 8, 2, 15),
> > +	MUX_GATE(CLK_TOP_DPI0_SEL, "dpi0_sel", dpi0_parents,
> > +		0x0080, 16, 3, 23),
> > +	MUX_GATE(CLK_TOP_DPI1_SEL, "dpi1_sel", dpi1_parents,
> > +		0x0080, 24, 2, 31),
> > +
> > +	MUX_GATE(CLK_TOP_TVE_SEL, "tve_sel", tve_parents,
> > +		0x0090, 0, 3, 7),
> > +	MUX_GATE(CLK_TOP_HDMI_SEL, "hdmi_sel", hdmi_parents,
> > +		0x0090, 8, 2, 15),
> > +	MUX_GATE(CLK_TOP_APLL_SEL, "apll_sel", apll_parents,
> > +		0x0090, 16, 3, 23),
> > +
> > +	MUX_GATE_FLAGS(CLK_TOP_RTC_SEL, "rtc_sel", rtc_parents,
> > +		0x00A0, 0, 2, 7, CLK_IS_CRITICAL),
> > +	MUX_GATE(CLK_TOP_NFI2X_SEL, "nfi2x_sel", nfi2x_parents,
> > +		0x00A0, 8, 3, 15),
> > +	MUX_GATE(CLK_TOP_EMMC_HCLK_SEL, "emmc_hclk_sel", emmc_hclk_parents,
> > +		0x00A0, 24, 2, 31),
> > +
> > +	MUX_GATE(CLK_TOP_FLASH_SEL, "flash_sel", flash_parents,
> > +		0x00B0, 0, 3, 7),
> > +	MUX_GATE(CLK_TOP_DI_SEL, "di_sel", di_parents,
> > +		0x00B0, 8, 2, 15),
> > +	MUX_GATE(CLK_TOP_NR_SEL, "nr_sel", nr_osd_parents,
> > +		0x00B0, 16, 3, 23),
> > +	MUX_GATE(CLK_TOP_OSD_SEL, "osd_sel", nr_osd_parents,
> > +		0x00B0, 24, 3, 31),
> > +
> > +	MUX_GATE(CLK_TOP_HDMIRX_BIST_SEL, "hdmirx_bist_sel",
> > +		hdmirx_bist_parents, 0x00C0, 0, 3, 7),
> > +	MUX_GATE(CLK_TOP_INTDIR_SEL, "intdir_sel", intdir_parents,
> > +		0x00C0, 8, 2, 15),
> > +	MUX_GATE(CLK_TOP_ASM_I_SEL, "asm_i_sel", asm_parents,
> > +		0x00C0, 16, 2, 23),
> > +	MUX_GATE(CLK_TOP_ASM_M_SEL, "asm_m_sel", asm_parents,
> > +		0x00C0, 24, 3, 31),
> > +
> > +	MUX_GATE(CLK_TOP_ASM_H_SEL, "asm_h_sel", asm_parents,
> > +		0x00D0, 0, 2, 7),
> > +	MUX_GATE(CLK_TOP_MS_CARD_SEL, "ms_card_sel", ms_card_parents,
> > +		0x00D0, 16, 2, 23),
> > +	MUX_GATE(CLK_TOP_ETHIF_SEL, "ethif_sel", ethif_parents,
> > +		0x00D0, 24, 3, 31),
> > +
> > +	MUX_GATE(CLK_TOP_HDMIRX26_24_SEL, "hdmirx26_24_sel", hdmirx_parents,
> > +		0x00E0, 0, 1, 7),
> > +	MUX_GATE(CLK_TOP_MSDC30_3_SEL, "msdc30_3_sel", msdc30_parents,
> > +		0x00E0, 8, 3, 15),
> > +	MUX_GATE(CLK_TOP_CMSYS_SEL, "cmsys_sel", cmsys_parents,
> > +		0x00E0, 16, 4, 23),
> > +
> > +	MUX_GATE(CLK_TOP_SPI1_SEL, "spi2_sel", spi_parents,
> > +		0x00E0, 24, 3, 31),
> > +	MUX_GATE(CLK_TOP_SPI2_SEL, "spi1_sel", spi_parents,
> > +		0x00F0, 0, 3, 7),
> > +	MUX_GATE(CLK_TOP_8BDAC_SEL, "8bdac_sel", clk_8bdac_parents,
> > +		0x00F0, 8, 2, 15),
> > +	MUX_GATE(CLK_TOP_AUD2DVD_SEL, "aud2dvd_sel", aud2dvd_parents,
> > +		0x00F0, 16, 1, 23),
> > +
> > +	MUX(CLK_TOP_PADMCLK_SEL, "padmclk_sel", padmclk_parents,
> > +		0x0100, 0, 3),
> > +
> > +	MUX(CLK_TOP_AUD_MUX1_SEL, "aud_mux1_sel", aud_mux_parents,
> > +		0x012c, 0, 3),
> > +	MUX(CLK_TOP_AUD_MUX2_SEL, "aud_mux2_sel", aud_mux_parents,
> > +		0x012c, 3, 3),
> > +	MUX(CLK_TOP_AUDPLL_MUX_SEL, "audpll_sel", aud_mux_parents,
> > +		0x012c, 6, 3),
> > +	MUX_GATE(CLK_TOP_AUD_K1_SRC_SEL, "aud_k1_src_sel", aud_src_parents,
> > +		0x012c, 15, 1, 23),
> > +	MUX_GATE(CLK_TOP_AUD_K2_SRC_SEL, "aud_k2_src_sel", aud_src_parents,
> > +		0x012c, 16, 1, 24),
> > +	MUX_GATE(CLK_TOP_AUD_K3_SRC_SEL, "aud_k3_src_sel", aud_src_parents,
> > +		0x012c, 17, 1, 25),
> > +	MUX_GATE(CLK_TOP_AUD_K4_SRC_SEL, "aud_k4_src_sel", aud_src_parents,
> > +		0x012c, 18, 1, 26),
> > +	MUX_GATE(CLK_TOP_AUD_K5_SRC_SEL, "aud_k5_src_sel", aud_src_parents,
> > +		0x012c, 19, 1, 27),
> > +	MUX_GATE(CLK_TOP_AUD_K6_SRC_SEL, "aud_k6_src_sel", aud_src_parents,
> > +		0x012c, 20, 1, 28),
> > +};
> > +
> > +static const struct mtk_clk_divider top_adj_divs[] = {
> > +	DIV_ADJ(CLK_TOP_AUD_EXTCK1_DIV, "audio_ext1_ck", "aud_ext1",
> > +		0x0120, 0, 8),
> > +	DIV_ADJ(CLK_TOP_AUD_EXTCK2_DIV, "audio_ext2_ck", "aud_ext2",
> > +		0x0120, 8, 8),
> > +	DIV_ADJ(CLK_TOP_AUD_MUX1_DIV, "aud_mux1_div", "aud_mux1_sel",
> > +		0x0120, 16, 8),
> > +	DIV_ADJ(CLK_TOP_AUD_MUX2_DIV, "aud_mux2_div", "aud_mux2_sel",
> > +		0x0120, 24, 8),
> > +	DIV_ADJ(CLK_TOP_AUD_K1_SRC_DIV, "aud_k1_src_div", "aud_k1_src_sel",
> > +		0x0124, 0, 8),
> > +	DIV_ADJ(CLK_TOP_AUD_K2_SRC_DIV, "aud_k2_src_div", "aud_k2_src_sel",
> > +		0x0124, 8, 8),
> > +	DIV_ADJ(CLK_TOP_AUD_K3_SRC_DIV, "aud_k3_src_div", "aud_k3_src_sel",
> > +		0x0124, 16, 8),
> > +	DIV_ADJ(CLK_TOP_AUD_K4_SRC_DIV, "aud_k4_src_div", "aud_k4_src_sel",
> > +		0x0124, 24, 8),
> > +	DIV_ADJ(CLK_TOP_AUD_K5_SRC_DIV, "aud_k5_src_div", "aud_k5_src_sel",
> > +		0x0128, 0, 8),
> > +	DIV_ADJ(CLK_TOP_AUD_K6_SRC_DIV, "aud_k6_src_div", "aud_k6_src_sel",
> > +		0x0128, 8, 8),
> > +};
> > +
> > +static const struct mtk_gate_regs top_aud_cg_regs = {
> > +	.sta_ofs = 0x012C,
> > +};
> > +
> > +#define GATE_TOP_AUD(_id, _name, _parent, _shift) {	\
> > +		.id = _id,				\
> > +		.name = _name,				\
> > +		.parent_name = _parent,			\
> > +		.regs = &top_aud_cg_regs,		\
> > +		.shift = _shift,			\
> > +		.ops = &mtk_clk_gate_ops_no_setclr,	\
> > +	}
> > +
> > +static const struct mtk_gate top_clks[] = {
> > +	GATE_TOP_AUD(CLK_TOP_AUD_48K_TIMING, "a1sys_hp_ck", "aud_mux1_div",
> > +		21),
> > +	GATE_TOP_AUD(CLK_TOP_AUD_44K_TIMING, "a2sys_hp_ck", "aud_mux2_div",
> > +		22),
> > +	GATE_TOP_AUD(CLK_TOP_AUD_I2S1_MCLK, "aud_i2s1_mclk", "aud_k1_src_div",
> > +		23),
> > +	GATE_TOP_AUD(CLK_TOP_AUD_I2S2_MCLK, "aud_i2s2_mclk", "aud_k2_src_div",
> > +		24),
> > +	GATE_TOP_AUD(CLK_TOP_AUD_I2S3_MCLK, "aud_i2s3_mclk", "aud_k3_src_div",
> > +		25),
> > +	GATE_TOP_AUD(CLK_TOP_AUD_I2S4_MCLK, "aud_i2s4_mclk", "aud_k4_src_div",
> > +		26),
> > +	GATE_TOP_AUD(CLK_TOP_AUD_I2S5_MCLK, "aud_i2s5_mclk", "aud_k5_src_div",
> > +		27),
> > +	GATE_TOP_AUD(CLK_TOP_AUD_I2S6_MCLK, "aud_i2s6_mclk", "aud_k6_src_div",
> > +		28),
> > +};
> > +
> > +void __iomem *devm_of_iomap(struct device *dev, int index)
> 
> Sorry what is this?

To reduce duplicated code of looking up base address.

> > +{
> > +	struct resource res;
> > +
> > +	if (!dev)
> > +		return NULL;
> > +
> > +	if (of_address_to_resource(dev->of_node, index, &res))
> > +		return NULL;
> > +
> > +	return devm_ioremap(dev, res.start, resource_size(&res));
> 
> Can't we just use platform_get_resouce() and
> devm_ioremap_resource() here? It looks like we always have a
> platform device.

I'll change it with these 2 functions.

> > +}
> > +
> > +static int mtk_topckgen_init(struct platform_device *pdev)
> > +{
> > +	struct clk_onecell_data *clk_data;
> > +	void __iomem *base;
> > +	int r;
> > +	struct device_node *node = pdev->dev.of_node;
> > +
> > +	base = devm_of_iomap(&pdev->dev, 0);
> > +	if (!base)
> > +		return -ENOMEM;
> > +
> > +	clk_data = mtk_alloc_clk_data(CLK_TOP_NR);
> > +
> > +	mtk_clk_register_fixed_clks(top_fixed_clks, ARRAY_SIZE(top_fixed_clks),
> > +								clk_data);
> > +
> > +	mtk_clk_register_factors(top_fixed_divs, ARRAY_SIZE(top_fixed_divs),
> > +								clk_data);
> > +
> > +	mtk_clk_register_composites(top_muxes, ARRAY_SIZE(top_muxes),
> > +				base, &lock, clk_data);
> > +
> > +	mtk_clk_register_dividers(top_adj_divs, ARRAY_SIZE(top_adj_divs),
> > +				base, &lock, clk_data);
> > +
> > +	mtk_clk_register_gates(node, top_clks, ARRAY_SIZE(top_clks),
> > +						clk_data);
> > +
> > +	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
> > +
> > +	return r;
> > +}
> > +
> > +static const struct mtk_gate_regs infra_cg_regs = {
> > +	.set_ofs = 0x0040,
> > +	.clr_ofs = 0x0044,
> > +	.sta_ofs = 0x0048,
> > +};
> > +
> > +#define GATE_ICG(_id, _name, _parent, _shift) {		\
> > +		.id = _id,				\
> > +		.name = _name,				\
> > +		.parent_name = _parent,			\
> > +		.regs = &infra_cg_regs,			\
> > +		.shift = _shift,			\
> > +		.ops = &mtk_clk_gate_ops_setclr,	\
> > +	}
> > +
> > +static const struct mtk_gate infra_clks[] = {
> > +	GATE_ICG(CLK_INFRA_DBG, "dbgclk", "axi_sel", 0),
> > +	GATE_ICG(CLK_INFRA_SMI, "smi_ck", "mm_sel", 1),
> > +	GATE_ICG(CLK_INFRA_QAXI_CM4, "cm4_ck", "axi_sel", 2),
> > +	GATE_ICG(CLK_INFRA_AUD_SPLIN_B, "audio_splin_bck", "hadds2pll_294m", 4),
> > +	GATE_ICG(CLK_INFRA_AUDIO, "audio_ck", "clk26m", 5),
> > +	GATE_ICG(CLK_INFRA_EFUSE, "efuse_ck", "clk26m", 6),
> > +	GATE_ICG(CLK_INFRA_L2C_SRAM, "l2c_sram_ck", "mm_sel", 7),
> > +	GATE_ICG(CLK_INFRA_M4U, "m4u_ck", "mem_sel", 8),
> > +	GATE_ICG(CLK_INFRA_CONNMCU, "connsys_bus", "wbg_dig_ck_416m", 12),
> > +	GATE_ICG(CLK_INFRA_TRNG, "trng_ck", "axi_sel", 13),
> > +	GATE_ICG(CLK_INFRA_RAMBUFIF, "rambufif_ck", "mem_sel", 14),
> > +	GATE_ICG(CLK_INFRA_CPUM, "cpum_ck", "mem_sel", 15),
> > +	GATE_ICG(CLK_INFRA_KP, "kp_ck", "axi_sel", 16),
> > +	GATE_ICG(CLK_INFRA_CEC, "cec_ck", "rtc_sel", 18),
> > +	GATE_ICG(CLK_INFRA_IRRX, "irrx_ck", "axi_sel", 19),
> > +	GATE_ICG(CLK_INFRA_PMICSPI, "pmicspi_ck", "pmicspi_sel", 22),
> > +	GATE_ICG(CLK_INFRA_PMICWRAP, "pmicwrap_ck", "axi_sel", 23),
> > +	GATE_ICG(CLK_INFRA_DDCCI, "ddcci_ck", "axi_sel", 24),
> > +};
> > +
> > +static const struct mtk_fixed_factor infra_fixed_divs[] = {
> > +	FACTOR(CLK_INFRA_CLK_13M, "clk13m", "clk26m", 1, 2),
> > +};
> > +
> > +static struct clk_onecell_data *infra_clk_data;
> > +
> > +static void mtk_infrasys_init_early(struct device_node *node)
> > +{
> > +	int r, i;
> > +
> > +	if (!infra_clk_data) {
> > +		infra_clk_data = mtk_alloc_clk_data(CLK_INFRA_NR);
> > +
> > +		for (i = 0; i < CLK_INFRA_NR; i++)
> > +			infra_clk_data->clks[i] = ERR_PTR(-EPROBE_DEFER);
> > +	}
> > +
> > +	mtk_clk_register_factors(infra_fixed_divs, ARRAY_SIZE(infra_fixed_divs),
> > +						infra_clk_data);
> > +
> > +	r = of_clk_add_provider(node, of_clk_src_onecell_get, infra_clk_data);
> > +	if (r)
> > +		pr_err("%s(): could not register clock provider: %d\n",
> > +			__func__, r);
> > +}
> > +CLK_OF_DECLARE_DRIVER(mtk_infra, "mediatek,mt2701-infracfg",
> > +			mtk_infrasys_init_early);
> > +
> > +static int mtk_infrasys_init(struct platform_device *pdev)
> > +{
> > +	int r, i;
> > +	struct device_node *node = pdev->dev.of_node;
> > +
> > +	if (!infra_clk_data) {
> > +		infra_clk_data = mtk_alloc_clk_data(CLK_INFRA_NR);
> > +	} else {
> > +		for (i = 0; i < CLK_INFRA_NR; i++) {
> > +			if (infra_clk_data->clks[i] == ERR_PTR(-EPROBE_DEFER))
> > +				infra_clk_data->clks[i] = ERR_PTR(-ENOENT);
> > +		}
> > +	}
> > +
> > +	mtk_clk_register_gates(node, infra_clks, ARRAY_SIZE(infra_clks),
> > +						infra_clk_data);
> > +	mtk_clk_register_factors(infra_fixed_divs, ARRAY_SIZE(infra_fixed_divs),
> > +						infra_clk_data);
> > +
> > +	r = of_clk_add_provider(node, of_clk_src_onecell_get, infra_clk_data);
> > +
> > +	return r;
> > +}
> > +
> > +static const struct mtk_gate_regs peri0_cg_regs = {
> > +	.set_ofs = 0x0008,
> > +	.clr_ofs = 0x0010,
> > +	.sta_ofs = 0x0018,
> > +};
> > +
> > +static const struct mtk_gate_regs peri1_cg_regs = {
> > +	.set_ofs = 0x000c,
> > +	.clr_ofs = 0x0014,
> > +	.sta_ofs = 0x001c,
> > +};
> > +
> > +#define GATE_PERI0(_id, _name, _parent, _shift) {	\
> > +		.id = _id,				\
> > +		.name = _name,				\
> > +		.parent_name = _parent,			\
> > +		.regs = &peri0_cg_regs,			\
> > +		.shift = _shift,			\
> > +		.ops = &mtk_clk_gate_ops_setclr,	\
> > +	}
> > +
> > +#define GATE_PERI1(_id, _name, _parent, _shift) {	\
> > +		.id = _id,				\
> > +		.name = _name,				\
> > +		.parent_name = _parent,			\
> > +		.regs = &peri1_cg_regs,			\
> > +		.shift = _shift,			\
> > +		.ops = &mtk_clk_gate_ops_setclr,	\
> > +	}
> > +
> > +static const struct mtk_gate peri_clks[] = {
> > +	GATE_PERI0(CLK_PERI_USB0_MCU, "usb0_mcu_ck", "axi_sel", 31),
> > +	GATE_PERI0(CLK_PERI_ETH, "eth_ck", "clk26m", 30),
> > +	GATE_PERI0(CLK_PERI_SPI0, "spi0_ck", "spi0_sel", 29),
> > +	GATE_PERI0(CLK_PERI_AUXADC, "auxadc_ck", "clk26m", 28),
> > +	GATE_PERI0(CLK_PERI_I2C3, "i2c3_ck", "clk26m", 27),
> > +	GATE_PERI0(CLK_PERI_I2C2, "i2c2_ck", "axi_sel", 26),
> > +	GATE_PERI0(CLK_PERI_I2C1, "i2c1_ck", "axi_sel", 25),
> > +	GATE_PERI0(CLK_PERI_I2C0, "i2c0_ck", "axi_sel", 24),
> > +	GATE_PERI0(CLK_PERI_BTIF, "bitif_ck", "axi_sel", 23),
> > +	GATE_PERI0(CLK_PERI_UART3, "uart3_ck", "axi_sel", 22),
> > +	GATE_PERI0(CLK_PERI_UART2, "uart2_ck", "axi_sel", 21),
> > +	GATE_PERI0(CLK_PERI_UART1, "uart1_ck", "axi_sel", 20),
> > +	GATE_PERI0(CLK_PERI_UART0, "uart0_ck", "axi_sel", 19),
> > +	GATE_PERI0(CLK_PERI_NLI, "nli_ck", "axi_sel", 18),
> > +	GATE_PERI0(CLK_PERI_MSDC50_3, "msdc50_3_ck", "emmc_hclk_sel", 17),
> > +	GATE_PERI0(CLK_PERI_MSDC30_3, "msdc30_3_ck", "msdc30_3_sel", 16),
> > +	GATE_PERI0(CLK_PERI_MSDC30_2, "msdc30_2_ck", "msdc30_2_sel", 15),
> > +	GATE_PERI0(CLK_PERI_MSDC30_1, "msdc30_1_ck", "msdc30_1_sel", 14),
> > +	GATE_PERI0(CLK_PERI_MSDC30_0, "msdc30_0_ck", "msdc30_0_sel", 13),
> > +	GATE_PERI0(CLK_PERI_AP_DMA, "ap_dma_ck", "axi_sel", 12),
> > +	GATE_PERI0(CLK_PERI_USB1, "usb1_ck", "usb20_sel", 11),
> > +	GATE_PERI0(CLK_PERI_USB0, "usb0_ck", "usb20_sel", 10),
> > +	GATE_PERI0(CLK_PERI_PWM, "pwm_ck", "axi_sel", 9),
> > +	GATE_PERI0(CLK_PERI_PWM7, "pwm7_ck", "axi_sel", 8),
> > +	GATE_PERI0(CLK_PERI_PWM6, "pwm6_ck", "axi_sel", 7),
> > +	GATE_PERI0(CLK_PERI_PWM5, "pwm5_ck", "axi_sel", 6),
> > +	GATE_PERI0(CLK_PERI_PWM4, "pwm4_ck", "axi_sel", 5),
> > +	GATE_PERI0(CLK_PERI_PWM3, "pwm3_ck", "axi_sel", 4),
> > +	GATE_PERI0(CLK_PERI_PWM2, "pwm2_ck", "axi_sel", 3),
> > +	GATE_PERI0(CLK_PERI_PWM1, "pwm1_ck", "axi_sel", 2),
> > +	GATE_PERI0(CLK_PERI_THERM, "therm_ck", "axi_sel", 1),
> > +	GATE_PERI0(CLK_PERI_NFI, "nfi_ck", "nfi2x_sel", 0),
> > +
> > +	GATE_PERI1(CLK_PERI_FCI, "fci_ck", "ms_card_sel", 11),
> > +	GATE_PERI1(CLK_PERI_SPI2, "spi2_ck", "spi2_sel", 10),
> > +	GATE_PERI1(CLK_PERI_SPI1, "spi1_ck", "spi1_sel", 9),
> > +	GATE_PERI1(CLK_PERI_HOST89_DVD, "host89_dvd_ck", "aud2dvd_sel", 8),
> > +	GATE_PERI1(CLK_PERI_HOST89_SPI, "host89_spi_ck", "spi0_sel", 7),
> > +	GATE_PERI1(CLK_PERI_HOST89_INT, "host89_int_ck", "axi_sel", 6),
> > +	GATE_PERI1(CLK_PERI_FLASH, "flash_ck", "nfi2x_sel", 5),
> > +	GATE_PERI1(CLK_PERI_NFI_PAD, "nfi_pad_ck", "nfi1x_pad", 4),
> > +	GATE_PERI1(CLK_PERI_NFI_ECC, "nfi_ecc_ck", "nfi1x_pad", 3),
> > +	GATE_PERI1(CLK_PERI_GCPU, "gcpu_ck", "axi_sel", 2),
> > +	GATE_PERI1(CLK_PERI_USB_SLV, "usbslv_ck", "axi_sel", 1),
> > +	GATE_PERI1(CLK_PERI_USB1_MCU, "usb1_mcu_ck", "axi_sel", 0),
> > +};
> > +
> > +static const char * const uart_ck_sel_parents[] = {
> > +	"clk26m",
> > +	"uart_sel",
> > +};
> > +
> > +static const struct mtk_composite peri_muxs[] = {
> > +	MUX(CLK_PERI_UART0_SEL, "uart0_ck_sel", uart_ck_sel_parents,
> > +		0x40c, 0, 1),
> > +	MUX(CLK_PERI_UART1_SEL, "uart1_ck_sel", uart_ck_sel_parents,
> > +		0x40c, 1, 1),
> > +	MUX(CLK_PERI_UART2_SEL, "uart2_ck_sel", uart_ck_sel_parents,
> > +		0x40c, 2, 1),
> > +	MUX(CLK_PERI_UART3_SEL, "uart3_ck_sel", uart_ck_sel_parents,
> > +		0x40c, 3, 1),
> > +};
> > +
> > +static int mtk_pericfg_init(struct platform_device *pdev)
> > +{
> > +	struct clk_onecell_data *clk_data;
> > +	void __iomem *base;
> > +	int r;
> > +	struct device_node *node = pdev->dev.of_node;
> > +
> > +	base = devm_of_iomap(&pdev->dev, 0);
> > +	if (!base)
> > +		return -ENOMEM;
> > +
> > +	clk_data = mtk_alloc_clk_data(CLK_PERI_NR);
> > +
> > +	mtk_clk_register_gates(node, peri_clks, ARRAY_SIZE(peri_clks),
> > +						clk_data);
> > +
> > +	mtk_clk_register_composites(peri_muxs, ARRAY_SIZE(peri_muxs), base,
> > +			&lock, clk_data);
> > +
> > +	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
> > +
> > +	return r;
> 
> Just return of_clk_add_provider()?

Okay.

> > +}
> [...]
> > +
> > +static int clk_mt2701_probe(struct platform_device *pdev)
> > +{
> > +	int (*clk_init)(struct platform_device *);
> > +	int r;
> > +
> > +	clk_init = of_device_get_match_data(&pdev->dev);
> > +	if (!clk_init)
> > +		return -EINVAL;
> > +
> > +	r = clk_init(pdev);
> > +	if (r) {
> > +		dev_err(&pdev->dev,
> > +			"could not register clock provider: %s: %d\n",
> > +			pdev->name, r);
> > +	}
> 
> Braces are unnecessary.

I'll remove it.

> > +
> > +	return r;
> > +}
> > +
> > +static struct platform_driver clk_mt2701_drv = {
> > +	.probe = clk_mt2701_probe,
> > +	.driver = {
> > +		.name = "clk-mt2701",
> > +		.owner = THIS_MODULE,
> 
> This is unnecessary because platform_driver_register() already
> does it.

Okay, I'll remove it.

> > +		.of_match_table = of_match_clk_mt2701,
> > +	},
> > +};
> > +
> > +static int __init clk_mt2701_init(void)
> > +{
> > +	return platform_driver_register(&clk_mt2701_drv);
> > +}
> > +
> > +arch_initcall(clk_mt2701_init);
> > diff --git a/drivers/clk/mediatek/clk-mtk.c b/drivers/clk/mediatek/clk-mtk.c
> > index bb30f70..0541df7 100644
> > --- a/drivers/clk/mediatek/clk-mtk.c
> > +++ b/drivers/clk/mediatek/clk-mtk.c
> > @@ -58,6 +58,9 @@ void mtk_clk_register_fixed_clks(const struct mtk_fixed_clk *clks,
> >  	for (i = 0; i < num; i++) {
> >  		const struct mtk_fixed_clk *rc = &clks[i];
> >  
> > +		if (clk_data && !IS_ERR_OR_NULL(clk_data->clks[rc->id]))
> > +			continue;
> > +
> >  		clk = clk_register_fixed_rate(NULL, rc->name, rc->parent, 0,
> >  					      rc->rate);
> >  
> > @@ -81,6 +84,9 @@ void mtk_clk_register_factors(const struct mtk_fixed_factor *clks,
> >  	for (i = 0; i < num; i++) {
> >  		const struct mtk_fixed_factor *ff = &clks[i];
> >  
> > +		if (clk_data && !IS_ERR_OR_NULL(clk_data->clks[ff->id]))
> > +			continue;
> > +
> >  		clk = clk_register_fixed_factor(NULL, ff->name, ff->parent_name,
> >  				CLK_SET_RATE_PARENT, ff->mult, ff->div);
> >  
> > @@ -116,6 +122,9 @@ int mtk_clk_register_gates(struct device_node *node,
> >  	for (i = 0; i < num; i++) {
> >  		const struct mtk_gate *gate = &clks[i];
> >  
> > +		if (!IS_ERR_OR_NULL(clk_data->clks[gate->id]))
> > +			continue;
> > +
> >  		clk = mtk_clk_register_gate(gate->name, gate->parent_name,
> >  				regmap,
> >  				gate->regs->set_ofs,
> > @@ -232,6 +241,9 @@ void mtk_clk_register_composites(const struct mtk_composite *mcs,
> >  	for (i = 0; i < num; i++) {
> >  		const struct mtk_composite *mc = &mcs[i];
> >  
> > +		if (clk_data && !IS_ERR_OR_NULL(clk_data->clks[mc->id]))
> > +			continue;
> > +
> >  		clk = mtk_clk_register_composite(mc, base, lock);
> >  
> >  		if (IS_ERR(clk)) {
> > @@ -244,3 +256,31 @@ void mtk_clk_register_composites(const struct mtk_composite *mcs,
> >  			clk_data->clks[mc->id] = clk;
> >  	}
> >  }
> > +
> > +void mtk_clk_register_dividers(const struct mtk_clk_divider *mcds,
> > +			int num, void __iomem *base, spinlock_t *lock,
> > +				struct clk_onecell_data *clk_data)
> > +{
> > +	struct clk *clk;
> > +	int i;
> > +
> > +	for (i = 0; i <  num; i++) {
> > +		const struct mtk_clk_divider *mcd = &mcds[i];
> > +
> > +		if (clk_data && !IS_ERR_OR_NULL(clk_data->clks[mcd->id]))
> 
> NULL is a valid clk. IS_ERR_OR_NULL is usually wrong.

Why NULL is a valid clk?

clk_data is designed for multiple initialization from different clock
types, such as infra_clk_data in clk-mt2701.c. So it will ignore valid
clocks to avoid duplicated clock registration. Here I assume a clock
pointer with error code or NULL to be an invalid (not initialized)
clock.

> > +			continue;
> > +
> > +		clk = clk_register_divider(NULL, mcd->name, mcd->parent_name,
> > +			mcd->flags, base +  mcd->div_reg, mcd->div_shift,
> > +			mcd->div_width, mcd->clk_divider_flags, lock);
> > +
> > +		if (IS_ERR(clk)) {
> > +			pr_err("Failed to register clk %s: %ld\n",
> > +				mcd->name, PTR_ERR(clk));
> > +			continue;
> > +		}
> > +
> > +		if (clk_data)
> > +			clk_data->clks[mcd->id] = clk;
> > +	}
> > +}
> > diff --git a/drivers/clk/mediatek/clk-mtk.h b/drivers/clk/mediatek/clk-mtk.h
> > index 9f24fcf..f5d6b70 100644
> > --- a/drivers/clk/mediatek/clk-mtk.h
> > +++ b/drivers/clk/mediatek/clk-mtk.h
> > @@ -87,7 +87,8 @@ struct mtk_composite {
> >   * In case the rate change propagation to parent clocks is undesirable,
> >   * this macro allows to specify the clock flags manually.
> >   */
> > -#define MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width, _gate, _flags) {	\
> > +#define MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width,	\
> > +			_gate, _flags) {				\
> >  		.id = _id,						\
> >  		.name = _name,						\
> >  		.mux_reg = _reg,					\
> > @@ -106,7 +107,8 @@ struct mtk_composite {
> >   * parent clock by default.
> >   */
> >  #define MUX_GATE(_id, _name, _parents, _reg, _shift, _width, _gate)	\
> > -	MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width, _gate, CLK_SET_RATE_PARENT)
> > +	MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width,	\
> > +		_gate, CLK_SET_RATE_PARENT)
> >  
> >  #define MUX(_id, _name, _parents, _reg, _shift, _width) {		\
> >  		.id = _id,						\
> > @@ -121,7 +123,8 @@ struct mtk_composite {
> >  		.flags = CLK_SET_RATE_PARENT,				\
> >  	}
> >  
> > -#define DIV_GATE(_id, _name, _parent, _gate_reg, _gate_shift, _div_reg, _div_width, _div_shift) {	\
> > +#define DIV_GATE(_id, _name, _parent, _gate_reg, _gate_shift, _div_reg,	\
> > +					_div_width, _div_shift) {	\
> 
> Did anything actually change? Checkpatch fix?

Right. Just limit max line length to 80 chars.

> >  		.id = _id,						\
> >  		.parent = _parent,					\
> >  		.name = _name,						\

^ permalink raw reply

* [PATCHv2 3/4] mfd: altr-a10sr: Add Arria10 SR Monitor
From: Lee Jones @ 2016-10-31  8:02 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1477598426-28125-4-git-send-email-tthayer@opensource.altera.com>

On Thu, 27 Oct 2016, tthayer at opensource.altera.com wrote:

> From: Thor Thayer <tthayer@opensource.altera.com>
> 
> Add the Altera Arria10 DevKit System Resource Monitor functionality
> to the MFD device.
> 
> Signed-off-by: Thor Thayer <tthayer@opensource.altera.com>
> ---
> v2  Change from -mon to -monitor for clarity
> ---
>  drivers/mfd/altera-a10sr.c | 4 ++++
>  1 file changed, 4 insertions(+)
> 
> diff --git a/drivers/mfd/altera-a10sr.c b/drivers/mfd/altera-a10sr.c
> index 06e1f7f..30de652 100644
> --- a/drivers/mfd/altera-a10sr.c
> +++ b/drivers/mfd/altera-a10sr.c
> @@ -33,6 +33,10 @@
>  		.name = "altr_a10sr_gpio",
>  		.of_compatible = "altr,a10sr-gpio",
>  	},
> +	{
> +		.name = "altr_a10sr_monitor",
> +		.of_compatible = "altr,a10sr-monitor",

So long as you use whichever compatible you agree on with Rob:

For my own reference:
  Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>

> +	},
>  };
>  
>  static bool altr_a10sr_reg_readable(struct device *dev, unsigned int reg)

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org ? Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply

* [PATCH] staging: vc04_services: add vchiq_pagelist_info structure
From: Michael Zoran @ 2016-10-31  8:10 UTC (permalink / raw)
  To: linux-arm-kernel

The current dma_map_sg based implementation for bulk messages
computes many offsets into a single allocation multiple times in
both the create and free code paths.  This is inefficient,
error prone and in fact still has a few lingering issues
with arm64.

This change replaces a small portion of that inplementation with
new code that uses a new struct vchiq_pagelist_info to store the
needed information rather then complex offset calculations.

This improved implementation should be more efficient and easier
to understand and maintain.

Tests Run(Both Pass):
vchiq_test -p 1
vchiq_test -f 10

Signed-off-by: Michael Zoran <mzoran@crowfest.net>
---
 .../interface/vchiq_arm/vchiq_2835_arm.c           | 223 +++++++++++----------
 1 file changed, 113 insertions(+), 110 deletions(-)

diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_2835_arm.c b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_2835_arm.c
index 12938f2..a297d89 100644
--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_2835_arm.c
+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_2835_arm.c
@@ -62,6 +62,18 @@ typedef struct vchiq_2835_state_struct {
    VCHIQ_ARM_STATE_T arm_state;
 } VCHIQ_2835_ARM_STATE_T;
 
+struct vchiq_pagelist_info {
+	PAGELIST_T *pagelist;
+	size_t pagelist_buffer_size;
+	dma_addr_t dma_addr;
+	enum dma_data_direction dma_dir;
+	unsigned int num_pages;
+	unsigned int pages_need_release;
+	struct page **pages;
+	struct scatterlist *scatterlist;
+	unsigned int scatterlist_mapped;
+};
+
 static void __iomem *g_regs;
 static unsigned int g_cache_line_size = sizeof(CACHE_LINE_SIZE);
 static unsigned int g_fragments_size;
@@ -77,13 +89,13 @@ static DEFINE_SEMAPHORE(g_free_fragments_mutex);
 static irqreturn_t
 vchiq_doorbell_irq(int irq, void *dev_id);
 
-static int
+static struct vchiq_pagelist_info *
 create_pagelist(char __user *buf, size_t count, unsigned short type,
-		struct task_struct *task, PAGELIST_T **ppagelist,
-		dma_addr_t *dma_addr);
+		struct task_struct *task);
 
 static void
-free_pagelist(dma_addr_t dma_addr, PAGELIST_T *pagelist, int actual);
+free_pagelist(struct vchiq_pagelist_info *pagelistinfo,
+	      int actual);
 
 int vchiq_platform_init(struct platform_device *pdev, VCHIQ_STATE_T *state)
 {
@@ -226,29 +238,27 @@ VCHIQ_STATUS_T
 vchiq_prepare_bulk_data(VCHIQ_BULK_T *bulk, VCHI_MEM_HANDLE_T memhandle,
 	void *offset, int size, int dir)
 {
-	PAGELIST_T *pagelist;
-	int ret;
-	dma_addr_t dma_addr;
+	struct vchiq_pagelist_info *pagelistinfo;
 
 	WARN_ON(memhandle != VCHI_MEM_HANDLE_INVALID);
 
-	ret = create_pagelist((char __user *)offset, size,
-			(dir == VCHIQ_BULK_RECEIVE)
-			? PAGELIST_READ
-			: PAGELIST_WRITE,
-			current,
-			&pagelist,
-			&dma_addr);
+	pagelistinfo = create_pagelist((char __user *)offset, size,
+				       (dir == VCHIQ_BULK_RECEIVE)
+				       ? PAGELIST_READ
+				       : PAGELIST_WRITE,
+				       current);
 
-	if (ret != 0)
+	if (!pagelistinfo)
 		return VCHIQ_ERROR;
 
 	bulk->handle = memhandle;
-	bulk->data = (void *)(unsigned long)dma_addr;
+	bulk->data = (void *)(unsigned long)pagelistinfo->dma_addr;
 
-	/* Store the pagelist address in remote_data, which isn't used by the
-	   slave. */
-	bulk->remote_data = pagelist;
+	/*
+	 * Store the pagelistinfo address in remote_data,
+	 * which isn't used by the slave.
+	 */
+	bulk->remote_data = pagelistinfo;
 
 	return VCHIQ_SUCCESS;
 }
@@ -257,8 +267,8 @@ void
 vchiq_complete_bulk(VCHIQ_BULK_T *bulk)
 {
 	if (bulk && bulk->remote_data && bulk->actual)
-		free_pagelist((dma_addr_t)(unsigned long)bulk->data,
-			      (PAGELIST_T *)bulk->remote_data, bulk->actual);
+		free_pagelist((struct vchiq_pagelist_info *)bulk->remote_data,
+			      bulk->actual);
 }
 
 void
@@ -346,6 +356,25 @@ vchiq_doorbell_irq(int irq, void *dev_id)
 	return ret;
 }
 
+static void
+cleaup_pagelistinfo(struct vchiq_pagelist_info *pagelistinfo)
+{
+	if (pagelistinfo->scatterlist_mapped) {
+		dma_unmap_sg(g_dev, pagelistinfo->scatterlist,
+			     pagelistinfo->num_pages, pagelistinfo->dma_dir);
+	}
+
+	if (pagelistinfo->pages_need_release) {
+		unsigned int i;
+
+		for (i = 0; i < pagelistinfo->num_pages; i++)
+			put_page(pagelistinfo->pages[i]);
+	}
+
+	dma_free_coherent(g_dev, pagelistinfo->pagelist_buffer_size,
+			  pagelistinfo->pagelist, pagelistinfo->dma_addr);
+}
+
 /* There is a potential problem with partial cache lines (pages?)
 ** at the ends of the block when reading. If the CPU accessed anything in
 ** the same line (page?) then it may have pulled old data into the cache,
@@ -354,52 +383,64 @@ vchiq_doorbell_irq(int irq, void *dev_id)
 ** cached area.
 */
 
-static int
+static struct vchiq_pagelist_info *
 create_pagelist(char __user *buf, size_t count, unsigned short type,
-		struct task_struct *task, PAGELIST_T **ppagelist,
-		dma_addr_t *dma_addr)
+		struct task_struct *task)
 {
 	PAGELIST_T *pagelist;
+	struct vchiq_pagelist_info *pagelistinfo;
 	struct page **pages;
 	u32 *addrs;
 	unsigned int num_pages, offset, i, k;
 	int actual_pages;
-        unsigned long *need_release;
 	size_t pagelist_size;
 	struct scatterlist *scatterlist, *sg;
 	int dma_buffers;
-	int dir;
+	dma_addr_t dma_addr;
 
 	offset = ((unsigned int)(unsigned long)buf & (PAGE_SIZE - 1));
 	num_pages = (count + offset + PAGE_SIZE - 1) / PAGE_SIZE;
 
 	pagelist_size = sizeof(PAGELIST_T) +
-			(num_pages * sizeof(unsigned long)) +
-			sizeof(unsigned long) +
+			(num_pages * sizeof(u32)) +
 			(num_pages * sizeof(pages[0]) +
-			(num_pages * sizeof(struct scatterlist)));
-
-	*ppagelist = NULL;
-
-	dir = (type == PAGELIST_WRITE) ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
+			(num_pages * sizeof(struct scatterlist))) +
+			sizeof(struct vchiq_pagelist_info);
 
 	/* Allocate enough storage to hold the page pointers and the page
 	** list
 	*/
 	pagelist = dma_zalloc_coherent(g_dev,
 				       pagelist_size,
-				       dma_addr,
+				       &dma_addr,
 				       GFP_KERNEL);
 
 	vchiq_log_trace(vchiq_arm_log_level, "create_pagelist - %pK",
 			pagelist);
 	if (!pagelist)
-		return -ENOMEM;
+		return NULL;
+
+	addrs		= pagelist->addrs;
+	pages		= (struct page **)(addrs + num_pages);
+	scatterlist	= (struct scatterlist *)(pages + num_pages);
+	pagelistinfo	= (struct vchiq_pagelist_info *)
+			  (scatterlist + num_pages);
 
-	addrs = pagelist->addrs;
-        need_release = (unsigned long *)(addrs + num_pages);
-	pages = (struct page **)(addrs + num_pages + 1);
-	scatterlist = (struct scatterlist *)(pages + num_pages);
+	pagelist->length = count;
+	pagelist->type = type;
+	pagelist->offset = offset;
+
+	/* Populate the fields of the pagelistinfo structure */
+	pagelistinfo->pagelist = pagelist;
+	pagelistinfo->pagelist_buffer_size = pagelist_size;
+	pagelistinfo->dma_addr = dma_addr;
+	pagelistinfo->dma_dir =  (type == PAGELIST_WRITE) ?
+				  DMA_TO_DEVICE : DMA_FROM_DEVICE;
+	pagelistinfo->num_pages = num_pages;
+	pagelistinfo->pages_need_release = 0;
+	pagelistinfo->pages = pages;
+	pagelistinfo->scatterlist = scatterlist;
+	pagelistinfo->scatterlist_mapped = 0;
 
 	if (is_vmalloc_addr(buf)) {
 		unsigned long length = count;
@@ -417,7 +458,7 @@ create_pagelist(char __user *buf, size_t count, unsigned short type,
 			length -= bytes;
 			off = 0;
 		}
-		*need_release = 0; /* do not try and release vmalloc pages */
+		/* do not try and release vmalloc pages */
 	} else {
 		down_read(&task->mm->mmap_sem);
 		actual_pages = get_user_pages(
@@ -440,34 +481,28 @@ create_pagelist(char __user *buf, size_t count, unsigned short type,
 				actual_pages--;
 				put_page(pages[actual_pages]);
 			}
-			dma_free_coherent(g_dev, pagelist_size,
-					  pagelist, *dma_addr);
-			if (actual_pages == 0)
-				actual_pages = -ENOMEM;
-			return actual_pages;
+			cleaup_pagelistinfo(pagelistinfo);
+			return NULL;
 		}
-		*need_release = 1; /* release user pages */
+		 /* release user pages */
+		pagelistinfo->pages_need_release = 1;
 	}
 
-	pagelist->length = count;
-	pagelist->type = type;
-	pagelist->offset = offset;
-
 	for (i = 0; i < num_pages; i++)
 		sg_set_page(scatterlist + i, pages[i], PAGE_SIZE, 0);
 
 	dma_buffers = dma_map_sg(g_dev,
 				 scatterlist,
 				 num_pages,
-				 dir);
+				 pagelistinfo->dma_dir);
 
 	if (dma_buffers == 0) {
-		dma_free_coherent(g_dev, pagelist_size,
-				  pagelist, *dma_addr);
-
-		return -EINTR;
+		cleaup_pagelistinfo(pagelistinfo);
+		return NULL;
 	}
 
+	pagelistinfo->scatterlist_mapped = 1;
+
 	/* Combine adjacent blocks for performance */
 	k = 0;
 	for_each_sg(scatterlist, sg, dma_buffers, i) {
@@ -499,11 +534,8 @@ create_pagelist(char __user *buf, size_t count, unsigned short type,
 		char *fragments;
 
 		if (down_interruptible(&g_free_fragments_sema) != 0) {
-			dma_unmap_sg(g_dev, scatterlist, num_pages,
-				     DMA_BIDIRECTIONAL);
-			dma_free_coherent(g_dev, pagelist_size,
-					  pagelist, *dma_addr);
-			return -EINTR;
+			cleaup_pagelistinfo(pagelistinfo);
+			return NULL;
 		}
 
 		WARN_ON(g_free_fragments == NULL);
@@ -517,42 +549,28 @@ create_pagelist(char __user *buf, size_t count, unsigned short type,
 			(fragments - g_fragments_base) / g_fragments_size;
 	}
 
-	*ppagelist = pagelist;
-
-	return 0;
+	return pagelistinfo;
 }
 
 static void
-free_pagelist(dma_addr_t dma_addr, PAGELIST_T *pagelist, int actual)
+free_pagelist(struct vchiq_pagelist_info *pagelistinfo,
+	      int actual)
 {
-        unsigned long *need_release;
-	struct page **pages;
-	unsigned int num_pages, i;
-	size_t pagelist_size;
-	struct scatterlist *scatterlist;
-	int dir;
+	unsigned int i;
+	PAGELIST_T *pagelist   = pagelistinfo->pagelist;
+	struct page **pages    = pagelistinfo->pages;
+	unsigned int num_pages = pagelistinfo->num_pages;
 
 	vchiq_log_trace(vchiq_arm_log_level, "free_pagelist - %pK, %d",
-			pagelist, actual);
-
-	dir = (pagelist->type == PAGELIST_WRITE) ? DMA_TO_DEVICE :
-						   DMA_FROM_DEVICE;
-
-	num_pages =
-		(pagelist->length + pagelist->offset + PAGE_SIZE - 1) /
-		PAGE_SIZE;
+			pagelistinfo->pagelist, actual);
 
-	pagelist_size = sizeof(PAGELIST_T) +
-			(num_pages * sizeof(unsigned long)) +
-			sizeof(unsigned long) +
-			(num_pages * sizeof(pages[0]) +
-			(num_pages * sizeof(struct scatterlist)));
-
-        need_release = (unsigned long *)(pagelist->addrs + num_pages);
-	pages = (struct page **)(pagelist->addrs + num_pages + 1);
-	scatterlist = (struct scatterlist *)(pages + num_pages);
-
-	dma_unmap_sg(g_dev, scatterlist, num_pages, dir);
+	/*
+	 * NOTE: dma_unmap_sg must be called before the
+	 * cpu can touch any of the data/pages.
+	 */
+	dma_unmap_sg(g_dev, pagelistinfo->scatterlist,
+		     pagelistinfo->num_pages, pagelistinfo->dma_dir);
+	pagelistinfo->scatterlist_mapped = 0;
 
 	/* Deal with any partial cache lines (fragments) */
 	if (pagelist->type >= PAGELIST_READ_WITH_FRAGMENTS) {
@@ -590,27 +608,12 @@ free_pagelist(dma_addr_t dma_addr, PAGELIST_T *pagelist, int actual)
 		up(&g_free_fragments_sema);
 	}
 
-	if (*need_release) {
-		unsigned int length = pagelist->length;
-		unsigned int offset = pagelist->offset;
-
-		for (i = 0; i < num_pages; i++) {
-			struct page *pg = pages[i];
-
-			if (pagelist->type != PAGELIST_WRITE) {
-				unsigned int bytes = PAGE_SIZE - offset;
-
-				if (bytes > length)
-					bytes = length;
-
-				length -= bytes;
-				offset = 0;
-				set_page_dirty(pg);
-			}
-			put_page(pg);
-		}
+	/* Need to mark all the pages dirty. */
+	if (pagelist->type != PAGELIST_WRITE &&
+	    pagelistinfo->pages_need_release) {
+		for (i = 0; i < num_pages; i++)
+			set_page_dirty(pages[i]);
 	}
 
-	dma_free_coherent(g_dev, pagelist_size,
-			  pagelist, dma_addr);
+	cleaup_pagelistinfo(pagelistinfo);
 }
-- 
2.10.1

^ permalink raw reply related

* [PATCH 1/4] mfd: ti_am335x_tscadc: store physical address
From: Lee Jones @ 2016-10-31  8:16 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <954adfa7-94d9-f785-aeab-6f20c8f4c3b9@kernel.org>

On Sun, 30 Oct 2016, Jonathan Cameron wrote:

> On 26/10/16 13:17, Lee Jones wrote:
> > On Fri, 30 Sep 2016, Mugunthan V N wrote:
> > 
> >> On Wednesday 28 September 2016 01:10 AM, Lee Jones wrote:
> >>> On Wed, 21 Sep 2016, Mugunthan V N wrote:
> >>>
> >>>> store the physical address of the device in its priv to use it
> >>>> for DMA addressing in the client drivers.
> >>>>
> >>>> Signed-off-by: Mugunthan V N <mugunthanvnm@ti.com>
> >>>> ---
> >>>>  drivers/mfd/ti_am335x_tscadc.c       | 1 +
> >>>>  include/linux/mfd/ti_am335x_tscadc.h | 1 +
> >>>>  2 files changed, 2 insertions(+)
> >>>>
> >>>> diff --git a/drivers/mfd/ti_am335x_tscadc.c b/drivers/mfd/ti_am335x_tscadc.c
> >>>> index c8f027b..0f3fab4 100644
> >>>> --- a/drivers/mfd/ti_am335x_tscadc.c
> >>>> +++ b/drivers/mfd/ti_am335x_tscadc.c
> >>>> @@ -183,6 +183,7 @@ static	int ti_tscadc_probe(struct platform_device *pdev)
> >>>>  		tscadc->irq = err;
> >>>>  
> >>>>  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> >>>> +	tscadc->tscadc_phys_base = res->start;
> >>>
> >>> This is unusual.  Can't you use a virt_to_phys() variant instead?
> >>>
> >>
> >> I tried using virt_to_phys(), but its not working for me.
> >> Also saw many drivers uses like this to get physical address
> >> ("git grep -n " res->start;" drivers/*").
> > 
> > Very well:
> > 
> > For my own reference:
> >   Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
> > 
> > Let me know how you wish this set to be handled.
> I'm happy to pick up the whole series.  There are some more mfd
> header changes in patch 2 but as they only add defines, I
> don't mind that much if I don't an Ack from you on those
> (btw this got to V3 but as patch 1 didn't change I'll carry
> your ack forwards).
> 
> Do you want an immutable branch?  Seems unlikely to cause
> much trouble even if there is a merge issue on all 10ish
> lines of mfd code in the next merge window.

Not at the moment, but if you could set things up so it's possible to
create one at a later date if things go Pete Tong, that would be
great.

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org ? Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply

* [PATCH] pinctrl: at91: add support for OUTPUT config
From: Linus Walleij @ 2016-10-31  8:17 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1477641251-30468-1-git-send-email-wenyou.yang@atmel.com>

On Fri, Oct 28, 2016 at 9:54 AM, Wenyou Yang <wenyou.yang@atmel.com> wrote:

> From: Boris BREZILLON <b.brezillon@overkiz.com>
>
> Add support for pin output control through the pinctrl config:
>  - support enabling/disabling output on a given pin
>  - support output level setting (high or low)
>
> Signed-off-by: Boris BREZILLON <b.brezillon@overkiz.com>
> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>
> Signed-off-by: Wenyou Yang <wenyou.yang@atmel.com>

Patch applied.

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH 1/2] ARM: oxnas: Add OX820 SMP support
From: Russell King - ARM Linux @ 2016-10-31  8:18 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <9124cda0-d16b-0992-2552-8a617aeb5d40@baylibre.com>

On Mon, Oct 17, 2016 at 11:34:32AM +0200, Neil Armstrong wrote:
> On 10/17/2016 11:06 AM, Arnd Bergmann wrote:
> > On Monday, October 17, 2016 10:43:02 AM CEST Neil Armstrong wrote:
> > This seems to have been copied from plat-versatile, but is really
> > not needed here since you apparently have proper hardware support for
> > starting up the CPUs.
> Yes it seems.
> 
> > 
> > Any reason you can't just write to the cpu_ctrl register
> > once and keep going without that whole holding_pen loop
> > and spinlock?
> I suppose but I did not find any good examples except the plat-versatile code.
> I will try some simpler code.

There's plenty of examples - most ARM SMP platforms in the kernel now
do not blindly copy the versatile code.  You only have to go looking
for arch/arm/*/platsmp.c files to find them.

I'm not sure what you'd call a "good example" - maybe the imx code?
arch/arm/mach-imx/platsmp.c can't be simpler:

static int imx_boot_secondary(unsigned int cpu, struct task_struct *idle)
{
        imx_set_cpu_jump(cpu, v7_secondary_startup);
        imx_enable_cpu(cpu, true);
        return 0;
}

I guess the difficult thing is to understand what each of those called
functions does... though the function names give a very accurate clue
there.

and because plat-versatile is almost entirely software-based, it's
easy to understand and follow, _despite_ being completely broken
for things like PM and kexec (which, the platform does not support.)

-- 
RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.

^ permalink raw reply

* [PATCH v4 00/23] soc: renesas: Add R-Car RST driver for obtaining mode pin state
From: Simon Horman @ 2016-10-31  8:19 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <CAMuHMdVbpuv94x-ocTSDoEj+SbnESDbkuMXsXExQvoyZepp+Zg@mail.gmail.com>

On Wed, Oct 26, 2016 at 02:00:24PM +0200, Geert Uytterhoeven wrote:
> Hi Mike, Stephen,
> 
> On Fri, Oct 21, 2016 at 3:17 PM, Geert Uytterhoeven
> <geert+renesas@glider.be> wrote:
> > Currently the R-Car Clock Pulse Generator (CPG) drivers obtains the
> > state of the mode pins either by a call from the platform code, or
> > directly by using a hardcoded register access. This is a bit messy, and
> > creates a dependency between driver and platform code.
> >
> > This patch series converts the various Renesas R-Car clock drivers
> > and support code from reading the mode pin states using a hardcoded
> > register access to using a new minimalistic R-Car RST driver.
> >
> > All R-Car clock drivers will rely on the presence in DT of a device node
> > for the RST module.  Backwards compatibility with old DTBs is retained
> > only for R-Car Gen2, which has fallback code using its own private copy
> > of rcar_gen2_read_mode_pins().
> >
> > After this, there is still one remaining user of
> > rcar_gen2_read_mode_pins() left in platform code. A patch series to
> > remove that user has already been posted, though ("[PATCH/RFT 0/4] ARM:
> > shmobile: R-Car Gen2: Allow booting secondary CPU cores in debug mode").
> > Since v3, the other user has been removed in commit 9f5ce39ddb8f68b3
> > ("ARM: shmobile: rcar-gen2: Obtain extal frequency from DT").
> >
> > This series consists of 5 parts:
> >   A. Patches 1 and 2 add DT bindings and driver code for the R-Car RST
> >      driver,
> >   B. Patches 3-11 add device nodes for the RST modules to the R-Car DTS
> >      files,
> >   C. Patches 12-17 convert the clock drivers to call into the new R-Car
> >      RST driver,
> >   D. Patches 18-20 remove passing mode pin state to the clock drivers
> >      from the platform code,
> >   E. Patches 21-23 remove dead code from the clock drivers.
> >
> > As is usually the case with moving functionality from platform code to
> > DT, there are lots of hard dependencies:
> >   - The DT updates in Part B can be merged as soon as the DT bindings in
> >     Part A have been approved,
> >   - The clock driver updates in Part C depend functionally on the driver
> >     code in Part A, and on the DT updates in Part B,
> >   - The board code cleanups in Part D depend on the clock driver updates
> >     in Part C,
> >   - The block driver cleanups in part E depend on the board code
> >     cleanups in part D.
> >
> > Hence to maintain the required lockstep between SoC driver, clock
> > drivers, shmobile platform code, and shmobile DT, I propose to queue up
> > all patches in a single branch against v4.9-rc1, and send pull requests
> > to both Mike/Stephen (clock) and Simon (rest).
> >
> > ***
> 
> >   - Mike/Stephen/Simon/Magnus: Are you OK with the suggested merge
> >     approach above?
> 
> Is this OK for you?
> 
> I'd like to move forward with this, as this is a prerequisite for adding
> support for new SoCs (RZ/G) without adding more copies of
> rcar_gen2_read_mode_pins(), and removing that function from platform code
> for good.

This seems reasonable to me but likely the ARM SoC maintainers will want to
know about this plan before it is executed.

^ permalink raw reply

* [PATCH 0/6] mfd: audit and demodule drivers/mfd/ab* drivers
From: Linus Walleij @ 2016-10-31  8:26 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161030012440.10495-1-paul.gortmaker@windriver.com>

On Sun, Oct 30, 2016 at 2:24 AM, Paul Gortmaker
<paul.gortmaker@windriver.com> wrote:

> Nothing new here ; just another instance where we had modular remove
> and exit functions -- but the Makefile/Kconfig limited the use case to
> being always built-in; hence the code is useless and needs removing.
>
> So we remove the dead code and the misleading hints of modularity from
> these drivers, hoping that we manage to prevent some new drivers from
> copying these same old mistakes into pending new drivers.
(...)
> Paul Gortmaker (6):
>   mfd: ab3100-core: Make it explicitly non-modular
>   mfd: ab8500-core: Make it explicitly non-modular
>   mfd: ab8500-debugfs: Make it explicitly non-modular
>   mfd: ab8500-gpadc: Make it explicitly non-modular
>   mfd: ab8500: make sysctrl explicitly non-modular
>   mfd: abx500-core: drop unused MODULE_ tags from non-modular code

Acked-by: Linus Walleij <linus.walleij@linaro.org>

For the whole series.

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH v4 00/23] soc: renesas: Add R-Car RST driver for obtaining mode pin state
From: Geert Uytterhoeven @ 2016-10-31  8:32 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161031081923.GF18195@verge.net.au>

Hi Mike, Stephen, Arnd, Olof, Kevin,

Is the merge strategy [see ##### below] OK for you?
Thanks a lot!

On Mon, Oct 31, 2016 at 9:19 AM, Simon Horman <horms@verge.net.au> wrote:
> On Wed, Oct 26, 2016 at 02:00:24PM +0200, Geert Uytterhoeven wrote:
>> On Fri, Oct 21, 2016 at 3:17 PM, Geert Uytterhoeven
>> <geert+renesas@glider.be> wrote:
>> > Currently the R-Car Clock Pulse Generator (CPG) drivers obtains the
>> > state of the mode pins either by a call from the platform code, or
>> > directly by using a hardcoded register access. This is a bit messy, and
>> > creates a dependency between driver and platform code.
>> >
>> > This patch series converts the various Renesas R-Car clock drivers
>> > and support code from reading the mode pin states using a hardcoded
>> > register access to using a new minimalistic R-Car RST driver.
>> >
>> > All R-Car clock drivers will rely on the presence in DT of a device node
>> > for the RST module.  Backwards compatibility with old DTBs is retained
>> > only for R-Car Gen2, which has fallback code using its own private copy
>> > of rcar_gen2_read_mode_pins().
>> >
>> > After this, there is still one remaining user of
>> > rcar_gen2_read_mode_pins() left in platform code. A patch series to
>> > remove that user has already been posted, though ("[PATCH/RFT 0/4] ARM:
>> > shmobile: R-Car Gen2: Allow booting secondary CPU cores in debug mode").
>> > Since v3, the other user has been removed in commit 9f5ce39ddb8f68b3
>> > ("ARM: shmobile: rcar-gen2: Obtain extal frequency from DT").
>> >
>> > This series consists of 5 parts:
>> >   A. Patches 1 and 2 add DT bindings and driver code for the R-Car RST
>> >      driver,
>> >   B. Patches 3-11 add device nodes for the RST modules to the R-Car DTS
>> >      files,
>> >   C. Patches 12-17 convert the clock drivers to call into the new R-Car
>> >      RST driver,
>> >   D. Patches 18-20 remove passing mode pin state to the clock drivers
>> >      from the platform code,
>> >   E. Patches 21-23 remove dead code from the clock drivers.
>> >
>> > As is usually the case with moving functionality from platform code to
>> > DT, there are lots of hard dependencies:
>> >   - The DT updates in Part B can be merged as soon as the DT bindings in
>> >     Part A have been approved,
>> >   - The clock driver updates in Part C depend functionally on the driver
>> >     code in Part A, and on the DT updates in Part B,
>> >   - The board code cleanups in Part D depend on the clock driver updates
>> >     in Part C,
>> >   - The block driver cleanups in part E depend on the board code
>> >     cleanups in part D.
>> >
>> > Hence to maintain the required lockstep between SoC driver, clock
>> > drivers, shmobile platform code, and shmobile DT, I propose to queue up
>> > all patches in a single branch against v4.9-rc1, and send pull requests
>> > to both Mike/Stephen (clock) and Simon (rest).
>> >
>> > ***
>>
>> >   - Mike/Stephen/Simon/Magnus: Are you OK with the suggested merge
>> >     approach above?
>>
>> Is this OK for you?

#####

(link to the full series at
 https://groups.google.com/forum/#!topic/linux.kernel/fLSFsjOgPT8)

>>
>> I'd like to move forward with this, as this is a prerequisite for adding
>> support for new SoCs (RZ/G) without adding more copies of
>> rcar_gen2_read_mode_pins(), and removing that function from platform code
>> for good.
>
> This seems reasonable to me but likely the ARM SoC maintainers will want to
> know about this plan before it is executed.

OK, adding more people in the loop...

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert at linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

^ permalink raw reply

* [PATCH 5/9] ASoC: pxa: switch to new ac97 bus support
From: Robert Jarzmik @ 2016-10-31  8:37 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1477510907-23495-6-git-send-email-robert.jarzmik@free.fr>

Robert Jarzmik <robert.jarzmik@free.fr> writes:

> Switch to the new ac97 bus support in sound/ac97 instead of the legacy
> snd_ac97 one.
>
> Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr>
I realized this one impacts sound/arm/pxa2xx-ac97.c.
This deserves a v2, with this patch being split into 2 pieces :
 - one for pxa2xx-ac97-lib.*
 - one which will be the true switch to the new ac97 bus.

Cheers.

--
Robert

^ permalink raw reply

* [PATCH V2 6/6] arm64: Add uprobe support
From: Pratyush Anand @ 2016-10-31  8:40 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161030140901.h6mdxbb4pgqpqs7j@localhost>

Hi Catalin,

On Sun, Oct 30, 2016 at 7:39 PM, Catalin Marinas
<catalin.marinas@arm.com> wrote:
> On Tue, Sep 27, 2016 at 01:18:00PM +0530, Pratyush Anand wrote:
>> --- /dev/null
>> +++ b/arch/arm64/kernel/probes/uprobes.c
>> @@ -0,0 +1,221 @@
>> +/*
>> + * Copyright (C) 2014-2016 Pratyush Anand <panand@redhat.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/highmem.h>
>> +#include <linux/ptrace.h>
>> +#include <linux/uprobes.h>
>> +#include <asm/cacheflush.h>
>> +
>> +#include "decode-insn.h"
>> +
>> +#define UPROBE_INV_FAULT_CODE        UINT_MAX
>> +
>> +bool is_trap_insn(uprobe_opcode_t *insn)
>> +{
>> +     return false;
>> +}
>
> On the previous series, I had a comment left unanswered with regards to
> always returning false in is_trap_insn():
>
> Looking at handle_swbp(), if we hit a breakpoint for which we don't have
> a valid uprobe, this function currently sends a SIGTRAP. But if
> is_trap_insn() returns false always, is_trap_at_addr() would return 0 in
> this case so the SIGTRAP is never issued.

A agreed on this that the older implementation i.e. the default one of
is_trap_insn() is better for the time being.

probably 'strtle r0, [r0], #160' would have the closest matching
aarch32 instruction wrt BRK64_OPCODE_UPROBES(0xd42000A0). But that too
seems a bad instruction. So, there might not be any aarch32
instruction which will match to uprobe BRK instruction.


Therefore, if I send a V3 by removing aacrh64
Hi Catalin,

On Sun, Oct 30, 2016 at 7:39 PM, Catalin Marinas
<catalin.marinas@arm.com> wrote:
> On Tue, Sep 27, 2016 at 01:18:00PM +0530, Pratyush Anand wrote:
>> --- /dev/null
>> +++ b/arch/arm64/kernel/probes/uprobes.c
>> @@ -0,0 +1,221 @@
>> +/*
>> + * Copyright (C) 2014-2016 Pratyush Anand <panand@redhat.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/highmem.h>
>> +#include <linux/ptrace.h>
>> +#include <linux/uprobes.h>
>> +#include <asm/cacheflush.h>
>> +
>> +#include "decode-insn.h"
>> +
>> +#define UPROBE_INV_FAULT_CODE        UINT_MAX
>> +
>> +bool is_trap_insn(uprobe_opcode_t *insn)
>> +{
>> +     return false;
>> +}
>
> On the previous series, I had a comment left unanswered with regards to
> always returning false in is_trap_insn():
>
> Looking at handle_swbp(), if we hit a breakpoint for which we don't have
> a valid uprobe, this function currently sends a SIGTRAP. But if
> is_trap_insn() returns false always, is_trap_at_addr() would return 0 in
> this case so the SIGTRAP is never issued.

A agreed on this that the older implementation i.e. the default one of
is_trap_insn() is better for the time being.

probably 'strtle r0, [r0], #160' would have the closest matching
aarch32 instruction wrt BRK64_OPCODE_UPROBES(0xd42000A0). But that too
seems a bad instruction. So, there might not be any aarch32
instruction which will match to uprobe BRK instruction.


Therefore, if I send a V3 by removing aacrh64
Hi Catalin,

On Sun, Oct 30, 2016 at 7:39 PM, Catalin Marinas
<catalin.marinas@arm.com> wrote:
> On Tue, Sep 27, 2016 at 01:18:00PM +0530, Pratyush Anand wrote:
>> --- /dev/null
>> +++ b/arch/arm64/kernel/probes/uprobes.c
>> @@ -0,0 +1,221 @@
>> +/*
>> + * Copyright (C) 2014-2016 Pratyush Anand <panand@redhat.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/highmem.h>
>> +#include <linux/ptrace.h>
>> +#include <linux/uprobes.h>
>> +#include <asm/cacheflush.h>
>> +
>> +#include "decode-insn.h"
>> +
>> +#define UPROBE_INV_FAULT_CODE        UINT_MAX
>> +
>> +bool is_trap_insn(uprobe_opcode_t *insn)
>> +{
>> +     return false;
>> +}
>
> On the previous series, I had a comment left unanswered with regards to
> always returning false in is_trap_insn():
>
> Looking at handle_swbp(), if we hit a breakpoint for which we don't have
> a valid uprobe, this function currently sends a SIGTRAP. But if
> is_trap_insn() returns false always, is_trap_at_addr() would return 0 in
> this case so the SIGTRAP is never issued.

A agreed on this that the older implementation i.e. the default one of
is_trap_insn() is better for the time being. I sent out V2 before your
last comment on it in V1 :(.

probably 'strtle r0, [r0], #160' would have the closest matching
aarch32 instruction wrt BRK64_OPCODE_UPROBES(0xd42000A0). But that too
seems a bad aarch32 instruction. So, there might not be any aarch32
instruction which will match to uprobe BRK instruction.


Therefore, if I send a V3 by removing aacrh64 is_trap_insn(), would
that be acceptable, or do you see any other issue with this patch
series? If there is anything else, I would address that in V3 as well.

Thanks for your review.

~Pratyush

^ permalink raw reply

* [PATCH 0/5] drm/sun4i: Handle TV overscan
From: Russell King - ARM Linux @ 2016-10-31  8:42 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161018100349.qm2f554oiwyjwrsi@lukather>

On Tue, Oct 18, 2016 at 12:03:49PM +0200, Maxime Ripard wrote:
> The first one is that this overscanning should be reported by the
> connector I guess? but this is really TV specific, so we need one way
> to let the user tell how the image is displayed on its side, and we
> cannot really autodetect it, and this needs to be done at runtime so
> that we can present some shiny interface to let it select which
> overscan ratio works for him/her.

See xbmc... they go through a nice shiny setup which includes adjusting
the visible area.  From what I remember, it has pointers on each corner
which you can adjust to be just visible on the screen, so xbmc knows
how much overscan there is, and xbmc itself reduces down to the user
set size.

> The second one is that we still need to expose the reduced modes to
> userspace, and not only the displayed size, so that the applications
> know what they must draw on. But I guess this could be adjusted by the
> core too.
> 
> In order to work consistently, I think all planes should be adjusted
> that way, so that relative coordinates are from the primary plane
> origin, and not the display origin. But that could be adjusted too by
> the core I guess.

I'm not sure about that - we want the graphics to be visible, but that
may not be appropriate for an video overlay frame.  It's quite common
for (eg) broadcast video to contain dead pixels or other artifacts on
the right hand side, and the broadcast video expects overscan to be
present.

I know this because I have run my TV with overscan disabled, even for
broadcast TV.

> The fourth one being the major one. Every time I raised the issue on
> IRC, the answer basically was "we don't care about analog", so I'm a
> bit pessimistic about whether dealing with this in the core would be
> accepted, hence why I chose to deal with this at the driver level.

Yea, that's quite sad, "analog" has become a dirty word, but really
this has nothing to do with "analog" at all - there are LCD TVs (and
some monitors) out there which take HDMI signals but refuse to
disable overscan no matter what you do to them if you provide them
with a "broadcast"  mode - so the analog excuse is very poor.

-- 
RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.

^ permalink raw reply

* Memory range end be inclusive or exclusive? Re: [PATCH v1 1/4] kexec: (bugfix) calc correct end address of memory ranges in device tree
From: AKASHI Takahiro @ 2016-10-31  8:50 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20160906002958.GB16712@linaro.org>

Simon,

On Tue, Sep 06, 2016 at 09:29:59AM +0900, AKASHI Takahiro wrote:
> Simon,
> 
> What is your opinion on this issue?

Pinged you several times so far.

Can you please give me your comment?
(attached below is the original patch.)

-Takahiro AKASHI

> 
> On Mon, Aug 01, 2016 at 01:52:40PM +0900, AKASHI Takahiro wrote:
> > On Fri, Jul 29, 2016 at 06:23:56PM +0100, Russell King - ARM Linux wrote:
> > > On Fri, Jul 29, 2016 at 10:12:26AM -0700, Geoff Levand wrote:
> > > > On Fri, 2016-07-29 at 09:27 +0100, Russell King - ARM Linux wrote:
> > > > 
> > > > > So, these functions are a mess and need fixing.
> > > > 
> > > > Since this change isn't really related to arm64 support, I'll
> > > > drop this patch from my series.
> > > 
> > > Do you have a case which triggers bugs in this code?
> > 
> > Actually, this patch was necessary when my kdump used "usable-memory"
> > properties in "memory" nodes, as on ppc64, to limit the usable memory
> > regions that can be used by crash dump kernel.
> > Since then, I've moved to the approach of using "mem=" kernel parameter,
> > then introducing a new property, "linux,usable-memory-range," under /chosen
> > and now we don't need this patch any more.
> > 
> > So, we can drop it but I still believe that it is buggy.
> 
> Due to the discussions[1], I may want to re-enable "usable-memory"
> property on arm64. In addition, I would like to add a function,
> dtb_add_usable_memory_properties(), a variant of
> add_usable_memory_properties(), to kexec/dt-ops.c.
> So this issue is quite crucial now.
> 
> [1] http://lists.infradead.org/pipermail/linux-arm-kernel/2016-August/452685.html
> 
> -Takahiro AKASHI
> 
> > Thanks,
> > -Takahiro AKASHI
> > 
> > > -- 
> > > RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
> > > FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
> > > according to speedtest.net.
===8<===
From: AKASHI Takahiro <takahiro.akashi@linaro.org>

The end address of "reg" attribute in device tree's memory should be
inclusive.
---
 kexec/fs2dt.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/kexec/fs2dt.c b/kexec/fs2dt.c
index 79aa0f3..953f78a 100644
--- a/kexec/fs2dt.c
+++ b/kexec/fs2dt.c
@@ -236,7 +236,8 @@ static void add_dyn_reconf_usable_mem_property__(int fd)
 						    ranges_size*8);
 				}
 				ranges[rlen++] = cpu_to_be64(loc_base);
-				ranges[rlen++] = cpu_to_be64(loc_end - loc_base);
+				ranges[rlen++] = cpu_to_be64(loc_end
+								- loc_base + 1);
 				rngs_cnt++;
 			}
 		}
@@ -350,7 +351,7 @@ static void add_usable_mem_property(int fd, size_t len)
 					    ranges_size*sizeof(*ranges));
 			}
 			ranges[rlen++] = cpu_to_be64(loc_base);
-			ranges[rlen++] = cpu_to_be64(loc_end - loc_base);
+			ranges[rlen++] = cpu_to_be64(loc_end - loc_base + 1);
 		}
 	}
 
-- 
2.10.0

^ permalink raw reply related

* [PATCH] [ARM] Fix stack alignment when processing backtraces
From: Russell King - ARM Linux @ 2016-10-31  8:51 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161018170510.GA12248@obsidianresearch.com>

On Tue, Oct 18, 2016 at 11:05:10AM -0600, Jason Gunthorpe wrote:
> The dumpstm helper within c_backtrace pushed 5 dwords onto the stack
> causing the stack to become unaligned and then calls printk. This
> causes memory corruption in the kernel which assumes AAPCS calling
> convention.
> 
> Since this bit of asm doesn't use the standard prologue just add
> another register to restore alignment.
> 
> Fixes: 7ab3f8d595a1b ("[ARM] Add ability to dump exception stacks to kernel backtraces")
> Signed-off-by: Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
> ---
>  arch/arm/lib/backtrace.S | 5 +++--
>  1 file changed, 3 insertions(+), 2 deletions(-)
> 
> In my case the kernel was hitting a WARN_ON during boot and then
> reliably failed to start the compiled-in initramfs.
> 
> I'm inferring that the stack misalignment caused some kind of memory
> corruption which wiped out the unpacked initramfs.
> 
> Saw with gcc 5.4.0 on a kirkwood armv5te
> 
> diff --git a/arch/arm/lib/backtrace.S b/arch/arm/lib/backtrace.S
> index fab5a50503ae..25e1cce19991 100644
> --- a/arch/arm/lib/backtrace.S
> +++ b/arch/arm/lib/backtrace.S
> @@ -116,7 +116,8 @@ ENDPROC(c_backtrace)
>  #define reg   r5
>  #define stack r6
>  
> -.Ldumpstm:	stmfd	sp!, {instr, reg, stack, r7, lr}
> +	        /* Must maintain 8 byte stack alignment */
> +.Ldumpstm:	stmfd	sp!, {r3, instr, reg, stack, r7, lr}
>  		mov	stack, r0
>  		mov	instr, r1
>  		mov	reg, #10
> @@ -140,7 +141,7 @@ ENDPROC(c_backtrace)
>  		teq	r7, #0
>  		adrne	r0, .Lcr
>  		blne	printk
> -		ldmfd	sp!, {instr, reg, stack, r7, pc}
> +		ldmfd	sp!, {r3, instr, reg, stack, r7, pc}

I'd prefer r8 to get used rather than r3, as it makes it look like
r3 is somehow required to be preserved when that's not the case.
Makes the code slightly more difficult to understand.

-- 
RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.

^ permalink raw reply

* [PATCH 5/5] irqchip: st: Remove obsolete platforms from dt binding doc
From: Peter Griffin @ 2016-10-31  9:06 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1477065443-10668-6-git-send-email-patrice.chotard@st.com>

On Fri, 21 Oct 2016, patrice.chotard at st.com wrote:

> From: Patrice Chotard <patrice.chotard@st.com>
> 
> STiH415/6 SoC support is being removed from the kernel.
> This patch updates the sti irchip and removes
> references to these obsolete platforms.
> 
> Signed-off-by: Patrice Chotard <patrice.chotard@st.com>
> Cc: <tglx@linutronix.de>
> Cc: <jason@lakedaemon.net>
> Cc: <marc.zyngier@arm.com>
> ---
>  .../devicetree/bindings/interrupt-controller/st,sti-irq-syscfg.txt  | 6 ++----
>  1 file changed, 2 insertions(+), 4 deletions(-)

Acked-by: Peter Griffin <peter.griffin@linaro.org>

^ permalink raw reply

* [PATCH v4 0/3] Add initial ZTE VOU DRM/KMS driver
From: Shawn Guo @ 2016-10-31  9:17 UTC (permalink / raw)
  To: linux-arm-kernel

From: Shawn Guo <shawn.guo@linaro.org>

The series adds the initial ZTE VOU display controller DRM/KMS driver.
There are still some features to be added, like overlay plane, scaling,
and more output devices support.  But it's already useful with dual
CRTCs and HDMI display working.

Changes for v4:
 - Move the hardware setup done in zx_hdmi_hw_init() and clock enable
   into .enable hook of drm_encoder_helper_funcs.
 - Compare pipe to crtc->index instead of using our own index counter.
 - Save the pipe check in zx_vou_enable[disable]_vblank by putting frame
   interrupt bit into zx_crtc_bits.
 - Use DRM_DEV_* for log messages instead of old dev_* functions
 - Return directly in case of -ETIMEDOUT in zx_hdmi_i2c_read to simplify
   the code for error condition.
 - Shuffle things around to make the crtc and plane state check in
   zx_gl_plane_atomic_check a bit clearer
 - Move hardware register definitions into headers instead of disturbing
   C file reading.
 - Save the call to drm_connector_unregister(), so that
   drm_connector_cleanup can directly be used as drm_connector_funcs
   .destroy hook.
 - Move vblank notification from .atomic_begin hook to .atomic_flush,
   so that vblank event is sent to user space after planes are committed
   rather than before.

Changes for v3:
 - Rebase to v4.9-rc1
 - Update bindings doc to use 'ranges' for address translation between
   parent and child devices.
 - Call drm_dev_register() last in bind function and drm_dev_unregister()
   first in unbind, so that drm_connector_regiser() can be saved from
   HDMI driver.
 - Instead of using open-coded drm_do_get_edid(), add an I2C adapter for
   HDMI DDC read and use drm_get_edid().
 - Improve the plane .atomic_check implementation by calling helper
   function drm_plane_helper_check_state().
 - Rename zx_crtc.c to zx_vou.c to avoid the confusion that the file
   implements crtc instance.
 - Store vou pointer in zx_crtc, so that we do not need to embed the
   pointer in zx_drm_private.
 - Create zx_readl/zx_writel/zx_writel_mask for register access.
 - Define a few macro helpers to ease the register bit setting, like
   SYNC_WIDE, BACK_PORCH and FRONT_PORCH.
 - Define main/aux channel specific register offset and bits in zx_crtc
   to save the use of is_main check
 - Sort include headers alphabetically
 - Removing encoder pointer out of the structure and constify struct
   vou_inf
 - Add log message for error conditions
 - Make the function calls in teardown path asymmetrical
 - A few coding style improvements like defining macro for sub-module
   address and changing code to save indentation level
 - Add a MAINTAINERS entry for ZTE ZX DRM driver

Changes for v2:
 - Change device tree bindings to kill the virtual display-subsystem
   node make VOU the parent node.

Shawn Guo (3):
  dt-bindings: add bindings doc for ZTE VOU display controller
  drm: zte: add initial vou drm driver
  MAINTAINERS: add an entry for ZTE ZX DRM driver

 .../devicetree/bindings/display/zte,vou.txt        |  84 +++
 MAINTAINERS                                        |   7 +
 drivers/gpu/drm/Kconfig                            |   2 +
 drivers/gpu/drm/Makefile                           |   1 +
 drivers/gpu/drm/zte/Kconfig                        |   8 +
 drivers/gpu/drm/zte/Makefile                       |   7 +
 drivers/gpu/drm/zte/zx_drm_drv.c                   | 267 +++++++++
 drivers/gpu/drm/zte/zx_drm_drv.h                   |  36 ++
 drivers/gpu/drm/zte/zx_hdmi.c                      | 624 +++++++++++++++++++
 drivers/gpu/drm/zte/zx_hdmi_regs.h                 |  56 ++
 drivers/gpu/drm/zte/zx_plane.c                     | 299 ++++++++++
 drivers/gpu/drm/zte/zx_plane.h                     |  26 +
 drivers/gpu/drm/zte/zx_plane_regs.h                |  91 +++
 drivers/gpu/drm/zte/zx_vou.c                       | 661 +++++++++++++++++++++
 drivers/gpu/drm/zte/zx_vou.h                       |  46 ++
 drivers/gpu/drm/zte/zx_vou_regs.h                  | 157 +++++
 16 files changed, 2372 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/zte,vou.txt
 create mode 100644 drivers/gpu/drm/zte/Kconfig
 create mode 100644 drivers/gpu/drm/zte/Makefile
 create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.c
 create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.h
 create mode 100644 drivers/gpu/drm/zte/zx_hdmi.c
 create mode 100644 drivers/gpu/drm/zte/zx_hdmi_regs.h
 create mode 100644 drivers/gpu/drm/zte/zx_plane.c
 create mode 100644 drivers/gpu/drm/zte/zx_plane.h
 create mode 100644 drivers/gpu/drm/zte/zx_plane_regs.h
 create mode 100644 drivers/gpu/drm/zte/zx_vou.c
 create mode 100644 drivers/gpu/drm/zte/zx_vou.h
 create mode 100644 drivers/gpu/drm/zte/zx_vou_regs.h

-- 
1.9.1

^ permalink raw reply

* [PATCH v4 1/3] dt-bindings: add bindings doc for ZTE VOU display controller
From: Shawn Guo @ 2016-10-31  9:17 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1477905445-4983-1-git-send-email-shawnguo@kernel.org>

From: Shawn Guo <shawn.guo@linaro.org>

It adds initial bindings doc for ZTE VOU display controller.  HDMI is
the only supported output device right now.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
Acked-by: Rob Herring <robh@kernel.org>
---
 .../devicetree/bindings/display/zte,vou.txt        | 84 ++++++++++++++++++++++
 1 file changed, 84 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/zte,vou.txt

diff --git a/Documentation/devicetree/bindings/display/zte,vou.txt b/Documentation/devicetree/bindings/display/zte,vou.txt
new file mode 100644
index 000000000000..740e5bd2e4f7
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/zte,vou.txt
@@ -0,0 +1,84 @@
+ZTE VOU Display Controller
+
+This is a display controller found on ZTE ZX296718 SoC.  It includes multiple
+Graphic Layer (GL) and Video Layer (VL), two Mixers/Channels, and a few blocks
+handling scaling, color space conversion etc.  VOU also integrates the support
+for typical output devices, like HDMI, TV Encoder, VGA, and RGB LCD.
+
+* Master VOU node
+
+It must be the parent node of all the sub-device nodes.
+
+Required properties:
+ - compatible: should be "zte,zx296718-vou"
+ - #address-cells: should be <1>
+ - #size-cells: should be <1>
+ - ranges: list of address translations between VOU and sub-devices
+
+* VOU DPC device
+
+Required properties:
+ - compatible: should be "zte,zx296718-dpc"
+ - reg: Physical base address and length of DPC register regions, one for each
+   entry in 'reg-names'
+ - reg-names: The names of register regions. The following regions are required:
+	"osd"
+	"timing_ctrl"
+	"dtrc"
+	"vou_ctrl"
+	"otfppu"
+ - interrupts: VOU DPC interrupt number to CPU
+ - clocks: A list of phandle + clock-specifier pairs, one for each entry
+   in 'clock-names'
+ - clock-names: A list of clock names.  The following clocks are required:
+	"aclk"
+	"ppu_wclk"
+	"main_wclk"
+	"aux_wclk"
+
+* HDMI output device
+
+Required properties:
+ - compatible: should be "zte,zx296718-hdmi"
+ - reg: Physical base address and length of the HDMI device IO region
+ - interrupts : HDMI interrupt number to CPU
+ - clocks: A list of phandle + clock-specifier pairs, one for each entry
+   in 'clock-names'
+ - clock-names: A list of clock names.  The following clocks are required:
+	"osc_cec"
+	"osc_clk"
+	"xclk"
+
+Example:
+
+vou: vou at 1440000 {
+	compatible = "zte,zx296718-vou";
+	#address-cells = <1>;
+	#size-cells = <1>;
+	ranges = <0 0x1440000 0x10000>;
+
+	dpc: dpc at 0 {
+		compatible = "zte,zx296718-dpc";
+		reg = <0x0000 0x1000>, <0x1000 0x1000>,
+		      <0x5000 0x1000>, <0x6000 0x1000>,
+		      <0xa000 0x1000>;
+		reg-names = "osd", "timing_ctrl",
+			    "dtrc", "vou_ctrl",
+			    "otfppu";
+		interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&topcrm VOU_ACLK>, <&topcrm VOU_PPU_WCLK>,
+			 <&topcrm VOU_MAIN_WCLK>, <&topcrm VOU_AUX_WCLK>;
+		clock-names = "aclk", "ppu_wclk",
+			      "main_wclk", "aux_wclk";
+	};
+
+	hdmi: hdmi at c000 {
+		compatible = "zte,zx296718-hdmi";
+		reg = <0xc000 0x4000>;
+		interrupts = <GIC_SPI 82 IRQ_TYPE_EDGE_RISING>;
+		clocks = <&topcrm HDMI_OSC_CEC>,
+			 <&topcrm HDMI_OSC_CLK>,
+			 <&topcrm HDMI_XCLK>;
+		clock-names = "osc_cec", "osc_clk", "xclk";
+	};
+};
-- 
1.9.1

^ permalink raw reply related

* [PATCH v4 2/3] drm: zte: add initial vou drm driver
From: Shawn Guo @ 2016-10-31  9:17 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1477905445-4983-1-git-send-email-shawnguo@kernel.org>

From: Shawn Guo <shawn.guo@linaro.org>

It adds the initial ZTE VOU display controller DRM driver.  There are
still some features to be added, like overlay plane, scaling, and more
output devices support.  But it's already useful with dual CRTCs and
HDMI monitor working.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
 drivers/gpu/drm/Kconfig             |   2 +
 drivers/gpu/drm/Makefile            |   1 +
 drivers/gpu/drm/zte/Kconfig         |   8 +
 drivers/gpu/drm/zte/Makefile        |   7 +
 drivers/gpu/drm/zte/zx_drm_drv.c    | 267 +++++++++++++++
 drivers/gpu/drm/zte/zx_drm_drv.h    |  36 ++
 drivers/gpu/drm/zte/zx_hdmi.c       | 624 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/zte/zx_hdmi_regs.h  |  56 +++
 drivers/gpu/drm/zte/zx_plane.c      | 299 ++++++++++++++++
 drivers/gpu/drm/zte/zx_plane.h      |  26 ++
 drivers/gpu/drm/zte/zx_plane_regs.h |  91 +++++
 drivers/gpu/drm/zte/zx_vou.c        | 661 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/zte/zx_vou.h        |  46 +++
 drivers/gpu/drm/zte/zx_vou_regs.h   | 157 +++++++++
 14 files changed, 2281 insertions(+)
 create mode 100644 drivers/gpu/drm/zte/Kconfig
 create mode 100644 drivers/gpu/drm/zte/Makefile
 create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.c
 create mode 100644 drivers/gpu/drm/zte/zx_drm_drv.h
 create mode 100644 drivers/gpu/drm/zte/zx_hdmi.c
 create mode 100644 drivers/gpu/drm/zte/zx_hdmi_regs.h
 create mode 100644 drivers/gpu/drm/zte/zx_plane.c
 create mode 100644 drivers/gpu/drm/zte/zx_plane.h
 create mode 100644 drivers/gpu/drm/zte/zx_plane_regs.h
 create mode 100644 drivers/gpu/drm/zte/zx_vou.c
 create mode 100644 drivers/gpu/drm/zte/zx_vou.h
 create mode 100644 drivers/gpu/drm/zte/zx_vou_regs.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 483059a22b1b..a91f8cecbe0f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -223,6 +223,8 @@ source "drivers/gpu/drm/hisilicon/Kconfig"
 
 source "drivers/gpu/drm/mediatek/Kconfig"
 
+source "drivers/gpu/drm/zte/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 25c720454017..f3251750c92b 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -86,3 +86,4 @@ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
 obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
 obj-$(CONFIG_DRM_ARCPGU)+= arc/
 obj-y			+= hisilicon/
+obj-$(CONFIG_DRM_ZTE)	+= zte/
diff --git a/drivers/gpu/drm/zte/Kconfig b/drivers/gpu/drm/zte/Kconfig
new file mode 100644
index 000000000000..4065b2840f1c
--- /dev/null
+++ b/drivers/gpu/drm/zte/Kconfig
@@ -0,0 +1,8 @@
+config DRM_ZTE
+	tristate "DRM Support for ZTE SoCs"
+	depends on DRM && ARCH_ZX
+	select DRM_KMS_CMA_HELPER
+	select DRM_KMS_FB_HELPER
+	select DRM_KMS_HELPER
+	help
+	  Choose this option to enable DRM on ZTE ZX SoCs.
diff --git a/drivers/gpu/drm/zte/Makefile b/drivers/gpu/drm/zte/Makefile
new file mode 100644
index 000000000000..699180bfd57c
--- /dev/null
+++ b/drivers/gpu/drm/zte/Makefile
@@ -0,0 +1,7 @@
+zxdrm-y := \
+	zx_drm_drv.o \
+	zx_hdmi.o \
+	zx_plane.o \
+	zx_vou.o
+
+obj-$(CONFIG_DRM_ZTE) += zxdrm.o
diff --git a/drivers/gpu/drm/zte/zx_drm_drv.c b/drivers/gpu/drm/zte/zx_drm_drv.c
new file mode 100644
index 000000000000..abc8099e6f53
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_drm_drv.c
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * 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/list.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/spinlock.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drmP.h>
+
+#include "zx_drm_drv.h"
+#include "zx_vou.h"
+
+struct zx_drm_private {
+	struct drm_fbdev_cma *fbdev;
+};
+
+static void zx_drm_fb_output_poll_changed(struct drm_device *drm)
+{
+	struct zx_drm_private *priv = drm->dev_private;
+
+	drm_fbdev_cma_hotplug_event(priv->fbdev);
+}
+
+static const struct drm_mode_config_funcs zx_drm_mode_config_funcs = {
+	.fb_create = drm_fb_cma_create,
+	.output_poll_changed = zx_drm_fb_output_poll_changed,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static void zx_drm_lastclose(struct drm_device *drm)
+{
+	struct zx_drm_private *priv = drm->dev_private;
+
+	drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static const struct file_operations zx_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 = noop_llseek,
+	.mmap = drm_gem_cma_mmap,
+};
+
+static struct drm_driver zx_drm_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+			   DRIVER_ATOMIC,
+	.lastclose = zx_drm_lastclose,
+	.get_vblank_counter = drm_vblank_no_hw_counter,
+	.enable_vblank = zx_vou_enable_vblank,
+	.disable_vblank = zx_vou_disable_vblank,
+	.gem_free_object = drm_gem_cma_free_object,
+	.gem_vm_ops = &drm_gem_cma_vm_ops,
+	.dumb_create = drm_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 = drm_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,
+	.fops = &zx_drm_fops,
+	.name = "zx-vou",
+	.desc = "ZTE VOU Controller DRM",
+	.date = "20160811",
+	.major = 1,
+	.minor = 0,
+};
+
+static int zx_drm_bind(struct device *dev)
+{
+	struct drm_device *drm;
+	struct zx_drm_private *priv;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	drm = drm_dev_alloc(&zx_drm_driver, dev);
+	if (!drm)
+		return -ENOMEM;
+
+	drm->dev_private = priv;
+	dev_set_drvdata(dev, drm);
+
+	drm_mode_config_init(drm);
+	drm->mode_config.min_width = 16;
+	drm->mode_config.min_height = 16;
+	drm->mode_config.max_width = 4096;
+	drm->mode_config.max_height = 4096;
+	drm->mode_config.funcs = &zx_drm_mode_config_funcs;
+
+	ret = component_bind_all(dev, drm);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to bind all components: %d\n", ret);
+		goto out_unregister;
+	}
+
+	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
+	if (ret < 0) {
+		DRM_DEV_ERROR(dev, "failed to init vblank: %d\n", ret);
+		goto out_unbind;
+	}
+
+	/*
+	 * We will manage irq handler on our own.  In this case, irq_enabled
+	 * need to be true for using vblank core support.
+	 */
+	drm->irq_enabled = true;
+
+	drm_mode_config_reset(drm);
+	drm_kms_helper_poll_init(drm);
+
+	priv->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc,
+					 drm->mode_config.num_connector);
+	if (IS_ERR(priv->fbdev)) {
+		ret = PTR_ERR(priv->fbdev);
+		DRM_DEV_ERROR(dev, "failed to init cma fbdev: %d\n", ret);
+		priv->fbdev = NULL;
+		goto out_poll_fini;
+	}
+
+	ret = drm_dev_register(drm, 0);
+	if (ret)
+		goto out_fbdev_fini;
+
+	return 0;
+
+out_fbdev_fini:
+	if (priv->fbdev) {
+		drm_fbdev_cma_fini(priv->fbdev);
+		priv->fbdev = NULL;
+	}
+out_poll_fini:
+	drm_kms_helper_poll_fini(drm);
+	drm_mode_config_cleanup(drm);
+	drm_vblank_cleanup(drm);
+out_unbind:
+	component_unbind_all(dev, drm);
+out_unregister:
+	dev_set_drvdata(dev, NULL);
+	drm->dev_private = NULL;
+	drm_dev_unref(drm);
+	return ret;
+}
+
+static void zx_drm_unbind(struct device *dev)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+	struct zx_drm_private *priv = drm->dev_private;
+
+	drm_dev_unregister(drm);
+	if (priv->fbdev) {
+		drm_fbdev_cma_fini(priv->fbdev);
+		priv->fbdev = NULL;
+	}
+	drm_kms_helper_poll_fini(drm);
+	drm_mode_config_cleanup(drm);
+	drm_vblank_cleanup(drm);
+	component_unbind_all(dev, drm);
+	dev_set_drvdata(dev, NULL);
+	drm->dev_private = NULL;
+	drm_dev_unref(drm);
+}
+
+static const struct component_master_ops zx_drm_master_ops = {
+	.bind = zx_drm_bind,
+	.unbind = zx_drm_unbind,
+};
+
+static int compare_of(struct device *dev, void *data)
+{
+	return dev->of_node == data;
+}
+
+static int zx_drm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *parent = dev->of_node;
+	struct device_node *child;
+	struct component_match *match = NULL;
+	int ret;
+
+	ret = of_platform_populate(parent, NULL, NULL, dev);
+	if (ret)
+		return ret;
+
+	for_each_available_child_of_node(parent, child) {
+		component_match_add(dev, &match, compare_of, child);
+		of_node_put(child);
+	}
+
+	return component_master_add_with_match(dev, &zx_drm_master_ops, match);
+}
+
+static int zx_drm_remove(struct platform_device *pdev)
+{
+	component_master_del(&pdev->dev, &zx_drm_master_ops);
+	return 0;
+}
+
+static const struct of_device_id zx_drm_of_match[] = {
+	{ .compatible = "zte,zx296718-vou", },
+	{ /* end */ },
+};
+MODULE_DEVICE_TABLE(of, zx_drm_of_match);
+
+static struct platform_driver zx_drm_platform_driver = {
+	.probe = zx_drm_probe,
+	.remove = zx_drm_remove,
+	.driver	= {
+		.name = "zx-drm",
+		.of_match_table	= zx_drm_of_match,
+	},
+};
+
+static struct platform_driver *drivers[] = {
+	&zx_crtc_driver,
+	&zx_hdmi_driver,
+	&zx_drm_platform_driver,
+};
+
+static int zx_drm_init(void)
+{
+	return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
+}
+module_init(zx_drm_init);
+
+static void zx_drm_exit(void)
+{
+	platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
+}
+module_exit(zx_drm_exit);
+
+MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
+MODULE_DESCRIPTION("ZTE ZX VOU DRM driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/zte/zx_drm_drv.h b/drivers/gpu/drm/zte/zx_drm_drv.h
new file mode 100644
index 000000000000..e65cd18a6cba
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_drm_drv.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * 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 __ZX_DRM_DRV_H__
+#define __ZX_DRM_DRV_H__
+
+extern struct platform_driver zx_crtc_driver;
+extern struct platform_driver zx_hdmi_driver;
+
+static inline u32 zx_readl(void __iomem *reg)
+{
+	return readl_relaxed(reg);
+}
+
+static inline void zx_writel(void __iomem *reg, u32 val)
+{
+	writel_relaxed(val, reg);
+}
+
+static inline void zx_writel_mask(void __iomem *reg, u32 mask, u32 val)
+{
+	u32 tmp;
+
+	tmp = zx_readl(reg);
+	tmp = (tmp & ~mask) | (val & mask);
+	zx_writel(reg, tmp);
+}
+
+#endif /* __ZX_DRM_DRV_H__ */
diff --git a/drivers/gpu/drm/zte/zx_hdmi.c b/drivers/gpu/drm/zte/zx_hdmi.c
new file mode 100644
index 000000000000..6bf6c364811e
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_hdmi.c
@@ -0,0 +1,624 @@
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * 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/delay.h>
+#include <linux/err.h>
+#include <linux/hdmi.h>
+#include <linux/irq.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+#include <drm/drmP.h>
+
+#include "zx_hdmi_regs.h"
+#include "zx_vou.h"
+
+#define ZX_HDMI_INFOFRAME_SIZE		31
+#define DDC_SEGMENT_ADDR		0x30
+
+struct zx_hdmi_i2c {
+	struct i2c_adapter adap;
+	struct mutex lock;
+};
+
+struct zx_hdmi {
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+	struct zx_hdmi_i2c *ddc;
+	struct device *dev;
+	struct drm_device *drm;
+	void __iomem *mmio;
+	struct clk *cec_clk;
+	struct clk *osc_clk;
+	struct clk *xclk;
+	bool sink_is_hdmi;
+	bool sink_has_audio;
+	const struct vou_inf *inf;
+};
+
+#define to_zx_hdmi(x) container_of(x, struct zx_hdmi, x)
+
+static const struct vou_inf vou_inf_hdmi = {
+	.id = VOU_HDMI,
+	.data_sel = VOU_YUV444,
+	.clocks_en_bits = BIT(24) | BIT(18) | BIT(6),
+	.clocks_sel_bits = BIT(13) | BIT(2),
+};
+
+static inline u8 hdmi_readb(struct zx_hdmi *hdmi, u16 offset)
+{
+	return readl_relaxed(hdmi->mmio + offset * 4);
+}
+
+static inline void hdmi_writeb(struct zx_hdmi *hdmi, u16 offset, u8 val)
+{
+	writel_relaxed(val, hdmi->mmio + offset * 4);
+}
+
+static inline void hdmi_writeb_mask(struct zx_hdmi *hdmi, u16 offset,
+				    u8 mask, u8 val)
+{
+	u8 tmp;
+
+	tmp = hdmi_readb(hdmi, offset);
+	tmp = (tmp & ~mask) | (val & mask);
+	hdmi_writeb(hdmi, offset, tmp);
+}
+
+static int zx_hdmi_infoframe_trans(struct zx_hdmi *hdmi,
+				   union hdmi_infoframe *frame, u8 fsel)
+{
+	u8 buffer[ZX_HDMI_INFOFRAME_SIZE];
+	int num;
+	int i;
+
+	hdmi_writeb(hdmi, TPI_INFO_FSEL, fsel);
+
+	num = hdmi_infoframe_pack(frame, buffer, ZX_HDMI_INFOFRAME_SIZE);
+	if (num < 0) {
+		DRM_DEV_ERROR(hdmi->dev, "failed to pack infoframe: %d\n", num);
+		return num;
+	}
+
+	for (i = 0; i < num; i++)
+		hdmi_writeb(hdmi, TPI_INFO_B0 + i, buffer[i]);
+
+	hdmi_writeb_mask(hdmi, TPI_INFO_EN, TPI_INFO_TRANS_RPT,
+			 TPI_INFO_TRANS_RPT);
+	hdmi_writeb_mask(hdmi, TPI_INFO_EN, TPI_INFO_TRANS_EN,
+			 TPI_INFO_TRANS_EN);
+
+	return num;
+}
+
+static int zx_hdmi_config_video_vsi(struct zx_hdmi *hdmi,
+				    struct drm_display_mode *mode)
+{
+	union hdmi_infoframe frame;
+	int ret;
+
+	ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi,
+							  mode);
+	if (ret) {
+		DRM_DEV_ERROR(hdmi->dev, "failed to get vendor infoframe: %d\n",
+			      ret);
+		return ret;
+	}
+
+	return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_VSIF);
+}
+
+static int zx_hdmi_config_video_avi(struct zx_hdmi *hdmi,
+				    struct drm_display_mode *mode)
+{
+	union hdmi_infoframe frame;
+	int ret;
+
+	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode);
+	if (ret) {
+		DRM_DEV_ERROR(hdmi->dev, "failed to get avi infoframe: %d\n",
+			      ret);
+		return ret;
+	}
+
+	/* We always use YUV444 for HDMI output. */
+	frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
+
+	return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_AVI);
+}
+
+static void zx_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+				     struct drm_display_mode *mode,
+				     struct drm_display_mode *adj_mode)
+{
+	struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
+
+	if (hdmi->sink_is_hdmi) {
+		zx_hdmi_config_video_avi(hdmi, mode);
+		zx_hdmi_config_video_vsi(hdmi, mode);
+	}
+}
+
+static void zx_hdmi_phy_start(struct zx_hdmi *hdmi)
+{
+	/* Copy from ZTE BSP code */
+	hdmi_writeb(hdmi, 0x222, 0x0);
+	hdmi_writeb(hdmi, 0x224, 0x4);
+	hdmi_writeb(hdmi, 0x909, 0x0);
+	hdmi_writeb(hdmi, 0x7b0, 0x90);
+	hdmi_writeb(hdmi, 0x7b1, 0x00);
+	hdmi_writeb(hdmi, 0x7b2, 0xa7);
+	hdmi_writeb(hdmi, 0x7b8, 0xaa);
+	hdmi_writeb(hdmi, 0x7b2, 0xa7);
+	hdmi_writeb(hdmi, 0x7b3, 0x0f);
+	hdmi_writeb(hdmi, 0x7b4, 0x0f);
+	hdmi_writeb(hdmi, 0x7b5, 0x55);
+	hdmi_writeb(hdmi, 0x7b7, 0x03);
+	hdmi_writeb(hdmi, 0x7b9, 0x12);
+	hdmi_writeb(hdmi, 0x7ba, 0x32);
+	hdmi_writeb(hdmi, 0x7bc, 0x68);
+	hdmi_writeb(hdmi, 0x7be, 0x40);
+	hdmi_writeb(hdmi, 0x7bf, 0x84);
+	hdmi_writeb(hdmi, 0x7c1, 0x0f);
+	hdmi_writeb(hdmi, 0x7c8, 0x02);
+	hdmi_writeb(hdmi, 0x7c9, 0x03);
+	hdmi_writeb(hdmi, 0x7ca, 0x40);
+	hdmi_writeb(hdmi, 0x7dc, 0x31);
+	hdmi_writeb(hdmi, 0x7e2, 0x04);
+	hdmi_writeb(hdmi, 0x7e0, 0x06);
+	hdmi_writeb(hdmi, 0x7cb, 0x68);
+	hdmi_writeb(hdmi, 0x7f9, 0x02);
+	hdmi_writeb(hdmi, 0x7b6, 0x02);
+	hdmi_writeb(hdmi, 0x7f3, 0x0);
+}
+
+static void zx_hdmi_hw_enable(struct zx_hdmi *hdmi)
+{
+	/* Enable pclk */
+	hdmi_writeb_mask(hdmi, CLKPWD, CLKPWD_PDIDCK, CLKPWD_PDIDCK);
+
+	/* Enable HDMI for TX */
+	hdmi_writeb_mask(hdmi, FUNC_SEL, FUNC_HDMI_EN, FUNC_HDMI_EN);
+
+	/* Enable deep color packet */
+	hdmi_writeb_mask(hdmi, P2T_CTRL, P2T_DC_PKT_EN, P2T_DC_PKT_EN);
+
+	/* Enable HDMI/MHL mode for output */
+	hdmi_writeb_mask(hdmi, TEST_TXCTRL, TEST_TXCTRL_HDMI_MODE,
+			 TEST_TXCTRL_HDMI_MODE);
+
+	/* Configure reg_qc_sel */
+	hdmi_writeb(hdmi, HDMICTL4, 0x3);
+
+	/* Enable interrupt */
+	hdmi_writeb_mask(hdmi, INTR1_MASK, INTR1_MONITOR_DETECT,
+			 INTR1_MONITOR_DETECT);
+
+	/* Start up phy */
+	zx_hdmi_phy_start(hdmi);
+}
+
+static void zx_hdmi_hw_disable(struct zx_hdmi *hdmi)
+{
+	/* Disable interrupt */
+	hdmi_writeb_mask(hdmi, INTR1_MASK, INTR1_MONITOR_DETECT, 0);
+
+	/* Disable deep color packet */
+	hdmi_writeb_mask(hdmi, P2T_CTRL, P2T_DC_PKT_EN, P2T_DC_PKT_EN);
+
+	/* Disable HDMI for TX */
+	hdmi_writeb_mask(hdmi, FUNC_SEL, FUNC_HDMI_EN, 0);
+
+	/* Disable pclk */
+	hdmi_writeb_mask(hdmi, CLKPWD, CLKPWD_PDIDCK, 0);
+}
+
+static void zx_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+	struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
+
+	clk_prepare_enable(hdmi->cec_clk);
+	clk_prepare_enable(hdmi->osc_clk);
+	clk_prepare_enable(hdmi->xclk);
+
+	zx_hdmi_hw_enable(hdmi);
+
+	vou_inf_enable(hdmi->inf, encoder->crtc);
+}
+
+static void zx_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct zx_hdmi *hdmi = to_zx_hdmi(encoder);
+
+	vou_inf_disable(hdmi->inf, encoder->crtc);
+
+	zx_hdmi_hw_disable(hdmi);
+
+	clk_disable_unprepare(hdmi->xclk);
+	clk_disable_unprepare(hdmi->osc_clk);
+	clk_disable_unprepare(hdmi->cec_clk);
+}
+
+static const struct drm_encoder_helper_funcs zx_hdmi_encoder_helper_funcs = {
+	.enable	= zx_hdmi_encoder_enable,
+	.disable = zx_hdmi_encoder_disable,
+	.mode_set = zx_hdmi_encoder_mode_set,
+};
+
+static const struct drm_encoder_funcs zx_hdmi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static int zx_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+	struct zx_hdmi *hdmi = to_zx_hdmi(connector);
+	struct edid *edid;
+	int ret;
+
+	edid = drm_get_edid(connector, &hdmi->ddc->adap);
+	if (!edid)
+		return 0;
+
+	hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid);
+	hdmi->sink_has_audio = drm_detect_monitor_audio(edid);
+	drm_mode_connector_update_edid_property(connector, edid);
+	ret = drm_add_edid_modes(connector, edid);
+	kfree(edid);
+
+	return ret;
+}
+
+static enum drm_mode_status
+zx_hdmi_connector_mode_valid(struct drm_connector *connector,
+			     struct drm_display_mode *mode)
+{
+	return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs zx_hdmi_connector_helper_funcs = {
+	.get_modes = zx_hdmi_connector_get_modes,
+	.mode_valid = zx_hdmi_connector_mode_valid,
+};
+
+static enum drm_connector_status
+zx_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct zx_hdmi *hdmi = to_zx_hdmi(connector);
+
+	return (hdmi_readb(hdmi, TPI_HPD_RSEN) & TPI_HPD_CONNECTION) ?
+		connector_status_connected : connector_status_disconnected;
+}
+
+static const struct drm_connector_funcs zx_hdmi_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = zx_hdmi_connector_detect,
+	.destroy = drm_connector_cleanup,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int zx_hdmi_register(struct drm_device *drm, struct zx_hdmi *hdmi)
+{
+	struct drm_encoder *encoder = &hdmi->encoder;
+
+	encoder->possible_crtcs = VOU_CRTC_MASK;
+
+	drm_encoder_init(drm, encoder, &zx_hdmi_encoder_funcs,
+			 DRM_MODE_ENCODER_TMDS, NULL);
+	drm_encoder_helper_add(encoder, &zx_hdmi_encoder_helper_funcs);
+
+	hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
+
+	drm_connector_init(drm, &hdmi->connector, &zx_hdmi_connector_funcs,
+			   DRM_MODE_CONNECTOR_HDMIA);
+	drm_connector_helper_add(&hdmi->connector,
+				 &zx_hdmi_connector_helper_funcs);
+
+	drm_mode_connector_attach_encoder(&hdmi->connector, encoder);
+
+	return 0;
+}
+
+static irqreturn_t zx_hdmi_irq_thread(int irq, void *dev_id)
+{
+	struct zx_hdmi *hdmi = dev_id;
+
+	drm_helper_hpd_irq_event(hdmi->connector.dev);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t zx_hdmi_irq_handler(int irq, void *dev_id)
+{
+	struct zx_hdmi *hdmi = dev_id;
+	u8 lstat;
+
+	lstat = hdmi_readb(hdmi, L1_INTR_STAT);
+
+	/* Monitor detect/HPD interrupt */
+	if (lstat & L1_INTR_STAT_INTR1) {
+		u8 stat;
+
+		stat = hdmi_readb(hdmi, INTR1_STAT);
+		hdmi_writeb(hdmi, INTR1_STAT, stat);
+
+		if (stat & INTR1_MONITOR_DETECT)
+			return IRQ_WAKE_THREAD;
+	}
+
+	return IRQ_NONE;
+}
+
+static int zx_hdmi_i2c_read(struct zx_hdmi *hdmi, struct i2c_msg *msg)
+{
+	int len = msg->len;
+	u8 *buf = msg->buf;
+	int retry = 0;
+	int ret = 0;
+
+	/* Bits [9:8] of bytes */
+	hdmi_writeb(hdmi, ZX_DDC_DIN_CNT2, (len >> 8) & 0xff);
+	/* Bits [7:0] of bytes */
+	hdmi_writeb(hdmi, ZX_DDC_DIN_CNT1, len & 0xff);
+
+	/* Clear FIFO */
+	hdmi_writeb_mask(hdmi, ZX_DDC_CMD, DDC_CMD_MASK, DDC_CMD_CLEAR_FIFO);
+
+	/* Kick off the read */
+	hdmi_writeb_mask(hdmi, ZX_DDC_CMD, DDC_CMD_MASK,
+			 DDC_CMD_SEQUENTIAL_READ);
+
+	while (len > 0) {
+		int cnt, i;
+
+		/* FIFO needs some time to get ready */
+		usleep_range(500, 1000);
+
+		cnt = hdmi_readb(hdmi, ZX_DDC_DOUT_CNT) & DDC_DOUT_CNT_MASK;
+		if (cnt == 0) {
+			if (++retry > 5) {
+				DRM_DEV_ERROR(hdmi->dev,
+					      "DDC FIFO read timed out!");
+				return -ETIMEDOUT;
+			}
+			continue;
+		}
+
+		for (i = 0; i < cnt; i++)
+			*buf++ = hdmi_readb(hdmi, ZX_DDC_DATA);
+		len -= cnt;
+	}
+
+	return ret;
+}
+
+static int zx_hdmi_i2c_write(struct zx_hdmi *hdmi, struct i2c_msg *msg)
+{
+	/*
+	 * The DDC I2C adapter is only for reading EDID data, so we assume
+	 * that the write to this adapter must be the EDID data offset.
+	 */
+	if ((msg->len != 1) ||
+	    ((msg->addr != DDC_ADDR) && (msg->addr != DDC_SEGMENT_ADDR)))
+		return -EINVAL;
+
+	if (msg->addr == DDC_SEGMENT_ADDR)
+		hdmi_writeb(hdmi, ZX_DDC_SEGM, msg->addr << 1);
+	else if (msg->addr == DDC_ADDR)
+		hdmi_writeb(hdmi, ZX_DDC_ADDR, msg->addr << 1);
+
+	hdmi_writeb(hdmi, ZX_DDC_OFFSET, msg->buf[0]);
+
+	return 0;
+}
+
+static int zx_hdmi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+			    int num)
+{
+	struct zx_hdmi *hdmi = i2c_get_adapdata(adap);
+	struct zx_hdmi_i2c *ddc = hdmi->ddc;
+	int i, ret = 0;
+
+	mutex_lock(&ddc->lock);
+
+	/* Enable DDC master access */
+	hdmi_writeb_mask(hdmi, TPI_DDC_MASTER_EN, HW_DDC_MASTER, HW_DDC_MASTER);
+
+	for (i = 0; i < num; i++) {
+		DRM_DEV_DEBUG(hdmi->dev,
+			      "xfer: num: %d/%d, len: %d, flags: %#x\n",
+			      i + 1, num, msgs[i].len, msgs[i].flags);
+
+		if (msgs[i].flags & I2C_M_RD)
+			ret = zx_hdmi_i2c_read(hdmi, &msgs[i]);
+		else
+			ret = zx_hdmi_i2c_write(hdmi, &msgs[i]);
+
+		if (ret < 0)
+			break;
+	}
+
+	if (!ret)
+		ret = num;
+
+	/* Disable DDC master access */
+	hdmi_writeb_mask(hdmi, TPI_DDC_MASTER_EN, HW_DDC_MASTER, 0);
+
+	mutex_unlock(&ddc->lock);
+
+	return ret;
+}
+
+static u32 zx_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm zx_hdmi_algorithm = {
+	.master_xfer	= zx_hdmi_i2c_xfer,
+	.functionality	= zx_hdmi_i2c_func,
+};
+
+static int zx_hdmi_ddc_register(struct zx_hdmi *hdmi)
+{
+	struct i2c_adapter *adap;
+	struct zx_hdmi_i2c *ddc;
+	int ret;
+
+	ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
+	if (!ddc)
+		return -ENOMEM;
+
+	hdmi->ddc = ddc;
+	mutex_init(&ddc->lock);
+
+	adap = &ddc->adap;
+	adap->owner = THIS_MODULE;
+	adap->class = I2C_CLASS_DDC;
+	adap->dev.parent = hdmi->dev;
+	adap->algo = &zx_hdmi_algorithm;
+	snprintf(adap->name, sizeof(adap->name), "zx hdmi i2c");
+
+	ret = i2c_add_adapter(adap);
+	if (ret) {
+		DRM_DEV_ERROR(hdmi->dev, "failed to add I2C adapter: %d\n",
+			      ret);
+		return ret;
+	}
+
+	i2c_set_adapdata(adap, hdmi);
+
+	return 0;
+}
+
+static int zx_hdmi_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct resource *res;
+	struct zx_hdmi *hdmi;
+	int irq;
+	int ret;
+
+	hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+
+	hdmi->dev = dev;
+	hdmi->drm = drm;
+	hdmi->inf = &vou_inf_hdmi;
+
+	dev_set_drvdata(dev, hdmi);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	hdmi->mmio = devm_ioremap_resource(dev, res);
+	if (IS_ERR(hdmi->mmio)) {
+		ret = PTR_ERR(hdmi->mmio);
+		DRM_DEV_ERROR(dev, "failed to remap hdmi region: %d\n", ret);
+		return ret;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	hdmi->cec_clk = devm_clk_get(hdmi->dev, "osc_cec");
+	if (IS_ERR(hdmi->cec_clk)) {
+		ret = PTR_ERR(hdmi->cec_clk);
+		DRM_DEV_ERROR(dev, "failed to get cec_clk: %d\n", ret);
+		return ret;
+	}
+
+	hdmi->osc_clk = devm_clk_get(hdmi->dev, "osc_clk");
+	if (IS_ERR(hdmi->osc_clk)) {
+		ret = PTR_ERR(hdmi->osc_clk);
+		DRM_DEV_ERROR(dev, "failed to get osc_clk: %d\n", ret);
+		return ret;
+	}
+
+	hdmi->xclk = devm_clk_get(hdmi->dev, "xclk");
+	if (IS_ERR(hdmi->xclk)) {
+		ret = PTR_ERR(hdmi->xclk);
+		DRM_DEV_ERROR(dev, "failed to get xclk: %d\n", ret);
+		return ret;
+	}
+
+	ret = zx_hdmi_ddc_register(hdmi);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to register ddc: %d\n", ret);
+		return ret;
+	}
+
+	ret = zx_hdmi_register(drm, hdmi);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to register hdmi: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_request_threaded_irq(dev, irq, zx_hdmi_irq_handler,
+					zx_hdmi_irq_thread, IRQF_SHARED,
+					dev_name(dev), hdmi);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to request threaded irq: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void zx_hdmi_unbind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct zx_hdmi *hdmi = dev_get_drvdata(dev);
+
+	hdmi->connector.funcs->destroy(&hdmi->connector);
+	hdmi->encoder.funcs->destroy(&hdmi->encoder);
+}
+
+static const struct component_ops zx_hdmi_component_ops = {
+	.bind = zx_hdmi_bind,
+	.unbind = zx_hdmi_unbind,
+};
+
+static int zx_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &zx_hdmi_component_ops);
+}
+
+static int zx_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &zx_hdmi_component_ops);
+	return 0;
+}
+
+static const struct of_device_id zx_hdmi_of_match[] = {
+	{ .compatible = "zte,zx296718-hdmi", },
+	{ /* end */ },
+};
+MODULE_DEVICE_TABLE(of, zx_hdmi_of_match);
+
+struct platform_driver zx_hdmi_driver = {
+	.probe = zx_hdmi_probe,
+	.remove = zx_hdmi_remove,
+	.driver	= {
+		.name = "zx-hdmi",
+		.of_match_table	= zx_hdmi_of_match,
+	},
+};
diff --git a/drivers/gpu/drm/zte/zx_hdmi_regs.h b/drivers/gpu/drm/zte/zx_hdmi_regs.h
new file mode 100644
index 000000000000..de911f66b658
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_hdmi_regs.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * 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 __ZX_HDMI_REGS_H__
+#define __ZX_HDMI_REGS_H__
+
+#define FUNC_SEL			0x000b
+#define FUNC_HDMI_EN			BIT(0)
+#define CLKPWD				0x000d
+#define CLKPWD_PDIDCK			BIT(2)
+#define P2T_CTRL			0x0066
+#define P2T_DC_PKT_EN			BIT(7)
+#define L1_INTR_STAT			0x007e
+#define L1_INTR_STAT_INTR1		BIT(0)
+#define INTR1_STAT			0x008f
+#define INTR1_MASK			0x0095
+#define INTR1_MONITOR_DETECT		(BIT(5) | BIT(6))
+#define ZX_DDC_ADDR			0x00ed
+#define ZX_DDC_SEGM			0x00ee
+#define ZX_DDC_OFFSET			0x00ef
+#define ZX_DDC_DIN_CNT1			0x00f0
+#define ZX_DDC_DIN_CNT2			0x00f1
+#define ZX_DDC_CMD			0x00f3
+#define DDC_CMD_MASK			0xf
+#define DDC_CMD_CLEAR_FIFO		0x9
+#define DDC_CMD_SEQUENTIAL_READ		0x2
+#define ZX_DDC_DATA			0x00f4
+#define ZX_DDC_DOUT_CNT			0x00f5
+#define DDC_DOUT_CNT_MASK		0x1f
+#define TEST_TXCTRL			0x00f7
+#define TEST_TXCTRL_HDMI_MODE		BIT(1)
+#define HDMICTL4			0x0235
+#define TPI_HPD_RSEN			0x063b
+#define TPI_HPD_CONNECTION		(BIT(1) | BIT(2))
+#define TPI_INFO_FSEL			0x06bf
+#define FSEL_AVI			0
+#define FSEL_GBD			1
+#define FSEL_AUDIO			2
+#define FSEL_SPD			3
+#define FSEL_MPEG			4
+#define FSEL_VSIF			5
+#define TPI_INFO_B0			0x06c0
+#define TPI_INFO_EN			0x06df
+#define TPI_INFO_TRANS_EN		BIT(7)
+#define TPI_INFO_TRANS_RPT		BIT(6)
+#define TPI_DDC_MASTER_EN		0x06f8
+#define HW_DDC_MASTER			BIT(7)
+
+#endif /* __ZX_HDMI_REGS_H__ */
diff --git a/drivers/gpu/drm/zte/zx_plane.c b/drivers/gpu/drm/zte/zx_plane.c
new file mode 100644
index 000000000000..70dfea9267dd
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_plane.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * 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 <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drmP.h>
+
+#include "zx_drm_drv.h"
+#include "zx_plane.h"
+#include "zx_plane_regs.h"
+#include "zx_vou.h"
+
+struct zx_plane {
+	struct drm_plane plane;
+	void __iomem *layer;
+	void __iomem *csc;
+	void __iomem *hbsc;
+	void __iomem *rsz;
+};
+
+#define to_zx_plane(plane)	container_of(plane, struct zx_plane, plane)
+
+static const uint32_t gl_formats[] = {
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_RGB888,
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_ARGB1555,
+	DRM_FORMAT_ARGB4444,
+};
+
+static int zx_gl_plane_atomic_check(struct drm_plane *plane,
+				    struct drm_plane_state *plane_state)
+{
+	struct drm_framebuffer *fb = plane_state->fb;
+	struct drm_crtc *crtc = plane_state->crtc;
+	struct drm_crtc_state *crtc_state;
+	struct drm_rect clip;
+
+	if (!crtc || !fb)
+		return 0;
+
+	crtc_state = drm_atomic_get_existing_crtc_state(plane_state->state,
+							crtc);
+	if (WARN_ON(!crtc_state))
+		return -EINVAL;
+
+	/* nothing to check when disabling or disabled */
+	if (!crtc_state->enable)
+		return 0;
+
+	/* plane must be enabled */
+	if (!plane_state->crtc)
+		return -EINVAL;
+
+	clip.x1 = 0;
+	clip.y1 = 0;
+	clip.x2 = crtc_state->adjusted_mode.hdisplay;
+	clip.y2 = crtc_state->adjusted_mode.vdisplay;
+
+	return drm_plane_helper_check_state(plane_state, &clip,
+					    DRM_PLANE_HELPER_NO_SCALING,
+					    DRM_PLANE_HELPER_NO_SCALING,
+					    false, true);
+}
+
+static int zx_gl_get_fmt(uint32_t format)
+{
+	switch (format) {
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XRGB8888:
+		return GL_FMT_ARGB8888;
+	case DRM_FORMAT_RGB888:
+		return GL_FMT_RGB888;
+	case DRM_FORMAT_RGB565:
+		return GL_FMT_RGB565;
+	case DRM_FORMAT_ARGB1555:
+		return GL_FMT_ARGB1555;
+	case DRM_FORMAT_ARGB4444:
+		return GL_FMT_ARGB4444;
+	default:
+		WARN_ONCE(1, "invalid pixel format %d\n", format);
+		return -EINVAL;
+	}
+}
+
+static inline void zx_gl_set_update(struct zx_plane *zplane)
+{
+	void __iomem *layer = zplane->layer;
+
+	zx_writel_mask(layer + GL_CTRL0, GL_UPDATE, GL_UPDATE);
+}
+
+static inline void zx_gl_rsz_set_update(struct zx_plane *zplane)
+{
+	zx_writel(zplane->rsz + RSZ_ENABLE_CFG, 1);
+}
+
+void zx_plane_set_update(struct drm_plane *plane)
+{
+	struct zx_plane *zplane = to_zx_plane(plane);
+
+	zx_gl_rsz_set_update(zplane);
+	zx_gl_set_update(zplane);
+}
+
+static void zx_gl_rsz_setup(struct zx_plane *zplane, u32 src_w, u32 src_h,
+			    u32 dst_w, u32 dst_h)
+{
+	void __iomem *rsz = zplane->rsz;
+
+	zx_writel(rsz + RSZ_SRC_CFG, RSZ_VER(src_h - 1) | RSZ_HOR(src_w - 1));
+	zx_writel(rsz + RSZ_DEST_CFG, RSZ_VER(dst_h - 1) | RSZ_HOR(dst_w - 1));
+
+	zx_gl_rsz_set_update(zplane);
+}
+
+static void zx_gl_plane_atomic_update(struct drm_plane *plane,
+				      struct drm_plane_state *old_state)
+{
+	struct zx_plane *zplane = to_zx_plane(plane);
+	struct drm_framebuffer *fb = plane->state->fb;
+	struct drm_gem_cma_object *cma_obj;
+	void __iomem *layer = zplane->layer;
+	void __iomem *csc = zplane->csc;
+	void __iomem *hbsc = zplane->hbsc;
+	u32 src_x, src_y, src_w, src_h;
+	u32 dst_x, dst_y, dst_w, dst_h;
+	unsigned int depth, bpp;
+	uint32_t format;
+	dma_addr_t paddr;
+	u32 stride;
+	int fmt;
+
+	if (!fb)
+		return;
+
+	format = fb->pixel_format;
+	stride = fb->pitches[0];
+
+	src_x = plane->state->src_x >> 16;
+	src_y = plane->state->src_y >> 16;
+	src_w = plane->state->src_w >> 16;
+	src_h = plane->state->src_h >> 16;
+
+	dst_x = plane->state->crtc_x;
+	dst_y = plane->state->crtc_y;
+	dst_w = plane->state->crtc_w;
+	dst_h = plane->state->crtc_h;
+
+	drm_fb_get_bpp_depth(format, &depth, &bpp);
+
+	cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+	paddr = cma_obj->paddr + fb->offsets[0];
+	paddr += src_y * stride + src_x * bpp / 8;
+	zx_writel(layer + GL_ADDR, paddr);
+
+	/* Set up source height/width register */
+	zx_writel(layer + GL_SRC_SIZE, GL_SRC_W(src_w) | GL_SRC_H(src_h));
+
+	/* Set up start position register */
+	zx_writel(layer + GL_POS_START, GL_POS_X(dst_x) | GL_POS_Y(dst_y));
+
+	/* Set up end position register */
+	zx_writel(layer + GL_POS_END,
+		  GL_POS_X(dst_x + dst_w) | GL_POS_Y(dst_y + dst_h));
+
+	/* Set up stride register */
+	zx_writel(layer + GL_STRIDE, stride & 0xffff);
+
+	/* Set up graphic layer data format */
+	fmt = zx_gl_get_fmt(format);
+	if (fmt >= 0)
+		zx_writel_mask(layer + GL_CTRL1, GL_DATA_FMT_MASK,
+			       fmt << GL_DATA_FMT_SHIFT);
+
+	/* Initialize global alpha with a sane value */
+	zx_writel_mask(layer + GL_CTRL2, GL_GLOBAL_ALPHA_MASK,
+		       0xff << GL_GLOBAL_ALPHA_SHIFT);
+
+	/* Setup CSC for the GL */
+	if (dst_h > 720)
+		zx_writel_mask(csc + CSC_CTRL0, CSC_COV_MODE_MASK,
+			       CSC_BT709_IMAGE_RGB2YCBCR << CSC_COV_MODE_SHIFT);
+	else
+		zx_writel_mask(csc + CSC_CTRL0, CSC_COV_MODE_MASK,
+			       CSC_BT601_IMAGE_RGB2YCBCR << CSC_COV_MODE_SHIFT);
+	zx_writel_mask(csc + CSC_CTRL0, CSC_WORK_ENABLE, CSC_WORK_ENABLE);
+
+	/* Always use scaler since it exists (set for not bypass) */
+	zx_writel_mask(layer + GL_CTRL3, GL_SCALER_BYPASS_MODE,
+		       GL_SCALER_BYPASS_MODE);
+
+	zx_gl_rsz_setup(zplane, src_w, src_h, dst_w, dst_h);
+
+	/* Enable HBSC block */
+	zx_writel_mask(hbsc + HBSC_CTRL0, HBSC_CTRL_EN, HBSC_CTRL_EN);
+
+	zx_gl_set_update(zplane);
+}
+
+static const struct drm_plane_helper_funcs zx_gl_plane_helper_funcs = {
+	.atomic_check = zx_gl_plane_atomic_check,
+	.atomic_update = zx_gl_plane_atomic_update,
+};
+
+static void zx_plane_destroy(struct drm_plane *plane)
+{
+	drm_plane_helper_disable(plane);
+	drm_plane_cleanup(plane);
+}
+
+static const struct drm_plane_funcs zx_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.destroy = zx_plane_destroy,
+	.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 void zx_plane_hbsc_init(struct zx_plane *zplane)
+{
+	void __iomem *hbsc = zplane->hbsc;
+
+	/*
+	 *  Initialize HBSC block with a sane configuration per recommedation
+	 *  from ZTE BSP code.
+	 */
+	zx_writel(hbsc + HBSC_SATURATION, 0x200);
+	zx_writel(hbsc + HBSC_HUE, 0x0);
+	zx_writel(hbsc + HBSC_BRIGHT, 0x0);
+	zx_writel(hbsc + HBSC_CONTRAST, 0x200);
+
+	zx_writel(hbsc + HBSC_THRESHOLD_COL1, (0x3ac << 16) | 0x40);
+	zx_writel(hbsc + HBSC_THRESHOLD_COL2, (0x3c0 << 16) | 0x40);
+	zx_writel(hbsc + HBSC_THRESHOLD_COL3, (0x3c0 << 16) | 0x40);
+}
+
+struct drm_plane *zx_plane_init(struct drm_device *drm, struct device *dev,
+				struct zx_layer_data *data,
+				enum drm_plane_type type)
+{
+	const struct drm_plane_helper_funcs *helper;
+	struct zx_plane *zplane;
+	struct drm_plane *plane;
+	const uint32_t *formats;
+	unsigned int format_count;
+	int ret;
+
+	zplane = devm_kzalloc(dev, sizeof(*zplane), GFP_KERNEL);
+	if (!zplane)
+		return ERR_PTR(-ENOMEM);
+
+	plane = &zplane->plane;
+
+	zplane->layer = data->layer;
+	zplane->hbsc = data->hbsc;
+	zplane->csc = data->csc;
+	zplane->rsz = data->rsz;
+
+	zx_plane_hbsc_init(zplane);
+
+	switch (type) {
+	case DRM_PLANE_TYPE_PRIMARY:
+		helper = &zx_gl_plane_helper_funcs;
+		formats = gl_formats;
+		format_count = ARRAY_SIZE(gl_formats);
+		break;
+	case DRM_PLANE_TYPE_OVERLAY:
+		/* TODO: add video layer (vl) support */
+		break;
+	default:
+		return ERR_PTR(-ENODEV);
+	}
+
+	ret = drm_universal_plane_init(drm, plane, VOU_CRTC_MASK,
+				       &zx_plane_funcs, formats, format_count,
+				       type, NULL);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to init universal plane: %d\n", ret);
+		return ERR_PTR(ret);
+	}
+
+	drm_plane_helper_add(plane, helper);
+
+	return plane;
+}
diff --git a/drivers/gpu/drm/zte/zx_plane.h b/drivers/gpu/drm/zte/zx_plane.h
new file mode 100644
index 000000000000..2b82cd558d9d
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_plane.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * 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 __ZX_PLANE_H__
+#define __ZX_PLANE_H__
+
+struct zx_layer_data {
+	void __iomem *layer;
+	void __iomem *csc;
+	void __iomem *hbsc;
+	void __iomem *rsz;
+};
+
+struct drm_plane *zx_plane_init(struct drm_device *drm, struct device *dev,
+				struct zx_layer_data *data,
+				enum drm_plane_type type);
+void zx_plane_set_update(struct drm_plane *plane);
+
+#endif /* __ZX_PLANE_H__ */
diff --git a/drivers/gpu/drm/zte/zx_plane_regs.h b/drivers/gpu/drm/zte/zx_plane_regs.h
new file mode 100644
index 000000000000..3dde6716a558
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_plane_regs.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * 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 __ZX_PLANE_REGS_H__
+#define __ZX_PLANE_REGS_H__
+
+/* GL registers */
+#define GL_CTRL0			0x00
+#define GL_UPDATE			BIT(5)
+#define GL_CTRL1			0x04
+#define GL_DATA_FMT_SHIFT		0
+#define GL_DATA_FMT_MASK		(0xf << GL_DATA_FMT_SHIFT)
+#define GL_FMT_ARGB8888			0
+#define GL_FMT_RGB888			1
+#define GL_FMT_RGB565			2
+#define GL_FMT_ARGB1555			3
+#define GL_FMT_ARGB4444			4
+#define GL_CTRL2			0x08
+#define GL_GLOBAL_ALPHA_SHIFT		8
+#define GL_GLOBAL_ALPHA_MASK		(0xff << GL_GLOBAL_ALPHA_SHIFT)
+#define GL_CTRL3			0x0c
+#define GL_SCALER_BYPASS_MODE		BIT(0)
+#define GL_STRIDE			0x18
+#define GL_ADDR				0x1c
+#define GL_SRC_SIZE			0x38
+#define GL_SRC_W_SHIFT			16
+#define GL_SRC_W_MASK			(0x3fff << GL_SRC_W_SHIFT)
+#define GL_SRC_H_SHIFT			0
+#define GL_SRC_H_MASK			(0x3fff << GL_SRC_H_SHIFT)
+#define GL_POS_START			0x9c
+#define GL_POS_END			0xa0
+#define GL_POS_X_SHIFT			16
+#define GL_POS_X_MASK			(0x1fff << GL_POS_X_SHIFT)
+#define GL_POS_Y_SHIFT			0
+#define GL_POS_Y_MASK			(0x1fff << GL_POS_Y_SHIFT)
+
+#define GL_SRC_W(x)	(((x) << GL_SRC_W_SHIFT) & GL_SRC_W_MASK)
+#define GL_SRC_H(x)	(((x) << GL_SRC_H_SHIFT) & GL_SRC_H_MASK)
+#define GL_POS_X(x)	(((x) << GL_POS_X_SHIFT) & GL_POS_X_MASK)
+#define GL_POS_Y(x)	(((x) << GL_POS_Y_SHIFT) & GL_POS_Y_MASK)
+
+/* CSC registers */
+#define CSC_CTRL0			0x30
+#define CSC_COV_MODE_SHIFT		16
+#define CSC_COV_MODE_MASK		(0xffff << CSC_COV_MODE_SHIFT)
+#define CSC_BT601_IMAGE_RGB2YCBCR	0
+#define CSC_BT601_IMAGE_YCBCR2RGB	1
+#define CSC_BT601_VIDEO_RGB2YCBCR	2
+#define CSC_BT601_VIDEO_YCBCR2RGB	3
+#define CSC_BT709_IMAGE_RGB2YCBCR	4
+#define CSC_BT709_IMAGE_YCBCR2RGB	5
+#define CSC_BT709_VIDEO_RGB2YCBCR	6
+#define CSC_BT709_VIDEO_YCBCR2RGB	7
+#define CSC_BT2020_IMAGE_RGB2YCBCR	8
+#define CSC_BT2020_IMAGE_YCBCR2RGB	9
+#define CSC_BT2020_VIDEO_RGB2YCBCR	10
+#define CSC_BT2020_VIDEO_YCBCR2RGB	11
+#define CSC_WORK_ENABLE			BIT(0)
+
+/* RSZ registers */
+#define RSZ_SRC_CFG			0x00
+#define RSZ_DEST_CFG			0x04
+#define RSZ_ENABLE_CFG			0x14
+
+#define RSZ_VER_SHIFT			16
+#define RSZ_VER_MASK			(0xffff << RSZ_VER_SHIFT)
+#define RSZ_HOR_SHIFT			0
+#define RSZ_HOR_MASK			(0xffff << RSZ_HOR_SHIFT)
+
+#define RSZ_VER(x)	(((x) << RSZ_VER_SHIFT) & RSZ_VER_MASK)
+#define RSZ_HOR(x)	(((x) << RSZ_HOR_SHIFT) & RSZ_HOR_MASK)
+
+/* HBSC registers */
+#define HBSC_SATURATION			0x00
+#define HBSC_HUE			0x04
+#define HBSC_BRIGHT			0x08
+#define HBSC_CONTRAST			0x0c
+#define HBSC_THRESHOLD_COL1		0x10
+#define HBSC_THRESHOLD_COL2		0x14
+#define HBSC_THRESHOLD_COL3		0x18
+#define HBSC_CTRL0			0x28
+#define HBSC_CTRL_EN			BIT(2)
+
+#endif /* __ZX_PLANE_REGS_H__ */
diff --git a/drivers/gpu/drm/zte/zx_vou.c b/drivers/gpu/drm/zte/zx_vou.c
new file mode 100644
index 000000000000..73fe15c17c32
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_vou.c
@@ -0,0 +1,661 @@
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * 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_address.h>
+#include <video/videomode.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drmP.h>
+
+#include "zx_drm_drv.h"
+#include "zx_plane.h"
+#include "zx_vou.h"
+#include "zx_vou_regs.h"
+
+#define GL_NUM	2
+#define VL_NUM	3
+
+enum vou_chn_type {
+	VOU_CHN_MAIN,
+	VOU_CHN_AUX,
+};
+
+struct zx_crtc_regs {
+	u32 fir_active;
+	u32 fir_htiming;
+	u32 fir_vtiming;
+	u32 timing_shift;
+	u32 timing_pi_shift;
+};
+
+static const struct zx_crtc_regs main_crtc_regs = {
+	.fir_active = FIR_MAIN_ACTIVE,
+	.fir_htiming = FIR_MAIN_H_TIMING,
+	.fir_vtiming = FIR_MAIN_V_TIMING,
+	.timing_shift = TIMING_MAIN_SHIFT,
+	.timing_pi_shift = TIMING_MAIN_PI_SHIFT,
+};
+
+static const struct zx_crtc_regs aux_crtc_regs = {
+	.fir_active = FIR_AUX_ACTIVE,
+	.fir_htiming = FIR_AUX_H_TIMING,
+	.fir_vtiming = FIR_AUX_V_TIMING,
+	.timing_shift = TIMING_AUX_SHIFT,
+	.timing_pi_shift = TIMING_AUX_PI_SHIFT,
+};
+
+struct zx_crtc_bits {
+	u32 polarity_mask;
+	u32 polarity_shift;
+	u32 int_frame_mask;
+	u32 tc_enable;
+	u32 gl_enable;
+};
+
+static const struct zx_crtc_bits main_crtc_bits = {
+	.polarity_mask = MAIN_POL_MASK,
+	.polarity_shift = MAIN_POL_SHIFT,
+	.int_frame_mask = TIMING_INT_MAIN_FRAME,
+	.tc_enable = MAIN_TC_EN,
+	.gl_enable = OSD_CTRL0_GL0_EN,
+};
+
+static const struct zx_crtc_bits aux_crtc_bits = {
+	.polarity_mask = AUX_POL_MASK,
+	.polarity_shift = AUX_POL_SHIFT,
+	.int_frame_mask = TIMING_INT_AUX_FRAME,
+	.tc_enable = AUX_TC_EN,
+	.gl_enable = OSD_CTRL0_GL1_EN,
+};
+
+struct zx_crtc {
+	struct drm_crtc crtc;
+	struct drm_plane *primary;
+	struct zx_vou_hw *vou;
+	void __iomem *chnreg;
+	const struct zx_crtc_regs *regs;
+	const struct zx_crtc_bits *bits;
+	enum vou_chn_type chn_type;
+	struct clk *pixclk;
+};
+
+#define to_zx_crtc(x) container_of(x, struct zx_crtc, crtc)
+
+struct zx_vou_hw {
+	struct device *dev;
+	void __iomem *osd;
+	void __iomem *timing;
+	void __iomem *vouctl;
+	void __iomem *otfppu;
+	void __iomem *dtrc;
+	struct clk *axi_clk;
+	struct clk *ppu_clk;
+	struct clk *main_clk;
+	struct clk *aux_clk;
+	struct zx_crtc *main_crtc;
+	struct zx_crtc *aux_crtc;
+};
+
+static inline struct zx_vou_hw *crtc_to_vou(struct drm_crtc *crtc)
+{
+	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
+
+	return zcrtc->vou;
+}
+
+void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc)
+{
+	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
+	struct zx_vou_hw *vou = zcrtc->vou;
+	bool is_main = zcrtc->chn_type == VOU_CHN_MAIN;
+	u32 data_sel_shift = inf->id << 1;
+
+	/* Select data format */
+	zx_writel_mask(vou->vouctl + VOU_INF_DATA_SEL, 0x3 << data_sel_shift,
+		       inf->data_sel << data_sel_shift);
+
+	/* Select channel */
+	zx_writel_mask(vou->vouctl + VOU_INF_CH_SEL, 0x1 << inf->id,
+		       zcrtc->chn_type << inf->id);
+
+	/* Select interface clocks */
+	zx_writel_mask(vou->vouctl + VOU_CLK_SEL, inf->clocks_sel_bits,
+		       is_main ? 0 : inf->clocks_sel_bits);
+
+	/* Enable interface clocks */
+	zx_writel_mask(vou->vouctl + VOU_CLK_EN, inf->clocks_en_bits,
+		       inf->clocks_en_bits);
+
+	/* Enable the device */
+	zx_writel_mask(vou->vouctl + VOU_INF_EN, 1 << inf->id, 1 << inf->id);
+}
+
+void vou_inf_disable(const struct vou_inf *inf, struct drm_crtc *crtc)
+{
+	struct zx_vou_hw *vou = crtc_to_vou(crtc);
+
+	/* Disable the device */
+	zx_writel_mask(vou->vouctl + VOU_INF_EN, 1 << inf->id, 0);
+
+	/* Disable interface clocks */
+	zx_writel_mask(vou->vouctl + VOU_CLK_EN, inf->clocks_en_bits, 0);
+}
+
+static inline void vou_chn_set_update(struct zx_crtc *zcrtc)
+{
+	zx_writel(zcrtc->chnreg + CHN_UPDATE, 1);
+}
+
+static void zx_crtc_enable(struct drm_crtc *crtc)
+{
+	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
+	struct zx_vou_hw *vou = zcrtc->vou;
+	const struct zx_crtc_regs *regs = zcrtc->regs;
+	const struct zx_crtc_bits *bits = zcrtc->bits;
+	struct videomode vm;
+	u32 pol = 0;
+	u32 val;
+	int ret;
+
+	drm_display_mode_to_videomode(mode, &vm);
+
+	/* Set up timing parameters */
+	val = V_ACTIVE(vm.vactive - 1);
+	val |= H_ACTIVE(vm.hactive - 1);
+	zx_writel(vou->timing + regs->fir_active, val);
+
+	val = SYNC_WIDE(vm.hsync_len - 1);
+	val |= BACK_PORCH(vm.hback_porch - 1);
+	val |= FRONT_PORCH(vm.hfront_porch - 1);
+	zx_writel(vou->timing + regs->fir_htiming, val);
+
+	val = SYNC_WIDE(vm.vsync_len - 1);
+	val |= BACK_PORCH(vm.vback_porch - 1);
+	val |= FRONT_PORCH(vm.vfront_porch - 1);
+	zx_writel(vou->timing + regs->fir_vtiming, val);
+
+	/* Set up polarities */
+	if (vm.flags & DISPLAY_FLAGS_VSYNC_LOW)
+		pol |= 1 << POL_VSYNC_SHIFT;
+	if (vm.flags & DISPLAY_FLAGS_HSYNC_LOW)
+		pol |= 1 << POL_HSYNC_SHIFT;
+
+	zx_writel_mask(vou->timing + TIMING_CTRL, bits->polarity_mask,
+		       pol << bits->polarity_shift);
+
+	/* Setup SHIFT register by following what ZTE BSP does */
+	zx_writel(vou->timing + regs->timing_shift, H_SHIFT_VAL);
+	zx_writel(vou->timing + regs->timing_pi_shift, H_PI_SHIFT_VAL);
+
+	/* Enable TIMING_CTRL */
+	zx_writel_mask(vou->timing + TIMING_TC_ENABLE, bits->tc_enable,
+		       bits->tc_enable);
+
+	/* Configure channel screen size */
+	zx_writel_mask(zcrtc->chnreg + CHN_CTRL1, CHN_SCREEN_W_MASK,
+		       vm.hactive << CHN_SCREEN_W_SHIFT);
+	zx_writel_mask(zcrtc->chnreg + CHN_CTRL1, CHN_SCREEN_H_MASK,
+		       vm.vactive << CHN_SCREEN_H_SHIFT);
+
+	/* Update channel */
+	vou_chn_set_update(zcrtc);
+
+	/* Enable channel */
+	zx_writel_mask(zcrtc->chnreg + CHN_CTRL0, CHN_ENABLE, CHN_ENABLE);
+
+	/* Enable Graphic Layer */
+	zx_writel_mask(vou->osd + OSD_CTRL0, bits->gl_enable,
+		       bits->gl_enable);
+
+	drm_crtc_vblank_on(crtc);
+
+	ret = clk_set_rate(zcrtc->pixclk, mode->clock * 1000);
+	if (ret) {
+		DRM_DEV_ERROR(vou->dev, "failed to set pixclk rate: %d\n", ret);
+		return;
+	}
+
+	ret = clk_prepare_enable(zcrtc->pixclk);
+	if (ret)
+		DRM_DEV_ERROR(vou->dev, "failed to enable pixclk: %d\n", ret);
+}
+
+static void zx_crtc_disable(struct drm_crtc *crtc)
+{
+	struct zx_crtc *zcrtc = to_zx_crtc(crtc);
+	const struct zx_crtc_bits *bits = zcrtc->bits;
+	struct zx_vou_hw *vou = zcrtc->vou;
+
+	clk_disable_unprepare(zcrtc->pixclk);
+
+	drm_crtc_vblank_off(crtc);
+
+	/* Disable Graphic Layer */
+	zx_writel_mask(vou->osd + OSD_CTRL0, bits->gl_enable, 0);
+
+	/* Disable channel */
+	zx_writel_mask(zcrtc->chnreg + CHN_CTRL0, CHN_ENABLE, 0);
+
+	/* Disable TIMING_CTRL */
+	zx_writel_mask(vou->timing + TIMING_TC_ENABLE, bits->tc_enable, 0);
+}
+
+static void zx_crtc_atomic_flush(struct drm_crtc *crtc,
+				  struct drm_crtc_state *old_state)
+{
+	struct drm_pending_vblank_event *event = crtc->state->event;
+
+	if (!event)
+		return;
+
+	crtc->state->event = NULL;
+
+	spin_lock_irq(&crtc->dev->event_lock);
+	if (drm_crtc_vblank_get(crtc) == 0)
+		drm_crtc_arm_vblank_event(crtc, event);
+	else
+		drm_crtc_send_vblank_event(crtc, event);
+	spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static const struct drm_crtc_helper_funcs zx_crtc_helper_funcs = {
+	.enable = zx_crtc_enable,
+	.disable = zx_crtc_disable,
+	.atomic_flush = zx_crtc_atomic_flush,
+};
+
+static const struct drm_crtc_funcs zx_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,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+static int zx_crtc_init(struct drm_device *drm, struct zx_vou_hw *vou,
+			enum vou_chn_type chn_type)
+{
+	struct device *dev = vou->dev;
+	struct zx_layer_data data;
+	struct zx_crtc *zcrtc;
+	int ret;
+
+	zcrtc = devm_kzalloc(dev, sizeof(*zcrtc), GFP_KERNEL);
+	if (!zcrtc)
+		return -ENOMEM;
+
+	zcrtc->vou = vou;
+	zcrtc->chn_type = chn_type;
+
+	if (chn_type == VOU_CHN_MAIN) {
+		data.layer = vou->osd + MAIN_GL_OFFSET;
+		data.csc = vou->osd + MAIN_CSC_OFFSET;
+		data.hbsc = vou->osd + MAIN_HBSC_OFFSET;
+		data.rsz = vou->otfppu + MAIN_RSZ_OFFSET;
+		zcrtc->chnreg = vou->osd + OSD_MAIN_CHN;
+		zcrtc->regs = &main_crtc_regs;
+		zcrtc->bits = &main_crtc_bits;
+	} else {
+		data.layer = vou->osd + AUX_GL_OFFSET;
+		data.csc = vou->osd + AUX_CSC_OFFSET;
+		data.hbsc = vou->osd + AUX_HBSC_OFFSET;
+		data.rsz = vou->otfppu + AUX_RSZ_OFFSET;
+		zcrtc->chnreg = vou->osd + OSD_AUX_CHN;
+		zcrtc->regs = &aux_crtc_regs;
+		zcrtc->bits = &aux_crtc_bits;
+	}
+
+	zcrtc->pixclk = devm_clk_get(dev, (chn_type == VOU_CHN_MAIN) ?
+					  "main_wclk" : "aux_wclk");
+	if (IS_ERR(zcrtc->pixclk)) {
+		ret = PTR_ERR(zcrtc->pixclk);
+		DRM_DEV_ERROR(dev, "failed to get pix clk: %d\n", ret);
+		return ret;
+	}
+
+	zcrtc->primary = zx_plane_init(drm, dev, &data, DRM_PLANE_TYPE_PRIMARY);
+	if (IS_ERR(zcrtc->primary)) {
+		ret = PTR_ERR(zcrtc->primary);
+		DRM_DEV_ERROR(dev, "failed to init primary plane: %d\n", ret);
+		return ret;
+	}
+
+	ret = drm_crtc_init_with_planes(drm, &zcrtc->crtc, zcrtc->primary, NULL,
+					&zx_crtc_funcs, NULL);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to init drm crtc: %d\n", ret);
+		return ret;
+	}
+
+	drm_crtc_helper_add(&zcrtc->crtc, &zx_crtc_helper_funcs);
+
+	if (chn_type == VOU_CHN_MAIN)
+		vou->main_crtc = zcrtc;
+	else
+		vou->aux_crtc = zcrtc;
+
+	return 0;
+}
+
+static inline struct drm_crtc *zx_find_crtc(struct drm_device *drm, int pipe)
+{
+	struct drm_crtc *crtc;
+
+	list_for_each_entry(crtc, &drm->mode_config.crtc_list, head)
+		if (crtc->index == pipe)
+			return crtc;
+
+	return NULL;
+}
+
+int zx_vou_enable_vblank(struct drm_device *drm, unsigned int pipe)
+{
+	struct drm_crtc *crtc;
+	struct zx_crtc *zcrtc;
+	struct zx_vou_hw *vou;
+	u32 int_frame_mask;
+
+	crtc = zx_find_crtc(drm, pipe);
+	if (!crtc)
+		return 0;
+
+	vou = crtc_to_vou(crtc);
+	zcrtc = to_zx_crtc(crtc);
+	int_frame_mask = zcrtc->bits->int_frame_mask;
+
+	zx_writel_mask(vou->timing + TIMING_INT_CTRL, int_frame_mask,
+		       int_frame_mask);
+
+	return 0;
+}
+
+void zx_vou_disable_vblank(struct drm_device *drm, unsigned int pipe)
+{
+	struct drm_crtc *crtc;
+	struct zx_crtc *zcrtc;
+	struct zx_vou_hw *vou;
+
+	crtc = zx_find_crtc(drm, pipe);
+	if (!crtc)
+		return;
+
+	vou = crtc_to_vou(crtc);
+	zcrtc = to_zx_crtc(crtc);
+
+	zx_writel_mask(vou->timing + TIMING_INT_CTRL,
+		       zcrtc->bits->int_frame_mask, 0);
+}
+
+static irqreturn_t vou_irq_handler(int irq, void *dev_id)
+{
+	struct zx_vou_hw *vou = dev_id;
+	u32 state;
+
+	/* Handle TIMING_CTRL frame interrupts */
+	state = zx_readl(vou->timing + TIMING_INT_STATE);
+	zx_writel(vou->timing + TIMING_INT_STATE, state);
+
+	if (state & TIMING_INT_MAIN_FRAME)
+		drm_crtc_handle_vblank(&vou->main_crtc->crtc);
+
+	if (state & TIMING_INT_AUX_FRAME)
+		drm_crtc_handle_vblank(&vou->aux_crtc->crtc);
+
+	/* Handle OSD interrupts */
+	state = zx_readl(vou->osd + OSD_INT_STA);
+	zx_writel(vou->osd + OSD_INT_CLRSTA, state);
+
+	if (state & OSD_INT_MAIN_UPT) {
+		vou_chn_set_update(vou->main_crtc);
+		zx_plane_set_update(vou->main_crtc->primary);
+	}
+
+	if (state & OSD_INT_AUX_UPT) {
+		vou_chn_set_update(vou->aux_crtc);
+		zx_plane_set_update(vou->aux_crtc->primary);
+	}
+
+	if (state & OSD_INT_ERROR)
+		DRM_DEV_ERROR(vou->dev, "OSD ERROR: 0x%08x!\n", state);
+
+	return IRQ_HANDLED;
+}
+
+static void vou_dtrc_init(struct zx_vou_hw *vou)
+{
+	/* Clear bit for bypass by ID */
+	zx_writel_mask(vou->dtrc + DTRC_DETILE_CTRL,
+		       TILE2RASTESCAN_BYPASS_MODE, 0);
+
+	/* Select ARIDR mode */
+	zx_writel_mask(vou->dtrc + DTRC_DETILE_CTRL, DETILE_ARIDR_MODE_MASK,
+		       DETILE_ARID_IN_ARIDR);
+
+	/* Bypass decompression for both frames */
+	zx_writel_mask(vou->dtrc + DTRC_F0_CTRL, DTRC_DECOMPRESS_BYPASS,
+		       DTRC_DECOMPRESS_BYPASS);
+	zx_writel_mask(vou->dtrc + DTRC_F1_CTRL, DTRC_DECOMPRESS_BYPASS,
+		       DTRC_DECOMPRESS_BYPASS);
+
+	/* Set up ARID register */
+	zx_writel(vou->dtrc + DTRC_ARID, DTRC_ARID3(0xf) | DTRC_ARID2(0xe) |
+		  DTRC_ARID1(0xf) | DTRC_ARID0(0xe));
+}
+
+static void vou_hw_init(struct zx_vou_hw *vou)
+{
+	/* Set GL0 to main channel and GL1 to aux channel */
+	zx_writel_mask(vou->osd + OSD_CTRL0, OSD_CTRL0_GL0_SEL, 0);
+	zx_writel_mask(vou->osd + OSD_CTRL0, OSD_CTRL0_GL1_SEL,
+		       OSD_CTRL0_GL1_SEL);
+
+	/* Release reset for all VOU modules */
+	zx_writel(vou->vouctl + VOU_SOFT_RST, ~0);
+
+	/* Select main clock for GL0 and aux clock for GL1 module */
+	zx_writel_mask(vou->vouctl + VOU_CLK_SEL, VOU_CLK_GL0_SEL, 0);
+	zx_writel_mask(vou->vouctl + VOU_CLK_SEL, VOU_CLK_GL1_SEL,
+		       VOU_CLK_GL1_SEL);
+
+	/* Enable clock auto-gating for all VOU modules */
+	zx_writel(vou->vouctl + VOU_CLK_REQEN, ~0);
+
+	/* Enable all VOU module clocks */
+	zx_writel(vou->vouctl + VOU_CLK_EN, ~0);
+
+	/* Clear both OSD and TIMING_CTRL interrupt state */
+	zx_writel(vou->osd + OSD_INT_CLRSTA, ~0);
+	zx_writel(vou->timing + TIMING_INT_STATE, ~0);
+
+	/* Enable OSD and TIMING_CTRL interrrupts */
+	zx_writel(vou->osd + OSD_INT_MSK, OSD_INT_ENABLE);
+	zx_writel(vou->timing + TIMING_INT_CTRL, TIMING_INT_ENABLE);
+
+	/* Select GPC as input to gl/vl scaler as a sane default setting */
+	zx_writel(vou->otfppu + OTFPPU_RSZ_DATA_SOURCE, 0x2a);
+
+	/*
+	 * Needs to reset channel and layer logic per frame when frame starts
+	 * to get VOU work properly.
+	 */
+	zx_writel_mask(vou->osd + OSD_RST_CLR, RST_PER_FRAME, RST_PER_FRAME);
+
+	vou_dtrc_init(vou);
+}
+
+static int zx_crtc_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct zx_vou_hw *vou;
+	struct resource *res;
+	int irq;
+	int ret;
+
+	vou = devm_kzalloc(dev, sizeof(*vou), GFP_KERNEL);
+	if (!vou)
+		return -ENOMEM;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "osd");
+	vou->osd = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vou->osd)) {
+		ret = PTR_ERR(vou->osd);
+		DRM_DEV_ERROR(dev, "failed to remap osd region: %d\n", ret);
+		return ret;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "timing_ctrl");
+	vou->timing = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vou->timing)) {
+		ret = PTR_ERR(vou->timing);
+		DRM_DEV_ERROR(dev, "failed to remap timing_ctrl region: %d\n",
+			      ret);
+		return ret;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dtrc");
+	vou->dtrc = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vou->dtrc)) {
+		ret = PTR_ERR(vou->dtrc);
+		DRM_DEV_ERROR(dev, "failed to remap dtrc region: %d\n", ret);
+		return ret;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vou_ctrl");
+	vou->vouctl = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vou->vouctl)) {
+		ret = PTR_ERR(vou->vouctl);
+		DRM_DEV_ERROR(dev, "failed to remap vou_ctrl region: %d\n",
+			      ret);
+		return ret;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otfppu");
+	vou->otfppu = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vou->otfppu)) {
+		ret = PTR_ERR(vou->otfppu);
+		DRM_DEV_ERROR(dev, "failed to remap otfppu region: %d\n", ret);
+		return ret;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	vou->axi_clk = devm_clk_get(dev, "aclk");
+	if (IS_ERR(vou->axi_clk)) {
+		ret = PTR_ERR(vou->axi_clk);
+		DRM_DEV_ERROR(dev, "failed to get axi_clk: %d\n", ret);
+		return ret;
+	}
+
+	vou->ppu_clk = devm_clk_get(dev, "ppu_wclk");
+	if (IS_ERR(vou->ppu_clk)) {
+		ret = PTR_ERR(vou->ppu_clk);
+		DRM_DEV_ERROR(dev, "failed to get ppu_clk: %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(vou->axi_clk);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to enable axi_clk: %d\n", ret);
+		return ret;
+	}
+
+	clk_prepare_enable(vou->ppu_clk);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to enable ppu_clk: %d\n", ret);
+		goto disable_axi_clk;
+	}
+
+	vou->dev = dev;
+	dev_set_drvdata(dev, vou);
+
+	vou_hw_init(vou);
+
+	ret = devm_request_irq(dev, irq, vou_irq_handler, 0, "zx_vou", vou);
+	if (ret < 0) {
+		DRM_DEV_ERROR(dev, "failed to request vou irq: %d\n", ret);
+		goto disable_ppu_clk;
+	}
+
+	ret = zx_crtc_init(drm, vou, VOU_CHN_MAIN);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to init main channel crtc: %d\n",
+			      ret);
+		goto disable_ppu_clk;
+	}
+
+	ret = zx_crtc_init(drm, vou, VOU_CHN_AUX);
+	if (ret) {
+		DRM_DEV_ERROR(dev, "failed to init aux channel crtc: %d\n",
+			      ret);
+		goto disable_ppu_clk;
+	}
+
+	return 0;
+
+disable_ppu_clk:
+	clk_disable_unprepare(vou->ppu_clk);
+disable_axi_clk:
+	clk_disable_unprepare(vou->axi_clk);
+	return ret;
+}
+
+static void zx_crtc_unbind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct zx_vou_hw *vou = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(vou->axi_clk);
+	clk_disable_unprepare(vou->ppu_clk);
+}
+
+static const struct component_ops zx_crtc_component_ops = {
+	.bind = zx_crtc_bind,
+	.unbind = zx_crtc_unbind,
+};
+
+static int zx_crtc_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &zx_crtc_component_ops);
+}
+
+static int zx_crtc_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &zx_crtc_component_ops);
+	return 0;
+}
+
+static const struct of_device_id zx_crtc_of_match[] = {
+	{ .compatible = "zte,zx296718-dpc", },
+	{ /* end */ },
+};
+MODULE_DEVICE_TABLE(of, zx_crtc_of_match);
+
+struct platform_driver zx_crtc_driver = {
+	.probe = zx_crtc_probe,
+	.remove = zx_crtc_remove,
+	.driver	= {
+		.name = "zx-crtc",
+		.of_match_table	= zx_crtc_of_match,
+	},
+};
diff --git a/drivers/gpu/drm/zte/zx_vou.h b/drivers/gpu/drm/zte/zx_vou.h
new file mode 100644
index 000000000000..349e06cd86f4
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_vou.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * 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 __ZX_VOU_H__
+#define __ZX_VOU_H__
+
+#define VOU_CRTC_MASK		0x3
+
+/* VOU output interfaces */
+enum vou_inf_id {
+	VOU_HDMI	= 0,
+	VOU_RGB_LCD	= 1,
+	VOU_TV_ENC	= 2,
+	VOU_MIPI_DSI	= 3,
+	VOU_LVDS	= 4,
+	VOU_VGA		= 5,
+};
+
+enum vou_inf_data_sel {
+	VOU_YUV444	= 0,
+	VOU_RGB_101010	= 1,
+	VOU_RGB_888	= 2,
+	VOU_RGB_666	= 3,
+};
+
+struct vou_inf {
+	enum vou_inf_id id;
+	enum vou_inf_data_sel data_sel;
+	u32 clocks_en_bits;
+	u32 clocks_sel_bits;
+};
+
+void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc);
+void vou_inf_disable(const struct vou_inf *inf, struct drm_crtc *crtc);
+
+int zx_vou_enable_vblank(struct drm_device *drm, unsigned int pipe);
+void zx_vou_disable_vblank(struct drm_device *drm, unsigned int pipe);
+
+#endif /* __ZX_VOU_H__ */
diff --git a/drivers/gpu/drm/zte/zx_vou_regs.h b/drivers/gpu/drm/zte/zx_vou_regs.h
new file mode 100644
index 000000000000..f44e7a4ae441
--- /dev/null
+++ b/drivers/gpu/drm/zte/zx_vou_regs.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2016 Linaro Ltd.
+ * Copyright 2016 ZTE Corporation.
+ *
+ * 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 __ZX_VOU_REGS_H__
+#define __ZX_VOU_REGS_H__
+
+/* Sub-module offset */
+#define MAIN_GL_OFFSET			0x130
+#define MAIN_CSC_OFFSET			0x580
+#define MAIN_HBSC_OFFSET		0x820
+#define MAIN_RSZ_OFFSET			0x600 /* OTFPPU sub-module */
+
+#define AUX_GL_OFFSET			0x200
+#define AUX_CSC_OFFSET			0x5d0
+#define AUX_HBSC_OFFSET			0x860
+#define AUX_RSZ_OFFSET			0x800
+
+/* OSD (GPC_GLOBAL) registers */
+#define OSD_INT_STA			0x04
+#define OSD_INT_CLRSTA			0x08
+#define OSD_INT_MSK			0x0c
+#define OSD_INT_AUX_UPT			BIT(14)
+#define OSD_INT_MAIN_UPT		BIT(13)
+#define OSD_INT_GL1_LBW			BIT(10)
+#define OSD_INT_GL0_LBW			BIT(9)
+#define OSD_INT_VL2_LBW			BIT(8)
+#define OSD_INT_VL1_LBW			BIT(7)
+#define OSD_INT_VL0_LBW			BIT(6)
+#define OSD_INT_BUS_ERR			BIT(3)
+#define OSD_INT_CFG_ERR			BIT(2)
+#define OSD_INT_ERROR (\
+	OSD_INT_GL1_LBW | OSD_INT_GL0_LBW | \
+	OSD_INT_VL2_LBW | OSD_INT_VL1_LBW | OSD_INT_VL0_LBW | \
+	OSD_INT_BUS_ERR | OSD_INT_CFG_ERR \
+)
+#define OSD_INT_ENABLE (OSD_INT_ERROR | OSD_INT_AUX_UPT | OSD_INT_MAIN_UPT)
+#define OSD_CTRL0			0x10
+#define OSD_CTRL0_GL0_EN		BIT(7)
+#define OSD_CTRL0_GL0_SEL		BIT(6)
+#define OSD_CTRL0_GL1_EN		BIT(5)
+#define OSD_CTRL0_GL1_SEL		BIT(4)
+#define OSD_RST_CLR			0x1c
+#define RST_PER_FRAME			BIT(19)
+
+/* Main/Aux channel registers */
+#define OSD_MAIN_CHN			0x470
+#define OSD_AUX_CHN			0x4d0
+#define CHN_CTRL0			0x00
+#define CHN_ENABLE			BIT(0)
+#define CHN_CTRL1			0x04
+#define CHN_SCREEN_W_SHIFT		18
+#define CHN_SCREEN_W_MASK		(0x1fff << CHN_SCREEN_W_SHIFT)
+#define CHN_SCREEN_H_SHIFT		5
+#define CHN_SCREEN_H_MASK		(0x1fff << CHN_SCREEN_H_SHIFT)
+#define CHN_UPDATE			0x08
+
+/* TIMING_CTRL registers */
+#define TIMING_TC_ENABLE		0x04
+#define AUX_TC_EN			BIT(1)
+#define MAIN_TC_EN			BIT(0)
+#define FIR_MAIN_ACTIVE			0x08
+#define FIR_AUX_ACTIVE			0x0c
+#define V_ACTIVE_SHIFT			16
+#define V_ACTIVE_MASK			(0xffff << V_ACTIVE_SHIFT)
+#define H_ACTIVE_SHIFT			0
+#define H_ACTIVE_MASK			(0xffff << H_ACTIVE_SHIFT)
+#define FIR_MAIN_H_TIMING		0x10
+#define FIR_MAIN_V_TIMING		0x14
+#define FIR_AUX_H_TIMING		0x18
+#define FIR_AUX_V_TIMING		0x1c
+#define SYNC_WIDE_SHIFT			22
+#define SYNC_WIDE_MASK			(0x3ff << SYNC_WIDE_SHIFT)
+#define BACK_PORCH_SHIFT		11
+#define BACK_PORCH_MASK			(0x7ff << BACK_PORCH_SHIFT)
+#define FRONT_PORCH_SHIFT		0
+#define FRONT_PORCH_MASK		(0x7ff << FRONT_PORCH_SHIFT)
+#define TIMING_CTRL			0x20
+#define AUX_POL_SHIFT			3
+#define AUX_POL_MASK			(0x7 << AUX_POL_SHIFT)
+#define MAIN_POL_SHIFT			0
+#define MAIN_POL_MASK			(0x7 << MAIN_POL_SHIFT)
+#define POL_DE_SHIFT			2
+#define POL_VSYNC_SHIFT			1
+#define POL_HSYNC_SHIFT			0
+#define TIMING_INT_CTRL			0x24
+#define TIMING_INT_STATE		0x28
+#define TIMING_INT_AUX_FRAME		BIT(3)
+#define TIMING_INT_MAIN_FRAME		BIT(1)
+#define TIMING_INT_AUX_FRAME_SEL_VSW	(0x2 << 10)
+#define TIMING_INT_MAIN_FRAME_SEL_VSW	(0x2 << 6)
+#define TIMING_INT_ENABLE (\
+	TIMING_INT_MAIN_FRAME_SEL_VSW | TIMING_INT_AUX_FRAME_SEL_VSW | \
+	TIMING_INT_MAIN_FRAME | TIMING_INT_AUX_FRAME \
+)
+#define TIMING_MAIN_SHIFT		0x2c
+#define TIMING_AUX_SHIFT		0x30
+#define H_SHIFT_VAL			0x0048
+#define TIMING_MAIN_PI_SHIFT		0x68
+#define TIMING_AUX_PI_SHIFT		0x6c
+#define H_PI_SHIFT_VAL			0x000f
+
+#define V_ACTIVE(x)	(((x) << V_ACTIVE_SHIFT) & V_ACTIVE_MASK)
+#define H_ACTIVE(x)	(((x) << H_ACTIVE_SHIFT) & H_ACTIVE_MASK)
+
+#define SYNC_WIDE(x)	(((x) << SYNC_WIDE_SHIFT) & SYNC_WIDE_MASK)
+#define BACK_PORCH(x)	(((x) << BACK_PORCH_SHIFT) & BACK_PORCH_MASK)
+#define FRONT_PORCH(x)	(((x) << FRONT_PORCH_SHIFT) & FRONT_PORCH_MASK)
+
+/* DTRC registers */
+#define DTRC_F0_CTRL			0x2c
+#define DTRC_F1_CTRL			0x5c
+#define DTRC_DECOMPRESS_BYPASS		BIT(17)
+#define DTRC_DETILE_CTRL		0x68
+#define TILE2RASTESCAN_BYPASS_MODE	BIT(30)
+#define DETILE_ARIDR_MODE_MASK		(0x3 << 0)
+#define DETILE_ARID_ALL			0
+#define DETILE_ARID_IN_ARIDR		1
+#define DETILE_ARID_BYP_BUT_ARIDR	2
+#define DETILE_ARID_IN_ARIDR2		3
+#define DTRC_ARID			0x6c
+#define DTRC_ARID3_SHIFT		24
+#define DTRC_ARID3_MASK			(0xff << DTRC_ARID3_SHIFT)
+#define DTRC_ARID2_SHIFT		16
+#define DTRC_ARID2_MASK			(0xff << DTRC_ARID2_SHIFT)
+#define DTRC_ARID1_SHIFT		8
+#define DTRC_ARID1_MASK			(0xff << DTRC_ARID1_SHIFT)
+#define DTRC_ARID0_SHIFT		0
+#define DTRC_ARID0_MASK			(0xff << DTRC_ARID0_SHIFT)
+#define DTRC_DEC2DDR_ARID		0x70
+
+#define DTRC_ARID3(x)	(((x) << DTRC_ARID3_SHIFT) & DTRC_ARID3_MASK)
+#define DTRC_ARID2(x)	(((x) << DTRC_ARID2_SHIFT) & DTRC_ARID2_MASK)
+#define DTRC_ARID1(x)	(((x) << DTRC_ARID1_SHIFT) & DTRC_ARID1_MASK)
+#define DTRC_ARID0(x)	(((x) << DTRC_ARID0_SHIFT) & DTRC_ARID0_MASK)
+
+/* VOU_CTRL registers */
+#define VOU_INF_EN			0x00
+#define VOU_INF_CH_SEL			0x04
+#define VOU_INF_DATA_SEL		0x08
+#define VOU_SOFT_RST			0x14
+#define VOU_CLK_SEL			0x18
+#define VOU_CLK_GL1_SEL			BIT(5)
+#define VOU_CLK_GL0_SEL			BIT(4)
+#define VOU_CLK_REQEN			0x20
+#define VOU_CLK_EN			0x24
+
+/* OTFPPU_CTRL registers */
+#define OTFPPU_RSZ_DATA_SOURCE		0x04
+
+#endif /* __ZX_VOU_REGS_H__ */
-- 
1.9.1

^ permalink raw reply related

* [PATCH v4 3/3] MAINTAINERS: add an entry for ZTE ZX DRM driver
From: Shawn Guo @ 2016-10-31  9:17 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1477905445-4983-1-git-send-email-shawnguo@kernel.org>

From: Shawn Guo <shawn.guo@linaro.org>

Add myself as the maintainer of ZTE ZX DRM driver.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 1cd38a7e0064..907dbd3261c5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4281,6 +4281,13 @@ S:	Maintained
 F:	drivers/gpu/drm/tilcdc/
 F:	Documentation/devicetree/bindings/display/tilcdc/
 
+DRM DRIVERS FOR ZTE ZX
+M:	Shawn Guo <shawnguo@kernel.org>
+L:	dri-devel at lists.freedesktop.org
+S:	Maintained
+F:	drivers/gpu/drm/zte/
+F:	Documentation/devicetree/bindings/display/zte,vou.txt
+
 DSBR100 USB FM RADIO DRIVER
 M:	Alexey Klimov <klimov.linux@gmail.com>
 L:	linux-media at vger.kernel.org
-- 
1.9.1

^ permalink raw reply related

* [PATCH v3] clkdev: Detect errors in clk_hw_register_clkdev() for mass registration
From: Russell King - ARM Linux @ 2016-10-31  9:22 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1477033358-7934-1-git-send-email-geert@linux-m68k.org>

On Fri, Oct 21, 2016 at 09:02:38AM +0200, Geert Uytterhoeven wrote:
> Unlike clk_register_clkdev(), clk_hw_register_clkdev() doesn't check for
> passed error objects from a previous registration call. Hence the caller
> of clk_hw_register_*() has to check for errors before calling
> clk_hw_register_clkdev*().
> 
> Make clk_hw_register_clkdev() more similar to clk_register_clkdev() by
> adding this error check, removing the burden from callers that do mass
> registration.
> 
> Fixes: e4f1b49bda6d6aa2 ("clkdev: Add clk_hw based registration APIs")
> Fixes: 944b9a41e004534f ("clk: ls1x: Migrate to clk_hw based OF and registration APIs")
> Fixes: 44ce9a9ae977736f ("MIPS: TXx9: Convert to Common Clock Framework")
> Fixes: f48d947a162dfa9d ("clk: clps711x: Migrate to clk_hw based OF and registration APIs")
> Fixes: b4626a7f489238a5 ("CLK: Add Loongson1C clock support")
> Signed-off-by: Geert Uytterhoeven <geert@linux-m68k.org>

Please put this in the patch system, thanks.

> ---
> v3:
>   - Add more Fixes tags for drivers not checking errors in v4.9-rc1,
> 
> v2:
>   - Correct wrong function references
>     s/clkdev_{,hw_}create/clk_{,hw_}register_clkdev/
> ---
>  drivers/clk/clkdev.c | 8 ++++++++
>  1 file changed, 8 insertions(+)
> 
> diff --git a/drivers/clk/clkdev.c b/drivers/clk/clkdev.c
> index 97ae60fa15849954..bb8a77a5985f8627 100644
> --- a/drivers/clk/clkdev.c
> +++ b/drivers/clk/clkdev.c
> @@ -448,12 +448,20 @@ int clk_register_clkdev(struct clk *clk, const char *con_id,
>   *
>   * con_id or dev_id may be NULL as a wildcard, just as in the rest of
>   * clkdev.
> + *
> + * To make things easier for mass registration, we detect error clk_hws
> + * from a previous clk_hw_register_*() call, and return the error code for
> + * those.  This is to permit this function to be called immediately
> + * after clk_hw_register_*().
>   */
>  int clk_hw_register_clkdev(struct clk_hw *hw, const char *con_id,
>  	const char *dev_id)
>  {
>  	struct clk_lookup *cl;
>  
> +	if (IS_ERR(hw))
> +		return PTR_ERR(hw);
> +
>  	/*
>  	 * Since dev_id can be NULL, and NULL is handled specially, we must
>  	 * pass it as either a NULL format string, or with "%s".
> -- 
> 1.9.1
> 

-- 
RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.

^ permalink raw reply

* [v15, 6/7] base: soc: introduce soc_device_match() interface
From: Y.B. Lu @ 2016-10-31  9:28 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <2572890.e6aV4hmMEL@wuerfel>

> -----Original Message-----
> From: Arnd Bergmann [mailto:arnd at arndb.de]
> Sent: Friday, October 28, 2016 6:48 PM
> To: linuxppc-dev at lists.ozlabs.org
> Cc: Y.B. Lu; linux-mmc at vger.kernel.org; ulf.hansson at linaro.org; Scott
> Wood; Mark Rutland; Greg Kroah-Hartman; X.B. Xie; M.H. Lian; linux-
> i2c at vger.kernel.org; linux-clk at vger.kernel.org; Qiang Zhao; Russell King;
> Bhupesh Sharma; Joerg Roedel; Claudiu Manoil; devicetree at vger.kernel.org;
> Rob Herring; Santosh Shilimkar; linux-arm-kernel at lists.infradead.org;
> netdev at vger.kernel.org; linux-kernel at vger.kernel.org; Leo Li;
> iommu at lists.linux-foundation.org; Kumar Gala; Geert Uytterhoeven
> Subject: Re: [v15, 6/7] base: soc: introduce soc_device_match() interface
> 
> On Friday, October 28, 2016 2:50:17 PM CEST Yangbo Lu wrote:
> > +
> > +static int soc_device_match_one(struct device *dev, void *arg) {
> > +       struct soc_device *soc_dev = container_of(dev, struct
> soc_device, dev);
> > +       const struct soc_device_attribute *match = arg;
> > +
> > +       if (match->machine &&
> > +           !glob_match(match->machine, soc_dev->attr->machine))
> > +               return 0;
> > +
> > +       if (match->family &&
> > +           !glob_match(match->family, soc_dev->attr->family))
> > +               return 0;
> > +
> >
> 
> Geert found a bug in my code, and submitted a fix at
> https://patchwork.kernel.org/patch/9361395/
> 
> I think you should include that one in your series.
> 

[Lu Yangbo-B47093] Ok, no problem. Thanks :)

> 	Arnd

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox